HEL-430: replace directory search picker with collapsible tree
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
8ceafe70b1
commit
fbafb8015d
+2
-7
@@ -167,13 +167,8 @@ function directoryPickerItems(array $dirs): array
|
||||
}
|
||||
unset($children);
|
||||
$pathMap = dirPathMap($dirs);
|
||||
$nameCounts = [];
|
||||
foreach ($dirs as $d) {
|
||||
$n = (string) $d['name'];
|
||||
$nameCounts[$n] = ($nameCounts[$n] ?? 0) + 1;
|
||||
}
|
||||
$out = [];
|
||||
$walk = static function (int $parent) use (&$walk, $byParent, $pathMap, $nameCounts, &$out): void {
|
||||
$walk = static function (int $parent) use (&$walk, $byParent, $pathMap, &$out): void {
|
||||
foreach ($byParent[$parent] ?? [] as $d) {
|
||||
$id = (int) $d['id'];
|
||||
$name = (string) $d['name'];
|
||||
@@ -182,7 +177,7 @@ function directoryPickerItems(array $dirs): array
|
||||
'name' => $name,
|
||||
'path' => (string) ($pathMap[$id] ?? $name),
|
||||
'level' => (int) ($d['level'] ?? 1),
|
||||
'dup' => ($nameCounts[$name] ?? 0) > 1,
|
||||
'parent_id' => $parent,
|
||||
];
|
||||
$walk($id);
|
||||
}
|
||||
|
||||
+31
-15
@@ -287,28 +287,44 @@ img { display: block; }
|
||||
max-height: min(420px, calc(100vh - 16px));
|
||||
padding: 0; overflow: hidden;
|
||||
}
|
||||
.v-fusion .dir-picker-search {
|
||||
padding: 8px; border-bottom: 1px solid var(--hairline);
|
||||
background: var(--surface);
|
||||
}
|
||||
.v-fusion[data-theme="night"] .dir-picker-search { background: var(--surface-2); }
|
||||
.v-fusion .dir-picker-search .search { min-width: 0; width: 100%; height: 32px; }
|
||||
.v-fusion .dir-picker-list {
|
||||
overflow-y: auto; overscroll-behavior: contain; padding: 6px;
|
||||
flex: 1; min-height: 0;
|
||||
}
|
||||
.v-fusion .dir-picker-group {
|
||||
font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
|
||||
color: var(--text-3); padding: 8px 10px 4px;
|
||||
.v-fusion .dir-picker-reset {
|
||||
margin: 0 0 4px; padding: 0 0 4px;
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
}
|
||||
.v-fusion .dir-picker-opt {
|
||||
display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
|
||||
white-space: normal;
|
||||
.v-fusion .dir-tree-row {
|
||||
display: flex; align-items: center; gap: 2px;
|
||||
padding-left: calc(var(--dir-indent, 0) * 12px);
|
||||
}
|
||||
.v-fusion .dir-tree-toggle {
|
||||
flex: 0 0 24px; width: 24px; height: 24px; border-radius: 7px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
color: var(--text-3);
|
||||
transition: background-color 120ms ease, color 120ms ease, transform 160ms var(--ease-out);
|
||||
}
|
||||
.v-fusion .dir-tree-toggle.is-leaf { pointer-events: none; }
|
||||
.v-fusion .dir-tree-toggle:active:not(.is-leaf) { transform: scale(0.94); }
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.v-fusion .dir-tree-toggle:hover:not(.is-leaf) { background: var(--hover); color: var(--text); }
|
||||
}
|
||||
.v-fusion .dir-tree-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
|
||||
.v-fusion .dir-tree-chevron {
|
||||
display: block; width: 12px; height: 12px;
|
||||
transition: transform 120ms var(--ease-out);
|
||||
}
|
||||
.v-fusion .dir-tree-toggle[aria-expanded="true"] .dir-tree-chevron { transform: rotate(90deg); }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.v-fusion .dir-tree-toggle, .v-fusion .dir-tree-chevron { transition: none; }
|
||||
}
|
||||
.v-fusion .dir-tree-row .dir-picker-opt {
|
||||
flex: 1; min-width: 0;
|
||||
display: block; white-space: nowrap;
|
||||
overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.v-fusion .dir-picker-opt-name { font-size: 13px; line-height: 1.35; }
|
||||
.v-fusion .dir-picker-opt-path {
|
||||
font-size: 11.5px; color: var(--text-3); font-weight: 400; line-height: 1.3;
|
||||
}
|
||||
.v-fusion .dir-picker-opt.is-active:not([aria-selected="true"]) { background: var(--hover); }
|
||||
|
||||
/* ============================================================
|
||||
|
||||
+185
-108
@@ -513,7 +513,7 @@ let dpView = null;
|
||||
let dpFocusYmd = null;
|
||||
let popRepositionBound = false;
|
||||
let dirPickerItems = [];
|
||||
let dirPickerQuery = "";
|
||||
let dirExpanded = new Set();
|
||||
|
||||
function popRoot() {
|
||||
return root() || document.body;
|
||||
@@ -541,7 +541,7 @@ function ensurePops() {
|
||||
dirPopEl.id = "jn-dir-picker";
|
||||
dirPopEl.className = "jn-pop dir-picker-pop";
|
||||
dirPopEl.hidden = true;
|
||||
dirPopEl.setAttribute("role", "listbox");
|
||||
dirPopEl.setAttribute("role", "tree");
|
||||
dirPopEl.setAttribute("aria-label", "选择归档目录");
|
||||
host.appendChild(menuEl);
|
||||
host.appendChild(dpEl);
|
||||
@@ -664,100 +664,153 @@ function commitSelect(index) {
|
||||
select.focus();
|
||||
}
|
||||
|
||||
const DIR_RECENT_KEY = "jn_recent_dirs";
|
||||
function dirRecentIds() {
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem(DIR_RECENT_KEY) || "[]");
|
||||
return Array.isArray(raw) ? raw.map((x) => +x).filter((id) => id > 0) : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
function dirParentId(item) {
|
||||
return +(item && item.parent_id ? item.parent_id : 0);
|
||||
}
|
||||
function rememberDir(id) {
|
||||
function dirByParentMap() {
|
||||
const map = new Map();
|
||||
dirPickerItems.forEach((it) => {
|
||||
const pid = dirParentId(it);
|
||||
if (!map.has(pid)) map.set(pid, []);
|
||||
map.get(pid).push(it);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
function dirItemById(id) {
|
||||
id = +id;
|
||||
if (!id) return;
|
||||
const next = [id, ...dirRecentIds().filter((x) => x !== id)].slice(0, 6);
|
||||
try { localStorage.setItem(DIR_RECENT_KEY, JSON.stringify(next)); } catch (e) {}
|
||||
return dirPickerItems.find((it) => +it.id === id) || null;
|
||||
}
|
||||
function dirMatches(item, q) {
|
||||
if (!q) return true;
|
||||
const n = (item.name || "").toLowerCase();
|
||||
const p = (item.path || "").toLowerCase();
|
||||
return n.includes(q) || p.includes(q);
|
||||
function dirAncestorIds(id) {
|
||||
const out = [];
|
||||
let cur = dirItemById(id);
|
||||
while (cur) {
|
||||
const pid = dirParentId(cur);
|
||||
if (!pid) break;
|
||||
out.push(pid);
|
||||
cur = dirItemById(pid);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dirOptButton(item, selectedId, extraClass, indent) {
|
||||
function seedDirExpanded(selectedId) {
|
||||
dirExpanded = new Set(dirAncestorIds(selectedId).map(String));
|
||||
}
|
||||
function dirChevron() {
|
||||
const ns = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(ns, "svg");
|
||||
svg.setAttribute("class", "dir-tree-chevron");
|
||||
svg.setAttribute("width", "12");
|
||||
svg.setAttribute("height", "12");
|
||||
svg.setAttribute("viewBox", "0 0 12 12");
|
||||
svg.setAttribute("aria-hidden", "true");
|
||||
const path = document.createElementNS(ns, "path");
|
||||
path.setAttribute("d", "M4.25 2.5 8.25 6l-4 3.5");
|
||||
path.setAttribute("fill", "none");
|
||||
path.setAttribute("stroke", "currentColor");
|
||||
path.setAttribute("stroke-width", "1.5");
|
||||
path.setAttribute("stroke-linecap", "round");
|
||||
path.setAttribute("stroke-linejoin", "round");
|
||||
svg.appendChild(path);
|
||||
return svg;
|
||||
}
|
||||
function dirAllButton(selectedId) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "dir-picker-reset";
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "jn-menu-opt dir-picker-opt" + (extraClass ? " " + extraClass : "");
|
||||
btn.setAttribute("role", "option");
|
||||
btn.className = "jn-menu-opt dir-picker-opt";
|
||||
btn.setAttribute("role", "treeitem");
|
||||
btn.setAttribute("aria-level", "1");
|
||||
btn.dataset.id = "0";
|
||||
const name = document.createElement("span");
|
||||
name.className = "dir-picker-opt-name";
|
||||
name.textContent = "全部目录";
|
||||
btn.appendChild(name);
|
||||
if (+selectedId === 0) btn.setAttribute("aria-selected", "true");
|
||||
wrap.appendChild(btn);
|
||||
return wrap;
|
||||
}
|
||||
function dirTreeRow(item, selectedId, hasKids, expanded) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "dir-tree-row";
|
||||
row.style.setProperty("--dir-indent", String(Math.max(0, (item.level || 1) - 1)));
|
||||
if (hasKids) {
|
||||
const tog = document.createElement("button");
|
||||
tog.type = "button";
|
||||
tog.className = "dir-tree-toggle";
|
||||
tog.dataset.dirToggle = String(item.id);
|
||||
tog.setAttribute("aria-expanded", expanded ? "true" : "false");
|
||||
tog.setAttribute("aria-label", (expanded ? "收起 " : "展开 ") + item.name);
|
||||
tog.appendChild(dirChevron());
|
||||
row.appendChild(tog);
|
||||
} else {
|
||||
const sp = document.createElement("span");
|
||||
sp.className = "dir-tree-toggle is-leaf";
|
||||
sp.setAttribute("aria-hidden", "true");
|
||||
row.appendChild(sp);
|
||||
}
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "jn-menu-opt dir-picker-opt";
|
||||
btn.setAttribute("role", "treeitem");
|
||||
btn.setAttribute("aria-level", String(item.level || 1));
|
||||
btn.dataset.id = String(item.id);
|
||||
if (indent) btn.style.paddingLeft = (10 + Math.max(0, (item.level || 1) - 1) * 14) + "px";
|
||||
if (hasKids) btn.setAttribute("aria-expanded", expanded ? "true" : "false");
|
||||
btn.title = item.path || item.name;
|
||||
const name = document.createElement("span");
|
||||
name.className = "dir-picker-opt-name";
|
||||
name.textContent = item.name;
|
||||
btn.appendChild(name);
|
||||
if (item.dup || (item.level || 1) > 1 || extraClass === "is-recent") {
|
||||
const path = document.createElement("span");
|
||||
path.className = "dir-picker-opt-path";
|
||||
path.textContent = item.path;
|
||||
btn.appendChild(path);
|
||||
}
|
||||
if (+selectedId === +item.id) btn.setAttribute("aria-selected", "true");
|
||||
return btn;
|
||||
row.appendChild(btn);
|
||||
return row;
|
||||
}
|
||||
function renderDirPicker() {
|
||||
if (!dirPopEl) return;
|
||||
const wrap = popAnchor && popAnchor.closest("[data-dir-picker]");
|
||||
const selectedId = wrap ? +(wrap.dataset.selected || 0) : 0;
|
||||
const q = dirPickerQuery.trim().toLowerCase();
|
||||
const search = dirPopEl.querySelector("[data-dir-picker-q]");
|
||||
const keepQ = search ? search.value : dirPickerQuery;
|
||||
const byParent = dirByParentMap();
|
||||
dirPopEl.innerHTML = "";
|
||||
const head = document.createElement("div");
|
||||
head.className = "dir-picker-search";
|
||||
head.innerHTML = `<div class="search"><span aria-hidden="true">⌕</span><input type="search" data-dir-picker-q placeholder="搜索目录名或完整路径" aria-label="搜索目录" autocomplete="off"></div>`;
|
||||
const inp = head.querySelector("input");
|
||||
inp.value = keepQ;
|
||||
dirPopEl.appendChild(head);
|
||||
const list = document.createElement("div");
|
||||
list.className = "dir-picker-list";
|
||||
const allBtn = document.createElement("button");
|
||||
allBtn.type = "button";
|
||||
allBtn.className = "jn-menu-opt dir-picker-opt";
|
||||
allBtn.setAttribute("role", "option");
|
||||
allBtn.dataset.id = "0";
|
||||
const allName = document.createElement("span");
|
||||
allName.className = "dir-picker-opt-name";
|
||||
allName.textContent = "全部目录";
|
||||
allBtn.appendChild(allName);
|
||||
if (selectedId === 0) allBtn.setAttribute("aria-selected", "true");
|
||||
list.appendChild(allBtn);
|
||||
const byId = new Map(dirPickerItems.map((it) => [+it.id, it]));
|
||||
const recents = dirRecentIds().map((id) => byId.get(id)).filter(Boolean).filter((it) => dirMatches(it, q));
|
||||
if (recents.length) {
|
||||
const lab = document.createElement("div");
|
||||
lab.className = "dir-picker-group";
|
||||
lab.textContent = "最近使用";
|
||||
list.appendChild(lab);
|
||||
recents.forEach((it) => list.appendChild(dirOptButton(it, selectedId, "is-recent", false)));
|
||||
}
|
||||
const tree = dirPickerItems.filter((it) => dirMatches(it, q));
|
||||
const treeLab = document.createElement("div");
|
||||
treeLab.className = "dir-picker-group";
|
||||
treeLab.textContent = q ? "匹配目录" : "目录树";
|
||||
list.appendChild(treeLab);
|
||||
if (!tree.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "dir-picker-empty";
|
||||
empty.textContent = "无匹配目录";
|
||||
list.appendChild(empty);
|
||||
} else {
|
||||
tree.forEach((it) => list.appendChild(dirOptButton(it, selectedId, "", true)));
|
||||
}
|
||||
list.appendChild(dirAllButton(selectedId));
|
||||
const walk = (parentId) => {
|
||||
(byParent.get(parentId) || []).forEach((it) => {
|
||||
const kids = byParent.get(+it.id) || [];
|
||||
const expanded = dirExpanded.has(String(it.id));
|
||||
list.appendChild(dirTreeRow(it, selectedId, kids.length > 0, expanded));
|
||||
if (kids.length && expanded) walk(+it.id);
|
||||
});
|
||||
};
|
||||
walk(0);
|
||||
dirPopEl.appendChild(list);
|
||||
}
|
||||
function dirPickerOptions() {
|
||||
return dirPopEl ? $$(".jn-menu-opt", dirPopEl) : [];
|
||||
return dirPopEl ? $$(".dir-picker-opt", dirPopEl) : [];
|
||||
}
|
||||
function dirPickerFocusables() {
|
||||
if (!dirPopEl) return [];
|
||||
return $$("button:not([disabled])", dirPopEl);
|
||||
}
|
||||
function restoreDirFocus(id, wantToggle) {
|
||||
if (!dirPopEl || id == null) return;
|
||||
const sel = wantToggle
|
||||
? dirPopEl.querySelector(`[data-dir-toggle="${id}"]`)
|
||||
: dirPopEl.querySelector(`.dir-picker-opt[data-id="${id}"]`);
|
||||
if (sel) sel.focus();
|
||||
}
|
||||
function toggleDirExpand(id, force) {
|
||||
const key = String(id);
|
||||
const open = force === true ? true : force === false ? false : !dirExpanded.has(key);
|
||||
if (open) dirExpanded.add(key);
|
||||
else dirExpanded.delete(key);
|
||||
const ae = document.activeElement;
|
||||
const keepId = ae && dirPopEl && dirPopEl.contains(ae)
|
||||
? (ae.dataset.dirToggle || ae.dataset.id)
|
||||
: key;
|
||||
const wantToggle = !!(ae && ae.hasAttribute && ae.hasAttribute("data-dir-toggle"));
|
||||
renderDirPicker();
|
||||
if (popAnchor) placePopover(dirPopEl, popAnchor);
|
||||
restoreDirFocus(keepId, wantToggle);
|
||||
}
|
||||
function moveDirHighlight(delta) {
|
||||
const opts = dirPickerOptions();
|
||||
@@ -770,6 +823,10 @@ function moveDirHighlight(delta) {
|
||||
opts[next].classList.add("is-active");
|
||||
opts[next].focus();
|
||||
}
|
||||
function dirOptHasKids(id) {
|
||||
const kids = dirByParentMap().get(+id);
|
||||
return !!(kids && kids.length);
|
||||
}
|
||||
function openDirPicker(trigger) {
|
||||
if (!trigger) return;
|
||||
const wrap = trigger.closest("[data-dir-picker]");
|
||||
@@ -780,7 +837,7 @@ function openDirPicker(trigger) {
|
||||
try { dirPickerItems = JSON.parse(wrap.getAttribute("data-dirs") || "[]"); }
|
||||
catch (e) { dirPickerItems = []; }
|
||||
if (!Array.isArray(dirPickerItems)) dirPickerItems = [];
|
||||
dirPickerQuery = "";
|
||||
seedDirExpanded(+(wrap.dataset.selected || 0));
|
||||
popAnchor = trigger;
|
||||
popKind = "dir";
|
||||
trigger.setAttribute("aria-expanded", "true");
|
||||
@@ -788,8 +845,11 @@ function openDirPicker(trigger) {
|
||||
renderDirPicker();
|
||||
placePopover(dirPopEl, trigger);
|
||||
bindPopReposition(true);
|
||||
const q = dirPopEl.querySelector("[data-dir-picker-q]");
|
||||
if (q) q.focus();
|
||||
const selected = dirPopEl.querySelector(".dir-picker-opt[aria-selected='true']") || dirPopEl.querySelector(".dir-picker-opt");
|
||||
if (selected) {
|
||||
selected.focus();
|
||||
selected.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
function commitDirPicker(id) {
|
||||
const trigger = popAnchor;
|
||||
@@ -797,7 +857,6 @@ function commitDirPicker(id) {
|
||||
const form = wrap && wrap.closest("form");
|
||||
const hidden = wrap && wrap.querySelector('input[name="dir_id"]');
|
||||
if (!hidden || !form) { closePops(); return; }
|
||||
rememberDir(id);
|
||||
hidden.value = String(id || 0);
|
||||
closePops();
|
||||
form.submit();
|
||||
@@ -1191,7 +1250,13 @@ document.addEventListener("click", (e) => {
|
||||
document.addEventListener("click", (e) => {
|
||||
const t = e.target;
|
||||
if (!dirPopEl || dirPopEl.hidden || !dirPopEl.contains(t)) return;
|
||||
const opt = t.closest(".jn-menu-opt");
|
||||
const tog = t.closest("[data-dir-toggle]");
|
||||
if (tog) {
|
||||
e.preventDefault();
|
||||
toggleDirExpand(tog.dataset.dirToggle);
|
||||
return;
|
||||
}
|
||||
const opt = t.closest(".dir-picker-opt");
|
||||
if (opt && !opt.disabled) commitDirPicker(+opt.dataset.id);
|
||||
});
|
||||
|
||||
@@ -1276,20 +1341,6 @@ document.addEventListener("change", (e) => {
|
||||
|
||||
document.addEventListener("input", (e) => {
|
||||
if (e.target.closest(".form-field") || isDateField(e.target)) setErr(e.target, "");
|
||||
if (e.target.matches && e.target.matches("[data-dir-picker-q]") && popKind === "dir") {
|
||||
dirPickerQuery = e.target.value || "";
|
||||
const keep = e.target;
|
||||
const start = keep.selectionStart;
|
||||
const end = keep.selectionEnd;
|
||||
renderDirPicker();
|
||||
const next = dirPopEl.querySelector("[data-dir-picker-q]");
|
||||
if (next) {
|
||||
next.focus();
|
||||
try { next.setSelectionRange(start, end); } catch (err) {}
|
||||
}
|
||||
repositionOpenPop();
|
||||
return;
|
||||
}
|
||||
if (e.target.closest("[data-calc-interest]") || e.target.closest("[data-calc-discount]")) {
|
||||
const ci = e.target.closest("[data-calc-interest]");
|
||||
if (ci) {
|
||||
@@ -1315,31 +1366,59 @@ document.addEventListener("keydown", (e) => {
|
||||
}
|
||||
if (popKind === "dir" && dirPopEl && !dirPopEl.hidden) {
|
||||
const opts = dirPickerOptions();
|
||||
const focusables = dirPickerFocusables();
|
||||
const activeOpt = document.activeElement && document.activeElement.classList.contains("dir-picker-opt")
|
||||
? document.activeElement
|
||||
: (dirPopEl.querySelector(".dir-picker-opt.is-active") || null);
|
||||
const activeId = activeOpt ? +activeOpt.dataset.id : 0;
|
||||
if (e.key === "Escape") { e.preventDefault(); closePops(); return; }
|
||||
if (e.key === "ArrowDown") { e.preventDefault(); moveDirHighlight(1); return; }
|
||||
if (e.key === "ArrowUp") { e.preventDefault(); moveDirHighlight(-1); return; }
|
||||
if (e.key === "ArrowRight" && activeId) {
|
||||
e.preventDefault();
|
||||
if (dirOptHasKids(activeId) && !dirExpanded.has(String(activeId))) toggleDirExpand(activeId, true);
|
||||
else moveDirHighlight(1);
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
if (activeId && dirExpanded.has(String(activeId))) {
|
||||
toggleDirExpand(activeId, false);
|
||||
return;
|
||||
}
|
||||
const parentId = activeId ? dirParentId(dirItemById(activeId)) : 0;
|
||||
if (parentId) restoreDirFocus(parentId, false);
|
||||
else restoreDirFocus(0, false);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Home" && opts[0]) { e.preventDefault(); opts.forEach((el) => el.classList.remove("is-active")); opts[0].classList.add("is-active"); opts[0].focus(); return; }
|
||||
if (e.key === "End" && opts.length) { e.preventDefault(); opts.forEach((el) => el.classList.remove("is-active")); opts[opts.length - 1].classList.add("is-active"); opts[opts.length - 1].focus(); return; }
|
||||
if (e.key === "Enter") {
|
||||
const active = document.activeElement && document.activeElement.classList.contains("jn-menu-opt")
|
||||
? document.activeElement
|
||||
: (dirPopEl.querySelector(".jn-menu-opt.is-active") || opts[0]);
|
||||
if (active && dirPopEl.contains(active) && !e.target.matches("[data-dir-picker-q]")) {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
const tog = e.target.closest && e.target.closest("[data-dir-toggle]");
|
||||
if (tog) {
|
||||
e.preventDefault();
|
||||
commitDirPicker(+active.dataset.id);
|
||||
toggleDirExpand(tog.dataset.dirToggle);
|
||||
return;
|
||||
}
|
||||
if (e.target.matches && e.target.matches("[data-dir-picker-q]")) {
|
||||
const hit = (e.target.classList && e.target.classList.contains("dir-picker-opt") && dirPopEl.contains(e.target))
|
||||
? e.target
|
||||
: (dirPopEl.querySelector(".dir-picker-opt.is-active") || opts[0]);
|
||||
if (hit) {
|
||||
e.preventDefault();
|
||||
const q = (dirPickerQuery || "").trim();
|
||||
const hit = q
|
||||
? (opts.find((el) => el.classList.contains("is-active") && el.dataset.id !== "0") || opts.find((el) => el.dataset.id !== "0") || opts[0])
|
||||
: (dirPopEl.querySelector(".jn-menu-opt.is-active") || opts[0]);
|
||||
if (hit) commitDirPicker(+hit.dataset.id);
|
||||
return;
|
||||
commitDirPicker(+hit.dataset.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === "Tab") {
|
||||
const i = focusables.indexOf(document.activeElement);
|
||||
if (e.shiftKey) {
|
||||
if (i <= 0) { e.preventDefault(); closePops(); }
|
||||
} else if (i === -1 || i >= focusables.length - 1) {
|
||||
e.preventDefault();
|
||||
closePops();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === "Tab") { closePops(); return; }
|
||||
}
|
||||
if (popKind === "menu" && menuEl && !menuEl.hidden) {
|
||||
const opts = $$(".jn-menu-opt:not(:disabled)", menuEl);
|
||||
@@ -1505,8 +1584,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
syncCheckAll();
|
||||
initCalc();
|
||||
enhanceDateInputs();
|
||||
const picker = document.querySelector("[data-dir-picker]");
|
||||
if (picker && +picker.dataset.selected > 0) rememberDir(+picker.dataset.selected);
|
||||
const mo = new MutationObserver((recs) => {
|
||||
recs.forEach((r) => {
|
||||
r.addedNodes.forEach((n) => {
|
||||
|
||||
+1
-1
@@ -2857,7 +2857,7 @@ pageHeader($me, $taskTotal);
|
||||
<form method="get" action="index.php" class="filter-bar" style="margin-top:12px;">
|
||||
<div class="dir-picker" data-dir-picker data-selected="<?= $dirId ?>" data-dirs="<?= e(json_encode($dirPickerItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>">
|
||||
<input type="hidden" name="dir_id" value="<?= $dirId ?>">
|
||||
<button type="button" class="dir-picker-trigger" data-dir-picker-trigger aria-haspopup="listbox" aria-expanded="false" aria-controls="jn-dir-picker" aria-label="按归档目录筛选" title="<?= e($dirPickerLabel) ?>">
|
||||
<button type="button" class="dir-picker-trigger" data-dir-picker-trigger aria-haspopup="tree" aria-expanded="false" aria-controls="jn-dir-picker" aria-label="按归档目录筛选" title="<?= e($dirPickerLabel) ?>">
|
||||
<span class="dir-picker-trigger-text"><?= e($dirPickerLabel) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user