Page not found
The address may be outdated or misspelled. No private or duplicate route is exposed here.
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 {
The address may be outdated or misspelled. No private or duplicate route is exposed here.
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.
bunx @wrnexus/cli@0.5.0 create my-appAccess approval, response time, licensing terms, and support level remain owner-controlled. This site does not collect access requests directly.
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.
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.
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.
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.
The gateway can dispatch multiple applications while preserving route, component, asset, config, and session boundaries. Validate host routing and isolation before production.
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 compatibility bridges SSR-safe Capacitor capabilities; native route generation is experimental and is not general web portability. Test each target platform.
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.
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.
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.
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.
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.
Production-ready, server-rendered components documented directly from the installed .wrn sources.
Showing {category} components
Theme-aware, responsive accordion component.
View component → -integrationsTheme-aware, responsive advanced date picker component.
View component → -integrationsTheme-aware, responsive advanced range slider component.
View component → -advanced-formsTheme-aware, responsive advanced select component.
View component → -baseTheme-aware, responsive alert component.
View component → -baseTheme-aware, responsive avatar component.
View component → -baseTheme-aware, responsive avatar group component.
View component → -baseTheme-aware, responsive badge component.
View component → -baseTheme-aware, responsive blockquote component.
View component → -navigationTheme-aware, responsive breadcrumb component.
View component → -baseTheme-aware, responsive button component.
View component → -baseTheme-aware, responsive button group component.
View component → -baseTheme-aware, responsive card component.
View component → -baseTheme-aware, responsive carousel component.
View component → -integrationsTheme-aware, responsive chart component.
View component → -baseTheme-aware, responsive chat bubble component.
View component → -formsTheme-aware, responsive checkbox component.
View component → -integrationsTheme-aware, responsive clipboard component.
View component → -baseTheme-aware, responsive collapse component.
View component → -formsTheme-aware, responsive color picker component.
View component → -layoutTheme-aware, responsive columns component.
View component → -advanced-formsEditable autocomplete combobox with local and remote suggestions.
View component → -integrationsTheme-aware, responsive confetti component.
View component → -layoutTheme-aware, responsive container component.
View component → -overlaysTheme-aware, responsive context menu component.
View component → -advanced-formsTheme-aware, responsive copy markup component.
View component → -layoutTheme-aware, responsive custom scrollbar component.
View component → -integrationsTheme-aware, responsive data map component.
View component → -integrationsTheme-aware, responsive data table component.
View component → -baseTheme-aware, responsive date picker component.
View component → -baseTheme-aware, responsive device frame component.
View component → -layoutTheme-aware, responsive divider component.
View component → -integrationsTheme-aware, responsive drag and drop component.
View component → -overlaysTheme-aware, responsive drawer component.
View component → -overlaysTheme-aware, responsive dropdown component.
View component → -formsTheme-aware, responsive file input component.
View component → -integrationsTheme-aware, responsive file upload component.
View component → -baseTheme-aware, responsive file upload progress component.
View component → -layoutTheme-aware, responsive grid component.
View component → -layoutTheme-aware, responsive image component.
View component → -formsTheme-aware, responsive input component.
View component → -formsTheme-aware, responsive input group component.
View component → -advanced-formsTheme-aware, responsive input number component.
View component → -layoutTheme-aware, responsive kbd component.
View component → -layoutTheme-aware, responsive layout splitter component.
View component → -baseTheme-aware, responsive legend indicator component.
View component → -layoutTheme-aware, responsive link component.
View component → -baseTheme-aware, responsive list component.
View component → -baseTheme-aware, responsive list group component.
View component → -integrationsTheme-aware, responsive map component.
View component → -baseTheme-aware, responsive marquee component.
View component → -navigationTheme-aware, responsive mega menu component.
View component → -overlaysTheme-aware, responsive modal component.
View component → -navigationTheme-aware, responsive nav component.
View component → -navigationTheme-aware, responsive navbar component.
View component → -navigationTheme-aware, responsive pagination component.
View component → -advanced-formsSecure multi-cell PIN and verification-code input with regex and paste support.
View component → -overlaysTheme-aware, responsive popover component.
View component → -baseTheme-aware, responsive progress component.
View component → -formsTheme-aware, responsive radio component.
View component → -formsTheme-aware, responsive range slider component.
View component → -baseTheme-aware, responsive rating component.
View component → -navigationTheme-aware, responsive scrollspy component.
View component → -advanced-formsTheme-aware, responsive search box component.
View component → -formsTheme-aware, responsive select component.
View component → -navigationTheme-aware, responsive sidebar component.
View component → -baseTheme-aware, responsive skeleton component.
View component → -baseTheme-aware, responsive spinner component.
View component → -navigationTheme-aware, responsive stepper component.
View component → -advanced-formsTheme-aware, responsive strong password component.
View component → -baseTheme-aware, responsive styled icon component.
View component → -formsTheme-aware, responsive switch component.
View component → -tablesTheme-aware, responsive table component.
View component → -navigationTheme-aware, responsive tabs component.
View component → -formsTheme-aware, responsive textarea component.
View component → -baseTheme-aware, responsive timeline component.
View component → -formsTheme-aware, responsive time picker component.
View component → -baseTheme-aware, responsive toast component.
View component → -integrationsTheme-aware, responsive toast notifications component.
View component → -advanced-formsTheme-aware, responsive toggle count component.
View component → -advanced-formsAccessible password field with optional show and hide controls.
View component → -overlaysTheme-aware, responsive tooltip component.
View component → -baseTheme-aware, responsive tree view component.
View component → -layoutTheme-aware, responsive typography component.
View component → -integrationsTheme-aware, responsive wysiwyg editor component.
View component →Theme-aware, responsive accordion component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
variant | string | Optional | "default" |
class | string | Optional | "" |
id | string | Optional | "accordion" |
items | string | Optional | [] |
defaultOpen | string | Optional | [] |
multiple | boolean | Optional | false |
alwaysOpen | boolean | Optional | false |
disabled | boolean | Optional | false |
indicator | string | Optional | "plus" |
indicatorPosition | string | Optional | "start" |
showIndicator | boolean | Optional | true |
bordered | boolean | Optional | false |
separated | boolean | Optional | false |
flush | boolean | Optional | false |
contentItalic | boolean | Optional | false |
This component does not declare a slot.
changeopencloseInstalled 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>
- }
-}
-Theme-aware, responsive advanced date picker component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Advanced Date Picker" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultinputchangeopencloseclearInstalled 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>
- }
-}
-Theme-aware, responsive advanced range slider component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Advanced Range Slider" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultinputchangestartendInstalled 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>
- }
-}
-Theme-aware, responsive advanced select component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Advanced Select" |
name | string | Optional | "" |
value | string | Optional | "" |
values | string | Optional | [] |
options | string | Optional | [] |
groups | string | Optional | [] |
placeholder | string | Optional | "Select an option" |
placeholderIcon | string | Optional | "" |
searchPlaceholder | string | Optional | "Search options…" |
multiple | boolean | Optional | false |
searchable | boolean | Optional | true |
defaultOpen | boolean | Optional | false |
clearable | boolean | Optional | true |
allowEmpty | boolean | Optional | true |
tags | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
invalid | boolean | Optional | false |
validationMessage | string | Optional | "" |
helpText | string | Optional | "" |
loading | boolean | Optional | false |
loadingLabel | string | Optional | "Loading options…" |
emptyLabel | string | Optional | "No options found" |
selectedOptionsLabel | string | Optional | "Selected options" |
clearLabel | string | Optional | "Clear selection" |
createLabel | string | Optional | "Create" |
loadMoreLabel | string | Optional | "Load more" |
searchMode | string | Optional | "contains" |
searchFields | string | Optional | "label,description" |
minSearchLength | number | Optional | 0 |
searchResultLimit | number | Optional | 0 |
maxSelections | number | Optional | 0 |
showCounter | boolean | Optional | false |
counterTemplate | string | Optional | "{selected} selected" |
optionTemplate | string | Optional | "default" |
selectedTemplate | string | Optional | "default" |
closeOnSelect | boolean | Optional | true |
scrollToSelected | boolean | Optional | true |
fixed | boolean | Optional | false |
placement | string | Optional | "bottom" |
remote | boolean | Optional | false |
remoteUrl | string | Optional | "" |
remoteQueryParam | string | Optional | "q" |
remoteDebounce | number | Optional | 250 |
remoteAutoLoad | boolean | Optional | true |
infinite | boolean | Optional | false |
hasMore | boolean | Optional | false |
page | number | Optional | 1 |
class | string | Optional | "" |
This component does not declare a slot.
searchselectchangeclearopencloseloaderrorInstalled 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>
- }
-}
-Theme-aware, responsive alert component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "info" |
variant | string | Optional | "soft" |
class | string | Optional | "" |
radius | string | Optional | "md" |
shadow | string | Optional | "sm" |
title | string | Optional | "Alert" |
description | string | Optional | "" |
items | string | Optional | [] |
actions | string | Optional | [] |
showIcon | boolean | Optional | false |
icon | string | Optional | "" |
dismissible | boolean | Optional | false |
dismissLabel | string | Optional | "Dismiss alert" |
role | string | Optional | "alert" |
live | string | Optional | "polite" |
linkLabel | string | Optional | "" |
linkHref | string | Optional | "" |
actionLabel | string | Optional | "" |
actionHref | string | Optional | "" |
compact | boolean | Optional | false |
This component does not declare a slot.
dismissactionInstalled 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>
- }
-}
-Theme-aware, responsive avatar group component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
items | string | Optional | [] |
size | string | Optional | "md" |
color | string | Optional | "primary" |
variant | string | Optional | "solid" |
shape | string | Optional | "circle" |
layout | string | Optional | "stack" |
maxVisible | number | Optional | 4 |
columns | number | Optional | 3 |
borderColor | string | Optional | "" |
showTooltips | boolean | Optional | true |
overflowLabel | string | Optional | "Show remaining members" |
class | string | Optional | "" |
This component does not declare a slot.
overflowInstalled 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>
- }
-}
-Theme-aware, responsive avatar component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
src | string | Optional | "" |
alt | string | Optional | "" |
initials | string | Optional | "" |
size | string | Optional | "md" |
color | string | Optional | "primary" |
variant | string | Optional | "solid" |
shape | string | Optional | "circle" |
status | string | Optional | "" |
statusLabel | string | Optional | "" |
statusPosition | string | Optional | "bottom" |
badge | string | Optional | "" |
badgeIcon | string | Optional | "" |
badgeLabel | string | Optional | "" |
tooltip | string | Optional | "" |
name | string | Optional | "" |
description | string | Optional | "" |
loading | string | Optional | "lazy" |
class | string | Optional | "" |
This component does not declare a slot.
loaderrorclickInstalled 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>
- }
-}
-Theme-aware, responsive badge component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
label | string | Optional | "Badge" |
size | string | Optional | "md" |
color | string | Optional | "primary" |
variant | string | Optional | "solid" |
shape | string | Optional | "pill" |
class | string | Optional | "" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
dot | boolean | Optional | false |
dotOnly | boolean | Optional | false |
dotLabel | string | Optional | "Status" |
animated | boolean | Optional | false |
avatarSrc | string | Optional | "" |
avatarAlt | string | Optional | "" |
dismissible | boolean | Optional | false |
dismissLabel | string | Optional | "Remove badge" |
truncate | boolean | Optional | false |
maxWidth | string | Optional | "12rem" |
anchorLabel | string | Optional | "" |
anchorIcon | string | Optional | "" |
placement | string | Optional | "inline" |
anchorLabelText | string | Optional | "Badge anchor" |
This component does not declare a slot.
dismissInstalled 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>
- }
-}
-Theme-aware, responsive blockquote component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
quote | string | Optional | "I just wanted to say that I'm very happy with my purchase so far. The documentation is outstanding - clear and detailed." |
citation | string | Optional | "" |
citationTitle | string | Optional | "" |
citationUrl | string | Optional | "" |
avatarSrc | string | Optional | "" |
avatarAlt | string | Optional | "" |
size | string | Optional | "md" |
color | string | Optional | "primary" |
align | string | Optional | "left" |
variant | string | Optional | "default" |
quoteMark | boolean | Optional | true |
italic | boolean | Optional | true |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive breadcrumb component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Breadcrumb" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultnavigateclickInstalled 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>
- }
-}
-Theme-aware, responsive button group component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
items | string | Optional | [] |
value | string | Optional | "" |
size | string | Optional | "md" |
color | string | Optional | "primary" |
variant | string | Optional | "default" |
orientation | string | Optional | "horizontal" |
responsive | boolean | Optional | false |
attached | boolean | Optional | true |
selectable | boolean | Optional | false |
toolbar | boolean | Optional | false |
disabled | boolean | Optional | false |
ariaLabel | string | Optional | "Button group" |
class | string | Optional | "" |
defaultclickselectchangeInstalled 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>
- }
-}
-Theme-aware, responsive button component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
label | string | Optional | "Button" |
loadingLabel | string | Optional | "Loading…" |
description | string | Optional | "" |
as | string | Optional | "" |
href | string | Optional | "" |
target | string | Optional | "" |
rel | string | Optional | "" |
type | string | Optional | "button" |
variant | string | Optional | "default" |
color | string | Optional | "primary" |
size | string | Optional | "default" |
disabled | boolean | Optional | false |
loading | boolean | Optional | false |
pill | boolean | Optional | false |
fullWidth | boolean | Optional | false |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
ariaLabel | string | Optional | "" |
ariaPressed | string | Optional | "" |
ariaExpanded | string | Optional | "" |
ariaControls | string | Optional | "" |
title | string | Optional | "" |
autofocus | boolean | Optional | false |
controlClass | string | Optional | "" |
class | string | Optional | "" |
defaultclickfocusblurInstalled 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>
- }
-}
-Theme-aware, responsive card component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
title | string | Optional | "Card title" |
subtitle | string | Optional | "" |
description | string | Optional | "" |
header | string | Optional | "" |
footer | string | Optional | "" |
imageSrc | string | Optional | "" |
imageAlt | string | Optional | "" |
imagePosition | string | Optional | "top" |
actionLabel | string | Optional | "" |
actionHref | string | Optional | "" |
headerActions | string | Optional | [] |
navigation | string | Optional | [] |
activeNav | string | Optional | "" |
mobileNavigation | boolean | Optional | false |
alertTitle | string | Optional | "" |
alertDescription | string | Optional | "" |
empty | boolean | Optional | false |
emptyTitle | string | Optional | "No data to show" |
emptyIcon | string | Optional | "icon-[lucide--inbox]" |
items | string | Optional | [] |
size | string | Optional | "md" |
color | string | Optional | "primary" |
variant | string | Optional | "default" |
layout | string | Optional | "vertical" |
align | string | Optional | "left" |
hover | string | Optional | "none" |
scrollable | boolean | Optional | false |
maxHeight | string | Optional | "18rem" |
dismissible | boolean | Optional | false |
ariaLabel | string | Optional | "" |
class | string | Optional | "" |
defaultclickactionnavigatedismissloaderrorInstalled 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>
- }
-}
-Theme-aware, responsive carousel component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "" |
description | string | Optional | "" |
items | string | Optional | [] |
activeIndex | number | Optional | 0 |
slidesPerView | number | Optional | 1 |
gap | string | Optional | "0.75rem" |
showPagination | boolean | Optional | false |
isAutoPlay | boolean | Optional | false |
autoplayInterval | number | Optional | 4000 |
isInfiniteLoop | boolean | Optional | false |
isRTL | boolean | Optional | false |
isCentered | boolean | Optional | false |
isDraggable | boolean | Optional | false |
isAutoHeight | boolean | Optional | false |
isSnap | boolean | Optional | false |
showCounter | boolean | Optional | false |
thumbnails | string | Optional | "none" |
ariaLabel | string | Optional | "Content carousel" |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultinitializechangepreviousnextplaypausereachStartreachEnddragStartdragEndInstalled 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>
- }
-}
-Theme-aware, responsive chart component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Chart" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselectdataPointClicklegendToggleInstalled 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>
- }
-}
-Theme-aware, responsive chat bubble component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "" |
description | string | Optional | "" |
items | string | Optional | [] |
oneSided | boolean | Optional | false |
showAvatars | boolean | Optional | false |
showMetadata | boolean | Optional | false |
ariaLabel | string | Optional | "Conversation" |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultactionmessageClickavatarClicklinkClickInstalled 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>
- }
-}
-Theme-aware, responsive checkbox component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Checkbox" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
value | string | Optional | "on" |
values | string | Optional | [] |
options | string | Optional | [] |
checked | boolean | Optional | false |
indeterminate | boolean | Optional | false |
orientation | string | Optional | "vertical" |
card | boolean | Optional | false |
rightAligned | boolean | Optional | false |
list | boolean | Optional | false |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurinvalidInstalled 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>
- }
-}
-Theme-aware, responsive clipboard component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Clipboard" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultcopysuccesserrorInstalled 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>
- }
-}
-Theme-aware, responsive collapse component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
items | string | Optional | [] |
multiple | boolean | Optional | false |
mode | string | Optional | "panel" |
initialOpenIndexes | string | Optional | [] |
ariaLabel | string | Optional | "Collapsible content" |
class | string | Optional | "" |
defaulttoggleopencloseInstalled 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>
- }
-}
-Theme-aware, responsive color picker component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Color" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
value | string | Optional | "#2563eb" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
variant | string | Optional | "normal" |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurInstalled 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>
- }
-}
-Theme-aware, responsive columns component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
columns | number | Optional | 2 |
gap | string | Optional | "md" |
maxWidth | string | Optional | "xl" |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Editable autocomplete combobox with local and remote suggestions.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "ComboBox" |
name | string | Optional | "" |
value | string | Optional | "" |
options | string | Optional | [] |
groups | string | Optional | [] |
placeholder | string | Optional | "Search or select an option" |
searchPlaceholder | string | Optional | "Start typing…" |
clearable | boolean | Optional | true |
allowCustomValue | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
invalid | boolean | Optional | false |
validationMessage | string | Optional | "" |
helpText | string | Optional | "" |
loading | boolean | Optional | false |
loadingLabel | string | Optional | "Loading suggestions…" |
emptyLabel | string | Optional | "No matching options" |
clearLabel | string | Optional | "Clear value" |
toggleLabel | string | Optional | "Toggle suggestions" |
searchMode | string | Optional | "contains" |
searchFields | string | Optional | "label,description" |
minSearchLength | number | Optional | 0 |
searchResultLimit | number | Optional | 0 |
optionTemplate | string | Optional | "default" |
defaultOpen | boolean | Optional | false |
closeOnSelect | boolean | Optional | true |
fixed | boolean | Optional | false |
placement | string | Optional | "bottom" |
autocomplete | string | Optional | "off" |
remote | boolean | Optional | false |
remoteUrl | string | Optional | "" |
remoteQueryParam | string | Optional | "q" |
remoteDebounce | number | Optional | 250 |
remoteAutoLoad | boolean | Optional | true |
infinite | boolean | Optional | false |
hasMore | boolean | Optional | false |
page | number | Optional | 1 |
loadMoreLabel | string | Optional | "Load more" |
class | string | Optional | "" |
This component does not declare a slot.
searchselectchangeclearopencloseloaderrorInstalled 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>
- }
-}
-Theme-aware, responsive confetti component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Confetti" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultstartcompleteInstalled 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>
- }
-}
-Theme-aware, responsive container component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
columns | number | Optional | 2 |
gap | string | Optional | "md" |
maxWidth | string | Optional | "xl" |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive context menu component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Context Menu" |
description | string | Optional | "" |
open | boolean | Optional | false |
placement | string | Optional | "bottom" |
closeLabel | string | Optional | "Close" |
class | string | Optional | "" |
defaultopencloseselectInstalled 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>
- }
-}
-Theme-aware, responsive copy markup component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Copy Markup" |
name | string | Optional | "" |
value | string | Optional | "" |
placeholder | string | Optional | "" |
type | string | Optional | "text" |
min | string | Optional | "" |
max | string | Optional | "" |
step | string | Optional | "" |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
copysuccesserrorInstalled 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>
- }
-}
-Theme-aware, responsive custom scrollbar component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
columns | number | Optional | 2 |
gap | string | Optional | "md" |
maxWidth | string | Optional | "xl" |
class | string | Optional | "" |
defaultscrollInstalled 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>
- }
-}
-Theme-aware, responsive data map component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Data Map" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselectchangeInstalled 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>
- }
-}
-Theme-aware, responsive data table component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
caption | string | Optional | "Data Table" |
columns | string | Optional | [] |
rows | string | Optional | [] |
striped | boolean | Optional | true |
class | string | Optional | "" |
defaultsortselectchangerowClickpageChangeInstalled 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>
- }
-}
-Theme-aware, responsive date picker component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Date Picker" |
id | string | Optional | "" |
name | string | Optional | "" |
value | string | Optional | "" |
placeholder | string | Optional | "" |
type | string | Optional | "date" |
locale | string | Optional | "en-US" |
firstDayOfWeek | number | Optional | 0 |
months | string | Optional | [{ 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 | string | Optional | [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 | string | Optional | [2024, 2025, 2026, 2027, 2028, 2029, 2030] |
min | string | Optional | "" |
max | string | Optional | "" |
step | string | Optional | "" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
variant | string | Optional | "normal" |
inline | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangeopenclosefocusblurinvalidInstalled 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>
- }
-}
-Theme-aware, responsive device frame component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Device Frame" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
device | string | Optional | "phone" |
orientation | string | Optional | "portrait" |
src | string | Optional | "" |
srcdoc | string | Optional | "" |
frameTitle | string | Optional | "Device preview" |
showToolbar | boolean | Optional | true |
allow | string | Optional | "" |
class | string | Optional | "" |
defaultchangerotateInstalled 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>
- }
-}
-Theme-aware, responsive divider component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
This component does not declare a slot.
This component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive drag and drop component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Drag And Drop" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultdragStartdragEnddragEnterdragLeavedropchangeInstalled 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>
- }
-}
-Theme-aware, responsive drawer component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Drawer" |
description | string | Optional | "" |
open | boolean | Optional | false |
placement | string | Optional | "bottom" |
closeLabel | string | Optional | "Close" |
class | string | Optional | "" |
defaultopencloseInstalled 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>
- }
-}
-Theme-aware, responsive dropdown component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Dropdown" |
description | string | Optional | "" |
open | boolean | Optional | false |
placement | string | Optional | "bottom" |
closeLabel | string | Optional | "Close" |
class | string | Optional | "" |
defaultopencloseselectchangeInstalled 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>
- }
-}
-Theme-aware, responsive file input component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "File" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "Choose a file" |
value | string | Optional | "" |
icon | string | Optional | "icon-[lucide--upload]" |
iconPosition | string | Optional | "start" |
accept | string | Optional | "" |
multiple | boolean | Optional | false |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
variant | string | Optional | "normal" |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurselectclearinvalidInstalled 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>
- }
-}
-Theme-aware, responsive file upload progress component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Progress" |
value | number | Optional | 50 |
max | number | Optional | 100 |
showValue | boolean | Optional | true |
fileName | string | Optional | "" |
fileSize | string | Optional | "" |
uploadedSize | string | Optional | "" |
status | string | Optional | "uploading" |
cancelLabel | string | Optional | "Cancel upload" |
retryLabel | string | Optional | "Retry upload" |
class | string | Optional | "" |
This component does not declare a slot.
cancelretrycompleteInstalled 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>
- }
-}
-Theme-aware, responsive file upload component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "File Upload" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselectuploadprogresssuccesserrorcancelremoveInstalled 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>
- }
-}
-Theme-aware, responsive grid component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
columns | number | Optional | 2 |
gap | string | Optional | "md" |
maxWidth | string | Optional | "xl" |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive image component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
src | string | Optional | "" |
alt | string | Optional | "" |
width | string | Optional | "" |
height | string | Optional | "" |
loading | string | Optional | "lazy" |
rounded | boolean | Optional | false |
class | string | Optional | "" |
defaultloaderrorInstalled 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>
- }
-}
-Theme-aware, responsive input group component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Input group" |
hiddenLabel | boolean | Optional | false |
value | string | Optional | "" |
placeholder | string | Optional | "" |
type | string | Optional | "text" |
startText | string | Optional | "" |
endText | string | Optional | "" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
actionLabel | string | Optional | "" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
variant | string | Optional | "normal" |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblursubmitactionInstalled 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>
- }
-}
-Theme-aware, responsive input number component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
variant | string | Optional | "default" |
class | string | Optional | "" |
id | string | Optional | "" |
name | string | Optional | "quantity" |
value | number | Optional | 0 |
min | string | Optional | "" |
max | string | Optional | "" |
step | number | Optional | 1 |
precision | string | Optional | "auto" |
label | string | Optional | "" |
description | string | Optional | "" |
helpText | string | Optional | "" |
error | string | Optional | "" |
invalid | boolean | Optional | false |
prefix | string | Optional | "" |
suffix | string | Optional | "" |
placeholder | string | Optional | "" |
autocomplete | string | Optional | "off" |
inputMode | string | Optional | "decimal" |
ariaLabel | string | Optional | "" |
required | boolean | Optional | false |
disabled | boolean | Optional | false |
inputDisabled | boolean | Optional | false |
buttonsDisabled | boolean | Optional | false |
readonly | boolean | Optional | false |
allowInput | boolean | Optional | true |
keyboard | boolean | Optional | true |
wheel | boolean | Optional | false |
clamp | boolean | Optional | true |
fullWidth | boolean | Optional | false |
showButtons | boolean | Optional | true |
showValidationMessage | boolean | Optional | true |
decrementLabel | string | Optional | "Decrease value" |
incrementLabel | string | Optional | "Increase value" |
controlsLabel | string | Optional | "Quantity controls" |
requiredMessage | string | Optional | "A value is required." |
minMessage | string | Optional | "Value is below the minimum." |
maxMessage | string | Optional | "Value is above the maximum." |
This component does not declare a slot.
inputchangeincrementdecrementInstalled 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>
- }
-}
-Theme-aware, responsive input component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Input" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
value | string | Optional | "" |
type | string | Optional | "text" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
autocomplete | string | Optional | "" |
inputmode | string | Optional | "" |
minlength | string | Optional | "" |
maxlength | string | Optional | "" |
pattern | string | Optional | "" |
min | string | Optional | "" |
max | string | Optional | "" |
step | string | Optional | "" |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurinvalidkeydownkeyupInstalled 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>
- }
-}
-Theme-aware, responsive kbd component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "⌘ K" |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive layout splitter component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
columns | number | Optional | 2 |
gap | string | Optional | "md" |
maxWidth | string | Optional | "xl" |
class | string | Optional | "" |
defaultresizeStartresizeresizeEndInstalled 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>
- }
-}
-Theme-aware, responsive legend indicator component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Legend Indicator" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaulttoggleselectInstalled 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>
- }
-}
-Theme-aware, responsive link component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Link" |
href | string | Optional | "#" |
target | string | Optional | "" |
rel | string | Optional | "" |
external | boolean | Optional | false |
class | string | Optional | "" |
defaultclickfocusblurInstalled 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>
- }
-}
-Theme-aware, responsive list group component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "List Group" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselectchangeInstalled 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>
- }
-}
-Theme-aware, responsive list component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "List" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselectclickInstalled 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>
- }
-}
-Theme-aware, responsive map component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Map" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultreadymovezoommarkerClickInstalled 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>
- }
-}
-Theme-aware, responsive marquee component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Marquee" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultpauseresumeInstalled 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>
- }
-}
-Theme-aware, responsive mega menu component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Mega Menu" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultopencloseselectInstalled 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>
- }
-}
-Theme-aware, responsive modal component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Modal" |
description | string | Optional | "" |
open | boolean | Optional | false |
placement | string | Optional | "bottom" |
closeLabel | string | Optional | "Close" |
class | string | Optional | "" |
defaultopenclosecancelconfirmInstalled 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>
- }
-}
-Theme-aware, responsive nav component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Nav" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultselectchangeInstalled 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>
- }
-}
-Theme-aware, responsive navbar component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Navbar" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaulttoggleopencloseInstalled 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>
- }
-}
-Theme-aware, responsive pagination component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Pagination" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultchangepreviousnextInstalled 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>
- }
-}
-Secure multi-cell PIN and verification-code input with regex and paste support.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Verification code" |
name | string | Optional | "pin" |
value | string | Optional | "" |
length | number | Optional | 4 |
pattern | string | Optional | "[0-9]" |
type | string | Optional | "text" |
inputMode | string | Optional | "numeric" |
placeholder | string | Optional | "○" |
autocomplete | string | Optional | "one-time-code" |
masked | boolean | Optional | false |
disabled | boolean | Optional | false |
readonly | boolean | Optional | false |
required | boolean | Optional | false |
autoFocus | boolean | Optional | false |
autoSubmit | boolean | Optional | false |
allowPaste | boolean | Optional | true |
clearable | boolean | Optional | true |
clearLabel | string | Optional | "Clear code" |
separator | string | Optional | "" |
groupSize | number | Optional | 0 |
helpText | string | Optional | "" |
invalid | boolean | Optional | false |
validationMessage | string | Optional | "" |
class | string | Optional | "" |
This component does not declare a slot.
inputchangecompletepasteclearerrorInstalled 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>
- }
-}
-Theme-aware, responsive popover component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Popover" |
description | string | Optional | "" |
open | boolean | Optional | false |
placement | string | Optional | "bottom" |
closeLabel | string | Optional | "Close" |
class | string | Optional | "" |
defaultopencloseInstalled 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>
- }
-}
-Theme-aware, responsive progress component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Progress" |
value | number | Optional | 50 |
max | number | Optional | 100 |
showValue | boolean | Optional | true |
class | string | Optional | "" |
This component does not declare a slot.
This component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive radio component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Radio" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
value | string | Optional | "on" |
options | string | Optional | [] |
checked | boolean | Optional | false |
orientation | string | Optional | "vertical" |
card | boolean | Optional | false |
rightAligned | boolean | Optional | false |
list | boolean | Optional | false |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurinvalidInstalled 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>
- }
-}
-Theme-aware, responsive range slider component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Range" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
value | number | Optional | 50 |
min | number | Optional | 0 |
max | number | Optional | 100 |
step | number | Optional | 1 |
showValue | boolean | Optional | true |
showBounds | boolean | Optional | true |
showSteps | boolean | Optional | false |
marks | string | Optional | [] |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurInstalled 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>
- }
-}
-Theme-aware, responsive rating component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Rating" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultinputchangeInstalled 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>
- }
-}
-Theme-aware, responsive scrollspy component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Scrollspy" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultchangeInstalled 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>
- }
-}
-Theme-aware, responsive search box component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Search Box" |
name | string | Optional | "" |
value | string | Optional | "" |
placeholder | string | Optional | "" |
type | string | Optional | "search" |
min | string | Optional | "" |
max | string | Optional | "" |
step | string | Optional | "" |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangesearchsubmitclearInstalled 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>
- }
-}
-Theme-aware, responsive select component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Select" |
hiddenLabel | boolean | Optional | false |
value | string | Optional | "" |
values | string | Optional | [] |
options | string | Optional | [] |
placeholder | string | Optional | "Select an option" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
multiple | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusbluropencloseinvalidInstalled 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>
- }
-}
-Theme-aware, responsive sidebar component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Sidebar" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaulttoggleopencloseInstalled 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>
- }
-}
-Theme-aware, responsive skeleton component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
color | string | Optional | "primary" |
label | string | Optional | "Loading" |
size | string | Optional | "md" |
lines | number | Optional | 3 |
class | string | Optional | "" |
This component does not declare a slot.
This component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive spinner component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
color | string | Optional | "primary" |
label | string | Optional | "Loading" |
size | string | Optional | "md" |
lines | number | Optional | 3 |
class | string | Optional | "" |
This component does not declare a slot.
This component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive stepper component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Stepper" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultchangepreviousnextcompleteInstalled 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>
- }
-}
-Theme-aware, responsive strong password component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Password" |
name | string | Optional | "password" |
value | string | Optional | "" |
placeholder | string | Optional | "Create a strong password" |
autocomplete | string | Optional | "new-password" |
minLength | number | Optional | 8 |
specialCharactersSet | string | Optional | "!@#$%^&*()_+-=[]{}|;:,.<>?" |
requireLowercase | boolean | Optional | true |
requireUppercase | boolean | Optional | true |
requireNumber | boolean | Optional | true |
requireSpecialCharacter | boolean | Optional | true |
showRequirements | boolean | Optional | true |
presentation | string | Optional | "inline" |
hintText | string | Optional | "Use a unique password you do not use elsewhere." |
emptyLabel | string | Optional | "Enter a password" |
weakLabel | string | Optional | "Weak" |
fairLabel | string | Optional | "Fair" |
goodLabel | string | Optional | "Good" |
strongLabel | string | Optional | "Strong" |
disabled | boolean | Optional | false |
readonly | boolean | Optional | false |
required | boolean | Optional | false |
invalid | boolean | Optional | false |
validationMessage | string | Optional | "" |
class | string | Optional | "" |
This component does not declare a slot.
inputchangestrengthInstalled 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>
- }
-}
-Theme-aware, responsive styled icon component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Styled Icon" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive switch component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Switch" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
value | string | Optional | "on" |
checked | boolean | Optional | false |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurInstalled 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>
- }
-}
-Theme-aware, responsive table component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
caption | string | Optional | "Table" |
columns | string | Optional | [] |
rows | string | Optional | [] |
striped | boolean | Optional | true |
class | string | Optional | "" |
defaultsortselectrowClickInstalled 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>
- }
-}
-Theme-aware, responsive tabs component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Tabs" |
items | string | Optional | [] |
active | string | Optional | "" |
orientation | string | Optional | "horizontal" |
class | string | Optional | "" |
defaultchangeselectInstalled 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>
- }
-}
-Theme-aware, responsive textarea component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Textarea" |
hiddenLabel | boolean | Optional | false |
placeholder | string | Optional | "" |
value | string | Optional | "" |
variant | string | Optional | "normal" |
icon | string | Optional | "" |
iconPosition | string | Optional | "start" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
rows | number | Optional | 5 |
resize | string | Optional | "vertical" |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
minlength | string | Optional | "" |
maxlength | string | Optional | "" |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusblurinvalidInstalled 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>
- }
-}
-Theme-aware, responsive time picker component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
id | string | Optional | "" |
name | string | Optional | "" |
label | string | Optional | "Time" |
hiddenLabel | boolean | Optional | false |
value | string | Optional | "" |
placeholder | string | Optional | "" |
variant | string | Optional | "normal" |
icon | string | Optional | "icon-[lucide--clock-3]" |
iconPosition | string | Optional | "end" |
helperText | string | Optional | "" |
cornerHint | string | Optional | "" |
error | string | Optional | "" |
inline | boolean | Optional | false |
min | string | Optional | "" |
max | string | Optional | "" |
step | string | Optional | "" |
format | string | Optional | "24" |
minuteStep | number | Optional | 5 |
hours | string | Optional | ["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 | string | Optional | ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"] |
readonly | boolean | Optional | false |
disabled | boolean | Optional | false |
required | boolean | Optional | false |
class | string | Optional | "" |
This component does not declare a slot.
inputchangefocusbluropencloseinvalidInstalled 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>
- }
-}
-Theme-aware, responsive timeline component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Timeline" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselectInstalled 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>
- }
-}
-Theme-aware, responsive toast notifications component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Toast Notifications" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultadddismissclearactionInstalled 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>
- }
-}
-Theme-aware, responsive toast component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Toast" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultdismissactionInstalled 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>
- }
-}
-Theme-aware, responsive toggle count component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
variant | string | Optional | "segmented" |
class | string | Optional | "" |
name | string | Optional | "billing-cycle" |
value | string | Optional | "monthly" |
firstValue | string | Optional | "monthly" |
firstLabel | string | Optional | "Monthly" |
secondValue | string | Optional | "annual" |
secondLabel | string | Optional | "Annual" |
ariaLabel | string | Optional | "Billing frequency" |
items | string | Optional | [] |
currency | string | Optional | "$" |
suffix | string | Optional | "" |
firstValueKey | string | Optional | "monthly" |
secondValueKey | string | Optional | "annual" |
emptyValue | string | Optional | "—" |
align | string | Optional | "end" |
fullWidth | boolean | Optional | true |
disabled | boolean | Optional | false |
animate | boolean | Optional | true |
animationDuration | number | Optional | 450 |
animationSteps | number | Optional | 18 |
This component does not declare a slot.
changetoggleInstalled 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>
- }
-}
-Accessible password field with optional show and hide controls.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
label | string | Optional | "Password" |
name | string | Optional | "password" |
value | string | Optional | "" |
placeholder | string | Optional | "Enter your password" |
autocomplete | string | Optional | "current-password" |
minlength | string | Optional | "" |
maxlength | string | Optional | "" |
pattern | string | Optional | "(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}" |
fields | string | Optional | [] |
visible | boolean | Optional | false |
toggleable | boolean | Optional | true |
toggleMode | string | Optional | "button" |
checkboxLabel | string | Optional | "Show password" |
showLabel | string | Optional | "Show password" |
hideLabel | string | Optional | "Hide password" |
disabled | boolean | Optional | false |
readonly | boolean | Optional | false |
required | boolean | Optional | false |
invalid | boolean | Optional | false |
helpText | string | Optional | "" |
validationMessage | string | Optional | "" |
class | string | Optional | "" |
This component does not declare a slot.
inputchangetoggleInstalled 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>
- }
-}
-Theme-aware, responsive tooltip component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Tooltip" |
description | string | Optional | "" |
open | boolean | Optional | false |
placement | string | Optional | "bottom" |
closeLabel | string | Optional | "Close" |
class | string | Optional | "" |
defaultopencloseInstalled 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>
- }
-}
-Theme-aware, responsive tree view component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Tree View" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultselecttoggleexpandcollapseInstalled 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>
- }
-}
-Theme-aware, responsive typography component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
columns | number | Optional | 2 |
gap | string | Optional | "md" |
maxWidth | string | Optional | "xl" |
class | string | Optional | "" |
defaultThis component does not declare a custom event.
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>
- }
-}
-Theme-aware, responsive wysiwyg editor component.
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".
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "Wysiwyg Editor" |
description | string | Optional | "" |
items | string | Optional | [] |
variant | string | Optional | "default" |
class | string | Optional | "" |
defaultinputchangefocusblurInstalled 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>
- }
-}
-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.
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0API 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Authentication
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Authorization
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Configuration 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Database
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Deployment
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Forms 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Internationalization 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Middleware
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Mobile
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Observability
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Pages 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Project 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Pub/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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Queues
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Realtime
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Routing
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Application 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Server 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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Testing
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Troubleshooting
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Upgrading
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Uploads
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.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview guide · 0.5.0Workspaces 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
- Menu
+ W WRNexusJS
+ Menu
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.wrnpage 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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Language reference · 0.5.0The .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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
License
Owner decision requiredThis 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
-
+
- Browse documentation
+ Browse documentation
26 focused packagesPackage 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
-
+
- Browse documentation
+ Browse documentation
AI · Package reference@wrnexus/ai
Server-side Anthropic client with generation and streaming.
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
-
+
- Browse documentation
+ Browse documentation
Security · Package reference@wrnexus/authz
Role, permission, policy, and authorization guards.
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
-
+
- Browse documentation
+ Browse documentation
Tooling · Package reference@wrnexus/cli
Create, develop, build, generate, test, and maintain WRNexusJS apps.
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
-
+
- Browse documentation
+ Browse documentation
Core · Package reference@wrnexus/compiler
Parser and code generators for the .wrn language.
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
-
+
- Browse documentation
+ Browse documentation
Core · Package reference@wrnexus/core
Contexts, middleware, security, sessions, caching, JSX, and realtime.
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
-
+
- Browse documentation
+ Browse documentation
Frontend · Package reference@wrnexus/csr
Reactive, navigation, and realtime browser runtimes.
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
-
+
- Browse documentation
+ Browse documentation
Data · Package reference@wrnexus/db
Database adapters, typed queries, models, migrations, and sessions.
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
-
+
- Browse documentation
+ Browse documentation
Runtime · Package reference@wrnexus/dev-server
Development and production servers, HMR, assets, and gateways.
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
-
+
- Browse documentation
+ Browse documentation
Security · Package reference@wrnexus/encryption
Hashing, HMAC, authenticated encryption, and key derivation.
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
-
+
- Browse documentation
+ Browse documentation
Tooling · Package reference@wrnexus/helpers
Safe Context URL helpers and forward-auth login redirects.
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
-
+
- Browse documentation
+ Browse documentation
Frontend · Package reference@wrnexus/i18n
Translation loading, locale resolution, and Intl formatting.
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
-
+
- Browse documentation
+ Browse documentation
Security · Package reference@wrnexus/jwt
HS256 JWT signing, verification, and bearer authentication.
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
-
+
- Browse documentation
+ Browse documentation
Native · Package reference@wrnexus/mobile
SSR-safe compatibility access to Capacitor plugins.
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
-
+
- Browse documentation
+ Browse documentation
Native · Package reference@wrnexus/native
Cross-platform browser and Capacitor capability registry.
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
-
+
- Browse documentation
+ Browse documentation
Security · Package reference@wrnexus/oauth
OAuth 2.0, PKCE, provider presets, and profile mapping.
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
-
+
- Browse documentation
+ Browse documentation
Realtime · Package reference@wrnexus/pubsub
In-process and Redis-backed publish/subscribe.
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
-
+
- Browse documentation
+ Browse documentation
Data · Package reference@wrnexus/queue
Background jobs with delay, concurrency, retry, and repetition.
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
-
+
- Browse documentation
+ Browse documentation
Frontend · Package reference@wrnexus/reactive
Small type-safe reactive signal primitives.
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
-
+
- Browse documentation
+ Browse documentation
Core · Package reference@wrnexus/router
Filesystem discovery, route matching, and typed route generation.
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
-
+
- Browse documentation
+ Browse documentation
Runtime · Package reference@wrnexus/ssr
Secure HTML document rendering and SEO metadata.
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
-
+
- Browse documentation
+ Browse documentation
Frontend · Package reference@wrnexus/styles
CSS pipeline, themes, fonts, profiles, and application config.
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
-
+
- Browse documentation
+ Browse documentation
Tooling · Package reference@wrnexus/test
WRNexusJS-aware component, route, and browser testing utilities.
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
-
+
- Browse documentation
+ Browse documentation
Runtime · Package reference@wrnexus/tracking
Error/event capture, middleware, filtering, and sinks.
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
-
+
- Browse documentation
+ Browse documentation
Frontend · Package reference@wrnexus/ui
Themeable server-rendered UI components and CSS.
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.
+</div>Explore the component library
Browse interactive examples and complete component usage in the dedicated WRNexusJS component showcase.
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
-
+
- Browse documentation
+ Browse documentation
Data · Package reference@wrnexus/uploader
Validated local/S3 uploads and secure file serving.
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
-
+
- Browse documentation
+ Browse documentation
Security · Package reference@wrnexus/validation
Typed schemas, coercion, validation, and browser descriptors.
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
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
- Browse documentation
- 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
+ Browse documentation
+ 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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
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
- Browse documentation
+ W WRNexusJS
+ Browse documentation
Preview · runnable-project extraction pendingSecure 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}
+
+ #{i}
+ {r.name}
+ {r.email}
+
+ {:empty}
+ No submissions yet.
+ {/each}
+
+
+ }
+}
+```
+
+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: `