Bumps all 47 packages, the root manifest and the VS Code extension to 0.8.6, and rebuilds the editor compiler, language server and extension bundles that embed the version. The release carries the output delivery fix: camelCase outputs now reach parent bindings, and 18 components emit through output.* instead of hand-built CustomEvents. See the 0.8.6 migration entry for what changes for consumers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
313 lines
12 KiB
Plaintext
313 lines
12 KiB
Plaintext
component Card {
|
|
outputs {
|
|
click(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
action(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
navigate(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
dismiss(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
load(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
error(payload: { error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)
|
|
}
|
|
|
|
props {
|
|
title: string = "Card title"
|
|
subtitle: string = ""
|
|
description: string = ""
|
|
header: string = ""
|
|
footer: string = ""
|
|
imageSrc: string = ""
|
|
imageAlt: string = ""
|
|
imagePosition: string = "top"
|
|
actionLabel: string = ""
|
|
actionHref: string = ""
|
|
headerActions: unknown[] = []
|
|
navigation: unknown[] = []
|
|
activeNav: string = ""
|
|
mobileNavigation: boolean = false
|
|
alertTitle: string = ""
|
|
alertDescription: string = ""
|
|
empty: boolean = false
|
|
emptyTitle: string = "No data to show"
|
|
emptyIcon: string = "icon-[lucide--inbox]"
|
|
items: unknown[] = []
|
|
size: string = "md"
|
|
color: string = "primary"
|
|
variant: string = "default"
|
|
layout: string = "vertical"
|
|
align: string = "left"
|
|
hover: string = "none"
|
|
scrollable: boolean = false
|
|
maxHeight: string = "18rem"
|
|
dismissible: boolean = false
|
|
ariaLabel: string = ""
|
|
class: string = ""
|
|
}
|
|
|
|
state dismissed: boolean = false
|
|
|
|
functions {
|
|
// Outputs must go through output.*; a CustomEvent dispatched on the root
|
|
// bubbles past every parent @binding without being seen, because the
|
|
// runtime keeps parent handlers in a registry only the output proxy reads.
|
|
client function dispatchCardNavigation(sourceEvent, item, index) {
|
|
output.navigate({ component: "Card", item: item, index: index })
|
|
}
|
|
|
|
client function dispatchCardNavigationValue(sourceEvent) {
|
|
output.navigate({
|
|
component: "Card",
|
|
value: sourceEvent.currentTarget.value
|
|
})
|
|
}
|
|
|
|
client function dispatchCardHeaderAction(sourceEvent, action, index) {
|
|
output.action({ component: "Card", action: action, index: index })
|
|
}
|
|
|
|
client function dismissCard() {
|
|
dismissed = true
|
|
output.dismiss({ component: "Card", title: title })
|
|
}
|
|
}
|
|
|
|
view {
|
|
<div
|
|
{...attrs}
|
|
data-ui-component="Card"
|
|
data-wrn-card
|
|
data-size='{size}'
|
|
data-color='{color}'
|
|
data-variant='{variant}'
|
|
data-layout='{layout}'
|
|
data-hover='{hover}'
|
|
data-align='{align}'
|
|
class='wire-next wire-next--card wire-next--color-{color} wire-next--size-{size} wire-next--variant-{variant} {class}'
|
|
>
|
|
{#if items.length > 0}
|
|
<div class="wire-next__card-group">
|
|
{#each items as item, itemIndex}
|
|
<article
|
|
class="wire-next__card-panel"
|
|
aria-label='{item.ariaLabel || item.title || item.label || "Card item"}'
|
|
data-index='{itemIndex}'
|
|
>
|
|
{#if item.imageSrc || item.image}
|
|
<div class="wire-next__card-media">
|
|
<img
|
|
src='{item.imageSrc || item.image}'
|
|
alt='{item.imageAlt || item.alt || ""}'
|
|
loading='{item.loading || "lazy"}'
|
|
decoding="async"
|
|
@load='output.load({ item: item, index: itemIndex })'
|
|
@error='output.error({ item: item, index: itemIndex })'
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="wire-next__card-content">
|
|
{#if item.title || item.label}
|
|
<h3>{item.title || item.label}</h3>
|
|
{/if}
|
|
{#if item.subtitle}
|
|
<p class="wire-next__card-subtitle">{item.subtitle}</p>
|
|
{/if}
|
|
{#if item.description}
|
|
<p class="wire-next__card-description">{item.description}</p>
|
|
{/if}
|
|
{#if item.actionLabel || item.href}
|
|
<a
|
|
href='{item.actionHref || item.href || "#"}'
|
|
class="wire-next__card-action"
|
|
@click='output.action({ item: item, index: itemIndex })'
|
|
>
|
|
<span>{item.actionLabel || "Learn more"}</span>
|
|
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
</article>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<article
|
|
class="wire-next__card-panel"
|
|
aria-label='{ariaLabel || title || "Card"}'
|
|
hidden='{dismissed}'
|
|
>
|
|
{#if imageSrc && imagePosition === "overlay"}
|
|
<div class="wire-next__card-overlay">
|
|
<img
|
|
src='{imageSrc}'
|
|
alt='{imageAlt}'
|
|
loading="lazy"
|
|
decoding="async"
|
|
@load='output.load({ src: imageSrc })'
|
|
@error='output.error({ src: imageSrc })'
|
|
/>
|
|
<div class="wire-next__card-overlay-shade" aria-hidden="true"></div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if imageSrc && (imagePosition === "top" || imagePosition === "left")}
|
|
<div class="wire-next__card-media wire-next__card-media--{imagePosition}">
|
|
<img
|
|
src='{imageSrc}'
|
|
alt='{imageAlt}'
|
|
loading="lazy"
|
|
decoding="async"
|
|
@load='output.load({ src: imageSrc })'
|
|
@error='output.error({ src: imageSrc })'
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="wire-next__card-content">
|
|
{#if header || title || subtitle || headerActions.length > 0 || dismissible}
|
|
<header class="wire-next__card-header">
|
|
<div class="wire-next__card-heading">
|
|
{#if header}
|
|
<p class="wire-next__card-eyebrow">{header}</p>
|
|
{/if}
|
|
{#if title}
|
|
<h3>{title}</h3>
|
|
{/if}
|
|
{#if subtitle}
|
|
<p class="wire-next__card-subtitle">{subtitle}</p>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if headerActions.length > 0 || dismissible}
|
|
<div class="wire-next__card-header-actions">
|
|
{#each headerActions as headerAction, actionIndex}
|
|
<button
|
|
type="button"
|
|
aria-label='{headerAction.ariaLabel || headerAction.label || "Card action"}'
|
|
@click='dispatchCardHeaderAction(event, headerAction, actionIndex)'
|
|
>
|
|
{#if headerAction.icon}
|
|
<span class='{headerAction.icon}' aria-hidden="true"></span>
|
|
{/if}
|
|
{#if headerAction.label && !headerAction.icon}
|
|
<span>{headerAction.label}</span>
|
|
{/if}
|
|
</button>
|
|
{/each}
|
|
|
|
{#if dismissible}
|
|
<button
|
|
type="button"
|
|
aria-label="Dismiss card"
|
|
@click='dismissCard(event)'
|
|
>
|
|
<span class="icon-[lucide--x]" aria-hidden="true"></span>
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</header>
|
|
{/if}
|
|
|
|
{#if navigation.length > 0}
|
|
<nav aria-label='{ariaLabel || title || "Card navigation"}' class="wire-next__card-navigation">
|
|
{#if mobileNavigation}
|
|
<select
|
|
class="wire-next__card-navigation-select"
|
|
aria-label='{ariaLabel || title || "Card navigation"}'
|
|
@change='dispatchCardNavigationValue(event)'
|
|
>
|
|
{#each navigation as navItem}
|
|
<option
|
|
value='{navItem.value || navItem.href || navItem.label}'
|
|
selected='{activeNav === navItem.value || activeNav === navItem.href}'
|
|
>
|
|
{navItem.label}
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
{/if}
|
|
|
|
<div class="wire-next__card-tabs" data-mobile-navigation='{mobileNavigation}'>
|
|
{#each navigation as navItem, navIndex}
|
|
<button
|
|
type="button"
|
|
aria-pressed='{activeNav === navItem.value || activeNav === navItem.href}'
|
|
@click='dispatchCardNavigation(event, navItem, navIndex)'
|
|
>
|
|
{#if navItem.icon}
|
|
<span class='{navItem.icon}' aria-hidden="true"></span>
|
|
{/if}
|
|
<span>{navItem.label}</span>
|
|
{#if navItem.badge}
|
|
<span class="wire-next__card-tab-badge">{navItem.badge}</span>
|
|
{/if}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</nav>
|
|
{/if}
|
|
|
|
{#if alertTitle || alertDescription}
|
|
<div class="wire-next__card-alert" role="status">
|
|
{#if alertTitle}
|
|
<strong>{alertTitle}</strong>
|
|
{/if}
|
|
{#if alertDescription}
|
|
<p>{alertDescription}</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<div
|
|
class="wire-next__card-body"
|
|
data-scrollable='{scrollable}'
|
|
style='max-height: {scrollable ? maxHeight : "none"};'
|
|
>
|
|
{#if empty}
|
|
<div class="wire-next__card-empty">
|
|
<span class='{emptyIcon}' aria-hidden="true"></span>
|
|
<p>{emptyTitle}</p>
|
|
</div>
|
|
{:else}
|
|
{#if description}
|
|
<p class="wire-next__card-description">{description}</p>
|
|
{/if}
|
|
<slot></slot>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if footer || actionLabel}
|
|
<footer class="wire-next__card-footer">
|
|
{#if footer}
|
|
<span>{footer}</span>
|
|
{/if}
|
|
{#if actionLabel}
|
|
<a
|
|
href='{actionHref || "#"}'
|
|
class="wire-next__card-action"
|
|
@click='output.action({ href: actionHref, label: actionLabel })'
|
|
>
|
|
<span>{actionLabel}</span>
|
|
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
|
|
</a>
|
|
{/if}
|
|
</footer>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if imageSrc && (imagePosition === "bottom" || imagePosition === "right")}
|
|
<div class="wire-next__card-media wire-next__card-media--{imagePosition}">
|
|
<img
|
|
src='{imageSrc}'
|
|
alt='{imageAlt}'
|
|
loading="lazy"
|
|
decoding="async"
|
|
@load='output.load({ src: imageSrc })'
|
|
@error='output.error({ src: imageSrc })'
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</article>
|
|
{/if}
|
|
</div>
|
|
}
|
|
}
|