fix(csr,ui): deliver component outputs to parent bindings
An output only reaches a parent @binding when the component calls output.<name>(). Two separate faults meant most of the library never got there, and both failed silently at each end. HTML lowercases attribute names, so a parent's @sizeChange registered under "sizechange" while the component emitted "sizeChange". The lookup missed, fell through to a DOM dispatch, and the binding was never invoked. That made all 17 camelCase outputs undeliverable -- DataTable.pageChange and .rowClick, Map.markerClick, ChatBubble.messageClick, LayoutSplitter.sizeChange and the rest. invokeComponentOutput now falls back to a case-insensitive lookup, and a csr test fails without it. Separately, 18 components dispatched hand-built CustomEvents rather than calling output.*. A bubbling event on the component's own root never reaches a binding, because parent handlers live in a registry only the output proxy reads. Card, Footer, Breadcrumb, Accordion, alert, Badge, AnnouncementBar, AvatarGroup, ToggleCount and InputNumber now emit properly; Marquee, Map, Timeline, List and SearchBox additionally declare the outputs they were already firing. Dispatches on window are left alone -- that is how Toaster, Modal and DataTable signal across component boundaries. Verified in a browser both ways before and after: an AnnouncementBar dispatching its own bubbling "dismiss" never reached a page-level @dismiss, and reached it immediately once it called output.dismiss(). This corrects the audit, which called the LayoutSplitter failure "narrow and unexplained" and read 32 dead outputs as 16 components needing a rebuild. "Outputs work elsewhere" was an assumption; the components that worked happened to use lowercase names and output.*. The dead-output ratchet drops from 32 to 22, and a new test forbids the raw-CustomEvent pattern outright. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,22 +49,24 @@ component Accordion {
|
||||
return multiple || alwaysOpen
|
||||
}
|
||||
|
||||
client 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, {
|
||||
// Named rather than computed: an output is resolved as a property name, so
|
||||
// output[eventName] would not reach a parent binding.
|
||||
client function dispatchAccordionEvent(sourceEvent, eventName, value, item, payload) {
|
||||
payload = {
|
||||
component: "Accordion",
|
||||
value: value,
|
||||
item: item,
|
||||
open: isOpen(value),
|
||||
openValues: openValues
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
if (eventName === "open") {
|
||||
output.open(payload)
|
||||
} else if (eventName === "close") {
|
||||
output.close(payload)
|
||||
} else {
|
||||
output.change(payload)
|
||||
}
|
||||
}
|
||||
|
||||
client function toggleItem(sourceEvent, value, item, wasOpen) {
|
||||
|
||||
@@ -127,7 +127,7 @@ badge: string = ""
|
||||
aria-label='{dismissLabel}'
|
||||
title='{dismissLabel}'
|
||||
class="wire-announcement__dismiss"
|
||||
@click='dismissed = true; event.currentTarget.dispatchEvent(new CustomEvent("dismiss", { bubbles: true, detail: { message: message } }))'
|
||||
@click='dismissed = true; output.dismiss({ message: message })'
|
||||
>
|
||||
<span
|
||||
class="icon-[lucide--x]"
|
||||
|
||||
@@ -30,17 +30,13 @@ component AvatarGroup {
|
||||
return items.slice(Number(maxVisible))
|
||||
}
|
||||
|
||||
client function toggleOverflow(sourceEvent, root, customEvent) {
|
||||
client function toggleOverflow() {
|
||||
overflowOpen = !overflowOpen
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-avatar-group]")
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("overflow", true, false, {
|
||||
output.overflow({
|
||||
component: "AvatarGroup",
|
||||
open: overflowOpen,
|
||||
hiddenCount: hiddenMembers().length
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ label: string = "Breadcrumb"
|
||||
<a
|
||||
href='{homeHref || "/"}'
|
||||
class="wire-breadcrumb__link wire-breadcrumb__home"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: { label: homeLabel, href: homeHref || "/", value: "home" }, itemIndex: -1 } }))'
|
||||
@click='output.select({ item: { label: homeLabel, href: homeHref || "/", value: "home" }, itemIndex: -1 })'
|
||||
>
|
||||
{#if homeIcon === "icon-[lucide--house]"}
|
||||
<span class="icon-[lucide--house] wire-breadcrumb__icon" aria-hidden="true"></span>
|
||||
@@ -72,7 +72,7 @@ label: string = "Breadcrumb"
|
||||
target='{item.target || ""}'
|
||||
rel='{item.external ? "noopener noreferrer" : (item.rel || "")}'
|
||||
class="wire-breadcrumb__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, itemIndex: itemIndex } }))'
|
||||
@click='output.select({ item: item, itemIndex: itemIndex })'
|
||||
>
|
||||
{#if item.icon}
|
||||
<span class='{item.icon} wire-breadcrumb__icon' aria-hidden="true"></span>
|
||||
|
||||
@@ -79,7 +79,7 @@ component Footer {
|
||||
rel='{child.external ? "noopener noreferrer" : (child.rel || "")}'
|
||||
aria-current='{child.active ? "page" : ""}'
|
||||
class="wire-footer__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } })); child.action && event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } }))'
|
||||
@click='output.select({ item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex }); child.action && output.action({ item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex })'
|
||||
>
|
||||
{#if child.icon}
|
||||
<span
|
||||
@@ -127,7 +127,7 @@ component Footer {
|
||||
rel='{item.external ? "noopener noreferrer" : (item.rel || "")}'
|
||||
aria-current='{item.active ? "page" : ""}'
|
||||
class="wire-footer__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, itemIndex: itemIndex } })); item.action && event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: item, itemIndex: itemIndex } }))'
|
||||
@click='output.select({ item: item, itemIndex: itemIndex }); item.action && output.action({ item: item, itemIndex: itemIndex })'
|
||||
>
|
||||
{#if item.icon}
|
||||
<span
|
||||
@@ -199,7 +199,7 @@ component Footer {
|
||||
rel='{child.external ? "noopener noreferrer" : (child.rel || "")}'
|
||||
aria-current='{child.active ? "page" : ""}'
|
||||
class="wire-footer__link"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } })); child.action && event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex } }))'
|
||||
@click='output.select({ item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex }); child.action && output.action({ item: child, parent: item, itemIndex: childIndex, sectionIndex: itemIndex })'
|
||||
>
|
||||
{#if child.icon}
|
||||
<span
|
||||
|
||||
@@ -232,26 +232,17 @@ component InputNumber {
|
||||
)
|
||||
}
|
||||
|
||||
// Outputs are resolved by name, so each one is written out. Raw
|
||||
// CustomEvents dispatched on the root -- what this did before -- never
|
||||
// reach a parent @binding.
|
||||
client function dispatchInputNumberEvent(
|
||||
sourceEvent,
|
||||
eventName,
|
||||
action,
|
||||
previousValue,
|
||||
root,
|
||||
customEvent
|
||||
payload
|
||||
) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-input-number]")
|
||||
|
||||
if (!root && sourceEvent.target) {
|
||||
root = sourceEvent.target.closest("[data-wrn-input-number]")
|
||||
}
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent(eventName, true, false, {
|
||||
payload = {
|
||||
component: "InputNumber",
|
||||
name: name,
|
||||
value: currentValue,
|
||||
@@ -261,8 +252,17 @@ component InputNumber {
|
||||
max: hasMax() ? Number(max) : null,
|
||||
step: normalizedStep(),
|
||||
valid: !isInvalid()
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
if (eventName === "input") {
|
||||
output.input(payload)
|
||||
} else if (eventName === "change") {
|
||||
output.change(payload)
|
||||
} else if (eventName === "increment") {
|
||||
output.increment(payload)
|
||||
} else if (eventName === "decrement") {
|
||||
output.decrement(payload)
|
||||
}
|
||||
}
|
||||
|
||||
client function applyControlValue(nextValue, action, sourceEvent, previousValue) {
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
// and silently swallows the rule that follows it.
|
||||
component LayoutSplitter {
|
||||
outputs {
|
||||
// Not named resize. An output named after a native DOM event never reaches
|
||||
// a parent binding: the component emits it, but @resize on the tag is
|
||||
// never invoked. sizeChange is unambiguous and does arrive.
|
||||
// Named sizeChange rather than resize so it cannot be confused with the
|
||||
// native window event a caller may already be listening for. An earlier
|
||||
// comment here claimed a natively-named output could never reach a parent
|
||||
// binding; that was wrong, and the rename was never what fixed anything.
|
||||
sizeChange(payload: { size: number })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
component List {
|
||||
outputs {
|
||||
select(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
@@ -52,7 +56,7 @@ component List {
|
||||
class:py-2.5='size === "sm"'
|
||||
class:px-5='size === "lg"'
|
||||
class:py-4='size === "lg"'
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))'
|
||||
@click='output.select({ item: item, index: index })'
|
||||
>
|
||||
{#if item.icon}
|
||||
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
component Map {
|
||||
outputs {
|
||||
// markerClick and select both fire for a marker press; select is the
|
||||
// generic name callers reach for, markerClick the explicit one.
|
||||
markerClick(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
select(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
zoom(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
@@ -54,7 +62,7 @@ component Map {
|
||||
aria-label='{item.label || item.title || "Map marker"}'
|
||||
class="absolute inline-flex size-10 items-center justify-center rounded-full border-4 border-[var(--wire-color-surface-raised)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-lg transition hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;'
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("markerClick", { bubbles: true, detail: item })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: item }))'
|
||||
@click='output.markerClick(item); output.select(item)'
|
||||
>
|
||||
<span class='{item.icon || "icon-[lucide--map-pin]"}' aria-hidden="true"></span>
|
||||
</button>
|
||||
@@ -66,7 +74,7 @@ component Map {
|
||||
type="button"
|
||||
aria-label="Zoom in"
|
||||
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "in" } }))'
|
||||
@click='output.zoom({ direction: "in" })'
|
||||
>
|
||||
<span class="icon-[lucide--plus] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
@@ -74,7 +82,7 @@ component Map {
|
||||
type="button"
|
||||
aria-label="Zoom out"
|
||||
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "out" } }))'
|
||||
@click='output.zoom({ direction: "out" })'
|
||||
>
|
||||
<span class="icon-[lucide--minus] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
component Marquee {
|
||||
outputs {
|
||||
// Fired when the reader pauses the scroll, by hover, focus or the button.
|
||||
pause(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
resume(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
@@ -27,8 +33,8 @@ component Marquee {
|
||||
|
||||
<div
|
||||
class="group relative min-w-0 flex-1 overflow-hidden"
|
||||
@mouseenter='paused = true; event.currentTarget.dispatchEvent(new CustomEvent("pause", { bubbles: true }))'
|
||||
@mouseleave='paused = false; event.currentTarget.dispatchEvent(new CustomEvent("resume", { bubbles: true }))'
|
||||
@mouseenter='paused = true; output.pause({})'
|
||||
@mouseleave='paused = false; output.resume({})'
|
||||
@focusin='paused = true'
|
||||
@focusout='paused = false'
|
||||
>
|
||||
@@ -77,7 +83,7 @@ component Marquee {
|
||||
type="button"
|
||||
aria-label='{paused ? "Resume announcements" : "Pause announcements"}'
|
||||
class="flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] px-4 text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='paused = !paused; event.currentTarget.dispatchEvent(new CustomEvent(paused ? "pause" : "resume", { bubbles: true }))'
|
||||
@click='paused = !paused; paused ? output.pause({}) : output.resume({})'
|
||||
>
|
||||
<span class="icon-[lucide--pause] size-4" data-show='!paused' aria-hidden="true"></span>
|
||||
<span class="icon-[lucide--play] size-4" data-show='paused' aria-hidden="true"></span>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
component SearchBox {
|
||||
outputs {
|
||||
search(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
clear(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
@@ -22,7 +27,7 @@ component SearchBox {
|
||||
data-ui-component="SearchBox"
|
||||
role="search"
|
||||
class='w-full {class}'
|
||||
@submit='event.preventDefault(); event.currentTarget.dispatchEvent(new CustomEvent("search", { bubbles: true, detail: { value: query, name: name } }))'
|
||||
@submit='event.preventDefault(); output.search({ value: query, name: name })'
|
||||
>
|
||||
<label
|
||||
class="mb-2 block text-sm font-semibold text-[var(--wire-color-text)]"
|
||||
@@ -61,7 +66,7 @@ component SearchBox {
|
||||
aria-label="Clear search"
|
||||
data-show='query.length > 0 && !disabled'
|
||||
class="absolute right-12 inline-flex size-8 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
|
||||
@click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); event.currentTarget.dispatchEvent(new CustomEvent("clear", { bubbles: true, detail: { value: "", name: name } }))'
|
||||
@click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); output.clear({ value: "", name: name })'
|
||||
>
|
||||
<span class="icon-[lucide--x] size-4" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
component Timeline {
|
||||
outputs {
|
||||
select(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
@@ -30,7 +34,7 @@ component Timeline {
|
||||
{#each items as item, index}
|
||||
<li
|
||||
class="relative pb-8 pl-8 last:pb-0"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))'
|
||||
@click='output.select({ item: item, index: index })'
|
||||
>
|
||||
<span
|
||||
class="absolute -left-[1.05rem] top-0 flex size-8 items-center justify-center rounded-full border-4 border-[var(--wire-color-background)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-sm"
|
||||
|
||||
@@ -57,34 +57,17 @@ component ToggleCount {
|
||||
: emptyValue
|
||||
}
|
||||
|
||||
client function dispatchToggleEvent(sourceEvent, previousValue, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
client function dispatchToggleEvent(sourceEvent, previousValue, payload) {
|
||||
payload = {
|
||||
component: "ToggleCount",
|
||||
name: name,
|
||||
value: selectedValue,
|
||||
previousValue: previousValue,
|
||||
firstValue: firstValue,
|
||||
secondValue: secondValue
|
||||
}
|
||||
|
||||
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)
|
||||
output.change(payload)
|
||||
output.toggle(payload)
|
||||
}
|
||||
|
||||
client function selectValue(sourceEvent, nextValue, previousValue) {
|
||||
|
||||
@@ -35,22 +35,23 @@ component Alert {
|
||||
state visible: boolean = true
|
||||
|
||||
functions {
|
||||
client 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, {
|
||||
// The output name has to be written out rather than computed: outputs are
|
||||
// resolved as named properties, so a dynamic key would not reach a parent
|
||||
// binding. Only two names exist here, so a branch is honest and cheap.
|
||||
client function dispatchAlertEvent(sourceEvent, eventName, action, payload) {
|
||||
payload = {
|
||||
component: "Alert",
|
||||
title: title,
|
||||
color: color,
|
||||
variant: variant,
|
||||
action: action
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
if (eventName === "dismiss") {
|
||||
output.dismiss(payload)
|
||||
} else {
|
||||
output.action(payload)
|
||||
}
|
||||
}
|
||||
|
||||
client function dismissAlert(sourceEvent) {
|
||||
|
||||
@@ -35,15 +35,13 @@ component Badge {
|
||||
state visible: boolean = true
|
||||
|
||||
functions {
|
||||
client function dismissBadge(sourceEvent, root, customEvent) {
|
||||
// output.dismiss reaches a parent @dismiss binding; a raw dispatchEvent on
|
||||
// the root does not. The runtime registers parent handlers in a registry
|
||||
// that only the output proxy consults, so the CustomEvent this used to
|
||||
// build bubbled past every binding and was never seen by anyone.
|
||||
client function dismissBadge() {
|
||||
visible = false
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-badge]")
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("dismiss", true, false, {
|
||||
component: "Badge",
|
||||
label: label
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
output.dismiss({ component: "Badge", label: label })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,67 +45,27 @@ component Card {
|
||||
state dismissed: boolean = false
|
||||
|
||||
functions {
|
||||
client function dispatchCardNavigation(sourceEvent, item, index, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("navigate", true, false, {
|
||||
component: "Card",
|
||||
item: item,
|
||||
index: index
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
// 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, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("navigate", true, false, {
|
||||
client function dispatchCardNavigationValue(sourceEvent) {
|
||||
output.navigate({
|
||||
component: "Card",
|
||||
value: sourceEvent.currentTarget.value
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
}
|
||||
|
||||
client function dispatchCardHeaderAction(sourceEvent, action, index, root, customEvent) {
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("action", true, false, {
|
||||
component: "Card",
|
||||
action: action,
|
||||
index: index
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
client function dispatchCardHeaderAction(sourceEvent, action, index) {
|
||||
output.action({ component: "Card", action: action, index: index })
|
||||
}
|
||||
|
||||
client function dismissCard(sourceEvent, root, customEvent) {
|
||||
client function dismissCard() {
|
||||
dismissed = true
|
||||
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
|
||||
|
||||
if (!root) {
|
||||
return
|
||||
}
|
||||
|
||||
customEvent = document.createEvent("CustomEvent")
|
||||
customEvent.initCustomEvent("dismiss", true, false, {
|
||||
component: "Card",
|
||||
title: title
|
||||
})
|
||||
root.dispatchEvent(customEvent)
|
||||
output.dismiss({ component: "Card", title: title })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +97,8 @@ component Card {
|
||||
alt='{item.imageAlt || item.alt || ""}'
|
||||
loading='{item.loading || "lazy"}'
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { item: item, index: itemIndex } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { item: item, index: itemIndex } }))'
|
||||
@load='output.load({ item: item, index: itemIndex })'
|
||||
@error='output.error({ item: item, index: itemIndex })'
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -157,7 +117,7 @@ component Card {
|
||||
<a
|
||||
href='{item.actionHref || item.href || "#"}'
|
||||
class="wire-next__card-action"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: item, index: itemIndex } }))'
|
||||
@click='output.action({ item: item, index: itemIndex })'
|
||||
>
|
||||
<span>{item.actionLabel || "Learn more"}</span>
|
||||
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
|
||||
@@ -180,8 +140,8 @@ component Card {
|
||||
alt='{imageAlt}'
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@load='output.load({ src: imageSrc })'
|
||||
@error='output.error({ src: imageSrc })'
|
||||
/>
|
||||
<div class="wire-next__card-overlay-shade" aria-hidden="true"></div>
|
||||
</div>
|
||||
@@ -194,8 +154,8 @@ component Card {
|
||||
alt='{imageAlt}'
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@load='output.load({ src: imageSrc })'
|
||||
@error='output.error({ src: imageSrc })'
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -323,7 +283,7 @@ component Card {
|
||||
<a
|
||||
href='{actionHref || "#"}'
|
||||
class="wire-next__card-action"
|
||||
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { href: actionHref, label: actionLabel } }))'
|
||||
@click='output.action({ href: actionHref, label: actionLabel })'
|
||||
>
|
||||
<span>{actionLabel}</span>
|
||||
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
|
||||
@@ -340,8 +300,8 @@ component Card {
|
||||
alt='{imageAlt}'
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
|
||||
@load='output.load({ src: imageSrc })'
|
||||
@error='output.error({ src: imageSrc })'
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user