refactor: split tree shell dom runtime
This commit is contained in:
@@ -20,6 +20,14 @@ import {
|
||||
getFileTreeRowOwnerDocumentId,
|
||||
normalizeTreeShellTreeItems,
|
||||
} from "./tree-shell-filetree-runtime.js";
|
||||
import {
|
||||
focusTreeShellPickerRowElement,
|
||||
hydrateTreeShellInitialPageTree,
|
||||
hydrateTreeShellInitialPickerTree,
|
||||
patchTreeShellPageActiveDom,
|
||||
patchTreeShellPageExpansionDom,
|
||||
patchTreeShellPickerActiveDom,
|
||||
} from "./tree-shell-dom-runtime.js";
|
||||
import {
|
||||
computeTreeShellPickerStateActionResult,
|
||||
getTreeShellVisiblePickerEntries,
|
||||
@@ -2943,90 +2951,38 @@ function startTreeShellRuntime() {
|
||||
});
|
||||
};
|
||||
|
||||
const patchPageTreeActiveDom = () => {
|
||||
if (mode !== "page") return;
|
||||
appElement.querySelectorAll('[data-rust-rendered-row="page"]').forEach((row) => {
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
const nodeId = normalizeText(row.dataset.nodeId);
|
||||
const isFocused = nodeId === focusedNodeId;
|
||||
row.dataset.active = String(nodeId === currentActiveDocumentId);
|
||||
row.dataset.focused = String(isFocused);
|
||||
row.tabIndex = isFocused ? 0 : -1;
|
||||
row.dataset.dropFeedback = String(activePageDropNodeId === nodeId);
|
||||
const patchPageTreeActiveDom = () =>
|
||||
patchTreeShellPageActiveDom({
|
||||
mode,
|
||||
appElement,
|
||||
normalizeText,
|
||||
focusedNodeId,
|
||||
currentActiveDocumentId,
|
||||
activePageDropNodeId,
|
||||
});
|
||||
};
|
||||
|
||||
const patchPageTreeExpansionDom = (nodeId) => {
|
||||
if (mode !== "page") return false;
|
||||
const normalizedNodeId = normalizeText(nodeId);
|
||||
if (!normalizedNodeId) return false;
|
||||
const item = itemById.get(normalizedNodeId);
|
||||
if (!item) return false;
|
||||
const nodeElement = appElement.querySelector(
|
||||
`.tree-node[data-node-id="${CSS.escape(normalizedNodeId)}"]`,
|
||||
);
|
||||
if (!(nodeElement instanceof HTMLElement)) return false;
|
||||
const row = nodeElement.querySelector(
|
||||
`:scope > .tree-row[data-node-id="${CSS.escape(normalizedNodeId)}"]`,
|
||||
);
|
||||
const children = getSiblings(normalizedNodeId);
|
||||
const hasChildren = item.childCount > 0 && children.length > 0;
|
||||
const isExpanded = hasChildren && expanded.has(normalizedNodeId);
|
||||
if (row instanceof HTMLElement) {
|
||||
row.setAttribute("aria-expanded", hasChildren ? String(isExpanded) : "false");
|
||||
const toggleButton = row.querySelector('[data-testid="tree-node-toggle"]');
|
||||
if (toggleButton instanceof HTMLButtonElement) {
|
||||
toggleButton.textContent = isExpanded ? "▾" : "▸";
|
||||
toggleButton.setAttribute(
|
||||
"aria-label",
|
||||
`${isExpanded ? "折叠" : "展开"} ${item.title}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!hasChildren) {
|
||||
patchPageTreeActiveDom();
|
||||
return true;
|
||||
}
|
||||
let childrenList = Array.from(nodeElement.children).find(
|
||||
(child) => child instanceof HTMLElement && child.classList.contains("tree-children"),
|
||||
);
|
||||
if (isExpanded) {
|
||||
if (!(childrenList instanceof HTMLElement)) {
|
||||
childrenList = document.createElement("ul");
|
||||
childrenList.className = "tree-children";
|
||||
children.forEach((child) => {
|
||||
childrenList.appendChild(renderNode(child));
|
||||
});
|
||||
nodeElement.appendChild(childrenList);
|
||||
}
|
||||
childrenList.hidden = false;
|
||||
childrenList.style.display = "";
|
||||
} else if (childrenList instanceof HTMLElement) {
|
||||
childrenList.hidden = true;
|
||||
childrenList.style.display = "none";
|
||||
}
|
||||
patchPageTreeActiveDom();
|
||||
return true;
|
||||
};
|
||||
const patchPageTreeExpansionDom = (nodeId) =>
|
||||
patchTreeShellPageExpansionDom({
|
||||
mode,
|
||||
appElement,
|
||||
normalizeText,
|
||||
itemById,
|
||||
expanded,
|
||||
getSiblings,
|
||||
renderNode,
|
||||
patchPageTreeActiveDom,
|
||||
}, nodeId);
|
||||
|
||||
const hydrateInitialPageTree = () => {
|
||||
if (mode !== "page") return false;
|
||||
const root = appElement.querySelector('[data-rust-page-renderer="initial_v1"]');
|
||||
if (!(root instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
root.querySelectorAll('[data-rust-rendered-row="page"]').forEach((row) => {
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
const nodeId = normalizeText(row.dataset.nodeId);
|
||||
const item = itemById.get(nodeId);
|
||||
if (!item) return;
|
||||
bindPageRowEvents(row, item);
|
||||
const hydrateInitialPageTree = () =>
|
||||
hydrateTreeShellInitialPageTree({
|
||||
mode,
|
||||
appElement,
|
||||
normalizeText,
|
||||
itemById,
|
||||
bindPageRowEvents,
|
||||
focusedNodeId,
|
||||
focusRowElement,
|
||||
});
|
||||
if (focusedNodeId) {
|
||||
focusRowElement(focusedNodeId);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const syncFileTreeSelectionDom = () => {
|
||||
if (mode !== "filetree") return;
|
||||
@@ -3333,56 +3289,30 @@ function startTreeShellRuntime() {
|
||||
});
|
||||
};
|
||||
|
||||
const focusPickerRowElement = (pickerItemKey) => {
|
||||
const normalizedItemKey = normalizeText(pickerItemKey);
|
||||
window.requestAnimationFrame(() => {
|
||||
const row =
|
||||
normalizedItemKey === "__root__"
|
||||
? appElement.querySelector('[data-rust-rendered-row="picker-root"]')
|
||||
: appElement.querySelector(
|
||||
`.tree-row[data-node-id="${CSS.escape(normalizedItemKey)}"]`,
|
||||
);
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
row.focus({ preventScroll: true });
|
||||
row.scrollIntoView({ block: "nearest" });
|
||||
});
|
||||
};
|
||||
const focusPickerRowElement = (pickerItemKey) =>
|
||||
focusTreeShellPickerRowElement({
|
||||
appElement,
|
||||
normalizeText,
|
||||
}, pickerItemKey);
|
||||
|
||||
const patchPickerActiveDom = () => {
|
||||
if (mode !== "picker") return;
|
||||
appElement
|
||||
.querySelectorAll('[data-rust-rendered-row="picker"], [data-rust-rendered-row="picker-root"]')
|
||||
.forEach((row) => {
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
const nodeId = normalizeText(row.dataset.nodeId);
|
||||
const isRoot = row.dataset.rustRenderedRow === "picker-root";
|
||||
const isFocused = isRoot
|
||||
? currentActivePickerItemKey === "__root__"
|
||||
: currentActivePickerItemKey === nodeId ||
|
||||
(!currentActivePickerItemKey && currentActiveDocumentId === nodeId);
|
||||
row.dataset.focused = String(isFocused);
|
||||
row.tabIndex = isFocused ? 0 : -1;
|
||||
});
|
||||
};
|
||||
const patchPickerActiveDom = () =>
|
||||
patchTreeShellPickerActiveDom({
|
||||
mode,
|
||||
appElement,
|
||||
normalizeText,
|
||||
currentActivePickerItemKey,
|
||||
currentActiveDocumentId,
|
||||
});
|
||||
|
||||
const hydrateInitialPickerTree = () => {
|
||||
if (mode !== "picker") return false;
|
||||
const root = appElement.querySelector('[data-rust-picker-renderer="initial_v1"]');
|
||||
if (!(root instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
root.querySelectorAll('[data-rust-rendered-row="picker-root"]').forEach((row) => {
|
||||
bindPickerRootEvents(row);
|
||||
const hydrateInitialPickerTree = () =>
|
||||
hydrateTreeShellInitialPickerTree({
|
||||
mode,
|
||||
appElement,
|
||||
normalizeText,
|
||||
itemById,
|
||||
bindPickerRootEvents,
|
||||
bindPickerRowEvents,
|
||||
});
|
||||
root.querySelectorAll('[data-rust-rendered-row="picker"]').forEach((row) => {
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
const nodeId = normalizeText(row.dataset.nodeId);
|
||||
const item = itemById.get(nodeId);
|
||||
if (!item) return;
|
||||
bindPickerRowEvents(row, item);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
const hydrateInitialRenderer = () => {
|
||||
if (mode === "page") {
|
||||
|
||||
Reference in New Issue
Block a user