fix: Resolve replaceChild and activeElement errors in FolkJS components

- Fix activeElement undefined error by guarding against missing shadowRoot
- Fix replaceChild "parameter 2 is not of type Node" error in all 15 child
  components by using :scope > div selector to find container div directly
  instead of incorrectly searching inside slot.parentElement

The bug was caused by looking for a nested div that doesn't exist - the slot's
parent IS the container div that needs to be replaced.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-01-03 13:38:06 +01:00
parent 8eef5b58b7
commit 42b29ff9d7
16 changed files with 66 additions and 102 deletions

View File

@ -269,13 +269,10 @@ export class FolkCalendar extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
// Get element references // Get element references

View File

@ -238,13 +238,10 @@ export class FolkChat extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
// Get element references // Get element references

View File

@ -258,13 +258,10 @@ export class FolkEmbed extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
const content = wrapper.querySelector(".content") as HTMLElement; const content = wrapper.querySelector(".content") as HTMLElement;

View File

@ -229,13 +229,11 @@ export class FolkGoogleItem extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our card
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; const cardEl = wrapper.querySelector(".card");
const existingDiv = parent.querySelector("div"); if (containerDiv && cardEl) {
if (existingDiv) { containerDiv.replaceWith(cardEl);
parent.replaceChild(wrapper.querySelector(".card")!, existingDiv);
}
} }
// Toggle visibility on badge click // Toggle visibility on badge click

View File

@ -264,13 +264,10 @@ export class FolkImageGen extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#promptInput = wrapper.querySelector(".prompt-input"); this.#promptInput = wrapper.querySelector(".prompt-input");

View File

@ -308,13 +308,10 @@ export class FolkMap extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#mapEl = wrapper.querySelector(".map"); this.#mapEl = wrapper.querySelector(".map");

View File

@ -173,10 +173,10 @@ export class FolkMarkdown extends FolkShape {
</div> </div>
`; `;
// Move existing slot content into our wrapper // Replace the container div (slot's parent) with our wrapper
const slot = root.querySelector("slot"); const containerDiv = root.querySelector(":scope > div");
if (slot) { if (containerDiv) {
slot.parentElement?.replaceChild(wrapper, slot.parentElement.querySelector("div")!); containerDiv.replaceWith(wrapper);
} }
// Get references to elements // Get references to elements

View File

@ -366,13 +366,10 @@ export class FolkObsNote extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#editor = wrapper.querySelector(".editor"); this.#editor = wrapper.querySelector(".editor");

View File

@ -195,13 +195,11 @@ export class FolkPiano extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our piano container
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; const pianoContainer = wrapper.querySelector(".piano-container");
const existingDiv = parent.querySelector("div"); if (containerDiv && pianoContainer) {
if (existingDiv) { containerDiv.replaceWith(pianoContainer);
parent.replaceChild(wrapper.querySelector(".piano-container")!, existingDiv);
}
} }
// Get references // Get references

View File

@ -293,13 +293,10 @@ export class FolkPrompt extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#messagesEl = wrapper.querySelector(".messages"); this.#messagesEl = wrapper.querySelector(".messages");

View File

@ -356,7 +356,9 @@ export class FolkShape extends FolkElement {
return; return;
} }
const focusedElement = (this.renderRoot as ShadowRoot).activeElement as HTMLElement | null; const shadowRoot = this.renderRoot as ShadowRoot | undefined;
if (!shadowRoot) return;
const focusedElement = shadowRoot.activeElement as HTMLElement | null;
const target = event.composedPath()[0] as HTMLElement; const target = event.composedPath()[0] as HTMLElement;
let handle: Handle | null = null; let handle: Handle | null = null;
if (target) { if (target) {

View File

@ -88,12 +88,11 @@ export class FolkSlide extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our slide container
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
slot.parentElement.replaceChild( const slideContainer = wrapper.querySelector(".slide-container");
wrapper.querySelector(".slide-container")!, if (containerDiv && slideContainer) {
slot.parentElement.querySelector("div")! containerDiv.replaceWith(slideContainer);
);
} }
// Update label from attribute // Update label from attribute

View File

@ -349,13 +349,10 @@ export class FolkTranscription extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#recordBtn = wrapper.querySelector(".record-btn"); this.#recordBtn = wrapper.querySelector(".record-btn");

View File

@ -325,13 +325,10 @@ export class FolkVideoChat extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
const content = wrapper.querySelector(".content") as HTMLElement; const content = wrapper.querySelector(".content") as HTMLElement;

View File

@ -359,13 +359,10 @@ export class FolkVideoGen extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#promptInput = wrapper.querySelector(".prompt-input"); this.#promptInput = wrapper.querySelector(".prompt-input");

View File

@ -340,13 +340,10 @@ export class FolkWorkflowBlock extends FolkShape {
</div> </div>
`; `;
const slot = root.querySelector("slot"); // Replace the container div (slot's parent) with our wrapper
if (slot?.parentElement) { const containerDiv = root.querySelector(":scope > div");
const parent = slot.parentElement; if (containerDiv) {
const existingDiv = parent.querySelector("div"); containerDiv.replaceWith(wrapper);
if (existingDiv) {
parent.replaceChild(wrapper, existingDiv);
}
} }
this.#contentEl = wrapper.querySelector(".content"); this.#contentEl = wrapper.querySelector(".content");