release: WRNexusJS 0.5.0
This commit is contained in:
@@ -2,14 +2,202 @@ component Accordion {
|
||||
props {
|
||||
size = "default"
|
||||
color = "primary"
|
||||
items = []
|
||||
multiple = false
|
||||
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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--accordion {class}">
|
||||
{#each items as item}<details open="{item.open}"><summary>{item.label}</summary><div>{item.content}</div></details>{/each}
|
||||
<slot />
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component AdvancedRangeSlider {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event start = function
|
||||
@event end = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Advanced Range Slider"
|
||||
|
||||
@@ -1,19 +1,121 @@
|
||||
component AvatarGroup {
|
||||
props {
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Avatar Group"
|
||||
description = ""
|
||||
items = []
|
||||
variant = "default"
|
||||
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 {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--avatar-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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,67 @@
|
||||
component Blockquote {
|
||||
props {
|
||||
size = "default"
|
||||
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"
|
||||
title = "Blockquote"
|
||||
description = ""
|
||||
items = []
|
||||
align = "left"
|
||||
variant = "default"
|
||||
quoteMark = true
|
||||
italic = true
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--blockquote 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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Breadcrumb {
|
||||
props {
|
||||
@event navigate = function
|
||||
@event click = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Breadcrumb"
|
||||
|
||||
@@ -1,19 +1,89 @@
|
||||
component ButtonGroup {
|
||||
props {
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Button Group"
|
||||
description = ""
|
||||
@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 {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--button-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}
|
||||
<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 />
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,461 @@
|
||||
// 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 = "Carousel"
|
||||
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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--carousel 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
|
||||
{...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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Chart {
|
||||
props {
|
||||
@event select = function
|
||||
@event dataPointClick = function
|
||||
@event legendToggle = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Chart"
|
||||
|
||||
@@ -1,18 +1,146 @@
|
||||
component ChatBubble {
|
||||
props {
|
||||
@event action = function
|
||||
@event messageClick = function
|
||||
@event avatarClick = function
|
||||
@event linkClick = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Chat Bubble"
|
||||
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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--chat-bubble 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}
|
||||
<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>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Clipboard {
|
||||
props {
|
||||
@event copy = function
|
||||
@event success = function
|
||||
@event error = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Clipboard"
|
||||
|
||||
@@ -1,15 +1,95 @@
|
||||
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 {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--collapse {class}">
|
||||
{#each items as item}<details open="{item.open}"><summary>{item.label}</summary><div>{item.content}</div></details>{/each}
|
||||
<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 />
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
component ColorPicker {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Color Picker"
|
||||
id = ""
|
||||
name = ""
|
||||
value = ""
|
||||
label = "Color"
|
||||
hiddenLabel = false
|
||||
placeholder = ""
|
||||
type = "color"
|
||||
min = ""
|
||||
max = ""
|
||||
step = ""
|
||||
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 {
|
||||
<label class="wire-next wire-next--field wire-next--color-picker {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Confetti {
|
||||
props {
|
||||
@event start = function
|
||||
@event complete = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Confetti"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component ContextMenu {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Context Menu"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component CopyMarkup {
|
||||
props {
|
||||
@event copy = function
|
||||
@event success = function
|
||||
@event error = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Copy Markup"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
component CustomScrollbar {
|
||||
props {
|
||||
@event scroll = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
columns = 2
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component DataMap {
|
||||
props {
|
||||
@event select = function
|
||||
@event change = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Data Map"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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"
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--date-picker {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--device-frame 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 {...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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
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"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Drawer {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Drawer"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component Dropdown {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
@event select = function
|
||||
@event change = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Dropdown"
|
||||
|
||||
@@ -1,20 +1,52 @@
|
||||
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"
|
||||
label = "File Input"
|
||||
id = ""
|
||||
name = ""
|
||||
label = "File"
|
||||
hiddenLabel = false
|
||||
placeholder = "Choose a file"
|
||||
value = ""
|
||||
placeholder = ""
|
||||
type = "file"
|
||||
min = ""
|
||||
max = ""
|
||||
step = ""
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--file-input {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
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"
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--file-upload-progress {class}">
|
||||
<div class="wire-next__row"><span>{label}</span>{#if showValue}<strong>{value}%</strong>{/if}</div>
|
||||
<progress value="{value}" max="{max}"></progress>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Image {
|
||||
props {
|
||||
@event load = function
|
||||
@event error = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
src = ""
|
||||
|
||||
@@ -1,20 +1,51 @@
|
||||
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"
|
||||
label = "Input Group"
|
||||
id = ""
|
||||
name = ""
|
||||
label = "Input group"
|
||||
hiddenLabel = false
|
||||
value = ""
|
||||
placeholder = ""
|
||||
type = "text"
|
||||
min = ""
|
||||
max = ""
|
||||
step = ""
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--input-group {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component LayoutSplitter {
|
||||
props {
|
||||
@event resizeStart = function
|
||||
@event resize = function
|
||||
@event resizeEnd = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
columns = 2
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component LegendIndicator {
|
||||
props {
|
||||
@event toggle = function
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Legend Indicator"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Link {
|
||||
props {
|
||||
@event click = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Link"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component List {
|
||||
props {
|
||||
@event select = function
|
||||
@event click = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "List"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component ListGroup {
|
||||
props {
|
||||
@event select = function
|
||||
@event change = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "List Group"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component Map {
|
||||
props {
|
||||
@event ready = function
|
||||
@event move = function
|
||||
@event zoom = function
|
||||
@event markerClick = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Map"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Marquee {
|
||||
props {
|
||||
@event pause = function
|
||||
@event resume = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Marquee"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component MegaMenu {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Mega Menu"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component Modal {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
@event cancel = function
|
||||
@event confirm = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Modal"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Nav {
|
||||
props {
|
||||
@event select = function
|
||||
@event change = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Nav"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Navbar {
|
||||
props {
|
||||
@event toggle = function
|
||||
@event open = function
|
||||
@event close = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Navbar"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Pagination {
|
||||
props {
|
||||
@event change = function
|
||||
@event previous = function
|
||||
@event next = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Pagination"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Popover {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Popover"
|
||||
|
||||
@@ -1,20 +1,68 @@
|
||||
component RangeSlider {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Range Slider"
|
||||
id = ""
|
||||
name = ""
|
||||
value = ""
|
||||
label = "Range"
|
||||
hiddenLabel = false
|
||||
placeholder = ""
|
||||
type = "range"
|
||||
min = ""
|
||||
max = ""
|
||||
step = ""
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--range-slider {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Rating {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Rating"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
component Scrollspy {
|
||||
props {
|
||||
@event change = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Scrollspy"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Sidebar {
|
||||
props {
|
||||
@event toggle = function
|
||||
@event open = function
|
||||
@event close = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Sidebar"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component Stepper {
|
||||
props {
|
||||
@event change = function
|
||||
@event previous = function
|
||||
@event next = function
|
||||
@event complete = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Stepper"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Tabs {
|
||||
props {
|
||||
@event change = function
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Tabs"
|
||||
|
||||
@@ -1,20 +1,63 @@
|
||||
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"
|
||||
label = "Time Picker"
|
||||
id = ""
|
||||
name = ""
|
||||
label = "Time"
|
||||
hiddenLabel = false
|
||||
value = ""
|
||||
placeholder = ""
|
||||
type = "time"
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--time-picker {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
component Timeline {
|
||||
props {
|
||||
@event select = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Timeline"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Toast {
|
||||
props {
|
||||
@event dismiss = function
|
||||
@event action = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Toast"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component ToastNotifications {
|
||||
props {
|
||||
@event add = function
|
||||
@event dismiss = function
|
||||
@event clear = function
|
||||
@event action = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Toast Notifications"
|
||||
|
||||
@@ -2,19 +2,236 @@ component ToggleCount {
|
||||
props {
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Toggle Count"
|
||||
name = ""
|
||||
value = ""
|
||||
placeholder = ""
|
||||
type = "text"
|
||||
min = ""
|
||||
max = ""
|
||||
step = ""
|
||||
disabled = false
|
||||
required = false
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--toggle-count {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component TreeView {
|
||||
props {
|
||||
@event select = function
|
||||
@event toggle = function
|
||||
@event expand = function
|
||||
@event collapse = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Tree View"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
component WysiwygEditor {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Wysiwyg Editor"
|
||||
|
||||
@@ -1,19 +1,142 @@
|
||||
component Alert {
|
||||
props {
|
||||
size = "default"
|
||||
color = "primary"
|
||||
color = "info"
|
||||
variant = "soft"
|
||||
class = ""
|
||||
radius = "md"
|
||||
shadow = "sm"
|
||||
|
||||
title = "Alert"
|
||||
description = ""
|
||||
items = []
|
||||
variant = "default"
|
||||
class = ""
|
||||
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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--alert 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
|
||||
{...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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,87 @@
|
||||
component Avatar {
|
||||
props {
|
||||
size = "default"
|
||||
@event load = function
|
||||
@event error = function
|
||||
@event click = function
|
||||
src = ""
|
||||
alt = ""
|
||||
initials = ""
|
||||
size = "md"
|
||||
color = "primary"
|
||||
title = "Avatar"
|
||||
variant = "solid"
|
||||
shape = "circle"
|
||||
|
||||
status = ""
|
||||
statusLabel = ""
|
||||
statusPosition = "bottom"
|
||||
|
||||
badge = ""
|
||||
badgeIcon = ""
|
||||
badgeLabel = ""
|
||||
|
||||
tooltip = ""
|
||||
name = ""
|
||||
description = ""
|
||||
items = []
|
||||
variant = "default"
|
||||
loading = "lazy"
|
||||
class = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--avatar 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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,102 @@
|
||||
component Badge {
|
||||
props {
|
||||
size = "default"
|
||||
label = "Badge"
|
||||
size = "md"
|
||||
color = "primary"
|
||||
title = "Badge"
|
||||
description = ""
|
||||
items = []
|
||||
variant = "default"
|
||||
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 {
|
||||
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--badge 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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Button {
|
||||
props {
|
||||
@event click = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
label = "Button"
|
||||
loadingLabel = "Loading…"
|
||||
description = ""
|
||||
|
||||
@@ -1,19 +1,259 @@
|
||||
component Card {
|
||||
props {
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Card"
|
||||
@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 class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--card 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
|
||||
{...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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,62 @@
|
||||
component Checkbox {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
@event invalid = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Checkbox"
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice wire-next--checkbox {class}"><input {...attrs} type="checkbox" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" /><span>{label}</span></label>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,92 @@
|
||||
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"
|
||||
label = "Input"
|
||||
id = ""
|
||||
name = ""
|
||||
value = ""
|
||||
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 = ""
|
||||
disabled = false
|
||||
required = false
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--input {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>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,56 @@
|
||||
component Radio {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
@event invalid = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Radio"
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice wire-next--radio {class}"><input {...attrs} type="radio" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" /><span>{label}</span></label>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,61 @@
|
||||
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"
|
||||
label = "Select"
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--select {class}"><span>{label}</span><select {...attrs} name="{name}" multiple="{multiple}" disabled="{disabled}"><option value="">{placeholder}</option>{#each options as option}<option value="{option.value}" selected="{option.value === value}">{option.label}</option>{/each}</select></label>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
component Switch {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Switch"
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--choice wire-next--switch {class}"><input {...attrs} type="checkbox" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" /><span>{label}</span></label>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
component Table {
|
||||
props {
|
||||
@event sort = function
|
||||
@event select = function
|
||||
@event rowClick = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
caption = "Table"
|
||||
|
||||
@@ -1,17 +1,53 @@
|
||||
component Textarea {
|
||||
props {
|
||||
@event input = function
|
||||
@event change = function
|
||||
@event focus = function
|
||||
@event blur = function
|
||||
@event invalid = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
label = "Textarea"
|
||||
id = ""
|
||||
name = ""
|
||||
value = ""
|
||||
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 {
|
||||
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--textarea {class}"><span>{label}</span><textarea {...attrs} name="{name}" placeholder="{placeholder}" rows="{rows}" disabled="{disabled}" required="{required}">{value}</textarea></label>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
component Tooltip {
|
||||
props {
|
||||
@event open = function
|
||||
@event close = function
|
||||
size = "default"
|
||||
color = "primary"
|
||||
title = "Tooltip"
|
||||
|
||||
Reference in New Issue
Block a user