From 5a75e88f73751436caa4c969b77ac513ae835e39 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 29 Jul 2026 13:31:10 +0530 Subject: [PATCH] docs: move component reference to showcase --- app/docs.test.ts | 35 +- app/pages/404.wrn | 4 +- app/pages/access.wrn | 4 +- app/pages/architecture.wrn | 4 +- app/pages/benchmarks.wrn | 4 +- app/pages/changelog.wrn | 4 +- app/pages/components.wrn | 107 - app/pages/components/accordion.wrn | 232 - app/pages/components/advanced-date-picker.wrn | 51 - .../components/advanced-range-slider.wrn | 50 - app/pages/components/advanced-select.wrn | 493 - app/pages/components/alert.wrn | 175 - app/pages/components/avatar-group.wrn | 148 - app/pages/components/avatar.wrn | 117 - app/pages/components/badge.wrn | 135 - app/pages/components/blockquote.wrn | 96 - app/pages/components/breadcrumb.wrn | 46 - app/pages/components/button-group.wrn | 117 - app/pages/components/button.wrn | 191 - app/pages/components/card.wrn | 290 - app/pages/components/carousel.wrn | 490 - app/pages/components/chart.wrn | 49 - app/pages/components/chat-bubble.wrn | 175 - app/pages/components/checkbox.wrn | 91 - app/pages/components/clipboard.wrn | 49 - app/pages/components/collapse.wrn | 122 - app/pages/components/color-picker.wrn | 69 - app/pages/components/columns.wrn | 42 - app/pages/components/combo-box.wrn | 420 - app/pages/components/confetti.wrn | 48 - app/pages/components/container.wrn | 42 - app/pages/components/context-menu.wrn | 52 - app/pages/components/copy-markup.wrn | 53 - app/pages/components/custom-scrollbar.wrn | 43 - app/pages/components/data-map.wrn | 48 - app/pages/components/data-table.wrn | 55 - app/pages/components/date-picker.wrn | 102 - app/pages/components/device-frame.wrn | 63 - app/pages/components/divider.wrn | 38 - app/pages/components/drag-and-drop.wrn | 52 - app/pages/components/drawer.wrn | 51 - app/pages/components/dropdown.wrn | 53 - app/pages/components/file-input.wrn | 83 - app/pages/components/file-upload-progress.wrn | 72 - app/pages/components/file-upload.wrn | 53 - app/pages/components/grid.wrn | 42 - app/pages/components/image.wrn | 48 - app/pages/components/input-group.wrn | 80 - app/pages/components/input-number.wrn | 654 - app/pages/components/input.wrn | 120 - app/pages/components/kbd.wrn | 38 - app/pages/components/layout-splitter.wrn | 45 - app/pages/components/legend-indicator.wrn | 48 - app/pages/components/link.wrn | 46 - app/pages/components/list-group.wrn | 48 - app/pages/components/list.wrn | 48 - app/pages/components/map.wrn | 50 - app/pages/components/marquee.wrn | 48 - app/pages/components/mega-menu.wrn | 47 - app/pages/components/modal.wrn | 53 - app/pages/components/nav.wrn | 46 - app/pages/components/navbar.wrn | 47 - app/pages/components/pagination.wrn | 47 - app/pages/components/pin-input.wrn | 140 - app/pages/components/popover.wrn | 51 - app/pages/components/progress.wrn | 46 - app/pages/components/radio.wrn | 87 - app/pages/components/range-slider.wrn | 105 - app/pages/components/rating.wrn | 48 - app/pages/components/scrollspy.wrn | 45 - app/pages/components/search-box.wrn | 55 - app/pages/components/select.wrn | 90 - app/pages/components/sidebar.wrn | 47 - app/pages/components/skeleton.wrn | 42 - app/pages/components/spinner.wrn | 42 - app/pages/components/stepper.wrn | 48 - app/pages/components/strong-password.wrn | 264 - app/pages/components/styled-icon.wrn | 46 - app/pages/components/switch.wrn | 70 - app/pages/components/table.wrn | 53 - app/pages/components/tabs.wrn | 46 - app/pages/components/textarea.wrn | 82 - app/pages/components/time-picker.wrn | 106 - app/pages/components/timeline.wrn | 47 - app/pages/components/toast-notifications.wrn | 50 - app/pages/components/toast.wrn | 48 - app/pages/components/toggle-count.wrn | 264 - app/pages/components/toggle-password.wrn | 182 - app/pages/components/tooltip.wrn | 51 - app/pages/components/tree-view.wrn | 50 - app/pages/components/typography.wrn | 42 - app/pages/components/wysiwyg-editor.wrn | 50 - app/pages/examples.wrn | 4 +- app/pages/getting-started.wrn | 4 +- app/pages/guides/api-routes.wrn | 4 +- app/pages/guides/authentication.wrn | 4 +- app/pages/guides/authorization.wrn | 4 +- .../guides/configuration-and-profiles.wrn | 4 +- app/pages/guides/database.wrn | 4 +- app/pages/guides/deployment.wrn | 4 +- app/pages/guides/forms-and-validation.wrn | 4 +- app/pages/guides/i18n-and-themes.wrn | 4 +- app/pages/guides/middleware.wrn | 4 +- app/pages/guides/mobile.wrn | 4 +- app/pages/guides/observability.wrn | 4 +- app/pages/guides/pages-and-components.wrn | 4 +- app/pages/guides/project-structure.wrn | 4 +- app/pages/guides/pubsub.wrn | 4 +- app/pages/guides/queues.wrn | 4 +- app/pages/guides/realtime.wrn | 4 +- app/pages/guides/routing.wrn | 4 +- app/pages/guides/security.wrn | 4 +- app/pages/guides/server-data.wrn | 4 +- app/pages/guides/testing.wrn | 4 +- app/pages/guides/troubleshooting.wrn | 4 +- app/pages/guides/upgrading.wrn | 4 +- app/pages/guides/uploads.wrn | 4 +- app/pages/guides/workspaces-and-gateway.wrn | 4 +- app/pages/index.wrn | 4 +- app/pages/language.wrn | 4 +- app/pages/license.wrn | 4 +- app/pages/packages.wrn | 4 +- app/pages/packages/ai.wrn | 4 +- app/pages/packages/authz.wrn | 4 +- app/pages/packages/cli.wrn | 4 +- app/pages/packages/compiler.wrn | 4 +- app/pages/packages/core.wrn | 4 +- app/pages/packages/csr.wrn | 4 +- app/pages/packages/db.wrn | 4 +- app/pages/packages/dev-server.wrn | 4 +- app/pages/packages/encryption.wrn | 4 +- app/pages/packages/helpers.wrn | 4 +- app/pages/packages/i18n.wrn | 4 +- app/pages/packages/jwt.wrn | 4 +- app/pages/packages/mobile.wrn | 4 +- app/pages/packages/native.wrn | 4 +- app/pages/packages/oauth.wrn | 4 +- app/pages/packages/pubsub.wrn | 4 +- app/pages/packages/queue.wrn | 4 +- app/pages/packages/reactive.wrn | 4 +- app/pages/packages/router.wrn | 4 +- app/pages/packages/ssr.wrn | 4 +- app/pages/packages/styles.wrn | 4 +- app/pages/packages/test.wrn | 4 +- app/pages/packages/tracking.wrn | 4 +- app/pages/packages/ui.wrn | 6 +- app/pages/packages/uploader.wrn | 4 +- app/pages/packages/validation.wrn | 4 +- app/pages/releases/0.5.0.wrn | 4 +- app/pages/roadmap.wrn | 4 +- app/pages/search.wrn | 6 +- app/pages/security.wrn | 4 +- app/pages/showcase.wrn | 4 +- app/pages/support.wrn | 4 +- app/pages/tutorial.wrn | 4 +- public/docs-index.json | 602 - public/llms-full.txt | 17341 +++++++++- public/llms.txt | 28213 +--------------- public/sitemap.xml | 2 +- scripts/generate-docs.ts | 95 +- scripts/generate-portal.ts | 65 +- 161 files changed, 17507 insertions(+), 37637 deletions(-) delete mode 100644 app/pages/components.wrn delete mode 100644 app/pages/components/accordion.wrn delete mode 100644 app/pages/components/advanced-date-picker.wrn delete mode 100644 app/pages/components/advanced-range-slider.wrn delete mode 100644 app/pages/components/advanced-select.wrn delete mode 100644 app/pages/components/alert.wrn delete mode 100644 app/pages/components/avatar-group.wrn delete mode 100644 app/pages/components/avatar.wrn delete mode 100644 app/pages/components/badge.wrn delete mode 100644 app/pages/components/blockquote.wrn delete mode 100644 app/pages/components/breadcrumb.wrn delete mode 100644 app/pages/components/button-group.wrn delete mode 100644 app/pages/components/button.wrn delete mode 100644 app/pages/components/card.wrn delete mode 100644 app/pages/components/carousel.wrn delete mode 100644 app/pages/components/chart.wrn delete mode 100644 app/pages/components/chat-bubble.wrn delete mode 100644 app/pages/components/checkbox.wrn delete mode 100644 app/pages/components/clipboard.wrn delete mode 100644 app/pages/components/collapse.wrn delete mode 100644 app/pages/components/color-picker.wrn delete mode 100644 app/pages/components/columns.wrn delete mode 100644 app/pages/components/combo-box.wrn delete mode 100644 app/pages/components/confetti.wrn delete mode 100644 app/pages/components/container.wrn delete mode 100644 app/pages/components/context-menu.wrn delete mode 100644 app/pages/components/copy-markup.wrn delete mode 100644 app/pages/components/custom-scrollbar.wrn delete mode 100644 app/pages/components/data-map.wrn delete mode 100644 app/pages/components/data-table.wrn delete mode 100644 app/pages/components/date-picker.wrn delete mode 100644 app/pages/components/device-frame.wrn delete mode 100644 app/pages/components/divider.wrn delete mode 100644 app/pages/components/drag-and-drop.wrn delete mode 100644 app/pages/components/drawer.wrn delete mode 100644 app/pages/components/dropdown.wrn delete mode 100644 app/pages/components/file-input.wrn delete mode 100644 app/pages/components/file-upload-progress.wrn delete mode 100644 app/pages/components/file-upload.wrn delete mode 100644 app/pages/components/grid.wrn delete mode 100644 app/pages/components/image.wrn delete mode 100644 app/pages/components/input-group.wrn delete mode 100644 app/pages/components/input-number.wrn delete mode 100644 app/pages/components/input.wrn delete mode 100644 app/pages/components/kbd.wrn delete mode 100644 app/pages/components/layout-splitter.wrn delete mode 100644 app/pages/components/legend-indicator.wrn delete mode 100644 app/pages/components/link.wrn delete mode 100644 app/pages/components/list-group.wrn delete mode 100644 app/pages/components/list.wrn delete mode 100644 app/pages/components/map.wrn delete mode 100644 app/pages/components/marquee.wrn delete mode 100644 app/pages/components/mega-menu.wrn delete mode 100644 app/pages/components/modal.wrn delete mode 100644 app/pages/components/nav.wrn delete mode 100644 app/pages/components/navbar.wrn delete mode 100644 app/pages/components/pagination.wrn delete mode 100644 app/pages/components/pin-input.wrn delete mode 100644 app/pages/components/popover.wrn delete mode 100644 app/pages/components/progress.wrn delete mode 100644 app/pages/components/radio.wrn delete mode 100644 app/pages/components/range-slider.wrn delete mode 100644 app/pages/components/rating.wrn delete mode 100644 app/pages/components/scrollspy.wrn delete mode 100644 app/pages/components/search-box.wrn delete mode 100644 app/pages/components/select.wrn delete mode 100644 app/pages/components/sidebar.wrn delete mode 100644 app/pages/components/skeleton.wrn delete mode 100644 app/pages/components/spinner.wrn delete mode 100644 app/pages/components/stepper.wrn delete mode 100644 app/pages/components/strong-password.wrn delete mode 100644 app/pages/components/styled-icon.wrn delete mode 100644 app/pages/components/switch.wrn delete mode 100644 app/pages/components/table.wrn delete mode 100644 app/pages/components/tabs.wrn delete mode 100644 app/pages/components/textarea.wrn delete mode 100644 app/pages/components/time-picker.wrn delete mode 100644 app/pages/components/timeline.wrn delete mode 100644 app/pages/components/toast-notifications.wrn delete mode 100644 app/pages/components/toast.wrn delete mode 100644 app/pages/components/toggle-count.wrn delete mode 100644 app/pages/components/toggle-password.wrn delete mode 100644 app/pages/components/tooltip.wrn delete mode 100644 app/pages/components/tree-view.wrn delete mode 100644 app/pages/components/typography.wrn delete mode 100644 app/pages/components/wysiwyg-editor.wrn diff --git a/app/docs.test.ts b/app/docs.test.ts index eaeb5bec..356f49a0 100644 --- a/app/docs.test.ts +++ b/app/docs.test.ts @@ -60,25 +60,17 @@ test("every package page contains installation, guide, and complete API sections } }); -test("UI components have a dedicated catalog and complete detail pages", () => { +test("UI components use the dedicated external showcase", () => { const reference = JSON.parse( readFileSync(join(root, "node_modules", "@wrnexus", "ui", "component-reference.json"), "utf8"), ) as { count: number; components: Array<{ name: string; props: unknown[] }> }; - const catalog = readFileSync(join(root, "app", "pages", "components.wrn"), "utf8"); - const details = readdirSync(join(root, "app", "pages", "components")).filter((file) => - file.endsWith(".wrn"), - ); - expect(catalog).toContain(`@wrnexus/ui · ${reference.count} components`); - expect(catalog.match(/class="component-card"/g)).toHaveLength(reference.count); - expect(details).toHaveLength(reference.count); - const button = readFileSync(join(root, "app", "pages", "components", "button.wrn"), "utf8"); - expect(button).toContain("Props and attributes"); - expect(button).toContain("Requirement"); - expect(button).toContain("Default"); - expect(button).toContain('id="usage"'); - expect(button).toContain('id="source"'); + expect(existsSync(join(root, "app", "pages", "components.wrn"))).toBe(false); + expect( + readdirSync(join(root, "app", "pages", "components")).filter((file) => file.endsWith(".wrn")), + ).toHaveLength(0); const uiPackage = readFileSync(join(packagePages, "ui.wrn"), "utf8"); - expect(uiPackage).toContain('href="/components"'); + expect(uiPackage).toContain('href="https://component.wrnexusjs.dev/"'); + expect(reference.count).toBeGreaterThan(0); expect(uiPackage).not.toContain("All 901 UI components and props"); }); @@ -88,7 +80,9 @@ test("primary navigation exposes the component catalog", () => { join(root, "app", "pages", "getting-started.wrn"), join(root, "app", "pages", "packages", "core.wrn"), ]) { - expect(readFileSync(file, "utf8")).toContain('Components'); + expect(readFileSync(file, "utf8")).toContain( + 'Components', + ); } }); @@ -152,9 +146,14 @@ test("portal exposes truthful access and machine discovery", () => { const llms = readFileSync(join(root, "public", "llms.txt"), "utf8"); expect(llms).toContain(`# WRNexusJS documentation ${frameworkVersion}`); - expect(llms).toContain("# Installed package documentation"); - expect(llms).toContain("## @wrnexus/helpers"); + expect(llms).toContain("# Installed package index"); + expect(llms).toContain("- @wrnexus/helpers"); expect(llms).toContain("redirectToLogin"); + expect(llms).toContain("https://component.wrnexusjs.dev/"); + const llmsFull = readFileSync(join(root, "public", "llms-full.txt"), "utf8"); + expect(llmsFull).toContain("# Installed package documentation"); + expect(llmsFull).toContain("# Complete @wrnexus/ui component reference and source contracts"); + expect(llmsFull).toContain("### Complete .wrn source contract"); }); test("workspace guide documents app addition and safe forward-auth redirects", () => { diff --git a/app/pages/404.wrn b/app/pages/404.wrn index 543e4147..536a25b6 100644 --- a/app/pages/404.wrn +++ b/app/pages/404.wrn @@ -7,8 +7,8 @@ page Pagenotfound { view {
-
W WRNexusJS
- +
W WRNexusJS
+
diff --git a/app/pages/access.wrn b/app/pages/access.wrn index 2e60258e..f3ac9177 100644 --- a/app/pages/access.wrn +++ b/app/pages/access.wrn @@ -7,8 +7,8 @@ page Access { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Request preview access

WRNexusJS 0.5.0 packages are not available from the public npm registry. Installation requires approval and private registry credentials supplied by WorkRoot. Never paste registry tokens into source control, issue reports, or support messages.

Request process

  1. Contact WorkRoot through the public contact path at workroot.in.
  2. Describe the application, team, expected deployment, and Bun environment.
  3. After approval, follow the registry instructions supplied privately.
  4. Use the canonical scaffold command below only after authentication.
bunx @wrnexus/cli@0.5.0 create my-app

Access approval, response time, licensing terms, and support level remain owner-controlled. This site does not collect access requests directly.

diff --git a/app/pages/architecture.wrn b/app/pages/architecture.wrn index 3a6aaff2..b44e4e49 100644 --- a/app/pages/architecture.wrn +++ b/app/pages/architecture.wrn @@ -7,8 +7,8 @@ page Architecture { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Framework architecture · 0.5.0

Architecture

WRNexusJS is Bun-native and SSR-first. File discovery maps pages and API handlers; middleware enriches or short-circuits a request; the compiler turns .wrn declarations into server render functions and small feature runtimes.

Request and build lifecycle

  1. DiscoverPages, APIs, middleware, components, layouts, schemas, rooms
  2. CompileParse .wrn, validate grammar, generate server code
  3. RequestLimits, security, locale, session, middleware
  4. MatchStatic and dynamic file route
  5. RenderSSR data, escaped interpolation, components, metadata
  6. EnhanceOnly required reactive/directive runtimes

Package boundaries

core owns contexts, middleware, sessions and rooms; router discovers routes; compiler parses .wrn; ssr renders documents; csr supplies browser runtimes; dev-server and cli orchestrate development and builds.

Security and data flow

Validation happens at trust boundaries. Session authentication establishes identity; authorization makes resource decisions. CSP, Trusted Types, CSRF, upload checks, WebSocket origins, request limits, and output escaping are layered controls—not substitutes for application policy.

Realtime and scale

Rooms are process-local unless connected through pub/sub. Redis-backed pub/sub distributes events. Queue durability depends on the selected driver and must be evaluated explicitly.

Workspaces and gateways

The gateway can dispatch multiple applications while preserving route, component, asset, config, and session boundaries. Validate host routing and isolation before production.

Build and deployment

wrnexus build . produces dist/server.js and hashed/static assets. Run the server with Bun, apply migrations before traffic, terminate TLS at a trusted edge, and forward only expected proxy headers.

Mobile

Mobile compatibility bridges SSR-safe Capacitor capabilities; native route generation is experimental and is not general web portability. Test each target platform.

Generated files and limits

app/routes.gen.ts, .wrnexus/, and dist/ are generated. The runtime is Bun-only. Preview packages are private. Historical compatibility and long-term support policy are not yet published.

diff --git a/app/pages/benchmarks.wrn b/app/pages/benchmarks.wrn index 70a4bdd2..9733fb8f 100644 --- a/app/pages/benchmarks.wrn +++ b/app/pages/benchmarks.wrn @@ -7,8 +7,8 @@ page Benchmarks { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Benchmarks

No publishable benchmark dataset yet

No comparative speed claims are published. A valid baseline must record scripts, commit, Bun/framework versions, hardware, OS, warmup, samples, workload, raw results, median, percentiles, memory, HTML size, browser JavaScript size, and run date.

The roadmap starts with WRNexusJS-only measurements before any maintained equivalent-workload comparison.

diff --git a/app/pages/changelog.wrn b/app/pages/changelog.wrn index 12c0cfdf..7e106c88 100644 --- a/app/pages/changelog.wrn +++ b/app/pages/changelog.wrn @@ -7,8 +7,8 @@ page Changelog { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Changelog

0.5.0 2026-07-13

Documentation is aligned to all 31 installed packages. This release adds @wrnexus/helpers, original-request URL helpers, safe login redirects, working wrnexus workspace add, and forward-auth redirect propagation.

Migration notes

Run wrnexus update --latest and keep every @wrnexus/* package on 0.5.0. Existing applications must explicitly add @wrnexus/helpers before importing it; newly scaffolded applications include it automatically.

Versioning and support

The packages use semantic-looking versions, but a formal compatibility and old-release support policy has not been approved. Preview consumers should treat minor releases as potentially requiring migration review.

diff --git a/app/pages/components.wrn b/app/pages/components.wrn deleted file mode 100644 index 90179e5b..00000000 --- a/app/pages/components.wrn +++ /dev/null @@ -1,107 +0,0 @@ -page Components { - seo { - title = "Components" - description = "Browse all 85 themeable WRNexusJS UI components, props, slots, events, and examples." - } - state query = "" - state category = "All" - - view { -
- -
- W WRNexusJS - - -
- -
@wrnexus/ui · 85 components

Component reference

Production-ready, server-rendered components documented directly from the installed .wrn sources.

Showing {category} components

base

Accordion

Theme-aware, responsive accordion component.

17 props0 slots3 events
View component →
-integrations

AdvancedDatePicker

Theme-aware, responsive advanced date picker component.

7 props1 slots5 events
View component →
-integrations

AdvancedRangeSlider

Theme-aware, responsive advanced range slider component.

7 props1 slots4 events
View component →
-advanced-forms

AdvancedSelect

Theme-aware, responsive advanced select component.

51 props0 slots8 events
View component →
-base

Alert

Theme-aware, responsive alert component.

21 props0 slots2 events
View component →
-base

Avatar

Theme-aware, responsive avatar component.

18 props0 slots3 events
View component →
-base

AvatarGroup

Theme-aware, responsive avatar group component.

12 props0 slots1 events
View component →
-base

Badge

Theme-aware, responsive badge component.

22 props0 slots1 events
View component →
-base

Blockquote

Theme-aware, responsive blockquote component.

13 props1 slots0 events
View component →
-navigation

Breadcrumb

Theme-aware, responsive breadcrumb component.

7 props1 slots2 events
View component →
-base

Button

Theme-aware, responsive button component.

25 props1 slots3 events
View component →
-base

ButtonGroup

Theme-aware, responsive button group component.

13 props1 slots3 events
View component →
-base

Card

Theme-aware, responsive card component.

31 props1 slots6 events
View component →
-base

Carousel

Theme-aware, responsive carousel component.

22 props1 slots10 events
View component →
-integrations

Chart

Theme-aware, responsive chart component.

7 props1 slots3 events
View component →
-base

ChatBubble

Theme-aware, responsive chat bubble component.

11 props1 slots4 events
View component →
-forms

Checkbox

Theme-aware, responsive checkbox component.

27 props0 slots5 events
View component →
-integrations

Clipboard

Theme-aware, responsive clipboard component.

7 props1 slots3 events
View component →
-base

Collapse

Theme-aware, responsive collapse component.

8 props1 slots3 events
View component →
-forms

ColorPicker

Theme-aware, responsive color picker component.

19 props0 slots4 events
View component →
-layout

Columns

Theme-aware, responsive columns component.

6 props1 slots0 events
View component →
-advanced-forms

ComboBox

Editable autocomplete combobox with local and remote suggestions.

41 props0 slots8 events
View component →
-integrations

Confetti

Theme-aware, responsive confetti component.

7 props1 slots2 events
View component →
-layout

Container

Theme-aware, responsive container component.

6 props1 slots0 events
View component →
-overlays

ContextMenu

Theme-aware, responsive context menu component.

8 props1 slots3 events
View component →
-advanced-forms

CopyMarkup

Theme-aware, responsive copy markup component.

13 props0 slots3 events
View component →
-layout

CustomScrollbar

Theme-aware, responsive custom scrollbar component.

6 props1 slots1 events
View component →
-integrations

DataMap

Theme-aware, responsive data map component.

7 props1 slots2 events
View component →
-integrations

DataTable

Theme-aware, responsive data table component.

7 props1 slots5 events
View component →
-base

DatePicker

Theme-aware, responsive date picker component.

25 props0 slots7 events
View component →
-base

DeviceFrame

Theme-aware, responsive device frame component.

14 props1 slots2 events
View component →
-layout

Divider

Theme-aware, responsive divider component.

5 props0 slots0 events
View component →
-integrations

DragAndDrop

Theme-aware, responsive drag and drop component.

7 props1 slots6 events
View component →
-overlays

Drawer

Theme-aware, responsive drawer component.

8 props1 slots2 events
View component →
-overlays

Dropdown

Theme-aware, responsive dropdown component.

8 props1 slots4 events
View component →
-forms

FileInput

Theme-aware, responsive file input component.

21 props0 slots7 events
View component →
-integrations

FileUpload

Theme-aware, responsive file upload component.

7 props1 slots7 events
View component →
-base

FileUploadProgress

Theme-aware, responsive file upload progress component.

13 props0 slots3 events
View component →
-layout

Grid

Theme-aware, responsive grid component.

6 props1 slots0 events
View component →
-layout

Image

Theme-aware, responsive image component.

9 props1 slots2 events
View component →
-forms

Input

Theme-aware, responsive input component.

28 props0 slots7 events
View component →
-forms

InputGroup

Theme-aware, responsive input group component.

23 props0 slots6 events
View component →
-advanced-forms

InputNumber

Theme-aware, responsive input number component.

40 props0 slots4 events
View component →
-layout

Kbd

Theme-aware, responsive kbd component.

4 props1 slots0 events
View component →
-layout

LayoutSplitter

Theme-aware, responsive layout splitter component.

6 props1 slots3 events
View component →
-base

LegendIndicator

Theme-aware, responsive legend indicator component.

7 props1 slots2 events
View component →
-layout

Link

Theme-aware, responsive link component.

8 props1 slots3 events
View component →
-base

List

Theme-aware, responsive list component.

7 props1 slots2 events
View component →
-base

ListGroup

Theme-aware, responsive list group component.

7 props1 slots2 events
View component →
-integrations

Map

Theme-aware, responsive map component.

7 props1 slots4 events
View component →
-base

Marquee

Theme-aware, responsive marquee component.

7 props1 slots2 events
View component →
-navigation

MegaMenu

Theme-aware, responsive mega menu component.

7 props1 slots3 events
View component →
-overlays

Modal

Theme-aware, responsive modal component.

8 props1 slots4 events
View component →
-navigation

Nav

Theme-aware, responsive nav component.

7 props1 slots2 events
View component →
-navigation

Navbar

Theme-aware, responsive navbar component.

7 props1 slots3 events
View component →
-navigation

Pagination

Theme-aware, responsive pagination component.

7 props1 slots3 events
View component →
-advanced-forms

PinInput

Secure multi-cell PIN and verification-code input with regex and paste support.

26 props0 slots6 events
View component →
-overlays

Popover

Theme-aware, responsive popover component.

8 props1 slots2 events
View component →
-base

Progress

Theme-aware, responsive progress component.

7 props0 slots0 events
View component →
-forms

Radio

Theme-aware, responsive radio component.

25 props0 slots5 events
View component →
-forms

RangeSlider

Theme-aware, responsive range slider component.

26 props0 slots4 events
View component →
-base

Rating

Theme-aware, responsive rating component.

7 props1 slots2 events
View component →
-navigation

Scrollspy

Theme-aware, responsive scrollspy component.

7 props1 slots1 events
View component →
-advanced-forms

SearchBox

Theme-aware, responsive search box component.

13 props0 slots5 events
View component →
-forms

Select

Theme-aware, responsive select component.

22 props0 slots7 events
View component →
-navigation

Sidebar

Theme-aware, responsive sidebar component.

7 props1 slots3 events
View component →
-base

Skeleton

Theme-aware, responsive skeleton component.

5 props0 slots0 events
View component →
-base

Spinner

Theme-aware, responsive spinner component.

5 props0 slots0 events
View component →
-navigation

Stepper

Theme-aware, responsive stepper component.

7 props1 slots4 events
View component →
-advanced-forms

StrongPassword

Theme-aware, responsive strong password component.

27 props0 slots3 events
View component →
-base

StyledIcon

Theme-aware, responsive styled icon component.

7 props1 slots0 events
View component →
-forms

Switch

Theme-aware, responsive switch component.

20 props0 slots4 events
View component →
-tables

Table

Theme-aware, responsive table component.

7 props1 slots3 events
View component →
-navigation

Tabs

Theme-aware, responsive tabs component.

7 props1 slots2 events
View component →
-forms

Textarea

Theme-aware, responsive textarea component.

23 props0 slots5 events
View component →
-base

Timeline

Theme-aware, responsive timeline component.

7 props1 slots1 events
View component →
-forms

TimePicker

Theme-aware, responsive time picker component.

26 props0 slots7 events
View component →
-base

Toast

Theme-aware, responsive toast component.

7 props1 slots2 events
View component →
-integrations

ToastNotifications

Theme-aware, responsive toast notifications component.

7 props1 slots4 events
View component →
-advanced-forms

ToggleCount

Theme-aware, responsive toggle count component.

23 props0 slots2 events
View component →
-advanced-forms

TogglePassword

Accessible password field with optional show and hide controls.

24 props0 slots3 events
View component →
-overlays

Tooltip

Theme-aware, responsive tooltip component.

8 props1 slots2 events
View component →
-base

TreeView

Theme-aware, responsive tree view component.

7 props1 slots4 events
View component →
-layout

Typography

Theme-aware, responsive typography component.

6 props1 slots0 events
View component →
-integrations

WysiwygEditor

Theme-aware, responsive wysiwyg editor component.

7 props1 slots4 events
View component →
- - -
- } -} diff --git a/app/pages/components/accordion.wrn b/app/pages/components/accordion.wrn deleted file mode 100644 index 196d4c10..00000000 --- a/app/pages/components/accordion.wrn +++ /dev/null @@ -1,232 +0,0 @@ -page Accordioncomponent { - seo { - title = "Accordion component" - description = "Theme-aware, responsive accordion component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Accordion

Theme-aware, responsive accordion component.

@wrnexus/ui 0.5.017 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Accordion
-  size="default"
-  color="primary"
-  variant="default"
-  id="accordion"
-  items="[]"
-/>

Legacy mount name: data-component="Accordion".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
variantstringOptional"default"
classstringOptional""
idstringOptional"accordion"
itemsstringOptional[]
defaultOpenstringOptional[]
multiplebooleanOptionalfalse
alwaysOpenbooleanOptionalfalse
disabledbooleanOptionalfalse
indicatorstringOptional"plus"
indicatorPositionstringOptional"start"
showIndicatorbooleanOptionaltrue
borderedbooleanOptionalfalse
separatedbooleanOptionalfalse
flushbooleanOptionalfalse
contentItalicbooleanOptionalfalse

Slots

This component does not declare a slot.

Events

  • change
  • open
  • close

Source contract

Installed source: components/Accordion.wrn

component Accordion {
-  props {
-    size = "default"
-    color = "primary"
-    variant = "default"
-    class = ""
-
-    id = "accordion"
-    items = []
-    defaultOpen = []
-    multiple = false
-    alwaysOpen = false
-    disabled = false
-
-    indicator = "plus"
-    indicatorPosition = "start"
-    showIndicator = true
-    bordered = false
-    separated = false
-    flush = false
-    contentItalic = false
-
-    @event change = function
-    @event open = function
-    @event close = function
-  }
-
-  state openValues = defaultOpen
-
-  functions {
-    function itemValue(item, index) {
-      return item.value !== undefined && item.value !== ""
-        ? String(item.value)
-        : String(index)
-    }
-
-    function nestedValue(parent, parentIndex, item, index) {
-      return itemValue(parent, parentIndex) + "." + itemValue(item, index)
-    }
-
-    function isOpen(value) {
-      return openValues.includes(value)
-    }
-
-    function allowsMultiple() {
-      return multiple || alwaysOpen
-    }
-
-    function dispatchAccordionEvent(sourceEvent, eventName, value, item, root, customEvent) {
-      root = sourceEvent.currentTarget.closest("[data-wrn-accordion]")
-
-      if (!root) {
-        return
-      }
-
-      customEvent = document.createEvent("CustomEvent")
-      customEvent.initCustomEvent(eventName, true, false, {
-        component: "Accordion",
-        value: value,
-        item: item,
-        open: isOpen(value),
-        openValues: openValues
-      })
-      root.dispatchEvent(customEvent)
-    }
-
-    function toggleItem(sourceEvent, value, item, wasOpen) {
-      if (disabled || item.disabled) {
-        return
-      }
-
-      wasOpen = isOpen(value)
-
-      if (wasOpen) {
-        openValues = openValues.filter((entry) => entry !== value)
-      } else if (allowsMultiple()) {
-        openValues = openValues.concat([value])
-      } else {
-        openValues = [value]
-      }
-
-      dispatchAccordionEvent(
-        sourceEvent,
-        wasOpen ? "close" : "open",
-        value,
-        item
-      )
-      dispatchAccordionEvent(sourceEvent, "change", value, item)
-    }
-
-  }
-
-  view {
-        <div
-      {...attrs}
-      id="{id}"
-      data-wrn-accordion
-      data-variant="{variant}"
-      data-indicator="{indicator}"
-      data-multiple="{allowsMultiple() ? 'true' : 'false'}"
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--accordion wire-next--accordion-{variant} {bordered ? 'wire-next--accordion-bordered' : ''} {separated ? 'wire-next--accordion-separated' : ''} {flush ? 'wire-next--accordion-flush' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-    >
-      {#each items as item, index}
-        <section
-          class="wire-next__accordion-item"
-          data-open="{isOpen(itemValue(item, index)) ? 'true' : 'false'}"
-          data-disabled="{disabled || item.disabled ? 'true' : 'false'}"
-        >
-        <h3 class="wire-next__accordion-heading">
-        <button
-              type="button"
-              id="{id}-trigger-{index}"
-              aria-expanded="{isOpen(itemValue(item, index)) ? 'true' : 'false'}"
-              aria-controls="{id}-panel-{index}"
-              disabled="{disabled || item.disabled}"
-              @click="toggleItem(event, itemValue(item, index), item)"
-            >
-              {#if showIndicator && indicatorPosition === "start"}
-                <span class="wire-next__accordion-indicator" aria-hidden="true">
-                  {#if indicator === "chevron"}
-                    <span class="icon-[lucide--chevron-down]">
-        </span>
-                  {:else}
-                    <span>+</span>
-                  {/if}
-                </span>
-              {/if}
-              <span>{item.label || item.title}</span>
-              {#if showIndicator && indicatorPosition === "end"}
-                <span class="wire-next__accordion-indicator" aria-hidden="true">
-                  {#if indicator === "chevron"}
-                    <span class="icon-[lucide--chevron-down]">
-        </span>
-                  {:else}
-                    <span>+</span>
-                  {/if}
-                </span>
-              {/if}
-            </button>
-        </h3>
-        <div
-            id="{id}-panel-{index}"
-            class="wire-next__accordion-panel"
-            role="region"
-            aria-labelledby="{id}-trigger-{index}"
-            aria-hidden="{isOpen(itemValue(item, index)) ? 'false' : 'true'}"
-          >
-        <div>
-        <div class="wire-next__accordion-content {contentItalic ? 'wire-next__accordion-content-italic' : ''}">
-                {#if item.content}<p>{item.content}</p>{/if}
-
-                {#if item.children && item.children.length > 0}
-                  <div class="wire-next__accordion-nested">
-                    {#each item.children as child, childIndex}
-                      <section
-                        class="wire-next__accordion-item"
-                        data-open="{isOpen(nestedValue(item, index, child, childIndex)) ? 'true' : 'false'}"
-                      >
-        <h4 class="wire-next__accordion-heading">
-        <button
-                            type="button"
-                            id="{id}-trigger-{index}-{childIndex}"
-                            aria-expanded="{isOpen(nestedValue(item, index, child, childIndex)) ? 'true' : 'false'}"
-                            aria-controls="{id}-panel-{index}-{childIndex}"
-                            disabled="{disabled || child.disabled}"
-                            @click="toggleItem(event, nestedValue(item, index, child, childIndex), child)"
-                          >
-                            {#if showIndicator}
-                              <span class="wire-next__accordion-indicator" aria-hidden="true">
-                                {#if indicator === "chevron"}
-                                  <span class="icon-[lucide--chevron-down]">
-        </span>
-                                {:else}
-                                  <span>+</span>
-                                {/if}
-                              </span>
-                            {/if}
-                            <span>{child.label || child.title}</span>
-        </button>
-        </h4>
-        <div
-                          id="{id}-panel-{index}-{childIndex}"
-                          class="wire-next__accordion-panel"
-                          role="region"
-                          aria-labelledby="{id}-trigger-{index}-{childIndex}"
-                          aria-hidden="{isOpen(nestedValue(item, index, child, childIndex)) ? 'false' : 'true'}"
-                        >
-        <div>
-        <div class="wire-next__accordion-content {contentItalic ? 'wire-next__accordion-content-italic' : ''}">
-        <p>{child.content}</p>
-        </div>
-        </div>
-        </div>
-        </section>
-                    {/each}
-                  </div>
-                {/if}
-              </div>
-        </div>
-        </div>
-        </section>
-      {/each}
-    </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/advanced-date-picker.wrn b/app/pages/components/advanced-date-picker.wrn deleted file mode 100644 index 522bfdd6..00000000 --- a/app/pages/components/advanced-date-picker.wrn +++ /dev/null @@ -1,51 +0,0 @@ -page AdvancedDatePickercomponent { - seo { - title = "AdvancedDatePicker component" - description = "Theme-aware, responsive advanced date picker component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

AdvancedDatePicker

Theme-aware, responsive advanced date picker component.

@wrnexus/ui 0.5.07 props1 slots5 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<AdvancedDatePicker
-  size="default"
-  color="primary"
-  title="Advanced Date Picker"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="AdvancedDatePicker".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Advanced Date Picker"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • input
  • change
  • open
  • close
  • clear

Source contract

Installed source: components/AdvancedDatePicker.wrn

component AdvancedDatePicker {
-  props {
-    @event input = function
-    @event change = function
-    @event open = function
-    @event close = function
-    @event clear = function
-    size = "default"
-    color = "primary"
-    title = "Advanced Date Picker"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-date-picker wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/advanced-range-slider.wrn b/app/pages/components/advanced-range-slider.wrn deleted file mode 100644 index 23aded89..00000000 --- a/app/pages/components/advanced-range-slider.wrn +++ /dev/null @@ -1,50 +0,0 @@ -page AdvancedRangeSlidercomponent { - seo { - title = "AdvancedRangeSlider component" - description = "Theme-aware, responsive advanced range slider component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

AdvancedRangeSlider

Theme-aware, responsive advanced range slider component.

@wrnexus/ui 0.5.07 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<AdvancedRangeSlider
-  size="default"
-  color="primary"
-  title="Advanced Range Slider"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="AdvancedRangeSlider".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Advanced Range Slider"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • input
  • change
  • start
  • end

Source contract

Installed source: components/AdvancedRangeSlider.wrn

component AdvancedRangeSlider {
-  props {
-    @event input = function
-    @event change = function
-    @event start = function
-    @event end = function
-    size = "default"
-    color = "primary"
-    title = "Advanced Range Slider"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-range-slider wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/advanced-select.wrn b/app/pages/components/advanced-select.wrn deleted file mode 100644 index f93d055f..00000000 --- a/app/pages/components/advanced-select.wrn +++ /dev/null @@ -1,493 +0,0 @@ -page AdvancedSelectcomponent { - seo { - title = "AdvancedSelect component" - description = "Theme-aware, responsive advanced select component." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

AdvancedSelect

Theme-aware, responsive advanced select component.

@wrnexus/ui 0.5.051 props0 slots8 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<AdvancedSelect
-  size="default"
-  color="primary"
-  label="Advanced Select"
-  name="name"
-  value="value"
-/>

Legacy mount name: data-component="AdvancedSelect".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Advanced Select"
namestringOptional""
valuestringOptional""
valuesstringOptional[]
optionsstringOptional[]
groupsstringOptional[]
placeholderstringOptional"Select an option"
placeholderIconstringOptional""
searchPlaceholderstringOptional"Search options…"
multiplebooleanOptionalfalse
searchablebooleanOptionaltrue
defaultOpenbooleanOptionalfalse
clearablebooleanOptionaltrue
allowEmptybooleanOptionaltrue
tagsbooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
invalidbooleanOptionalfalse
validationMessagestringOptional""
helpTextstringOptional""
loadingbooleanOptionalfalse
loadingLabelstringOptional"Loading options…"
emptyLabelstringOptional"No options found"
selectedOptionsLabelstringOptional"Selected options"
clearLabelstringOptional"Clear selection"
createLabelstringOptional"Create"
loadMoreLabelstringOptional"Load more"
searchModestringOptional"contains"
searchFieldsstringOptional"label,description"
minSearchLengthnumberOptional0
searchResultLimitnumberOptional0
maxSelectionsnumberOptional0
showCounterbooleanOptionalfalse
counterTemplatestringOptional"{selected} selected"
optionTemplatestringOptional"default"
selectedTemplatestringOptional"default"
closeOnSelectbooleanOptionaltrue
scrollToSelectedbooleanOptionaltrue
fixedbooleanOptionalfalse
placementstringOptional"bottom"
remotebooleanOptionalfalse
remoteUrlstringOptional""
remoteQueryParamstringOptional"q"
remoteDebouncenumberOptional250
remoteAutoLoadbooleanOptionaltrue
infinitebooleanOptionalfalse
hasMorebooleanOptionalfalse
pagenumberOptional1
classstringOptional""

Slots

This component does not declare a slot.

Events

  • search
  • select
  • change
  • clear
  • open
  • close
  • load
  • error

Source contract

Installed source: components/AdvancedSelect.wrn

component AdvancedSelect {
-  props {
-    size = "default"
-    color = "primary"
-    label = "Advanced Select"
-    name = ""
-    value = ""
-    values = []
-    options = []
-    groups = []
-    placeholder = "Select an option"
-    placeholderIcon = ""
-    searchPlaceholder = "Search options…"
-    multiple = false
-    searchable = true
-    defaultOpen = false
-    clearable = true
-    allowEmpty = true
-    tags = false
-    disabled = false
-    required = false
-    invalid = false
-    validationMessage = ""
-    helpText = ""
-    loading = false
-    loadingLabel = "Loading options…"
-    emptyLabel = "No options found"
-    selectedOptionsLabel = "Selected options"
-    clearLabel = "Clear selection"
-    createLabel = "Create"
-    loadMoreLabel = "Load more"
-    searchMode = "contains"
-    searchFields = "label,description"
-    minSearchLength = 0
-    searchResultLimit = 0
-    maxSelections = 0
-    showCounter = false
-    counterTemplate = "{selected} selected"
-    optionTemplate = "default"
-    selectedTemplate = "default"
-    closeOnSelect = true
-    scrollToSelected = true
-    fixed = false
-    placement = "bottom"
-    remote = false
-    remoteUrl = ""
-    remoteQueryParam = "q"
-    remoteDebounce = 250
-    remoteAutoLoad = true
-    infinite = false
-    hasMore = false
-    page = 1
-    class = ""
-    @event search = function
-    @event select = function
-    @event change = function
-    @event clear = function
-    @event open = function
-    @event close = function
-    @event load = function
-    @event error = function
-  }
-
-  state open = defaultOpen
-  state query = ""
-  state activeIndex = -1
-  state selectedValue = value
-  state selectedValues = values
-
-  functions {
-    function allOptions() {
-      return [...groups.flatMap((group) => group.options || []), ...options]
-    }
-
-    function searchableText(option) {
-      return ((option.label || "") + " " + (option.description || "")).toLowerCase()
-    }
-
-    function matches(option) {
-      if (!query || query.length < Number(minSearchLength || 0)) {
-        return true
-      }
-      if (searchMode === "startsWith") {
-        return searchableText(option).startsWith(query.toLowerCase())
-      }
-      if (searchMode === "exact") {
-        return searchableText(option) === query.toLowerCase()
-      }
-      return searchableText(option).includes(query.toLowerCase())
-    }
-
-    function matchingOptions(list) {
-      return list.filter((option) => matches(option))
-    }
-
-    function visibleOptions(list) {
-      if (searchResultLimit > 0) {
-        return matchingOptions(list).slice(0, Number(searchResultLimit))
-      }
-      return matchingOptions(list)
-    }
-
-    function flatVisibleOptions() {
-      return visibleOptions(allOptions())
-    }
-
-    function isSelected(option) {
-      return (
-        multiple
-        ? selectedValues.includes(option.value)
-        : selectedValue === option.value
-      )
-    }
-
-    function selectedOptions() {
-      return allOptions().filter((option) => isSelected(option))
-    }
-
-    function selectedCount() {
-      return multiple ? selectedValues.length : selectedValue ? 1 : 0
-    }
-
-    function counterText() {
-      return (
-        maxSelections
-        ? selectedCount() + " / " + maxSelections + " selected"
-        : selectedCount() + " selected"
-      )
-    }
-
-    function selectedText() {
-      return (
-        multiple
-        ? selectedValues.join(", ")
-        : selectedOptions().length
-          ? selectedOptions()[0].label
-          : ""
-      )
-    }
-
-    function triggerText() {
-      return selectedCount() ? selectedText() : placeholder
-    }
-
-    function canSelect(option) {
-      if (option.disabled) {
-        return false
-      }
-      if (!multiple || isSelected(option)) {
-        return true
-      }
-      if (maxSelections <= 0) {
-        return true
-      }
-      return selectedCount() < maxSelections
-    }
-
-    function chooseSingle(option) {
-      if (!canSelect(option)) {
-        return
-      }
-      selectedValue = option.value
-      query = ""
-      if (closeOnSelect) {
-        open = false
-      }
-    }
-
-    function chooseMultiple(option) {
-      if (!canSelect(option)) {
-        return
-      }
-      if (isSelected(option)) {
-        selectedValues = selectedValues.filter((item) => item !== option.value)
-      } else {
-        selectedValues = selectedValues.concat([option.value])
-      }
-      query = ""
-    }
-
-    function chooseOption(option) {
-      if (multiple) {
-        chooseMultiple(option)
-        return
-      }
-      chooseSingle(option)
-    }
-
-    function clearSelection(event) {
-      event.stopPropagation()
-      selectedValue = ""
-      selectedValues = []
-      query = ""
-      open = false
-    }
-
-    function toggle() {
-      if (disabled) {
-        return;
-      };
-      open = !open;
-      activeIndex = open && flatVisibleOptions().length ? 0 : -1;
-    }
-
-    function moveActive(direction) {
-      if (!flatVisibleOptions().length) {
-        return
-      }
-      activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length
-    }
-
-    function handleKeydown(event) {
-      if (disabled) {
-        return
-      }
-      if (event.key === "ArrowDown") {
-        event.preventDefault()
-        if (!open) {
-          open = true
-        }
-        moveActive(1)
-      } else if (event.key === "ArrowUp") {
-        event.preventDefault()
-        if (!open) {
-          open = true
-        }
-        moveActive(-1)
-      } else if (event.key === "Enter" || event.key === " ") {
-        event.preventDefault()
-        if (!open) {
-          open = true
-        } else if (activeIndex >= 0) {
-          chooseOption(flatVisibleOptions()[activeIndex])
-        }
-      } else if (event.key === "Escape") {
-        open = false
-      } else if (event.key === "Home" && open) {
-        event.preventDefault()
-        activeIndex = 0
-      } else if (event.key === "End" && open) {
-        event.preventDefault()
-        activeIndex = flatVisibleOptions().length - 1
-      }
-    }
-
-    function optionIndex(option) {
-      return flatVisibleOptions().findIndex((item) => item.value === option.value)
-    }
-  }
-
-  view {
-        <div
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-select {open ? 'wire-next--open' : ''} {fixed ? 'wire-next--advanced-select-fixed' : ''} {invalid ? 'wire-next--invalid' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-      data-placement="{placement}"
-      data-search-mode="{searchMode}"
-      data-remote="{remote ? 'true' : 'false'}"
-      data-remote-url="{remoteUrl}"
-      data-remote-query-param="{remoteQueryParam}"
-      data-remote-debounce="{remoteDebounce}"
-      data-remote-auto-load="{remoteAutoLoad ? 'true' : 'false'}"
-      data-infinite="{infinite ? 'true' : 'false'}"
-      data-page="{page}"
-      data-wrn-select
-      @focusout="if (!event.currentTarget.contains(event.relatedTarget)) { open = false }"
-    >
-        <div class="wire-next__row">
-        <label id="{name}-label" for="{name}-trigger">{label}</label>
-        <small data-show="showCounter" data-text="counterText()">{counterText()}</small>
-        </div>
-        <div class="wire-next__select-control">
-        <button
-          {...attrs}
-          id="{name}-trigger"
-          type="button"
-          class="wire-next__select-trigger"
-          role="combobox"
-          aria-haspopup="listbox"
-          aria-expanded="{open}"
-          aria-controls="{name}-listbox"
-          aria-labelledby="{name}-label"
-          aria-invalid="{invalid}"
-          disabled="{disabled}"
-          @click="toggle()"
-          @keydown="handleKeydown(event)"
-        >
-        <span class="wire-next__select-value">
-        <span data-show="!multiple" data-text="triggerText()">{triggerText()}</span>
-        <span
-              data-show="multiple && selectedTemplate === 'count' && selectedCount() > 0"
-              data-text="selectedCount() + ' selected'"
-            >{selectedCount()} selected</span>
-        <span
-              data-show="multiple && selectedTemplate === 'text' && selectedCount() > 0"
-              data-text="selectedText()"
-            >{selectedText()}</span>
-        <span
-              class="wire-next__select-tags"
-              aria-label="{selectedOptionsLabel}"
-              data-show="multiple && selectedTemplate !== 'count' && selectedTemplate !== 'text' && selectedCount() > 0"
-            >
-              {#each allOptions() as option}
-                <span data-show="isSelected(option)">
-                  {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true">
-        </i>{/if}
-                  {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
-                  {#if optionTemplate === "color" && option.color}<i class="wire-next__color-dot" style="--option-color: {option.color}">
-        </i>{/if}
-                  {option.label}
-                </span>
-              {/each}
-            </span>
-        <span data-show="multiple && selectedCount() === 0">
-              {#if placeholderIcon}<i class="{placeholderIcon}" aria-hidden="true">
-        </i>{/if}
-              <span class="wire-next__placeholder">{placeholder}</span>
-        </span>
-        </span>
-        <span
-            class="wire-next__select-chevron icon-[lucide--chevrons-up-down]"
-            aria-hidden="true"
-          >
-        </span>
-        </button>
-        <button
-          type="button"
-          class="wire-next__clear-select"
-          data-show="clearable && allowEmpty && selectedCount() > 0 && !disabled"
-          aria-label="{clearLabel}"
-          title="{clearLabel}"
-          @click="clearSelection(event)"
-        >
-        <span class="icon-[lucide--x]" aria-hidden="true">
-        </span>
-        </button>
-        </div>
-        <div
-        class="wire-next__select-dropdown {fixed ? 'wire-next__select-dropdown--fixed' : ''}"
-        data-show="open"
-        style="{open ? '' : 'display: none'}"
-      >
-          {#if searchable}
-            <label class="wire-next__select-search">
-        <span class="wire-visually-hidden">{searchPlaceholder}</span>
-        <span class="wire-next__search-icon" aria-hidden="true">⌕</span>
-        <input
-                type="search"
-                value="{query}"
-                placeholder="{searchPlaceholder}"
-                autocomplete="off"
-                @input="query = event.target.value; activeIndex = flatVisibleOptions().length ? 0 : -1"
-                @keydown="handleKeydown(event)"
-              />
-        </label>
-          {/if}
-
-          <div
-            class="wire-next__select-message"
-            data-show="remote && query.length !== 0 && query.length < minSearchLength"
-          >Enter at least {minSearchLength} characters.</div>
-        <div class="wire-next__select-message" data-show="loading" role="status">
-        <i class="wire-spinner wire-spinner--inline" aria-hidden="true">
-        </i>
-            {loadingLabel}
-          </div>
-        <div
-              id="{name}-listbox"
-              class="wire-next__select-list"
-              data-show="(!remote || (query.length === 0 && remoteAutoLoad) || query.length >= minSearchLength) && !loading"
-              role="listbox"
-              aria-multiselectable="{multiple}"
-            >
-              {#each groups as group}
-                {#if visibleOptions(group.options || []).length}
-                  <div class="wire-next__option-group" role="group" aria-label="{group.label}">
-        <div class="wire-next__group-label">{group.label}</div>
-                    {#each group.options || [] as option}
-                      <button
-                        type="button"
-                        class="wire-next__select-option {isSelected(option) ? 'wire-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wire-next__select-option--active' : ''}"
-                        data-option-value="{option.value}"
-                        data-show="matches(option)"
-                        role="option"
-                        aria-selected="{isSelected(option)}"
-                        disabled="{!canSelect(option)}"
-                        @mouseenter="activeIndex = optionIndex(option)"
-                        @click="chooseOption(option)"
-                      >
-                        {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true">
-        </i>{/if}
-                        {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
-                        {#if optionTemplate === "color" && option.color}<i class="wire-next__color-dot" style="--option-color: {option.color}">
-        </i>{/if}
-                        <span>
-        <strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
-        <i
-                          class="wire-next__check icon-[lucide--check]"
-                          data-show="isSelected(option)"
-                          aria-hidden="true"
-                        >
-        </i>
-        </button>
-                    {/each}
-                  </div>
-                {/if}
-              {/each}
-
-              {#each options as option}
-                <button
-                  type="button"
-                  class="wire-next__select-option {isSelected(option) ? 'wire-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wire-next__select-option--active' : ''}"
-                  data-option-value="{option.value}"
-                  data-show="matches(option)"
-                  role="option"
-                  aria-selected="{isSelected(option)}"
-                  disabled="{!canSelect(option)}"
-                  @mouseenter="activeIndex = optionIndex(option)"
-                  @click="chooseOption(option)"
-                >
-                  {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true">
-        </i>{/if}
-                  {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
-                  {#if optionTemplate === "color" && option.color}<i class="wire-next__color-dot" style="--option-color: {option.color}">
-        </i>{/if}
-                  <span>
-        <strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
-        <i
-                    class="wire-next__check icon-[lucide--check]"
-                    data-show="isSelected(option)"
-                    aria-hidden="true"
-                  >
-        </i>
-        </button>
-              {/each}
-
-              {#if !loading && !flatVisibleOptions().length}
-                <div class="wire-next__select-message">{emptyLabel}</div>
-              {/if}
-            </div>
-
-          {#if tags && query && !allOptions().some((option) => option.label.toLowerCase() === query.toLowerCase())}
-            <button type="button" class="wire-next__create-option">{createLabel} “{query}”</button>
-          {/if}
-          <button
-            type="button"
-            class="wire-next__load-more"
-            data-wrn-select-load-more
-            data-show="infinite && hasMore"
-          >{loadMoreLabel}</button>
-        </div>
-        <input
-        type="hidden"
-        name="{name}"
-        value="{multiple ? selectedValues.join(',') : selectedValue}"
-        required="{required}"
-        aria-describedby="{validationMessage ? `${name}-validation` : helpText ? `${name}-help` : ''}"
-      />
-
-      {#if helpText && !validationMessage}<small id="{name}-help">{helpText}</small>{/if}
-      <small
-        id="{name}-validation"
-        class="wire-next__validation"
-        data-error="{name}"
-      >{validationMessage}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/alert.wrn b/app/pages/components/alert.wrn deleted file mode 100644 index b66f3062..00000000 --- a/app/pages/components/alert.wrn +++ /dev/null @@ -1,175 +0,0 @@ -page Alertcomponent { - seo { - title = "Alert component" - description = "Theme-aware, responsive alert component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Alert

Theme-aware, responsive alert component.

@wrnexus/ui 0.5.021 props0 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Alert
-  size="default"
-  color="info"
-  variant="soft"
-  radius="md"
-  shadow="sm"
-/>

Legacy mount name: data-component="Alert".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"info"
variantstringOptional"soft"
classstringOptional""
radiusstringOptional"md"
shadowstringOptional"sm"
titlestringOptional"Alert"
descriptionstringOptional""
itemsstringOptional[]
actionsstringOptional[]
showIconbooleanOptionalfalse
iconstringOptional""
dismissiblebooleanOptionalfalse
dismissLabelstringOptional"Dismiss alert"
rolestringOptional"alert"
livestringOptional"polite"
linkLabelstringOptional""
linkHrefstringOptional""
actionLabelstringOptional""
actionHrefstringOptional""
compactbooleanOptionalfalse

Slots

This component does not declare a slot.

Events

  • dismiss
  • action

Source contract

Installed source: components/alert.wrn

component Alert {
-  props {
-    size = "default"
-    color = "info"
-    variant = "soft"
-    class = ""
-    radius = "md"
-    shadow = "sm"
-
-    title = "Alert"
-    description = ""
-    items = []
-    actions = []
-
-    showIcon = false
-    icon = ""
-    dismissible = false
-    dismissLabel = "Dismiss alert"
-    role = "alert"
-    live = "polite"
-
-    linkLabel = ""
-    linkHref = ""
-    actionLabel = ""
-    actionHref = ""
-    compact = false
-
-    @event dismiss = function
-    @event action = function
-  }
-
-  state visible = true
-
-  functions {
-    function dispatchAlertEvent(sourceEvent, eventName, action, root, customEvent) {
-      root = sourceEvent.currentTarget.closest("[data-wrn-alert]")
-
-      if (!root) {
-        return
-      }
-
-      customEvent = document.createEvent("CustomEvent")
-      customEvent.initCustomEvent(eventName, true, false, {
-        component: "Alert",
-        title: title,
-        color: color,
-        variant: variant,
-        action: action
-      })
-      root.dispatchEvent(customEvent)
-    }
-
-    function dismissAlert(sourceEvent) {
-      visible = false
-      dispatchAlertEvent(sourceEvent, "dismiss", null)
-    }
-
-    function selectAction(sourceEvent, action) {
-      dispatchAlertEvent(sourceEvent, "action", action)
-    }
-  }
-
-  view {
-        <section
-      {...attrs}
-      data-wrn-alert
-      data-color="{color}"
-      data-variant="{variant}"
-      data-radius="{radius}"
-      data-shadow="{shadow}"
-      data-show="visible"
-      aria-hidden="{visible ? 'false' : 'true'}"
-      role="{role}"
-      aria-live="{live}"
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--alert wire-next--alert-{variant} {compact ? 'wire-next--alert-compact' : ''} {class}"
-    >
-      {#if showIcon}
-        <span class="wire-next__alert-icon" aria-hidden="true">
-          {#if icon}
-            <span class="{icon}">
-        </span>
-          {:else if color === "success"}
-            <span class="icon-[lucide--circle-check]">
-        </span>
-          {:else if color === "danger"}
-            <span class="icon-[lucide--circle-x]">
-        </span>
-          {:else if color === "warning"}
-            <span class="icon-[lucide--triangle-alert]">
-        </span>
-          {:else}
-            <span class="icon-[lucide--info]">
-        </span>
-          {/if}
-        </span>
-      {/if}
-
-      <div class="wire-next__alert-body">
-        {#if title}<strong class="wire-next__alert-title">{title}</strong>{/if}
-        {#if description}<p>{description}</p>{/if}
-
-        {#if items.length > 0}
-          <ul>
-            {#each items as item}<li>{item.label || item}</li>{/each}
-          </ul>
-        {/if}
-
-        {#if actions.length > 0 || actionLabel || linkLabel}
-          <div class="wire-next__alert-actions">
-            {#each actions as item}
-              <a
-                href="{item.href || '#'}"
-                data-variant="{item.variant || 'link'}"
-                @click="selectAction(event, item)"
-              >{item.label}</a>
-            {/each}
-            {#if actionLabel}
-              <a
-                href="{actionHref || '#'}"
-                data-variant="action"
-                @click="selectAction(event, { label: actionLabel, href: actionHref })"
-              >{actionLabel}</a>
-            {/if}
-            {#if linkLabel}
-              <a
-                href="{linkHref || '#'}"
-                data-variant="link"
-                @click="selectAction(event, { label: linkLabel, href: linkHref })"
-              >{linkLabel}</a>
-            {/if}
-          </div>
-        {/if}
-      </div>
-
-      {#if dismissible}
-        <button
-          type="button"
-          class="wire-next__alert-dismiss"
-          aria-label="{dismissLabel}"
-          @click="dismissAlert(event)"
-        >
-        <span class="icon-[lucide--x]" aria-hidden="true">
-        </span>
-        </button>
-      {/if}
-    </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/avatar-group.wrn b/app/pages/components/avatar-group.wrn deleted file mode 100644 index ccc5bd0b..00000000 --- a/app/pages/components/avatar-group.wrn +++ /dev/null @@ -1,148 +0,0 @@ -page AvatarGroupcomponent { - seo { - title = "AvatarGroup component" - description = "Theme-aware, responsive avatar group component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

AvatarGroup

Theme-aware, responsive avatar group component.

@wrnexus/ui 0.5.012 props0 slots1 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<AvatarGroup
-  items="[]"
-  size="md"
-  color="primary"
-  variant="solid"
-  shape="circle"
-/>

Legacy mount name: data-component="AvatarGroup".

Props and attributes

PropTypeRequirementDefault
itemsstringOptional[]
sizestringOptional"md"
colorstringOptional"primary"
variantstringOptional"solid"
shapestringOptional"circle"
layoutstringOptional"stack"
maxVisiblenumberOptional4
columnsnumberOptional3
borderColorstringOptional""
showTooltipsbooleanOptionaltrue
overflowLabelstringOptional"Show remaining members"
classstringOptional""

Slots

This component does not declare a slot.

Events

  • overflow

Source contract

Installed source: components/AvatarGroup.wrn

component AvatarGroup {
-  props {
-    items = []
-    size = "md"
-    color = "primary"
-    variant = "solid"
-    shape = "circle"
-    layout = "stack"
-    maxVisible = 4
-    columns = 3
-    borderColor = ""
-    showTooltips = true
-    overflowLabel = "Show remaining members"
-    class = ""
-
-    @event overflow = function
-  }
-
-  state overflowOpen = false
-
-  functions {
-    function visibleMembers() {
-      return items.slice(0, Number(maxVisible))
-    }
-
-    function hiddenMembers() {
-      return items.slice(Number(maxVisible))
-    }
-
-    function toggleOverflow(sourceEvent, root, customEvent) {
-      overflowOpen = !overflowOpen
-      root = sourceEvent.currentTarget.closest("[data-wrn-avatar-group]")
-
-      customEvent = document.createEvent("CustomEvent")
-      customEvent.initCustomEvent("overflow", true, false, {
-        component: "AvatarGroup",
-        open: overflowOpen,
-        hiddenCount: hiddenMembers().length
-      })
-      root.dispatchEvent(customEvent)
-    }
-  }
-
-  view {
-        <div
-      {...attrs}
-      class="wire-next wire-next--avatar-group {class}"
-      data-wrn-avatar-group
-      data-layout="{layout}"
-      data-shape="{shape}"
-      data-size="{size}"
-      style="--wire-avatar-group-columns:{columns}; --wire-avatar-group-ring:{borderColor || 'var(--wire-color-bg)'}"
-      role="group"
-      aria-label="Avatar group"
-    >
-        <div class="wire-next__avatar-group-members">
-        {#each visibleMembers() as item}
-          <span
-            class="wire-next__avatar-group-member"
-            data-size="{item.size || size}"
-            data-shape="{item.shape || shape}"
-            data-color="{item.color || color}"
-            data-variant="{item.variant || variant}"
-            tabindex="{showTooltips && (item.tooltip || item.name) ? '0' : '-1'}"
-            aria-label="{item.name || item.alt || item.initials || 'Group member'}"
-          >
-        <span class="wire-next__avatar-group-avatar">
-              {#if item.src}
-                <img src="{item.src}" alt="{item.alt || item.name || ''}" loading="lazy" />
-              {:else if item.initials}
-                <span aria-hidden="true">{item.initials}</span>
-              {:else}
-                <span class="icon-[lucide--user-round]" aria-hidden="true">
-        </span>
-              {/if}
-            </span>
-
-            {#if showTooltips && (item.tooltip || item.name)}
-              <span class="wire-next__avatar-group-tooltip" role="tooltip">
-                {item.tooltip || item.name}
-              </span>
-            {/if}
-          </span>
-        {/each}
-
-        {#if hiddenMembers().length > 0}
-          <span class="wire-next__avatar-group-overflow">
-        <button
-              type="button"
-              class="wire-next__avatar-group-overflow-button"
-              aria-label="{overflowLabel}"
-              aria-expanded="{overflowOpen ? 'true' : 'false'}"
-              @click="toggleOverflow(event)"
-            >
-              +{hiddenMembers().length}
-            </button>
-        <span
-              class="wire-next__avatar-group-menu"
-              role="menu"
-              data-show="overflowOpen"
-              aria-hidden="{overflowOpen ? 'false' : 'true'}"
-            >
-              {#each hiddenMembers() as item}
-                <span class="wire-next__avatar-group-menu-item" role="menuitem">
-        <span class="wire-next__avatar-group-menu-avatar">
-                    {#if item.src}
-                      <img src="{item.src}" alt="" loading="lazy" />
-                    {:else}
-                      <span>{item.initials || '?'}</span>
-                    {/if}
-                  </span>
-        <span>{item.name || item.alt || item.initials || 'Team member'}</span>
-        </span>
-              {/each}
-            </span>
-        </span>
-        {/if}
-      </div>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/avatar.wrn b/app/pages/components/avatar.wrn deleted file mode 100644 index 4a4bf768..00000000 --- a/app/pages/components/avatar.wrn +++ /dev/null @@ -1,117 +0,0 @@ -page Avatarcomponent { - seo { - title = "Avatar component" - description = "Theme-aware, responsive avatar component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Avatar

Theme-aware, responsive avatar component.

@wrnexus/ui 0.5.018 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Avatar
-  src="src"
-  alt="alt"
-  initials="initials"
-  size="md"
-  color="primary"
-/>

Legacy mount name: data-component="Avatar".

Props and attributes

PropTypeRequirementDefault
srcstringOptional""
altstringOptional""
initialsstringOptional""
sizestringOptional"md"
colorstringOptional"primary"
variantstringOptional"solid"
shapestringOptional"circle"
statusstringOptional""
statusLabelstringOptional""
statusPositionstringOptional"bottom"
badgestringOptional""
badgeIconstringOptional""
badgeLabelstringOptional""
tooltipstringOptional""
namestringOptional""
descriptionstringOptional""
loadingstringOptional"lazy"
classstringOptional""

Slots

This component does not declare a slot.

Events

  • load
  • error
  • click

Source contract

Installed source: components/avatar.wrn

component Avatar {
-  props {
-    @event load = function
-    @event error = function
-    @event click = function
-    src = ""
-    alt = ""
-    initials = ""
-    size = "md"
-    color = "primary"
-    variant = "solid"
-    shape = "circle"
-
-    status = ""
-    statusLabel = ""
-    statusPosition = "bottom"
-
-    badge = ""
-    badgeIcon = ""
-    badgeLabel = ""
-
-    tooltip = ""
-    name = ""
-    description = ""
-    loading = "lazy"
-    class = ""
-  }
-
-  view {
-        <span
-      {...attrs}
-      class="wire-next wire-next--avatar {description ? 'wire-next--avatar-media' : ''} {class}"
-      data-wrn-avatar
-    >
-        <span
-        class="wire-next__avatar-wrap"
-        data-shape="{shape}"
-        data-size="{size}"
-        data-color="{color}"
-        data-variant="{variant}"
-        data-status-position="{statusPosition}"
-        tabindex="{tooltip ? '0' : '-1'}"
-        aria-label="{tooltip || name || alt || initials || 'Avatar'}"
-      >
-        <span class="wire-next__avatar">
-          {#if src}
-            <img src="{src}" alt="{alt || name}" loading="{loading}" />
-          {:else if initials}
-            <span class="wire-next__avatar-initials" aria-hidden="true">{initials}</span>
-          {:else}
-            <span class="wire-next__avatar-placeholder icon-[lucide--user-round]" aria-hidden="true">
-        </span>
-          {/if}
-        </span>
-
-        {#if status}
-          <span
-            class="wire-next__avatar-status"
-            data-status="{status}"
-            title="{statusLabel || status}"
-            aria-label="{statusLabel || status}"
-          >
-        </span>
-        {/if}
-
-        {#if badgeIcon}
-          <span class="wire-next__avatar-badge" aria-label="{badgeLabel || 'Linked account'}">
-        <span class="{badgeIcon}" aria-hidden="true">
-        </span>
-        </span>
-        {:else if badge}
-          <span class="wire-next__avatar-badge wire-next__avatar-badge--text" aria-label="{badgeLabel || badge}">
-            {badge}
-          </span>
-        {/if}
-
-        {#if tooltip}
-          <span class="wire-next__avatar-tooltip" role="tooltip">{tooltip}</span>
-        {/if}
-      </span>
-
-      {#if description}
-        <span class="wire-next__avatar-copy">
-        <strong>{name || alt || initials}</strong>
-        <span>{description}</span>
-        </span>
-      {/if}
-    </span>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/badge.wrn b/app/pages/components/badge.wrn deleted file mode 100644 index eeb92386..00000000 --- a/app/pages/components/badge.wrn +++ /dev/null @@ -1,135 +0,0 @@ -page Badgecomponent { - seo { - title = "Badge component" - description = "Theme-aware, responsive badge component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Badge

Theme-aware, responsive badge component.

@wrnexus/ui 0.5.022 props0 slots1 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Badge
-  label="Badge"
-  size="md"
-  color="primary"
-  variant="solid"
-  shape="pill"
-/>

Legacy mount name: data-component="Badge".

Props and attributes

PropTypeRequirementDefault
labelstringOptional"Badge"
sizestringOptional"md"
colorstringOptional"primary"
variantstringOptional"solid"
shapestringOptional"pill"
classstringOptional""
iconstringOptional""
iconPositionstringOptional"start"
dotbooleanOptionalfalse
dotOnlybooleanOptionalfalse
dotLabelstringOptional"Status"
animatedbooleanOptionalfalse
avatarSrcstringOptional""
avatarAltstringOptional""
dismissiblebooleanOptionalfalse
dismissLabelstringOptional"Remove badge"
truncatebooleanOptionalfalse
maxWidthstringOptional"12rem"
anchorLabelstringOptional""
anchorIconstringOptional""
placementstringOptional"inline"
anchorLabelTextstringOptional"Badge anchor"

Slots

This component does not declare a slot.

Events

  • dismiss

Source contract

Installed source: components/Badge.wrn

component Badge {
-  props {
-    label = "Badge"
-    size = "md"
-    color = "primary"
-    variant = "solid"
-    shape = "pill"
-    class = ""
-
-    icon = ""
-    iconPosition = "start"
-    dot = false
-    dotOnly = false
-    dotLabel = "Status"
-    animated = false
-
-    avatarSrc = ""
-    avatarAlt = ""
-    dismissible = false
-    dismissLabel = "Remove badge"
-    truncate = false
-    maxWidth = "12rem"
-
-    anchorLabel = ""
-    anchorIcon = ""
-    placement = "inline"
-    anchorLabelText = "Badge anchor"
-
-    @event dismiss = function
-  }
-
-  state visible = true
-
-  functions {
-    function dismissBadge(sourceEvent, root, customEvent) {
-      visible = false
-      root = sourceEvent.currentTarget.closest("[data-wrn-badge]")
-      customEvent = document.createEvent("CustomEvent")
-      customEvent.initCustomEvent("dismiss", true, false, {
-        component: "Badge",
-        label: label
-      })
-      root.dispatchEvent(customEvent)
-    }
-  }
-
-  view {
-        <span
-      {...attrs}
-      class="wire-next wire-next--badge-root {anchorLabel || anchorIcon ? 'wire-next--badge-anchored' : ''} {class}"
-      data-wrn-badge
-      data-placement="{placement}"
-      data-show="visible"
-      aria-hidden="{visible ? 'false' : 'true'}"
-    >
-      {#if anchorLabel || anchorIcon}
-        <button type="button" class="wire-next__badge-anchor" aria-label="{anchorLabelText}">
-          {#if anchorIcon}<span class="{anchorIcon}" aria-hidden="true">
-        </span>{/if}
-          {#if anchorLabel}<span>{anchorLabel}</span>{/if}
-        </button>
-      {/if}
-
-      <span
-        class="wire-next__badge"
-        data-size="{size}"
-        data-color="{color}"
-        data-variant="{variant}"
-        data-shape="{shape}"
-        data-animated="{animated ? 'true' : 'false'}"
-        style="--wire-badge-max-width:{maxWidth}"
-        role="{dotOnly ? 'status' : ''}"
-        aria-label="{dotOnly ? dotLabel : ''}"
-      >
-        {#if animated}
-          <span class="wire-next__badge-ping" aria-hidden="true">
-        </span>
-        {/if}
-        {#if avatarSrc}
-          <img class="wire-next__badge-avatar" src="{avatarSrc}" alt="{avatarAlt}" loading="lazy" />
-        {/if}
-        {#if dot || dotOnly}
-          <span class="wire-next__badge-dot" aria-hidden="true">
-        </span>
-        {/if}
-        {#if icon && iconPosition === "start"}
-          <span class="wire-next__badge-icon {icon}" aria-hidden="true">
-        </span>
-        {/if}
-        {#if !dotOnly}
-          <span class="wire-next__badge-label {truncate ? 'wire-next__badge-label--truncate' : ''}">
-            {label}
-          </span>
-        {/if}
-        {#if icon && iconPosition === "end"}
-          <span class="wire-next__badge-icon {icon}" aria-hidden="true">
-        </span>
-        {/if}
-        {#if dismissible}
-          <button type="button" class="wire-next__badge-dismiss" aria-label="{dismissLabel}" @click="dismissBadge(event)">
-        <span class="icon-[lucide--x]" aria-hidden="true">
-        </span>
-        </button>
-        {/if}
-      </span>
-        </span>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/blockquote.wrn b/app/pages/components/blockquote.wrn deleted file mode 100644 index 226d9172..00000000 --- a/app/pages/components/blockquote.wrn +++ /dev/null @@ -1,96 +0,0 @@ -page Blockquotecomponent { - seo { - title = "Blockquote component" - description = "Theme-aware, responsive blockquote component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Blockquote

Theme-aware, responsive blockquote component.

@wrnexus/ui 0.5.013 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Blockquote
-  quote="I just wanted to say that I'm very happy with my purchase so far. The documentation is outstanding - clear and detailed."
-  citation="citation"
-  citationTitle="citationTitle"
-  citationUrl="citationUrl"
-  avatarSrc="avatarSrc"
-/>

Legacy mount name: data-component="Blockquote".

Props and attributes

PropTypeRequirementDefault
quotestringOptional"I just wanted to say that I'm very happy with my purchase so far. The documentation is outstanding - clear and detailed."
citationstringOptional""
citationTitlestringOptional""
citationUrlstringOptional""
avatarSrcstringOptional""
avatarAltstringOptional""
sizestringOptional"md"
colorstringOptional"primary"
alignstringOptional"left"
variantstringOptional"default"
quoteMarkbooleanOptionaltrue
italicbooleanOptionaltrue
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/Blockquote.wrn

component Blockquote {
-  props {
-    quote = "I just wanted to say that I'm very happy with my purchase so far. The documentation is outstanding - clear and detailed."
-    citation = ""
-    citationTitle = ""
-    citationUrl = ""
-    avatarSrc = ""
-    avatarAlt = ""
-    size = "md"
-    color = "primary"
-    align = "left"
-    variant = "default"
-    quoteMark = true
-    italic = true
-    class = ""
-  }
-
-  view {
-        <figure
-      {...attrs}
-      class="wire-next wire-next--blockquote wire-next--color-{color} wire-next--size-{size} {class}"
-      data-size="{size}"
-      data-color="{color}"
-      data-align="{align}"
-      data-variant="{variant}"
-      data-italic="{italic}"
-    >
-        <blockquote cite="{citationUrl}">
-        {#if quoteMark}
-          <span class="wire-next__blockquote-mark" aria-hidden="true">“</span>
-        {/if}
-
-        <div class="wire-next__blockquote-copy">
-          {#if quote}
-            <p>{quote}</p>
-          {:else}
-            <slot />
-          {/if}
-        </div>
-        </blockquote>
-
-      {#if citation || citationTitle || avatarSrc}
-        <figcaption class="wire-next__blockquote-citation">
-          {#if avatarSrc}
-            <img
-              class="wire-next__blockquote-avatar"
-              src="{avatarSrc}"
-              alt="{avatarAlt}"
-              loading="lazy"
-            />
-          {/if}
-
-          <span class="wire-next__blockquote-attribution">
-            {#if citation}
-              {#if citationUrl}
-                <cite>
-        <a href="{citationUrl}">{citation}</a>
-        </cite>
-              {:else}
-                <cite>{citation}</cite>
-              {/if}
-            {/if}
-            {#if citationTitle}<span>{citationTitle}</span>{/if}
-          </span>
-        </figcaption>
-      {/if}
-    </figure>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/breadcrumb.wrn b/app/pages/components/breadcrumb.wrn deleted file mode 100644 index 496983b5..00000000 --- a/app/pages/components/breadcrumb.wrn +++ /dev/null @@ -1,46 +0,0 @@ -page Breadcrumbcomponent { - seo { - title = "Breadcrumb component" - description = "Theme-aware, responsive breadcrumb component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Breadcrumb

Theme-aware, responsive breadcrumb component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Breadcrumb
-  size="default"
-  color="primary"
-  label="Breadcrumb"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Breadcrumb".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Breadcrumb"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • navigate
  • click

Source contract

Installed source: components/Breadcrumb.wrn

component Breadcrumb {
-  props {
-    @event navigate = function
-    @event click = function
-    size = "default"
-    color = "primary"
-    label = "Breadcrumb"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--breadcrumb wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/button-group.wrn b/app/pages/components/button-group.wrn deleted file mode 100644 index 97d0362b..00000000 --- a/app/pages/components/button-group.wrn +++ /dev/null @@ -1,117 +0,0 @@ -page ButtonGroupcomponent { - seo { - title = "ButtonGroup component" - description = "Theme-aware, responsive button group component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

ButtonGroup

Theme-aware, responsive button group component.

@wrnexus/ui 0.5.013 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ButtonGroup
-  items="[]"
-  value="value"
-  size="md"
-  color="primary"
-  variant="default"
-/>

Legacy mount name: data-component="ButtonGroup".

Props and attributes

PropTypeRequirementDefault
itemsstringOptional[]
valuestringOptional""
sizestringOptional"md"
colorstringOptional"primary"
variantstringOptional"default"
orientationstringOptional"horizontal"
responsivebooleanOptionalfalse
attachedbooleanOptionaltrue
selectablebooleanOptionalfalse
toolbarbooleanOptionalfalse
disabledbooleanOptionalfalse
ariaLabelstringOptional"Button group"
classstringOptional""

Slots

  • default

Events

  • click
  • select
  • change

Source contract

Installed source: components/ButtonGroup.wrn

component ButtonGroup {
-  props {
-    @event click = function
-    @event select = function
-    @event change = function
-    items = []
-    value = ""
-    size = "md"
-    color = "primary"
-    variant = "default"
-    orientation = "horizontal"
-    responsive = false
-    attached = true
-    selectable = false
-    toolbar = false
-    disabled = false
-    ariaLabel = "Button group"
-    class = ""
-  }
-
-  state selectedValue = value
-
-  functions {
-    function itemValue(item, index) {
-      return item.value || item.label || String(index)
-    }
-
-    function selectItem(item, index) {
-      previousValue = selectedValue
-      nextValue = itemValue(item, index)
-
-      $emit("select", {
-        value: nextValue,
-        previousValue: previousValue,
-        item: item,
-        index: index
-      })
-
-      if (selectable && previousValue !== nextValue) {
-        selectedValue = nextValue
-        $emit("change", {
-          value: nextValue,
-          previousValue: previousValue,
-          item: item,
-          index: index
-        })
-      }
-    }
-  }
-
-  view {
-        <div
-      {...attrs}
-      class="wire-next wire-next--button-group {class}"
-      role="{toolbar ? 'toolbar' : 'group'}"
-      aria-label="{ariaLabel}"
-      aria-orientation="{orientation}"
-      data-size="{size}"
-      data-color="{color}"
-      data-variant="{variant}"
-      data-orientation="{orientation}"
-      data-responsive="{responsive}"
-      data-attached="{attached}"
-      data-selectable="{selectable}"
-      data-disabled="{disabled}"
-    >
-      {#if items}
-        {#each items as item, index}
-          <button
-            type="{item.type || 'button'}"
-            class="wire-btn wire-btn--variant-{item.variant || variant} wire-btn--color-{item.color || color} wire-btn--size-{item.size || size}"
-            disabled="{disabled || item.disabled}"
-            aria-label="{item.ariaLabel || item.label}"
-            aria-pressed="{selectable ? (selectedValue === (item.value || item.label || String(index)) ? 'true' : 'false') : ''}"
-            data-value="{item.value || item.label || String(index)}"
-            data-selected="{selectable && selectedValue === (item.value || item.label || String(index)) ? 'true' : 'false'}"
-            @click="selectItem(item, index)"
-          >
-            {#if item.icon}
-              <span class="wire-btn__icon {item.icon}" aria-hidden="true">
-        </span>
-            {/if}
-            <span class="wire-btn__label">{item.label}</span>
-        </button>
-        {/each}
-      {/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/button.wrn b/app/pages/components/button.wrn deleted file mode 100644 index fb090cd2..00000000 --- a/app/pages/components/button.wrn +++ /dev/null @@ -1,191 +0,0 @@ -page Buttoncomponent { - seo { - title = "Button component" - description = "Theme-aware, responsive button component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Button

Theme-aware, responsive button component.

@wrnexus/ui 0.5.025 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Button
-  label="Button"
-  loadingLabel="Loading…"
-  description="description"
-  as="as"
-  href="href"
-/>

Legacy mount name: data-component="Button".

Props and attributes

PropTypeRequirementDefault
labelstringOptional"Button"
loadingLabelstringOptional"Loading…"
descriptionstringOptional""
asstringOptional""
hrefstringOptional""
targetstringOptional""
relstringOptional""
typestringOptional"button"
variantstringOptional"default"
colorstringOptional"primary"
sizestringOptional"default"
disabledbooleanOptionalfalse
loadingbooleanOptionalfalse
pillbooleanOptionalfalse
fullWidthbooleanOptionalfalse
iconstringOptional""
iconPositionstringOptional"start"
ariaLabelstringOptional""
ariaPressedstringOptional""
ariaExpandedstringOptional""
ariaControlsstringOptional""
titlestringOptional""
autofocusbooleanOptionalfalse
controlClassstringOptional""
classstringOptional""

Slots

  • default

Events

  • click
  • focus
  • blur

Source contract

Installed source: components/button.wrn

component Button {
-  props {
-    @event click = function
-    @event focus = function
-    @event blur = function
-    label = "Button"
-    loadingLabel = "Loading…"
-    description = ""
-    as = ""
-    href = ""
-    target = ""
-    rel = ""
-    type = "button"
-    variant = "default"
-    color = "primary"
-    size = "default"
-    disabled = false
-    loading = false
-    pill = false
-    fullWidth = false
-    icon = ""
-    iconPosition = "start"
-    ariaLabel = ""
-    ariaPressed = ""
-    ariaExpanded = ""
-    ariaControls = ""
-    title = ""
-    autofocus = false
-    controlClass = ""
-    class = ""
-  }
-
-  view {
-        <span
-      class="wire-action {fullWidth ? 'w-full' : ''} {class}"
-    >
-      {#if as === "a" || (as === "" && href)}
-        <a
-          {...attrs}
-          href="{disabled || loading ? '' : href}"
-          target="{target}"
-          rel="{rel}"
-          title="{title}"
-          aria-label="{ariaLabel || ((size === 'icon' || size === 'icon-xs' || size === 'icon-sm' || size === 'icon-lg') ? label : '')}"
-          aria-disabled="{disabled || loading}"
-          aria-busy="{loading}"
-          aria-pressed="{ariaPressed}"
-          aria-expanded="{ariaExpanded}"
-          aria-controls="{ariaControls}"
-          tabindex="{disabled || loading ? '-1' : '0'}"
-          class="wire-btn wire-btn--variant-{variant} wire-btn--color-{color} wire-btn--size-{size} {pill ? 'wire-btn--pill' : ''} {description ? 'wire-btn--with-description' : ''} {fullWidth ? 'w-full' : ''} {controlClass}"
-        >
-          {#if loading}
-            <span
-              class="wire-spinner wire-spinner--inline"
-              aria-hidden="true"
-            >
-        </span>
-          {/if}
-          {#if icon && !loading && iconPosition === "start"}
-            <span
-              class="wire-btn__icon {icon}"
-              aria-hidden="true"
-            >
-        </span>
-          {/if}
-          <span
-            class="wire-btn__copy {(size === 'icon' || size === 'icon-xs' || size === 'icon-sm' || size === 'icon-lg') ? 'wire-visually-hidden' : ''}"
-          >
-        <span
-              class="wire-btn__label"
-            >
-        <slot>{loading ? loadingLabel : label}</slot>
-        </span>
-            {#if description && !loading}
-              <span
-                class="wire-btn__description"
-              >
-                {description}
-              </span>
-            {/if}
-          </span>
-          {#if icon && !loading && iconPosition === "end"}
-            <span
-              class="wire-btn__icon {icon}"
-              aria-hidden="true"
-            >
-        </span>
-          {/if}
-          {#if (size === "icon" || size === "icon-xs" || size === "icon-sm" || size === "icon-lg") && (ariaLabel || label)}
-            <span
-              class="wire-btn__tooltip"
-              role="tooltip"
-              aria-hidden="true"
-            >
-              {ariaLabel || label}
-            </span>
-          {/if}
-        </a>
-      {:else}
-        <button
-          {...attrs}
-          type="{type}"
-          title="{title}"
-          autofocus="{autofocus}"
-          disabled="{disabled || loading}"
-          aria-label="{ariaLabel || ((size === 'icon' || size === 'icon-xs' || size === 'icon-sm' || size === 'icon-lg') ? label : '')}"
-          aria-busy="{loading}"
-          aria-pressed="{ariaPressed}"
-          aria-expanded="{ariaExpanded}"
-          aria-controls="{ariaControls}"
-          class="wire-btn wire-btn--variant-{variant} wire-btn--color-{color} wire-btn--size-{size} {pill ? 'wire-btn--pill' : ''} {description ? 'wire-btn--with-description' : ''} {fullWidth ? 'w-full' : ''} {controlClass}"
-        >
-          {#if loading}
-            <span
-              class="wire-spinner wire-spinner--inline"
-              aria-hidden="true"
-            >
-        </span>
-          {/if}
-          {#if icon && !loading && iconPosition === "start"}
-            <span
-              class="wire-btn__icon {icon}"
-              aria-hidden="true"
-            >
-        </span>
-          {/if}
-          <span
-            class="wire-btn__copy {(size === 'icon' || size === 'icon-xs' || size === 'icon-sm' || size === 'icon-lg') ? 'wire-visually-hidden' : ''}"
-          >
-        <span
-              class="wire-btn__label"
-            >
-        <slot>{loading ? loadingLabel : label}</slot>
-        </span>
-            {#if description && !loading}
-              <span
-                class="wire-btn__description"
-              >
-                {description}
-              </span>
-            {/if}
-          </span>
-          {#if icon && !loading && iconPosition === "end"}
-            <span
-              class="wire-btn__icon {icon}"
-              aria-hidden="true"
-            >
-        </span>
-          {/if}
-          {#if (size === "icon" || size === "icon-xs" || size === "icon-sm" || size === "icon-lg") && (ariaLabel || label)}
-            <span
-              class="wire-btn__tooltip"
-              role="tooltip"
-              aria-hidden="true"
-            >
-              {ariaLabel || label}
-            </span>
-          {/if}
-        </button>
-      {/if}
-    </span>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/card.wrn b/app/pages/components/card.wrn deleted file mode 100644 index b6e69c5b..00000000 --- a/app/pages/components/card.wrn +++ /dev/null @@ -1,290 +0,0 @@ -page Cardcomponent { - seo { - title = "Card component" - description = "Theme-aware, responsive card component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Card

Theme-aware, responsive card component.

@wrnexus/ui 0.5.031 props1 slots6 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Card
-  title="Card title"
-  subtitle="subtitle"
-  description="description"
-  header="header"
-  footer="footer"
-/>

Legacy mount name: data-component="Card".

Props and attributes

PropTypeRequirementDefault
titlestringOptional"Card title"
subtitlestringOptional""
descriptionstringOptional""
headerstringOptional""
footerstringOptional""
imageSrcstringOptional""
imageAltstringOptional""
imagePositionstringOptional"top"
actionLabelstringOptional""
actionHrefstringOptional""
headerActionsstringOptional[]
navigationstringOptional[]
activeNavstringOptional""
mobileNavigationbooleanOptionalfalse
alertTitlestringOptional""
alertDescriptionstringOptional""
emptybooleanOptionalfalse
emptyTitlestringOptional"No data to show"
emptyIconstringOptional"icon-[lucide--inbox]"
itemsstringOptional[]
sizestringOptional"md"
colorstringOptional"primary"
variantstringOptional"default"
layoutstringOptional"vertical"
alignstringOptional"left"
hoverstringOptional"none"
scrollablebooleanOptionalfalse
maxHeightstringOptional"18rem"
dismissiblebooleanOptionalfalse
ariaLabelstringOptional""
classstringOptional""

Slots

  • default

Events

  • click
  • action
  • navigate
  • dismiss
  • load
  • error

Source contract

Installed source: components/card.wrn

component Card {
-  props {
-    @event click = function
-    @event action = function
-    @event navigate = function
-    @event dismiss = function
-    @event load = function
-    @event error = function
-    title = "Card title"
-    subtitle = ""
-    description = ""
-    header = ""
-    footer = ""
-    imageSrc = ""
-    imageAlt = ""
-    imagePosition = "top"
-    actionLabel = ""
-    actionHref = ""
-    headerActions = []
-    navigation = []
-    activeNav = ""
-    mobileNavigation = false
-    alertTitle = ""
-    alertDescription = ""
-    empty = false
-    emptyTitle = "No data to show"
-    emptyIcon = "icon-[lucide--inbox]"
-    items = []
-    size = "md"
-    color = "primary"
-    variant = "default"
-    layout = "vertical"
-    align = "left"
-    hover = "none"
-    scrollable = false
-    maxHeight = "18rem"
-    dismissible = false
-    ariaLabel = ""
-    class = ""
-  }
-
-  state visible = true
-  state selectedNav = activeNav
-
-  functions {
-    function chooseNavigation(item, index) {
-      previousValue = selectedNav
-      selectedNav = item.value || item.label
-      $emit("navigate", {
-        value: selectedNav,
-        previousValue: previousValue,
-        item: item,
-        index: index
-      })
-    }
-
-    function chooseMobileNavigation(sourceEvent) {
-      previousValue = selectedNav
-      selectedNav = sourceEvent.target.value
-      $emit("navigate", {
-        value: selectedNav,
-        previousValue: previousValue
-      })
-    }
-
-    function chooseAction(action, index) {
-      $emit("action", {
-        action: action,
-        index: index
-      })
-    }
-
-    function dismissCard(sourceEvent) {
-      panel = sourceEvent.currentTarget.closest(".wire-next__card-panel")
-      if (panel) {
-        panel.setAttribute("hidden", "")
-      }
-      visible = false
-      $emit("dismiss", {
-        title: title
-      })
-    }
-  }
-
-  view {
-        <section
-      {...attrs}
-      class="wire-next wire-next--card {class}"
-      data-size="{size}"
-      data-color="{color}"
-      data-variant="{variant}"
-      data-layout="{layout}"
-      data-align="{align}"
-      data-hover="{hover}"
-      data-image-position="{imagePosition}"
-      data-group="{items.length > 0}"
-      aria-label="{ariaLabel || title}"
-    >
-      {#if items.length > 0}
-        <div class="wire-next__card-group">
-          {#each items as item}
-            <article class="wire-next__card-panel">
-              {#if item.imageSrc}
-                <div class="wire-next__card-media">
-        <img src="{item.imageSrc}" alt="{item.imageAlt || ''}" loading="lazy" />
-        </div>
-              {/if}
-              <div class="wire-next__card-body">
-                {#if item.title}<h3>{item.title}</h3>{/if}
-                {#if item.description}<p>{item.description}</p>{/if}
-              </div>
-              {#if item.footer}<footer class="wire-next__card-footer">{item.footer}</footer>{/if}
-            </article>
-          {/each}
-        </div>
-      {:else if visible}
-        <article class="wire-next__card-panel">
-          {#if imagePosition === "overlay" && imageSrc}
-            <div class="wire-next__card-media wire-next__card-media--overlay">
-        <img
-                src="{imageSrc}"
-                alt="{imageAlt}"
-                loading="lazy"
-                @load="$emit('load', { src: imageSrc })"
-                @error="$emit('error', { src: imageSrc })"
-              />
-        <div class="wire-next__card-overlay">
-                {#if title}<h3>{title}</h3>{/if}
-                {#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
-                {#if description}<p>{description}</p>{/if}
-                {#if footer}<small>{footer}</small>{/if}
-              </div>
-        </div>
-          {:else}
-            {#if header || headerActions.length > 0 || dismissible}
-              <header class="wire-next__card-header">
-        <span>{header}</span>
-                {#if headerActions.length > 0 || dismissible}
-                  <span class="wire-next__card-header-actions">
-                    {#each headerActions as action, index}
-                      <button
-                        type="button"
-                        aria-label="{action.ariaLabel || action.label}"
-                        title="{action.label}"
-                        @click="chooseAction(action, index)"
-                      >
-                        {#if action.icon}<span class="{action.icon}" aria-hidden="true">
-        </span>{/if}
-                        {#if !action.icon}<span>{action.label}</span>{/if}
-                      </button>
-                    {/each}
-                    {#if dismissible}
-                      <button type="button" aria-label="Close card" @click="dismissCard(event)">
-        <span class="icon-[lucide--x]" aria-hidden="true">
-        </span>
-        </button>
-                    {/if}
-                  </span>
-                {/if}
-              </header>
-            {/if}
-
-            {#if navigation.length > 0}
-              <nav class="wire-next__card-navigation" aria-label="{ariaLabel || title}">
-        <div class="wire-next__card-tabs">
-                  {#each navigation as item, index}
-                    <button
-                      type="button"
-                      data-active="{selectedNav === (item.value || item.label)}"
-                      aria-current="{selectedNav === (item.value || item.label) ? 'page' : ''}"
-                      @click="chooseNavigation(item, index)"
-                    >
-                      {item.label}
-                    </button>
-                  {/each}
-                </div>
-                {#if mobileNavigation}
-                  <select
-                    class="wire-next__card-navigation-select"
-                    aria-label="{ariaLabel || title}"
-                    value="{selectedNav}"
-                    @change="chooseMobileNavigation(event)"
-                  >
-                    {#each navigation as item}
-                      <option
-                        value="{item.value || item.label}"
-                        selected="{selectedNav === (item.value || item.label)}"
-                      >
-                        {item.label}
-                      </option>
-                    {/each}
-                  </select>
-                {/if}
-              </nav>
-            {/if}
-
-            {#if imageSrc && imagePosition !== "bottom"}
-              <div class="wire-next__card-media">
-        <img
-                  src="{imageSrc}"
-                  alt="{imageAlt}"
-                  loading="lazy"
-                  @load="$emit('load', { src: imageSrc })"
-                  @error="$emit('error', { src: imageSrc })"
-                />
-        </div>
-            {/if}
-
-            {#if alertTitle || alertDescription}
-              <aside class="wire-next__card-alert" role="status">
-                {#if alertTitle}<strong>{alertTitle}</strong>{/if}
-                {#if alertDescription}<span>{alertDescription}</span>{/if}
-              </aside>
-            {/if}
-
-            <div
-              class="wire-next__card-body"
-              data-scrollable="{scrollable}"
-              style="--wire-card-max-height: {maxHeight}"
-            >
-              {#if empty}
-                <div class="wire-next__card-empty">
-        <span class="{emptyIcon}" aria-hidden="true">
-        </span>
-        <strong>{emptyTitle}</strong>
-        </div>
-              {:else}
-                {#if title}<h3>{title}</h3>{/if}
-                {#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
-                {#if description}<p>{description}</p>{/if}
-                <slot />
-                {#if actionLabel}
-                  <a
-                    class="wire-next__card-action"
-                    href="{actionHref || '#'}"
-                    @click="$emit('action', { label: actionLabel, href: actionHref })"
-                  >
-                    {actionLabel}<span class="icon-[lucide--chevron-right]" aria-hidden="true">
-        </span>
-        </a>
-                {/if}
-              {/if}
-            </div>
-
-            {#if footer}<footer class="wire-next__card-footer">{footer}</footer>{/if}
-
-            {#if imageSrc && imagePosition === "bottom"}
-              <div class="wire-next__card-media">
-        <img
-                  src="{imageSrc}"
-                  alt="{imageAlt}"
-                  loading="lazy"
-                  @load="$emit('load', { src: imageSrc })"
-                  @error="$emit('error', { src: imageSrc })"
-                />
-        </div>
-            {/if}
-          {/if}
-        </article>
-      {/if}
-    </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/carousel.wrn b/app/pages/components/carousel.wrn deleted file mode 100644 index 1c89386b..00000000 --- a/app/pages/components/carousel.wrn +++ /dev/null @@ -1,490 +0,0 @@ -page Carouselcomponent { - seo { - title = "Carousel component" - description = "Theme-aware, responsive carousel component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Carousel

Theme-aware, responsive carousel component.

@wrnexus/ui 0.5.022 props1 slots10 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Carousel
-  size="default"
-  color="primary"
-  title="title"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Carousel".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional""
descriptionstringOptional""
itemsstringOptional[]
activeIndexnumberOptional0
slidesPerViewnumberOptional1
gapstringOptional"0.75rem"
showPaginationbooleanOptionalfalse
isAutoPlaybooleanOptionalfalse
autoplayIntervalnumberOptional4000
isInfiniteLoopbooleanOptionalfalse
isRTLbooleanOptionalfalse
isCenteredbooleanOptionalfalse
isDraggablebooleanOptionalfalse
isAutoHeightbooleanOptionalfalse
isSnapbooleanOptionalfalse
showCounterbooleanOptionalfalse
thumbnailsstringOptional"none"
ariaLabelstringOptional"Content carousel"
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • initialize
  • change
  • previous
  • next
  • play
  • pause
  • reachStart
  • reachEnd
  • dragStart
  • dragEnd

Source contract

Installed source: components/carousel.wrn

// Interactive, hydration-safe content carousel.
-component Carousel {
-  props {
-    @event initialize = function
-    @event change = function
-    @event previous = function
-    @event next = function
-    @event play = function
-    @event pause = function
-    @event reachStart = function
-    @event reachEnd = function
-    @event dragStart = function
-    @event dragEnd = function
-    size = "default"
-    color = "primary"
-    title = ""
-    description = ""
-    items = []
-    activeIndex = 0
-    slidesPerView = 1
-    gap = "0.75rem"
-    showPagination = false
-    isAutoPlay = false
-    autoplayInterval = 4000
-    isInfiniteLoop = false
-    isRTL = false
-    isCentered = false
-    isDraggable = false
-    isAutoHeight = false
-    isSnap = false
-    showCounter = false
-    thumbnails = "none"
-    ariaLabel = "Content carousel"
-    variant = "default"
-    class = ""
-  }
-
-  state currentIndex = activeIndex
-  state playing = false
-  state dragOrigin = null
-  state dragOffset = 0
-  state autoplayTimer = null
-  state carouselRoot = null
-
-  functions {
-    function slideCount() {
-      return items.length
-    }
-
-    function maximumIndex(count, visible) {
-      count = slideCount()
-      visible = Math.max(1, Number(slidesPerView) || 1)
-      if (isCentered || isSnap) {
-        return Math.max(0, count - 1)
-      }
-      return Math.max(0, count - visible)
-    }
-
-    function normalizedIndex(index, maximum) {
-      maximum = maximumIndex()
-      if (slideCount() === 0) {
-        return 0
-      }
-      if (isInfiniteLoop) {
-        if (index < 0) {
-          return maximum
-        }
-        if (index > maximum) {
-          return 0
-        }
-      }
-      return Math.max(0, Math.min(index, maximum))
-    }
-
-    function rememberRoot(sourceEvent, root) {
-      if (!sourceEvent || !sourceEvent.currentTarget) {
-        return
-      }
-      root = sourceEvent.currentTarget.closest(".wire-next--carousel")
-      if (root) {
-        carouselRoot = root
-      }
-    }
-
-    function syncNavigation(sourceEvent, index, root, viewport, slides, slide, thumbnails, thumbnail, rail) {
-      rememberRoot(sourceEvent)
-      root = carouselRoot
-      if (!root) {
-        return
-      }
-      if (isSnap) {
-        viewport = root.querySelector(".wire-next__carousel-viewport")
-        slides = root.querySelectorAll(".wire-next__carousel-slide")
-        slide = slides[index]
-        if (viewport && slide) {
-          viewport.scrollTo({
-            left: slide.offsetLeft - (viewport.clientWidth - slide.clientWidth) / 2,
-            behavior: "smooth"
-          })
-        }
-      }
-      thumbnails = root.querySelectorAll(".wire-next__carousel-thumbnails button")
-      if (thumbnails[index]) {
-        thumbnail = thumbnails[index]
-        rail = thumbnail.parentElement
-        rail.scrollTo({
-          left: thumbnail.offsetLeft - (rail.clientWidth - thumbnail.clientWidth) / 2,
-          top: thumbnail.offsetTop - (rail.clientHeight - thumbnail.clientHeight) / 2,
-          behavior: "smooth"
-        })
-      }
-    }
-
-    function handleSnapScroll(sourceEvent, slides, viewport, viewportCenter, closestIndex, closestDistance, slide, slideCenter, distance, previousIndex) {
-      if (!isSnap) {
-        return
-      }
-      rememberRoot(sourceEvent)
-      viewport = sourceEvent.currentTarget
-      slides = viewport.querySelectorAll(".wire-next__carousel-slide")
-      viewportCenter = viewport.scrollLeft + viewport.clientWidth / 2
-      closestIndex = currentIndex
-      closestDistance = 1000000000
-      slides.forEach(function (candidate, index) {
-        slideCenter = candidate.offsetLeft + candidate.clientWidth / 2
-        distance = Math.abs(slideCenter - viewportCenter)
-        if (distance < closestDistance) {
-          closestDistance = distance
-          closestIndex = index
-        }
-      })
-      closestIndex = normalizedIndex(closestIndex)
-      if (closestIndex !== currentIndex) {
-        previousIndex = currentIndex
-        currentIndex = closestIndex
-        $emit("change", {
-          index: currentIndex,
-          previousIndex: previousIndex,
-          item: items[currentIndex],
-          reason: "snap"
-        })
-        slides = carouselRoot.querySelectorAll(".wire-next__carousel-thumbnails button")
-        slide = slides[currentIndex]
-        if (slide) {
-          viewport = slide.parentElement
-          viewport.scrollTo({
-            left: slide.offsetLeft - (viewport.clientWidth - slide.clientWidth) / 2,
-            top: slide.offsetTop - (viewport.clientHeight - slide.clientHeight) / 2,
-            behavior: "smooth"
-          })
-        }
-      }
-    }
-
-    function selectSlide(index, reason, sourceEvent, previousIndex) {
-      if (slideCount() === 0) {
-        return
-      }
-      previousIndex = currentIndex
-      currentIndex = normalizedIndex(Number(index))
-      syncNavigation(sourceEvent, currentIndex)
-      if (previousIndex === currentIndex && reason !== "initialize") {
-        return
-      }
-      $emit("change", {
-        index: currentIndex,
-        previousIndex: previousIndex,
-        item: items[currentIndex],
-        reason: reason || "select"
-      })
-      if (currentIndex === 0) {
-        $emit("reachStart", { index: currentIndex })
-      }
-      if (currentIndex === maximumIndex()) {
-        $emit("reachEnd", { index: currentIndex })
-      }
-    }
-
-    function previousSlide(sourceEvent, previousIndex) {
-      previousIndex = currentIndex
-      selectSlide(currentIndex - 1, "previous", sourceEvent)
-      $emit("previous", {
-        index: currentIndex,
-        previousIndex: previousIndex,
-        item: items[currentIndex]
-      })
-    }
-
-    function nextSlide(sourceEvent, previousIndex) {
-      previousIndex = currentIndex
-      selectSlide(currentIndex + 1, "next", sourceEvent)
-      $emit("next", {
-        index: currentIndex,
-        previousIndex: previousIndex,
-        item: items[currentIndex]
-      })
-    }
-
-    function handleKeydown(sourceEvent) {
-      if (sourceEvent.key === "ArrowLeft") {
-        sourceEvent.preventDefault()
-        if (isRTL) {
-          nextSlide(sourceEvent)
-        } else {
-          previousSlide(sourceEvent)
-        }
-      }
-      if (sourceEvent.key === "ArrowRight") {
-        sourceEvent.preventDefault()
-        if (isRTL) {
-          previousSlide(sourceEvent)
-        } else {
-          nextSlide(sourceEvent)
-        }
-      }
-      if (sourceEvent.key === "Home") {
-        sourceEvent.preventDefault()
-        selectSlide(0, "keyboard", sourceEvent)
-      }
-      if (sourceEvent.key === "End") {
-        sourceEvent.preventDefault()
-        selectSlide(maximumIndex(), "keyboard", sourceEvent)
-      }
-    }
-
-    function beginDrag(sourceEvent) {
-      if (!isDraggable || isSnap) {
-        return
-      }
-      sourceEvent.preventDefault()
-      dragOrigin = sourceEvent.clientX
-      dragOffset = 0
-      sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId)
-      $emit("dragStart", { index: currentIndex, x: dragOrigin })
-    }
-
-    function moveDrag(sourceEvent) {
-      if (!isDraggable || isSnap || dragOrigin === null) {
-        return
-      }
-      sourceEvent.preventDefault()
-      dragOffset = sourceEvent.clientX - dragOrigin
-    }
-
-    function cancelDrag() {
-      dragOrigin = null
-      dragOffset = 0
-    }
-
-    function endDrag(sourceEvent, distance) {
-      if (!isDraggable || isSnap || dragOrigin === null) {
-        return
-      }
-      sourceEvent.preventDefault()
-      distance = dragOffset || sourceEvent.clientX - dragOrigin
-      dragOrigin = null
-      dragOffset = 0
-      if (Math.abs(distance) > 20) {
-        if ((distance < 0 && !isRTL) || (distance > 0 && isRTL)) {
-          nextSlide(sourceEvent)
-        } else {
-          previousSlide(sourceEvent)
-        }
-      }
-      $emit("dragEnd", {
-        index: currentIndex,
-        distance: distance
-      })
-    }
-
-    function advanceAutoplay() {
-      if (currentIndex >= maximumIndex()) {
-        selectSlide(0, "autoplay")
-      } else {
-        selectSlide(currentIndex + 1, "autoplay")
-      }
-    }
-
-    function startAutoplay() {
-      if (!isAutoPlay || playing || slideCount() < 2) {
-        return
-      }
-      playing = true
-      autoplayTimer = setInterval(advanceAutoplay, Math.max(1000, Number(autoplayInterval)))
-      $emit("play", { index: currentIndex, interval: autoplayInterval })
-    }
-
-    function pauseAutoplay() {
-      if (autoplayTimer) {
-        clearInterval(autoplayTimer)
-      }
-      autoplayTimer = null
-      if (playing) {
-        $emit("pause", { index: currentIndex })
-      }
-      playing = false
-    }
-
-  }
-
-  lifecycle {
-    mount {
-      $emit("initialize", { index: currentIndex, count: slideCount() })
-      startAutoplay()
-    }
-    unmount {
-      if (autoplayTimer) {
-        clearInterval(autoplayTimer)
-      }
-    }
-  }
-
-  view {
-        <section
-      {...attrs}
-      class="wire-next wire-next--carousel wire-next--color-{color} wire-next--size-{size} wire-next--variant-{variant} {class}"
-      data-rtl="{isRTL}"
-      data-centered="{isCentered}"
-      data-draggable="{isDraggable && !isSnap}"
-      data-dragging="{dragOrigin !== null}"
-      data-auto-height="{isAutoHeight}"
-      data-snap="{isSnap}"
-      data-thumbnails="{thumbnails}"
-      dir="{isRTL ? 'rtl' : 'ltr'}"
-      role="region"
-      aria-roledescription="carousel"
-      aria-label="{ariaLabel}"
-      @keydown="handleKeydown(event)"
-      @mouseenter="rememberRoot(event); pauseAutoplay()"
-      @mouseleave="startAutoplay()"
-      @focusin="rememberRoot(event); pauseAutoplay()"
-      @focusout="startAutoplay()"
-    >
-      {#if title || description}
-        <header class="wire-next__carousel-header">
-          {#if title}<h3>{title}</h3>{/if}
-          {#if description}<p>{description}</p>{/if}
-        </header>
-      {/if}
-
-        <div class="wire-next__carousel-layout">
-          {#if thumbnails === "vertical"}
-            <div class="wire-next__carousel-thumbnails" aria-label="Choose a slide">
-              {#each items as item, index}
-                <button
-                  type="button"
-                  data-active="{index === currentIndex}"
-                  aria-label="Show slide {index + 1}: {item.label || item.title}"
-                  aria-current="{index === currentIndex ? 'true' : 'false'}"
-                  @click="selectSlide(index, 'thumbnail', event)"
-                >
-                  {#if item.thumbnail}<img src="{item.thumbnail}" alt="" />{/if}
-                  <span>{item.label || item.title || "Slide " + (index + 1)}</span>
-        </button>
-              {/each}
-            </div>
-          {/if}
-
-          <div class="wire-next__carousel-main">
-        <div class="wire-next__carousel-stage">
-        <div
-                class="wire-next__carousel-viewport"
-                tabindex="0"
-                @scroll="handleSnapScroll(event)"
-                @pointerdown="beginDrag(event)"
-                @pointermove="moveDrag(event)"
-                @pointerup="endDrag(event)"
-                @pointercancel="cancelDrag()"
-                @lostpointercapture="cancelDrag()"
-              >
-        <div
-                  class="wire-next__carousel-track"
-                  style="--wire-carousel-index: {currentIndex}; --wire-carousel-per-view: {slidesPerView}; --wire-carousel-gap: {gap}; --wire-carousel-drag-offset: {dragOffset}px"
-                >
-                  {#each items as item, index}
-                    <article
-                      class="wire-next__carousel-slide"
-                      data-active="{index === currentIndex}"
-                      role="group"
-                      aria-roledescription="slide"
-                      aria-label="{index + 1} of {items.length}"
-                      aria-hidden="{index === currentIndex ? 'false' : 'true'}"
-                    >
-                      {#if item.imageSrc}
-                        <img src="{item.imageSrc}" alt="{item.imageAlt || item.title || ''}" />
-                      {/if}
-                      <div class="wire-next__carousel-slide-content">
-                        {#if item.eyebrow}<span>{item.eyebrow}</span>{/if}
-                        {#if item.title || item.label}<h4>{item.title || item.label}</h4>{/if}
-                        {#if item.description}<p>{item.description}</p>{/if}
-                        {#if item.actionLabel}
-                          <a href="{item.actionHref || '#'}">{item.actionLabel}</a>
-                        {/if}
-                      </div>
-        </article>
-                  {/each}
-                  <slot />
-        </div>
-        </div>
-        <button
-                class="wire-next__carousel-control wire-next__carousel-control--previous"
-                type="button"
-                aria-label="Previous slide"
-                disabled="{!isInfiniteLoop && currentIndex === 0}"
-                @click="previousSlide(event)"
-              >
-        <span class="icon-[lucide--chevron-left]" aria-hidden="true">
-        </span>
-        </button>
-        <button
-                class="wire-next__carousel-control wire-next__carousel-control--next"
-                type="button"
-                aria-label="Next slide"
-                disabled="{!isInfiniteLoop && currentIndex === maximumIndex()}"
-                @click="nextSlide(event)"
-              >
-        <span class="icon-[lucide--chevron-right]" aria-hidden="true">
-        </span>
-        </button>
-
-              {#if showCounter}
-                <output class="wire-next__carousel-counter" aria-live="polite">
-                  {currentIndex + 1} / {items.length}
-                </output>
-              {/if}
-            </div>
-
-            {#if showPagination}
-              <div class="wire-next__carousel-pagination" aria-label="Choose a slide">
-                {#each items as item, index}
-                  <button
-                    type="button"
-                    data-active="{index === currentIndex}"
-                    aria-label="Show slide {index + 1}"
-                    aria-current="{index === currentIndex ? 'true' : 'false'}"
-                    @click="selectSlide(index, 'pagination', event)"
-                  >
-        </button>
-                {/each}
-              </div>
-            {/if}
-
-            {#if thumbnails === "horizontal"}
-              <div class="wire-next__carousel-thumbnails" aria-label="Choose a slide">
-                {#each items as item, index}
-                  <button
-                    type="button"
-                    data-active="{index === currentIndex}"
-                    aria-label="Show slide {index + 1}: {item.label || item.title}"
-                    aria-current="{index === currentIndex ? 'true' : 'false'}"
-                    @click="selectSlide(index, 'thumbnail', event)"
-                  >
-                    {#if item.thumbnail}<img src="{item.thumbnail}" alt="" />{/if}
-                    <span>{item.label || item.title || "Slide " + (index + 1)}</span>
-        </button>
-                {/each}
-              </div>
-            {/if}
-          </div>
-        </div>
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/chart.wrn b/app/pages/components/chart.wrn deleted file mode 100644 index ba1a2d31..00000000 --- a/app/pages/components/chart.wrn +++ /dev/null @@ -1,49 +0,0 @@ -page Chartcomponent { - seo { - title = "Chart component" - description = "Theme-aware, responsive chart component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

Chart

Theme-aware, responsive chart component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Chart
-  size="default"
-  color="primary"
-  title="Chart"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Chart".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Chart"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select
  • dataPointClick
  • legendToggle

Source contract

Installed source: components/Chart.wrn

component Chart {
-  props {
-    @event select = function
-    @event dataPointClick = function
-    @event legendToggle = function
-    size = "default"
-    color = "primary"
-    title = "Chart"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--chart wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/chat-bubble.wrn b/app/pages/components/chat-bubble.wrn deleted file mode 100644 index 754fbb64..00000000 --- a/app/pages/components/chat-bubble.wrn +++ /dev/null @@ -1,175 +0,0 @@ -page ChatBubblecomponent { - seo { - title = "ChatBubble component" - description = "Theme-aware, responsive chat bubble component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

ChatBubble

Theme-aware, responsive chat bubble component.

@wrnexus/ui 0.5.011 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ChatBubble
-  size="default"
-  color="primary"
-  title="title"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="ChatBubble".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional""
descriptionstringOptional""
itemsstringOptional[]
oneSidedbooleanOptionalfalse
showAvatarsbooleanOptionalfalse
showMetadatabooleanOptionalfalse
ariaLabelstringOptional"Conversation"
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • action
  • messageClick
  • avatarClick
  • linkClick

Source contract

Installed source: components/ChatBubble.wrn

component ChatBubble {
-  props {
-    @event action = function
-    @event messageClick = function
-    @event avatarClick = function
-    @event linkClick = function
-    size = "default"
-    color = "primary"
-    title = ""
-    description = ""
-    items = []
-    oneSided = false
-    showAvatars = false
-    showMetadata = false
-    ariaLabel = "Conversation"
-    variant = "default"
-    class = ""
-  }
-
-  functions {
-    function messageDirection(item) {
-      return item.direction === "outgoing" ? "outgoing" : "incoming"
-    }
-
-    function selectMessage(item, index) {
-      $emit("messageClick", {
-        item: item,
-        index: index,
-        direction: messageDirection(item)
-      })
-    }
-
-    function selectAvatar(sourceEvent, item, index) {
-      sourceEvent.stopPropagation()
-      $emit("avatarClick", {
-        item: item,
-        index: index,
-        direction: messageDirection(item)
-      })
-    }
-
-    function selectLink(sourceEvent, link, item, index) {
-      sourceEvent.stopPropagation()
-      $emit("linkClick", {
-        link: link,
-        item: item,
-        index: index
-      })
-    }
-
-    function selectAction(sourceEvent, item, index) {
-      sourceEvent.stopPropagation()
-      $emit("action", {
-        action: item.action || "retry",
-        item: item,
-        index: index
-      })
-    }
-  }
-
-  view {
-        <section
-      {...attrs}
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--chat-bubble wire-next--variant-{variant} {class}"
-      data-one-sided="{oneSided}"
-      role="log"
-      aria-label="{ariaLabel}"
-      aria-live="polite"
-    >
-      {#if title || description}
-        <header class="wire-next__chat-header">
-          {#if title}<h3>{title}</h3>{/if}
-          {#if description}<p>{description}</p>{/if}
-        </header>
-      {/if}
-
-      {#if items.length}
-        <div class="wire-next__chat-thread">
-          {#each items as item, index}
-            <article
-              class="wire-next__chat-message"
-              data-direction="{messageDirection(item)}"
-              aria-label="{messageDirection(item) === 'outgoing' ? 'Sent message' : 'Received message'}"
-            >
-        <div class="wire-next__chat-row">
-                {#if showAvatars && (item.avatarSrc || item.avatarFallback)}
-                  <button
-                    class="wire-next__chat-avatar"
-                    type="button"
-                    aria-label="{item.avatarLabel || item.author || 'Message author'}"
-                    @click="selectAvatar(event, item, index)"
-                  >
-                    {#if item.avatarSrc}
-                      <img src="{item.avatarSrc}" alt="{item.avatarAlt || ''}" />
-                    {:else}
-                      <span>{item.avatarFallback}</span>
-                    {/if}
-                  </button>
-                {/if}
-
-                <div
-                  class="wire-next__chat-content"
-                  role="button"
-                  tabindex="0"
-                  @click="selectMessage(item, index)"
-                  @keydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); selectMessage(item, index) }"
-                >
-                  {#if item.title}<h4>{item.title}</h4>{/if}
-                  {#if item.text}<p>{item.text}</p>{/if}
-                  {#if item.bullets && item.bullets.length}
-                    <ul>
-                      {#each item.bullets as bullet}<li>{bullet}</li>{/each}
-                    </ul>
-                  {/if}
-                  {#if item.links && item.links.length}
-                    <nav aria-label="Message links">
-                      {#each item.links as link}
-                        <a
-                          href="{link.href || '#'}"
-                          @click="selectLink(event, link, item, index)"
-                        >{link.label}</a>
-                      {/each}
-                    </nav>
-                  {/if}
-                </div>
-        </div>
-
-              {#if showMetadata && (item.timestamp || item.status || item.actionLabel)}
-                <footer class="wire-next__chat-meta" data-tone="{item.statusTone || 'muted'}">
-                  {#if item.statusIcon}<span class="{item.statusIcon}" aria-hidden="true">
-        </span>{/if}
-                  {#if item.status}<span>{item.status}</span>{/if}
-                  {#if item.timestamp}<time datetime="{item.datetime || ''}">{item.timestamp}</time>{/if}
-                  {#if item.actionLabel}
-                    <button type="button" @click="selectAction(event, item, index)">
-                      {item.actionLabel}
-                    </button>
-                  {/if}
-                </footer>
-              {/if}
-            </article>
-          {/each}
-        </div>
-      {/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/checkbox.wrn b/app/pages/components/checkbox.wrn deleted file mode 100644 index 4b8d8a62..00000000 --- a/app/pages/components/checkbox.wrn +++ /dev/null @@ -1,91 +0,0 @@ -page Checkboxcomponent { - seo { - title = "Checkbox component" - description = "Theme-aware, responsive checkbox component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

Checkbox

Theme-aware, responsive checkbox component.

@wrnexus/ui 0.5.027 props0 slots5 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Checkbox
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Checkbox"
-/>

Legacy mount name: data-component="Checkbox".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Checkbox"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
valuestringOptional"on"
valuesstringOptional[]
optionsstringOptional[]
checkedbooleanOptionalfalse
indeterminatebooleanOptionalfalse
orientationstringOptional"vertical"
cardbooleanOptionalfalse
rightAlignedbooleanOptionalfalse
listbooleanOptionalfalse
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • invalid

Source contract

Installed source: components/Checkbox.wrn

component Checkbox {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Checkbox"
-    hiddenLabel = false
-    placeholder = ""
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    value = "on"
-    values = []
-    options = []
-    checked = false
-    indeterminate = false
-    orientation = "vertical"
-    card = false
-    rightAligned = false
-    list = false
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  functions {
-    function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { checked: sourceEvent.currentTarget.checked, value: sourceEvent.currentTarget.value, values: values, name: name, sourceEvent: sourceEvent }) }
-    function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice-field wire-next--checkbox-field {class}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-orientation="{orientation}" data-card="{card}" data-list="{list}" data-right-aligned="{rightAligned}">
-        <div class="wire-next__field-heading">{#if options.length || cornerHint}<span class="{hiddenLabel ? 'wire-next__sr-only' : ''}">{label}</span>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}{/if}</div>
-      {#if options.length}
-        <div class="wire-next__checkbox-group" role="group" aria-label="{hiddenLabel ? label : ''}" aria-invalid="{error ? 'true' : 'false'}">
-          {#each options as option}
-            <label class="wire-next__choice wire-next--checkbox" for="{id || name}-{option.value}" data-variant="{variant}" data-disabled="{disabled || option.disabled}">
-        <input id="{id || name}-{option.value}" type="checkbox" name="{name}" value="{option.value}" checked="{values.includes(option.value) || option.checked}" disabled="{disabled || option.disabled}" required="{required}" readonly="{readonly}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />
-        <span class="wire-next__choice-copy">
-        <strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
-        </label>
-          {/each}
-        </div>
-      {:else}
-        <label class="wire-next__choice wire-next--checkbox" for="{id || name}" data-variant="{variant}" data-icon-position="{iconPosition}">
-        <input id="{id || name}" type="checkbox" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-checked="{indeterminate ? 'mixed' : (checked ? 'true' : 'false')}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />
-          {#if icon}<span class="{icon}" aria-hidden="true">
-        </span>{/if}
-          <span class="{hiddenLabel || cornerHint ? 'wire-next__sr-only' : ''}">{label}</span>
-        </label>
-      {/if}
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/clipboard.wrn b/app/pages/components/clipboard.wrn deleted file mode 100644 index 3b3a07ae..00000000 --- a/app/pages/components/clipboard.wrn +++ /dev/null @@ -1,49 +0,0 @@ -page Clipboardcomponent { - seo { - title = "Clipboard component" - description = "Theme-aware, responsive clipboard component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

Clipboard

Theme-aware, responsive clipboard component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Clipboard
-  size="default"
-  color="primary"
-  title="Clipboard"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Clipboard".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Clipboard"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • copy
  • success
  • error

Source contract

Installed source: components/Clipboard.wrn

component Clipboard {
-  props {
-    @event copy = function
-    @event success = function
-    @event error = function
-    size = "default"
-    color = "primary"
-    title = "Clipboard"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--clipboard wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/collapse.wrn b/app/pages/components/collapse.wrn deleted file mode 100644 index 691eab3f..00000000 --- a/app/pages/components/collapse.wrn +++ /dev/null @@ -1,122 +0,0 @@ -page Collapsecomponent { - seo { - title = "Collapse component" - description = "Theme-aware, responsive collapse component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Collapse

Theme-aware, responsive collapse component.

@wrnexus/ui 0.5.08 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Collapse
-  size="default"
-  color="primary"
-  items="[]"
-  multiple="false"
-  mode="panel"
-/>

Legacy mount name: data-component="Collapse".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
itemsstringOptional[]
multiplebooleanOptionalfalse
modestringOptional"panel"
initialOpenIndexesstringOptional[]
ariaLabelstringOptional"Collapsible content"
classstringOptional""

Slots

  • default

Events

  • toggle
  • open
  • close

Source contract

Installed source: components/Collapse.wrn

component Collapse {
-  props {
-    @event toggle = function
-    @event open = function
-    @event close = function
-    size = "default"
-    color = "primary"
-    items = []
-    multiple = false
-    mode = "panel"
-    initialOpenIndexes = []
-    ariaLabel = "Collapsible content"
-    class = ""
-  }
-
-  state openIndexes = initialOpenIndexes
-
-  functions {
-    function isOpen(index) {
-      return openIndexes.includes(index)
-    }
-
-    function toggleItem(index, item, opening) {
-      if (item.disabled) {
-        return
-      }
-      opening = !isOpen(index)
-      if (opening) {
-        if (multiple) {
-          openIndexes = openIndexes.concat(index)
-        } else {
-          openIndexes = [index]
-        }
-        $emit("open", { index: index, item: item })
-      } else {
-        openIndexes = openIndexes.filter((openIndex) => openIndex !== index)
-        $emit("close", { index: index, item: item })
-      }
-      $emit("toggle", {
-        index: index,
-        item: item,
-        open: opening,
-        openIndexes: openIndexes
-      })
-    }
-  }
-
-  view {
-        <section
-      {...attrs}
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--collapse {class}"
-      data-mode="{mode}"
-      aria-label="{ariaLabel}"
-    >
-      {#each items as item, index}
-        <article class="wire-next__collapse-item" data-open="{isOpen(index)}">
-          {#if mode === "inline" && item.preview}
-            <p class="wire-next__collapse-preview">{item.preview}</p>
-          {/if}
-
-          <button
-            class="wire-next__collapse-trigger"
-            type="button"
-            aria-expanded="{isOpen(index) ? 'true' : 'false'}"
-            aria-controls="{item.id || 'collapse-panel-' + index}"
-            disabled="{item.disabled}"
-            @click="toggleItem(index, item)"
-          >
-        <span>{isOpen(index) ? (item.closeLabel || "Read less") : item.label}</span>
-        <span class="icon-[lucide--chevron-down] wire-next__collapse-chevron" aria-hidden="true">
-        </span>
-        </button>
-        <div
-            id="{item.id || 'collapse-panel-' + index}"
-            class="wire-next__collapse-panel"
-            data-open="{isOpen(index)}"
-            aria-hidden="{isOpen(index) ? 'false' : 'true'}"
-            inert="{!isOpen(index)}"
-          >
-        <div class="wire-next__collapse-content">
-              {#if item.title}<h4>{item.title}</h4>{/if}
-              {#if item.content}<p>{item.content}</p>{/if}
-              {#if item.links && item.links.length}
-                <nav aria-label="Related links">
-                  {#each item.links as link}<a href="{link.href || '#'}">{link.label}</a>{/each}
-                </nav>
-              {/if}
-            </div>
-        </div>
-        </article>
-      {/each}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/color-picker.wrn b/app/pages/components/color-picker.wrn deleted file mode 100644 index 99de1282..00000000 --- a/app/pages/components/color-picker.wrn +++ /dev/null @@ -1,69 +0,0 @@ -page ColorPickercomponent { - seo { - title = "ColorPicker component" - description = "Theme-aware, responsive color picker component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

ColorPicker

Theme-aware, responsive color picker component.

@wrnexus/ui 0.5.019 props0 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ColorPicker
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Color"
-/>

Legacy mount name: data-component="ColorPicker".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Color"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
valuestringOptional"#2563eb"
iconstringOptional""
iconPositionstringOptional"start"
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
variantstringOptional"normal"
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur

Source contract

Installed source: components/ColorPicker.wrn

component ColorPicker {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Color"
-    hiddenLabel = false
-    placeholder = ""
-    value = "#2563eb"
-    icon = ""
-    iconPosition = "start"
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    variant = "normal"
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  functions {
-    function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
-    function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--color-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__color-control" data-icon-position="{iconPosition}">{#if icon}<span class="{icon}" aria-hidden="true">
-        </span>{/if}<input id="{id || name}" type="color" name="{name}" value="{value}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" />
-        <output>{value}</output>
-        </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/columns.wrn b/app/pages/components/columns.wrn deleted file mode 100644 index 8d020266..00000000 --- a/app/pages/components/columns.wrn +++ /dev/null @@ -1,42 +0,0 @@ -page Columnscomponent { - seo { - title = "Columns component" - description = "Theme-aware, responsive columns component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Columns

Theme-aware, responsive columns component.

@wrnexus/ui 0.5.06 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Columns
-  size="default"
-  color="primary"
-  columns="2"
-  gap="md"
-  maxWidth="xl"
-/>

Legacy mount name: data-component="Columns".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
columnsnumberOptional2
gapstringOptional"md"
maxWidthstringOptional"xl"
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/Columns.wrn

component Columns {
-  props {
-    size = "default"
-    color = "primary"
-    columns = 2
-    gap = "md"
-    maxWidth = "xl"
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--columns wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}">
-        <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/combo-box.wrn b/app/pages/components/combo-box.wrn deleted file mode 100644 index a1ac467f..00000000 --- a/app/pages/components/combo-box.wrn +++ /dev/null @@ -1,420 +0,0 @@ -page ComboBoxcomponent { - seo { - title = "ComboBox component" - description = "Editable autocomplete combobox with local and remote suggestions." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

ComboBox

Editable autocomplete combobox with local and remote suggestions.

@wrnexus/ui 0.5.041 props0 slots8 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ComboBox
-  size="default"
-  color="primary"
-  label="ComboBox"
-  name="name"
-  value="value"
-/>

Legacy mount name: data-component="ComboBox".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"ComboBox"
namestringOptional""
valuestringOptional""
optionsstringOptional[]
groupsstringOptional[]
placeholderstringOptional"Search or select an option"
searchPlaceholderstringOptional"Start typing…"
clearablebooleanOptionaltrue
allowCustomValuebooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
invalidbooleanOptionalfalse
validationMessagestringOptional""
helpTextstringOptional""
loadingbooleanOptionalfalse
loadingLabelstringOptional"Loading suggestions…"
emptyLabelstringOptional"No matching options"
clearLabelstringOptional"Clear value"
toggleLabelstringOptional"Toggle suggestions"
searchModestringOptional"contains"
searchFieldsstringOptional"label,description"
minSearchLengthnumberOptional0
searchResultLimitnumberOptional0
optionTemplatestringOptional"default"
defaultOpenbooleanOptionalfalse
closeOnSelectbooleanOptionaltrue
fixedbooleanOptionalfalse
placementstringOptional"bottom"
autocompletestringOptional"off"
remotebooleanOptionalfalse
remoteUrlstringOptional""
remoteQueryParamstringOptional"q"
remoteDebouncenumberOptional250
remoteAutoLoadbooleanOptionaltrue
infinitebooleanOptionalfalse
hasMorebooleanOptionalfalse
pagenumberOptional1
loadMoreLabelstringOptional"Load more"
classstringOptional""

Slots

This component does not declare a slot.

Events

  • search
  • select
  • change
  • clear
  • open
  • close
  • load
  • error

Source contract

Installed source: components/Combobox.wrn

component ComboBox {
-  props {
-    size = "default"
-    color = "primary"
-    label = "ComboBox"
-    name = ""
-    value = ""
-    options = []
-    groups = []
-    placeholder = "Search or select an option"
-    searchPlaceholder = "Start typing…"
-    clearable = true
-    allowCustomValue = false
-    disabled = false
-    required = false
-    invalid = false
-    validationMessage = ""
-    helpText = ""
-    loading = false
-    loadingLabel = "Loading suggestions…"
-    emptyLabel = "No matching options"
-    clearLabel = "Clear value"
-    toggleLabel = "Toggle suggestions"
-    searchMode = "contains"
-    searchFields = "label,description"
-    minSearchLength = 0
-    searchResultLimit = 0
-    optionTemplate = "default"
-    defaultOpen = false
-    closeOnSelect = true
-    fixed = false
-    placement = "bottom"
-    autocomplete = "off"
-    remote = false
-    remoteUrl = ""
-    remoteQueryParam = "q"
-    remoteDebounce = 250
-    remoteAutoLoad = true
-    infinite = false
-    hasMore = false
-    page = 1
-    loadMoreLabel = "Load more"
-    class = ""
-    @event search = function
-    @event select = function
-    @event change = function
-    @event clear = function
-    @event open = function
-    @event close = function
-    @event load = function
-    @event error = function
-  }
-
-  state open = defaultOpen
-  state query = ""
-  state selectedValue = value
-  state activeIndex = -1
-
-  functions {
-    function allOptions() {
-      return [...groups.flatMap((group) => group.options || []), ...options]
-    }
-
-    function searchableText(option) {
-      return ((option.label || "") + " " + (option.description || "")).toLowerCase()
-    }
-
-    function matches(option) {
-      if (!query || query.length < minSearchLength) {
-        return true
-      }
-      if (searchMode === "startsWith") {
-        return searchableText(option).startsWith(query.toLowerCase())
-      }
-      if (searchMode === "exact") {
-        return searchableText(option) === query.toLowerCase()
-      }
-      return searchableText(option).includes(query.toLowerCase())
-    }
-
-    function matchingOptions(list) {
-      return list.filter((option) => matches(option))
-    }
-
-    function visibleOptions(list) {
-      if (searchResultLimit > 0) {
-        return matchingOptions(list).slice(0, searchResultLimit)
-      }
-      return matchingOptions(list)
-    }
-
-    function flatVisibleOptions() {
-      return visibleOptions(allOptions())
-    }
-
-    function isSelected(option) {
-      return selectedValue === option.value
-    }
-
-    function selectedOptions() {
-      return allOptions().filter((option) => isSelected(option))
-    }
-
-    function selectedText() {
-      return selectedOptions().length ? selectedOptions()[0].label : selectedValue
-    }
-
-    function inputText() {
-      return query || selectedText()
-    }
-
-    function shouldShowDropdown() {
-      if (!open) {
-        return false
-      }
-      if (loading) {
-        return true
-      }
-      if (remote && query.length !== 0 && query.length < minSearchLength) {
-        return true
-      }
-      if (flatVisibleOptions().length) {
-        return true
-      }
-      return infinite && hasMore
-    }
-
-    function canSelect(option) {
-      return !option.disabled
-    }
-
-    function chooseOption(option) {
-      if (!canSelect(option)) {
-        return
-      }
-      selectedValue = option.value
-      query = option.label
-      activeIndex = optionIndex(option)
-      if (closeOnSelect) {
-        open = false
-      }
-    }
-
-    function updateQuery(event) {
-      query = event.target.value
-      selectedValue = allowCustomValue ? event.target.value : ""
-      open = true
-      activeIndex = flatVisibleOptions().length ? 0 : -1
-    }
-
-    function clearValue(event) {
-      if (event) {
-        event.stopPropagation()
-      }
-      query = ""
-      selectedValue = ""
-      activeIndex = -1
-      open = false
-    }
-
-    function openDropdown() {
-      if (!disabled) {
-        open = true
-        activeIndex = flatVisibleOptions().length ? 0 : -1
-      }
-    }
-
-    function closeDropdown() {
-      open = false
-    }
-
-    function toggle() {
-      if (open) {
-        closeDropdown()
-      } else {
-        openDropdown()
-      }
-    }
-
-    function setValue(nextValue) {
-      selectedValue = nextValue
-      query = ""
-    }
-
-    function moveActive(direction) {
-      if (!flatVisibleOptions().length) {
-        return
-      }
-      activeIndex = (activeIndex + direction + flatVisibleOptions().length) % flatVisibleOptions().length
-    }
-
-    function handleKeydown(event) {
-      if (disabled) {
-        return
-      }
-      if (event.key === "ArrowDown") {
-        event.preventDefault()
-        openDropdown()
-        moveActive(1)
-      } else if (event.key === "ArrowUp") {
-        event.preventDefault()
-        openDropdown()
-        moveActive(-1)
-      } else if (event.key === "Enter" && open && activeIndex >= 0) {
-        event.preventDefault()
-        chooseOption(flatVisibleOptions()[activeIndex])
-      } else if (event.key === "Escape") {
-        closeDropdown()
-      } else if (event.key === "Home" && open) {
-        event.preventDefault()
-        activeIndex = 0
-      } else if (event.key === "End" && open) {
-        event.preventDefault()
-        activeIndex = flatVisibleOptions().length - 1
-      }
-    }
-
-    function optionIndex(option) {
-      return flatVisibleOptions().findIndex((item) => item.value === option.value)
-    }
-  }
-
-  view {
-        <div
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--advanced-select wire-next--combobox {open ? 'wire-next--open' : ''} {fixed ? 'wire-next--advanced-select-fixed' : ''} {invalid ? 'wire-next--invalid' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-      data-placement="{placement}"
-      data-search-mode="{searchMode}"
-      data-remote="{remote ? 'true' : 'false'}"
-      data-remote-url="{remoteUrl}"
-      data-remote-query-param="{remoteQueryParam}"
-      data-remote-debounce="{remoteDebounce}"
-      data-remote-auto-load="{remoteAutoLoad ? 'true' : 'false'}"
-      data-infinite="{infinite ? 'true' : 'false'}"
-      data-page="{page}"
-      data-wrn-select
-      data-wrn-combobox
-      @focusout="if (!event.currentTarget.contains(event.relatedTarget)) { closeDropdown() }"
-    >
-        <label id="{name}-label" for="{name}-input">{label}</label>
-        <div class="wire-next__select-control wire-next__combobox-control">
-        <span class="wire-next__search-icon icon-[lucide--search]" aria-hidden="true">
-        </span>
-        <input
-          {...attrs}
-          id="{name}-input"
-          class="wire-next__select-trigger wire-next__combobox-input"
-          type="text"
-          role="combobox"
-          name="{allowCustomValue ? name : ''}"
-          value="{inputText()}"
-          placeholder="{placeholder}"
-          autocomplete="{autocomplete}"
-          aria-autocomplete="list"
-          aria-haspopup="listbox"
-          aria-expanded="{open}"
-          aria-controls="{name}-listbox"
-          aria-labelledby="{name}-label"
-          aria-invalid="{invalid}"
-          disabled="{disabled}"
-          required="{required && allowCustomValue}"
-          @focus="openDropdown()"
-          @click="openDropdown()"
-          @input="updateQuery(event)"
-          @keydown="handleKeydown(event)"
-        />
-        <button
-          type="button"
-          class="wire-next__clear-select"
-          data-show="clearable && inputText() && !disabled"
-          aria-label="{clearLabel}"
-          title="{clearLabel}"
-          @click="clearValue(event)"
-        >
-        <span class="icon-[lucide--x]" aria-hidden="true">
-        </span>
-        </button>
-        <button
-          type="button"
-          class="wire-next__combobox-toggle"
-          aria-label="{toggleLabel}"
-          tabindex="-1"
-          disabled="{disabled}"
-          @click="toggle()"
-        >
-        <span class="wire-next__select-chevron icon-[lucide--chevrons-up-down]" aria-hidden="true">
-        </span>
-        </button>
-        </div>
-        <div
-        class="wire-next__select-dropdown {fixed ? 'wire-next__select-dropdown--fixed' : ''}"
-        data-show="shouldShowDropdown()"
-        style="{shouldShowDropdown() ? '' : 'display: none'}"
-      >
-        <div
-          class="wire-next__select-message"
-          data-show="remote && query.length !== 0 && query.length < minSearchLength"
-        >Enter at least {minSearchLength} characters.</div>
-        <div class="wire-next__select-message" data-show="loading" role="status">
-        <i class="wire-spinner wire-spinner--inline" aria-hidden="true">
-        </i>
-          {loadingLabel}
-        </div>
-        <div
-          id="{name}-listbox"
-          class="wire-next__select-list"
-          data-show="(!remote || (query.length === 0 && remoteAutoLoad) || query.length >= minSearchLength) && !loading"
-          role="listbox"
-        >
-          {#each groups as group}
-            {#if visibleOptions(group.options || []).length}
-              <div class="wire-next__option-group" role="group" aria-label="{group.label}">
-        <div class="wire-next__group-label">{group.label}</div>
-                {#each group.options || [] as option}
-                  <button
-                    type="button"
-                    class="wire-next__select-option {isSelected(option) ? 'wire-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wire-next__select-option--active' : ''}"
-                    data-option-value="{option.value}"
-                    data-show="matches(option)"
-                    role="option"
-                    aria-selected="{isSelected(option)}"
-                    disabled="{!canSelect(option)}"
-                    @mouseenter="activeIndex = optionIndex(option)"
-                    @mousedown="event.preventDefault()"
-                    @click="chooseOption(option)"
-                  >
-                    {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true">
-        </i>{/if}
-                    {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
-                    {#if optionTemplate === "color" && option.color}<i class="wire-next__color-dot" style="--option-color: {option.color}">
-        </i>{/if}
-                    <span>
-        <strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
-        <i class="wire-next__check icon-[lucide--check]" data-show="isSelected(option)" aria-hidden="true">
-        </i>
-        </button>
-                {/each}
-              </div>
-            {/if}
-          {/each}
-
-          {#each options as option}
-            <button
-              type="button"
-              class="wire-next__select-option {isSelected(option) ? 'wire-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wire-next__select-option--active' : ''}"
-              data-option-value="{option.value}"
-              data-show="matches(option)"
-              role="option"
-              aria-selected="{isSelected(option)}"
-              disabled="{!canSelect(option)}"
-              @mouseenter="activeIndex = optionIndex(option)"
-              @mousedown="event.preventDefault()"
-              @click="chooseOption(option)"
-            >
-              {#if optionTemplate === "icon" && option.icon}<i class="{option.icon}" aria-hidden="true">
-        </i>{/if}
-              {#if optionTemplate === "avatar" && option.avatar}<img src="{option.avatar}" alt="" />{/if}
-              {#if optionTemplate === "color" && option.color}<i class="wire-next__color-dot" style="--option-color: {option.color}">
-        </i>{/if}
-              <span>
-        <strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
-        <i class="wire-next__check icon-[lucide--check]" data-show="isSelected(option)" aria-hidden="true">
-        </i>
-        </button>
-          {/each}
-        </div>
-        <button
-          type="button"
-          class="wire-next__load-more"
-          data-wrn-select-load-more
-          data-show="infinite && hasMore"
-        >{loadMoreLabel}</button>
-        </div>
-
-      {#if !allowCustomValue}
-        <input
-          type="hidden"
-          name="{name}"
-          value="{selectedValue}"
-          required="{required}"
-          data-error="{name}"
-          aria-describedby="{validationMessage ? `${name}-validation` : helpText ? `${name}-help` : ''}"
-        />
-      {/if}
-
-      {#if helpText && !validationMessage}<small id="{name}-help">{helpText}</small>{/if}
-      <small
-        id="{name}-validation"
-        class="wire-next__validation"
-        data-error="{name}"
-      >{validationMessage}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/confetti.wrn b/app/pages/components/confetti.wrn deleted file mode 100644 index 44776a36..00000000 --- a/app/pages/components/confetti.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Confetticomponent { - seo { - title = "Confetti component" - description = "Theme-aware, responsive confetti component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

Confetti

Theme-aware, responsive confetti component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Confetti
-  size="default"
-  color="primary"
-  title="Confetti"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Confetti".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Confetti"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • start
  • complete

Source contract

Installed source: components/Confetti.wrn

component Confetti {
-  props {
-    @event start = function
-    @event complete = function
-    size = "default"
-    color = "primary"
-    title = "Confetti"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--confetti wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/container.wrn b/app/pages/components/container.wrn deleted file mode 100644 index 4db9b403..00000000 --- a/app/pages/components/container.wrn +++ /dev/null @@ -1,42 +0,0 @@ -page Containercomponent { - seo { - title = "Container component" - description = "Theme-aware, responsive container component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Container

Theme-aware, responsive container component.

@wrnexus/ui 0.5.06 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Container
-  size="default"
-  color="primary"
-  columns="2"
-  gap="md"
-  maxWidth="xl"
-/>

Legacy mount name: data-component="Container".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
columnsnumberOptional2
gapstringOptional"md"
maxWidthstringOptional"xl"
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/container.wrn

component Container {
-  props {
-    size = "default"
-    color = "primary"
-    columns = 2
-    gap = "md"
-    maxWidth = "xl"
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--container wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}">
-        <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/context-menu.wrn b/app/pages/components/context-menu.wrn deleted file mode 100644 index 826c90e7..00000000 --- a/app/pages/components/context-menu.wrn +++ /dev/null @@ -1,52 +0,0 @@ -page ContextMenucomponent { - seo { - title = "ContextMenu component" - description = "Theme-aware, responsive context menu component." - } - - view { -
- -
- W WRNexusJS - - -
- -
overlays component

ContextMenu

Theme-aware, responsive context menu component.

@wrnexus/ui 0.5.08 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ContextMenu
-  size="default"
-  color="primary"
-  title="Context Menu"
-  description="description"
-  open="false"
-/>

Legacy mount name: data-component="ContextMenu".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Context Menu"
descriptionstringOptional""
openbooleanOptionalfalse
placementstringOptional"bottom"
closeLabelstringOptional"Close"
classstringOptional""

Slots

  • default

Events

  • open
  • close
  • select

Source contract

Installed source: components/ContextMenu.wrn

component ContextMenu {
-  props {
-    @event open = function
-    @event close = function
-    @event select = function
-    size = "default"
-    color = "primary"
-    title = "Context Menu"
-    description = ""
-    open = false
-    placement = "bottom"
-    closeLabel = "Close"
-    class = ""
-  }
-  view {
-        <div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--context-menu wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}">
-        <header>
-        <strong>{title}</strong>
-        <button type="button" aria-label="{closeLabel}">×</button>
-        </header>
-      {#if description}<p>{description}</p>{/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/copy-markup.wrn b/app/pages/components/copy-markup.wrn deleted file mode 100644 index 7b16795e..00000000 --- a/app/pages/components/copy-markup.wrn +++ /dev/null @@ -1,53 +0,0 @@ -page CopyMarkupcomponent { - seo { - title = "CopyMarkup component" - description = "Theme-aware, responsive copy markup component." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

CopyMarkup

Theme-aware, responsive copy markup component.

@wrnexus/ui 0.5.013 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<CopyMarkup
-  size="default"
-  color="primary"
-  label="Copy Markup"
-  name="name"
-  value="value"
-/>

Legacy mount name: data-component="CopyMarkup".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Copy Markup"
namestringOptional""
valuestringOptional""
placeholderstringOptional""
typestringOptional"text"
minstringOptional""
maxstringOptional""
stepstringOptional""
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • copy
  • success
  • error

Source contract

Installed source: components/CopyMarkup.wrn

component CopyMarkup {
-  props {
-    @event copy = function
-    @event success = function
-    @event error = function
-    size = "default"
-    color = "primary"
-    label = "Copy Markup"
-    name = ""
-    value = ""
-    placeholder = ""
-    type = "text"
-    min = ""
-    max = ""
-    step = ""
-    disabled = false
-    required = false
-    class = ""
-  }
-  view {
-        <label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--copy-markup {class}">
-        <span>{label}</span>
-        <input {...attrs} type="{type}" name="{name}" value="{value}" placeholder="{placeholder}" min="{min}" max="{max}" step="{step}" disabled="{disabled}" required="{required}" />
-        </label>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/custom-scrollbar.wrn b/app/pages/components/custom-scrollbar.wrn deleted file mode 100644 index 2819d613..00000000 --- a/app/pages/components/custom-scrollbar.wrn +++ /dev/null @@ -1,43 +0,0 @@ -page CustomScrollbarcomponent { - seo { - title = "CustomScrollbar component" - description = "Theme-aware, responsive custom scrollbar component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

CustomScrollbar

Theme-aware, responsive custom scrollbar component.

@wrnexus/ui 0.5.06 props1 slots1 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<CustomScrollbar
-  size="default"
-  color="primary"
-  columns="2"
-  gap="md"
-  maxWidth="xl"
-/>

Legacy mount name: data-component="CustomScrollbar".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
columnsnumberOptional2
gapstringOptional"md"
maxWidthstringOptional"xl"
classstringOptional""

Slots

  • default

Events

  • scroll

Source contract

Installed source: components/CustomScrollbar.wrn

component CustomScrollbar {
-  props {
-    @event scroll = function
-    size = "default"
-    color = "primary"
-    columns = 2
-    gap = "md"
-    maxWidth = "xl"
-    class = ""
-  }
-  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>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/data-map.wrn b/app/pages/components/data-map.wrn deleted file mode 100644 index 7031f501..00000000 --- a/app/pages/components/data-map.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page DataMapcomponent { - seo { - title = "DataMap component" - description = "Theme-aware, responsive data map component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

DataMap

Theme-aware, responsive data map component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<DataMap
-  size="default"
-  color="primary"
-  title="Data Map"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="DataMap".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Data Map"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select
  • change

Source contract

Installed source: components/DataMap.wrn

component DataMap {
-  props {
-    @event select = function
-    @event change = function
-    size = "default"
-    color = "primary"
-    title = "Data Map"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--data-map wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/data-table.wrn b/app/pages/components/data-table.wrn deleted file mode 100644 index d28b729d..00000000 --- a/app/pages/components/data-table.wrn +++ /dev/null @@ -1,55 +0,0 @@ -page DataTablecomponent { - seo { - title = "DataTable component" - description = "Theme-aware, responsive data table component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

DataTable

Theme-aware, responsive data table component.

@wrnexus/ui 0.5.07 props1 slots5 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<DataTable
-  size="default"
-  color="primary"
-  caption="Data Table"
-  columns="[]"
-  rows="[]"
-/>

Legacy mount name: data-component="DataTable".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
captionstringOptional"Data Table"
columnsstringOptional[]
rowsstringOptional[]
stripedbooleanOptionaltrue
classstringOptional""

Slots

  • default

Events

  • sort
  • select
  • change
  • rowClick
  • pageChange

Source contract

Installed source: components/DataTable.wrn

component DataTable {
-  props {
-    @event sort = function
-    @event select = function
-    @event change = function
-    @event rowClick = function
-    @event pageChange = function
-    size = "default"
-    color = "primary"
-    caption = "Data Table"
-    columns = []
-    rows = []
-    striped = true
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}">
-        <table>
-        <caption>{caption}</caption>
-        <thead>
-        <tr>{#each columns as column}<th>{column.label}</th>{/each}</tr>
-        </thead>
-        <tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody>
-        </table>
-        <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/date-picker.wrn b/app/pages/components/date-picker.wrn deleted file mode 100644 index efa11523..00000000 --- a/app/pages/components/date-picker.wrn +++ /dev/null @@ -1,102 +0,0 @@ -page DatePickercomponent { - seo { - title = "DatePicker component" - description = "Theme-aware, responsive date picker component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

DatePicker

Theme-aware, responsive date picker component.

@wrnexus/ui 0.5.025 props0 slots7 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<DatePicker
-  size="default"
-  color="primary"
-  label="Date Picker"
-  id="id"
-  name="name"
-/>

Legacy mount name: data-component="DatePicker".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Date Picker"
idstringOptional""
namestringOptional""
valuestringOptional""
placeholderstringOptional""
typestringOptional"date"
localestringOptional"en-US"
firstDayOfWeeknumberOptional0
monthsstringOptional[{ label: "Jan", value: "01" }, { label: "Feb", value: "02" }, { label: "Mar", value: "03" }, { label: "Apr", value: "04" }, { label: "May", value: "05" }, { label: "Jun", value: "06" }, { label: "Jul", value: "07" }, { label: "Aug", value: "08" }, { label: "Sep", value: "09" }, { label: "Oct", value: "10" }, { label: "Nov", value: "11" }, { label: "Dec", value: "12" }]
daysstringOptional[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
yearsstringOptional[2024, 2025, 2026, 2027, 2028, 2029, 2030]
minstringOptional""
maxstringOptional""
stepstringOptional""
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
variantstringOptional"normal"
inlinebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • open
  • close
  • focus
  • blur
  • invalid

Source contract

Installed source: components/DatePicker.wrn

component DatePicker {
-  props {
-    @event input = function
-    @event change = function
-    @event open = function
-    @event close = function
-    @event focus = function
-    @event blur = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    label = "Date Picker"
-    id = ""
-    name = ""
-    value = ""
-    placeholder = ""
-    type = "date"
-    locale = "en-US"
-    firstDayOfWeek = 0
-    months = [{ label: "Jan", value: "01" }, { label: "Feb", value: "02" }, { label: "Mar", value: "03" }, { label: "Apr", value: "04" }, { label: "May", value: "05" }, { label: "Jun", value: "06" }, { label: "Jul", value: "07" }, { label: "Aug", value: "08" }, { label: "Sep", value: "09" }, { label: "Oct", value: "10" }, { label: "Nov", value: "11" }, { label: "Dec", value: "12" }]
-    days = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
-    years = [2024, 2025, 2026, 2027, 2028, 2029, 2030]
-    min = ""
-    max = ""
-    step = ""
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    variant = "normal"
-    inline = false
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  state currentValue = value
-  state selectedYear = value ? value.split("-")[0] : ""
-  state selectedMonth = value ? value.split("-")[1] : ""
-  state selectedDay = value ? value.split("-")[2] : ""
-  state expanded = false
-  functions {
-    function openCalendar(sourceEvent) { expanded = true; $emit("open", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function updateDate(part, nextValue, sourceEvent, nextDate, detail) { if (part === "year") { selectedYear = String(nextValue) } if (part === "month") { selectedMonth = String(nextValue).padStart(2, "0") } if (part === "day") { selectedDay = String(nextValue).padStart(2, "0") } if (!selectedYear || !selectedMonth || !selectedDay) { return } nextDate = selectedYear + "-" + selectedMonth + "-" + selectedDay; if ((min && nextDate < min) || (max && nextDate > max)) { return } currentValue = nextDate; detail = { value: currentValue, year: selectedYear, month: selectedMonth, day: selectedDay, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) }
-    function finishDate(sourceEvent) { if (!currentValue) { return } expanded = false; $emit("close", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function selectDay(sourceEvent, root, input, nextDate, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--date-picker"); input = root.querySelector(".wire-next__picker-value"); nextDate = input.getAttribute("value").slice(0, 5) + root.querySelector("select").getAttribute("value") + "-" + sourceEvent.currentTarget.dataset.value; currentValue = nextDate; input.setAttribute("value", nextDate); trigger = root.querySelector(".wire-next__date-trigger span"); if (trigger) { trigger.replaceChildren(nextDate) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: nextDate, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) }
-    function clearDate(sourceEvent, detail) { if (disabled || readonly) { return } currentValue = ""; detail = { value: currentValue, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) }
-    function handleFocus(sourceEvent) { $emit("focus", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function handleBlur(sourceEvent) { $emit("blur", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function handleInvalid(sourceEvent) { $emit("invalid", { name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--date-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}">
-        <div class="wire-next__field-heading">
-        <label for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__picker-control">
-        <input id="{id || name}" class="wire-next__sr-only wire-next__picker-value" type="text" name="{name}" value="{currentValue}" required="{required}" readonly aria-hidden="true" tabindex="-1" @invalid="handleInvalid(event)" />
-        <button type="button" class="wire-next__date-trigger" disabled="{disabled || readonly}" aria-haspopup="dialog" aria-expanded="{expanded}" @click="openCalendar(event)" @focus="handleFocus(event)" @blur="handleBlur(event)">
-        <span>{currentValue || placeholder || "Select date"}</span>
-        <i class="icon-[lucide--calendar-days]" aria-hidden="true">
-        </i>
-        </button>{#if currentValue && !readonly && !disabled}<button type="button" class="wire-next__picker-clear" aria-label="Clear date" @click="clearDate(event)">×</button>{/if}</div>
-        <div class="wire-next__calendar" role="dialog" aria-label="{label}">
-        <div class="wire-next__date-selectors">
-        <label>Month<select value="{selectedMonth}" @change="updateDate('month', event.currentTarget.value, event)">{#each months as month}<option value="{month.value}" selected="{month.value === selectedMonth}">{month.label}</option>{/each}</select>
-        </label>
-        <label>Year<select value="{selectedYear}" @change="updateDate('year', event.currentTarget.value, event)">{#each years as year}<option value="{year}" selected="{String(year) === selectedYear}">{year}</option>{/each}</select>
-        </label>
-        </div>
-        <div class="wire-next__calendar-grid">{#each days as day}<button type="button" data-value="{String(day).padStart(2, '0')}" aria-pressed="{String(day).padStart(2, '0') === selectedDay}" @click="selectDay(event)">{day}</button>{/each}</div>
-        <button type="button" class="wire-next__date-done" disabled="{!currentValue}" @click="finishDate(event)">Done</button>
-        </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/device-frame.wrn b/app/pages/components/device-frame.wrn deleted file mode 100644 index d2394cf3..00000000 --- a/app/pages/components/device-frame.wrn +++ /dev/null @@ -1,63 +0,0 @@ -page DeviceFramecomponent { - seo { - title = "DeviceFrame component" - description = "Theme-aware, responsive device frame component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

DeviceFrame

Theme-aware, responsive device frame component.

@wrnexus/ui 0.5.014 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<DeviceFrame
-  size="default"
-  color="primary"
-  title="Device Frame"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="DeviceFrame".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Device Frame"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
devicestringOptional"phone"
orientationstringOptional"portrait"
srcstringOptional""
srcdocstringOptional""
frameTitlestringOptional"Device preview"
showToolbarbooleanOptionaltrue
allowstringOptional""
classstringOptional""

Slots

  • default

Events

  • change
  • rotate

Source contract

Installed source: components/DeviceFrame.wrn

component DeviceFrame {
-  props {
-    @event change = function
-    @event rotate = function
-    size = "default"
-    color = "primary"
-    title = "Device Frame"
-    description = ""
-    items = []
-    variant = "default"
-    device = "phone"
-    orientation = "portrait"
-    src = ""
-    srcdoc = ""
-    frameTitle = "Device preview"
-    showToolbar = true
-    allow = ""
-    class = ""
-  }
-  state currentOrientation = orientation
-  functions {
-    function setDevice(nextDevice, sourceEvent) { $emit("change", { device: nextDevice, orientation: currentOrientation, sourceEvent: sourceEvent }) }
-    function rotateFrame(sourceEvent) { currentOrientation = currentOrientation === "portrait" ? "landscape" : "portrait"; $emit("rotate", { device: device, orientation: currentOrientation, sourceEvent: sourceEvent }); $emit("change", { device: device, orientation: currentOrientation, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <section {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--device-frame wire-next--variant-{variant} {class}" data-device="{device}" data-orientation="{currentOrientation}">
-        <header>
-        <div>{#if title}<strong>{title}</strong>{/if}{#if description}<p>{description}</p>{/if}</div>{#if showToolbar}<div class="wire-next__device-actions" role="toolbar" aria-label="Preview device">{#each items as item}<button type="button" aria-pressed="{item.value === device}" @click="setDevice(item.value, event)">{item.label}</button>{/each}<button type="button" aria-label="Rotate preview" @click="rotateFrame(event)">↻</button>
-        </div>{/if}</header>
-        <div class="wire-next__device-shell">{#if src || srcdoc}<iframe title="{frameTitle}" src="{src}" srcdoc="{srcdoc}" allow="{allow}">
-        </iframe>{:else}<div class="wire-next__device-content">
-        <slot />
-        </div>{/if}</div>
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/divider.wrn b/app/pages/components/divider.wrn deleted file mode 100644 index 42df8d27..00000000 --- a/app/pages/components/divider.wrn +++ /dev/null @@ -1,38 +0,0 @@ -page Dividercomponent { - seo { - title = "Divider component" - description = "Theme-aware, responsive divider component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Divider

Theme-aware, responsive divider component.

@wrnexus/ui 0.5.05 props0 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Divider
-  size="default"
-  color="primary"
-  label="label"
-  orientation="horizontal"
-/>

Legacy mount name: data-component="Divider".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

This component does not declare a slot.

Events

This component does not declare a custom event.

Source contract

Installed source: components/divider.wrn

component Divider {
-  props {
-    size = "default"
-    color = "primary"
-    label = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--divider wire-next--divider-{orientation} {class}" role="separator" aria-orientation="{orientation}">{#if label}<span>{label}</span>{/if}</div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/drag-and-drop.wrn b/app/pages/components/drag-and-drop.wrn deleted file mode 100644 index d0960f0d..00000000 --- a/app/pages/components/drag-and-drop.wrn +++ /dev/null @@ -1,52 +0,0 @@ -page DragAndDropcomponent { - seo { - title = "DragAndDrop component" - description = "Theme-aware, responsive drag and drop component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

DragAndDrop

Theme-aware, responsive drag and drop component.

@wrnexus/ui 0.5.07 props1 slots6 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<DragAndDrop
-  size="default"
-  color="primary"
-  title="Drag And Drop"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="DragAndDrop".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Drag And Drop"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • dragStart
  • dragEnd
  • dragEnter
  • dragLeave
  • drop
  • change

Source contract

Installed source: components/DragAndDrop.wrn

component DragAndDrop {
-  props {
-    @event dragStart = function
-    @event dragEnd = function
-    @event dragEnter = function
-    @event dragLeave = function
-    @event drop = function
-    @event change = function
-    size = "default"
-    color = "primary"
-    title = "Drag And Drop"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--drag-and-drop wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/drawer.wrn b/app/pages/components/drawer.wrn deleted file mode 100644 index 39d227cb..00000000 --- a/app/pages/components/drawer.wrn +++ /dev/null @@ -1,51 +0,0 @@ -page Drawercomponent { - seo { - title = "Drawer component" - description = "Theme-aware, responsive drawer component." - } - - view { -
- -
- W WRNexusJS - - -
- -
overlays component

Drawer

Theme-aware, responsive drawer component.

@wrnexus/ui 0.5.08 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Drawer
-  size="default"
-  color="primary"
-  title="Drawer"
-  description="description"
-  open="false"
-/>

Legacy mount name: data-component="Drawer".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Drawer"
descriptionstringOptional""
openbooleanOptionalfalse
placementstringOptional"bottom"
closeLabelstringOptional"Close"
classstringOptional""

Slots

  • default

Events

  • open
  • close

Source contract

Installed source: components/Drawer.wrn

component Drawer {
-  props {
-    @event open = function
-    @event close = function
-    size = "default"
-    color = "primary"
-    title = "Drawer"
-    description = ""
-    open = false
-    placement = "bottom"
-    closeLabel = "Close"
-    class = ""
-  }
-  view {
-        <div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--drawer wire-next--placement-{placement} {class}" role="dialog" aria-modal="true" aria-label="{title}">
-        <header>
-        <strong>{title}</strong>
-        <button type="button" aria-label="{closeLabel}">×</button>
-        </header>
-      {#if description}<p>{description}</p>{/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/dropdown.wrn b/app/pages/components/dropdown.wrn deleted file mode 100644 index 6daed11a..00000000 --- a/app/pages/components/dropdown.wrn +++ /dev/null @@ -1,53 +0,0 @@ -page Dropdowncomponent { - seo { - title = "Dropdown component" - description = "Theme-aware, responsive dropdown component." - } - - view { -
- -
- W WRNexusJS - - -
- -
overlays component

Dropdown

Theme-aware, responsive dropdown component.

@wrnexus/ui 0.5.08 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Dropdown
-  size="default"
-  color="primary"
-  title="Dropdown"
-  description="description"
-  open="false"
-/>

Legacy mount name: data-component="Dropdown".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Dropdown"
descriptionstringOptional""
openbooleanOptionalfalse
placementstringOptional"bottom"
closeLabelstringOptional"Close"
classstringOptional""

Slots

  • default

Events

  • open
  • close
  • select
  • change

Source contract

Installed source: components/Dropdown.wrn

component Dropdown {
-  props {
-    @event open = function
-    @event close = function
-    @event select = function
-    @event change = function
-    size = "default"
-    color = "primary"
-    title = "Dropdown"
-    description = ""
-    open = false
-    placement = "bottom"
-    closeLabel = "Close"
-    class = ""
-  }
-  view {
-        <div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--dropdown wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}">
-        <header>
-        <strong>{title}</strong>
-        <button type="button" aria-label="{closeLabel}">×</button>
-        </header>
-      {#if description}<p>{description}</p>{/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/file-input.wrn b/app/pages/components/file-input.wrn deleted file mode 100644 index 62623c3b..00000000 --- a/app/pages/components/file-input.wrn +++ /dev/null @@ -1,83 +0,0 @@ -page FileInputcomponent { - seo { - title = "FileInput component" - description = "Theme-aware, responsive file input component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

FileInput

Theme-aware, responsive file input component.

@wrnexus/ui 0.5.021 props0 slots7 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<FileInput
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="File"
-/>

Legacy mount name: data-component="FileInput".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"File"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional"Choose a file"
valuestringOptional""
iconstringOptional"icon-[lucide--upload]"
iconPositionstringOptional"start"
acceptstringOptional""
multiplebooleanOptionalfalse
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
variantstringOptional"normal"
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • select
  • clear
  • invalid

Source contract

Installed source: components/FileInput.wrn

component FileInput {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event select = function
-    @event clear = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "File"
-    hiddenLabel = false
-    placeholder = "Choose a file"
-    value = ""
-    icon = "icon-[lucide--upload]"
-    iconPosition = "start"
-    accept = ""
-    multiple = false
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    variant = "normal"
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  state selectedName = ""
-  functions {
-    function handleChange(sourceEvent) {
-      sourceEvent.stopPropagation()
-      selectedName = sourceEvent.currentTarget.files && sourceEvent.currentTarget.files.length ? sourceEvent.currentTarget.files[0].name : ""
-      $emit("input", { files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
-      $emit("change", { files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
-      if (selectedName) $emit("select", { files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
-      else $emit("clear", { name: name, sourceEvent: sourceEvent })
-    }
-    function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { name: name, sourceEvent: sourceEvent }) }
-    function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--file-input {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__file-control" data-icon-position="{iconPosition}">{#if icon}<span class="{icon}" aria-hidden="true">
-        </span>{/if}<span>{selectedName || value || placeholder}</span>
-        <input id="{id || name}" type="file" name="{name}" accept="{accept}" multiple="{multiple}" disabled="{disabled}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="handleChange(event)" @change="handleChange(event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />
-        </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/file-upload-progress.wrn b/app/pages/components/file-upload-progress.wrn deleted file mode 100644 index 10916aa6..00000000 --- a/app/pages/components/file-upload-progress.wrn +++ /dev/null @@ -1,72 +0,0 @@ -page FileUploadProgresscomponent { - seo { - title = "FileUploadProgress component" - description = "Theme-aware, responsive file upload progress component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

FileUploadProgress

Theme-aware, responsive file upload progress component.

@wrnexus/ui 0.5.013 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<FileUploadProgress
-  size="default"
-  color="primary"
-  label="Progress"
-  value="50"
-  max="100"
-/>

Legacy mount name: data-component="FileUploadProgress".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Progress"
valuenumberOptional50
maxnumberOptional100
showValuebooleanOptionaltrue
fileNamestringOptional""
fileSizestringOptional""
uploadedSizestringOptional""
statusstringOptional"uploading"
cancelLabelstringOptional"Cancel upload"
retryLabelstringOptional"Retry upload"
classstringOptional""

Slots

This component does not declare a slot.

Events

  • cancel
  • retry
  • complete

Source contract

Installed source: components/FileUploadProgress.wrn

component FileUploadProgress {
-  props {
-    @event cancel = function
-    @event retry = function
-    @event complete = function
-    size = "default"
-    color = "primary"
-    label = "Progress"
-    value = 50
-    max = 100
-    showValue = true
-    fileName = ""
-    fileSize = ""
-    uploadedSize = ""
-    status = "uploading"
-    cancelLabel = "Cancel upload"
-    retryLabel = "Retry upload"
-    class = ""
-  }
-  state currentValue = value
-  state currentStatus = status
-  functions {
-    function percent() { if (!Number(max)) { return 0 } return Math.max(0, Math.min(100, Math.round(Number(currentValue) / Number(max) * 100))) }
-    function detail(sourceEvent) { return { value: currentValue, max: max, percent: percent(), status: currentStatus, fileName: fileName, sourceEvent: sourceEvent } }
-    function cancelUpload(sourceEvent) { currentStatus = "cancelled"; $emit("cancel", detail(sourceEvent)) }
-    function retryUpload(sourceEvent) { currentValue = 0; currentStatus = "uploading"; $emit("retry", detail(sourceEvent)) }
-    function completeUpload(sourceEvent) { currentValue = max; currentStatus = "complete"; $emit("complete", detail(sourceEvent)) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload-progress {class}" data-status="{currentStatus}" aria-live="polite">
-        <div class="wire-next__row">
-        <span>{#if fileName}<strong>{fileName}</strong>
-        <small>{#if uploadedSize || fileSize}{uploadedSize}{#if uploadedSize && fileSize} of {/if}{fileSize}{:else}{label}{/if}</small>{:else}<strong>{label}</strong>{/if}</span>{#if showValue}<strong>{percent()}%</strong>{/if}</div>
-        <progress value="{currentValue}" max="{max}" aria-label="{label}">
-        </progress>
-        <div class="wire-next__upload-status">
-        <span class="wire-next__upload-status-icon" aria-hidden="true">
-        </span>
-        <span>{#if currentStatus === "uploading"}Uploading securely…{:else if currentStatus === "complete"}Upload complete{:else if currentStatus === "error"}Upload failed{:else}Upload cancelled{/if}</span>
-        </div>
-        <div class="wire-next__upload-actions">{#if currentStatus === "uploading"}<button type="button" @click="cancelUpload(event)">{cancelLabel}</button>
-        <button type="button" @click="completeUpload(event)">Complete</button>{/if}{#if currentStatus === "error" || currentStatus === "cancelled"}<button type="button" @click="retryUpload(event)">{retryLabel}</button>{/if}</div>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/file-upload.wrn b/app/pages/components/file-upload.wrn deleted file mode 100644 index f2beccf2..00000000 --- a/app/pages/components/file-upload.wrn +++ /dev/null @@ -1,53 +0,0 @@ -page FileUploadcomponent { - seo { - title = "FileUpload component" - description = "Theme-aware, responsive file upload component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

FileUpload

Theme-aware, responsive file upload component.

@wrnexus/ui 0.5.07 props1 slots7 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<FileUpload
-  size="default"
-  color="primary"
-  title="File Upload"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="FileUpload".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"File Upload"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select
  • upload
  • progress
  • success
  • error
  • cancel
  • remove

Source contract

Installed source: components/FileUpload.wrn

component FileUpload {
-  props {
-    @event select = function
-    @event upload = function
-    @event progress = function
-    @event success = function
-    @event error = function
-    @event cancel = function
-    @event remove = function
-    size = "default"
-    color = "primary"
-    title = "File Upload"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/grid.wrn b/app/pages/components/grid.wrn deleted file mode 100644 index 23f1289b..00000000 --- a/app/pages/components/grid.wrn +++ /dev/null @@ -1,42 +0,0 @@ -page Gridcomponent { - seo { - title = "Grid component" - description = "Theme-aware, responsive grid component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Grid

Theme-aware, responsive grid component.

@wrnexus/ui 0.5.06 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Grid
-  size="default"
-  color="primary"
-  columns="2"
-  gap="md"
-  maxWidth="xl"
-/>

Legacy mount name: data-component="Grid".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
columnsnumberOptional2
gapstringOptional"md"
maxWidthstringOptional"xl"
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/grid.wrn

component Grid {
-  props {
-    size = "default"
-    color = "primary"
-    columns = 2
-    gap = "md"
-    maxWidth = "xl"
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--grid wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}">
-        <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/image.wrn b/app/pages/components/image.wrn deleted file mode 100644 index 4208e1d6..00000000 --- a/app/pages/components/image.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Imagecomponent { - seo { - title = "Image component" - description = "Theme-aware, responsive image component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Image

Theme-aware, responsive image component.

@wrnexus/ui 0.5.09 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Image
-  size="default"
-  color="primary"
-  src="src"
-  alt="alt"
-  width="width"
-/>

Legacy mount name: data-component="Image".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
srcstringOptional""
altstringOptional""
widthstringOptional""
heightstringOptional""
loadingstringOptional"lazy"
roundedbooleanOptionalfalse
classstringOptional""

Slots

  • default

Events

  • load
  • error

Source contract

Installed source: components/Image.wrn

component Image {
-  props {
-    @event load = function
-    @event error = function
-    size = "default"
-    color = "primary"
-    src = ""
-    alt = ""
-    width = ""
-    height = ""
-    loading = "lazy"
-    rounded = false
-    class = ""
-  }
-  view {
-        <figure class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--image {rounded ? 'wire-next--rounded' : ''} {class}">
-        <img src="{src}" alt="{alt}" width="{width}" height="{height}" loading="{loading}" />
-        <slot />
-        </figure>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/input-group.wrn b/app/pages/components/input-group.wrn deleted file mode 100644 index 5aed88c6..00000000 --- a/app/pages/components/input-group.wrn +++ /dev/null @@ -1,80 +0,0 @@ -page InputGroupcomponent { - seo { - title = "InputGroup component" - description = "Theme-aware, responsive input group component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

InputGroup

Theme-aware, responsive input group component.

@wrnexus/ui 0.5.023 props0 slots6 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<InputGroup
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Input group"
-/>

Legacy mount name: data-component="InputGroup".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Input group"
hiddenLabelbooleanOptionalfalse
valuestringOptional""
placeholderstringOptional""
typestringOptional"text"
startTextstringOptional""
endTextstringOptional""
iconstringOptional""
iconPositionstringOptional"start"
actionLabelstringOptional""
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
variantstringOptional"normal"
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • submit
  • action

Source contract

Installed source: components/InputGroup.wrn

component InputGroup {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event submit = function
-    @event action = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Input group"
-    hiddenLabel = false
-    value = ""
-    placeholder = ""
-    type = "text"
-    startText = ""
-    endText = ""
-    icon = ""
-    iconPosition = "start"
-    actionLabel = ""
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    variant = "normal"
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  functions {
-    function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
-    function handleKeydown(sourceEvent) { if (sourceEvent.key === "Enter") { sourceEvent.stopPropagation(); $emit("submit", { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) } }
-    function handleAction(sourceEvent) { $emit("action", { name: name, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--input-group {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__input-group-control">
-        {#if startText}<span class="wire-next__input-addon">{startText}</span>{/if}
-        {#if icon}<span class="{icon} wire-next__field-icon" data-position="{iconPosition}" aria-hidden="true">
-        </span>{/if}
-        <input id="{id || name}" type="{type}" name="{name}" value="{value}" placeholder="{placeholder}" readonly="{readonly}" disabled="{disabled}" required="{required}" aria-invalid="{error ? 'true' : 'false'}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @keydown="handleKeydown(event)" />
-        {#if endText}<span class="wire-next__input-addon">{endText}</span>{/if}
-        {#if actionLabel}<button type="button" disabled="{disabled}" @click="handleAction(event)">{actionLabel}</button>{/if}
-      </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/input-number.wrn b/app/pages/components/input-number.wrn deleted file mode 100644 index 201abb0f..00000000 --- a/app/pages/components/input-number.wrn +++ /dev/null @@ -1,654 +0,0 @@ -page InputNumbercomponent { - seo { - title = "InputNumber component" - description = "Theme-aware, responsive input number component." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

InputNumber

Theme-aware, responsive input number component.

@wrnexus/ui 0.5.040 props0 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<InputNumber
-  size="default"
-  color="primary"
-  variant="default"
-  id="id"
-  name="quantity"
-/>

Legacy mount name: data-component="InputNumber".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
variantstringOptional"default"
classstringOptional""
idstringOptional""
namestringOptional"quantity"
valuenumberOptional0
minstringOptional""
maxstringOptional""
stepnumberOptional1
precisionstringOptional"auto"
labelstringOptional""
descriptionstringOptional""
helpTextstringOptional""
errorstringOptional""
invalidbooleanOptionalfalse
prefixstringOptional""
suffixstringOptional""
placeholderstringOptional""
autocompletestringOptional"off"
inputModestringOptional"decimal"
ariaLabelstringOptional""
requiredbooleanOptionalfalse
disabledbooleanOptionalfalse
inputDisabledbooleanOptionalfalse
buttonsDisabledbooleanOptionalfalse
readonlybooleanOptionalfalse
allowInputbooleanOptionaltrue
keyboardbooleanOptionaltrue
wheelbooleanOptionalfalse
clampbooleanOptionaltrue
fullWidthbooleanOptionalfalse
showButtonsbooleanOptionaltrue
showValidationMessagebooleanOptionaltrue
decrementLabelstringOptional"Decrease value"
incrementLabelstringOptional"Increase value"
controlsLabelstringOptional"Quantity controls"
requiredMessagestringOptional"A value is required."
minMessagestringOptional"Value is below the minimum."
maxMessagestringOptional"Value is above the maximum."

Slots

This component does not declare a slot.

Events

  • input
  • change
  • increment
  • decrement

Source contract

Installed source: components/InputNumber.wrn

component InputNumber {
-    props {
-        size = "default"
-        color = "primary"
-        variant = "default"
-        class = ""
-
-        id = ""
-        name = "quantity"
-        value = 0
-        min = ""
-        max = ""
-        step = 1
-        precision = "auto"
-
-        label = ""
-        description = ""
-        helpText = ""
-        error = ""
-        invalid = false
-
-        prefix = ""
-        suffix = ""
-        placeholder = ""
-        autocomplete = "off"
-        inputMode = "decimal"
-        ariaLabel = ""
-
-        required = false
-        disabled = false
-        inputDisabled = false
-        buttonsDisabled = false
-        readonly = false
-        allowInput = true
-        keyboard = true
-        wheel = false
-        clamp = true
-        fullWidth = false
-        showButtons = true
-        showValidationMessage = true
-
-        decrementLabel = "Decrease value"
-        incrementLabel = "Increase value"
-        controlsLabel = "Quantity controls"
-        requiredMessage = "A value is required."
-        minMessage = "Value is below the minimum."
-        maxMessage = "Value is above the maximum."
-
-        @event input = function
-        @event change = function
-        @event increment = function
-        @event decrement = function
-    }
-
-    state currentValue = value
-    state committedValue = value
-
-    functions {
-        function inputId() {
-            if (id !== "") {
-                return id
-            }
-
-            if (name !== "") {
-                return name
-            }
-
-            return "input-number"
-        }
-
-        function descriptionId() {
-            return inputId() + "-description"
-        }
-
-        function messageId() {
-            return inputId() + "-message"
-        }
-
-        function componentColor() {
-            if (color === "secondary") {
-                return "var(--wire-color-secondary)"
-            }
-
-            if (color === "success") {
-                return "var(--wire-color-success)"
-            }
-
-            if (color === "warning") {
-                return "var(--wire-color-warning)"
-            }
-
-            if (color === "danger") {
-                return "var(--wire-color-danger)"
-            }
-
-            if (color === "info") {
-                return "var(--wire-color-info)"
-            }
-
-            return "var(--wire-color-primary)"
-        }
-
-        function hasMin() {
-            return min !== "" && min !== null && min !== undefined
-        }
-
-        function hasMax() {
-            return max !== "" && max !== null && max !== undefined
-        }
-
-        function isBlank() {
-            return currentValue === "" || currentValue === null || currentValue === undefined
-        }
-
-        function normalizedStep() {
-            return Number(step) > 0 ? Number(step) : 1
-        }
-
-        function inferredPrecision() {
-            if (precision !== "auto" && precision !== "") {
-                return Math.max(0, Number(precision) || 0)
-            }
-
-            if (String(normalizedStep()).includes(".")) {
-                return String(normalizedStep()).split(".")[1].length
-            }
-
-            return 0
-        }
-
-        function precisionFactor() {
-            return Math.pow(10, inferredPrecision())
-        }
-
-        function roundValue(nextValue) {
-            return Math.round(Number(nextValue) * precisionFactor()) / precisionFactor()
-        }
-
-        function clampValue(nextValue) {
-            nextValue = Number(nextValue)
-
-            if (hasMin() && nextValue < Number(min)) {
-                nextValue = Number(min)
-            }
-
-            if (hasMax() && nextValue > Number(max)) {
-                nextValue = Number(max)
-            }
-
-            return roundValue(nextValue)
-        }
-
-        function isBelowMin() {
-            return !isBlank() && hasMin() && Number(currentValue) < Number(min)
-        }
-
-        function isAboveMax() {
-            return !isBlank() && hasMax() && Number(currentValue) > Number(max)
-        }
-
-        function isInvalid() {
-            return (
-                invalid ||
-                error !== "" ||
-                (required && isBlank()) ||
-                isBelowMin() ||
-                isAboveMax()
-            )
-        }
-
-        function validationMessage() {
-            if (error !== "") {
-                return error
-            }
-
-            if (required && isBlank()) {
-                return requiredMessage
-            }
-
-            if (isBelowMin()) {
-                return minMessage
-            }
-
-            if (isAboveMax()) {
-                return maxMessage
-            }
-
-            return ""
-        }
-
-        function hasMessage() {
-            return (
-                helpText !== "" ||
-                (showValidationMessage && isInvalid() && validationMessage() !== "")
-            )
-        }
-
-        function describedBy() {
-            if (description !== "" && hasMessage()) {
-                return descriptionId() + " " + messageId()
-            }
-
-            if (description !== "") {
-                return descriptionId()
-            }
-
-            if (hasMessage()) {
-                return messageId()
-            }
-
-            return ""
-        }
-
-        function decrementDisabled() {
-            return (
-                disabled ||
-                readonly ||
-                buttonsDisabled ||
-                (hasMin() && !isBlank() && Number(currentValue) <= Number(min))
-            )
-        }
-
-        function incrementDisabled() {
-            return (
-                disabled ||
-                readonly ||
-                buttonsDisabled ||
-                (hasMax() && !isBlank() && Number(currentValue) >= Number(max))
-            )
-        }
-
-        function dispatchInputNumberEvent(
-            sourceEvent,
-            eventName,
-            action,
-            previousValue,
-            root,
-            customEvent
-        ) {
-            root = sourceEvent.currentTarget.closest("[data-wrn-input-number]")
-
-            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",
-                name: name,
-                value: currentValue,
-                previousValue: previousValue,
-                action: action,
-                min: hasMin() ? Number(min) : null,
-                max: hasMax() ? Number(max) : null,
-                step: normalizedStep(),
-                valid: !isInvalid()
-            })
-            root.dispatchEvent(customEvent)
-        }
-
-        function applyControlValue(nextValue, action, sourceEvent, previousValue) {
-            previousValue = currentValue
-            currentValue = clampValue(nextValue)
-            committedValue = currentValue
-
-            dispatchInputNumberEvent(
-                sourceEvent,
-                "input",
-                action,
-                previousValue
-            )
-            dispatchInputNumberEvent(
-                sourceEvent,
-                "change",
-                action,
-                previousValue
-            )
-            dispatchInputNumberEvent(
-                sourceEvent,
-                action,
-                action,
-                previousValue
-            )
-        }
-
-        function incrementValue(sourceEvent, nextValue) {
-            if (incrementDisabled()) {
-                return
-            }
-
-            if (isBlank()) {
-                nextValue = hasMin() ? Number(min) : normalizedStep()
-            } else {
-                nextValue =
-                    Number(currentValue) +
-                    normalizedStep() * (sourceEvent.shiftKey ? 10 : 1)
-            }
-
-            applyControlValue(nextValue, "increment", sourceEvent)
-        }
-
-        function decrementValue(sourceEvent, nextValue) {
-            if (decrementDisabled()) {
-                return
-            }
-
-            if (isBlank()) {
-                nextValue = hasMax() ? Number(max) : -normalizedStep()
-            } else {
-                nextValue =
-                    Number(currentValue) -
-                    normalizedStep() * (sourceEvent.shiftKey ? 10 : 1)
-            }
-
-            applyControlValue(nextValue, "decrement", sourceEvent)
-        }
-
-        function handleInput(sourceEvent, previousValue, nextValue) {
-            sourceEvent.stopPropagation()
-            previousValue = currentValue
-            nextValue = sourceEvent.target.value
-
-            if (nextValue === "") {
-                currentValue = ""
-            } else if (!Number.isNaN(Number(nextValue))) {
-                currentValue = roundValue(Number(nextValue))
-            }
-
-            dispatchInputNumberEvent(
-                sourceEvent,
-                "input",
-                "input",
-                previousValue
-            )
-        }
-
-        function handleChange(sourceEvent, previousValue) {
-            sourceEvent.stopPropagation()
-            previousValue = committedValue
-
-            if (!isBlank()) {
-                currentValue = clamp
-                    ? clampValue(currentValue)
-                    : roundValue(currentValue)
-            }
-
-            committedValue = currentValue
-
-            dispatchInputNumberEvent(
-                sourceEvent,
-                "change",
-                "change",
-                previousValue
-            )
-        }
-
-        function handleKeydown(sourceEvent) {
-            if (
-                !keyboard ||
-                disabled ||
-                readonly ||
-                inputDisabled ||
-                !allowInput
-            ) {
-                return
-            }
-
-            if (sourceEvent.key === "ArrowUp") {
-                sourceEvent.preventDefault()
-                incrementValue(sourceEvent)
-            } else if (sourceEvent.key === "ArrowDown") {
-                sourceEvent.preventDefault()
-                decrementValue(sourceEvent)
-            } else if (sourceEvent.key === "Home" && hasMin()) {
-                sourceEvent.preventDefault()
-                applyControlValue(Number(min), "decrement", sourceEvent)
-            } else if (sourceEvent.key === "End" && hasMax()) {
-                sourceEvent.preventDefault()
-                applyControlValue(Number(max), "increment", sourceEvent)
-            }
-        }
-
-        function handleWheel(sourceEvent) {
-            if (
-                !wheel ||
-                disabled ||
-                readonly ||
-                inputDisabled ||
-                !allowInput
-            ) {
-                return
-            }
-
-            sourceEvent.preventDefault()
-
-            if (sourceEvent.deltaY < 0) {
-                incrementValue(sourceEvent)
-            } else if (sourceEvent.deltaY > 0) {
-                decrementValue(sourceEvent)
-            }
-        }
-    }
-
-    view {
-        <div
-            {...attrs}
-            data-wrn-input-number
-            data-variant='{variant}'
-            data-size='{size}'
-            data-color='{color}'
-            data-value='{currentValue}'
-            data-invalid='{isInvalid() ? "true" : "false"}'
-            data-disabled='{disabled ? "true" : "false"}'
-            style='--input-number-accent: {componentColor()};'
-            class='relative flex flex-col gap-1.5 text-[var(--wire-color-text)] {fullWidth ? "w-full" : variant === "compact" ? "w-fit max-w-full" : "w-full max-w-sm"} {disabled ? "opacity-60" : ""} {class}'
-        >
-            {#if label !== "" && variant !== "labeled" && variant !== "seat"}
-            <label
-                for='{inputId()}'
-                class='inline-flex items-center gap-1 text-sm font-semibold leading-5 text-[var(--wire-color-text)]'
-            >
-        <span>{label}</span>
-                {#if required}
-                <span
-                    aria-hidden="true"
-                    class='text-[var(--wire-color-danger)]'
-                >*</span>
-                {/if}
-            </label>
-            {/if}
-
-            {#if description !== "" && variant !== "labeled" && variant !== "seat"}
-            <p
-                id='{descriptionId()}'
-                class='m-0 text-xs leading-5 text-[var(--wire-color-muted)]'
-            >
-                {description}
-            </p>
-            {/if}
-
-            <div
-                class='group flex min-w-0 overflow-hidden border bg-[var(--wire-color-surface)] text-[var(--wire-color-text)] shadow-[var(--wire-shadow-1)] transition-[border-color,box-shadow,background-color] duration-[var(--wire-motion-base)] ease-[var(--wire-ease-standard)] focus-within:border-[var(--input-number-accent)] focus-within:shadow-[0_0_0_3px_color-mix(in_srgb,var(--input-number-accent)_18%,transparent)] {variant === "compact" ? "rounded-full" : "rounded-[var(--wire-radius-sm)]"} {size === "xs" ? "min-h-8 text-xs" : size === "sm" ? "min-h-9 text-sm" : size === "lg" ? "min-h-12 text-base" : size === "xl" ? "min-h-14 text-lg" : "min-h-10 text-sm"} {isInvalid() ? "border-[var(--wire-color-danger)] focus-within:border-[var(--wire-color-danger)] focus-within:shadow-[0_0_0_3px_color-mix(in_srgb,var(--wire-color-danger)_18%,transparent)]" : "border-[var(--wire-color-border)]"} {disabled ? "cursor-not-allowed bg-[var(--wire-color-surface-2)]" : ""}'
-            >
-                {#if variant === "horizontal" && showButtons}
-                <button
-                    type="button"
-                    aria-label='{decrementLabel}'
-                    aria-controls='{inputId()}'
-                    disabled='{decrementDisabled()}'
-                    @click='decrementValue(event)'
-                    class='inline-flex shrink-0 items-center justify-center border-r border-[var(--wire-color-border)] bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}'
-                >
-        <span
-                        aria-hidden="true"
-                        class='icon-[lucide--minus] size-4'
-                    >
-        </span>
-        </button>
-                {/if}
-
-                <div
-                    class='flex min-w-0 flex-1 {variant === "labeled" ? "flex-col items-stretch justify-center gap-0.5" : variant === "seat" ? "items-center justify-between gap-3" : "items-center"} {size === "xs" ? "px-2" : size === "sm" ? "px-2.5" : size === "lg" ? "px-4" : size === "xl" ? "px-5" : "px-3"}'
-                >
-                    {#if variant === "labeled" || variant === "seat"}
-                    <div class='min-w-0 flex-1'>
-                        {#if label !== ""}
-                        <label
-                            for='{inputId()}'
-                            class='block truncate font-semibold leading-4 text-[var(--wire-color-text)] {variant === "labeled" ? "text-xs font-medium text-[var(--wire-color-muted)]" : "text-sm"}'
-                        >
-                            {label}
-                            {#if required}
-                            <span
-                                aria-hidden="true"
-                                class='ml-0.5 text-[var(--wire-color-danger)]'
-                            >*</span>
-                            {/if}
-                        </label>
-                        {/if}
-
-                        {#if description !== ""}
-                        <span
-                            id='{descriptionId()}'
-                            class='block truncate text-xs leading-4 text-[var(--wire-color-muted)]'
-                        >
-                            {description}
-                        </span>
-                        {/if}
-                    </div>
-                    {/if}
-
-                    <div
-                        class='flex min-w-0 items-center {variant === "seat" ? "w-auto shrink-0" : "w-full"}'
-                    >
-                        {#if prefix !== ""}
-                        <span
-                            aria-hidden="true"
-                            class='shrink-0 pr-1.5 text-[var(--wire-color-muted)]'
-                        >
-                            {prefix}
-                        </span>
-                        {/if}
-
-                        <input
-                            id='{inputId()}'
-                            name='{name}'
-                            type="number"
-                            value='{currentValue}'
-                            min='{min}'
-                            max='{max}'
-                            step='{normalizedStep()}'
-                            placeholder='{placeholder}'
-                            autocomplete='{autocomplete}'
-                            inputmode='{inputMode}'
-                            aria-label='{ariaLabel !== "" ? ariaLabel : label !== "" ? label : name}'
-                            aria-describedby='{describedBy()}'
-                            aria-invalid='{isInvalid() ? "true" : "false"}'
-                            aria-required='{required ? "true" : "false"}'
-                            aria-disabled='{disabled || inputDisabled ? "true" : "false"}'
-                            required='{required}'
-                            disabled='{disabled}'
-                            readonly='{readonly || inputDisabled || !allowInput}'
-                            tabindex='{inputDisabled ? "-1" : "0"}'
-                            @input='handleInput(event)'
-                            @change='handleChange(event)'
-                            @keydown='handleKeydown(event)'
-                            @wheel='handleWheel(event)'
-                            class='min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-medium leading-none text-[var(--wire-color-text)] outline-none placeholder:text-[var(--wire-color-muted)] read-only:cursor-default disabled:cursor-not-allowed [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none {variant === "horizontal" || variant === "compact" || variant === "seat" ? "text-center" : "text-left"} {variant === "seat" ? "w-10 flex-none" : "w-full"}'
-                        />
-
-                        {#if suffix !== ""}
-                        <span
-                            aria-hidden="true"
-                            class='shrink-0 pl-1.5 text-[var(--wire-color-muted)]'
-                        >
-                            {suffix}
-                        </span>
-                        {/if}
-                    </div>
-        </div>
-
-                {#if variant === "horizontal" && showButtons}
-                <button
-                    type="button"
-                    aria-label='{incrementLabel}'
-                    aria-controls='{inputId()}'
-                    disabled='{incrementDisabled()}'
-                    @click='incrementValue(event)'
-                    class='inline-flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}'
-                >
-        <span
-                        aria-hidden="true"
-                        class='icon-[lucide--plus] size-4'
-                    >
-        </span>
-        </button>
-                {:else}
-                {#if showButtons}
-                <div
-                    role="group"
-                    aria-label='{controlsLabel}'
-                    class='flex shrink-0 border-l border-[var(--wire-color-border)] {variant === "vertical" ? "flex-col" : "flex-row"}'
-                >
-        <button
-                        type="button"
-                        aria-label='{decrementLabel}'
-                        aria-controls='{inputId()}'
-                        disabled='{decrementDisabled()}'
-                        @click='decrementValue(event)'
-                        class='inline-flex items-center justify-center bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {variant === "vertical" ? "flex-1 border-b border-[var(--wire-color-border)]" : "border-r border-[var(--wire-color-border)]"} {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}'
-                    >
-        <span
-                            aria-hidden="true"
-                            class='icon-[lucide--minus] size-4'
-                        >
-        </span>
-        </button>
-        <button
-                        type="button"
-                        aria-label='{incrementLabel}'
-                        aria-controls='{inputId()}'
-                        disabled='{incrementDisabled()}'
-                        @click='incrementValue(event)'
-                        class='inline-flex items-center justify-center bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {variant === "vertical" ? "flex-1" : ""} {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}'
-                    >
-        <span
-                            aria-hidden="true"
-                            class='icon-[lucide--plus] size-4'
-                        >
-        </span>
-        </button>
-        </div>
-                {/if}
-                {/if}
-            </div>
-
-            {#if showValidationMessage && isInvalid() && validationMessage() !== ""}
-            <p
-                id='{messageId()}'
-                role="alert"
-                aria-live="polite"
-                class='m-0 flex items-center gap-1.5 text-xs leading-5 text-[var(--wire-color-danger)]'
-            >
-        <span
-                    aria-hidden="true"
-                    class='icon-[lucide--circle-alert] size-3.5 shrink-0'
-                >
-        </span>
-        <span>{validationMessage()}</span>
-        </p>
-            {/if}
-
-            {#if (!showValidationMessage || !isInvalid() || validationMessage() === "") && helpText !== ""}
-            <p
-                id='{messageId()}'
-                class='m-0 text-xs leading-5 text-[var(--wire-color-muted)]'
-            >
-                {helpText}
-            </p>
-            {/if}
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/input.wrn b/app/pages/components/input.wrn deleted file mode 100644 index 7a5bfb8d..00000000 --- a/app/pages/components/input.wrn +++ /dev/null @@ -1,120 +0,0 @@ -page Inputcomponent { - seo { - title = "Input component" - description = "Theme-aware, responsive input component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

Input

Theme-aware, responsive input component.

@wrnexus/ui 0.5.028 props0 slots7 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Input
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Input"
-/>

Legacy mount name: data-component="Input".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Input"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
valuestringOptional""
typestringOptional"text"
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
autocompletestringOptional""
inputmodestringOptional""
minlengthstringOptional""
maxlengthstringOptional""
patternstringOptional""
minstringOptional""
maxstringOptional""
stepstringOptional""
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • invalid
  • keydown
  • keyup

Source contract

Installed source: components/Input.wrn

component Input {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event invalid = function
-    @event keydown = function
-    @event keyup = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Input"
-    hiddenLabel = false
-    placeholder = ""
-    value = ""
-    type = "text"
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    readonly = false
-    disabled = false
-    required = false
-    autocomplete = ""
-    inputmode = ""
-    minlength = ""
-    maxlength = ""
-    pattern = ""
-    min = ""
-    max = ""
-    step = ""
-    class = ""
-  }
-  functions {
-    function detail(sourceEvent) {
-      return { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }
-    }
-    function handleInput(sourceEvent) { sourceEvent.stopPropagation(); $emit("input", detail(sourceEvent)) }
-    function handleChange(sourceEvent) { sourceEvent.stopPropagation(); $emit("change", detail(sourceEvent)) }
-    function handleFocus(sourceEvent) { sourceEvent.stopPropagation(); $emit("focus", detail(sourceEvent)) }
-    function handleBlur(sourceEvent) { sourceEvent.stopPropagation(); $emit("blur", detail(sourceEvent)) }
-    function handleInvalid(sourceEvent) { sourceEvent.stopPropagation(); $emit("invalid", { value: sourceEvent.currentTarget.value, name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
-    function handleKeydown(sourceEvent) { sourceEvent.stopPropagation(); $emit("keydown", { key: sourceEvent.key, value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
-    function handleKeyup(sourceEvent) { sourceEvent.stopPropagation(); $emit("keyup", { key: sourceEvent.key, value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--input {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>
-        {#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}
-      </div>
-        <div class="wire-next__field-control" data-icon-position="{iconPosition}">
-        {#if icon}<span class="{icon} wire-next__field-icon" aria-hidden="true">
-        </span>{/if}
-        <input
-          id="{id || name}"
-          type="{type}"
-          name="{name}"
-          value="{value}"
-          placeholder="{variant === 'floating' ? ' ' : placeholder}"
-          readonly="{readonly}"
-          disabled="{disabled}"
-          required="{required}"
-          autocomplete="{autocomplete}"
-          inputmode="{inputmode}"
-          minlength="{minlength}"
-          maxlength="{maxlength}"
-          pattern="{pattern}"
-          min="{min}"
-          max="{max}"
-          step="{step}"
-          aria-invalid="{error ? 'true' : 'false'}"
-          aria-describedby="{error ? (id || name) + '-error' : (helperText ? (id || name) + '-help' : '')}"
-          @input="handleInput(event)"
-          @change="handleChange(event)"
-          @focus="handleFocus(event)"
-          @blur="handleBlur(event)"
-          @invalid="handleInvalid(event)"
-          @keydown="handleKeydown(event)"
-          @keyup="handleKeyup(event)"
-        />
-        {#if variant === "floating"}<span class="wire-next__field-floating-label">{label}</span>{/if}
-      </div>
-      {#if helperText}<small id="{(id || name) + '-help'}" class="wire-next__field-help">{helperText}</small>{/if}
-      <small id="{(id || name) + '-error'}" class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/kbd.wrn b/app/pages/components/kbd.wrn deleted file mode 100644 index 237c0314..00000000 --- a/app/pages/components/kbd.wrn +++ /dev/null @@ -1,38 +0,0 @@ -page Kbdcomponent { - seo { - title = "Kbd component" - description = "Theme-aware, responsive kbd component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Kbd

Theme-aware, responsive kbd component.

@wrnexus/ui 0.5.04 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Kbd
-  size="default"
-  color="primary"
-  label="⌘ K"
-/>

Legacy mount name: data-component="Kbd".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"⌘ K"
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/Kbd.wrn

component Kbd {
-  props {
-    size = "default"
-    color = "primary"
-    label = "⌘ K"
-    class = ""
-  }
-  view {
-        <kbd class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--kbd {class}">
-        <slot>{label}</slot>
-        </kbd>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/layout-splitter.wrn b/app/pages/components/layout-splitter.wrn deleted file mode 100644 index b870ffe6..00000000 --- a/app/pages/components/layout-splitter.wrn +++ /dev/null @@ -1,45 +0,0 @@ -page LayoutSplittercomponent { - seo { - title = "LayoutSplitter component" - description = "Theme-aware, responsive layout splitter component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

LayoutSplitter

Theme-aware, responsive layout splitter component.

@wrnexus/ui 0.5.06 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<LayoutSplitter
-  size="default"
-  color="primary"
-  columns="2"
-  gap="md"
-  maxWidth="xl"
-/>

Legacy mount name: data-component="LayoutSplitter".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
columnsnumberOptional2
gapstringOptional"md"
maxWidthstringOptional"xl"
classstringOptional""

Slots

  • default

Events

  • resizeStart
  • resize
  • resizeEnd

Source contract

Installed source: components/LayoutSplitter.wrn

component LayoutSplitter {
-  props {
-    @event resizeStart = function
-    @event resize = function
-    @event resizeEnd = function
-    size = "default"
-    color = "primary"
-    columns = 2
-    gap = "md"
-    maxWidth = "xl"
-    class = ""
-  }
-  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>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/legend-indicator.wrn b/app/pages/components/legend-indicator.wrn deleted file mode 100644 index 8c1f5b48..00000000 --- a/app/pages/components/legend-indicator.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page LegendIndicatorcomponent { - seo { - title = "LegendIndicator component" - description = "Theme-aware, responsive legend indicator component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

LegendIndicator

Theme-aware, responsive legend indicator component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<LegendIndicator
-  size="default"
-  color="primary"
-  title="Legend Indicator"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="LegendIndicator".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Legend Indicator"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • toggle
  • select

Source contract

Installed source: components/LegendIndicator.wrn

component LegendIndicator {
-  props {
-    @event toggle = function
-    @event select = function
-    size = "default"
-    color = "primary"
-    title = "Legend Indicator"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--legend-indicator wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/link.wrn b/app/pages/components/link.wrn deleted file mode 100644 index 68dc6ac4..00000000 --- a/app/pages/components/link.wrn +++ /dev/null @@ -1,46 +0,0 @@ -page Linkcomponent { - seo { - title = "Link component" - description = "Theme-aware, responsive link component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Link

Theme-aware, responsive link component.

@wrnexus/ui 0.5.08 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Link
-  size="default"
-  color="primary"
-  label="Link"
-  href="#"
-  target="target"
-/>

Legacy mount name: data-component="Link".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Link"
hrefstringOptional"#"
targetstringOptional""
relstringOptional""
externalbooleanOptionalfalse
classstringOptional""

Slots

  • default

Events

  • click
  • focus
  • blur

Source contract

Installed source: components/Link.wrn

component Link {
-  props {
-    @event click = function
-    @event focus = function
-    @event blur = function
-    size = "default"
-    color = "primary"
-    label = "Link"
-    href = "#"
-    target = ""
-    rel = ""
-    external = false
-    class = ""
-  }
-  view {
-        <a {...attrs} href="{href}" target="{target}" rel="{rel}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--link {class}">
-        <slot>{label}</slot>{#if external}<span aria-hidden="true">↗</span>{/if}</a>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/list-group.wrn b/app/pages/components/list-group.wrn deleted file mode 100644 index 19732d58..00000000 --- a/app/pages/components/list-group.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page ListGroupcomponent { - seo { - title = "ListGroup component" - description = "Theme-aware, responsive list group component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

ListGroup

Theme-aware, responsive list group component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ListGroup
-  size="default"
-  color="primary"
-  title="List Group"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="ListGroup".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"List Group"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select
  • change

Source contract

Installed source: components/ListGroup.wrn

component ListGroup {
-  props {
-    @event select = function
-    @event change = function
-    size = "default"
-    color = "primary"
-    title = "List Group"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--list-group wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/list.wrn b/app/pages/components/list.wrn deleted file mode 100644 index 49ec1dd4..00000000 --- a/app/pages/components/list.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Listcomponent { - seo { - title = "List component" - description = "Theme-aware, responsive list component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

List

Theme-aware, responsive list component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<List
-  size="default"
-  color="primary"
-  title="List"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="List".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"List"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select
  • click

Source contract

Installed source: components/List.wrn

component List {
-  props {
-    @event select = function
-    @event click = function
-    size = "default"
-    color = "primary"
-    title = "List"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--list wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/map.wrn b/app/pages/components/map.wrn deleted file mode 100644 index 1d722967..00000000 --- a/app/pages/components/map.wrn +++ /dev/null @@ -1,50 +0,0 @@ -page Mapcomponent { - seo { - title = "Map component" - description = "Theme-aware, responsive map component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

Map

Theme-aware, responsive map component.

@wrnexus/ui 0.5.07 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Map
-  size="default"
-  color="primary"
-  title="Map"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Map".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Map"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • ready
  • move
  • zoom
  • markerClick

Source contract

Installed source: components/Map.wrn

component Map {
-  props {
-    @event ready = function
-    @event move = function
-    @event zoom = function
-    @event markerClick = function
-    size = "default"
-    color = "primary"
-    title = "Map"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--map wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/marquee.wrn b/app/pages/components/marquee.wrn deleted file mode 100644 index f344e997..00000000 --- a/app/pages/components/marquee.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Marqueecomponent { - seo { - title = "Marquee component" - description = "Theme-aware, responsive marquee component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Marquee

Theme-aware, responsive marquee component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Marquee
-  size="default"
-  color="primary"
-  title="Marquee"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Marquee".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Marquee"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • pause
  • resume

Source contract

Installed source: components/Marquee.wrn

component Marquee {
-  props {
-    @event pause = function
-    @event resume = function
-    size = "default"
-    color = "primary"
-    title = "Marquee"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--marquee wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/mega-menu.wrn b/app/pages/components/mega-menu.wrn deleted file mode 100644 index 667f893a..00000000 --- a/app/pages/components/mega-menu.wrn +++ /dev/null @@ -1,47 +0,0 @@ -page MegaMenucomponent { - seo { - title = "MegaMenu component" - description = "Theme-aware, responsive mega menu component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

MegaMenu

Theme-aware, responsive mega menu component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<MegaMenu
-  size="default"
-  color="primary"
-  label="Mega Menu"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="MegaMenu".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Mega Menu"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • open
  • close
  • select

Source contract

Installed source: components/MegaMenu.wrn

component MegaMenu {
-  props {
-    @event open = function
-    @event close = function
-    @event select = function
-    size = "default"
-    color = "primary"
-    label = "Mega Menu"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--mega-menu wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/modal.wrn b/app/pages/components/modal.wrn deleted file mode 100644 index 7be2a09c..00000000 --- a/app/pages/components/modal.wrn +++ /dev/null @@ -1,53 +0,0 @@ -page Modalcomponent { - seo { - title = "Modal component" - description = "Theme-aware, responsive modal component." - } - - view { -
- -
- W WRNexusJS - - -
- -
overlays component

Modal

Theme-aware, responsive modal component.

@wrnexus/ui 0.5.08 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Modal
-  size="default"
-  color="primary"
-  title="Modal"
-  description="description"
-  open="false"
-/>

Legacy mount name: data-component="Modal".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Modal"
descriptionstringOptional""
openbooleanOptionalfalse
placementstringOptional"bottom"
closeLabelstringOptional"Close"
classstringOptional""

Slots

  • default

Events

  • open
  • close
  • cancel
  • confirm

Source contract

Installed source: components/Modal.wrn

component Modal {
-  props {
-    @event open = function
-    @event close = function
-    @event cancel = function
-    @event confirm = function
-    size = "default"
-    color = "primary"
-    title = "Modal"
-    description = ""
-    open = false
-    placement = "bottom"
-    closeLabel = "Close"
-    class = ""
-  }
-  view {
-        <div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--modal wire-next--placement-{placement} {class}" role="dialog" aria-modal="true" aria-label="{title}">
-        <header>
-        <strong>{title}</strong>
-        <button type="button" aria-label="{closeLabel}">×</button>
-        </header>
-      {#if description}<p>{description}</p>{/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/nav.wrn b/app/pages/components/nav.wrn deleted file mode 100644 index a1e0cab8..00000000 --- a/app/pages/components/nav.wrn +++ /dev/null @@ -1,46 +0,0 @@ -page Navcomponent { - seo { - title = "Nav component" - description = "Theme-aware, responsive nav component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Nav

Theme-aware, responsive nav component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Nav
-  size="default"
-  color="primary"
-  label="Nav"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Nav".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Nav"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • select
  • change

Source contract

Installed source: components/Nav.wrn

component Nav {
-  props {
-    @event select = function
-    @event change = function
-    size = "default"
-    color = "primary"
-    label = "Nav"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--nav wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/navbar.wrn b/app/pages/components/navbar.wrn deleted file mode 100644 index b9db3a58..00000000 --- a/app/pages/components/navbar.wrn +++ /dev/null @@ -1,47 +0,0 @@ -page Navbarcomponent { - seo { - title = "Navbar component" - description = "Theme-aware, responsive navbar component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Navbar

Theme-aware, responsive navbar component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Navbar
-  size="default"
-  color="primary"
-  label="Navbar"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Navbar".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Navbar"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • toggle
  • open
  • close

Source contract

Installed source: components/Navbar.wrn

component Navbar {
-  props {
-    @event toggle = function
-    @event open = function
-    @event close = function
-    size = "default"
-    color = "primary"
-    label = "Navbar"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--navbar wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/pagination.wrn b/app/pages/components/pagination.wrn deleted file mode 100644 index 38707e0d..00000000 --- a/app/pages/components/pagination.wrn +++ /dev/null @@ -1,47 +0,0 @@ -page Paginationcomponent { - seo { - title = "Pagination component" - description = "Theme-aware, responsive pagination component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Pagination

Theme-aware, responsive pagination component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Pagination
-  size="default"
-  color="primary"
-  label="Pagination"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Pagination".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Pagination"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • change
  • previous
  • next

Source contract

Installed source: components/Pagination.wrn

component Pagination {
-  props {
-    @event change = function
-    @event previous = function
-    @event next = function
-    size = "default"
-    color = "primary"
-    label = "Pagination"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--pagination wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/pin-input.wrn b/app/pages/components/pin-input.wrn deleted file mode 100644 index d47db663..00000000 --- a/app/pages/components/pin-input.wrn +++ /dev/null @@ -1,140 +0,0 @@ -page PinInputcomponent { - seo { - title = "PinInput component" - description = "Secure multi-cell PIN and verification-code input with regex and paste support." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

PinInput

Secure multi-cell PIN and verification-code input with regex and paste support.

@wrnexus/ui 0.5.026 props0 slots6 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<PinInput
-  size="default"
-  color="primary"
-  label="Verification code"
-  name="pin"
-  value="value"
-/>

Legacy mount name: data-component="PinInput".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Verification code"
namestringOptional"pin"
valuestringOptional""
lengthnumberOptional4
patternstringOptional"[0-9]"
typestringOptional"text"
inputModestringOptional"numeric"
placeholderstringOptional"○"
autocompletestringOptional"one-time-code"
maskedbooleanOptionalfalse
disabledbooleanOptionalfalse
readonlybooleanOptionalfalse
requiredbooleanOptionalfalse
autoFocusbooleanOptionalfalse
autoSubmitbooleanOptionalfalse
allowPastebooleanOptionaltrue
clearablebooleanOptionaltrue
clearLabelstringOptional"Clear code"
separatorstringOptional""
groupSizenumberOptional0
helpTextstringOptional""
invalidbooleanOptionalfalse
validationMessagestringOptional""
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • complete
  • paste
  • clear
  • error

Source contract

Installed source: components/PinInput.wrn

component PinInput {
-  props {
-    size = "default"
-    color = "primary"
-    label = "Verification code"
-    name = "pin"
-    value = ""
-    length = 4
-    pattern = "[0-9]"
-    type = "text"
-    inputMode = "numeric"
-    placeholder = "○"
-    autocomplete = "one-time-code"
-    masked = false
-    disabled = false
-    readonly = false
-    required = false
-    autoFocus = false
-    autoSubmit = false
-    allowPaste = true
-    clearable = true
-    clearLabel = "Clear code"
-    separator = ""
-    groupSize = 0
-    helpText = ""
-    invalid = false
-    validationMessage = ""
-    class = ""
-    @event input = function
-    @event change = function
-    @event complete = function
-    @event paste = function
-    @event clear = function
-    @event error = function
-  }
-
-  functions {
-    function inputIndexes() {
-      return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].slice(
-        0,
-        Math.max(1, Math.min(12, Number(length)))
-      )
-    }
-  }
-
-  view {
-        <fieldset
-      {...attrs}
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--pin-input {invalid ? 'wire-next--invalid' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-      data-wrn-pin-input
-      data-length="{length}"
-      data-pattern="{pattern}"
-      data-allow-paste="{allowPaste ? 'true' : 'false'}"
-      data-auto-submit="{autoSubmit ? 'true' : 'false'}"
-      data-value="{value}"
-      disabled="{disabled}"
-      aria-describedby="{validationMessage ? `${name}-validation` : helpText ? `${name}-help` : ''}"
-    >
-        <div class="wire-next__pin-heading">
-        <legend>{label}</legend>
-        <button
-          type="button"
-          class="wire-next__pin-clear"
-          data-pin-clear
-          aria-label="{clearLabel}"
-          title="{clearLabel}"
-          style="{clearable && value ? '' : 'display: none'}"
-        >
-        <span class="icon-[lucide--rotate-ccw]" aria-hidden="true">
-        </span>
-        <span>{clearLabel}</span>
-        </button>
-        </div>
-        <div class="wire-next__pin-cells" role="group" aria-label="{label}">
-        {#each inputIndexes() as index}
-          <input
-            data-pin-cell
-            data-pin-index="{index}"
-            id="{name}-{index}"
-            type="{masked ? 'password' : type}"
-            inputmode="{inputMode}"
-            maxlength="1"
-            value="{value[index] || ''}"
-            placeholder="{placeholder}"
-            autocomplete="{index === 0 ? autocomplete : 'off'}"
-            aria-label="{label} digit {index + 1} of {length}"
-            aria-required="{required ? 'true' : 'false'}"
-            readonly="{readonly}"
-            disabled="{disabled}"
-            autofocus="{autoFocus && index === 0}"
-          />
-          {#if separator && groupSize !== 0 && (index + 1) % groupSize === 0 && index + 1 !== length}
-            <span class="wire-next__pin-separator" aria-hidden="true">{separator}</span>
-          {/if}
-        {/each}
-      </div>
-        <input
-        type="hidden"
-        name="{name}"
-        value="{value}"
-        required="{required}"
-        data-pin-value
-      />
-
-      {#if helpText && !validationMessage}<small id="{name}-help">{helpText}</small>{/if}
-      <small
-        id="{name}-validation"
-        class="wire-next__validation"
-        data-error="{name}"
-      >{validationMessage}</small>
-        </fieldset>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/popover.wrn b/app/pages/components/popover.wrn deleted file mode 100644 index e1840b8b..00000000 --- a/app/pages/components/popover.wrn +++ /dev/null @@ -1,51 +0,0 @@ -page Popovercomponent { - seo { - title = "Popover component" - description = "Theme-aware, responsive popover component." - } - - view { -
- -
- W WRNexusJS - - -
- -
overlays component

Popover

Theme-aware, responsive popover component.

@wrnexus/ui 0.5.08 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Popover
-  size="default"
-  color="primary"
-  title="Popover"
-  description="description"
-  open="false"
-/>

Legacy mount name: data-component="Popover".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Popover"
descriptionstringOptional""
openbooleanOptionalfalse
placementstringOptional"bottom"
closeLabelstringOptional"Close"
classstringOptional""

Slots

  • default

Events

  • open
  • close

Source contract

Installed source: components/Popover.wrn

component Popover {
-  props {
-    @event open = function
-    @event close = function
-    size = "default"
-    color = "primary"
-    title = "Popover"
-    description = ""
-    open = false
-    placement = "bottom"
-    closeLabel = "Close"
-    class = ""
-  }
-  view {
-        <div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--popover wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}">
-        <header>
-        <strong>{title}</strong>
-        <button type="button" aria-label="{closeLabel}">×</button>
-        </header>
-      {#if description}<p>{description}</p>{/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/progress.wrn b/app/pages/components/progress.wrn deleted file mode 100644 index 876fa168..00000000 --- a/app/pages/components/progress.wrn +++ /dev/null @@ -1,46 +0,0 @@ -page Progresscomponent { - seo { - title = "Progress component" - description = "Theme-aware, responsive progress component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Progress

Theme-aware, responsive progress component.

@wrnexus/ui 0.5.07 props0 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Progress
-  size="default"
-  color="primary"
-  label="Progress"
-  value="50"
-  max="100"
-/>

Legacy mount name: data-component="Progress".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Progress"
valuenumberOptional50
maxnumberOptional100
showValuebooleanOptionaltrue
classstringOptional""

Slots

This component does not declare a slot.

Events

This component does not declare a custom event.

Source contract

Installed source: components/progress.wrn

component Progress {
-  props {
-    size = "default"
-    color = "primary"
-    label = "Progress"
-    value = 50
-    max = 100
-    showValue = true
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--progress {class}">
-        <div class="wire-next__row">
-        <span>{label}</span>{#if showValue}<strong>{value}%</strong>{/if}</div>
-        <progress value="{value}" max="{max}">
-        </progress>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/radio.wrn b/app/pages/components/radio.wrn deleted file mode 100644 index 09ce69fe..00000000 --- a/app/pages/components/radio.wrn +++ /dev/null @@ -1,87 +0,0 @@ -page Radiocomponent { - seo { - title = "Radio component" - description = "Theme-aware, responsive radio component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

Radio

Theme-aware, responsive radio component.

@wrnexus/ui 0.5.025 props0 slots5 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Radio
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Radio"
-/>

Legacy mount name: data-component="Radio".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Radio"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
valuestringOptional"on"
optionsstringOptional[]
checkedbooleanOptionalfalse
orientationstringOptional"vertical"
cardbooleanOptionalfalse
rightAlignedbooleanOptionalfalse
listbooleanOptionalfalse
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • invalid

Source contract

Installed source: components/Radio.wrn

component Radio {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Radio"
-    hiddenLabel = false
-    placeholder = ""
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    value = "on"
-    options = []
-    checked = false
-    orientation = "vertical"
-    card = false
-    rightAligned = false
-    list = false
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  functions {
-    function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { checked: sourceEvent.currentTarget.checked, value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent }) }
-    function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice-field wire-next--radio-field {class}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-orientation="{orientation}" data-card="{card}" data-list="{list}" data-right-aligned="{rightAligned}">
-        <div class="wire-next__field-heading">{#if options.length || cornerHint}<span class="{hiddenLabel ? 'wire-next__sr-only' : ''}">{label}</span>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}{/if}</div>
-      {#if options.length}
-        <div class="wire-next__radio-group" role="radiogroup" aria-label="{hiddenLabel ? label : ''}" aria-invalid="{error ? 'true' : 'false'}">
-          {#each options as option}
-            <label class="wire-next__choice wire-next--radio" for="{id || name}-{option.value}" data-variant="{variant}" data-disabled="{disabled || option.disabled}">
-        <input id="{id || name}-{option.value}" type="radio" name="{name}" value="{option.value}" checked="{option.value === value || option.checked}" disabled="{disabled || option.disabled}" required="{required}" readonly="{readonly}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />
-        <span class="wire-next__choice-copy">
-        <strong>{option.label}</strong>{#if option.description}<small>{option.description}</small>{/if}</span>
-        </label>
-          {/each}
-        </div>
-      {:else}
-        <label class="wire-next__choice wire-next--radio" for="{id || name}" data-variant="{variant}" data-icon-position="{iconPosition}">
-        <input id="{id || name}" type="radio" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" />{#if icon}<span class="{icon}" aria-hidden="true">
-        </span>{/if}<span class="{hiddenLabel || cornerHint ? 'wire-next__sr-only' : ''}">{label}</span>
-        </label>
-      {/if}
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/range-slider.wrn b/app/pages/components/range-slider.wrn deleted file mode 100644 index bd207a96..00000000 --- a/app/pages/components/range-slider.wrn +++ /dev/null @@ -1,105 +0,0 @@ -page RangeSlidercomponent { - seo { - title = "RangeSlider component" - description = "Theme-aware, responsive range slider component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

RangeSlider

Theme-aware, responsive range slider component.

@wrnexus/ui 0.5.026 props0 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<RangeSlider
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Range"
-/>

Legacy mount name: data-component="RangeSlider".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Range"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
valuenumberOptional50
minnumberOptional0
maxnumberOptional100
stepnumberOptional1
showValuebooleanOptionaltrue
showBoundsbooleanOptionaltrue
showStepsbooleanOptionalfalse
marksstringOptional[]
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur

Source contract

Installed source: components/RangeSlider.wrn

component RangeSlider {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Range"
-    hiddenLabel = false
-    placeholder = ""
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    value = 50
-    min = 0
-    max = 100
-    step = 1
-    showValue = true
-    showBounds = true
-    showSteps = false
-    marks = []
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  state currentValue = value
-  functions {
-    function percent() { if (Number(max) === Number(min)) { return 0 } return (Number(currentValue) - Number(min)) / (Number(max) - Number(min)) * 100 }
-    function setValue(nextValue, sourceEvent, commit, detail, root, slider, input, output, progressValue) { if (readonly || disabled) { return } currentValue = Number(nextValue); root = sourceEvent.currentTarget.closest(".wire-next--range-slider"); if (root) { slider = root.querySelector("[role=slider]"); input = root.querySelector("input[name='" + name + "']"); output = root.querySelector("output"); progressValue = (currentValue - Number(min)) / (Number(max) - Number(min)) * 100; if (slider) { slider.setAttribute("aria-valuenow", String(currentValue)); slider.style.setProperty("--wire-range-progress", progressValue + "%") } if (input) { input.value = String(currentValue) } if (output) { output.textContent = String(currentValue) } } detail = { value: currentValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; $emit("input", detail); if (commit) { $emit("change", detail) } }
-    function valueFromPointer(sourceEvent, rect, ratio) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); return Number(min) + ratio * (Number(max) - Number(min)) }
-    function beginSlide(sourceEvent, rect, ratio, rawValue) { if (readonly || disabled) { return } sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId); rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function moveSlide(sourceEvent, rect, ratio, rawValue) { if (!sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { return } rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function endSlide(sourceEvent, rect, ratio, rawValue) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); if (sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { sourceEvent.currentTarget.releasePointerCapture(sourceEvent.pointerId) } $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function selectMark(sourceEvent, root, slider, input, output, nextValue, progressValue, detail) { if (readonly || disabled) { return } root = sourceEvent.currentTarget.closest(".wire-next--range-slider"); slider = root.querySelector(".wire-next__custom-slider"); input = root.querySelector(".wire-next__range-value"); output = root.querySelector("output"); nextValue = Number(sourceEvent.currentTarget.dataset.value); currentValue = nextValue; progressValue = (nextValue - Number(min)) / (Number(max) - Number(min)) * 100; slider.setAttribute("aria-valuenow", String(nextValue)); slider.style.setProperty("--wire-range-progress", progressValue + "%"); input.setAttribute("value", String(nextValue)); output.replaceChildren(String(nextValue)); detail = { value: nextValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) }
-    function handleKey(sourceEvent) { if (sourceEvent.key === "ArrowRight") { sourceEvent.preventDefault(); currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "ArrowLeft") { sourceEvent.preventDefault(); currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "Home") { sourceEvent.preventDefault(); currentValue = Number(min); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "End") { sourceEvent.preventDefault(); currentValue = Number(max); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) } }
-    function emitFocus(nameEvent, sourceEvent) { $emit(nameEvent, { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function decreaseValue(sourceEvent) { currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function increaseValue(sourceEvent) { currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); $emit("input", { value: currentValue, name: name, sourceEvent: sourceEvent }); $emit("change", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--range-slider {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__range-control" data-variant="{variant}" data-icon-position="{iconPosition}" data-show-steps="{showSteps}">
-        {#if icon}<span class="{icon}" aria-hidden="true">
-        </span>{/if}
-        <button type="button" class="wire-next__range-stepper" aria-label="Decrease {label}" disabled="{disabled || readonly}" @click="decreaseValue(event)">−</button>
-        <div class="wire-next__range-track">
-        <input id="{id || name}" class="wire-next__sr-only wire-next__range-value" type="number" name="{name}" value="{currentValue}" min="{min}" max="{max}" step="{step}" required="{required}" readonly />
-        <div class="wire-next__custom-slider" role="slider" tabindex="{disabled ? '-1' : '0'}" aria-label="{hiddenLabel ? label : ''}" aria-valuemin="{min}" aria-valuemax="{max}" aria-valuenow="{currentValue}" aria-disabled="{disabled}" aria-readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" style="--wire-range-progress: {percent()}%" @pointerdown="beginSlide(event)" @pointermove="moveSlide(event)" @pointerup="endSlide(event)" @pointercancel="endSlide(event)" @keydown="handleKey(event)" @focus="emitFocus('focus', event)" @blur="emitFocus('blur', event)">
-        <span class="wire-next__slider-fill">
-        </span>
-        <span class="wire-next__slider-thumb">
-        </span>
-            {#if marks.length}<span class="wire-next__slider-marks">{#each marks as mark}<button type="button" data-value="{mark.value}" style="left: {(Number(mark.value) - Number(min)) / (Number(max) - Number(min)) * 100}%" aria-label="{mark.label}" @click="selectMark(event)">
-        </button>{/each}</span>{/if}
-          </div>
-          {#if showBounds}<div class="wire-next__range-bounds">
-        <span>{min}</span>
-        <span>Step {step}</span>
-        <span>{max}</span>
-        </div>{/if}
-        </div>
-        {#if showValue}<output for="{id || name}">{currentValue}</output>{/if}
-        <button type="button" class="wire-next__range-stepper" aria-label="Increase {label}" disabled="{disabled || readonly}" @click="increaseValue(event)">+</button>
-        </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/rating.wrn b/app/pages/components/rating.wrn deleted file mode 100644 index 2d456e6c..00000000 --- a/app/pages/components/rating.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Ratingcomponent { - seo { - title = "Rating component" - description = "Theme-aware, responsive rating component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Rating

Theme-aware, responsive rating component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Rating
-  size="default"
-  color="primary"
-  title="Rating"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Rating".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Rating"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • input
  • change

Source contract

Installed source: components/Rating.wrn

component Rating {
-  props {
-    @event input = function
-    @event change = function
-    size = "default"
-    color = "primary"
-    title = "Rating"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--rating wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/scrollspy.wrn b/app/pages/components/scrollspy.wrn deleted file mode 100644 index d3ac3f35..00000000 --- a/app/pages/components/scrollspy.wrn +++ /dev/null @@ -1,45 +0,0 @@ -page Scrollspycomponent { - seo { - title = "Scrollspy component" - description = "Theme-aware, responsive scrollspy component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Scrollspy

Theme-aware, responsive scrollspy component.

@wrnexus/ui 0.5.07 props1 slots1 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Scrollspy
-  size="default"
-  color="primary"
-  label="Scrollspy"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Scrollspy".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Scrollspy"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • change

Source contract

Installed source: components/Scrollspy.wrn

component Scrollspy {
-  props {
-    @event change = function
-    size = "default"
-    color = "primary"
-    label = "Scrollspy"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--scrollspy wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/search-box.wrn b/app/pages/components/search-box.wrn deleted file mode 100644 index ecb2f1c4..00000000 --- a/app/pages/components/search-box.wrn +++ /dev/null @@ -1,55 +0,0 @@ -page SearchBoxcomponent { - seo { - title = "SearchBox component" - description = "Theme-aware, responsive search box component." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

SearchBox

Theme-aware, responsive search box component.

@wrnexus/ui 0.5.013 props0 slots5 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<SearchBox
-  size="default"
-  color="primary"
-  label="Search Box"
-  name="name"
-  value="value"
-/>

Legacy mount name: data-component="SearchBox".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Search Box"
namestringOptional""
valuestringOptional""
placeholderstringOptional""
typestringOptional"search"
minstringOptional""
maxstringOptional""
stepstringOptional""
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • search
  • submit
  • clear

Source contract

Installed source: components/SearchBox.wrn

component SearchBox {
-  props {
-    @event input = function
-    @event change = function
-    @event search = function
-    @event submit = function
-    @event clear = function
-    size = "default"
-    color = "primary"
-    label = "Search Box"
-    name = ""
-    value = ""
-    placeholder = ""
-    type = "search"
-    min = ""
-    max = ""
-    step = ""
-    disabled = false
-    required = false
-    class = ""
-  }
-  view {
-        <label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--search-box {class}">
-        <span>{label}</span>
-        <input {...attrs} type="{type}" name="{name}" value="{value}" placeholder="{placeholder}" min="{min}" max="{max}" step="{step}" disabled="{disabled}" required="{required}" />
-        </label>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/select.wrn b/app/pages/components/select.wrn deleted file mode 100644 index 699c0a87..00000000 --- a/app/pages/components/select.wrn +++ /dev/null @@ -1,90 +0,0 @@ -page Selectcomponent { - seo { - title = "Select component" - description = "Theme-aware, responsive select component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

Select

Theme-aware, responsive select component.

@wrnexus/ui 0.5.022 props0 slots7 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Select
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Select"
-/>

Legacy mount name: data-component="Select".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Select"
hiddenLabelbooleanOptionalfalse
valuestringOptional""
valuesstringOptional[]
optionsstringOptional[]
placeholderstringOptional"Select an option"
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
multiplebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • open
  • close
  • invalid

Source contract

Installed source: components/Select.wrn

component Select {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event open = function
-    @event close = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Select"
-    hiddenLabel = false
-    value = ""
-    values = []
-    options = []
-    placeholder = "Select an option"
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    multiple = false
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  functions {
-    function selected(option) {
-      if (multiple) return values.includes(option.value)
-      return option.value === value
-    }
-    function emitField(nameEvent, sourceEvent) {
-      sourceEvent.stopPropagation()
-      $emit(nameEvent, { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent })
-    }
-    function handleFocus(sourceEvent) { emitField("focus", sourceEvent); $emit("open", { name: name, sourceEvent: sourceEvent }) }
-    function handleBlur(sourceEvent) { emitField("blur", sourceEvent); $emit("close", { name: name, sourceEvent: sourceEvent }) }
-    function handleInvalid(sourceEvent) { $emit("invalid", { name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--select {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-readonly="{readonly}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__field-control" data-icon-position="{iconPosition}">
-        {#if icon}<span class="{icon} wire-next__field-icon" aria-hidden="true">
-        </span>{/if}
-        <select id="{id || name}" name="{name}" multiple="{multiple}" disabled="{disabled || readonly}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="handleFocus(event)" @blur="handleBlur(event)" @invalid="handleInvalid(event)">
-          {#if !multiple}<option value="" selected="{value === ''}" disabled="{required}">{placeholder}</option>{/if}
-          {#each options as option}<option value="{option.value}" selected="{selected(option)}" disabled="{option.disabled}">{option.label}</option>{/each}
-        </select>
-        {#if variant === "floating"}<span class="wire-next__field-floating-label">{label}</span>{/if}
-      </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}
-      <small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/sidebar.wrn b/app/pages/components/sidebar.wrn deleted file mode 100644 index 737615fa..00000000 --- a/app/pages/components/sidebar.wrn +++ /dev/null @@ -1,47 +0,0 @@ -page Sidebarcomponent { - seo { - title = "Sidebar component" - description = "Theme-aware, responsive sidebar component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Sidebar

Theme-aware, responsive sidebar component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Sidebar
-  size="default"
-  color="primary"
-  label="Sidebar"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Sidebar".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Sidebar"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • toggle
  • open
  • close

Source contract

Installed source: components/Sidebar.wrn

component Sidebar {
-  props {
-    @event toggle = function
-    @event open = function
-    @event close = function
-    size = "default"
-    color = "primary"
-    label = "Sidebar"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--sidebar wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/skeleton.wrn b/app/pages/components/skeleton.wrn deleted file mode 100644 index cbb1feb3..00000000 --- a/app/pages/components/skeleton.wrn +++ /dev/null @@ -1,42 +0,0 @@ -page Skeletoncomponent { - seo { - title = "Skeleton component" - description = "Theme-aware, responsive skeleton component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Skeleton

Theme-aware, responsive skeleton component.

@wrnexus/ui 0.5.05 props0 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Skeleton
-  color="primary"
-  label="Loading"
-  size="md"
-  lines="3"
-/>

Legacy mount name: data-component="Skeleton".

Props and attributes

PropTypeRequirementDefault
colorstringOptional"primary"
labelstringOptional"Loading"
sizestringOptional"md"
linesnumberOptional3
classstringOptional""

Slots

This component does not declare a slot.

Events

This component does not declare a custom event.

Source contract

Installed source: components/skeleton.wrn

component Skeleton {
-  props {
-    color = "primary"
-    label = "Loading"
-    size = "md"
-    lines = 3
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--skeleton wire-next--size-{size} {class}" role="status" aria-label="{label}">
-      {#if "Skeleton" === "Skeleton"}{#each Array(lines).fill(0) as line}<span>
-        </span>{/each}{:else}<i aria-hidden="true">
-        </i>{/if}
-    </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/spinner.wrn b/app/pages/components/spinner.wrn deleted file mode 100644 index b11b7b19..00000000 --- a/app/pages/components/spinner.wrn +++ /dev/null @@ -1,42 +0,0 @@ -page Spinnercomponent { - seo { - title = "Spinner component" - description = "Theme-aware, responsive spinner component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Spinner

Theme-aware, responsive spinner component.

@wrnexus/ui 0.5.05 props0 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Spinner
-  color="primary"
-  label="Loading"
-  size="md"
-  lines="3"
-/>

Legacy mount name: data-component="Spinner".

Props and attributes

PropTypeRequirementDefault
colorstringOptional"primary"
labelstringOptional"Loading"
sizestringOptional"md"
linesnumberOptional3
classstringOptional""

Slots

This component does not declare a slot.

Events

This component does not declare a custom event.

Source contract

Installed source: components/spinner.wrn

component Spinner {
-  props {
-    color = "primary"
-    label = "Loading"
-    size = "md"
-    lines = 3
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--spinner wire-next--size-{size} {class}" role="status" aria-label="{label}">
-      {#if "Spinner" === "Skeleton"}{#each Array(lines).fill(0) as line}<span>
-        </span>{/each}{:else}<i aria-hidden="true">
-        </i>{/if}
-    </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/stepper.wrn b/app/pages/components/stepper.wrn deleted file mode 100644 index 77a95e3b..00000000 --- a/app/pages/components/stepper.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Steppercomponent { - seo { - title = "Stepper component" - description = "Theme-aware, responsive stepper component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Stepper

Theme-aware, responsive stepper component.

@wrnexus/ui 0.5.07 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Stepper
-  size="default"
-  color="primary"
-  label="Stepper"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Stepper".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Stepper"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • change
  • previous
  • next
  • complete

Source contract

Installed source: components/Stepper.wrn

component Stepper {
-  props {
-    @event change = function
-    @event previous = function
-    @event next = function
-    @event complete = function
-    size = "default"
-    color = "primary"
-    label = "Stepper"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--stepper wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/strong-password.wrn b/app/pages/components/strong-password.wrn deleted file mode 100644 index 9ceb2d41..00000000 --- a/app/pages/components/strong-password.wrn +++ /dev/null @@ -1,264 +0,0 @@ -page StrongPasswordcomponent { - seo { - title = "StrongPassword component" - description = "Theme-aware, responsive strong password component." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

StrongPassword

Theme-aware, responsive strong password component.

@wrnexus/ui 0.5.027 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<StrongPassword
-  size="default"
-  color="primary"
-  label="Password"
-  name="password"
-  value="value"
-/>

Legacy mount name: data-component="StrongPassword".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Password"
namestringOptional"password"
valuestringOptional""
placeholderstringOptional"Create a strong password"
autocompletestringOptional"new-password"
minLengthnumberOptional8
specialCharactersSetstringOptional"!@#$%^&*()_+-=[]{}|;:,.<>?"
requireLowercasebooleanOptionaltrue
requireUppercasebooleanOptionaltrue
requireNumberbooleanOptionaltrue
requireSpecialCharacterbooleanOptionaltrue
showRequirementsbooleanOptionaltrue
presentationstringOptional"inline"
hintTextstringOptional"Use a unique password you do not use elsewhere."
emptyLabelstringOptional"Enter a password"
weakLabelstringOptional"Weak"
fairLabelstringOptional"Fair"
goodLabelstringOptional"Good"
strongLabelstringOptional"Strong"
disabledbooleanOptionalfalse
readonlybooleanOptionalfalse
requiredbooleanOptionalfalse
invalidbooleanOptionalfalse
validationMessagestringOptional""
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • strength

Source contract

Installed source: components/StrongPassword.wrn

component StrongPassword {
-  props {
-    size = "default"
-    color = "primary"
-    label = "Password"
-    name = "password"
-    value = ""
-    placeholder = "Create a strong password"
-    autocomplete = "new-password"
-    minLength = 8
-    specialCharactersSet = "!@#$%^&*()_+-=[]{}|;:,.<>?"
-    requireLowercase = true
-    requireUppercase = true
-    requireNumber = true
-    requireSpecialCharacter = true
-    showRequirements = true
-    presentation = "inline"
-    hintText = "Use a unique password you do not use elsewhere."
-    emptyLabel = "Enter a password"
-    weakLabel = "Weak"
-    fairLabel = "Fair"
-    goodLabel = "Good"
-    strongLabel = "Strong"
-    disabled = false
-    readonly = false
-    required = false
-    invalid = false
-    validationMessage = ""
-    class = ""
-    @event input = function
-    @event change = function
-    @event strength = function
-  }
-
-  state password = value
-  state detailsOpen = false
-
-  functions {
-    function hasMinimumLength() {
-      return password.length >= Number(minLength)
-    }
-
-    function hasLowercase() {
-      return password !== password.toUpperCase()
-    }
-
-    function hasUppercase() {
-      return password !== password.toLowerCase()
-    }
-
-    function hasNumber() {
-      return containsCharacterAt("0123456789", 0)
-    }
-
-    function hasSpecialCharacter() {
-      return containsCharacterAt(specialCharactersSet, 0)
-    }
-
-    function containsCharacterAt(characters, index) {
-      if (index >= characters.length) {
-        return false
-      }
-      if (password.includes(characters[index])) {
-        return true
-      }
-      return containsCharacterAt(characters, index + 1)
-    }
-
-    function score() {
-      return (hasMinimumLength() ? 1 : 0) + (requireLowercase && hasLowercase() ? 1 : 0) + (requireUppercase && hasUppercase() ? 1 : 0) + (requireNumber && hasNumber() ? 1 : 0) + (requireSpecialCharacter && hasSpecialCharacter() ? 1 : 0)
-    }
-
-    function maximumScore() {
-      return 1 + (requireLowercase ? 1 : 0) + (requireUppercase ? 1 : 0) + (requireNumber ? 1 : 0) + (requireSpecialCharacter ? 1 : 0)
-    }
-
-    function strengthPercent() {
-      return Math.round(score() / maximumScore() * 100)
-    }
-
-    function strengthLevel() {
-      if (!password) {
-        return "empty"
-      }
-      if (strengthPercent() <= 25) {
-        return "weak"
-      }
-      if (strengthPercent() <= 50) {
-        return "fair"
-      }
-      if (strengthPercent() < 100) {
-        return "good"
-      }
-      return "strong"
-    }
-
-    function strengthLabel() {
-      if (strengthLevel() === "weak") {
-        return weakLabel
-      }
-      if (strengthLevel() === "fair") {
-        return fairLabel
-      }
-      if (strengthLevel() === "good") {
-        return goodLabel
-      }
-      if (strengthLevel() === "strong") {
-        return strongLabel
-      }
-      return emptyLabel
-    }
-
-  }
-
-  view {
-        <div
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--strong-password wire-next--strong-password-{presentation} {invalid ? 'wire-next--invalid' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-      data-wrn-strong-password
-      data-strength="{password ? strengthLevel() : 'empty'}"
-      data-score="{password ? score() : 0}"
-    >
-        <label for="{name}-strong-password">{label}</label>
-        <div class="wire-next__strong-password-anchor">
-        <input
-          {...attrs}
-          id="{name}-strong-password"
-          type="password"
-          name="{name}"
-          value="{password}"
-          placeholder="{placeholder}"
-          autocomplete="{autocomplete}"
-          minlength="{minLength}"
-          disabled="{disabled}"
-          readonly="{readonly}"
-          required="{required}"
-          aria-invalid="{invalid ? 'true' : 'false'}"
-          aria-describedby="{validationMessage ? name + '-validation' : hintText ? name + '-hint' : ''}"
-          @focus="detailsOpen = true"
-          @blur="detailsOpen = false"
-          @input="password = event.target.value; $emit('input', { value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() }); $emit('strength', { value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() })"
-          @change="$emit('change', { value: password, score: score(), maximumScore: maximumScore(), percent: strengthPercent(), level: strengthLevel() })"
-        />
-
-        {#if presentation === "popover"}
-          <div
-            class="wire-next__strong-password-popover"
-            data-open="{detailsOpen ? 'true' : 'false'}"
-            role="status"
-          >
-        <div class="wire-next__strong-password-popover-arrow" aria-hidden="true">
-        </div>
-        <div class="wire-next__strong-password-details">
-        <div class="wire-next__strong-password-summary">
-        <span>Password strength</span>
-        <strong>{password ? strengthLabel() : emptyLabel}</strong>
-        </div>
-        <div
-                class="wire-next__strong-password-meter"
-                role="progressbar"
-                aria-label="Password strength"
-                aria-valuemin="0"
-                aria-valuemax="100"
-                aria-valuenow="{password ? strengthPercent() : 0}"
-              >
-        <span style="width: {password ? strengthPercent() : 0}%">
-        </span>
-        </div>
-              {#if showRequirements}
-                <ul class="wire-next__strong-password-requirements">
-        <li data-met="{password && hasMinimumLength() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>At least {minLength} characters
-                  </li>
-                  {#if requireLowercase}<li data-met="{password && hasLowercase() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One lowercase letter</li>{/if}
-                  {#if requireUppercase}<li data-met="{password && hasUppercase() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One uppercase letter</li>{/if}
-                  {#if requireNumber}<li data-met="{password && hasNumber() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One number</li>{/if}
-                  {#if requireSpecialCharacter}<li data-met="{password && hasSpecialCharacter() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One special character: {specialCharactersSet}</li>{/if}
-                </ul>
-              {/if}
-              {#if hintText}<small id="{name}-hint">{hintText}</small>{/if}
-            </div>
-        </div>
-        {/if}
-      </div>
-
-      {#if presentation !== "popover"}
-        <div class="wire-next__strong-password-details">
-        <div class="wire-next__strong-password-summary">
-        <span>Password strength</span>
-        <strong>{password ? strengthLabel() : emptyLabel}</strong>
-        </div>
-        <div
-            class="wire-next__strong-password-meter"
-            role="progressbar"
-            aria-label="Password strength"
-            aria-valuemin="0"
-            aria-valuemax="100"
-            aria-valuenow="{password ? strengthPercent() : 0}"
-          >
-        <span style="width: {password ? strengthPercent() : 0}%">
-        </span>
-        </div>
-          {#if showRequirements}
-            <ul class="wire-next__strong-password-requirements">
-        <li data-met="{password && hasMinimumLength() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>At least {minLength} characters</li>
-              {#if requireLowercase}<li data-met="{password && hasLowercase() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One lowercase letter</li>{/if}
-              {#if requireUppercase}<li data-met="{password && hasUppercase() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One uppercase letter</li>{/if}
-              {#if requireNumber}<li data-met="{password && hasNumber() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One number</li>{/if}
-              {#if requireSpecialCharacter}<li data-met="{password && hasSpecialCharacter() ? 'true' : 'false'}">
-        <span aria-hidden="true">
-        </span>One special character: {specialCharactersSet}</li>{/if}
-            </ul>
-          {/if}
-          {#if hintText}<small id="{name}-hint">{hintText}</small>{/if}
-        </div>
-      {/if}
-
-      <small id="{name}-validation" class="wire-next__validation" data-error="{name}">{validationMessage}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/styled-icon.wrn b/app/pages/components/styled-icon.wrn deleted file mode 100644 index 1871c219..00000000 --- a/app/pages/components/styled-icon.wrn +++ /dev/null @@ -1,46 +0,0 @@ -page StyledIconcomponent { - seo { - title = "StyledIcon component" - description = "Theme-aware, responsive styled icon component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

StyledIcon

Theme-aware, responsive styled icon component.

@wrnexus/ui 0.5.07 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<StyledIcon
-  size="default"
-  color="primary"
-  title="Styled Icon"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="StyledIcon".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Styled Icon"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/StyledIcon.wrn

component StyledIcon {
-  props {
-    size = "default"
-    color = "primary"
-    title = "Styled Icon"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--styled-icon wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/switch.wrn b/app/pages/components/switch.wrn deleted file mode 100644 index 6654e949..00000000 --- a/app/pages/components/switch.wrn +++ /dev/null @@ -1,70 +0,0 @@ -page Switchcomponent { - seo { - title = "Switch component" - description = "Theme-aware, responsive switch component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

Switch

Theme-aware, responsive switch component.

@wrnexus/ui 0.5.020 props0 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Switch
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Switch"
-/>

Legacy mount name: data-component="Switch".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Switch"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
valuestringOptional"on"
checkedbooleanOptionalfalse
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur

Source contract

Installed source: components/Switch.wrn

component Switch {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Switch"
-    hiddenLabel = false
-    placeholder = ""
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    value = "on"
-    checked = false
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  functions {
-    function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); $emit(nameEvent, { checked: sourceEvent.currentTarget.checked, value: value, name: name, sourceEvent: sourceEvent }) }
-    function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice-field {class}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">{#if cornerHint}<span class="{hiddenLabel ? 'wire-next__sr-only' : ''}">{label}</span>
-        <span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <label class="wire-next__choice wire-next--switch" for="{id || name}" data-variant="{variant}" data-icon-position="{iconPosition}">
-        <input id="{id || name}" type="checkbox" role="switch" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-checked="{checked ? 'true' : 'false'}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" />{#if icon}<span class="{icon}" aria-hidden="true">
-        </span>{/if}<span class="{hiddenLabel || cornerHint ? 'wire-next__sr-only' : ''}">{label}</span>
-        </label>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}<small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/table.wrn b/app/pages/components/table.wrn deleted file mode 100644 index 65335122..00000000 --- a/app/pages/components/table.wrn +++ /dev/null @@ -1,53 +0,0 @@ -page Tablecomponent { - seo { - title = "Table component" - description = "Theme-aware, responsive table component." - } - - view { -
- -
- W WRNexusJS - - -
- -
tables component

Table

Theme-aware, responsive table component.

@wrnexus/ui 0.5.07 props1 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Table
-  size="default"
-  color="primary"
-  caption="Table"
-  columns="[]"
-  rows="[]"
-/>

Legacy mount name: data-component="Table".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
captionstringOptional"Table"
columnsstringOptional[]
rowsstringOptional[]
stripedbooleanOptionaltrue
classstringOptional""

Slots

  • default

Events

  • sort
  • select
  • rowClick

Source contract

Installed source: components/table.wrn

component Table {
-  props {
-    @event sort = function
-    @event select = function
-    @event rowClick = function
-    size = "default"
-    color = "primary"
-    caption = "Table"
-    columns = []
-    rows = []
-    striped = true
-    class = ""
-  }
-  view {
-        <div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}">
-        <table>
-        <caption>{caption}</caption>
-        <thead>
-        <tr>{#each columns as column}<th>{column.label}</th>{/each}</tr>
-        </thead>
-        <tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody>
-        </table>
-        <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/tabs.wrn b/app/pages/components/tabs.wrn deleted file mode 100644 index 66bb7f43..00000000 --- a/app/pages/components/tabs.wrn +++ /dev/null @@ -1,46 +0,0 @@ -page Tabscomponent { - seo { - title = "Tabs component" - description = "Theme-aware, responsive tabs component." - } - - view { -
- -
- W WRNexusJS - - -
- -
navigation component

Tabs

Theme-aware, responsive tabs component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Tabs
-  size="default"
-  color="primary"
-  label="Tabs"
-  items="[]"
-  active="active"
-/>

Legacy mount name: data-component="Tabs".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Tabs"
itemsstringOptional[]
activestringOptional""
orientationstringOptional"horizontal"
classstringOptional""

Slots

  • default

Events

  • change
  • select

Source contract

Installed source: components/Tabs.wrn

component Tabs {
-  props {
-    @event change = function
-    @event select = function
-    size = "default"
-    color = "primary"
-    label = "Tabs"
-    items = []
-    active = ""
-    orientation = "horizontal"
-    class = ""
-  }
-  view {
-        <nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tabs wire-next--{orientation} {class}" aria-label="{label}">
-      {#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
-      <slot />
-        </nav>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/textarea.wrn b/app/pages/components/textarea.wrn deleted file mode 100644 index 03ab5d5c..00000000 --- a/app/pages/components/textarea.wrn +++ /dev/null @@ -1,82 +0,0 @@ -page Textareacomponent { - seo { - title = "Textarea component" - description = "Theme-aware, responsive textarea component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

Textarea

Theme-aware, responsive textarea component.

@wrnexus/ui 0.5.023 props0 slots5 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Textarea
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Textarea"
-/>

Legacy mount name: data-component="Textarea".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Textarea"
hiddenLabelbooleanOptionalfalse
placeholderstringOptional""
valuestringOptional""
variantstringOptional"normal"
iconstringOptional""
iconPositionstringOptional"start"
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
rowsnumberOptional5
resizestringOptional"vertical"
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
minlengthstringOptional""
maxlengthstringOptional""
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • invalid

Source contract

Installed source: components/Textarea.wrn

component Textarea {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Textarea"
-    hiddenLabel = false
-    placeholder = ""
-    value = ""
-    variant = "normal"
-    icon = ""
-    iconPosition = "start"
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    rows = 5
-    resize = "vertical"
-    readonly = false
-    disabled = false
-    required = false
-    minlength = ""
-    maxlength = ""
-    class = ""
-  }
-  functions {
-    function emitField(nameEvent, sourceEvent) {
-      sourceEvent.stopPropagation()
-      $emit(nameEvent, { value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent })
-    }
-    function handleInvalid(sourceEvent) {
-      $emit("invalid", { value: sourceEvent.currentTarget.value, name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent })
-    }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--textarea {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__field-control" data-icon-position="{iconPosition}">
-        {#if icon}<span class="{icon} wire-next__field-icon" aria-hidden="true">
-        </span>{/if}
-        <textarea id="{id || name}" name="{name}" placeholder="{variant === 'floating' ? ' ' : placeholder}" rows="{rows}" style="resize: {resize}" readonly="{readonly}" disabled="{disabled}" required="{required}" minlength="{minlength}" maxlength="{maxlength}" aria-invalid="{error ? 'true' : 'false'}" aria-describedby="{error ? (id || name) + '-error' : (helperText ? (id || name) + '-help' : '')}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="handleInvalid(event)">{value}</textarea>
-        {#if variant === "floating"}<span class="wire-next__field-floating-label">{label}</span>{/if}
-      </div>
-      {#if helperText}<small id="{(id || name) + '-help'}" class="wire-next__field-help">{helperText}</small>{/if}
-      <small id="{(id || name) + '-error'}" class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/time-picker.wrn b/app/pages/components/time-picker.wrn deleted file mode 100644 index 76fbaf71..00000000 --- a/app/pages/components/time-picker.wrn +++ /dev/null @@ -1,106 +0,0 @@ -page TimePickercomponent { - seo { - title = "TimePicker component" - description = "Theme-aware, responsive time picker component." - } - - view { -
- -
- W WRNexusJS - - -
- -
forms component

TimePicker

Theme-aware, responsive time picker component.

@wrnexus/ui 0.5.026 props0 slots7 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<TimePicker
-  size="default"
-  color="primary"
-  id="id"
-  name="name"
-  label="Time"
-/>

Legacy mount name: data-component="TimePicker".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
idstringOptional""
namestringOptional""
labelstringOptional"Time"
hiddenLabelbooleanOptionalfalse
valuestringOptional""
placeholderstringOptional""
variantstringOptional"normal"
iconstringOptional"icon-[lucide--clock-3]"
iconPositionstringOptional"end"
helperTextstringOptional""
cornerHintstringOptional""
errorstringOptional""
inlinebooleanOptionalfalse
minstringOptional""
maxstringOptional""
stepstringOptional""
formatstringOptional"24"
minuteStepnumberOptional5
hoursstringOptional["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"]
minutesstringOptional["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"]
readonlybooleanOptionalfalse
disabledbooleanOptionalfalse
requiredbooleanOptionalfalse
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • focus
  • blur
  • open
  • close
  • invalid

Source contract

Installed source: components/TimePicker.wrn

component TimePicker {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    @event open = function
-    @event close = function
-    @event invalid = function
-    size = "default"
-    color = "primary"
-    id = ""
-    name = ""
-    label = "Time"
-    hiddenLabel = false
-    value = ""
-    placeholder = ""
-    variant = "normal"
-    icon = "icon-[lucide--clock-3]"
-    iconPosition = "end"
-    helperText = ""
-    cornerHint = ""
-    error = ""
-    inline = false
-    min = ""
-    max = ""
-    step = ""
-    format = "24"
-    minuteStep = 5
-    hours = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"]
-    minutes = ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"]
-    readonly = false
-    disabled = false
-    required = false
-    class = ""
-  }
-  state currentValue = value
-  state selectedHour = value ? value.split(":")[0] : "00"
-  state selectedMinute = value ? value.split(":")[1] : "00"
-  state expanded = false
-  functions {
-    function commit(part, nextValue, sourceEvent, next, detail) { if (part === "hour") { selectedHour = String(nextValue).padStart(2, "0") } if (part === "minute") { selectedMinute = String(nextValue).padStart(2, "0") } next = selectedHour + ":" + selectedMinute; if ((min && next < min) || (max && next > max)) { return } currentValue = next; detail = { value: currentValue, hour: selectedHour, minute: selectedMinute, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) }
-    function selectTimePart(sourceEvent, root, input, parts, hourValue, minuteValue, next, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--time-picker"); input = root.querySelector(".wire-next__time-value"); parts = String(input.value || value || "00:00").split(":"); hourValue = sourceEvent.currentTarget.dataset.part === "hour" ? sourceEvent.currentTarget.dataset.value : parts[0]; minuteValue = sourceEvent.currentTarget.dataset.part === "minute" ? sourceEvent.currentTarget.dataset.value : parts[1]; next = hourValue + ":" + minuteValue; if ((min && next < min) || (max && next > max)) { return } currentValue = next; input.setAttribute("value", next); trigger = root.querySelector(".wire-next__time-trigger span"); if (trigger) { trigger.replaceChildren(next) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: next, hour: hourValue, minute: minuteValue, name: name, sourceEvent: sourceEvent }; $emit("input", detail); $emit("change", detail) }
-    function openPicker(sourceEvent) { expanded = true; $emit("open", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function closePicker(sourceEvent) { expanded = false; $emit("close", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function handleFocus(sourceEvent) { $emit("focus", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function handleBlur(sourceEvent) { $emit("blur", { value: currentValue, name: name, sourceEvent: sourceEvent }) }
-    function handleInvalid(sourceEvent) { $emit("invalid", { name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
-  }
-  view {
-        <div {...attrs} class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--time-picker {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}">
-        <div class="wire-next__field-heading">
-        <label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
-        <div class="wire-next__field-control" data-icon-position="{iconPosition}">
-        {#if icon}<span class="{icon} wire-next__field-icon" aria-hidden="true">
-        </span>{/if}
-        <input id="{id || name}" class="wire-next__sr-only wire-next__time-value" type="text" name="{name}" value="{currentValue}" required="{required}" readonly aria-hidden="true" tabindex="-1" @invalid="handleInvalid(event)" />
-        <button type="button" class="wire-next__time-trigger" disabled="{disabled || readonly}" aria-haspopup="dialog" aria-expanded="{expanded}" @click="openPicker(event)" @focus="handleFocus(event)" @blur="handleBlur(event)">
-        <span>{currentValue || placeholder || "Select time"}</span>
-        <i class="{icon}" aria-hidden="true">
-        </i>
-        </button>
-        </div>
-        <div class="wire-next__time-panel" role="dialog" aria-label="{label}">
-        <div>
-        <strong>Hour</strong>
-        <div class="wire-next__time-options">{#each hours as hour}<button type="button" data-part="hour" data-value="{hour}" aria-pressed="{String(hour) === selectedHour}" @click="selectTimePart(event)">{hour}</button>{/each}</div>
-        </div>
-        <div>
-        <strong>Minute</strong>
-        <div class="wire-next__time-options">{#each minutes as minute}<button type="button" data-part="minute" data-value="{minute}" aria-pressed="{String(minute) === selectedMinute}" @click="selectTimePart(event)">{minute}</button>{/each}</div>
-        </div>
-        <button type="button" class="wire-next__time-done" @click="closePicker(event)">Done</button>
-        </div>
-      {#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}
-      <small class="wire-next__field-error" data-error="{name}">{error}</small>
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/timeline.wrn b/app/pages/components/timeline.wrn deleted file mode 100644 index d5822c34..00000000 --- a/app/pages/components/timeline.wrn +++ /dev/null @@ -1,47 +0,0 @@ -page Timelinecomponent { - seo { - title = "Timeline component" - description = "Theme-aware, responsive timeline component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Timeline

Theme-aware, responsive timeline component.

@wrnexus/ui 0.5.07 props1 slots1 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Timeline
-  size="default"
-  color="primary"
-  title="Timeline"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Timeline".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Timeline"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select

Source contract

Installed source: components/Timeline.wrn

component Timeline {
-  props {
-    @event select = function
-    size = "default"
-    color = "primary"
-    title = "Timeline"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--timeline wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/toast-notifications.wrn b/app/pages/components/toast-notifications.wrn deleted file mode 100644 index 08a62fc7..00000000 --- a/app/pages/components/toast-notifications.wrn +++ /dev/null @@ -1,50 +0,0 @@ -page ToastNotificationscomponent { - seo { - title = "ToastNotifications component" - description = "Theme-aware, responsive toast notifications component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

ToastNotifications

Theme-aware, responsive toast notifications component.

@wrnexus/ui 0.5.07 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ToastNotifications
-  size="default"
-  color="primary"
-  title="Toast Notifications"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="ToastNotifications".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Toast Notifications"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • add
  • dismiss
  • clear
  • action

Source contract

Installed source: components/ToastNotifications.wrn

component ToastNotifications {
-  props {
-    @event add = function
-    @event dismiss = function
-    @event clear = function
-    @event action = function
-    size = "default"
-    color = "primary"
-    title = "Toast Notifications"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toast-notifications wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/toast.wrn b/app/pages/components/toast.wrn deleted file mode 100644 index ed8dacff..00000000 --- a/app/pages/components/toast.wrn +++ /dev/null @@ -1,48 +0,0 @@ -page Toastcomponent { - seo { - title = "Toast component" - description = "Theme-aware, responsive toast component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

Toast

Theme-aware, responsive toast component.

@wrnexus/ui 0.5.07 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Toast
-  size="default"
-  color="primary"
-  title="Toast"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="Toast".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Toast"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • dismiss
  • action

Source contract

Installed source: components/Toast.wrn

component Toast {
-  props {
-    @event dismiss = function
-    @event action = function
-    size = "default"
-    color = "primary"
-    title = "Toast"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toast wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/toggle-count.wrn b/app/pages/components/toggle-count.wrn deleted file mode 100644 index 01a62584..00000000 --- a/app/pages/components/toggle-count.wrn +++ /dev/null @@ -1,264 +0,0 @@ -page ToggleCountcomponent { - seo { - title = "ToggleCount component" - description = "Theme-aware, responsive toggle count component." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

ToggleCount

Theme-aware, responsive toggle count component.

@wrnexus/ui 0.5.023 props0 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<ToggleCount
-  size="default"
-  color="primary"
-  variant="segmented"
-  name="billing-cycle"
-  value="monthly"
-/>

Legacy mount name: data-component="ToggleCount".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
variantstringOptional"segmented"
classstringOptional""
namestringOptional"billing-cycle"
valuestringOptional"monthly"
firstValuestringOptional"monthly"
firstLabelstringOptional"Monthly"
secondValuestringOptional"annual"
secondLabelstringOptional"Annual"
ariaLabelstringOptional"Billing frequency"
itemsstringOptional[]
currencystringOptional"$"
suffixstringOptional""
firstValueKeystringOptional"monthly"
secondValueKeystringOptional"annual"
emptyValuestringOptional"—"
alignstringOptional"end"
fullWidthbooleanOptionaltrue
disabledbooleanOptionalfalse
animatebooleanOptionaltrue
animationDurationnumberOptional450
animationStepsnumberOptional18

Slots

This component does not declare a slot.

Events

  • change
  • toggle

Source contract

Installed source: components/ToggleCount.wrn

component ToggleCount {
-  props {
-    size = "default"
-    color = "primary"
-    variant = "segmented"
-    class = ""
-
-    name = "billing-cycle"
-    value = "monthly"
-    firstValue = "monthly"
-    firstLabel = "Monthly"
-    secondValue = "annual"
-    secondLabel = "Annual"
-    ariaLabel = "Billing frequency"
-
-    items = []
-    currency = "$"
-    suffix = ""
-    firstValueKey = "monthly"
-    secondValueKey = "annual"
-    emptyValue = "—"
-
-    align = "end"
-    fullWidth = true
-    disabled = false
-    animate = true
-    animationDuration = 450
-    animationSteps = 18
-
-    @event change = function
-    @event toggle = function
-  }
-
-  state selectedValue = value
-
-  functions {
-    function isFirstSelected() {
-      return selectedValue === firstValue
-    }
-
-    function isSecondSelected() {
-      return selectedValue === secondValue
-    }
-
-    function displayValue(item) {
-      if (isSecondSelected()) {
-        return item[secondValueKey] !== undefined
-          ? item[secondValueKey]
-          : emptyValue
-      }
-
-      return item[firstValueKey] !== undefined
-        ? item[firstValueKey]
-        : emptyValue
-    }
-
-    function dispatchToggleEvent(sourceEvent, previousValue, root, customEvent) {
-      root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]")
-
-      if (!root) {
-        return
-      }
-
-      customEvent = document.createEvent("CustomEvent")
-      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)
-    }
-
-    function selectValue(sourceEvent, nextValue, previousValue) {
-      if (disabled || selectedValue === nextValue) {
-        return
-      }
-
-      previousValue = selectedValue
-      selectedValue = nextValue
-
-      if (
-        animate &&
-        !window.matchMedia("(prefers-reduced-motion: reduce)").matches
-      ) {
-        animateDisplayedValues(sourceEvent, previousValue)
-      }
-
-      dispatchToggleEvent(sourceEvent, previousValue)
-    }
-
-    function animateDisplayedValues(sourceEvent, previousValue, root, nodes) {
-      root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]")
-
-      if (!root) {
-        return
-      }
-
-      nodes = root.querySelectorAll("[data-toggle-count-value]")
-      animateValueAt(nodes, 0, previousValue)
-    }
-
-    function animateValueAt(nodes, index, previousValue, node, fromValue, toValue) {
-      if (index >= nodes.length) {
-        return
-      }
-
-      node = nodes[index]
-      fromValue = Number(
-        previousValue === secondValue
-          ? node.getAttribute("data-second-value")
-          : node.getAttribute("data-first-value")
-      )
-      toValue = Number(
-        selectedValue === secondValue
-          ? node.getAttribute("data-second-value")
-          : node.getAttribute("data-first-value")
-      )
-
-      if (!Number.isNaN(fromValue) && !Number.isNaN(toValue)) {
-        animateValueFrame(node, fromValue, toValue, 0)
-      }
-
-      animateValueAt(nodes, index + 1, previousValue)
-    }
-
-    function animateValueFrame(node, fromValue, toValue, frame, totalFrames, nextValue) {
-      totalFrames = Math.max(1, Number(animationSteps) || 1)
-
-      if (frame >= totalFrames) {
-        node.replaceChildren(String(toValue))
-        return
-      }
-
-      nextValue = Math.round(
-        fromValue +
-        (toValue - fromValue) * (frame / totalFrames)
-      )
-      node.replaceChildren(String(nextValue))
-
-      setTimeout(
-        animateValueFrame,
-        Math.max(1, Number(animationDuration) / totalFrames),
-        node,
-        fromValue,
-        toValue,
-        frame + 1
-      )
-    }
-
-    function toggleValue(sourceEvent) {
-      selectValue(
-        sourceEvent,
-        isFirstSelected() ? secondValue : firstValue
-      )
-    }
-  }
-
-  view {
-        <section
-      {...attrs}
-      data-wrn-toggle-count
-      data-variant="{variant}"
-      data-value="{selectedValue}"
-      data-disabled="{disabled ? 'true' : 'false'}"
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toggle-count wire-next--toggle-count-{variant} {fullWidth ? 'wire-next--toggle-count-full' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-    >
-        <input type="hidden" name="{name}" value="{selectedValue}" />
-        <div class="wire-next__toggle-count-control wire-next__toggle-count-control--{align}">
-        {#if variant === "switch"}
-          <span data-selected="{isFirstSelected() ? 'true' : 'false'}">{firstLabel}</span>
-        <button
-            type="button"
-            role="switch"
-            aria-label="{ariaLabel}"
-            aria-checked="{isSecondSelected() ? 'true' : 'false'}"
-            disabled="{disabled}"
-            @click="toggleValue(event)"
-            class="wire-next__toggle-count-switch"
-          >
-        <span aria-hidden="true">
-        </span>
-        </button>
-        <span data-selected="{isSecondSelected() ? 'true' : 'false'}">{secondLabel}</span>
-        {:else}
-          <div class="wire-next__toggle-count-segmented" role="group" aria-label="{ariaLabel}">
-        <button
-              type="button"
-              aria-pressed="{isFirstSelected() ? 'true' : 'false'}"
-              disabled="{disabled}"
-              @click="selectValue(event, firstValue)"
-            >{firstLabel}</button>
-        <button
-              type="button"
-              aria-pressed="{isSecondSelected() ? 'true' : 'false'}"
-              disabled="{disabled}"
-              @click="selectValue(event, secondValue)"
-            >{secondLabel}</button>
-        </div>
-        {/if}
-      </div>
-
-      {#if items.length > 0}
-        <div class="wire-next__toggle-count-items">
-          {#each items as item}
-            <article>
-        <span>{item.label || item.name}</span>
-        <strong>
-                {#if currency}<small>{currency}</small>{/if}
-                <span
-                  data-toggle-count-value
-                  data-first-value="{item[firstValueKey]}"
-                  data-second-value="{item[secondValueKey]}"
-                >{displayValue(item)}</span>
-                {#if suffix}<small>{suffix}</small>{/if}
-              </strong>
-              {#if item.description}<small>{item.description}</small>{/if}
-            </article>
-          {/each}
-        </div>
-      {/if}
-    </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/toggle-password.wrn b/app/pages/components/toggle-password.wrn deleted file mode 100644 index ef2c7868..00000000 --- a/app/pages/components/toggle-password.wrn +++ /dev/null @@ -1,182 +0,0 @@ -page TogglePasswordcomponent { - seo { - title = "TogglePassword component" - description = "Accessible password field with optional show and hide controls." - } - - view { -
- -
- W WRNexusJS - - -
- -
advanced-forms component

TogglePassword

Accessible password field with optional show and hide controls.

@wrnexus/ui 0.5.024 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<TogglePassword
-  size="default"
-  color="primary"
-  label="Password"
-  name="password"
-  value="value"
-/>

Legacy mount name: data-component="TogglePassword".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Password"
namestringOptional"password"
valuestringOptional""
placeholderstringOptional"Enter your password"
autocompletestringOptional"current-password"
minlengthstringOptional""
maxlengthstringOptional""
patternstringOptional"(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}"
fieldsstringOptional[]
visiblebooleanOptionalfalse
toggleablebooleanOptionaltrue
toggleModestringOptional"button"
checkboxLabelstringOptional"Show password"
showLabelstringOptional"Show password"
hideLabelstringOptional"Hide password"
disabledbooleanOptionalfalse
readonlybooleanOptionalfalse
requiredbooleanOptionalfalse
invalidbooleanOptionalfalse
helpTextstringOptional""
validationMessagestringOptional""
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • toggle

Source contract

Installed source: components/TogglePassword.wrn

component TogglePassword {
-  props {
-    size = "default"
-    color = "primary"
-    label = "Password"
-    name = "password"
-    value = ""
-    placeholder = "Enter your password"
-    autocomplete = "current-password"
-    minlength = ""
-    maxlength = ""
-    pattern = "(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}"
-    fields = []
-    visible = false
-    toggleable = true
-    toggleMode = "button"
-    checkboxLabel = "Show password"
-    showLabel = "Show password"
-    hideLabel = "Hide password"
-    disabled = false
-    readonly = false
-    required = false
-    invalid = false
-    helpText = ""
-    validationMessage = ""
-    class = ""
-    @event input = function
-    @event change = function
-    @event toggle = function
-  }
-
-  state revealed = visible
-
-  functions {
-    function toggleVisibility() {
-      if (disabled || readonly || !toggleable) {
-        return
-      }
-      revealed = !revealed
-    }
-
-    function fieldId(field, index) {
-      return field.id || field.name || name + "-" + index
-    }
-
-    function fieldName(field, index) {
-      return field.name || name + "-" + index
-    }
-  }
-
-  view {
-        <fieldset
-      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--toggle-password {invalid ? 'wire-next--invalid' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
-      data-wrn-toggle-password
-      data-visible="{revealed ? 'true' : 'false'}"
-    >
-      {#if fields.length}
-        <div class="wire-next__password-fields">
-          {#each fields as field, index}
-            <div class="wire-next__password-field">
-        <label for="{fieldId(field, index)}">{field.label || label}</label>
-        <div class="wire-next__password-control">
-        <input
-                  id="{fieldId(field, index)}"
-                  type="{revealed ? 'text' : 'password'}"
-                  name="{fieldName(field, index)}"
-                  value="{field.value || ''}"
-                  placeholder="{field.placeholder || placeholder}"
-                  autocomplete="{field.autocomplete || autocomplete}"
-                  minlength="{field.minlength || minlength}"
-                  maxlength="{field.maxlength || maxlength}"
-                  pattern="{field.pattern || pattern}"
-                  disabled="{disabled || field.disabled}"
-                  readonly="{readonly || field.readonly}"
-                  required="{required || field.required}"
-                  aria-invalid="{invalid ? 'true' : 'false'}"
-                  @input="$emit('input', { name: event.target.name, value: event.target.value, visible: revealed })"
-                  @change="$emit('change', { name: event.target.name, value: event.target.value, visible: revealed })"
-                />
-                {#if toggleable && toggleMode === "button"}
-                  <button
-                    type="button"
-                    class="wire-next__password-toggle"
-                    aria-label="{revealed ? hideLabel : showLabel}"
-                    title="{revealed ? hideLabel : showLabel}"
-                    aria-pressed="{revealed ? 'true' : 'false'}"
-                    disabled="{disabled || readonly}"
-                    @click="toggleVisibility(); $emit('toggle', { visible: revealed })"
-                  >
-        <span class="wire-next__password-icon wire-next__password-icon--show" aria-hidden="true">
-        </span>
-        </button>
-                {/if}
-              </div>
-        </div>
-          {/each}
-        </div>
-      {:else}
-        <label for="{name}-password">{label}</label>
-        <div class="wire-next__password-control">
-        <input
-            {...attrs}
-            id="{name}-password"
-            type="{revealed ? 'text' : 'password'}"
-            name="{name}"
-            value="{value}"
-            placeholder="{placeholder}"
-            autocomplete="{autocomplete}"
-            minlength="{minlength}"
-            maxlength="{maxlength}"
-            pattern="{pattern}"
-            disabled="{disabled}"
-            readonly="{readonly}"
-            required="{required}"
-            aria-invalid="{invalid ? 'true' : 'false'}"
-            aria-describedby="{validationMessage ? name + '-validation' : helpText ? name + '-help' : ''}"
-            @input="$emit('input', { value: event.target.value, visible: revealed })"
-            @change="$emit('change', { value: event.target.value, visible: revealed })"
-          />
-          {#if toggleable && toggleMode === "button"}
-            <button
-              type="button"
-              class="wire-next__password-toggle"
-              aria-label="{revealed ? hideLabel : showLabel}"
-              title="{revealed ? hideLabel : showLabel}"
-              aria-pressed="{revealed ? 'true' : 'false'}"
-              disabled="{disabled || readonly}"
-              @click="toggleVisibility(); $emit('toggle', { visible: revealed })"
-            >
-        <span class="wire-next__password-icon wire-next__password-icon--show" aria-hidden="true">
-        </span>
-        </button>
-          {/if}
-        </div>
-      {/if}
-      {#if toggleable && toggleMode === "checkbox"}
-        <label class="wire-next__password-checkbox">
-        <input
-            type="checkbox"
-            checked="{revealed}"
-            disabled="{disabled || readonly}"
-            @change="revealed = event.target.checked; $emit('toggle', { visible: revealed })"
-          />
-        <span>{checkboxLabel}</span>
-        </label>
-      {/if}
-      {#if helpText && !validationMessage}<small id="{name}-help">{helpText}</small>{/if}
-      <small
-        id="{name}-validation"
-        class="wire-next__validation"
-        data-error="{name}"
-      >{validationMessage}</small>
-        </fieldset>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/tooltip.wrn b/app/pages/components/tooltip.wrn deleted file mode 100644 index 9b40297b..00000000 --- a/app/pages/components/tooltip.wrn +++ /dev/null @@ -1,51 +0,0 @@ -page Tooltipcomponent { - seo { - title = "Tooltip component" - description = "Theme-aware, responsive tooltip component." - } - - view { -
- -
- W WRNexusJS - - -
- -
overlays component

Tooltip

Theme-aware, responsive tooltip component.

@wrnexus/ui 0.5.08 props1 slots2 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Tooltip
-  size="default"
-  color="primary"
-  title="Tooltip"
-  description="description"
-  open="false"
-/>

Legacy mount name: data-component="Tooltip".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Tooltip"
descriptionstringOptional""
openbooleanOptionalfalse
placementstringOptional"bottom"
closeLabelstringOptional"Close"
classstringOptional""

Slots

  • default

Events

  • open
  • close

Source contract

Installed source: components/tooltip.wrn

component Tooltip {
-  props {
-    @event open = function
-    @event close = function
-    size = "default"
-    color = "primary"
-    title = "Tooltip"
-    description = ""
-    open = false
-    placement = "bottom"
-    closeLabel = "Close"
-    class = ""
-  }
-  view {
-        <div data-show="{open}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tooltip wire-next--placement-{placement} {class}" role="region" aria-modal="false" aria-label="{title}">
-        <header>
-        <strong>{title}</strong>
-        <button type="button" aria-label="{closeLabel}">×</button>
-        </header>
-      {#if description}<p>{description}</p>{/if}
-      <slot />
-        </div>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/tree-view.wrn b/app/pages/components/tree-view.wrn deleted file mode 100644 index 2c48936e..00000000 --- a/app/pages/components/tree-view.wrn +++ /dev/null @@ -1,50 +0,0 @@ -page TreeViewcomponent { - seo { - title = "TreeView component" - description = "Theme-aware, responsive tree view component." - } - - view { -
- -
- W WRNexusJS - - -
- -
base component

TreeView

Theme-aware, responsive tree view component.

@wrnexus/ui 0.5.07 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<TreeView
-  size="default"
-  color="primary"
-  title="Tree View"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="TreeView".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Tree View"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • select
  • toggle
  • expand
  • collapse

Source contract

Installed source: components/TreeView.wrn

component TreeView {
-  props {
-    @event select = function
-    @event toggle = function
-    @event expand = function
-    @event collapse = function
-    size = "default"
-    color = "primary"
-    title = "Tree View"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tree-view wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/typography.wrn b/app/pages/components/typography.wrn deleted file mode 100644 index c2a553f2..00000000 --- a/app/pages/components/typography.wrn +++ /dev/null @@ -1,42 +0,0 @@ -page Typographycomponent { - seo { - title = "Typography component" - description = "Theme-aware, responsive typography component." - } - - view { -
- -
- W WRNexusJS - - -
- -
layout component

Typography

Theme-aware, responsive typography component.

@wrnexus/ui 0.5.06 props1 slots0 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Typography
-  size="default"
-  color="primary"
-  columns="2"
-  gap="md"
-  maxWidth="xl"
-/>

Legacy mount name: data-component="Typography".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
columnsnumberOptional2
gapstringOptional"md"
maxWidthstringOptional"xl"
classstringOptional""

Slots

  • default

Events

This component does not declare a custom event.

Source contract

Installed source: components/Typography.wrn

component Typography {
-  props {
-    size = "default"
-    color = "primary"
-    columns = 2
-    gap = "md"
-    maxWidth = "xl"
-    class = ""
-  }
-  view {
-        <article class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--typography wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}">
-        <slot />
-        </article>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/components/wysiwyg-editor.wrn b/app/pages/components/wysiwyg-editor.wrn deleted file mode 100644 index 0987bcf1..00000000 --- a/app/pages/components/wysiwyg-editor.wrn +++ /dev/null @@ -1,50 +0,0 @@ -page WysiwygEditorcomponent { - seo { - title = "WysiwygEditor component" - description = "Theme-aware, responsive wysiwyg editor component." - } - - view { -
- -
- W WRNexusJS - - -
- -
integrations component

WysiwygEditor

Theme-aware, responsive wysiwyg editor component.

@wrnexus/ui 0.5.07 props1 slots4 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<WysiwygEditor
-  size="default"
-  color="primary"
-  title="Wysiwyg Editor"
-  description="description"
-  items="[]"
-/>

Legacy mount name: data-component="WysiwygEditor".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
titlestringOptional"Wysiwyg Editor"
descriptionstringOptional""
itemsstringOptional[]
variantstringOptional"default"
classstringOptional""

Slots

  • default

Events

  • input
  • change
  • focus
  • blur

Source contract

Installed source: components/WysiwygEditor.wrn

component WysiwygEditor {
-  props {
-    @event input = function
-    @event change = function
-    @event focus = function
-    @event blur = function
-    size = "default"
-    color = "primary"
-    title = "Wysiwyg Editor"
-    description = ""
-    items = []
-    variant = "default"
-    class = ""
-  }
-  view {
-        <section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--wysiwyg-editor wire-next--variant-{variant} {class}">
-      {#if title}<strong>{title}</strong>{/if}
-      {#if description}<p>{description}</p>{/if}
-      {#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
-      <slot />
-        </section>
-    }
-}
-
- - -
- } -} diff --git a/app/pages/examples.wrn b/app/pages/examples.wrn index 309fdf88..56a90b15 100644 --- a/app/pages/examples.wrn +++ b/app/pages/examples.wrn @@ -7,8 +7,8 @@ page Examples { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Examples

Examples are tied to installed 0.5.0 package documentation. The focused snippets in guides are source-verified; standalone runnable projects and CI compilation are tracked as remaining work.

Minimal .wrn page

Documented

Database CRUD

Planned fixture

Authentication and protected route

Planned fixture

Permissions

Planned fixture

Forms and validation

Documented

Realtime dashboard

Planned fixture

File upload

Planned fixture

Background queue

Planned fixture

Redis pub/sub

Planned fixture

Workspace gateway

Planned fixture

Deployment

Planned fixture

Experimental mobile mode

Planned fixture
diff --git a/app/pages/getting-started.wrn b/app/pages/getting-started.wrn index c4988e9f..bd3508f9 100644 --- a/app/pages/getting-started.wrn +++ b/app/pages/getting-started.wrn @@ -7,8 +7,8 @@ page Gettingstarted { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · v0.5.0

Build a contact inbox

This path creates a server-rendered page, validated API route, middleware, and realtime next step. It requires approved registry access and Bun 1.3.x; this site was verified with Bun 1.3.14.

1. Create the project

bunx @wrnexus/cli@0.5.0 create my-app
 cd my-app
 bun install
diff --git a/app/pages/guides/api-routes.wrn b/app/pages/guides/api-routes.wrn
index 46338000..2bb0dc1c 100644
--- a/app/pages/guides/api-routes.wrn
+++ b/app/pages/guides/api-routes.wrn
@@ -7,8 +7,8 @@ page APIroutes {
   view {
     
     
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

API routes

Export GET, POST, PUT, PATCH, or DELETE from app/api files. Validate request bodies, enforce authentication and authorization, cap request sizes, and return Web Responses.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/authentication.wrn b/app/pages/guides/authentication.wrn index 60033590..e282280d 100644 --- a/app/pages/guides/authentication.wrn +++ b/app/pages/guides/authentication.wrn @@ -7,8 +7,8 @@ page Authentication { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Authentication

Configure session authentication, log users in through supported auth helpers, and read identity from context. Cookie flags, rotation, expiry, and secret storage remain deployment responsibilities.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/authorization.wrn b/app/pages/guides/authorization.wrn index c8bbdea6..f6db0df5 100644 --- a/app/pages/guides/authorization.wrn +++ b/app/pages/guides/authorization.wrn @@ -7,8 +7,8 @@ page Authorization { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Authorization

Authentication identifies; authorization decides. Enforce permissions in server routes and policies, including object ownership. UI hiding is never an authorization boundary.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/configuration-and-profiles.wrn b/app/pages/guides/configuration-and-profiles.wrn index 81accb41..427fdd0f 100644 --- a/app/pages/guides/configuration-and-profiles.wrn +++ b/app/pages/guides/configuration-and-profiles.wrn @@ -7,8 +7,8 @@ page Configurationandprofiles { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Configuration and profiles

wrnexus.config.ts owns styles, SEO, security, data, mobile, fonts, and profiles. Keep secrets in validated environment variables and review merged production configuration.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/database.wrn b/app/pages/guides/database.wrn index 0facad54..082822c8 100644 --- a/app/pages/guides/database.wrn +++ b/app/pages/guides/database.wrn @@ -7,8 +7,8 @@ page Database { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Database

Configure SQLite or the installed supported driver, keep queries in named SQL blocks, generate typed functions, and apply migrations before traffic. Back up data and test rollback independently.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/deployment.wrn b/app/pages/guides/deployment.wrn index eeddda1b..21e7d027 100644 --- a/app/pages/guides/deployment.wrn +++ b/app/pages/guides/deployment.wrn @@ -7,8 +7,8 @@ page Deployment { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Deployment

Run bun run build, apply migrations, and start dist/server.js with Bun. Configure TLS, proxy trust, environment validation, health checks, graceful restarts, logs, backups, and restrictive security headers.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/forms-and-validation.wrn b/app/pages/guides/forms-and-validation.wrn index e78cb908..5e655bc3 100644 --- a/app/pages/guides/forms-and-validation.wrn +++ b/app/pages/guides/forms-and-validation.wrn @@ -7,8 +7,8 @@ page Formsandvalidation { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Forms and validation

Define a v.object schema, use data-schema on the form, show field errors with data-error, and always call parseBody on the server. refine is server-only.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/i18n-and-themes.wrn b/app/pages/guides/i18n-and-themes.wrn index 3ec3f47b..802b5cb1 100644 --- a/app/pages/guides/i18n-and-themes.wrn +++ b/app/pages/guides/i18n-and-themes.wrn @@ -7,8 +7,8 @@ page Internationalizationandthemes { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Internationalization and themes

Store locale JSON under app/locales and use translation directives. Themes resolve CSS tokens; ensure contrast, system preference behavior, persistence, and non-color cues.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/middleware.wrn b/app/pages/guides/middleware.wrn index 272cbc74..b9fac710 100644 --- a/app/pages/guides/middleware.wrn +++ b/app/pages/guides/middleware.wrn @@ -7,8 +7,8 @@ page Middleware { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Middleware

Middleware receives context and next. Return next() to continue or return a Response to stop. Put request limits and trust-boundary controls before business logic.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/mobile.wrn b/app/pages/guides/mobile.wrn index 68955efc..63737065 100644 --- a/app/pages/guides/mobile.wrn +++ b/app/pages/guides/mobile.wrn @@ -7,8 +7,8 @@ page Mobile { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Mobile

Mobile capabilities are experimental in this preview. Test Capacitor permissions and lifecycle on each platform; do not assume every .wrn or browser API converts to native.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/observability.wrn b/app/pages/guides/observability.wrn index d4699659..a0ec42d1 100644 --- a/app/pages/guides/observability.wrn +++ b/app/pages/guides/observability.wrn @@ -7,8 +7,8 @@ page Observability { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Observability

Use tracking middleware and sinks with redaction, sampling, stable request identifiers, alert ownership, and retention limits. Never capture registry tokens or session secrets.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/pages-and-components.wrn b/app/pages/guides/pages-and-components.wrn index d3c23c3a..7efe9ed2 100644 --- a/app/pages/guides/pages-and-components.wrn +++ b/app/pages/guides/pages-and-components.wrn @@ -7,8 +7,8 @@ page Pagesandcomponents { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Pages and components

Pages are routable, components are reusable, and layouts provide shared slots. Mount a component with data-component and keep browser state scoped and minimal.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/project-structure.wrn b/app/pages/guides/project-structure.wrn index 222ea18e..fcaf2512 100644 --- a/app/pages/guides/project-structure.wrn +++ b/app/pages/guides/project-structure.wrn @@ -7,8 +7,8 @@ page Projectstructure { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Project structure

Pages, components, layouts, APIs, middleware, schemas, database files, locales, realtime rooms, and styles live under app. Never edit app/routes.gen.ts, .wrnexus, or dist by hand.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/pubsub.wrn b/app/pages/guides/pubsub.wrn index 363215be..6919f896 100644 --- a/app/pages/guides/pubsub.wrn +++ b/app/pages/guides/pubsub.wrn @@ -7,8 +7,8 @@ page Pubsub { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Pub/sub

The in-process driver cannot cross processes. Use Redis where instances must share events, define channel ownership, and design for reconnects and duplicate delivery.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/queues.wrn b/app/pages/guides/queues.wrn index 797b43c9..e879aed7 100644 --- a/app/pages/guides/queues.wrn +++ b/app/pages/guides/queues.wrn @@ -7,8 +7,8 @@ page Queues { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Queues

Queue behavior is preview-level. Treat in-process work as non-durable, make handlers idempotent, cap retries, record failures, and choose a production persistence strategy.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/realtime.wrn b/app/pages/guides/realtime.wrn index b178f342..4c85078a 100644 --- a/app/pages/guides/realtime.wrn +++ b/app/pages/guides/realtime.wrn @@ -7,8 +7,8 @@ page Realtime { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Realtime

defineRoom handles connection and messages. Validate message shapes, authorize subscriptions, restrict origins, bound payloads, and use pub/sub to scale across processes.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/routing.wrn b/app/pages/guides/routing.wrn index ae4af20c..fc45ecbc 100644 --- a/app/pages/guides/routing.wrn +++ b/app/pages/guides/routing.wrn @@ -7,8 +7,8 @@ page Routing { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Routing

A page filename defines its URL; index maps to the directory root and bracket segments are dynamic parameters. API files under app/api expose HTTP method functions and receive a Context.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/security.wrn b/app/pages/guides/security.wrn index 1bbc74fb..cdd697eb 100644 --- a/app/pages/guides/security.wrn +++ b/app/pages/guides/security.wrn @@ -7,8 +7,8 @@ page Applicationsecurity { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Application security

Use CSP, CSRF, Trusted Types, session hardening, validation, origin checks, upload restrictions, encryption, request limits, and explicit CORS. See the security policy for reporting.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/server-data.wrn b/app/pages/guides/server-data.wrn index 85fe4a29..8e5b44fc 100644 --- a/app/pages/guides/server-data.wrn +++ b/app/pages/guides/server-data.wrn @@ -7,8 +7,8 @@ page Serverdata { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Server data

Use an ssr API binding, return the desired response field, and render it with a server #each block. Values are escaped. Avoid fetching private data through a route that lacks authorization.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/testing.wrn b/app/pages/guides/testing.wrn index 02e7a1e6..09b5a9be 100644 --- a/app/pages/guides/testing.wrn +++ b/app/pages/guides/testing.wrn @@ -7,8 +7,8 @@ page Testing { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Testing

Use Bun tests and @wrnexus/test helpers. Cover server HTML, API status and validation, authorization failures, reactive behavior, and a production startup smoke test.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/troubleshooting.wrn b/app/pages/guides/troubleshooting.wrn index 88b595cb..f5ce22cf 100644 --- a/app/pages/guides/troubleshooting.wrn +++ b/app/pages/guides/troubleshooting.wrn @@ -7,8 +7,8 @@ page Troubleshooting { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Troubleshooting

Confirm Bun and package versions, regenerate docs/routes through supported commands, read the first compiler diagnostic, check file naming, validate config, and reproduce under a production build.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/upgrading.wrn b/app/pages/guides/upgrading.wrn index 6f14c77e..d6bdbb8e 100644 --- a/app/pages/guides/upgrading.wrn +++ b/app/pages/guides/upgrading.wrn @@ -7,8 +7,8 @@ page Upgrading { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Upgrading

Back up and commit first, then use wrnexus update --latest as documented by the installed CLI. Review migrations and keep every @wrnexus package aligned. Current release: 0.5.0.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/uploads.wrn b/app/pages/guides/uploads.wrn index 5a537837..7a857b6e 100644 --- a/app/pages/guides/uploads.wrn +++ b/app/pages/guides/uploads.wrn @@ -7,8 +7,8 @@ page Uploads { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Uploads

Configure named stores, accepted MIME/extensions, and maxBytes. Random keys avoid path traversal. Private files require an authenticated serving route; v1 buffers each file in memory.

Release scope

This guide describes installed 0.5.0 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · Troubleshooting · Support

diff --git a/app/pages/guides/workspaces-and-gateway.wrn b/app/pages/guides/workspaces-and-gateway.wrn index a78a1063..14380312 100644 --- a/app/pages/guides/workspaces-and-gateway.wrn +++ b/app/pages/guides/workspaces-and-gateway.wrn @@ -7,8 +7,8 @@ page Workspacesandgateway { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview guide · 0.5.0

Workspaces and gateway

A workspace runs isolated applications behind one domain-routing gateway. Add an application from the workspace root; the CLI scaffolds apps/reports and registers it in wrnexus.workspace.ts:

wrnexus workspace add reports --domain=reports.localhost
 bun install
 bun run dev

Forward authentication

Point protected applications at a dedicated verifier endpoint. The verifier must return 2xx for an authenticated session, 401/403 to deny access, or an HTTP redirect to begin browser login.

// wrnexus.workspace.ts
diff --git a/app/pages/index.wrn b/app/pages/index.wrn
index f2e6f635..993f0a00 100644
--- a/app/pages/index.wrn
+++ b/app/pages/index.wrn
@@ -7,8 +7,8 @@ page Home {
   view {
     
     
-
W WRNexusJS
- +
W WRNexusJS
+

WRNexusJS v0.5.0

Build from the server.
Ship only what matters.

WRNexusJS is an SSR-first, Bun-native full-stack framework. Build typed pages, APIs, validated forms, realtime rooms, secure sessions, workspaces, and optional mobile experiences with the .wrn component language.

Private developer preview. Request access to the package registry.

app/pages/dashboard.wrn
page Dashboard {
   ssr { api tasks GET /api/tasks { return tasks } }
   view {
diff --git a/app/pages/language.wrn b/app/pages/language.wrn
index 1a044411..f1a0244e 100644
--- a/app/pages/language.wrn
+++ b/app/pages/language.wrn
@@ -7,8 +7,8 @@ page Thewrnlanguage {
   view {
     
     
-
W WRNexusJS
- +
W WRNexusJS
+
Language reference · 0.5.0

The .wrn language

File anatomy

page Account {
   layout = "public"
   seo { title = "Account" description = "Manage your account." }
diff --git a/app/pages/license.wrn b/app/pages/license.wrn
index 74d15d1b..259c5aa9 100644
--- a/app/pages/license.wrn
+++ b/app/pages/license.wrn
@@ -7,8 +7,8 @@ page License {
   view {
     
     
-
W WRNexusJS
- +
W WRNexusJS
+

License

Owner decision required

This documentation repository contains no public license file, and the packages are unavailable from the public npm registry. No open-source license or redistribution right should be inferred.

Approved preview users must follow the private/commercial terms supplied by WorkRoot. Contact WorkRoot before copying, redistributing, or using WRNexusJS in production.

diff --git a/app/pages/packages.wrn b/app/pages/packages.wrn index 7a49aa1e..5fa7583a 100644 --- a/app/pages/packages.wrn +++ b/app/pages/packages.wrn @@ -11,10 +11,10 @@ page Packages {
W WRNexusJS - +
- +
26 focused packages

Package reference

Everything in the framework, organized by responsibility and documented from the published 0.5.0 APIs.

Showing {category} packages

AI

@wrnexus/ai

Server-side Anthropic client with generation and streaming.

Open documentation →
diff --git a/app/pages/packages/ai.wrn b/app/pages/packages/ai.wrn index 9193fd4e..b0457016 100644 --- a/app/pages/packages/ai.wrn +++ b/app/pages/packages/ai.wrn @@ -9,10 +9,10 @@ page wrnexusai {
W WRNexusJS - +
- +
AI · Package reference

@wrnexus/ai

Server-side Anthropic client with generation and streaming.

v0.5.0Private registryAI

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/ai@0.5.0

Request preview access. Never put registry tokens in source control.

A tiny, zero-dependency Claude (Anthropic) client for WRNexusJS apps — generate and stream text with Claude from any server-side code.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/authz.wrn b/app/pages/packages/authz.wrn index 1a7a9e66..aa038c33 100644 --- a/app/pages/packages/authz.wrn +++ b/app/pages/packages/authz.wrn @@ -9,10 +9,10 @@ page wrnexusauthz {
W WRNexusJS - +
- +
Security · Package reference

@wrnexus/authz

Role, permission, policy, and authorization guards.

v0.5.0Private registrySecurity

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/authz@0.5.0

Request preview access. Never put registry tokens in source control.

Composable authorization for WRNexusJS — role-based (RBAC), policy-based (PBAC), and attribute-based (ABAC) access control that reduces to a boolean check plus an authorize() guard.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/cli.wrn b/app/pages/packages/cli.wrn index 5b416e10..1a069b6b 100644 --- a/app/pages/packages/cli.wrn +++ b/app/pages/packages/cli.wrn @@ -9,10 +9,10 @@ page wrnexuscli {
W WRNexusJS - +
- +
Tooling · Package reference

@wrnexus/cli

Create, develop, build, generate, test, and maintain WRNexusJS apps.

v0.5.0Private registryTooling

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/cli@0.5.0

Request preview access. Never put registry tokens in source control.

The wrnexus command-line tool that scaffolds, runs, builds, tests, and manages WRNexusJS apps.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/compiler.wrn b/app/pages/packages/compiler.wrn index 0af601d7..f437a4f4 100644 --- a/app/pages/packages/compiler.wrn +++ b/app/pages/packages/compiler.wrn @@ -9,10 +9,10 @@ page wrnexuscompiler {
W WRNexusJS - +
- +
Core · Package reference

@wrnexus/compiler

Parser and code generators for the .wrn language.

v0.5.0Private registryCore

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/compiler@0.5.0

Request preview access. Never put registry tokens in source control.

Compiler for the .wrn language — tokenizes, parses, and lowers .wrn page and component files to TypeScript.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/core.wrn b/app/pages/packages/core.wrn index 73c68be1..63ab826c 100644 --- a/app/pages/packages/core.wrn +++ b/app/pages/packages/core.wrn @@ -9,10 +9,10 @@ page wrnexuscore {
W WRNexusJS - +
- +
Core · Package reference

@wrnexus/core

Contexts, middleware, security, sessions, caching, JSX, and realtime.

v0.5.0Private registryCore

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/core@0.5.0

Request preview access. Never put registry tokens in source control.

The framework core: the request Context, middleware contract, and the security, session, caching, streaming, realtime, and JSX primitives every other WRNexusJS package builds on.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/csr.wrn b/app/pages/packages/csr.wrn index 5e365a9f..51702b9b 100644 --- a/app/pages/packages/csr.wrn +++ b/app/pages/packages/csr.wrn @@ -9,10 +9,10 @@ page wrnexuscsr {
W WRNexusJS - +
- +
Frontend · Package reference

@wrnexus/csr

Reactive, navigation, and realtime browser runtimes.

v0.5.0Private registryFrontend

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/csr@0.5.0

Request preview access. Never put registry tokens in source control.

The browser-side client runtime for WRNexusJS — generic, self-contained JS that hydrates server-rendered pages with reactivity, client-side navigation, and realtime rooms.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/db.wrn b/app/pages/packages/db.wrn index 1c216dd6..94ca2fbd 100644 --- a/app/pages/packages/db.wrn +++ b/app/pages/packages/db.wrn @@ -9,10 +9,10 @@ page wrnexusdb {
W WRNexusJS - +
- +
Data · Package reference

@wrnexus/db

Database adapters, typed queries, models, migrations, and sessions.

v0.5.0Private registryData

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/db@0.5.0

Request preview access. Never put registry tokens in source control.

The database layer for WRNexusJS: TS models as the single source of truth for DDL, validation, and result typing, plus a driver-based Db client, migrations, and a sqlc-style query generator.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/dev-server.wrn b/app/pages/packages/dev-server.wrn index baa4bd4a..60a266a0 100644 --- a/app/pages/packages/dev-server.wrn +++ b/app/pages/packages/dev-server.wrn @@ -9,10 +9,10 @@ page wrnexusdevserver {
W WRNexusJS - +
- +
Runtime · Package reference

@wrnexus/dev-server

Development and production servers, HMR, assets, and gateways.

v0.5.0Private registryRuntime

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/dev-server@0.5.0

Request preview access. Never put registry tokens in source control.

The WRNexusJS HTTP + WebSocket server runtime — request dispatch, SSR document assembly, live-reload (HMR), and the portable production handler.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/encryption.wrn b/app/pages/packages/encryption.wrn index 680ecaa4..3ea52102 100644 --- a/app/pages/packages/encryption.wrn +++ b/app/pages/packages/encryption.wrn @@ -9,10 +9,10 @@ page wrnexusencryption {
W WRNexusJS - +
- +
Security · Package reference

@wrnexus/encryption

Hashing, HMAC, authenticated encryption, and key derivation.

v0.5.0Private registrySecurity

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/encryption@0.5.0

Request preview access. Never put registry tokens in source control.

Dependency-free crypto helpers for WRNexusJS: authenticated symmetric encryption (AES-256-GCM), hashing, and HMAC signing.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/helpers.wrn b/app/pages/packages/helpers.wrn index cdae58c7..653e3389 100644 --- a/app/pages/packages/helpers.wrn +++ b/app/pages/packages/helpers.wrn @@ -9,10 +9,10 @@ page wrnexushelpers {
W WRNexusJS - +
- +
Tooling · Package reference

@wrnexus/helpers

Safe Context URL helpers and forward-auth login redirects.

v0.5.0Private registryTooling

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/helpers@0.5.0

Request preview access. Never put registry tokens in source control.

Safe convenience helpers for common WRNexusJS application flows. The package uses standard Context, URL, and Response values and has no runtime dependency beyond @wrnexus/core.

bun add @wrnexus/helpers
diff --git a/app/pages/packages/i18n.wrn b/app/pages/packages/i18n.wrn index 23e473c8..d7d75298 100644 --- a/app/pages/packages/i18n.wrn +++ b/app/pages/packages/i18n.wrn @@ -9,10 +9,10 @@ page wrnexusi18n {
W WRNexusJS - +
- +
Frontend · Package reference

@wrnexus/i18n

Translation loading, locale resolution, and Intl formatting.

v0.5.0Private registryFrontend

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/i18n@0.5.0

Request preview access. Never put registry tokens in source control.

Per-request translations plus locale-aware number, date, and currency formatting for WRNexusJS apps.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/jwt.wrn b/app/pages/packages/jwt.wrn index a38045e0..93c38c71 100644 --- a/app/pages/packages/jwt.wrn +++ b/app/pages/packages/jwt.wrn @@ -9,10 +9,10 @@ page wrnexusjwt {
W WRNexusJS - +
- +
Security · Package reference

@wrnexus/jwt

HS256 JWT signing, verification, and bearer authentication.

v0.5.0Private registrySecurity

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/jwt@0.5.0

Request preview access. Never put registry tokens in source control.

Dependency-free JSON Web Tokens (HS256) via Web Crypto, plus a bearer-token auth middleware for WRNexusJS.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/mobile.wrn b/app/pages/packages/mobile.wrn index 2065811b..d0cfc15f 100644 --- a/app/pages/packages/mobile.wrn +++ b/app/pages/packages/mobile.wrn @@ -9,10 +9,10 @@ page wrnexusmobile {
W WRNexusJS - +
- +
Native · Package reference

@wrnexus/mobile

SSR-safe compatibility access to Capacitor plugins.

v0.5.0Private registryNative

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/mobile@0.5.0

Request preview access. Never put registry tokens in source control.

SSR-safe access to Capacitor plugins from WRNexusJS browser code.

Overview

diff --git a/app/pages/packages/native.wrn b/app/pages/packages/native.wrn index c9bd39cc..d53c983e 100644 --- a/app/pages/packages/native.wrn +++ b/app/pages/packages/native.wrn @@ -9,10 +9,10 @@ page wrnexusnative {
W WRNexusJS - +
- +
Native · Package reference

@wrnexus/native

Cross-platform browser and Capacitor capability registry.

v0.5.0Private registryNative

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/native@0.5.0

Request preview access. Never put registry tokens in source control.

Cross-platform capabilities for browsers, Capacitor WebViews, and compiled native apps.

Overview

diff --git a/app/pages/packages/oauth.wrn b/app/pages/packages/oauth.wrn index 66cbf84f..c0ca9159 100644 --- a/app/pages/packages/oauth.wrn +++ b/app/pages/packages/oauth.wrn @@ -9,10 +9,10 @@ page wrnexusoauth {
W WRNexusJS - +
- +
Security · Package reference

@wrnexus/oauth

OAuth 2.0, PKCE, provider presets, and profile mapping.

v0.5.0Private registrySecurity

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/oauth@0.5.0

Request preview access. Never put registry tokens in source control.

Dependency-free OAuth 2.0 sign-in for any provider, with PKCE and presets for Google, GitHub, and Discord.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/pubsub.wrn b/app/pages/packages/pubsub.wrn index ab3da702..d91467d5 100644 --- a/app/pages/packages/pubsub.wrn +++ b/app/pages/packages/pubsub.wrn @@ -9,10 +9,10 @@ page wrnexuspubsub {
W WRNexusJS - +
- +
Realtime · Package reference

@wrnexus/pubsub

In-process and Redis-backed publish/subscribe.

v0.5.0Private registryRealtime

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/pubsub@0.5.0

Request preview access. Never put registry tokens in source control.

Topic-based publish/subscribe with a pluggable driver — in-process by default, Redis for cross-process messaging.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/queue.wrn b/app/pages/packages/queue.wrn index 9c2530cf..9f96bacf 100644 --- a/app/pages/packages/queue.wrn +++ b/app/pages/packages/queue.wrn @@ -9,10 +9,10 @@ page wrnexusqueue {
W WRNexusJS - +
- +
Data · Package reference

@wrnexus/queue

Background jobs with delay, concurrency, retry, and repetition.

v0.5.0Private registryData

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/queue@0.5.0

Request preview access. Never put registry tokens in source control.

A background job queue with delays, retries + exponential backoff, recurring jobs, and concurrent workers.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/reactive.wrn b/app/pages/packages/reactive.wrn index d661f2e4..fd9a41c1 100644 --- a/app/pages/packages/reactive.wrn +++ b/app/pages/packages/reactive.wrn @@ -9,10 +9,10 @@ page wrnexusreactive {
W WRNexusJS - +
- +
Frontend · Package reference

@wrnexus/reactive

Small type-safe reactive signal primitives.

v0.5.0Private registryFrontend

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/reactive@0.5.0

Request preview access. Never put registry tokens in source control.

Tiny, type-safe reactive primitives (signals) with zero dependencies.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/router.wrn b/app/pages/packages/router.wrn index feb94a64..800fe291 100644 --- a/app/pages/packages/router.wrn +++ b/app/pages/packages/router.wrn @@ -9,10 +9,10 @@ page wrnexusrouter {
W WRNexusJS - +
- +
Core · Package reference

@wrnexus/router

Filesystem discovery, route matching, and typed route generation.

v0.5.0Private registryCore

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/router@0.5.0

Request preview access. Never put registry tokens in source control.

File-based router that maps an app/ directory onto route tables and matches request paths against them.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/ssr.wrn b/app/pages/packages/ssr.wrn index fc7e1fca..421b4562 100644 --- a/app/pages/packages/ssr.wrn +++ b/app/pages/packages/ssr.wrn @@ -9,10 +9,10 @@ page wrnexusssr {
W WRNexusJS - +
- +
Runtime · Package reference

@wrnexus/ssr

Secure HTML document rendering and SEO metadata.

v0.5.0Private registryRuntime

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/ssr@0.5.0

Request preview access. Never put registry tokens in source control.

Server-side rendering: wraps a page's HTML body in a complete HTML document with a metadata-driven <head>.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/styles.wrn b/app/pages/packages/styles.wrn index 584713ef..ca63dda3 100644 --- a/app/pages/packages/styles.wrn +++ b/app/pages/packages/styles.wrn @@ -9,10 +9,10 @@ page wrnexusstyles {
W WRNexusJS - +
- +
Frontend · Package reference

@wrnexus/styles

CSS pipeline, themes, fonts, profiles, and application config.

v0.5.0Private registryFrontend

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/styles@0.5.0

Request preview access. Never put registry tokens in source control.

Global CSS bundling, the --wire-* design-token theme system, and the wrnexus.config.ts app-config loader for WRNexusJS apps.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/test.wrn b/app/pages/packages/test.wrn index 2fb9dd70..ffcc7c7d 100644 --- a/app/pages/packages/test.wrn +++ b/app/pages/packages/test.wrn @@ -9,10 +9,10 @@ page wrnexustest {
W WRNexusJS - +
- +
Tooling · Package reference

@wrnexus/test

WRNexusJS-aware component, route, and browser testing utilities.

v0.5.0Private registryTooling

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/test@0.5.0

Request preview access. Never put registry tokens in source control.

Testing utilities for WRNexusJS apps — component rendering, reactive-DOM mounting, route handler calls, and a full in-process app harness, plus a one-import re-export of bun:test.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/tracking.wrn b/app/pages/packages/tracking.wrn index a525f7d3..d917cdfe 100644 --- a/app/pages/packages/tracking.wrn +++ b/app/pages/packages/tracking.wrn @@ -9,10 +9,10 @@ page wrnexustracking {
W WRNexusJS - +
- +
Runtime · Package reference

@wrnexus/tracking

Error/event capture, middleware, filtering, and sinks.

v0.5.0Private registryRuntime

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/tracking@0.5.0

Request preview access. Never put registry tokens in source control.

Error tracking for WRNexusJS apps: capture exceptions manually or via middleware and fan them out to pluggable sinks.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/packages/ui.wrn b/app/pages/packages/ui.wrn index 1e82e5e0..892f8f01 100644 --- a/app/pages/packages/ui.wrn +++ b/app/pages/packages/ui.wrn @@ -9,10 +9,10 @@ page wrnexusui {
W WRNexusJS - +
- +
Frontend · Package reference

@wrnexus/ui

Themeable server-rendered UI components and CSS.

v0.5.0Private registryFrontend

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/ui@0.5.0

Request preview access. Never put registry tokens in source control.

First-party Wire UI component library — a set of themeable .wrn components plus a single tokenized stylesheet.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

@@ -152,7 +152,7 @@ const router = buildRouter(appDir, { componentDirs: [uiComponentsDir()]  <div data-component="badge" label="New"></div> <button data-component="button" label="Save" variant="primary" size="lg"></button> <div data-component="alert" variant="success" title="Done" message="Saved."></div> -</div>

Explore the component library

The component reference now has a dedicated catalog with searchable cards and one complete page per component.

Browse all 85 components →

+</div>

Explore the component library

Browse interactive examples and complete component usage in the dedicated WRNexusJS component showcase.

Browse all 85 components →

diff --git a/app/pages/packages/uploader.wrn b/app/pages/packages/uploader.wrn index 1184a5b0..bc21c2e0 100644 --- a/app/pages/packages/uploader.wrn +++ b/app/pages/packages/uploader.wrn @@ -9,10 +9,10 @@ page wrnexusuploader {
W WRNexusJS - +
- +
Data · Package reference

@wrnexus/uploader

Validated local/S3 uploads and secure file serving.

v0.5.0Private registryData

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/uploader@0.5.0

Request preview access. Never put registry tokens in source control.

Config-driven file uploads + serving for WRNexusJS. Declare named storage stores (local disk or any S3-compatible backend) in wrnexus.config.ts, upload with one function call, drop a drag-and-drop widget on a page, and serve files back — public or private. Zero external dependencies (S3 is signed with a built-in AWS SigV4 implementation, like the rest of the framework).

Usage

diff --git a/app/pages/packages/validation.wrn b/app/pages/packages/validation.wrn index 1cbef12b..a923e76b 100644 --- a/app/pages/packages/validation.wrn +++ b/app/pages/packages/validation.wrn @@ -9,10 +9,10 @@ page wrnexusvalidation {
W WRNexusJS - +
- +
Security · Package reference

@wrnexus/validation

Typed schemas, coercion, validation, and browser descriptors.

v0.5.0Private registrySecurity

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/validation@0.5.0

Request preview access. Never put registry tokens in source control.

One fluent schema, validated on the server (API bodies, env vars) and mirrored to an eval-free browser validator for forms.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

diff --git a/app/pages/releases/0.5.0.wrn b/app/pages/releases/0.5.0.wrn index 489d53bf..7f550455 100644 --- a/app/pages/releases/0.5.0.wrn +++ b/app/pages/releases/0.5.0.wrn @@ -7,8 +7,8 @@ page Release050 { view {
-
W WRNexusJS
- +
W WRNexusJS
+

WRNexusJS 0.5.0

Released 2026-07-13. All 31 installed packages are aligned to this version. Highlights include the new helpers package, reliable workspace app addition, and browser SSO redirects through forward authentication. See the changelog, upgrade guide, and package references.

diff --git a/app/pages/roadmap.wrn b/app/pages/roadmap.wrn index b47432f5..021dd268 100644 --- a/app/pages/roadmap.wrn +++ b/app/pages/roadmap.wrn @@ -7,8 +7,8 @@ page Roadmap { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Roadmap

Roadmap items are direction, not delivery commitments. Dates require explicit owner approval.

Now

  • Private preview onboarding and accurate package references
  • Runnable documentation fixtures and link/accessibility checks
  • License, support, and disclosure owner decisions

Next

  • Durable queue driver guidance
  • Expanded database/auth/realtime examples
  • Versioned release notes and migration fixtures

Later / exploration

  • Historical documentation selector
  • Maintained reproducible benchmarks
  • Broader mobile/native coverage
diff --git a/app/pages/search.wrn b/app/pages/search.wrn index 9c24c06f..61d5baf4 100644 --- a/app/pages/search.wrn +++ b/app/pages/search.wrn @@ -7,9 +7,9 @@ page Search { view {
-
W WRNexusJS
- -

Search documentation

This build-time index is local and sends no query to a third party. Use your browser’s find command to filter this compact index.

Core documentation

Get started · Packages · Components · Language · Architecture

Guides

Project structure · Routing · Pages and components · Server data · API routes · Middleware · Forms and validation · Authentication · Authorization · Application security · Database · Uploads · Realtime · Pub/sub · Queues · Testing · Workspaces and gateway · Deployment · Configuration and profiles · Internationalization and themes · Mobile · Observability · Upgrading · Troubleshooting

Package APIs

@wrnexus/ai · @wrnexus/authz · @wrnexus/compiler · @wrnexus/core · @wrnexus/csr · @wrnexus/db · @wrnexus/dev-server · @wrnexus/encryption · @wrnexus/helpers · @wrnexus/i18n · @wrnexus/jwt · @wrnexus/mobile · @wrnexus/native · @wrnexus/oauth · @wrnexus/pubsub · @wrnexus/queue · @wrnexus/reactive · @wrnexus/router · @wrnexus/ssr · @wrnexus/styles · @wrnexus/test · @wrnexus/tracking · @wrnexus/ui · @wrnexus/uploader · @wrnexus/validation · @wrnexus/dev-toolbar · @wrnexus/syntax · @wrnexus/plugin · @wrnexus/captcha · @wrnexus/auth · @wrnexus/cli

+
W WRNexusJS
+ +

Search documentation

This build-time index is local and sends no query to a third party. Use your browser’s find command to filter this compact index.

Core documentation

Get started · Packages · Components · Language · Architecture

Guides

Project structure · Routing · Pages and components · Server data · API routes · Middleware · Forms and validation · Authentication · Authorization · Application security · Database · Uploads · Realtime · Pub/sub · Queues · Testing · Workspaces and gateway · Deployment · Configuration and profiles · Internationalization and themes · Mobile · Observability · Upgrading · Troubleshooting

Package APIs

@wrnexus/ai · @wrnexus/authz · @wrnexus/compiler · @wrnexus/core · @wrnexus/csr · @wrnexus/db · @wrnexus/dev-server · @wrnexus/encryption · @wrnexus/helpers · @wrnexus/i18n · @wrnexus/jwt · @wrnexus/mobile · @wrnexus/native · @wrnexus/oauth · @wrnexus/pubsub · @wrnexus/queue · @wrnexus/reactive · @wrnexus/router · @wrnexus/ssr · @wrnexus/styles · @wrnexus/test · @wrnexus/tracking · @wrnexus/ui · @wrnexus/uploader · @wrnexus/validation · @wrnexus/dev-toolbar · @wrnexus/syntax · @wrnexus/plugin · @wrnexus/captcha · @wrnexus/auth · @wrnexus/cli

diff --git a/app/pages/security.wrn b/app/pages/security.wrn index 37da7410..30cb87b4 100644 --- a/app/pages/security.wrn +++ b/app/pages/security.wrn @@ -7,8 +7,8 @@ page Security { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Security

WRNexusJS provides primitives for CSP, CSRF, Trusted Types, sessions, validation, authorization, encryption, upload restrictions, request limits, and WebSocket-origin checks. Applications remain responsible for correct configuration, business authorization, secrets, dependencies, data protection, and operations.

Supported releases

Only the current private-preview release 0.5.0 is documented here. A formal old-release support window is not yet published.

Report a vulnerability

Use WorkRoot’s approved private contact path at workroot.in. Do not publish exploit details or secrets. Include affected version, impact, reproduction, and a safe contact method. Response targets, encryption key, bounty, audit, and certification are not currently claimed.

Deployment controls

Use restrictive CSP and permissions policies, HSTS only on HTTPS production origins, MIME sniffing protection, restrictive referrers, explicit CORS, secure cookies, CSRF validation, request limits, and origin checks. See application security.

diff --git a/app/pages/showcase.wrn b/app/pages/showcase.wrn index 4e67648a..af4a11f7 100644 --- a/app/pages/showcase.wrn +++ b/app/pages/showcase.wrn @@ -7,8 +7,8 @@ page Showcase { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Showcase

Only confirmed public properties are listed; no customer or traffic claims are made.

WorkRoot

Creator/company site demonstrating a public WRNexusJS deployment. Exact deployed version and infrastructure notes await owner confirmation.

WRNexusJS docs

This documentation portal, built and verified against WRNexusJS 0.5.0 on Bun.

Screenshots are intentionally deferred until approved assets and alt text are available.

diff --git a/app/pages/support.wrn b/app/pages/support.wrn index 4c89de04..240b5634 100644 --- a/app/pages/support.wrn +++ b/app/pages/support.wrn @@ -7,8 +7,8 @@ page Support { view {
-
W WRNexusJS
- +
W WRNexusJS
+

Support

WRNexusJS has no public Discord, public issue tracker, or guaranteed community support channel listed by this repository. Preview access and support begin through WorkRoot’s public contact path.

Include WRNexusJS version, Bun version, OS, a minimal reproduction, expected and actual behavior, and sanitized logs. Never include registry tokens, credentials, session cookies, or private data.

Support scope, service levels, and commercial terms require owner confirmation.

diff --git a/app/pages/tutorial.wrn b/app/pages/tutorial.wrn index b19d823c..16152dbe 100644 --- a/app/pages/tutorial.wrn +++ b/app/pages/tutorial.wrn @@ -7,8 +7,8 @@ page Tutorial { view {
-
W WRNexusJS
- +
W WRNexusJS
+
Preview · runnable-project extraction pending

Secure task board

This tutorial connects the installed 0.5.0 APIs into one design. Snippets are limited to declarations and README patterns verified in the installed packages; a CI-compiled standalone fixture remains on the roadmap.

Application shape

A .wrn page renders tasks from an SSR API binding. A shared validation schema protects mutations. Session middleware identifies users, authorization policies gate updates, and a realtime room broadcasts changes.

Schema and create route

import { v, parseBody } from "@wrnexus/validation";
 const task = v.object({ title: v.string().trim().min(3).max(120) });
 export const POST = async (ctx) => {
diff --git a/public/docs-index.json b/public/docs-index.json
index bb94b563..cb41ea28 100644
--- a/public/docs-index.json
+++ b/public/docs-index.json
@@ -52,13 +52,6 @@
       "version": "0.5.0",
       "stability": "preview"
     },
-    {
-      "url": "/components",
-      "title": "components",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
     {
       "url": "/guides/project-structure",
       "title": "project structure",
@@ -520,601 +513,6 @@
       "section": "package",
       "version": "0.5.0",
       "stability": "preview"
-    },
-    {
-      "url": "/components/accordion",
-      "title": "accordion",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/advanced-date-picker",
-      "title": "advanced date picker",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/advanced-range-slider",
-      "title": "advanced range slider",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/advanced-select",
-      "title": "advanced select",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/alert",
-      "title": "alert",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/avatar",
-      "title": "avatar",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/avatar-group",
-      "title": "avatar group",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/badge",
-      "title": "badge",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/blockquote",
-      "title": "blockquote",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/breadcrumb",
-      "title": "breadcrumb",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/button",
-      "title": "button",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/button-group",
-      "title": "button group",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/card",
-      "title": "card",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/carousel",
-      "title": "carousel",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/chart",
-      "title": "chart",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/chat-bubble",
-      "title": "chat bubble",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/checkbox",
-      "title": "checkbox",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/clipboard",
-      "title": "clipboard",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/collapse",
-      "title": "collapse",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/color-picker",
-      "title": "color picker",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/columns",
-      "title": "columns",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/combo-box",
-      "title": "combo box",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/confetti",
-      "title": "confetti",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/container",
-      "title": "container",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/context-menu",
-      "title": "context menu",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/copy-markup",
-      "title": "copy markup",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/custom-scrollbar",
-      "title": "custom scrollbar",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/data-map",
-      "title": "data map",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/data-table",
-      "title": "data table",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/date-picker",
-      "title": "date picker",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/device-frame",
-      "title": "device frame",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/divider",
-      "title": "divider",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/drag-and-drop",
-      "title": "drag and drop",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/drawer",
-      "title": "drawer",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/dropdown",
-      "title": "dropdown",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/file-input",
-      "title": "file input",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/file-upload",
-      "title": "file upload",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/file-upload-progress",
-      "title": "file upload progress",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/grid",
-      "title": "grid",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/image",
-      "title": "image",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/input",
-      "title": "input",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/input-group",
-      "title": "input group",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/input-number",
-      "title": "input number",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/kbd",
-      "title": "kbd",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/layout-splitter",
-      "title": "layout splitter",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/legend-indicator",
-      "title": "legend indicator",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/link",
-      "title": "link",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/list",
-      "title": "list",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/list-group",
-      "title": "list group",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/map",
-      "title": "map",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/marquee",
-      "title": "marquee",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/mega-menu",
-      "title": "mega menu",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/modal",
-      "title": "modal",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/nav",
-      "title": "nav",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/navbar",
-      "title": "navbar",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/pagination",
-      "title": "pagination",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/pin-input",
-      "title": "pin input",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/popover",
-      "title": "popover",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/progress",
-      "title": "progress",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/radio",
-      "title": "radio",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/range-slider",
-      "title": "range slider",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/rating",
-      "title": "rating",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/scrollspy",
-      "title": "scrollspy",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/search-box",
-      "title": "search box",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/select",
-      "title": "select",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/sidebar",
-      "title": "sidebar",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/skeleton",
-      "title": "skeleton",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/spinner",
-      "title": "spinner",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/stepper",
-      "title": "stepper",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/strong-password",
-      "title": "strong password",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/styled-icon",
-      "title": "styled icon",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/switch",
-      "title": "switch",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/table",
-      "title": "table",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/tabs",
-      "title": "tabs",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/textarea",
-      "title": "textarea",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/timeline",
-      "title": "timeline",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/time-picker",
-      "title": "time picker",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/toast",
-      "title": "toast",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/toast-notifications",
-      "title": "toast notifications",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/toggle-count",
-      "title": "toggle count",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/toggle-password",
-      "title": "toggle password",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/tooltip",
-      "title": "tooltip",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/tree-view",
-      "title": "tree view",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/typography",
-      "title": "typography",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
-    },
-    {
-      "url": "/components/wysiwyg-editor",
-      "title": "wysiwyg editor",
-      "section": "portal",
-      "version": "0.5.0",
-      "stability": "preview"
     }
   ]
 }
diff --git a/public/llms-full.txt b/public/llms-full.txt
index 992eecd0..35c8a253 100644
--- a/public/llms-full.txt
+++ b/public/llms-full.txt
@@ -1,763 +1,17436 @@
-# WRNexusJS 0.5.0 comprehensive documentation index
+# WRNexusJS documentation 0.5.0
 
-Status: Private Developer Preview. Runtime: Bun. UI language: .wrn.
-This is the documentation application, not the framework monorepo.
+Status: Private Developer Preview. This site documents 31 release-aligned packages.
 
-- https://wrnexusjs.dev/
-- https://wrnexusjs.dev/access
-- https://wrnexusjs.dev/getting-started
-- https://wrnexusjs.dev/tutorial
-- https://wrnexusjs.dev/architecture
-- https://wrnexusjs.dev/language
-- https://wrnexusjs.dev/packages
-- https://wrnexusjs.dev/components
-- https://wrnexusjs.dev/guides/project-structure
-- https://wrnexusjs.dev/guides/routing
-- https://wrnexusjs.dev/guides/pages-and-components
-- https://wrnexusjs.dev/guides/server-data
-- https://wrnexusjs.dev/guides/api-routes
-- https://wrnexusjs.dev/guides/middleware
-- https://wrnexusjs.dev/guides/forms-and-validation
-- https://wrnexusjs.dev/guides/authentication
-- https://wrnexusjs.dev/guides/authorization
-- https://wrnexusjs.dev/guides/security
-- https://wrnexusjs.dev/guides/database
-- https://wrnexusjs.dev/guides/uploads
-- https://wrnexusjs.dev/guides/realtime
-- https://wrnexusjs.dev/guides/pubsub
-- https://wrnexusjs.dev/guides/queues
-- https://wrnexusjs.dev/guides/testing
-- https://wrnexusjs.dev/guides/workspaces-and-gateway
-- https://wrnexusjs.dev/guides/deployment
-- https://wrnexusjs.dev/guides/configuration-and-profiles
-- https://wrnexusjs.dev/guides/i18n-and-themes
-- https://wrnexusjs.dev/guides/mobile
-- https://wrnexusjs.dev/guides/observability
-- https://wrnexusjs.dev/guides/upgrading
-- https://wrnexusjs.dev/guides/troubleshooting
-- https://wrnexusjs.dev/examples
-- https://wrnexusjs.dev/showcase
-- https://wrnexusjs.dev/benchmarks
-- https://wrnexusjs.dev/roadmap
-- https://wrnexusjs.dev/changelog
-- https://wrnexusjs.dev/releases/0.5.0
-- https://wrnexusjs.dev/security
-- https://wrnexusjs.dev/support
-- https://wrnexusjs.dev/license
-- https://wrnexusjs.dev/search
-- https://wrnexusjs.dev/404
-- https://wrnexusjs.dev/packages/ai
-- https://wrnexusjs.dev/packages/auth
-- https://wrnexusjs.dev/packages/authz
-- https://wrnexusjs.dev/packages/captcha
-- https://wrnexusjs.dev/packages/cli
-- https://wrnexusjs.dev/packages/compiler
-- https://wrnexusjs.dev/packages/core
-- https://wrnexusjs.dev/packages/csr
-- https://wrnexusjs.dev/packages/db
-- https://wrnexusjs.dev/packages/dev-server
-- https://wrnexusjs.dev/packages/dev-toolbar
-- https://wrnexusjs.dev/packages/encryption
-- https://wrnexusjs.dev/packages/helpers
-- https://wrnexusjs.dev/packages/i18n
-- https://wrnexusjs.dev/packages/jwt
-- https://wrnexusjs.dev/packages/mobile
-- https://wrnexusjs.dev/packages/native
-- https://wrnexusjs.dev/packages/oauth
-- https://wrnexusjs.dev/packages/plugin
-- https://wrnexusjs.dev/packages/pubsub
-- https://wrnexusjs.dev/packages/queue
-- https://wrnexusjs.dev/packages/reactive
-- https://wrnexusjs.dev/packages/router
-- https://wrnexusjs.dev/packages/ssr
-- https://wrnexusjs.dev/packages/styles
-- https://wrnexusjs.dev/packages/syntax
-- https://wrnexusjs.dev/packages/test
-- https://wrnexusjs.dev/packages/tracking
-- https://wrnexusjs.dev/packages/ui
-- https://wrnexusjs.dev/packages/uploader
-- https://wrnexusjs.dev/packages/validation
-- https://wrnexusjs.dev/components/accordion
-- https://wrnexusjs.dev/components/advanced-date-picker
-- https://wrnexusjs.dev/components/advanced-range-slider
-- https://wrnexusjs.dev/components/advanced-select
-- https://wrnexusjs.dev/components/alert
-- https://wrnexusjs.dev/components/avatar
-- https://wrnexusjs.dev/components/avatar-group
-- https://wrnexusjs.dev/components/badge
-- https://wrnexusjs.dev/components/blockquote
-- https://wrnexusjs.dev/components/breadcrumb
-- https://wrnexusjs.dev/components/button
-- https://wrnexusjs.dev/components/button-group
-- https://wrnexusjs.dev/components/card
-- https://wrnexusjs.dev/components/carousel
-- https://wrnexusjs.dev/components/chart
-- https://wrnexusjs.dev/components/chat-bubble
-- https://wrnexusjs.dev/components/checkbox
-- https://wrnexusjs.dev/components/clipboard
-- https://wrnexusjs.dev/components/collapse
-- https://wrnexusjs.dev/components/color-picker
-- https://wrnexusjs.dev/components/columns
-- https://wrnexusjs.dev/components/combo-box
-- https://wrnexusjs.dev/components/confetti
-- https://wrnexusjs.dev/components/container
-- https://wrnexusjs.dev/components/context-menu
-- https://wrnexusjs.dev/components/copy-markup
-- https://wrnexusjs.dev/components/custom-scrollbar
-- https://wrnexusjs.dev/components/data-map
-- https://wrnexusjs.dev/components/data-table
-- https://wrnexusjs.dev/components/date-picker
-- https://wrnexusjs.dev/components/device-frame
-- https://wrnexusjs.dev/components/divider
-- https://wrnexusjs.dev/components/drag-and-drop
-- https://wrnexusjs.dev/components/drawer
-- https://wrnexusjs.dev/components/dropdown
-- https://wrnexusjs.dev/components/file-input
-- https://wrnexusjs.dev/components/file-upload
-- https://wrnexusjs.dev/components/file-upload-progress
-- https://wrnexusjs.dev/components/grid
-- https://wrnexusjs.dev/components/image
-- https://wrnexusjs.dev/components/input
-- https://wrnexusjs.dev/components/input-group
-- https://wrnexusjs.dev/components/input-number
-- https://wrnexusjs.dev/components/kbd
-- https://wrnexusjs.dev/components/layout-splitter
-- https://wrnexusjs.dev/components/legend-indicator
-- https://wrnexusjs.dev/components/link
-- https://wrnexusjs.dev/components/list
-- https://wrnexusjs.dev/components/list-group
-- https://wrnexusjs.dev/components/map
-- https://wrnexusjs.dev/components/marquee
-- https://wrnexusjs.dev/components/mega-menu
-- https://wrnexusjs.dev/components/modal
-- https://wrnexusjs.dev/components/nav
-- https://wrnexusjs.dev/components/navbar
-- https://wrnexusjs.dev/components/pagination
-- https://wrnexusjs.dev/components/pin-input
-- https://wrnexusjs.dev/components/popover
-- https://wrnexusjs.dev/components/progress
-- https://wrnexusjs.dev/components/radio
-- https://wrnexusjs.dev/components/range-slider
-- https://wrnexusjs.dev/components/rating
-- https://wrnexusjs.dev/components/scrollspy
-- https://wrnexusjs.dev/components/search-box
-- https://wrnexusjs.dev/components/select
-- https://wrnexusjs.dev/components/sidebar
-- https://wrnexusjs.dev/components/skeleton
-- https://wrnexusjs.dev/components/spinner
-- https://wrnexusjs.dev/components/stepper
-- https://wrnexusjs.dev/components/strong-password
-- https://wrnexusjs.dev/components/styled-icon
-- https://wrnexusjs.dev/components/switch
-- https://wrnexusjs.dev/components/table
-- https://wrnexusjs.dev/components/tabs
-- https://wrnexusjs.dev/components/textarea
-- https://wrnexusjs.dev/components/timeline
-- https://wrnexusjs.dev/components/time-picker
-- https://wrnexusjs.dev/components/toast
-- https://wrnexusjs.dev/components/toast-notifications
-- https://wrnexusjs.dev/components/toggle-count
-- https://wrnexusjs.dev/components/toggle-password
-- https://wrnexusjs.dev/components/tooltip
-- https://wrnexusjs.dev/components/tree-view
-- https://wrnexusjs.dev/components/typography
-- https://wrnexusjs.dev/components/wysiwyg-editor
+# WrNexus
 
-For exact APIs, use installed package README files followed by exported declarations.
+> WrNexus is an SSR-first, **Bun-native** full-stack web framework. UI is written in
+> `.wrn` files (its own component language — NOT React/JSX/Vue). Routing is file-based.
+> This document teaches an AI how to write correct WrNexus code. It is private and
+> post-dates model training data, so rely on THIS document, not prior web-framework
+> assumptions.
 
-# Complete @wrnexus/ui component reference
+## Golden rules
 
-### Accordion
-Mount: data-component="Accordion"
+- **Pages, components, and layouts are `.wrn` files.** Do NOT write `.tsx`/`.jsx`/React
+  for UI. Do NOT use `useState`, hooks, JSX, or a client bundler.
+- **Routing is file-based** under `app/`. The filename is the route. No router config.
+- **Interactivity** lives in `state` + `{expr}` + `@event` inside `.wrn`. Components render
+  on the server and hydrate automatically — you never write client-side JS islands.
+- **Runtime is Bun only** (uses `Bun.serve`, `bun:sqlite`, `Bun.password`, …). Node is not supported.
+- To add files, prefer the CLI: `wrnexus generate page ` / `component ` / `api ` / `schema `.
+
+## Project layout
+
+```
+app/
+  pages/       *.wrn  → routes: index.wrn = "/", about.wrn = "/about", blog/[slug].wrn = "/blog/:slug"
+  components/  *.wrn  → reusable UI, mounted in a page/component via 
+ layouts/ *.wrn → named layouts; a page opts in with layout = "name" + api/ *.ts → HTTP handlers: export const GET/POST/PUT/PATCH/DELETE = async (ctx) => Response + middleware/ *.ts → export default async (ctx, next) => next() + realtime/ *.ts → export default defineRoom({ ... }) from "@wrnexus/core" (ws://host/realtime/) + schemas/ *.ts → validation schemas (the `v` builder), used by forms + parseBody + locales/ *.json → i18n messages per language + db/ schema.ts, queries/*.sql, migrations/*.sql + styles/ global.css → Tailwind (default) or plain CSS +wrnexus.config.ts → app config (AppConfig from "@wrnexus/styles") +public/ → static assets served at / +``` + +## `.wrn` page + +```wrn +page Home { + layout = "public" // optional: a component in app/layouts/.wrn ("none" to skip) + + state count = 0 // optional: seeds client-reactive state (omit for pure SSR) + + seo { + title = "Home" + description = "..." + canonical = "/" + } + + view { +

Hello

+

Count is {count}, doubled is {count * 2}.

+ +
+ } + + style { + h1 { color: var(--wire-color-text); } + } +} +``` + +## `.wrn` component + +```wrn +component Counter { + props { // props come from mount attributes; each is coerced to the + start = 0 // TYPE of its default (so start="5" arrives as the number 5) + label = "Count" + } + state count = start // state may reference props + view { + + } +} +``` + +Mount it from any page/component: `
`. +Components render on the server with their props, then hydrate — no per-component JS. + +## The `view { }` block (plain HTML + a few directives) + +- `{expr}` — interpolate a JS expression. Reactive if it references `state`: `{count}`, `{count * 2}`, `{user.name}`. +- `@event="expr"` — bind a DOM event; the expression runs in the reactive scope: `@click="count++"`, `@input="name = event.target.value"`. +- `
` — mount a component (attrs become string props, coerced). +- `` / `` — component/layout slots; fill with `
`. +- **Server loop (DB/list/table):** `{#each as [, ]} …rows… {:empty} …fallback… {/each}` — iterates SSR data on the server and renders markup per item. `{item.field}` interpolates (HTML-escaped, XSS-safe). `` is a JS expression, usually an `ssr` data binding (see "Data-driven tables" below). This is how you render a database table in `.wrn`. +- **Server conditional:** `{#if } … {:else if } … {:else} … {/if}` — renders the first truthy branch on the server. `` can reference `ssr` data, or the `item`/`index` of an enclosing `{#each}`. Works at page level and inside loops (e.g. `{#if r.active}{:else}{/if}` per row). For client-side show/hide based on reactive `state`, use `data-show="expr"` instead. +- i18n: `{t:home.title}` in text, `t:placeholder="form.name"` on attributes — resolved per request from `app/locales/`. +- Theme: any element with `data-wire-theme-toggle` toggles light/dark; `data-wire-theme-set="dark"` sets it. +- Void/self-closing tags are fine: `
`, ``. +- Only `{` and `}` are special (interpolation). Don't use a bare `}` in view text. + +## Data-driven tables / lists (server-rendered `.wrn`) + +Use an `ssr` data binding to fetch rows on the server, then `{#each}` to render them. +This renders on the **server** (SSR-first) and is HTML-escaped by default. + +```wrn +page Admin { + layout = "dashboard" + + // Fetch on the server. The api handler at /api/contacts returns { contacts: [...] }; + // this block's `return contacts` exposes that array (via `$data`) as the binding `rows`. + ssr { + api rows GET /api/contacts { return contacts } + } + + view { + + + {#each rows as r, i} + + + + + + {:empty} + + {/each} + +
#{i}{r.name}{r.email}
No submissions yet.
+ } +} +``` + +The matching API returns the array under a key the `ssr` block reads: + +```ts +// app/api/contacts.ts → GET /api/contacts +import { getDb } from "@wrnexus/db"; +export const GET = async () => { + const contacts = await getDb().all("SELECT id, name, email FROM contacts ORDER BY id DESC"); + return Response.json({ contacts }); // ssr block does `return contacts` +}; +``` + +**Prefer this `.wrn` + `{#each}` approach for DB-backed tables and lists.** (`.ts`/`.tsx` +pages returning an HTML string are also supported for fully-custom programmatic rendering, +but a `.wrn` page with `ssr` data + `{#each}` is the idiomatic, SSR-first way.) + +## API routes (`app/api/*.ts`) + +```ts +// app/api/users/list.ts → GET /api/users/list +import { getDb } from "@wrnexus/db"; + +export const GET = async (ctx) => { + return Response.json({ users: await ListUsers(getDb()) }); +}; + +export const POST = async (ctx) => { + const body = await ctx.req.json(); + return Response.json({ ok: true, body }, { status: 201 }); +}; +``` + +`ctx` (the `Context` from `@wrnexus/core`) has: +`req: Request`, `url: URL`, `params: Record` (dynamic route params, e.g. `/users/[id]` → `ctx.params.id`), +`lang: string`, `t(key, params?)` (i18n), `cookies` (get/set), `session` (get/set). Auth: `getUser(ctx)` after `sessionAuth`/`logIn`. + +When an SSO forward-auth verifier needs the URL that originally reached the gateway, use +`@wrnexus/helpers` instead of constructing it from untrusted headers: + +```ts +import { redirectToLogin } from "@wrnexus/helpers"; + +return redirectToLogin(ctx, "/login", { + allowedHosts: ["admin.localhost:3000", "reports.localhost:3000"], +}); +``` + +The package also exports `getOriginalRequestUrl`, `getOriginalRequestOrigin`, +`getOriginalRequestPath`, and `getOriginalRequestMethod`. Always pass `allowedHosts` when +using forwarded gateway URLs; the helper rejects untrusted redirect destinations. + +## Middleware & realtime + +```ts +// app/middleware/logger.ts +export default async function logger(ctx, next) { + console.log(ctx.req.method, ctx.url.pathname); + return next(); // return a Response WITHOUT calling next() to short-circuit +} +``` + +```ts +// app/realtime/chat.ts → ws://host/realtime/chat +import { defineRoom } from "@wrnexus/core"; +export default defineRoom({ + onConnect(client) { client.send({ type: "system", text: "connected" }); }, + onMessage(client, msg) { client.room.broadcast({ type: "message", data: msg }); }, +}); +``` +Client side: a page opts in with `data-room="chat"` (handled by the realtime runtime). + +## Config (`wrnexus.config.ts`) + +```ts +import type { AppConfig } from "@wrnexus/styles"; +const config: AppConfig = { + seo: { title: "App", titleTemplate: "%s | App", description: "..." }, + styles: { entry: "app/styles/global.css", process: async ({ entryPath, mode }) => /* Tailwind */ "" }, + fonts: { sans: '"Inter", system-ui, sans-serif', google: [{ family: "Inter", weights: [400, 600] }] }, + theme: { default: "dark", themes: { light: { "color-primary": "#2563eb" } } }, + i18n: { default: "en", locales: ["en", "es"] }, + db: { driver: "sqlite", url: "file:./dev.db" }, + security: { cors: { enabled: true, origin: ["http://localhost:5173"] } }, + // profiles: { production: { db: { driver: "postgres", url: process.env.DATABASE_URL } } }, +}; +export default config; +``` + +## Database (`@wrnexus/db`) + +```ts +// app/db/schema.ts +import { v, table } from "@wrnexus/db"; +export const users = table("users", { + id: v.id(), + name: v.string(), + email: v.string().unique(), + createdAt: v.timestamp(), +}); +``` +- Queries: write `app/db/queries/*.sql` with `-- name: ListUsers :many` blocks; `wrnexus db generate` emits typed functions. +- Access at runtime: `import { getDb } from "@wrnexus/db"; const rows = await ListUsers(getDb());` +- Migrations in `app/db/migrations/`; run `wrnexus db migrate` (dev auto-migrates sqlite). + +## Validation (`@wrnexus/validation`) + +```ts +// app/schemas/login.ts +import { v } from "@wrnexus/validation"; +export default v.object({ + email: v.string().email(), + password: v.string().min(8), +}); +``` +In an API route: `import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);` → `r.ok ? r.value : r.response`. +In a form: `
` + `` (client + server validation wired automatically). + +## AI / LLM (`@wrnexus/ai`) + +```ts +// app/api/ai.ts +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); // reads ANTHROPIC_API_KEY; default model claude-opus-4-8 +export const POST = async (ctx) => { + const { prompt } = await ctx.req.json(); + return ai.streamResponse(prompt); // or: return Response.json({ text: await ai.generate(prompt) }) +}; +``` + +## CLI + +``` +wrnexus dev . # dev server + HMR +wrnexus build . # production build → dist/server.js +bun dist/server.js # run the production server (or npm start) +wrnexus create # scaffold a new app +wrnexus update --latest # deps + syntax/config migrations + verification +wrnexus generate page # scaffold a page (aliases: g p) +wrnexus generate component | api | schema +wrnexus db migrate | rollback | status | new [--from-models] | generate | seed +wrnexus eject # copy a Wire UI component's .wrn into app/components to customize +``` + +## When asked to "create a page/component/feature" + +1. Create the `.wrn` file under `app/pages/` (or `app/components/`) with a `page`/`component` block — or run `wrnexus generate page `. +2. Put markup in `view { }`, interactive bits in `state` + `{expr}` + `@event`, reusable UI as components mounted via `data-component`. +3. For data, add an `app/api/*.ts` route and `getDb()`; for forms, add an `app/schemas/*.ts` and `data-schema`. +4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wire-*)`), or `style { }`. +5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration. + +# Canonical documentation locations + +- Framework and package documentation: https://wrnexusjs.dev/ +- Interactive UI component showcase and examples: https://component.wrnexusjs.dev/ + +# Installed package documentation + +The following README files and declarations come from the installed private 0.5.0 release. + +## @wrnexus/ai + +Documentation URL: https://wrnexusjs.dev/packages/ai + +# @wrnexus/ai + +> A tiny, zero-dependency Claude (Anthropic) client for WrNexus apps — generate and stream text with Claude from any server-side code. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/ai` is a thin, dependency-free wrapper over the Anthropic **Messages API**, +built on `fetch` (Bun-native, no SDK). Use it in API routes, jobs, or middleware to +call Claude. It defaults to the most capable model, **`claude-opus-4-8`**, reads your +key from `ANTHROPIC_API_KEY`, and supports both one-shot generation and streaming. + +## Installation + +```bash +bun add @wrnexus/ai +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +Set your key in the environment (e.g. `.env`): + +``` +ANTHROPIC_API_KEY=sk-ant-... +``` + +## API + +### `createAI(config?)` + +Creates a client. The key is read at call time, so it's safe to create at import. + +```ts +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); // or createAI({ apiKey, model, maxTokens, baseURL, version }) +``` + +`AIConfig` fields (all optional): + +| Field | Default | Description | +| ----------- | --------------------------- | -------------------------- | +| `apiKey` | `ANTHROPIC_API_KEY` | Anthropic API key | +| `model` | `"claude-opus-4-8"` | Model id | +| `maxTokens` | `4096` | Default max output tokens | +| `baseURL` | `https://api.anthropic.com` | API base URL | +| `version` | `"2023-06-01"` | `anthropic-version` header | + +### `ai.generate(prompt, opts?): Promise` + +One-shot text generation. `prompt` is a string or a `Message[]` history. + +```ts +const text = await ai.generate("Write a haiku about Bun."); + +const reply = await ai.generate( + [ + { role: "user", content: "My name is Ada." }, + { role: "assistant", content: "Hi Ada!" }, + { role: "user", content: "What's my name?" }, + ], + { system: "You are concise." }, +); +``` + +### `ai.stream(prompt, opts?): AsyncGenerator` + +Yields text deltas as they arrive. + +```ts +for await (const chunk of ai.stream("Tell me a story.")) { + process.stdout.write(chunk); +} +``` + +### `ai.streamResponse(prompt, opts?): Response` + +Returns a streaming `text/plain` `Response` — drop it straight into an API route. + +```ts +// app/api/chat.ts +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); + +export const POST = async (ctx) => { + const { prompt } = await ctx.req.json(); + return ai.streamResponse(prompt); +}; +``` + +### `GenerateOptions` + +| Option | Type | Description | +| ----------- | ------------------------------------------------- | ---------------------------------------------------- | +| `system` | `string` | System prompt | +| `model` | `string` | Override the model for this call | +| `maxTokens` | `number` | Override max output tokens | +| `thinking` | `boolean` | Enable adaptive extended thinking (deeper reasoning) | +| `effort` | `"low" \| "medium" \| "high" \| "xhigh" \| "max"` | Reasoning effort / token spend | +| `messages` | `Message[]` | Full history — supersedes `prompt` | +| `signal` | `AbortSignal` | Cancel the request | + +> `temperature` / `top_p` are intentionally **not** exposed — the current Claude +> models reject them (400). Steer output with prompting instead. + +### `AIError` + +Thrown on non-2xx responses or a model refusal. Carries `.status` and `.type` +(e.g. `"authentication_error"`, `"rate_limit_error"`, `"refusal"`). + +```ts +import { AIError } from "@wrnexus/ai"; +try { + await ai.generate("..."); +} catch (e) { + if (e instanceof AIError && e.type === "rate_limit_error") { + /* back off */ + } +} +``` + +## Usage + +### Return generated JSON from an API route + +```ts +// app/api/summarize.ts — summarize posted text +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); + +export const POST = async (ctx) => { + const { text } = await ctx.req.json().catch(() => ({})); + if (!text) return Response.json({ error: "Provide 'text'." }, { status: 400 }); + const summary = await ai.generate(`Summarize in one sentence:\n\n${text}`, { + system: "You are a precise summarizer.", + }); + return Response.json({ summary }); +}; +``` + +### Stream a chat response to the browser + +```ts +// app/api/chat.ts +import { createAI } from "@wrnexus/ai"; + +const ai = createAI({ model: "claude-sonnet-5" }); + +export const POST = async (ctx) => { + const { messages } = await ctx.req.json(); + return ai.streamResponse(messages, { + system: "Answer using concise Markdown.", + maxTokens: 1_500, + }); +}; +``` + +## Requirements / Notes + +- **Bun-only.** Uses `fetch`, `ReadableStream`, `TextDecoder`/`TextEncoder`, and + reads `ANTHROPIC_API_KEY` from `Bun.env` (falls back to `process.env`). +- **Zero dependencies** — no `@anthropic-ai/sdk`; talks to the Messages API directly. +- Defaults to `claude-opus-4-8`. Pass `{ model }` for a different model (e.g. + `"claude-sonnet-5"` for speed/cost, `"claude-haiku-4-5"` for the fastest). + +### Exported TypeScript declarations + +```ts +interface AIUsage { + inputTokens?: number; + outputTokens?: number; + totalTokens?: number; +} +interface AIResult { + value: T; + provider: string; + model?: string; + usage?: AIUsage; + finishReason?: string; + raw?: unknown; +} +interface AIProvider { + name: string; + generate(prompt: string | Message[], options?: GenerateOptions): Promise>; + stream?(prompt: string | Message[], options?: GenerateOptions): AsyncGenerator; +} +interface AIClientOptions { + providers: AIProvider[]; + fallback?: boolean; + onAttempt?: (provider: string, error?: unknown) => void | Promise; +} +interface AIClient { + generate(prompt: string | Message[], options?: GenerateOptions & { + provider?: string; + }): Promise>; + generateObject(prompt: string | Message[], options?: GenerateOptions & { + provider?: string; + validate?: (value: unknown) => value is T; + }): Promise>; + stream(prompt: string | Message[], options?: GenerateOptions & { + provider?: string; + }): AsyncGenerator; +} +declare function anthropicProvider(config?: AIConfig): AIProvider; +declare function aiProvider(name: string, client: AI): AIProvider; +declare function createAIClient(options: AIClientOptions): AIClient; + +/** + * @wrnexus/ai — a tiny, zero-dependency Claude (Anthropic) client for WrNexus apps. + * + * Use it in API routes, jobs, or anywhere server-side to generate text with Claude. + * It talks to the Anthropic Messages API over `fetch` (no SDK dependency, Bun-native), + * and defaults to the most capable model, `claude-opus-4-8`. + * + * import { createAI } from "@wrnexus/ai"; + * const ai = createAI(); // reads ANTHROPIC_API_KEY + * const text = await ai.generate("Write a haiku about Bun."); + * + * Streaming (great for API routes): + * export const POST = async (ctx) => ai.streamResponse(await ctx.req.text()); + */ +type Role = "user" | "assistant"; +interface Message { + role: Role; + content: string; +} +/** Reasoning effort — higher means deeper thinking + more tokens. */ +type Effort = "low" | "medium" | "high" | "xhigh" | "max"; +interface AIConfig { + /** Anthropic API key. Default: `ANTHROPIC_API_KEY` from the environment. */ + apiKey?: string; + /** Model id. Default: `claude-opus-4-8` (the most capable Claude model). */ + model?: string; + /** Default max output tokens. Default: 4096. */ + maxTokens?: number; + /** API base URL. Default: `https://api.anthropic.com`. */ + baseURL?: string; + /** `anthropic-version` header. Default: `2023-06-01`. */ + version?: string; +} +interface GenerateOptions { + /** System prompt — sets the assistant's role/behavior. */ + system?: string; + /** Override the model for this call. */ + model?: string; + /** Override max output tokens for this call. */ + maxTokens?: number; + /** Enable adaptive extended thinking (slower, deeper reasoning). */ + thinking?: boolean; + /** Reasoning effort / token spend (`output_config.effort`). */ + effort?: Effort; + /** Full message history — supersedes the `prompt` argument when provided. */ + messages?: Message[]; + /** Abort the request. */ + signal?: AbortSignal; +} +/** Thrown when the API returns a non-2xx response or refuses the request. */ +declare class AIError extends Error { + readonly status: number; + readonly type: string; + constructor(message: string, status?: number, type?: string); +} +interface AI { + /** Generate a full text response (non-streaming). */ + generate(prompt: string | Message[], opts?: GenerateOptions): Promise; + /** Stream the response as text deltas, as they arrive. */ + stream(prompt: string | Message[], opts?: GenerateOptions): AsyncGenerator; + /** Stream straight to a `Response` (text/plain) — drop-in for an API route return. */ + streamResponse(prompt: string | Message[], opts?: GenerateOptions): Response; +} +/** Create a Claude client. Reads `ANTHROPIC_API_KEY` from the environment by default. */ +declare function createAI(config?: AIConfig): AI; + +export { type AI, type AIClient, type AIClientOptions, type AIConfig, AIError, type AIProvider, type AIResult, type AIUsage, type Effort, type GenerateOptions, type Message, type Role, aiProvider, anthropicProvider, createAI, createAIClient }; +``` + +--- + +## @wrnexus/auth + +Documentation URL: https://wrnexusjs.dev/packages/auth + +# @wrnexus/auth + +Framework-native authentication, identity, account-security, and session management for WRNexusJS. + +## Capabilities + +- Password registration, login, recovery, reset, and authenticated password changes +- Email, phone, and username identities with verification and generic resend responses +- Magic links and passwordless email/SMS OTP login +- MFA transactions using verified email OTP, verified SMS OTP, TOTP, or recovery codes +- RFC 6238 TOTP with counter replay protection +- One-use recovery codes; regeneration invalidates previous unused codes +- Passkey/WebAuthn registration and strong passwordless sign-in through a provider contract +- OAuth account linking and provider sign-in +- Invitations, session rotation, idle and absolute expiry, revocation, and trusted devices +- Deny-by-default audited support impersonation +- Adaptive risk scoring, CAPTCHA escalation, temporary lockout, and optional login alerts +- Memory and SQL stores +- Optional encryption-keyring protection for TOTP and OAuth secrets +- Automatic API routes, middleware, browser schemas, components, runtime, migrations, and DevToolbar checks + +Passkeys are a strong sign-in method. They are not currently exposed as a selectable second step in `TwoFactorChallenge`; the implemented MFA methods are email OTP, SMS OTP, TOTP, and recovery codes. + +## Install + +```bash +bun add @wrnexus/auth +``` + +WRNexusJS discovers the package automatically. Do not copy package components, client scripts, schemas, or standard `/api/auth/*` route files into the application. + +## Default configuration + +Create the engine: + +```ts +// app/lib/auth.ts +import { createAuthEngine, MemoryAuthStore } from "@wrnexus/auth"; + +export const auth = createAuthEngine({ + store: new MemoryAuthStore(), + secret: process.env.AUTH_SECRET!, + issuer: "My application", + onSignedIn(ctx, returnTo) { + const safe = returnTo?.startsWith("/") && !returnTo.startsWith("//") ? returnTo : "/account"; + return Response.redirect(new URL(safe, ctx.url), 303); + }, + onSignedOut(ctx) { + return Response.redirect(new URL("/sign-in", ctx.url), 303); + }, + onSuccessfulSignUp() { + return { + autoSignIn: true, + redirectTo: "/account", + }; + }, + delivery: { + async send(message) { + // Queue email/SMS through your provider. Never log message.code or message.token. + }, + }, +}); +``` + +Authentication behavior belongs in this engine definition: delivery, token URL +mapping, successful sign-in/sign-out responses, password policy, risk thresholds, +MFA, passkeys, and auditing can all be configured in one server-only location. +The older `config.auth.onSignedIn` and `config.auth.onSignedOut` fields remain +supported as compatibility overrides, but new applications should configure +these hooks on `createAuthEngine`. + +### Successful signup behavior + +Without `onSuccessfulSignUp`, a successful package registration redirects to +`/sign-in`. + +To sign in immediately after registration: + +```ts +onSuccessfulSignUp(ctx, user) { + return { + autoSignIn: true, + redirectTo: "/account", + }; +} +``` + +Automatic sign-in runs the normal login policy. It does not bypass required +email or phone verification, CAPTCHA, MFA, account status, or risk checks. The +hook may also return a `Response` for a completely custom HTTP result, or return +`{ redirectTo: "/welcome" }` to redirect without creating a session. + +Register it through application configuration: + +```ts +// wrnexus.config.ts +import type { AuthConfig } from "@wrnexus/auth"; +import type { AppConfig } from "@wrnexus/styles"; +import { auth } from "./app/lib/auth.ts"; + +const config = { + auth: { + engine: auth, + routes: true, + middleware: true, + migrations: false, + }, +} satisfies AppConfig & { auth: AuthConfig }; + +export default config; +``` + +That configuration automatically activates package routes, auth-session middleware, components, browser validation schemas, and the auth client runtime. `setDefaultAuthEngine()` remains available only for advanced manual integrations and tests. + +## SQL production configuration + +```ts +import { createAuthEngine, SqlAuthStore } from "@wrnexus/auth"; +import { getDb } from "@wrnexus/db"; + +export const auth = createAuthEngine({ + store: new SqlAuthStore(getDb()), + secret: process.env.AUTH_SECRET!, +}); +``` + +```ts +export default { + db: { + // Application database configuration. + }, + auth: { + engine: auth, + routes: true, + middleware: true, + migrations: true, + }, +}; +``` + +The package contributes both ordered migrations: + +```text +001_auth.sql +002_auth_otp_purpose.sql +``` + +Migrations are enabled automatically only when `auth.engine` and a default `config.db` are present. Set `auth.migrations` explicitly when an application needs different behavior. + +## Delivered action URLs + +By default, the engine builds links from the supplied `baseUrl` and token purpose. Applications can map those links to their own page structure without replacing package APIs: + +```ts +const auth = createAuthEngine({ + store: new SqlAuthStore(getDb()), + secret: process.env.AUTH_SECRET!, + tokenUrl({ purpose, token, baseUrl }) { + if (!baseUrl) return undefined; + + const paths = { + "verify-email": `/verify-email?token=${encodeURIComponent(token)}`, + "verify-phone": `/verify-phone?token=${encodeURIComponent(token)}`, + "password-reset": `/recover/reset?token=${encodeURIComponent(token)}`, + "magic-link": `/magic-link?token=${encodeURIComponent(token)}`, + invite: `/invitation?token=${encodeURIComponent(token)}`, + }; + + const path = paths[purpose as keyof typeof paths]; + return path ? new URL(path, baseUrl).toString() : undefined; + }, +}); +``` + +Returning `undefined` intentionally omits the URL while still delivering the raw token. The callback runs only in trusted server code. + +## Built-in validation + +Every packaged auth form has a built-in `@wrnexus/validation` schema. The same resolved schema is used by the browser and the package API handler. + +Default use requires no `app/schemas` files: + +```wrn + + + + + +``` + +To customize one schema, extend the package default and register only that override: + +```ts +// app/schemas/custom-password-request.ts +import { authSchemas } from "@wrnexus/auth"; +import { v } from "@wrnexus/validation"; + +export default authSchemas.passwordResetRequest.extend({ + identifier: v + .string() + .trim() + .required("Enter your registered email address") + .email("Enter a valid registered email address"), +}); +``` + +```ts +import customPasswordRequest from "./app/schemas/custom-password-request.ts"; + +export default { + auth: { + engine: auth, + schemas: { + passwordResetRequest: customPasswordRequest, + }, + }, +}; +``` + +`` can keep its default `schema="auth-password-request"`. The plugin automatically publishes the overridden browser descriptor under that same built-in schema ID. All other forms continue using package defaults. + +## Route controls + +Use a boolean to enable or disable all package routes: + +```ts +auth: { + engine: auth, + routes: true, +} +``` + +Or control feature groups: + +```ts +routes: { + enabled: true, + registration: true, + login: true, + verification: true, + password: true, + invitations: true, + magicLink: true, + otp: true, + mfa: true, + sessions: true, + impersonation: false, + passkeys: true, +} +``` + +Application routes have normal framework precedence. Disable a package group only when the application intentionally owns every endpoint in that group; no `excludeRoutes` list is required. + +## Package endpoints + +```text +POST /api/auth/register +POST /api/auth/login +POST /api/auth/logout +POST /api/auth/verification/request +GET|POST /api/auth/verify/email +POST /api/auth/verify/phone +POST /api/auth/password/request +POST /api/auth/password/reset +POST /api/auth/password/change +POST /api/auth/invitations/accept +POST /api/auth/magic-link/request +GET|POST /api/auth/magic-link +POST /api/auth/otp/login/request +POST /api/auth/otp/login/complete +POST /api/auth/otp +POST /api/auth/otp/verify +POST /api/auth/totp/setup +POST /api/auth/totp/confirm +POST /api/auth/totp/disable +POST /api/auth/recovery-codes +POST /api/auth/mfa/otp +POST /api/auth/mfa/complete +GET /api/auth/sessions +POST /api/auth/sessions/revoke +POST /api/auth/impersonation/start +POST /api/auth/impersonation/stop +POST /api/auth/passkeys/register/options +POST /api/auth/passkeys/register/verify +POST /api/auth/passkeys/login/options +POST /api/auth/passkeys/login/verify +``` + +Each URL uses a route-specific module, so rewritten framework request URLs cannot make the handler fall through to a shared-dispatcher `404`. + +Unsafe package routes validate the framework CSRF token by default. Set `auth.csrf: false` only when an external API gateway provides an equivalent protection model. + +## Components + +```wrn + + + + + + + + + + + + + + + + +``` + +`identifier` is optional on verification components. Supply it when an unauthenticated verification page should support resending a token. The response remains generic whether the account exists or not. + +## CAPTCHA and risk + +The HTTP handlers never trust a browser `captchaVerified` field. CAPTCHA completion is accepted only from server-populated `ctx.locals.captcha.success` or `ctx.locals.captchaVerified === true`. + +Rate limiting remains an application or gateway responsibility. Apply it to registration, login, reset, magic-link, OTP, verification, passkey, invitation, and impersonation endpoints. + +## MFA + +1. Password, OAuth, magic-link, or OTP login may return `code: "mfa-required"` with a short-lived `mfaToken`. +2. The response lists only methods actually available to that user. +3. Email/SMS MFA is offered only for verified linked identities. +4. `beginMfaOtp()` issues an MFA-bound OTP when needed. +5. `completeMfa()` consumes the one-time transaction and creates the session. + +## Passkeys + +The browser runtime coordinates `navigator.credentials.create()` and `navigator.credentials.get()`. A configured server-side `PasskeyProvider` must verify the challenge, RP ID, origin, signature, user presence or verification, counter, and credential ownership. + +Multi-process deployments must provide a shared `PasskeyChallengeStore`; the default memory implementation is process-local. Missing passkey providers return a controlled `503` response rather than crashing the route. + +## Protect long-lived secrets + +```ts +import { createAuthSecretProtector } from "@wrnexus/auth"; +import { createKeyring } from "@wrnexus/encryption"; + +const keyring = createKeyring([ + { + id: "auth-2026-01", + secret: process.env.AUTH_ENCRYPTION_KEY!, + active: true, + }, +]); + +const auth = createAuthEngine({ + store: new SqlAuthStore(getDb()), + secret: process.env.AUTH_SECRET!, + secretProtector: createAuthSecretProtector(keyring), +}); +``` + +TOTP seeds and OAuth access/refresh tokens are protected before persistence. Keep old keys available during rotation. + +## Custom HTTP integration + +`createAuthHttpHandlers()` remains available for custom route paths or response behavior. Prefer package routes for standard flows; copied application API files are unnecessary. + +## Development + +```bash +bun run auth:dev +bun run validate:auth +``` + +Read [SECURITY.md](./SECURITY.md) before production deployment. + +### Exported TypeScript declarations + +```ts +export { A as AuthEngine, c as createAuthEngine, i as inferIdentityType, n as normalizeEmail, a as normalizeIdentity, b as normalizePhone, d as normalizeUsername, p as publicUser, s as safeAuthReturnTo } from './engine-jttXj6PP.js'; +import { n as AuthRiskSignals, l as AuthRiskDecision } from './types-JLkQpcAt.js'; +export { A as AuthAccountStatus, a as AuthClock, b as AuthDeliveryMessage, c as AuthDeliveryProvider, d as AuthEngineOptions, e as AuthIdentity, f as AuthIdentityType, g as AuthImpersonationDecision, h as AuthMfaMethod, i as AuthPublicUser, j as AuthRandom, k as AuthResult, m as AuthRiskLevel, o as AuthSecretProtector, p as AuthSecurityEvent, q as AuthSession, r as AuthSignedInHandler, s as AuthSignedOutHandler, M as AuthStore, t as AuthSuccessfulSignUpAction, u as AuthSuccessfulSignUpHandler, v as AuthTokenPurpose, w as AuthTokenUrlInput, x as AuthUser, y as AuthenticatedContext, L as LoginAttempt, z as LoginInput, N as MemoryPasskeyChallengeStore, O as OAuthAccount, B as OneTimeToken, C as OtpChallenge, P as PasskeyAuthenticationOptions, Q as PasskeyChallengeKind, S as PasskeyChallengeRecord, U as PasskeyChallengeStore, D as PasskeyCredential, E as PasskeyProvider, F as PasskeyRegistrationOptions, G as PasskeyVerificationResult, H as PasswordBreachProvider, I as PasswordCredential, R as RecoveryCodeRecord, J as RegisterInput, T as TotpCredential, K as TrustedDevice, V as assertPasskeyProvider } from './types-JLkQpcAt.js'; +export { MemoryAuthStore } from './stores/memory.js'; +export { SqlAuthStore } from './stores/sql.js'; +export { AUTH_SESSION_KEY, authSession, clearAuthSession, establishAuthSession, getAuthSession, getAuthUser, isAuthenticatedContext, requireAuth } from './middleware.js'; +export { A as AuthHttpOptions, a as AuthPasskeyHttpOptions, b as AuthSchemaOverrides, c as AuthSchemaSet, d as authBrowserSchemaDescriptors, e as authBrowserSchemaMap, f as authSchemas, g as authenticatorConfirmSchema, h as authenticatorDisableSchema, i as authenticatorSetupSchema, j as changePasswordSchema, k as createAuthHttpHandlers, l as emptyActionSchema, m as impersonationStartSchema, n as invitationAcceptSchema, o as loginSchema, p as magicLinkConsumeSchema, q as magicLinkRequestSchema, r as mfaOtpRequestSchema, s as mfaSchema, t as otpIssueSchema, u as otpLoginCompleteSchema, v as otpLoginRequestSchema, w as otpSchema, x as passkeyAuthenticationOptionsSchema, y as passkeyAuthenticationVerifySchema, z as passkeyRegistrationOptionsSchema, B as passkeyRegistrationVerifySchema, C as passwordResetRequestSchema, D as passwordResetSchema, E as recoveryCodesSchema, F as registerSchema, G as resolveAuthSchemas, H as sessionRevokeSchema, I as signUpSchema, J as verificationRequestSchema, K as verificationTokenSchema } from './index-CrF_lZDQ.js'; +export { AuthAuditIssue, AuthConfig, AuthPluginOptions, AuthRoutesConfig, authComponentsDir, authPlugin } from './plugin.js'; +export { DefaultAuthRouteOptions, clearDefaultAuthEngine, getDefaultAuthEngine, getDefaultAuthRouteOptions, getDefaultAuthSchemas, hasDefaultAuthEngine, setDefaultAuthEngine, setDefaultAuthRouteOptions, setDefaultAuthSchemas, tryGetDefaultAuthEngine } from './runtime.js'; +export { createAuthSecretProtector } from './protector.js'; +export { decodeBase32, encodeBase32, generateTotp, generateTotpSecret, totpUri, verifyTotp } from './totp/index.js'; +import '@wrnexus/oauth'; +import '@wrnexus/core'; +import '@wrnexus/db'; +import '@wrnexus/validation'; +import '@wrnexus/plugin'; +import '@wrnexus/encryption'; + +interface RiskPolicy { + captchaThreshold: number; + mfaThreshold: number; + blockThreshold: number; +} +declare function evaluateAuthRisk(signals?: AuthRiskSignals, policy?: RiskPolicy): AuthRiskDecision; + +export { AuthRiskDecision, AuthRiskSignals, type RiskPolicy, evaluateAuthRisk }; +``` + +--- + +## @wrnexus/authz + +Documentation URL: https://wrnexusjs.dev/packages/authz + +# @wrnexus/authz + +> Composable authorization for WrNexus — role-based (RBAC), policy-based (PBAC), and attribute-based (ABAC) access control that reduces to a boolean check plus an `authorize()` guard. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/authz` is a small, server-side authorization toolkit. It gives you three +interchangeable models — RBAC (roles → permissions), PBAC (policy predicates), and +ABAC (attribute matchers) — that all collapse to a `boolean | Promise` decision. +Wrap any decision in a `Middleware` guard (`authorize`, `requireRole`, `requirePermission`) +to protect WrNexus routes. Reach for it whenever a route or action needs to be gated on who +the user is, what roles they hold, or attributes of the user and the resource. It plugs into +`@wrnexus/core` by reading `ctx.user` as the authorization subject. + +## Installation + +```bash +bun add @wrnexus/authz +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +The package has a single entry point (`@wrnexus/authz`) exporting the following. + +### Types + +| Symbol | Description | +| ---------------------------------- | --------------------------------------------------------------------------------------------- | +| `Subject` | The authorized principal: `{ id?: string; roles?: string[]; [attribute: string]: unknown }`. | +| `Rbac` | An RBAC checker: `{ can(subject, permission): boolean; permissionsFor(roles): Set }`. | +| `Policy` | A predicate `(subject: S, resource?: R) => boolean \| Promise`. | + +### RBAC + +#### `defineRbac(roles: Record): Rbac` + +Builds an RBAC checker from a role → permissions map. Supported permission forms: + +- `"*"` — grants every permission. +- `"ns:*"` — namespace wildcard (e.g. `"post:*"` grants `"post:write"`). +- `"role:"` — inherits all permissions of another role (resolved recursively, cycle-safe). + +The returned `Rbac` provides: + +- `can(subject, permission)` — `true` if any of `subject.roles` grants `permission` (honouring `*` and namespace wildcards). Returns `false` when the subject has no roles. +- `permissionsFor(roles)` — the resolved `Set` of all permissions granted to a set of roles. + +#### `hasRole(subject: Subject | undefined, ...required: string[]): boolean` + +`true` if the subject holds **all** of the given roles. + +### PBAC / ABAC combinators + +- `any(...policies: Policy[]): Policy` — allow if **any** policy passes (OR); awaits async policies. +- `all(...policies: Policy[]): Policy` — allow only if **all** policies pass (AND); awaits async policies. +- `attr(name: string, match: unknown | ((value: unknown) => boolean)): Policy` — ABAC helper that allows when `subject[name]` equals `match`, or when `match` is a function, when `match(value)` is truthy. + +### Guards (middleware) + +Each guard returns a `@wrnexus/core` `Middleware`. A denied request short-circuits with +`Response.json({ ok: false, error: "Forbidden" }, { status: 403 })`. + +- `authorize(policy: (ctx: Context) => boolean | Promise): Middleware` — runs `policy` against the request `Context`; calls `next()` when it resolves truthy, otherwise returns 403. +- `requireRole(...roles: string[]): Middleware` — allows when `ctx.user` holds **any** of the listed roles. +- `requirePermission(rbac: Rbac, permission: string): Middleware` — allows when `rbac.can(ctx.user, permission)` is `true`. + +## Usage + +### RBAC + +```ts +import { defineRbac, hasRole } from "@wrnexus/authz"; + +const rbac = defineRbac({ + admin: ["*"], + editor: ["post:read", "post:write"], + viewer: ["post:read"], + // role inheritance: lead gets everything an editor has, plus post:publish + lead: ["role:editor", "post:publish"], +}); + +const user = { id: "u1", roles: ["editor"] }; + +rbac.can(user, "post:write"); // true +rbac.can(user, "post:delete"); // false +rbac.permissionsFor(["lead"]); // Set { "post:read", "post:write", "post:publish" } +hasRole(user, "editor"); // true +``` + +### Guarding routes + +```ts +import { authorize, requireRole, requirePermission, defineRbac } from "@wrnexus/authz"; + +const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"] }); + +// Only admins or editors +app.get("/dashboard", requireRole("admin", "editor"), handler); + +// Requires a specific permission +app.post("/posts", requirePermission(rbac, "post:write"), handler); + +// Arbitrary policy over the request context +app.delete( + "/posts/:id", + authorize((ctx) => hasRole(ctx.user, "admin")), + handler, +); +``` + +### PBAC / ABAC policies + +```ts +import { any, all, attr, authorize, type Policy } from "@wrnexus/authz"; + +interface User { + id: string; + department?: string; + roles?: string[]; +} +interface Post { + authorId: string; +} + +// Ownership policy (subject + resource) +const ownsPost: Policy = (u, post) => u.id === post?.authorId; + +// ABAC: attribute equality, or a predicate +const inEngineering = attr("department", "engineering"); +const isVerified = attr("verified", (v) => v === true); + +// Compose: allow if the user owns the post OR is in engineering AND verified +const canEdit = any(ownsPost, all(inEngineering, isVerified)); + +app.put( + "/posts/:id", + authorize((ctx) => canEdit(ctx.user as User, loadPost(ctx))), + handler, +); +``` + +## Requirements / Notes + +- **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported. +- Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`. +- Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise` (e.g. for a database ownership check). + +### Exported TypeScript declarations + +```ts +import { Context, Middleware } from '@wrnexus/core'; + +interface AuthorizationDecision { + allowed: boolean; + reason?: string; + policy?: string; + metadata?: Record; +} +type DecisionPolicy = (subject: S, resource?: R) => AuthorizationDecision | Promise; +declare function allow(reason?: string, metadata?: Record): AuthorizationDecision; +declare function deny(reason?: string, metadata?: Record): AuthorizationDecision; +declare function decision(name: string, policy: Policy, denial?: string): DecisionPolicy; +declare function owner>(subjectKey?: keyof SubjectType, resourceKey?: keyof Resource | string): DecisionPolicy; +declare function anyDecision(...policies: DecisionPolicy[]): DecisionPolicy; +declare function allDecisions(...policies: DecisionPolicy[]): DecisionPolicy; +declare function authorizeDecision(evaluate: (ctx: Context) => AuthorizationDecision | Promise): Middleware; +declare function filterAuthorized(subject: S, values: readonly R[], policy: Policy): Promise; + +/** + * @wrnexus/authz — authorization: role-based (RBAC), policy-based (PBAC), and + * attribute-based (ABAC). Compose freely; all three reduce to a boolean check + * plus an `authorize()` guard middleware. + * + * const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"] }); + * rbac.can(user, "post:write"); + * + * // PBAC/ABAC: a policy is a predicate over subject + resource + attributes + * const ownsPost: Policy = (u, post) => u.id === post.authorId; + * authorize((ctx) => ownsPost(ctx.user, resource)) // middleware + */ + +interface Subject { + id?: string; + roles?: string[]; + [attribute: string]: unknown; +} +interface Rbac { + /** True if any of the subject's roles grants `permission` (supports "*" and "ns:*"). */ + can(subject: Subject | undefined, permission: string): boolean; + /** All permissions granted to a set of roles. */ + permissionsFor(roles: string[]): Set; +} +/** Build an RBAC checker from a role → permissions map. */ +declare function defineRbac(roles: Record): Rbac; +/** True if the subject has ALL of the given roles. */ +declare function hasRole(subject: Subject | undefined, ...required: string[]): boolean; +/** A policy predicate: subject (+ optional resource/attributes) → allowed. */ +type Policy = (subject: S, resource?: R) => boolean | Promise; +/** Combine policies: allow if ANY passes (OR). */ +declare function any(...policies: Policy[]): Policy; +/** Combine policies: allow only if ALL pass (AND). */ +declare function all(...policies: Policy[]): Policy; +/** ABAC helper: allow when an attribute matches (equality or predicate). */ +declare function attr(name: string, match: unknown | ((value: unknown) => boolean)): Policy; +/** Guard a route with a policy over `ctx` (reads `ctx.user` as the subject). */ +declare function authorize(policy: (ctx: Context) => boolean | Promise): Middleware; +/** Guard requiring one of the given roles. */ +declare function requireRole(...roles: string[]): Middleware; +/** Guard requiring an RBAC permission. */ +declare function requirePermission(rbac: Rbac, permission: string): Middleware; + +export { type AuthorizationDecision, type DecisionPolicy, type Policy, type Rbac, type Subject, all, allDecisions, allow, any, anyDecision, attr, authorize, authorizeDecision, decision, defineRbac, deny, filterAuthorized, hasRole, owner, requirePermission, requireRole }; +``` + +--- + +## @wrnexus/captcha + +Documentation URL: https://wrnexusjs.dev/packages/captcha + +# @wrnexus/captcha + +A first-class CAPTCHA and anti-automation package for WRNexusJS. It supports self-hosted challenges, a managed WRNexus service, external providers, form submission guards, page gates, accessible audio, adaptive risk checks, and a Tailwind-only `.wrn` component. + +## Install + +```bash +bun add @wrnexus/captcha +``` + +WRNexusJS automatically discovers the package plugin, component, client runtime, styles, and DevToolbar audit. Use `` directly after installation. The browser runtime is injected once only on responses that render a CAPTCHA; no script tag, public-file copy, or manual plugin registration is required. Call `captchaPlugin(options)` explicitly only when an application needs to override the discovered package configuration. + +## Included challenge modes + +- Number, alphabet, and alphanumeric image challenges +- Addition, subtraction, multiplication, and exact-division calculations +- Generated shape-selection image challenges +- Audio alternatives for text, numbers, and calculations +- Honeypot and minimum-completion-time invisible checks +- Self-hosted “I’m not a robot” checkbox challenge with one-time server verification +- Always, once-per-session, and adaptive page gates +- Cloudflare Turnstile, Google reCAPTCHA, hCaptcha, managed, and custom providers + +## Create the self-hosted engine + +```ts +import { + createCaptchaEngine, + createCaptchaHttpHandlers, + RedisCaptchaStore, +} from "@wrnexus/captcha/server"; + +const engine = createCaptchaEngine({ + secret: process.env.CAPTCHA_SECRET!, + store: new RedisCaptchaStore(redis), + basePath: "/api/captcha", + challengeTtlMs: 2 * 60_000, + responseTokenTtlMs: 5 * 60_000, + maxAttempts: 3, + minCompletionMs: 800, +}); + +export const handlers = createCaptchaHttpHandlers(engine); +``` + +Mount the handlers from an API catch-all route: + +```ts +import type { Context } from "@wrnexus/core"; +import { handlers } from "../../lib/captcha.ts"; + +export async function POST(ctx: Context) { + return (await handlers.handle(ctx.req, ctx)) ?? new Response("Not Found", { status: 404 }); +} +export const GET = POST; +export const HEAD = POST; +``` + +## Use the component + +```wrn + +``` + +The component uses Tailwind utilities and `--wire-*` theme variables. It has no companion component CSS file. + +### Main props + +`provider`, `siteKey`, `type`, `action`, `presentation`, `difficulty`, `disturbance`, `imageStyle`, `allowedStyles`, `excludedStyles`, `randomizeStyle`, `locale`, `size`, `color`, `class`, `name`, `endpoint`, `verifyEndpoint`, `responseField`, labels/messages, `autoLoad`, `autoVerify`, `showVerify`, `showRefresh`, `showAudio`, `showListen`, `showStatus`, `disabled`, `required`, and the backward-compatible `compact` alias. + +### Component sizes + +Use one of the three supported display modes: + +```wrn + + + +``` + +`small`/`sm` are accepted as aliases for `compact`, while `large`/`lg` are accepted as aliases for `big`. The old `compact="true"` prop still forces compact mode. + +### Listen button visibility + +Audio remains available by default. Hide the Listen and Use audio controls with either of these props: + +```wrn + + +``` + +`showListen` is the direct UI switch. `showAudio` remains the broader backward-compatible audio switch. + +### I’m not a robot checkbox + +```wrn + +``` + +The checkbox is not a client-only boolean. Clicking it completes a self-hosted invisible challenge that is time-limited, attempt-limited, one-time-use, action-bound, optionally session/hostname/IP-bound, and verified on the server. It is a low-friction anti-automation layer; use adaptive escalation to a visual or external provider for high-risk traffic. + +### Visual disturbance + +Use `disturbance` for visual and image-selection challenges. It accepts an integer from `25` through `75`: + +- `25`: light disturbance and easiest readability +- `50`: balanced default +- `75`: maximum supported dots, line crossings, glyph movement, and image-tile noise + +The browser sends this value to the challenge API, and the server validates the range before generating the challenge. It is also returned in challenge metadata. + +### Generated image renderer styles + +Text, number, alphanumeric, and calculation CAPTCHA images support 18 concrete renderers plus a random mode: + +`classic`, `collision`, `snow`, `corrosion`, `spiderweb`, `cross-shadow`, `split`, `split2`, `cut`, `darts`, `distortion`, `stitch`, `striped`, `wave`, `grid-noise`, `scribble`, `pixel`, and `broken-lines`. + +Use a fixed style: + +```wrn + +``` + +Use a new random style whenever the challenge is refreshed: + +```wrn + +``` + +Control the random pool with comma-separated component props or arrays in the TypeScript API: + +```wrn + +``` + +```ts +const challenge = await engine.create({ + action: "checkout", + type: "alphanumeric", + imageStyle: "random", + allowedStyles: ["classic", "snow", "distortion", "wave"], + excludedStyles: ["collision"], +}); +``` + +Set `randomizeStyle: true` to force random selection even when `imageStyle` names a concrete renderer. The resolved style, requested style, and active pool are returned in challenge metadata. The answer is never embedded in metadata or browser JavaScript. + +### Events + +`@ready`, `@challenge`, `@input`, `@verify`, `@success`, `@failure`, `@expired`, `@refresh`, `@audioStart`, `@audioEnd`, and `@error`. + +## Protect a validated form API + +Validate a cloned request first, then consume the CAPTCHA response token. This prevents a valid token from being consumed when ordinary field validation fails. + +```ts +import { captchaGuard } from "@wrnexus/captcha/server"; +import { parseBody } from "@wrnexus/validation"; +import contactSchema from "../schemas/contact.ts"; +import { engine } from "../lib/captcha.ts"; + +const guard = captchaGuard({ + action: "contact-submit", + engine, + bindHostname: true, + bindSession: true, +}); + +export async function POST(ctx) { + const validation = await parseBody(contactSchema, ctx.req.clone()); + if (!validation.ok) return validation.response; + + return guard(ctx, async () => Response.json({ ok: true, submission: validation.value })); +} +``` + +The CAPTCHA runtime binds its required-form check in the capture phase, so a `data-schema` validator cannot submit the form before CAPTCHA verification. After a successful form request, the component automatically creates a fresh challenge. + +### Retryable operations such as login + +A login may consume a valid CAPTCHA and then fail because the password is +incorrect. Configure a short action-bound session grant so the user can correct +their credentials without solving CAPTCHA again: + +```ts +const guard = captchaGuard({ + action: "auth-login", + engine, + bindHostname: true, + bindSession: true, + verifiedForMs: 5 * 60_000, +}); +``` + +Keep the verified widget state for non-CAPTCHA form errors: + +```wrn + +``` + +The grant is stored in the current session and bound to the configured action. +Expired grants and CAPTCHA-specific errors still require and load a fresh +challenge. Keep login rate limits and authentication lockout enabled; +`verifiedForMs` removes repeated human verification, not credential-abuse +controls. + +## Validate a schema and CAPTCHA together + +```ts +const result = await parseWithCaptcha(signupSchema, body, ctx, { + action: "signup", + engine, +}); + +if (!result.ok) return Response.json({ ok: false, errors: result.errors }, { status: 400 }); +``` + +## Page gate + +```ts +export default captchaPageGate({ + action: "reports-access", + engine, + challengePath: "/captcha", + policy: { + mode: "session", + verifiedForMs: 15 * 60_000, + routeGroups: ["/reports"], + }, +}); +``` + +Use `mode: "always"` for every visit, `mode: "session"` for a temporary grant, or `mode: "adaptive"` with `signals(ctx)`. + +The challenge page should post the return path as a normal hidden field instead of constructing JavaScript inside the HTML `action` attribute: + +```wrn + + + + + +``` + +The `/api/page-grant` route reads `returnTo`, restricts it to the current origin, and redirects only after `captchaPageGate()` has verified and stored the temporary session grant. + +## External providers + +```ts +const turnstile = turnstileProvider({ + secretKey: process.env.TURNSTILE_SECRET!, + siteKey: process.env.PUBLIC_TURNSTILE_SITE_KEY!, + expectedHostnames: ["example.com"], + expectedAction: "signup", +}); +``` + +```wrn + +``` + +Use the matching provider in `captchaGuard({ provider: turnstile })`. reCAPTCHA and hCaptcha adapters follow the same pattern. + +## Managed provider + +```ts +const managed = managedCaptchaProvider({ + baseUrl: "https://captcha.example.com", + siteKey: process.env.PUBLIC_CAPTCHA_SITE_KEY!, + secretKey: process.env.CAPTCHA_SECRET_KEY!, +}); +``` + +For direct browser challenge creation, configure the component’s `endpoint` as the managed `/v1/challenges` URL and its `verifyEndpoint` as `/v1/solve`. Keep the secret key only in the server provider. + +## Stores + +- `MemoryCaptchaStore`: development and one-process applications +- `SqliteCaptchaStore`: adapter for SQLite-like `prepare().run/get/all()` clients +- `RedisCaptchaStore`: shared TTL storage with Lua-backed atomic consumption when `eval` is available +- `CaptchaStore`: implement this interface for PostgreSQL, MySQL, MongoDB, or another backend + +## Audio + +`AssetAudioRenderer` concatenates bundled English PCM WAV clips without calling an external service. Supply a custom `CaptchaAudioRenderer` for recorded voices, Hindi or other languages, or managed text-to-speech. + +## DevToolbar + +The automatically discovered CAPTCHA plugin registers its DevToolbar audit panel. It checks for likely client-side secrets, missing action bindings, missing provider site keys, optional CAPTCHA fields, accessible alternatives, and server-verification reminders. Explicit `captchaPlugin(options)` registration is needed only to override automatic configuration. + +## Testing + +Use deterministic custom generators in unit tests. Never require users or CI to solve random CAPTCHA images. The package includes engine, provider, policy, HTTP, storage, replay, expiry, binding, and audio authorization tests. + +## Custom challenge generator + +```ts +import { defineCaptchaGenerator, createCaptchaEngine } from "@wrnexus/captcha"; + +const wordChallenge = defineCaptchaGenerator({ + type: "word" as const, + generate(context) { + const answer = "NEXUS"; + return { + type: "word", + presentation: "visual", + prompt: "Enter the displayed word", + answer, + answerKind: "text", + image: renderYourImage(answer), + inputMode: "text", + }; + }, +}); + +const engine = createCaptchaEngine({ secret, generators: [wordChallenge] }); +``` + +Applications may also implement `CaptchaStore`, `CaptchaAudioRenderer`, or use `defineCaptchaProvider()` for a completely custom service. + +### Exported TypeScript declarations + +```ts +export { CaptchaAudioRenderer, CaptchaBinding, CaptchaChallenge, CaptchaChallengeGenerator, CaptchaChallengeRecord, CaptchaChallengeType, CaptchaConcreteImageStyle, CaptchaDifficulty, CaptchaEngine, CaptchaEngineOptions, CaptchaFailureCode, CaptchaGeneratorContext, CaptchaGuardOptions, CaptchaHttpHandlers, CaptchaImageItem, CaptchaImageStyle, CaptchaMiddleware, CaptchaPageGateOptions, CaptchaPolicyMode, CaptchaPolicyOptions, CaptchaPresentation, CaptchaProvider, CaptchaProviderClientConfig, CaptchaProviderName, CaptchaResponseTokenRecord, CaptchaRiskResult, CaptchaRiskSignals, CaptchaStore, CaptchaVerificationResult, CreateCaptchaOptions, GeneratedCaptchaChallenge, VerifyCaptchaInput } from './types.js'; +export { CaptchaHttpOptions, CaptchaParseResult, CaptchaSessionGrant, DefaultCaptchaEngine, ParseWithCaptchaOptions, bindingHash, bytesToBase64Url, captchaGuard, captchaPageGate, clearCaptchaGrants, constantTimeEqual, createCaptchaEngine, createCaptchaHttpHandlers, defaultRandomBytes, evaluateCaptchaRisk, hmacSha256, parseWithCaptcha, randomId, sha256, shouldRequireCaptcha, validCaptchaGrant } from './server/index.js'; +export { CaptchaAuditIssue, CaptchaPluginOptions, captchaComponentsDir, captchaPlugin } from './plugin.js'; +export { MemoryCaptchaStore, MemoryCaptchaStoreOptions, createMemoryCaptchaStore } from './stores/memory.js'; +export { SqliteCaptchaStore, SqliteCaptchaStoreOptions, SqliteDatabaseLike, SqliteStatementLike, createSqliteCaptchaStore } from './stores/sqlite.js'; +export { RedisCaptchaClient, RedisCaptchaStore, RedisCaptchaStoreOptions, createRedisCaptchaStore } from './stores/redis.js'; +export { SelfHostedCaptchaProvider, selfHostedProvider } from './providers/self-hosted.js'; +export { S as SiteverifyCaptchaProvider, a as SiteverifyPreset, b as SiteverifyProviderOptions } from './siteverify-Cg3TTAp4.js'; +export { TurnstileCaptchaProvider, turnstileProvider } from './providers/turnstile.js'; +export { RecaptchaProvider, recaptchaProvider } from './providers/recaptcha.js'; +export { HcaptchaProvider, hcaptchaProvider } from './providers/hcaptcha.js'; +export { ManagedCaptchaProvider, ManagedCaptchaProviderOptions, managedCaptchaProvider } from './providers/managed.js'; +export { defineCaptchaProvider } from './providers/custom.js'; +export { CAPTCHA_CONCRETE_IMAGE_STYLES, CAPTCHA_IMAGE_STYLES, CalculationCaptchaGenerator, ImageCaptchaGenerator, InvisibleCaptchaGenerator, ResolveCaptchaImageStyleOptions, ResolvedCaptchaImageStyle, Rgba, RgbaImage, TextCaptchaGenerator, alphaCaptchaGenerator, alphanumericCaptchaGenerator, bytesToBase64, calculationCaptchaGenerator, createImage, defaultCaptchaGenerators, defineCaptchaGenerator, drawGlyph, drawLine, drawText, encodePng, fillCircle, fillPolygon, fillRect, honeypotCaptchaGenerator, imageCaptchaGenerator, isCaptchaImageStyle, normalizeCaptchaImageStyle, normalizeCaptchaImageStyleList, notRobotCaptchaGenerator, numberCaptchaGenerator, pngDataUri, resolveCaptchaImageStyle, setPixel, timingCaptchaGenerator } from './challenges/index.js'; +export { AssetAudioRenderer, AssetAudioRendererOptions, createAssetAudioRenderer, resolveCaptchaAudioAssetsDir } from './audio/index.js'; +import '@wrnexus/core'; +import '@wrnexus/validation'; +import '@wrnexus/plugin'; +``` + +--- + +## @wrnexus/cli + +Documentation URL: https://wrnexusjs.dev/packages/cli + +# @wrnexus/cli + +> The `wrnexus` command-line tool that scaffolds, runs, builds, tests, and manages WrNexus apps. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/cli` provides the `wrnexus` executable — the single entry point for developing a WrNexus app. It runs the HMR dev server, produces a self-contained production build, scaffolds apps/pages/components, drives database migrations, regenerates typed routes and queries, runs tests, and manages configuration profiles. It also scaffolds multi-app monorepos and serves them behind a domain-routing gateway. This is a CLI/build-time package (it shells out to the Bun binary for the dev child and tests) and it also exports the workspace config types via a subpath. + +## Installation + +```bash +bun add @wrnexus/cli +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +Once installed, invoke it from an app directory: + +```bash +bunx wrnexus dev +# or add scripts: "dev": "wrnexus dev .", "build": "wrnexus build ." +``` + +## Commands + +Every command accepts an optional `[app-dir]` (defaults to `.`). Commands that read config or `.env` also accept `--profile=` (see [Profiles](#profiles)). + +| Command | Purpose | +| ------------------------------------- | ---------------------------------------------------------------------- | +| `wrnexus dev [app-dir] [--port=3000]` | Start the development server with live reload / HMR. | +| `wrnexus build [app-dir]` | Build a self-contained production server bundle + assets into `dist/`. | +| `wrnexus create ` | Scaffold a new single app from an inline template. | +| `wrnexus workspace ` | Scaffold a monorepo (`apps/*` + shared `packages/*`). | +| `wrnexus workspace add ` | Add and register an app in the current workspace. | +| `wrnexus gateway [--port=3000]` | Serve every workspace app behind one port, routed by domain. | +| `wrnexus production [workspace-dir]` | Build, migrate, and serve every workspace app in production. | +| `wrnexus generate ` | Scaffold a `page` \| `component` \| `api` \| `schema`. | +| `wrnexus generate routes` | Regenerate the typed routes file (`app/routes.gen.ts`). | +| `wrnexus generate docker` | Scaffold `Dockerfile`, `.dockerignore`, and `docker-compose.yml`. | +| `wrnexus generate mobile` | Scaffold a Capacitor shell for iOS and Android. | +| `wrnexus mobile add ` | Install Capacitor plugins and sync native projects. | +| `wrnexus eject ` | Copy Wire UI component `.wrn` sources into `app/components/`. | +| `wrnexus db ` | Database migrations and tooling (see [db](#wrnexus-db)). | +| `wrnexus test [app-dir] [--watch]` | Run the app's tests via `bun test` (defaults to the `test` profile). | +| `wrnexus profiles [app-dir]` | List config profiles and their `.env` files, marking the active one. | +| `wrnexus help` | Print usage. | + +`wrnexus g` is an alias for `wrnexus generate`. + +### `wrnexus dev` + +Supervises a child dev-server process (from `@wrnexus/dev-server`). The child owns file watching and HMR: CSS and client-island edits update the live page over a WebSocket with no restart; when a server module changes, the child exits with a restart code and the supervisor respawns it (the browser reconnects and morphs in the new HTML). On startup it regenerates typed DB queries and typed routes (best effort). Use `--port=` to change the port (default `3000`). + +```bash +wrnexus dev . --port=8080 +``` + +### `wrnexus build` + +Emits into `/dist/`: + +- `server.js` — a single, minified, self-contained Bun server with a **static** manifest of every page / api / realtime / middleware / component / layout module (no runtime filesystem scan or on-the-fly bundling). +- `reactive.js`, `theme.css`, `theme.js`, `ui.css`, and (if present) `styles.css` — hashed, minified browser assets. +- `public/` — copied verbatim. + +Before bundling, it regenerates typed queries for the default and every named database. Run the output with: + +```bash +bun dist/server.js # PORT env var optional +# Generated apps also provide: npm start +# Build and start together: npm run production +``` + +### `wrnexus create` + +Scaffolds a new app from an inline (dependency-free) template — `package.json`, `.gitignore`, config, and starter `app/` files. Use `npm run dev` during development, `npm run build && npm start` for production, or `npm run production` to build and start in one command. The generated production server currently requires Bun even when npm is used to manage packages and scripts. + +### `wrnexus update` + +`wrnexus update --latest` performs a complete project upgrade. It hands control to the exact target CLI, backs up important project files under `.wrnexus/update-backups/`, updates every `@wrnexus/*` dependency, refreshes framework-owned references, and applies every versioned syntax/config/file migration between the project version and target version. After installation it runs the project's `check` and `build` scripts; the new version is recorded only after verification succeeds. + +Use `--dry-run` to preview an upgrade or `--no-verify` when verification is intentionally handled elsewhere. Migrations never overwrite user-owned configuration wholesale: each release must provide a focused, idempotent transformation for any changed syntax or config contract. + +```bash +wrnexus create my-app +``` + +### `wrnexus generate` + +Scaffolds a single file from a template, refusing to overwrite an existing file. Types (with aliases): `page`/`p`, `component`/`c`, `api`/`a`, `schema`/`s`. Nested names create nested paths. + +```bash +wrnexus generate page about # app/pages/about.wrn +wrnexus generate component user-card # app/components/user-card.wrn +wrnexus generate api users/list # app/api/users/list.ts +wrnexus generate schema signup # app/schemas/signup.ts +wrnexus generate routes # regenerate app/routes.gen.ts +wrnexus generate docker # Dockerfile + compose + .dockerignore +wrnexus generate mobile --mode=webview --app-id=com.example.app --app-name="Example" --url=https://app.example.com +wrnexus generate mobile --mode=native +``` + +The mobile generator creates a separate `mobile/` package and reads +`config.mobile.mode`. `webview` creates a Capacitor shell that renders the hosted +WrNexus application. `native` creates a WebView-free Expo/React Native app whose +screens call the shared backend through `mobile/src/wrnexus.ts`. Native screens +do not render `.wrn` HTML. In either mode, run `bun install` in `mobile/`; iOS +device builds require macOS and Xcode. + +Install official or community Capacitor plugins through the root CLI: + +```bash +wrnexus mobile add @capacitor/camera @capacitor/haptics +wrnexus mobile sync +wrnexus mobile assets # generate native icons from config.mobile.icon +``` + +In native mode, `mobile add` runs `expo install` and `mobile sync` runs Expo +prebuild. In WebView mode they retain the Capacitor install/sync behavior. +`wrnexus mobile compile` maps portable `app/pages/**/*.wrn` pages to Expo Router +TSX routes. Native `bun run start` invokes this compilation automatically. + +Browser code can access installed plugins through the SSR-safe +`@wrnexus/mobile` bridge. The command adds each plugin to both the WrNexus app +(JavaScript proxy) and `mobile/` (native synchronization). + +`wrnexus mobile sync` also configures Android so only true network failures use +the local connection-error screen. HTTP errors such as 404 and 500 keep their +WrNexus response pages. + +### `wrnexus eject` + +Copies a Wire UI component's `.wrn` source out of `@wrnexus/ui` into `app/components/`, so the app owns and can edit it (the app copy shadows the library one by name). Run with no names to list available components. It skips components that already exist in the app. + +```bash +wrnexus eject button card modal +``` + +### `wrnexus db` + +Database migrations and tooling. Without a flag, commands target the **default** database (`db` in `wrnexus.config.ts`, files under `app/db/`). Pass `--db=` to target a named database (`databases.`, files under `app/db//`). + +| Subcommand | Purpose | +| ------------------------------- | ----------------------------------------------------------------------------------- | +| `db new [--from-models]` | Scaffold a migration; `--from-models` derives it from the TS models in `schema.ts`. | +| `db migrate` | Apply all pending migrations. | +| `db rollback` | Revert the last applied migration. | +| `db status` | List applied / pending migrations. | +| `db generate` | Regenerate typed queries (`queries/*.sql` → `queries.gen.ts`). | +| `db seed` | Run the database's `seed.ts` (default export / `seed` function). | +| `db studio [table]` | Inspect tables — list row counts, or dump the first 50 rows of one table. | + +```bash +wrnexus db new create_users --from-models +wrnexus db migrate +wrnexus db studio users +wrnexus db status --db=analytics +``` + +### `wrnexus workspace` and `wrnexus gateway` + +`workspace ` scaffolds a monorepo: several WrNexus apps under `apps/*` and shared libraries under `packages/*`, plus a `wrnexus.workspace.ts` that maps each app to the domains it serves. `gateway` runs every app behind one port and routes by `Host` header, with optional per-app auth and gateway-wide security (trusted hosts, rate limit, security headers, access log). + +```bash +wrnexus workspace acme +wrnexus gateway --port=3000 +``` + +For a complete production start, use the first-class workspace orchestrator: + +```bash +wrnexus production --host=0.0.0.0 --port=3000 +``` + +It builds every registered app, applies default and named-database SQL migrations +when present, and starts the production gateway only after preparation succeeds. +Use `--prepare-only`, `--no-build`, or `--no-migrate` when deployment stages are +managed separately; `--environment=` selects another workspace environment. + +From a workspace root, add and register another app in one command: + +```bash +wrnexus workspace add reports --domain=reports.localhost +bun install +``` + +Development gateways bind to `127.0.0.1` by default for reliable access on Windows, +macOS, and Linux. Open the configured app domain on the gateway port (for example +`http://localhost:3000` or `http://admin.localhost:3000`), not the internal child ports +printed while apps start. Pass `--host=0.0.0.0` to accept connections from other devices. + +### `wrnexus test` + +Runs the app's tests with `bun test`. Defaults to the `test` profile (config + `.env.test`). Pass `--watch` to re-run on change; extra flags pass straight through to `bun test`. + +```bash +wrnexus test . --watch +``` + +## Usage + +### Create and run a single application + +```bash +bunx @wrnexus/cli create customer-portal +cd customer-portal +bun install +bun run dev +``` + +### Add routes and shared UI to an existing app + +```bash +wrnexus generate page reports/monthly +wrnexus generate api reports/export +wrnexus generate component report-filter +wrnexus generate routes +``` + +### Create a multi-app workspace and add another app + +```bash +wrnexus workspace company-suite +cd company-suite +wrnexus workspace add reports --domain=reports.localhost +bun install +wrnexus gateway --port=3000 +``` + +Open `http://reports.localhost:3000`; the gateway selects `apps/reports` from the +request host. + +### Upgrade with migrations and verification + +```bash +wrnexus update --latest --dry-run +wrnexus update --latest +wrnexus doctor +``` + +## Profiles + +Pass `--profile=` to `dev`, `build`, `db` (or set `WRNEXUS_PROFILE`) to select a config profile. The CLI publishes `WRNEXUS_PROFILE` so config loaders and the dev child pick it up, and loads that profile's `.env` cascade (`.env`, `.env.local`, `.env.`, `.env..local`) into `process.env`. + +```bash +wrnexus dev --profile=uat +wrnexus profiles # ● development (config, .env.development) + # ○ production + # ○ uat (config, .env.uat) +``` + +## Subpath exports + +`@wrnexus/cli/workspace` exposes the workspace configuration types used by `wrnexus.workspace.ts`: + +```ts +import type { WorkspaceConfig, WorkspaceApp } from "@wrnexus/cli/workspace"; + +const config: WorkspaceConfig = { + security: { trustedHostsOnly: true, headers: true, accessLog: true }, + apps: [{ name: "web", dir: "apps/web", domains: ["localhost", "web.localhost"] }], +}; + +export default config; +``` + +## Requirements / Notes + +- **Bun-only.** The CLI runs on Bun, spawns the Bun binary for the dev child and `bun test`, and the production build uses `Bun.build`. Node is not supported. +- Orchestrates the rest of the framework: `@wrnexus/dev-server` (dev/prod server + gateway), `@wrnexus/router` (route + typed-routes codegen), `@wrnexus/compiler` (`.wrn` → `.ts`), `@wrnexus/db` (migrations, typed queries), `@wrnexus/styles` (config, profiles, `.env`, themes, styles), `@wrnexus/ui` (ejectable Wire UI components), `@wrnexus/validation`, `@wrnexus/csr`, and `@wrnexus/i18n`. +- Reads `wrnexus.config.ts` for `db` / `databases`, `theme`, `styles`, `seo`, `security`, `i18n`, and `profiles`, and `wrnexus.workspace.ts` for the gateway. + +### Exported TypeScript declarations + +```ts +#!/usr/bin/env bun +``` + +--- + +## @wrnexus/compiler + +Documentation URL: https://wrnexusjs.dev/packages/compiler + +# @wrnexus/compiler + +> Compiler for the `.wrn` language — tokenizes, parses, and lowers `.wrn` page and component files to TypeScript. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page. + +Static ES module imports may appear before the root declaration. Imported values are +available to server-rendered expressions, including component props: + +```wrn +import { appUrl } from "@wrnexus/helpers"; + +layout PublicLayout { + view { + + } +} +``` + +## Installation + +```bash +bun add @wrnexus/compiler +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +All exports come from the package root (`@wrnexus/compiler`). + +### `compileWireFile(source: string): string` + +Compile `.wrn` source to a TypeScript module string. Throws `ParseError` on invalid input. The output is prefixed with a `// compiled from .wrn` comment. + +### `compile(source: string): CompileResult` + +Richer entry point that returns the generated code, the AST, and any diagnostics. + +```ts +interface CompileResult { + code: string; + ast: PageAst; + diagnostics: string[]; +} +``` + +On a `ParseError` it pushes the message into `diagnostics` and re-throws. + +### `parse(source: string): PageAst` + +Run the lexer + recursive-descent parser and return the AST. Throws `ParseError` (lexer `LexError`s are caught and rethrown as `ParseError`). + +### `generate(ast: PageAst): string` + +Lower a `PageAst` to TypeScript. `page` ASTs become a default-export page component (plus `meta`, optional `layout`, `__wrnexusApi`/method handlers, `websocket`, and SSR/CSR data bindings); `component` ASTs become a module exporting `render(props)` and `__wrnexusComponent`. + +### `Lexer` + +On-demand lexer for `.wrn`. Yields structural tokens and exposes raw-span readers for the parser. + +```ts +class Lexer { + pos: number; + constructor(src: string); + next(): Token; // consume next structural token + peek(): Token; // look ahead without consuming + readPath(): string; // route path, e.g. /users/[id] + readToLineEnd(): string; // rest of line (state/prop initializers) + readBalancedBraces(): string; // inner text of a { ... } block, string-aware +} +``` + +`Token` is `{ type: TokenType; value: string; pos: number }`, where `TokenType` is one of `ident`, `string`, `lbrace`, `rbrace`, `lparen`, `rparen`, `at`, `eq`, `comma`, `eof`. + +### Errors + +| Class | Thrown by | Meaning | +| ------------ | ------------------------------------------------- | --------------------------------------------------------------- | +| `ParseError` | `parse`, `compile`, `compileWireFile`, `generate` | Invalid `.wrn` grammar or (rewrapped) lex failure. | +| `LexError` | `Lexer` | Unexpected character / unterminated string / unbalanced braces. | + +### AST types + +Exported type-only symbols describing the parsed tree: + +| Type | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PageAst` | Root node including top-level `imports`, `kind`, `name`, `types`, typed `props`, typed `states`, `view`, styles, functions, data APIs, lifecycle, and routes. | +| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. | +| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. | +| `StateDecl` | `{ name; valueType?; expr }` — a typed `state x: Type = ` declaration. | +| `PropDecl` | `{ name; valueType?; required; default }` — a typed prop declaration. | +| `SeoBlock` | `Record` from the `seo { ... }` block. | +| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. | +| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. | +| `DataMode` | `"ssr" \| "client"`. | +| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. | +| `RealtimeBlock` | `{ name; handlers }` — a `realtime { on evt(args) { ... } }` block. | + +## Usage + +Compile a page: + +```ts +import { compileWireFile } from "@wrnexus/compiler"; + +const ts = compileWireFile(` +page Home { + state count = 0 + seo { title = "Home" description = "Welcome" } + view { + + } +} +`); +// ts is a TypeScript module: exports `meta`, and a default page component +// returning an HTML string, wrapped in a data-scope for the reactive runtime. +``` + +Inspect the AST and diagnostics: + +```ts +import { compile, ParseError } from "@wrnexus/compiler"; + +try { + const { code, ast, diagnostics } = compile(source); + console.log(ast.kind, ast.name, ast.states.length); +} catch (err) { + if (err instanceof ParseError) console.error(err.message); +} +``` + +Drive the parse/codegen stages directly: + +```ts +import { parse, generate } from "@wrnexus/compiler"; + +const ast = parse(componentSource); // ast.kind === "component" +const module = generate(ast); // exports render(props) + __wrnexusComponent +``` + +Use the lexer standalone: + +```ts +import { Lexer } from "@wrnexus/compiler"; + +const lx = new Lexer("page Home {"); +lx.next(); // { type: "ident", value: "page", pos: 0 } +lx.next(); // { type: "ident", value: "Home", pos: 5 } +lx.next(); // { type: "lbrace", value: "{", pos: 10 } +``` + +## The `.wrn` language (as parsed) + +A file opens with `page ` or `component ` followed by a `{ ... }` body containing zero or more members: + +- `layout = ""` — selects `app/layouts/.wrn` (pages only). +- `types { }` — reusable interfaces and aliases for the current file. +- `props { name: Type = ... }` — typed component props. Omit `= ` to make a prop required. Legacy inferred props remain supported. +- `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with ``. +- `state : Type = ` — typed reactive state seeded from a raw JS expression. The annotation is optional for backward compatibility. +- `view { }` — plain HTML with `{expr}` interpolation in text and attributes, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and ``. Attribute expressions that reference `state` keep an SSR value and update reactively in the browser. +- `seo { key = "value" ... }` — metadata merged into the generated `meta`. +- `style { }` — inlined page/component stylesheet (repeatable). +- `functions { }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code. +- `api { }` — route handler, lowered to a `METHOD` export (repeatable). +- `ssr { ... }` / `client { ... }` — data blocks holding `api { ... }` bindings and their own `functions { ... }`. +- `realtime { on () { } ... }` — websocket handlers, lowered to a `websocket` export. + +`view` markup is parsed by a lenient dedicated HTML parser (`parseHtmlView`); HTML void elements (`
`, ``, …) take no closing tag. Line comments (`//`) are skipped by the lexer. + +## Requirements / Notes + +- Pure TypeScript with no runtime dependencies; runs under **Bun** as part of the WrNexus toolchain (Node is not supported). +- Generated modules target WrNexus runtime primitives (`data-scope`, `data-text`, `data-on-*`, `data-for`, `data-component`, `__wrnexus*`/`__wire*` helpers) — consume the output within a WrNexus app, e.g. via `@wrnexus/core`'s dev loader. + +### Exported TypeScript declarations + +```ts +import { PageAst as PageAst$1, WrnDiagnostic } from '@wrnexus/syntax'; +export { ActionBlock, ApiBlock, Attr, ComputedDecl, DataApiBlock, DataMode, EffectBlock, EventDecl, LexError, Lexer, LoadBlock, ModeFunctionsBlock, PageAst, ParseError, PropDecl, RealtimeBlock, SeoBlock, StateDecl, ViewNode, WrnDiagnostic, assertValidAst, diagnose, diagnosticFromError, eraseFunctionTypes, formatDiagnostic, inferredRuntimeType, parse, runtimeTypeOf } from '@wrnexus/syntax'; +import { PageAst } from '@wrnexus/syntax/parser'; + +/** + * Code generation: lower a `.wrn` AST to TypeScript that targets the framework's + * existing primitives. + * + * state -> a `data-scope` declaration consumed by the runtime + * view -> an HTML string returned by a page component + * @event="..." -> data-on-="..." + * "...{expr}..." -> text kept verbatim ({expr} is mustache for runtime) + * api="" -> SSR/client data binding declared in a mode block + * ssrGet/ssrText -> legacy server-side API fetch + render + * csrGet/csrText -> legacy browser-side API fetch + render + * style -> an inline page stylesheet + * functions -> server-only helpers for API/realtime code + * api M /p {b} -> export const M = async (ctx) => { b } + * realtime {..} -> export const websocket = { evt(ws, ...args) { b } } + */ + +declare function generate(ast: PageAst): string; + +declare class NativeCompileError extends Error { + constructor(message: string); +} +/** Compile a parsed `.wrn` page to an Expo Router React Native screen. */ +declare function generateNative(ast: PageAst): string; + +interface CompilationCacheEntry extends CompileResult { + key: string; + file: string; + sourceHash: string; + createdAt: number; +} +interface CompilationCacheOptions { + maxEntries?: number; + now?: () => number; +} +interface CompilationCache { + compile(source: string, file?: string, salt?: string): CompilationCacheEntry; + get(key: string): CompilationCacheEntry | undefined; + invalidate(file?: string): number; + clear(): void; + size(): number; + stats(): { + hits: number; + misses: number; + entries: number; + }; +} +declare function compilationKey(source: string, file?: string, salt?: string): string; +declare function createCompilationCache(options?: CompilationCacheOptions): CompilationCache; +declare class DependencyGraph { + #private; + set(file: string, dependencies: Iterable): void; + remove(file: string): void; + dependencies(file: string): string[]; + dependents(file: string): string[]; + affected(file: string): string[]; +} + +/** + * @wrnexus/compiler — the `.wrn` language compiler. + * + * Parsing and language diagnostics are provided by the canonical + * `@wrnexus/syntax` package. This package owns platform-specific codegen. + */ + +interface CompileResult { + code: string; + ast: PageAst$1; + /** Backward-compatible plain diagnostic messages. */ + diagnostics: string[]; + /** Structured diagnostics for editors, CI, and the DevToolbar. */ + richDiagnostics: WrnDiagnostic[]; +} +/** Compile `.wrn` source into an Expo Router React Native screen. */ +declare function compileNativeWireFile(source: string): string; +/** + * Compile `.wrn` source into TypeScript source. Errors include a stable code, + * source location, code frame, and actionable hint whenever available. + */ +declare function compileWireFile(source: string, filePath?: string): string; +/** Richer entry point returning the AST and structured diagnostics. */ +declare function compile(source: string, filePath?: string): CompileResult; + +export { type CompilationCache, type CompilationCacheEntry, type CompilationCacheOptions, type CompileResult, DependencyGraph, NativeCompileError, compilationKey, compile, compileNativeWireFile, compileWireFile, createCompilationCache, generate, generateNative }; +``` + +--- + +## @wrnexus/core + +Documentation URL: https://wrnexusjs.dev/packages/core + +# @wrnexus/core + +> The framework core: the request `Context`, middleware contract, and the security, session, caching, streaming, realtime, and JSX primitives every other WrNexus package builds on. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/core` is the shared foundation of WrNexus. It defines the `Context` +object that flows through every middleware, page, and API route, plus the +`Middleware`/`Next` contract they implement. On top of that it ships the +building blocks a real app needs: cookie-backed sessions, password auth, CSRF +protection, rate limiting, request logging, HTTP + in-memory caching, file +uploads, streaming/SSE responses, WebSocket "rooms", security headers/CORS, and +a server-side JSX runtime that renders to HTML strings. Everything here is +**server-side** and Bun-native (it uses `Bun.password`, `Bun.write`, the +web-standard `Request`/`Response`, and `crypto`). You depend on it directly and +transitively through the rest of the framework. + +## Installation + +```bash +bun add @wrnexus/core +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +### Context & middleware — `@wrnexus/core` + +The `Context` (`ctx`) is the single value passed to middleware and handlers. + +| Export | Kind | Description | +| ------------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------ | +| `Context` | type | Per-request object: `req`, `url`, `lang`, `t`, `params`, `locals`, `user?`, `ip?`, `cookies`, `session`, `localStorage`. | +| `Next` | type | `() => Promise \| Response` — invokes the next middleware/handler. | +| `Middleware` | type | `(ctx, next) => Promise \| Response`. Return `next()` to continue, or a `Response` to short-circuit. | +| `createContext(req, url)` | fn | Build a fresh `Context` for an incoming request (wires up cookies, session, localStorage snapshot). | +| `withContextHeaders(ctx, res)` | fn | Apply accumulated headers (e.g. `Set-Cookie`) from the context onto a response. | +| `PageComponent` | type | `(ctx) => string \| Promise` — a page module's default export. | +| `PageMeta` / `SeoConfig` | type | `` metadata: `title`, `description`, `canonical`, `robots`, `image`, `twitterCard`, `themeColor`, … | +| `TFunction` | type | `(key, params?) => string` — translate a key for `ctx.lang`, interpolating `{param}` placeholders. | + +Key `Context` fields: + +- `ctx.locals` — per-request scratch space for passing values between middleware. +- `ctx.user` — the authenticated user (populated by `sessionAuth`/`logIn`), or `null`. +- `ctx.ip` — the direct socket peer IP (not spoofable via headers). +- `ctx.cookies` / `ctx.session` / `ctx.localStorage` — see **Storage** below. + +### Authentication — `@wrnexus/core` + +Passwords are hashed with argon2id via `Bun.password`; sessions ride the +cookie-backed `SessionStore`. + +| Export | Signature | Notes | +| -------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `hashPassword(password)` | `(string) => Promise` | argon2id hash to store. | +| `verifyPassword(password, hash)` | `(string, string) => Promise` | Constant-safe; returns `false` on bad/empty hash. | +| `logIn(ctx, user)` | `(Context, U) => void` | Regenerates the session id (fixation defense), stores the user, sets `ctx.user`. | +| `logOut(ctx)` | `(Context) => void` | Clears the session and `ctx.user`. | +| `getUser(ctx)` | `(Context) => U \| null` | Current user from `ctx.user`, falling back to the session. | +| `sessionAuth()` | `() => Middleware` | Hydrates `ctx.user` from the session each request. Register early. | +| `requireAuth(options?)` | `(RequireAuthOptions?) => Middleware` | Guard: API/fetch requests get `401 JSON`, page navigations get `302` to `loginPath` (default `/login`) with `?next=`. | +| `SESSION_USER_KEY` | `"user"` | Session key holding the user. | + +`RequireAuthOptions`: `{ loginPath?: string }`. + +### CSRF — `@wrnexus/core` + +Double-submit cookie pattern: a readable `wire-csrf` cookie is echoed in an +`x-csrf-token` header on unsafe requests. + +| Export | Signature | Notes | +| ----------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `csrfToken(ctx)` | `(Context) => string` | Ensures the CSRF cookie exists and returns its token. | +| `verifyCsrf(ctx)` | `(Context) => boolean` | Safe methods (GET/HEAD/OPTIONS) pass; otherwise header/`ctx.locals._csrf` must match the cookie (constant-time). | +| `csrfProtection()` | `() => Middleware` | 403s unsafe requests with a missing/mismatched token. | +| `CSRF_COOKIE` / `CSRF_HEADER` | `"wire-csrf"` / `"x-csrf-token"` | Cookie & header names. | + +### Rate limiting — `@wrnexus/core` + +Fixed-window limiter that returns `429` with `Retry-After` and emits +`RateLimit-Limit`/`-Remaining`/`-Reset` headers. + +| Export | Signature | Notes | +| --------------------- | ----------------------------------- | ---------------------------------------------------------------------- | +| `rateLimit(options?)` | `(RateLimitOptions?) => Middleware` | Main middleware. | +| `peerKey(ctx)` | `(Context) => string` | Non-spoofable key from `ctx.ip` (default). | +| `proxyKey(ctx)` | `(Context) => string` | Trusts `x-forwarded-for`/`x-real-ip`. Use only behind a trusted proxy. | +| `defaultKey` | — | **Deprecated** alias of `proxyKey`. | + +`RateLimitOptions`: `windowMs` (default `60_000`), `max` (default `60`), +`key`, `trustProxy` (default `false` → keys on `peerKey`; `true` → `proxyKey`), +`message`, `headers` (default `true`), `store`. + +`RateLimitStore` is pluggable — implement `hit(key, windowMs, now) => Bucket | Promise` +(a `Bucket` is `{ count, resetAt }`) to back limits with Redis/SQL across +instances. The default store is process-local memory. + +### Request logging — `@wrnexus/core` + +| Export | Signature | Notes | +| ------------------------- | --------------------------------------- | -------------------------------------------------------------------------------- | +| `requestLogger(options?)` | `(RequestLoggerOptions?) => Middleware` | One record per request with a request id (stored on `ctx.locals[requestIdKey]`). | + +`RequestLoggerOptions`: `format` (`"pretty"` default \| `"json"`), `sink(line, record)` +(default `console.log`), `requestIdKey` (default `"requestId"`), `now`. +`RequestRecord` = `{ time, id, method, path, status, durationMs }`. + +### Caching — `@wrnexus/core` + +| Export | Kind | Notes | +| -------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `TTLCache` | class | In-memory TTL cache: `get`, `set`, `getOrLoad(key, loader, ttlMs?)`, `delete`, `clear`, `size`. Constructor takes a default `ttlMs` (60s). | +| `cacheControl(options)` | fn | Build a `Cache-Control` value from `CacheControlOptions`. | +| `withCacheControl(res, options)` | fn | Apply `Cache-Control` to a response. | +| `etag(body, weak?)` | fn | Stable quoted FNV-1a ETag (weak by default). | +| `notModified(req, tag)` | fn | `true` when `If-None-Match` matches — send a `304`. | + +`CacheControlOptions`: `maxAge`, `sMaxAge`, `private`, `noStore`, `noCache`, +`staleWhileRevalidate`, `immutable`. + +### File uploads — `@wrnexus/core` + +Bun parses `multipart/form-data` via `Request.formData()`; these helpers +validate and persist the resulting `File`s. + +| Export | Signature | Notes | +| --------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `collectUploads(form)` | `(FormData) => { field, file }[]` | Every non-empty `File` in a parsed form. | +| `saveUpload(file, options)` | `(File, SaveUploadOptions) => Promise` | Validates size/type, sanitizes the name, writes via `Bun.write`. Throws `UploadError`. | +| `sanitizeFilename(name)` | `(string) => string` | Strips separators, traversal, control/illegal chars; caps at 255. | +| `UploadError` | class | Thrown on rejected uploads. | + +`SaveUploadOptions`: `dir` (required), `maxBytes`, `allowedTypes` (MIME types +like `"image/png"` and/or extensions like `".png"`), `filename(file)`. +`SavedUpload` = `{ path, filename, size, type }`. + +### Streaming & SSE — `@wrnexus/core` + +| Export | Signature | Notes | +| ------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `streamResponse(source, init?)` | `(Iterable\|AsyncIterable, StreamResponseInit?) => Response` | Streaming `Response` from a chunk source (basis for streaming SSR). | +| `sse(source)` | `(Iterable\|AsyncIterable) => Response` | `text/event-stream` response. | + +`StreamResponseInit`: `status`, `headers`, `contentType` (default +`"text/html; charset=utf-8"`). `ServerSentEvent`: `{ data, event?, id?, retry? }`. + +### Realtime rooms — `@wrnexus/core` + +WebSocket rooms. A file in `app/realtime/` exports +`default defineRoom({ ... })` and is served at `ws://host/realtime/`. + +| Export | Signature | Notes | +| --------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------- | +| `defineRoom(handlers)` | `(RoomHandlers) => RoomDefinition` | Define a room. Export the result as `default`. | +| `isRoomDefinition(value)` | `(unknown) => boolean` | Type guard for a room definition. | +| `createRealtimeRegistry()` | `() => RealtimeRegistry` | Server-side connection manager mapping sockets ↔ rooms. | +| `bridgeRealtime(registry, bus, topic?)` | `(RealtimeRegistry, RealtimeBus, string?) => () => void` | Bridge broadcasts/`toUser` sends across processes via a pub/sub bus. | + +`RoomHandlers`: `authorize(info) => boolean` (gate before accept — return +`false` to reject with 403), `onConnect(client)`, `onMessage(client, message)` +(JSON auto-parsed), `onLeave(client)`. A handler receives a `RoomClient` with +`id`, `user`, `query`, `data`, `room`, and `send` / `broadcast` / +`to(id)` / `toUser(user)` / `close`. The `Room` API adds `state`, `clients()`, +`count()`, and `broadcast`. `RealtimeBus` is structurally satisfied by +`@wrnexus/pubsub`. Legacy `RealtimeHandler`/`RealtimeSocket` raw handlers are +still exported. Connection-targeted sends (`send`, `to(id)`) stay local; room +broadcasts and `toUser` cross the bridge. + +### Error pages — `@wrnexus/core` + +| Export | Signature | Notes | +| ------------------------------ | -------------------------------- | ----------------------------------------------------- | +| `renderError(err, mode)` | `(unknown, Mode) => Response` | Dev page (with stack) or generic prod page by `mode`. | +| `renderDevError(err, status?)` | `(unknown, number?) => Response` | Readable HTML error page including the stack trace. | +| `renderProdError(status?)` | `(number?) => Response` | Generic page that never leaks file paths. | +| `renderNotFound()` | `() => Response` | Simple 404 page. | + +`Mode` = `"development" | "production"`. + +### Security headers & CORS — `@wrnexus/core` + +| Export | Signature | Notes | +| -------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `withSecurityHeaders(req, res, mode, security?, nonce?)` | → `Response` | Applies CORS + CSP, HSTS, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, COOP, Trusted Types, and `extraHeaders`. | +| `createCorsPreflightResponse(req, security?)` | → `Response \| null` | Builds a `204`/`403` preflight response for CORS `OPTIONS` requests. | +| `isWebSocketOriginAllowed(req, security?)` | → `boolean` | Guards WS upgrades against cross-site hijacking (allows same-origin, configured CORS origins, and non-browser clients). | + +Config types: `SecurityConfig` (top-level), `CorsConfig`/`CorsOrigin`, +`ContentSecurityPolicyConfig`/`CspDirectiveValue`, `HstsConfig`, +`TrustedTypesConfig`, `PermissionsPolicyConfig`. WrNexus applies sensible +defaults (self-only CSP, `frame-ancestors 'none'`, restrictive Permissions-Policy, +HSTS in production, Trusted Types in production); each is individually +overridable or disable-able via `false`. + +### Storage: cookies, sessions, localStorage — `@wrnexus/core` + +These back the `ctx.cookies`, `ctx.session`, and `ctx.localStorage` fields. + +| Export | Kind | Notes | +| --------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `setSessionBackend(backend)` | fn | Swap the **sync** session persistence backend (`SessionBackend`) — e.g. `bun:sqlite`. Default is process-local memory. Call once at startup. | +| `loadSession(backend, options?)` | fn → `Middleware` | Back `ctx.session` with an **async** store (`AsyncSessionBackend`: `load`/`save`/`destroy`) — loads before the request, saves after. `options.ttlMs` default 24h. | +| `CookieStore` | type | `get`/`getAll`/`has`/`set(name, value, opts?)`/`delete`/`headers`. | +| `SessionStore` | type | `id`/`get`/`getAll`/`set`/`delete`/`regenerate`/`clear`. | +| `LocalStorageSnapshot` | type | Read-only view of the browser's localStorage sent via header for CSR bindings. | +| `CookieOptions` | type | `path`, `domain`, `maxAge`, `expires`, `httpOnly`, `secure`, `sameSite`. | +| `SessionEntry` / `SessionBackend` / `AsyncSessionBackend` | types | Session persistence contracts. | + +### Low-level security helpers — `@wrnexus/core` + +| Export | Signature | Notes | +| ----------------------------- | --------------------- | --------------------------------------------------- | +| `escapeHtml(value)` | `(string) => string` | Escape for HTML text/attributes. | +| `isSafeIslandName(name)` | `(string) => boolean` | Allow only a conservative `[A-Za-z0-9_-]+` charset. | +| `isSafeRequestPath(pathname)` | `(string) => boolean` | Reject NULs, `..` traversal, and backslashes. | + +### JSX runtime — `@wrnexus/core`, `@wrnexus/core/jsx-runtime`, `@wrnexus/core/jsx-dev-runtime` + +A server-side JSX runtime that renders to HTML **strings** (no virtual DOM). +Point `tsconfig`'s `jsxImportSource` at `@wrnexus/core`. + +| Export | Kind | Notes | +| ------------------------------------------ | ------ | --------------------------------------------------------------------------------------- | +| `jsx` / `jsxs` | fn | The runtime factory (TypeScript calls these automatically). Returns an `Html` instance. | +| `Fragment` | symbol | JSX fragment marker. | +| `Html` | class | Wraps a raw, already-safe HTML string (`toString()` returns it). | +| `mustache(expr)` | fn | Emit a `{{expr}}` placeholder (tagged-template or string form) for the client binder. | +| `JSXComponent` / `JSXProps` / `Renderable` | types | Component signature and renderable value types. | + +Values interpolated as children are HTML-escaped unless they are an `Html` +instance; use `dangerouslySetInnerHTML={{ __html }}` for trusted markup. Void +elements render without a closing tag; `className`→`class`, `htmlFor`→`for`, and +`style` objects are serialized to CSS text. + +The subpath exports map to the runtime TypeScript's JSX transform expects: + +```jsonc +// tsconfig.json +{ + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "@wrnexus/core", + }, +} +``` + +## Usage + +### A minimal middleware chain + +```ts +import { + createContext, + withContextHeaders, + sessionAuth, + requireAuth, + requestLogger, + rateLimit, + csrfProtection, + type Middleware, +} from "@wrnexus/core"; + +const chain: Middleware[] = [ + requestLogger({ format: "json" }), + rateLimit({ max: 100, windowMs: 60_000 }), + csrfProtection(), + sessionAuth(), + requireAuth({ loginPath: "/login" }), +]; +``` + +### Password auth + +```ts +import { hashPassword, verifyPassword, logIn, getUser } from "@wrnexus/core"; + +// Registration +const passwordHash = await hashPassword(form.password); + +// Login +if (await verifyPassword(form.password, user.passwordHash)) { + logIn(ctx, { id: user.id, email: user.email }); +} + +const current = getUser<{ id: string }>(ctx); // or null +``` + +### HTTP caching with ETags + +```ts +import { etag, notModified, withCacheControl } from "@wrnexus/core"; + +const body = JSON.stringify(data); +const tag = etag(body); +if (notModified(ctx.req, tag)) { + return new Response(null, { status: 304, headers: { ETag: tag } }); +} +const res = new Response(body, { headers: { ETag: tag, "content-type": "application/json" } }); +return withCacheControl(res, { maxAge: 60, staleWhileRevalidate: 300 }); +``` + +### Streaming SSE + +```ts +import { sse } from "@wrnexus/core"; + +async function* ticks() { + for (let n = 0; ; n++) { + yield { event: "tick", data: String(n) }; + await Bun.sleep(1000); + } +} +export default (ctx) => sse(ticks()); +``` + +### A realtime room + +```ts +// app/realtime/chat.ts +import { defineRoom } from "@wrnexus/core"; + +export default defineRoom({ + authorize: (info) => !!info.user, // require auth + onConnect(client) { + client.user = client.query.user; + client.room.broadcast({ type: "join", id: client.id }); + }, + onMessage(client, msg) { + client.broadcast({ type: "say", from: client.id, text: msg.text }); + }, +}); +``` + +Scale it across processes: + +```ts +import { createRealtimeRegistry, bridgeRealtime } from "@wrnexus/core"; +import { createPubSub } from "@wrnexus/pubsub"; +import { redisDriver } from "@wrnexus/pubsub/redis"; + +const registry = createRealtimeRegistry(); +bridgeRealtime(registry, createPubSub(redisDriver(process.env.REDIS_URL))); +``` + +### JSX rendering + +```tsx +import { Html } from "@wrnexus/core"; + +function Card({ title, body }: { title: string; body: string }) { + return ( +
+

{title}

+

{body}

+
+ ); +} + +const html: Html = ; +return new Response(html.toString(), { headers: { "content-type": "text/html" } }); +``` + +## Requirements / Notes + +- **Bun-only.** Uses `Bun.password` (argon2id), `Bun.write`, web-standard + `Request`/`Response`/`FormData`/`ReadableStream`, and the global `crypto`. + Node is not supported. +- Session and rate-limit backends default to **process-local memory**. For + multi-instance deployments, swap in a shared backend: `setSessionBackend` (sync, + e.g. `bun:sqlite`) or `loadSession` (async, e.g. Redis) for sessions, a custom + `RateLimitStore` for limits, and `bridgeRealtime` for realtime. +- Works with the rest of the framework: realtime bridging is structurally + compatible with [`@wrnexus/pubsub`](../pubsub); the security, auth, and JSX + primitives here are consumed by the WrNexus server/router packages. +- Subpath exports: `@wrnexus/core/jsx-runtime` and `@wrnexus/core/jsx-dev-runtime` + for TypeScript's automatic JSX transform. + +### Exported TypeScript declarations + +```ts +export { Fragment, Html, Component as JSXComponent, Props as JSXProps, Renderable, jsx, jsxs, mustache } from './jsx-runtime.js'; + +interface Tenant { + id: string; + slug?: string; + name?: string; + metadata?: Record; +} +type TenantResolver = (ctx: Context) => Tenant | null | Promise; +interface TenantMiddlewareOptions { + required?: boolean; + status?: number; +} +declare function tenantMiddleware(resolveTenant: TenantResolver, options?: TenantMiddlewareOptions): Middleware; +declare function tenantFromSubdomain(lookup: (slug: string, ctx: Context) => Tenant | null | Promise, rootDomains?: string[]): TenantResolver; +declare function requireTenant(ctx: Context): Tenant; +/** Wrap a repository so every operation receives the current tenant id. */ +declare function tenantScope(tenant: Tenant, repository: T): T & { + tenantId: string; +}; + +interface SpanRecord { + name: string; + startTime: number; + endTime?: number; + durationMs?: number; + status?: "ok" | "error"; + attributes: Record; + error?: unknown; +} +interface Tracer { + startSpan(name: string, attributes?: SpanRecord["attributes"]): Span; + records(): readonly SpanRecord[]; +} +interface Span { + setAttribute(name: string, value: string | number | boolean): void; + end(status?: "ok" | "error", error?: unknown): SpanRecord; +} +declare function createTracer(clock?: () => number): Tracer; +declare function withSpan(tracer: Tracer, name: string, run: (span: Span) => T | Promise, attributes?: SpanRecord["attributes"]): Promise; +interface TracingMiddlewareOptions { + /** Include W3C Server-Timing response headers. Defaults to true. */ + serverTiming?: boolean; + /** Fraction of requests to trace, from 0 to 1. Defaults to 1. */ + sampleRate?: number; + /** Called after a traced response completes. */ + onComplete?: (ctx: Context, records: readonly SpanRecord[]) => void | Promise; +} +declare function tracingMiddleware(tracerFactory?: (ctx: Context) => Tracer, options?: TracingMiddlewareOptions): Middleware; + +interface CookieOptions { + path?: string; + domain?: string; + maxAge?: number; + expires?: Date | string; + httpOnly?: boolean; + secure?: boolean; + sameSite?: "Strict" | "Lax" | "None" | "strict" | "lax" | "none"; +} +interface CookieStore { + get(name: string): string | undefined; + getAll(): Record; + has(name: string): boolean; + set(name: string, value: string, options?: CookieOptions): void; + delete(name: string, options?: CookieOptions): void; + headers(): string[]; +} +interface SessionStore { + id(): string; + get(key: string): T | undefined; + getAll(): Record; + set(key: string, value: unknown): void; + delete(key: string): void; + /** Issue a fresh session id, keeping the data — defends against fixation. */ + regenerate(): void; + clear(): void; +} +interface LocalStorageSnapshot { + get(key: string): string | undefined; + getAll(): Record; + has(key: string): boolean; +} +/** A stored session: its data plus an absolute expiry timestamp (ms). */ +interface SessionEntry { + data: Record; + expiresAt: number; +} +/** + * Pluggable session persistence. The default is process-local memory; swap in a + * shared backend (Redis, SQL, etc.) via `setSessionBackend` so sessions survive + * restarts and work across multiple instances. Methods are synchronous, so a + * backend must be sync (e.g. `bun:sqlite`); async stores need a load/save + * wrapper around the request (future work). + */ +interface SessionBackend { + get(id: string): SessionEntry | undefined; + set(id: string, entry: SessionEntry): void; + delete(id: string): void; + /** Optional: drop expired entries. Called periodically by the store. */ + gc?(now: number): void; +} +/** Replace the session persistence backend (call once at startup). */ +declare function setSessionBackend(backend: SessionBackend): void; +/** + * An ASYNC session store (Redis, a remote DB). Use it via the `loadSession` + * middleware, which loads the session before the request and saves it after — + * keeping the `ctx.session` API synchronous while persistence is shared across + * instances. + */ +interface AsyncSessionBackend { + load(id: string): Promise; + save(id: string, entry: SessionEntry): Promise; + destroy(id: string): Promise; +} +/** + * Back `ctx.session` with an async store. Register early (before anything reads + * `ctx.session`). Loads once at the start of the request and saves once at the + * end; regenerate/clear destroy the old id. + */ +declare function loadSession(backend: AsyncSessionBackend, options?: { + ttlMs?: number; +}): Middleware; + +/** + * Core request context and middleware contracts. + * + * The `Context` object is the single value that flows through middleware, + * pages and API routes. It is intentionally small and framework-agnostic so + * it can later be reused by the `.wrn` compiler output. + */ + +/** Translate a key for the active language, interpolating `{param}` placeholders. */ +type TFunction = (key: string, params?: Record) => string; +type Context = { + /** The raw incoming web-standard Request. */ + req: Request; + /** Parsed URL of the request (pathname, query, etc.). */ + url: URL; + /** Active language for this request (resolved by the runtime); "" if i18n is unused. */ + lang: string; + /** Translate a key for the active language (identity until the runtime sets it). */ + t: TFunction; + /** Dynamic route params, e.g. `/users/[id]` -> `{ id: "42" }`. */ + params: Record; + /** + * Per-request scratch space. Middleware can attach values here + * (e.g. the authenticated user) and downstream handlers can read them. + */ + locals: Record; + /** + * The authenticated user for this request, or null when anonymous. Populated + * by the `sessionAuth` middleware (or `logIn`); read via `getUser(ctx)`. + */ + user?: unknown; + /** Active tenant/workspace resolved by tenant middleware. */ + tenant?: Tenant; + /** Request tracer installed by observability middleware. */ + tracer?: Tracer; + /** + * The direct socket peer IP, set by the server from `server.requestIP`. This + * is NOT spoofable by request headers — prefer it over `x-forwarded-for` for + * rate limiting unless you run behind a trusted proxy. + */ + ip?: string; + /** Read/write HTTP cookies for the current response. */ + cookies: CookieStore; + /** In-memory cookie-backed session store. */ + session: SessionStore; + /** Read-only localStorage snapshot sent by the browser for CSR data bindings. */ + localStorage: LocalStorageSnapshot; +}; +/** Calls the next middleware in the chain (or the final route handler). */ +type Next = () => Promise | Response; +/** + * Middleware runs before pages and API routes. It can: + * - inspect/modify `ctx` + * - short-circuit by returning a `Response` without calling `next()` + * - continue by returning `await next()` + */ +type Middleware = (ctx: Context, next: Next) => Promise | Response; +/** SEO metadata rendered into the document ``. */ +type SeoConfig = { + /** BCP 47 document language used on `` (default: `en`). */ + lang?: string; + title?: string; + titleTemplate?: string; + description?: string; + canonical?: string; + canonicalBase?: string; + robots?: string; + keywords?: string | string[]; + image?: string; + siteName?: string; + type?: string; + locale?: string; + twitterCard?: string; + twitterSite?: string; + themeColor?: string; +}; +/** Page metadata rendered into the document ``. */ +type PageMeta = SeoConfig; +/** A page module's default export. Returns an HTML string for the body. */ +type PageComponent = (ctx: Context) => string | Promise; +/** Create a fresh context for an incoming request. */ +declare function createContext(req: Request, url: URL): Context; +/** Apply headers accumulated on the context, such as Set-Cookie. */ +declare function withContextHeaders(ctx: Context, res: Response): Response; + +/** + * Small, dependency-free security helpers shared across packages. + */ +/** + * Escape a string for safe interpolation into HTML text or attributes. + * Used for page metadata (title/description) so untrusted values can't + * break out of an attribute or inject markup. + */ +declare function escapeHtml(value: string): string; +declare function isSafeIslandName(name: string): boolean; +/** + * Reject obvious path-traversal in a request path before it is ever used to + * resolve a file. The router never builds file paths from request input + * (routes are resolved against a pre-scanned table), but this is a cheap + * defense-in-depth guard. + */ +declare function isSafeRequestPath(pathname: string): boolean; + +/** + * CSRF protection via the double-submit cookie pattern. + * + * The framework sets a readable `wire-csrf` cookie on page loads; the client + * echoes it in an `x-csrf-token` header on unsafe requests (the Wire UI form + * runtime does this automatically). The server checks header === cookie. A + * cross-site attacker can't read the cookie to forge the header, so the request + * is rejected — while same-origin requests pass. + */ + +declare const CSRF_COOKIE = "wire-csrf"; +declare const CSRF_HEADER = "x-csrf-token"; +/** Ensure the CSRF cookie exists (readable by JS) and return its token. */ +declare function csrfToken(ctx: Context): string; +/** + * Verify an unsafe request's CSRF token against the cookie. Safe methods + * (GET/HEAD/OPTIONS) always pass. The token may arrive in the `x-csrf-token` + * header or a `_csrf` field already parsed onto `ctx.locals`. + */ +declare function verifyCsrf(ctx: Context): boolean; +/** Middleware that 403s unsafe requests with a missing/mismatched CSRF token. */ +declare function csrfProtection(): Middleware; + +/** + * Authentication primitives. + * + * Passwords are hashed with argon2id via `Bun.password`. Sessions ride on the + * existing cookie-backed `SessionStore`: logging a user in stores a serializable + * user object under the "user" key, and `sessionAuth` hydrates `ctx.user` from + * it on every request. `requireAuth` is a guard middleware for protected routes. + */ + +/** Session key under which the authenticated user is stored. */ +declare const SESSION_USER_KEY = "user"; +/** Hash a plaintext password (argon2id). Store the returned string. */ +declare function hashPassword(password: string): Promise; +/** Verify a plaintext password against a stored hash. Safe against bad hashes. */ +declare function verifyPassword(password: string, hash: string): Promise; +/** Persist the authenticated user in the session and on the context. */ +declare function logIn(ctx: Context, user: U): void; +/** Clear the session and forget the current user. */ +declare function logOut(ctx: Context): void; +/** + * The currently-authenticated user, or null. Reads `ctx.user` first (set by + * `sessionAuth`/`logIn`), falling back to the session store. + */ +declare function getUser(ctx: Context): U | null; +/** + * Hydrate `ctx.user` from the session for every request. Register this early in + * the middleware chain so downstream pages and API routes can read `ctx.user`. + */ +declare function sessionAuth(): Middleware; +interface RequireAuthOptions { + /** Where to redirect unauthenticated page requests. Default "/login". */ + loginPath?: string; +} +/** + * Guard that requires an authenticated user. Unauthenticated requests that look + * like an API/fetch call get a 401 JSON response; page navigations get a 302 + * redirect to the login page with the original target preserved as `?next=`. + */ +declare function requireAuth(options?: RequireAuthOptions): Middleware; + +/** + * Fixed-window rate limiting middleware. Keeps an in-memory counter per key + * (client IP by default, read from `x-forwarded-for` / `x-real-ip`) and rejects + * requests over the limit with a 429 and a `Retry-After` header. Sets the + * `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` headers. + * + * The store is process-local; behind multiple instances use a shared store + * (out of scope here). Suitable as-is for single-process apps and dev. + */ + +interface RateLimitOptions { + /** Window length in milliseconds. Default 60_000 (1 minute). */ + windowMs?: number; + /** Max requests allowed per key per window. Default 60. */ + max?: number; + /** Derive the bucket key from the request. Default: client IP. */ + key?: (ctx: Context) => string; + /** + * Trust `x-forwarded-for` / `x-real-ip` for the client IP. Default false — + * those headers are attacker-spoofable, so by default we key on the direct + * socket peer (`ctx.ip`). Enable ONLY when behind a proxy that overwrites + * these headers (nginx, a load balancer, Cloudflare). + */ + trustProxy?: boolean; + /** Body returned on 429. Default "Too Many Requests". */ + message?: string; + /** Emit RateLimit-* headers. Default true. */ + headers?: boolean; + /** Persistence for the counters. Default: process-local memory. */ + store?: RateLimitStore; + /** Maximum in-memory keys before oldest buckets are evicted. Ignored for custom stores. */ + maxKeys?: number; +} +interface Bucket { + count: number; + resetAt: number; +} +/** + * Pluggable rate-limit counter store. The default is process-local memory; swap + * in a shared store (Redis/SQL) so limits hold across instances. `hit` records + * one request for `key` in the current window and returns the running bucket. + * It may be async (e.g. a Redis INCR + PEXPIRE) — the middleware awaits it. + */ +interface RateLimitStore { + hit(key: string, windowMs: number, now: number): Bucket | Promise; +} +declare function rateLimit(options?: RateLimitOptions): Middleware; +/** Non-spoofable key: the direct socket peer IP (set by the server). */ +declare function peerKey(ctx: Context): string; +/** Proxy-aware key: trusts `x-forwarded-for` / `x-real-ip`, else the peer IP. */ +declare function proxyKey(ctx: Context): string; +/** @deprecated Use `peerKey` (default) or `proxyKey`. Kept for compatibility. */ +declare const defaultKey: typeof proxyKey; + +/** + * Structured request logging middleware. Emits one record per request with a + * request id, method, path, status, and duration — as pretty text (dev) or JSON + * (production/log aggregation). The request id is stored on `ctx.locals` so + * downstream handlers can correlate their own logs. + */ + +interface RequestRecord { + time: string; + id: string; + method: string; + path: string; + status: number; + durationMs: number; +} +interface RequestLoggerOptions { + /** "pretty" (default) for humans, "json" for machines. */ + format?: "pretty" | "json"; + /** Where each finished record goes. Default console.log. */ + sink?: (line: string, record: RequestRecord) => void; + /** ctx.locals key for the request id. Default "requestId". */ + requestIdKey?: string; + /** Clock injection for tests. Default Date.now. */ + now?: () => number; +} +declare function requestLogger(options?: RequestLoggerOptions): Middleware; + +/** + * Caching primitives: + * - `TTLCache` — a small in-memory time-to-live cache with `getOrLoad`, for + * memoising expensive data (query results, computed pages). + * - HTTP helpers — `cacheControl` to build a directive, `withCacheControl` to + * apply it, and `etag` / `notModified` for conditional requests (304s). + */ +declare class TTLCache { + private readonly ttlMs; + private store; + private loading; + private revisions; + private generation; + constructor(ttlMs?: number); + get(key: string): V | undefined; + set(key: string, value: V, ttlMs?: number): void; + /** Return the cached value or compute, cache, and return it. */ + getOrLoad(key: string, loader: () => Promise | V, ttlMs?: number): Promise; + delete(key: string): void; + clear(): void; + get size(): number; +} +interface CacheControlOptions { + /** max-age in seconds. */ + maxAge?: number; + /** s-maxage (shared/CDN cache) in seconds. */ + sMaxAge?: number; + /** Mark private (per-user) rather than public. */ + private?: boolean; + /** no-store: never cache. Overrides other directives. */ + noStore?: boolean; + /** no-cache: revalidate before use. */ + noCache?: boolean; + /** stale-while-revalidate window in seconds. */ + staleWhileRevalidate?: number; + immutable?: boolean; +} +/** Build a Cache-Control header value from options. */ +declare function cacheControl(options: CacheControlOptions): string; +/** Apply a Cache-Control header to a response (returns the same response). */ +declare function withCacheControl(res: Response, options: CacheControlOptions): Response; +/** A stable, quoted ETag for a string/bytes body (FNV-1a, weak by default). */ +declare function etag(body: string | ArrayBuffer | Uint8Array, weak?: boolean): string; +/** True when the request's If-None-Match matches the given ETag (send a 304). */ +declare function notModified(req: Request, tag: string): boolean; + +/** + * File upload helpers. Bun parses `multipart/form-data` natively via + * `Request.formData()`, yielding web `File` objects; these helpers validate and + * persist them safely (size/type limits, filename sanitisation to prevent path + * traversal). + */ +declare class UploadError extends Error { + constructor(message: string); +} +interface SaveUploadOptions { + /** Destination directory. */ + dir: string; + /** Reject files larger than this many bytes. */ + maxBytes?: number; + /** Allowed MIME types (e.g. "image/png") and/or extensions (e.g. ".png"). */ + allowedTypes?: string[]; + /** Choose the stored filename. Default: the sanitised original name. */ + filename?: (file: File) => string; +} +interface SavedUpload { + path: string; + filename: string; + size: number; + type: string; +} +/** All `File` values in a parsed form, with their field names. */ +declare function collectUploads(form: FormData): { + field: string; + file: File; +}[]; +/** Validate and write one uploaded file to disk. Throws `UploadError` on reject. */ +declare function saveUpload(file: File, options: SaveUploadOptions): Promise; +/** Strip directory separators, traversal, and control chars from a filename. */ +declare function sanitizeFilename(name: string): string; + +/** + * Streaming response primitives. + * + * `streamResponse` turns a (sync or async) iterable of strings/bytes into a + * streaming `Response` — the basis for streaming SSR (send the shell, then flush + * page chunks as they render) and any progressively-generated output. `sse` + * builds a Server-Sent Events stream from an async iterable of events. + * + * API routes and pages can already return a `Response` with a `ReadableStream` + * body and the framework streams it unbuffered; these helpers just make the + * common cases ergonomic. + */ +interface StreamResponseInit { + status?: number; + headers?: HeadersInit; + /** Content-Type; default "text/html; charset=utf-8". */ + contentType?: string; +} +type Chunk = string | Uint8Array; +type ChunkSource = Iterable | AsyncIterable; +/** Build a streaming Response from an (async) iterable of chunks. */ +declare function streamResponse(source: ChunkSource, init?: StreamResponseInit): Response; +interface ServerSentEvent { + data: string; + event?: string; + id?: string; + /** Client reconnection hint in milliseconds. */ + retry?: number; +} +/** Build a Server-Sent Events (text/event-stream) Response from events. */ +declare function sse(source: Iterable | AsyncIterable): Response; + +/** + * Realtime rooms. + * + * A file in `app/realtime/` exports `default defineRoom({ onConnect, onMessage, + * onLeave })` and is served at `ws://host/realtime/`. The framework's + * client runtime (`/__wrnexus/realtime.js`) handles the browser side, so pages + * ship NO hand-written WebSocket code. + * + * Handlers get a `RoomClient` with everything you need: + * client.send(msg) → this connection + * client.broadcast(msg) → everyone else in the room + * client.room.broadcast(msg) → everyone (incl. sender) + * client.to(id | ids).send(msg) → specific connection(s) + * client.toUser(u | users).send() → a user / selected users (all their tabs) + * client.user = "u1" → identify a connection for targeting + * client.data / client.room.state → per-connection / shared room state + * + * The dynamic route `app/realtime/[room].ts` gives one handler many independent + * rooms — `/realtime/lobby` and `/realtime/game-7` are separate room instances. + */ +interface RawSocket { + send(data: string): unknown; + close(code?: number, reason?: string): void; +} +interface RealtimeSocket { + readonly data: Data; + send(data: string | Uint8Array): number; + subscribe(topic: string): void; + unsubscribe(topic: string): void; + publish(topic: string, data: string | Uint8Array): number; + isSubscribed(topic: string): boolean; + close(code?: number, reason?: string): void; +} +interface RealtimeHandler { + open?(ws: RealtimeSocket): void | Promise; + message?(ws: RealtimeSocket, message: string | Uint8Array): void | Promise; + close?(ws: RealtimeSocket, code?: number, reason?: string): void | Promise; + drain?(ws: RealtimeSocket): void | Promise; +} +interface Target { + /** Send a message (objects are JSON-serialized). */ + send(message: unknown): void; +} +interface Room> { + readonly name: string; + /** Shared, in-memory room state (lives while ≥1 client is connected). */ + readonly state: Record; + /** All connected clients. */ + clients(): RoomClient[]; + /** Number of connected clients. */ + count(): number; + /** Send to everyone in the room, including the sender. */ + broadcast(message: unknown): void; + /** Target specific connection id(s). */ + to(id: string | string[]): Target; + /** Target a user / users by identity (reaches all their connections). */ + toUser(user: string | string[]): Target; +} +interface RoomClient> { + /** Unique per connection (a tab). */ + readonly id: string; + /** App identity for targeting; assign it in `onConnect`. */ + user: string | undefined; + /** Query params from the connection URL. */ + readonly query: Record; + /** Per-connection scratch state. */ + readonly data: TData; + readonly room: Room; + /** Send to THIS connection. */ + send(message: unknown): void; + /** Send to everyone else in the room. */ + broadcast(message: unknown): void; + /** Target specific connection id(s). */ + to(id: string | string[]): Target; + /** Target a user / users by identity. */ + toUser(user: string | string[]): Target; + /** Close this connection. */ + close(code?: number, reason?: string): void; +} +/** Info available when authorizing a connection, before it is accepted. */ +interface RoomAuthInfo { + /** Authenticated session user id, or `?user=` — undefined when anonymous. */ + user?: string; + /** Connection URL query params. */ + query: Record; + /** The upgrade request's headers (cookies, etc.). */ + headers: Headers; +} +interface RoomHandlers> { + /** + * Gate the connection BEFORE it is accepted. Return false to reject the + * upgrade with 403 (e.g. `authorize: (info) => !!info.user` to require auth). + */ + authorize?(info: RoomAuthInfo): boolean | Promise; + /** A client connected (a new tab joined the room). */ + onConnect?(client: RoomClient): void | Promise; + /** A message arrived (JSON is parsed; non-JSON arrives as a string). */ + onMessage?(client: RoomClient, message: any): void | Promise; + /** A client disconnected. */ + onLeave?(client: RoomClient): void | Promise; +} +interface RoomDefinition> { + readonly __wrnexusRoom: true; + readonly handlers: RoomHandlers; +} +/** Define a realtime room. Export the result as the `default` of a realtime file. */ +declare function defineRoom>(handlers: RoomHandlers): RoomDefinition; +declare function isRoomDefinition(value: unknown): value is RoomDefinition; +interface RealtimeConnectMeta { + room: string; + def: RoomDefinition; + query?: Record; + user?: string; +} +/** One cross-instance message: a room broadcast, or a targeted user send. */ +interface RealtimeEnvelope { + room: string; + /** If set, deliver only to these user identities; otherwise the whole room. */ + users?: string[]; + message: unknown; +} +/** + * A pub/sub bridge for horizontal scaling. Wire the registry to a shared bus + * (Redis pub/sub, NATS, …): local broadcasts/`toUser` sends are published to + * peers, and messages received from peers are delivered via `registry.deliver`. + * Connection-targeted sends (`send`, `to(id)`) stay local (ids are per-process). + */ +interface RealtimeBridge { + publish(envelope: RealtimeEnvelope): void; +} +interface RealtimeRegistry { + open(socket: RawSocket, meta: RealtimeConnectMeta): void | Promise; + message(socket: RawSocket, raw: string | Uint8Array): void | Promise; + close(socket: RawSocket): void | Promise; + /** Attach a cross-instance bridge (call once at startup). */ + setBridge(bridge: RealtimeBridge): void; + /** Deliver an envelope received from a peer to LOCAL connections only. */ + deliver(envelope: RealtimeEnvelope): void; + /** Number of live connections (across all rooms) — for tests/metrics. */ + size(): number; +} +/** Create the registry that maps sockets ↔ rooms and drives room handlers. */ +declare function createRealtimeRegistry(): RealtimeRegistry; +/** + * A minimal pub/sub bus (structurally satisfied by `@wrnexus/pubsub`). Used to + * bridge realtime broadcasts across processes without a hard dependency. + */ +interface RealtimeBus { + publish(topic: string, message: unknown): void | Promise; + subscribe(topic: string, handler: (message: unknown, topic: string) => void): () => void; +} +/** + * Bridge a realtime registry across processes/instances via a pub/sub bus (use + * the Redis driver so it crosses machines). After this, `client.room.broadcast` + * and `client.toUser(...)` reach connected clients on **every** app process/ + * instance subscribed to the same bus — the foundation for realtime that works + * with multiple running apps behind the gateway. Connection-targeted sends + * (`send`, `to(id)`) stay local. Returns an unsubscribe function. + * + * import { createRealtimeRegistry, bridgeRealtime } from "@wrnexus/core"; + * import { createPubSub } from "@wrnexus/pubsub"; + * import { redisDriver } from "@wrnexus/pubsub/redis"; + * bridgeRealtime(registry, createPubSub(redisDriver(process.env.REDIS_URL))); + */ +declare function bridgeRealtime(registry: RealtimeRegistry, bus: RealtimeBus, topic?: string): () => void; + +/** + * Error + status pages. Every page here is a self-contained HTML document — + * inline CSS only, no external stylesheet, no JavaScript (so it renders under the + * strict CSP, even when the app's assets are what failed). Theme-aware via + * `prefers-color-scheme`, styled in the WrNexus design language (ink-navy, + * azure, a faint blueprint grid + glow). Development shows the stack trace; + * production never leaks internal paths. + */ +type Mode = "development" | "production"; +/** A beautiful, self-contained HTML page for any 4xx/5xx status. */ +declare function renderStatusPage(status: number): Response; +/** Readable, styled development error page — includes the stack trace. */ +declare function renderDevError(err: unknown, status?: number): Response; +/** Generic production error page — no stack, no file paths. */ +declare function renderProdError(status?: number): Response; +/** Pick the right error page for the current mode. */ +declare function renderError(err: unknown, mode: Mode): Response; +/** Beautiful 404 page. */ +declare function renderNotFound(): Response; + +type CorsOrigin = "*" | string | string[]; +interface CorsConfig { + /** Enable CORS headers and preflight handling. Defaults to false. */ + enabled?: boolean; + /** Allowed origins. Use "*" for public APIs. Defaults to "*". */ + origin?: CorsOrigin; + /** Allowed methods for preflight responses. */ + methods?: string[]; + /** Allowed request headers. Defaults to the browser's requested headers. */ + allowedHeaders?: string[]; + /** Response headers exposed to browser JavaScript. */ + exposedHeaders?: string[]; + /** Whether to send Access-Control-Allow-Credentials. */ + credentials?: boolean; + /** Access-Control-Max-Age, in seconds. */ + maxAge?: number; +} +type CspDirectiveValue = string | string[] | false | null | undefined; +interface ContentSecurityPolicyConfig { + /** Defaults to true. */ + enabled?: boolean; + /** Use Content-Security-Policy-Report-Only instead of enforcing. */ + reportOnly?: boolean; + /** Merge or remove directives. Set a directive to false/null to remove it. */ + directives?: Record; + /** Set false to start from an empty policy instead of WrNexus defaults. */ + useDefaults?: boolean; +} +interface HstsConfig { + /** Defaults to true in production, false in development. */ + enabled?: boolean; + /** Defaults to 31536000 seconds (1 year). */ + maxAge?: number; + /** Defaults to true. */ + includeSubDomains?: boolean; + /** Defaults to true. */ + preload?: boolean; +} +interface TrustedTypesConfig { + /** Defaults to true in production, false in development. */ + enabled?: boolean; + /** + * Defaults to ["*"] in production so browser extensions and dev tooling can + * create their own policies without noisy console errors. Set this to a + * concrete list, e.g. ["wrnexus", "default"], for stricter deployments. + */ + policyNames?: string[]; + /** Defaults to true. */ + requireForScript?: boolean; + /** Adds "allow-duplicates" to the trusted-types directive. */ + allowDuplicates?: boolean; +} +type PermissionsPolicyConfig = Record; +interface SecurityConfig { + /** Set false to skip all framework security headers except explicitly enabled CORS. */ + headers?: boolean; + /** + * Trust `X-Forwarded-Proto` / `X-Forwarded-Host` when building `ctx.url` — set + * this when the app runs behind a TLS-terminating reverse proxy (nginx, the + * WrNexus gateway, a load balancer). Without it, a proxied app sees the internal + * `http://` request and marks cookies (e.g. CSRF/session) non-`Secure`. Default + * false; enable ONLY when a trusted proxy actually sets these headers. + */ + trustProxy?: boolean; + cors?: boolean | CorsConfig; + contentSecurityPolicy?: false | ContentSecurityPolicyConfig; + hsts?: false | HstsConfig; + trustedTypes?: false | TrustedTypesConfig; + /** Defaults to "same-origin". */ + crossOriginOpenerPolicy?: false | "same-origin" | "same-origin-allow-popups" | "unsafe-none"; + /** Defaults to "DENY". */ + frameOptions?: false | "DENY" | "SAMEORIGIN"; + /** Defaults to "strict-origin-when-cross-origin". */ + referrerPolicy?: false | string; + /** Defaults to a restrictive browser capability policy. */ + permissionsPolicy?: false | PermissionsPolicyConfig; + /** Extra static headers applied last. */ + extraHeaders?: Record; +} +/** + * Guard a WebSocket upgrade against Cross-Site WebSocket Hijacking: browsers + * always send an `Origin` header on a WS handshake, and — unlike fetch — WS is + * NOT subject to CORS, so cookies would otherwise flow cross-site. We allow + * same-origin (Origin host === Host header), configured CORS origins, and + * non-browser clients (no Origin, which also carry no ambient cookies). + */ +declare function isWebSocketOriginAllowed(req: Request, security?: SecurityConfig): boolean; +declare function createCorsPreflightResponse(req: Request, security?: SecurityConfig): Response | null; +/** + * Build the request URL, honoring `X-Forwarded-Proto` / `X-Forwarded-Host` when + * `trustProxy` is set (app behind a TLS-terminating reverse proxy). This makes + * `ctx.url.protocol` reflect the EXTERNAL scheme, so protocol-dependent logic — + * `Secure` cookies, canonical URLs — is correct behind nginx / the gateway. + * Security checks that compare the raw `Host`/`Origin` headers don't use this URL, + * so they are unaffected. An invalid forwarded value is ignored by the URL setter. + */ +declare function resolveRequestUrl(req: Request, trustProxy?: boolean): URL; +declare function withSecurityHeaders(req: Request, res: Response, mode: Mode, security?: SecurityConfig, nonce?: string): Response; + +interface SchemaLike { + parse(input: unknown): T; +} +interface EndpointErrorBody { + code: string; + message: string; + details?: unknown; +} +declare class EndpointError extends Error { + readonly status: number; + readonly code: string; + readonly details?: unknown | undefined; + constructor(status: number, code: string, message: string, details?: unknown | undefined); +} +interface EndpointDefinition { + input?: SchemaLike; + output?: SchemaLike; + auth?: "optional" | "required"; + description?: string; + tags?: string[]; + handler(input: I, ctx: Context): O | Promise; +} +interface DefinedEndpoint { + readonly definition: EndpointDefinition; + (ctx: Context, input?: unknown): Promise; +} +/** Define a validated, typed endpoint that can also drive SDK/OpenAPI generation. */ +declare function defineEndpoint(definition: EndpointDefinition): DefinedEndpoint; +interface RpcClientOptions { + baseUrl?: string; + fetch?: typeof globalThis.fetch; + headers?: HeadersInit | (() => HeadersInit | Promise); +} +/** Create a tiny typed RPC caller for endpoints exposed by a WrNexus app. */ +declare function createRpcClient(options?: RpcClientOptions): (path: string, input: I) => Promise; + +interface CachePolicy { + ttlMs?: number; + staleWhileRevalidateMs?: number; + tags?: string[] | ((ctx: Context) => string[]); +} +interface LoaderDefinition { + cache?: CachePolicy; + load(ctx: Context): T | Promise; +} +interface ActionDefinition { + csrf?: boolean; + run(input: I, ctx: Context): O | Promise; + invalidate?: string[] | ((output: O, ctx: Context) => string[]); +} +interface DefinedLoader { + readonly definition: LoaderDefinition; + (ctx: Context): Promise; +} +interface DefinedAction { + readonly definition: ActionDefinition; + (input: I, ctx: Context): Promise; +} +declare function defineLoader(definition: LoaderDefinition): DefinedLoader; +declare function defineAction(definition: ActionDefinition): DefinedAction; +/** Request-local fetch deduplication keyed by a stable string. */ +declare function dedupe(ctx: Context, key: string, load: () => T | Promise): Promise; + +type FeatureValue = boolean | string | number; +type FeatureRule = FeatureValue | ((ctx: Context) => FeatureValue | Promise); +interface FeatureFlags { + get(name: string, ctx: Context): Promise; + enabled(name: string, ctx: Context): Promise; +} +declare function defineFeatureFlags(rules: Record): FeatureFlags; + +interface PerformanceBudgets { + routeJsBytes?: number; + routeCssBytes?: number; + htmlBytes?: number; + imageBytes?: number; + hydrationMs?: number; + serverRenderMs?: number; +} +interface PerformanceMeasurement { + routeJsBytes?: number; + routeCssBytes?: number; + htmlBytes?: number; + imageBytes?: number; + hydrationMs?: number; + serverRenderMs?: number; +} +interface BudgetViolation { + metric: keyof PerformanceBudgets; + budget: number; + actual: number; + overBy: number; +} +declare function checkPerformanceBudgets(budgets: PerformanceBudgets, measurement: PerformanceMeasurement): BudgetViolation[]; + +interface ProblemDetails { + type: string; + title: string; + status: number; + detail?: string; + instance?: string; + code?: string; + [key: string]: unknown; +} +interface ProblemDetailsInput { + type?: string; + title: string; + status: number; + detail?: string; + instance?: string; + code?: string; + [key: string]: unknown; +} +declare function problem(details: ProblemDetailsInput, headers?: HeadersInit): Response; +type ServiceToken = string | symbol | { + readonly key: symbol; + readonly __type?: T; +}; +declare function serviceToken(description: string): ServiceToken; +declare class ServiceContainer { + #private; + private readonly parent?; + constructor(parent?: ServiceContainer | undefined); + set(token: ServiceToken, value: T): this; + has(token: ServiceToken): boolean; + get(token: ServiceToken): T; + tryGet(token: ServiceToken): T | undefined; + scope(): ServiceContainer; +} +type LifecyclePhase = "starting" | "started" | "stopping" | "stopped"; +type LifecycleHandler = (signal: AbortSignal) => void | Promise; +declare class ApplicationLifecycle { + #private; + on(phase: LifecyclePhase, handler: LifecycleHandler): () => void; + run(phase: LifecyclePhase): Promise; + get signal(): AbortSignal; +} +interface HealthCheckResult { + status: "up" | "down" | "degraded"; + message?: string; + details?: unknown; + durationMs?: number; +} +type HealthCheck = () => HealthCheckResult | Promise; +declare class HealthRegistry { + #private; + register(name: string, check: HealthCheck): () => void; + check(): Promise<{ + status: "up" | "down" | "degraded"; + checks: Record; + }>; +} +declare function requestId(headers: Headers, preferred?: string): string; +interface IdempotencyRecord { + key: string; + value: T; + expiresAt: number; +} +interface IdempotencyStore { + get(key: string): Promise | null>; + set(record: IdempotencyRecord): Promise; + delete(key: string): Promise; +} +declare function memoryIdempotencyStore(now?: () => number): IdempotencyStore; +declare function withIdempotency(store: IdempotencyStore, key: string, execute: () => Promise, ttlMs?: number): Promise<{ + value: T; + replayed: boolean; +}>; + +export { type ActionDefinition, ApplicationLifecycle, type AsyncSessionBackend, type Bucket, type BudgetViolation, CSRF_COOKIE, CSRF_HEADER, type CacheControlOptions, type CachePolicy, type ContentSecurityPolicyConfig, type Context, type CookieOptions, type CookieStore, type CorsConfig, type CorsOrigin, type CspDirectiveValue, type DefinedAction, type DefinedEndpoint, type DefinedLoader, type EndpointDefinition, EndpointError, type EndpointErrorBody, type FeatureFlags, type FeatureRule, type FeatureValue, type HealthCheck, type HealthCheckResult, HealthRegistry, type HstsConfig, type IdempotencyRecord, type IdempotencyStore, type LifecycleHandler, type LifecyclePhase, type LoaderDefinition, type LocalStorageSnapshot, type Middleware, type Mode, type Next, type PageComponent, type PageMeta, type PerformanceBudgets, type PerformanceMeasurement, type PermissionsPolicyConfig, type ProblemDetails, type ProblemDetailsInput, type RateLimitOptions, type RateLimitStore, type RawSocket, type RealtimeBridge, type RealtimeBus, type RealtimeConnectMeta, type RealtimeEnvelope, type RealtimeHandler, type RealtimeRegistry, type RealtimeSocket, type RequestLoggerOptions, type RequestRecord, type RequireAuthOptions, type Room, type RoomAuthInfo, type RoomClient, type RoomDefinition, type RoomHandlers, type RpcClientOptions, SESSION_USER_KEY, type SaveUploadOptions, type SavedUpload, type SchemaLike, type SecurityConfig, type SeoConfig, type ServerSentEvent, ServiceContainer, type ServiceToken, type SessionBackend, type SessionEntry, type SessionStore, type Span, type SpanRecord, type StreamResponseInit, type TFunction, TTLCache, type Target, type Tenant, type TenantMiddlewareOptions, type TenantResolver, type Tracer, type TrustedTypesConfig, UploadError, bridgeRealtime, cacheControl, checkPerformanceBudgets, collectUploads, createContext, createCorsPreflightResponse, createRealtimeRegistry, createRpcClient, createTracer, csrfProtection, csrfToken, dedupe, defaultKey, defineAction, defineEndpoint, defineFeatureFlags, defineLoader, defineRoom, escapeHtml, etag, getUser, hashPassword, isRoomDefinition, isSafeIslandName, isSafeRequestPath, isWebSocketOriginAllowed, loadSession, logIn, logOut, memoryIdempotencyStore, notModified, peerKey, problem, proxyKey, rateLimit, renderDevError, renderError, renderNotFound, renderProdError, renderStatusPage, requestId, requestLogger, requireAuth, requireTenant, resolveRequestUrl, sanitizeFilename, saveUpload, serviceToken, sessionAuth, setSessionBackend, sse, streamResponse, tenantFromSubdomain, tenantMiddleware, tenantScope, tracingMiddleware, verifyCsrf, verifyPassword, withCacheControl, withContextHeaders, withIdempotency, withSecurityHeaders, withSpan }; +``` + +--- + +## @wrnexus/csr + +Documentation URL: https://wrnexusjs.dev/packages/csr + +# @wrnexus/csr + +> The browser-side client runtime for WrNexus — generic, self-contained JS that hydrates server-rendered pages with reactivity, client-side navigation, and realtime rooms. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/csr` holds the three client runtimes that WrNexus serves to the browser. Components are authored as `.wrn` files and rendered on the **server**; this package provides the single, generic runtime that **hydrates** that HTML in the browser — there are no per-component browser bundles. Each runtime is exported as a plain-JS string (no build step, no imports) intended to be served verbatim from a well-known URL: + +- **reactive** at `/__wrnexus/reactive.js` — reactive directives (`data-scope`, `data-text`, `data-for`, …) +- **nav** at `/__wrnexus/nav.js` — SPA-style client navigation with graceful fallback +- **realtime** at `/__wrnexus/realtime.js` — WebSocket "rooms", declarative or programmatic + +The package itself runs on the server (it just returns strings); the strings it returns run in the browser. A dev/prod server (see `@wrnexus/core`) is responsible for actually serving them. + +## Installation + +```bash +bun add @wrnexus/csr +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +All exports come from the package root (`@wrnexus/csr`). The runtime source is delivered as strings, so the "API" on the server side is small; the real surface is the browser directives/globals each string installs. + +### Runtime strings + +| Export | Type | Served at | Contents | +| ------------------ | -------- | ------------------------ | ------------------------------ | +| `REACTIVE_RUNTIME` | `string` | `/__wrnexus/reactive.js` | Reactive directive runtime | +| `NAV_RUNTIME` | `string` | `/__wrnexus/nav.js` | Client-side navigation runtime | +| `REALTIME_RUNTIME` | `string` | `/__wrnexus/realtime.js` | Realtime rooms runtime | + +### Accessor functions + +Convenience getters that return the same strings. + +```ts +getReactiveRuntime(): string // → REACTIVE_RUNTIME +getNavRuntime(): string // → NAV_RUNTIME +getRealtimeRuntime(): string // → REALTIME_RUNTIME +``` + +### Browser: reactive directives + +Applied to any subtree containing `data-scope`. Expressions are parsed by a tiny eval-free evaluator, so a strict CSP with no `unsafe-eval` works. + +| Directive | Purpose | +| -------------------------------------------------------- | ----------------------------------------------------------------------- | +| `data-scope="count: 0, name: 'x'"` | Declare reactive state on a subtree | +| `data-on-="count++"` | Run a statement in scope on a DOM event | +| `data-text="expr"` | Bind an element's `textContent` to an expression | +| `data-show="expr"` | Toggle visibility (`display`) on truthiness | +| `data-for="item in list"` (opt. index and `key item.id`) | Per-item rendering; stable keys preserve DOM identity during reorder | +| `data-key="item.id"` | Alternative key declaration for `data-for` templates | +| `{{expr}}` or `{expr}` | Interpolation inside text nodes and attribute values | +| `data-wrnexus-csr="id"` | Target for a generated CSR fetch binding (fetches `/__wrnexus/csr?...`) | + +Supported expression features: literals, identifiers, member access (`a.b`, `a[b]`), function/method calls, arrays, objects, arithmetic, comparison, equality, logical (`&& ||`), unary (`! - +`), and ternary. Statements support `++`/`--`, assignment operators (`= += -= *= /= %=`), and bare expression/method calls. Rendering is dependency-tracked: a signal change only re-runs the renderers that actually read it. + +Browser globals installed: `window.__wrnexusHydrateScopes(root)` and `window.__wrnexusHydrateCsrFetches(root)` — both idempotent, so re-running after a DOM swap or HMR morph is safe. Both run automatically on `DOMContentLoaded`. + +### Browser: navigation + +Intercepts same-origin `` clicks, fetches the target page, and swaps the `#app` container in place (via `importNode` — not `innerHTML` — so it works under a Trusted-Types CSP), updating history, title, and scroll, then re-hydrates. Cross-origin links, modified clicks, `download`/`data-no-nav`/`rel="external"`/`target` links, non-HTML responses, or a missing `#app` fall back to a full browser navigation. + +- Programmatic navigation: `window.__wrnexusNavigate(url)` +- Emits a `wrnexus:navigated` `CustomEvent` (`detail.url`) after each swap +- Sends `x-wrnexus-nav: 1` on fetches so the server can return the page fragment +- Appends any `/__wrnexus/*` runtime scripts the incoming page needs but the current document lacks + +### Browser: realtime rooms + +Connects to `/realtime/` over WebSocket (`ws`/`wss` chosen from `location.protocol`). Two usage modes. + +Programmatic API via `window.wire`: + +```ts +wire.room(name): Room // open (or reuse) a room connection +wire.bindRooms(root?) // (re)bind declarative [data-room] containers + +interface Room { + name: string; + send(obj: object | string): Room; // JSON-stringifies objects; queues until open + on(type: string, cb): Room; // filter by msg.type; "*" or a fn = all messages + on(cb): Room; + close(): Room; +} +``` + +Internal lifecycle messages are emitted to listeners as `{ type }`: `__open`, `__close`, `__error`, and `__raw` (non-JSON frames, with `data`). Reconnect uses exponential backoff capped at 5s; queued sends flush on reconnect. + +Declarative binding (zero JS) on a `data-room=""` container: + +| Attribute | On | Purpose | +| ------------------------------------ | --------------- | ------------------------------------------------------------------------ | +| `data-room=""` | container | Connect to room `` | +| `data-room-user=""` | container | Identify the connection (`?user=`) | +| `data-room-log` | element | Where incoming messages are appended | +| `