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
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export interface FluidPropertyCreateOptions {
* value, `autoProperty` to hand a key back to its Auto Property flow, or `cancel`.
*/
export default class FluidPropertyCreatePrompt extends Modal {
// onOpen() focuses the key input. Obsidian 1.13+ (undocumented flag) would then
// move focus to the first focusable element, the type pill.
hasInitialInputFocus = false;

private readonly opts: FluidPropertyCreateOptions;
private resolvePromise: (result: FluidPropertyCreateResult) => void;

Expand Down
5 changes: 5 additions & 0 deletions src/Modals/NativePropertyPrompt/NativePropertyPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ function isReservedTypeKey(key: string): boolean {
}

export default class NativePropertyPrompt extends Modal {
// onOpen() focuses the value editor. Obsidian 1.13+ (undocumented flag) would then
// move focus to the first focusable element, the type pill, orphaning the value
// autocomplete popover.
hasInitialInputFocus = false;

private readonly file: TFile;
private readonly property: Property;
private resolvePromise: (result: NativePropertyPromptResult) => void;
Expand Down
26 changes: 26 additions & 0 deletions tests/e2e/fluid-create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@ describe("MetaEdit fluid property creation", () => {
expect(await obsidian.dev.runtimeErrors()).toEqual([]);
});

// The type pill precedes the key input, so Obsidian 1.13's Modal.open() would
// move focus onto it after onOpen() unless the modal opts out.
test("the create modal opens with focus in the key input", async () => {
const {obsidian, sandbox} = getContext();
const notePath = sandbox.path("fluid-create-focus.md");
await writeLiveFile(obsidian, notePath, "---\n---\nbody\n");

const result = await evalJsonAsync<{keyInputFocused: boolean}>(
obsidian,
`
(async () => {
${FLUID_HELPERS}
const file = app.vault.getAbstractFileByPath(${JSON.stringify(notePath)});
const {promise, modal} = await openCreate(file);
const keyInputFocused = document.activeElement === modal.querySelector(".metaedit-fluid-create-key");
cancel(modal);
await promise;
return {keyInputFocused};
})()
`,
);

expect(result.keyInputFocused).toBe(true);
expect(await obsidian.dev.runtimeErrors()).toEqual([]);
});

test("layout: no horizontal overflow, compact height, and the floating dropdown never covers the buttons", async () => {
const {obsidian, sandbox} = getContext();
const notePath = sandbox.path("fluid-create-layout.md");
Expand Down
11 changes: 9 additions & 2 deletions tests/e2e/multi-value.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import { NATIVE_PROMPT_HELPERS_JS } from "./nativePromptHelpers";

const getContext = createMetaEditE2EHarness("multi-value");

// The legacy GenericSuggester is a SuggestModal, whose rows sit in
// `.prompt-results`. Obsidian's own property-value autocomplete also renders
// `.suggestion-item` rows (in a body-level `.suggestion-container`) whenever a
// native editor has focus, so a bare `.suggestion-item` cannot tell them apart.
// The legacy driver waits on this same selector, proving the probe can see it.
const LEGACY_SUGGESTION_SELECTOR = ".prompt-results .suggestion-item";

// Live regressions for the multi-value / array editing cluster (#94, #51, #31, #36).
//
// Since PR #168, `editMetaElement` routes eligible top-level YAML properties
Expand Down Expand Up @@ -332,7 +339,7 @@ async function driveNativeEdit(
}
try {
const {promise, host} = await openNative(file, ${JSON.stringify(opts.key)});
const sawLegacySuggester = Boolean(document.querySelector(".suggestion-item"));
const sawLegacySuggester = Boolean(document.querySelector(${JSON.stringify(LEGACY_SUGGESTION_SELECTOR)}));
const sawLegacyPrompt = Boolean(document.querySelector(".metaEditPromptInput"));
let pills = null;
if (action.kind === "editPill") pills = await editPillByText(host, action.from, action.to);
Expand Down Expand Up @@ -378,7 +385,7 @@ async function driveLegacyListEdit(
const editPromise = plugin.controller.editMetaElement(property, props, file);

const item = await waitFor(
".suggestion-item",
${JSON.stringify(LEGACY_SUGGESTION_SELECTOR)},
(el) => el.textContent?.trim() === ${JSON.stringify(opts.selectText)},
);
item.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
Expand Down
34 changes: 34 additions & 0 deletions tests/e2e/native-properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,40 @@ describe("MetaEdit native Obsidian property widgets", () => {
expect(result.content).toContain("summary: old");
expect(await obsidian.dev.runtimeErrors()).toEqual([]);
});

// Obsidian 1.13's Modal.open() moves focus to the modal's first focusable
// element - the type pill - after onOpen() unless the modal opts out. The
// sibling note gives the value autocomplete a suggestion, so the popover the
// value editor opened would be left orphaned and swallow the pill's Enter.
test("opens the edit prompt focused on the value editor and edits a pill in place", async () => {
const {obsidian, sandbox} = getContext();
const notePath = sandbox.path("native-focus.md");
await writeLiveFile(obsidian, sandbox.path("native-focus-sibling.md"), "---\nreviewers:\n - sibling\n---\nbody\n");
await writeLiveFile(obsidian, notePath, "---\nreviewers:\n - alpha\n - beta\n---\nbody\n");

const result = await evalJsonAsync<{focusedValueEditor: boolean; pills: string[]; content: string}>(
obsidian,
`
(async () => {
${NATIVE_PROMPT_HELPERS_JS}
const file = app.vault.getAbstractFileByPath(${JSON.stringify(notePath)});
app.metadataTypeManager.setType("reviewers", "multitext");
await sleep(200);

const {promise, host} = await openNative(file, "reviewers");
const focusedValueEditor = document.activeElement === host.querySelector(".multi-select-input");
const pills = await editPillByText(host, "alpha", "gamma");
await saveOpenModal(promise);
return {focusedValueEditor, pills, content: await app.vault.read(file)};
})()
`,
);

expect(result.focusedValueEditor).toBe(true);
expect(result.pills).toEqual(["gamma", "beta"]);
expect(result.content).toBe("---\nreviewers:\n - gamma\n - beta\n---\nbody\n");
expect(await obsidian.dev.runtimeErrors()).toEqual([]);
});
});

async function verifyWikilinkSuggestion(
Expand Down
Loading