fix(showcase): break wn-breadcrumb slotchange infinite loop (WHYNOT-WP-0002 T11)
WnBreadcrumb._onSlot inserted separator <span>s into its own light DOM on slotchange but cleaned up in the shadow DOM, so they were never removed — each insertion re-fired slotchange, looping the main thread and wedging the showcase page. Made _onSlot idempotent: exclude own separators when reading items, and mutate only when separators are not already correct. - Un-fixme the showcase visual test; add a warm-up full-page capture so deviceScaleFactor-2 sub-pixel snapping settles before the assertion. All 5 visual tests pass. - Remove the dead Google-Fonts @import from colors_and_type.css (token stacks are system-ui; webfont unused + a CI-flake source; no visual change). - Unblocks WHYNOT-WP-0003 T08 (showcase = per-version visual catalog); both T11 and T08 marked done. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -236,11 +236,23 @@ export class WnEmptyState extends WnBase {
|
||||
export class WnBreadcrumb extends WnBase {
|
||||
_onSlot(e) {
|
||||
const slot = e.target;
|
||||
const items = slot.assignedElements({ flatten: true });
|
||||
// Build the rendered tree: each item + a separator after it.
|
||||
const wrapper = this.shadowRoot?.querySelector('.wn-breadcrumb__list');
|
||||
if (!wrapper) return;
|
||||
wrapper.querySelectorAll('.wn-breadcrumb__sep').forEach(s => s.remove());
|
||||
// Separators are inserted into the LIGHT DOM (so they sit in document order
|
||||
// between the slotted items), which re-fires this slotchange. We must
|
||||
// therefore be idempotent: exclude our own separators when reading items,
|
||||
// and skip all mutation once the separators are already correct — otherwise
|
||||
// each insertion retriggers slotchange and the main thread loops forever.
|
||||
const items = slot.assignedElements({ flatten: true })
|
||||
.filter((el) => !el.classList.contains("wn-breadcrumb__sep"));
|
||||
const existing = [...this.querySelectorAll(":scope > .wn-breadcrumb__sep")];
|
||||
|
||||
if (existing.length === Math.max(0, items.length - 1)) {
|
||||
// Structure already correct — only refresh the "current" marker, do not
|
||||
// touch the child list (no mutation ⇒ no slotchange re-fire ⇒ loop ends).
|
||||
items.forEach((el, i) => el.classList.toggle("wn-breadcrumb__current", i === items.length - 1));
|
||||
return;
|
||||
}
|
||||
|
||||
existing.forEach((s) => s.remove());
|
||||
items.forEach((el, i) => {
|
||||
el.classList.toggle("wn-breadcrumb__current", i === items.length - 1);
|
||||
if (i > 0) {
|
||||
@@ -248,8 +260,6 @@ export class WnBreadcrumb extends WnBase {
|
||||
sep.className = "wn-breadcrumb__sep";
|
||||
sep.setAttribute("aria-hidden", "true");
|
||||
sep.textContent = "/";
|
||||
// Use light-DOM-relative insertion: items are still in light DOM,
|
||||
// so DOM-order separators between them belong in light DOM too.
|
||||
el.parentNode.insertBefore(sep, el);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user