e.stopPropagation()} onclick={() => onSelectNode(tree)} oncontextmenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(node, tree, parentTree, e.clientX, e.clientY); }} draggable={true} ondragstart={(e) => { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", ""); e.currentTarget.classList.add("pcr-nettree-row--dragging"); e.currentTarget._dragTree = tree; e.currentTarget._dragParent = parentTree; }} ondragend={(e) => { e.currentTarget.classList.remove("pcr-nettree-row--dragging"); }} ondragover={(e) => { const src = e.currentTarget.closest(".pcr-nettree-items")?.querySelector(".pcr-nettree-row--dragging"); if (!src || src._dragTree?.node === node) return; e.preventDefault(); e.dataTransfer.dropEffect = "move"; const rect = e.currentTarget.getBoundingClientRect(); const y = e.clientY - rect.top; const third = rect.height / 3; e.currentTarget.closest(".pcr-nettree-items")?.querySelectorAll(".pcr-nettree-row").forEach(r => r.classList.remove("pcr-nettree-row--drop-before", "pcr-nettree-row--drop-after", "pcr-nettree-row--drop-onto")); if (y < third) e.currentTarget.classList.add("pcr-nettree-row--drop-before"); else if (y > third * 2) e.currentTarget.classList.add("pcr-nettree-row--drop-after"); else e.currentTarget.classList.add("pcr-nettree-row--drop-onto"); }} ondragleave={(e) => { e.currentTarget.classList.remove("pcr-nettree-row--drop-before", "pcr-nettree-row--drop-after", "pcr-nettree-row--drop-onto"); }} ondrop={(e) => { e.preventDefault(); const src = e.currentTarget.closest(".pcr-nettree-items")?.querySelector(".pcr-nettree-row--dragging"); if (!src || src._dragTree?.node === node) return; const rect = e.currentTarget.getBoundingClientRect(); const y = e.clientY - rect.top; const third = rect.height / 3; const position = y < third ? "before" : y > third * 2 ? "after" : "into"; const sameParent = src._dragParent?.node === parentTree?.node; // Drop-after on an expanded parent row means "the gap between parent and // its first child" — file-explorer convention is prepend-as-first-child, // not sibling-of-parent. if (position === "after" && tree.hasChildren && !isCollapsed) { onDragDrop("reparent", src._dragTree, tree, src._dragParent, parentTree, "first-child"); } else if (position !== "into" && sameParent && !isRoot) { onDragDrop("reorder", src._dragTree, tree, src._dragParent, parentTree, position); } else { onDragDrop("reparent", src._dragTree, tree, src._dragParent, parentTree, position); } }} > {#if tree.hasChildren} {isCollapsed ? "\u25B6" : "\u25BC"} {/if} {#if !isRoot && parentTree} e.stopPropagation()} onclick={handleIndicatorClick} > {#if parentMode === "switch"} {isSelected ? "\u2705" : "\u2610"} {:else} {MODE_EMOJI[parentMode] || ""} {/if} {/if} {#if isRenaming} e.stopPropagation()} onclick={(e) => e.stopPropagation()} onblur={commitRename} onkeydown={(e) => { e.stopPropagation(); if (e.key === "Enter") { e.preventDefault(); commitRename(); } else if (e.key === "Escape") { e.preventDefault(); cancelRename(); } }} /> {:else} {tree.title} {/if} { e.stopPropagation(); onToggleLock(node); }}>{"\u{1F512}"} { e.stopPropagation(); onToggleDisable(node); }}>{"\u2298"}
{#if tree.hasChildren && !isCollapsed}
{#each tree.children as child} {/each} {#if tree.labels.length > 0}
l.index === switchIndex)} onpointerdown={(e) => e.stopPropagation()} onclick={() => { // Always open the owning tab first — onLabelClick jumps the // cursor in whatever editor is currently active, so the tab // must be swapped before we dispatch the selection. onSelectNode(tree); const sel = tree.labels.find(l => l.index === switchIndex); if (sel) onLabelClick(node, sel.index); }} oncontextmenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(node, tree, parentTree, e.clientX, e.clientY); }} > e.stopPropagation()} onclick={(e) => { e.stopPropagation(); onLabelClick(node, null, e.currentTarget); }} > {#if tree.labels.some(l => l.index === switchIndex)} {"\u2705"} {:else if mode === "switch" && switchIndex === 0} {"\u274C"} {:else} {MODE_EMOJI[mode] || "\u{1F3B2}"} {/if} {#if tree.labels.some(l => l.index === switchIndex)} {tree.labels.find(l => l.index === switchIndex)?.label} {:else if mode === "switch" && switchIndex === 0} None {:else} {tree.labels.length} options {/if}
{/if} {#each tree.wildcards as wc} {@const keyName = wc.name.includes("/") ? wc.name.split("/").pop() : wc.name} {@const hasRolled = wc.mode === "randomize" && wc.rolledLabel}
e.stopPropagation()} onclick={() => onSelectNode(tree, wc.name)} oncontextmenu={(e) => { e.preventDefault(); e.stopPropagation(); onSelectNode(tree, wc.name); }}> e.stopPropagation()} onclick={(e) => { e.stopPropagation(); onWildcardModeClick(node, wc.name, e.currentTarget); }} >{wc.mode === "switch" && wc.index > 0 ? "\u2705" : MODE_EMOJI[wc.mode === "randomize" ? "roll" : wc.mode] || "\u{1F3B2}"} 0 ? MODE_COLORS.switch : wc.mode === "combine" ? MODE_COLORS.combine : wc.mode === "iterate" ? MODE_COLORS.iterate : wc.mode === "none" ? "#b0b0b0" : MODE_COLORS.roll} > {#if hasRolled} {wc.rolledLabel} {:else if wc.mode === "switch" && wc.label} {keyName}: {wc.label} {:else if wc.mode === "combine"} {keyName} (all) {:else if wc.mode === "none"} {keyName} (off) {:else} {keyName} {/if}
{/each}
{/if}