mirror of
https://github.com/sbuffkin/hexmaker.git
synced 2026-07-22 14:30:24 +00:00
position:fixed failed because Obsidian's modal CSS animation sets transform on .modal, making fixed children position relative to it (not the viewport). Instead, toggle .duckmage-combo-open on .modal-content (overflow-y:visible) while the dropdown is open so the absolute-positioned dropdown doesn't trigger the scroll container. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
144 lines
4.8 KiB
TypeScript
144 lines
4.8 KiB
TypeScript
import { App, Setting } from "obsidian";
|
|
import { HexmakerModal } from "../HexmakerModal";
|
|
import type HexmakerPlugin from "../HexmakerPlugin";
|
|
|
|
export class SubmapPickerModal extends HexmakerModal {
|
|
constructor(
|
|
app: App,
|
|
private plugin: HexmakerPlugin,
|
|
private current: string | undefined,
|
|
private onLink: (mapName: string) => void,
|
|
private onUnlink: () => void,
|
|
) {
|
|
super(app);
|
|
}
|
|
|
|
onOpen(): void {
|
|
const { contentEl } = this;
|
|
contentEl.empty();
|
|
contentEl.addClass("duckmage-hex-editor");
|
|
this.makeDraggable();
|
|
|
|
contentEl.createEl("h2", { text: "Link submap" });
|
|
|
|
const maps = this.plugin.settings.maps;
|
|
if (maps.length === 0) {
|
|
contentEl.createEl("p", {
|
|
text: "No maps found. Create a map first.",
|
|
cls: "duckmage-maplinkmodal-empty",
|
|
});
|
|
contentEl.createEl("button", { text: "Cancel" })
|
|
.addEventListener("click", () => this.close());
|
|
return;
|
|
}
|
|
|
|
let selectedMap = this.current ?? maps[0].name;
|
|
let mapInput!: HTMLInputElement;
|
|
|
|
// ── Map combo ─────────────────────────────────────────────────────────
|
|
const mapLabel = contentEl.createDiv({ cls: "setting-item" });
|
|
mapLabel.createDiv({ cls: "setting-item-info" }).createDiv({
|
|
cls: "setting-item-name",
|
|
text: "Map",
|
|
});
|
|
|
|
const controlEl = mapLabel.createDiv({ cls: "setting-item-control" });
|
|
const comboWrap = controlEl.createDiv({ cls: "duckmage-link-combo" });
|
|
mapInput = comboWrap.createEl("input", {
|
|
type: "text",
|
|
cls: "duckmage-link-combo-input",
|
|
attr: { placeholder: "Filter maps…" },
|
|
});
|
|
// Start blank — selectedMap is just the fallback for the Link action
|
|
mapInput.value = "";
|
|
|
|
comboWrap.createEl("button", {
|
|
text: "▾",
|
|
cls: "duckmage-link-combo-arrow",
|
|
attr: { title: "Show all" },
|
|
}).addEventListener("mousedown", (e) => {
|
|
e.preventDefault();
|
|
if (dropdown.isShown()) closeDropdown();
|
|
else openDropdown(mapInput.value);
|
|
});
|
|
|
|
const dropdown = comboWrap.createDiv({ cls: "duckmage-link-combo-dropdown" });
|
|
dropdown.hide();
|
|
|
|
let isOpen = false;
|
|
|
|
const populateDropdown = (query: string) => {
|
|
dropdown.empty();
|
|
const q = query.trim().toLowerCase();
|
|
const matches = maps.filter((m) => !q || m.name.toLowerCase().includes(q));
|
|
if (matches.length === 0) {
|
|
dropdown.createDiv({ cls: "duckmage-link-combo-empty", text: "No matching maps" });
|
|
return;
|
|
}
|
|
for (const m of matches) {
|
|
const item = dropdown.createDiv({ cls: "duckmage-link-combo-item" });
|
|
item.textContent = m.name;
|
|
item.addEventListener("mousedown", (e) => {
|
|
e.preventDefault();
|
|
selectedMap = m.name;
|
|
mapInput.value = m.name;
|
|
closeDropdown();
|
|
});
|
|
}
|
|
};
|
|
|
|
const scrollPane = comboWrap.closest<HTMLElement>(".modal-content");
|
|
|
|
const openDropdown = (query: string) => {
|
|
isOpen = true;
|
|
populateDropdown(query);
|
|
scrollPane?.addClass("duckmage-combo-open");
|
|
dropdown.show();
|
|
};
|
|
|
|
const closeDropdown = () => {
|
|
isOpen = false;
|
|
dropdown.hide();
|
|
scrollPane?.removeClass("duckmage-combo-open");
|
|
};
|
|
|
|
mapInput.addEventListener("focus", () => openDropdown(""));
|
|
mapInput.addEventListener("blur", () => setTimeout(() => closeDropdown(), 150));
|
|
mapInput.addEventListener("input", () => {
|
|
if (!isOpen) openDropdown(mapInput.value);
|
|
else populateDropdown(mapInput.value);
|
|
});
|
|
mapInput.addEventListener("keydown", (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") closeDropdown();
|
|
});
|
|
|
|
// ── Current submap indicator ──────────────────────────────────────────
|
|
if (this.current) {
|
|
new Setting(contentEl)
|
|
.setName("Current")
|
|
.setDesc(this.current);
|
|
}
|
|
|
|
// ── Buttons ───────────────────────────────────────────────────────────
|
|
const btnRow = contentEl.createDiv({ cls: "duckmage-token-modal-buttons" });
|
|
|
|
if (this.current) {
|
|
btnRow.createEl("button", { text: "Remove link", cls: "mod-warning" })
|
|
.addEventListener("click", () => { this.close(); this.onUnlink(); });
|
|
}
|
|
|
|
btnRow.createEl("button", { text: "Link", cls: "mod-cta" })
|
|
.addEventListener("click", () => {
|
|
const map = mapInput.value.trim() || selectedMap;
|
|
this.close();
|
|
this.onLink(map);
|
|
});
|
|
|
|
btnRow.createEl("button", { text: "Cancel" })
|
|
.addEventListener("click", () => this.close());
|
|
}
|
|
|
|
onClose(): void {
|
|
this.contentEl.empty();
|
|
}
|
|
}
|