Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/gui/choiceList/AddChoiceControls.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,29 @@
}),
);
}
// Anchor the menu under the trigger button. Using the button rect (rather
// than the event coordinates) makes this work for keyboard activation too
// and is robust to Svelte 5's event delegation (currentTarget is the
// delegated root, not the button).
// Reflect open state for assistive tech (aria-expanded on the trigger).
menuOpen = true;
menu.onHide(() => {
menuOpen = false;
});

// Anchor the menu under the trigger button. Using the button rect (rather
// than the event coordinates) makes this work for keyboard activation too
// and is robust to Svelte 5's event delegation (currentTarget is the
// delegated root, not the button). Passing the button's width with
// `overlap` keeps the menu under the button when it has to flip, instead
// of ending at the button's left edge. The primary button is the bar's
// rightmost action, so its menu prefers to right-align (`left`).
const trigger = (evt.target as HTMLElement | null)?.closest("button");
if (trigger) {
const rect = trigger.getBoundingClientRect();
menu.showAtPosition({ x: rect.left, y: rect.bottom + 4 });
menu.showAtPosition({
x: rect.left,
y: rect.bottom + 4,
width: rect.width,
overlap: true,
left: !compact,
});
} else {
menu.showAtMouseEvent(evt);
}
Expand Down
17 changes: 13 additions & 4 deletions src/styles.css
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,18 @@
color: var(--text-muted);
}

/* One control column for every row, so inputs line up however long their
labels are. Stacked rows (dates, narrow screens, discovery forms) override
the width below. */
.quickAddModal.onePageInputModal .setting-item-control {
flex: none;
width: 60%;
}

.quickAddModal.onePageInputModal .setting-item-control > input {
width: 100%;
}

.quickAddModal .qa-onepage-textarea {
width: 100%;
height: 120px;
Expand All @@ -333,10 +345,6 @@
color: var(--text-muted);
}

.quickAddModal .qa-onepage-file-picker-setting .setting-item-control {
width: min(28rem, 62%);
}

.quickAddModal .qa-onepage-file-picker {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -428,6 +436,7 @@

.quickAddModal .qa-onepage-slider input[type="range"] {
width: 100%;
margin: 0;
}

.quickAddModal .qa-onepage-slider input[type="number"] {
Expand Down
28 changes: 27 additions & 1 deletion tests/e2e/choice-list-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CaptureChoice } from "../../src/types/choices/CaptureChoice";
import { MultiChoice } from "../../src/types/choices/MultiChoice";
import type IChoice from "../../src/types/choices/IChoice";
import { createQuickAddE2EHarness } from "./e2eVault";
import { waitForElement } from "./uiHelpers";
import { pressKey, waitForElement } from "./uiHelpers";

const getContext = createQuickAddE2EHarness("choice-list-layout");

Expand Down Expand Up @@ -48,3 +48,29 @@ it.each(["is-phone", "is-tablet"])("keeps choice controls compact under %s host
await obsidian.dev.evalJson("app.setting.close(); true");
}
});

it("opens the New choice menu under its button", async () => {
const { obsidian } = getContext();
try {
await obsidian.dev.evalJson("app.setting.open(); app.setting.openTabById('quickadd'); true");
await waitForElement(obsidian, ".qaNewChoiceBtn.mod-cta");
await obsidian.dev.evalJson("document.querySelector('.qaNewChoiceBtn.mod-cta').click(); true");
await waitForElement(obsidian, ".menu");
const { button, menu } = await obsidian.dev.evalJson<{
button: { left: number; right: number; bottom: number };
menu: { left: number; right: number; top: number };
}>(`(() => {
const rect = (selector) => {
const { left, right, top, bottom } = document.querySelector(selector).getBoundingClientRect();
return { left, right, top, bottom };
};
return { button: rect('.qaNewChoiceBtn.mod-cta'), menu: rect('.menu') };
})()`);
expect(menu.top).toBeGreaterThan(button.bottom);
expect(menu.right).toBeCloseTo(button.right, 0);
expect(menu.left).toBeLessThan(button.left);
} finally {
await pressKey(obsidian, "Escape");
await obsidian.dev.evalJson("app.setting.close(); true");
}
});
9 changes: 8 additions & 1 deletion tests/e2e/file-type-label.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ describe("FILE |type: default label", () => {
await pressKey(obsidian, "Enter", true);
await expectNoPrompt(obsidian);
await expect.poll(() => sandbox.read("out/attachment pair.md").catch(() => ""), POLL_OPTS)
.toBe(`image: [[${folder}/photo.png]]\npdf: [[${folder}/scan.pdf]]\n`);
.toMatch(/^image: \[\[[^\]]+\]\]\npdf: \[\[[^\]]+\]\]\n$/);
// Link text follows the vault's link format, so check where each link lands.
const note = JSON.stringify(sandbox.path("out/attachment pair.md"));
await expect.poll(() => obsidian.dev.evalJson<(string | null)[] | null>(`(() => {
const file = app.vault.getAbstractFileByPath(${note});
return app.metadataCache.getFileCache(file)?.links?.map(({ link }) =>
app.metadataCache.getFirstLinkpathDest(link, ${note})?.path ?? null) ?? null;
})()`), POLL_OPTS).toEqual([`${folder}/photo.png`, `${folder}/scan.pdf`]);
});
});
38 changes: 38 additions & 0 deletions tests/e2e/one-page-alignment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect, it } from "vitest";
import { createQuickAddE2EHarness } from "./e2eVault";
import { waitForElement } from "./uiHelpers";

const getContext = createQuickAddE2EHarness("one-page-alignment");

it("starts every one-page control at the same x regardless of label length", async () => {
const { obsidian } = getContext();
try {
await obsidian.dev.evalJson(`(() => {
const inputs = [];
for (const type of ['text', 'textarea', 'number', 'slider', 'suggester']) {
for (const label of ['A', 'A considerably longer label for this field']) {
inputs.push({ id: type + label, label, type, options: ['x'], sliderConfig: { min: 0, max: 10 } });
}
}
void app.plugins.plugins.quickadd.api.requestInputs(inputs).catch(() => undefined);
return true;
})()`);
await waitForElement(obsidian, ".onePageInputModal .qa-onepage-slider");
const lefts = await obsidian.dev.evalJson<Record<string, number>>(`(() => Object.fromEntries(
[...[...document.querySelectorAll('.onePageInputModal')].at(-1).querySelectorAll('.setting-item')].map((row, index) => [
index + ': ' + row.querySelector('.setting-item-name').textContent,
row.querySelector('input, textarea').getBoundingClientRect().left,
]),
))()`);
const values = Object.values(lefts);
expect(values).toHaveLength(10);
for (const [row, left] of Object.entries(lefts)) {
expect(left, row).toBeCloseTo(values[0], 0);
}
} finally {
await obsidian.dev.evalJson(`(() => {
[...[...document.querySelectorAll('.onePageInputModal')].at(-1).querySelectorAll('button')].find(e => e.textContent === 'Cancel')?.click();
return true;
})()`);
}
});
Loading