From 08c958ec96b70d709c75af68110ee0e87296f762 Mon Sep 17 00:00:00 2001 From: ther12k Date: Sun, 21 Jun 2026 12:16:24 +0000 Subject: [PATCH] fix(date-time-picker): focus natural language input first when enabled (issue #2046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When opening the date/time picker from Quick Actions (or any keyboard-driven path), the date input was always receiving initial focus, even when the user had natural language input enabled in settings. This was jarring because the NLP box is the more discoverable entry point when it is rendered. Fix: in DateTimePickerModal.onOpen(), prefer naturalLanguageInput.focus() when canUseNaturalLanguageInput() returns true and the element has been rendered. Fall back to the existing dateInput.focus() behavior otherwise, preserving the previous UX for users with NLP disabled. Adds tests/unit/issues/issue-2046-nlp-input-autofocus.test.ts covering: - NLP enabled → NLP input receives focus, date input does not - NLP disabled → date input receives focus, NLP input not rendered - Defensive: NLP enabled but ref missing → date input still receives focus Closes #2046 --- src/modals/DateTimePickerModal.ts | 9 +- .../issue-2046-nlp-input-autofocus.test.ts | 115 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tests/unit/issues/issue-2046-nlp-input-autofocus.test.ts diff --git a/src/modals/DateTimePickerModal.ts b/src/modals/DateTimePickerModal.ts index 3a45ca5f..f35c198f 100644 --- a/src/modals/DateTimePickerModal.ts +++ b/src/modals/DateTimePickerModal.ts @@ -119,7 +119,14 @@ export class DateTimePickerModal extends Modal { this.updateSelectButtonState(); window.setTimeout(() => { - this.dateInput?.focus(); + // Prefer the natural language input when it is enabled and rendered, + // so keyboard users land on the NLP box first (matches user expectation + // from Quick Actions). Fall back to the date input otherwise. + if (this.canUseNaturalLanguageInput() && this.naturalLanguageInput) { + this.naturalLanguageInput.focus(); + } else { + this.dateInput?.focus(); + } }, 100); } diff --git a/tests/unit/issues/issue-2046-nlp-input-autofocus.test.ts b/tests/unit/issues/issue-2046-nlp-input-autofocus.test.ts new file mode 100644 index 00000000..36d04d46 --- /dev/null +++ b/tests/unit/issues/issue-2046-nlp-input-autofocus.test.ts @@ -0,0 +1,115 @@ +import { DateTimePickerModal } from "../../../src/modals/DateTimePickerModal"; +import type TaskNotesPlugin from "../../../src/main"; + +/** + * Regression test for issue #2046: + * "Quick Actions for Task Under Cursor 'Set due date' autofocuses incorrectly" + * + * Expected behavior: + * When the user opens the date/time picker from Quick Actions (or any + * keyboard-driven path) and natural language input is enabled, the + * natural language input should receive initial focus — NOT the date + * input. When NLP is disabled, fall back to the date input. + */ + +function makeNLPEnabledPlugin(): TaskNotesPlugin { + return { + settings: { enableNaturalLanguageInput: true }, + } as unknown as TaskNotesPlugin; +} + +describe("Issue #2046: DateTimePickerModal focuses natural language input first when enabled", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("focuses the natural language input when NLP is enabled and rendered", () => { + const onSelect = jest.fn(); + const plugin = makeNLPEnabledPlugin(); + + const modal = new DateTimePickerModal({} as any, { + currentDate: null, + onSelect, + plugin, + }); + + modal.open(); + + const nlpInput = modal.contentEl.querySelector( + "input.date-time-picker-modal__nlp-input" + ); + const dateInput = modal.contentEl.querySelector( + 'input[type="date"].date-time-picker-modal__date-input' + ); + expect(nlpInput).toBeTruthy(); + expect(dateInput).toBeTruthy(); + + const nlpFocus = jest.spyOn(nlpInput!, "focus"); + const dateFocus = jest.spyOn(dateInput!, "focus"); + + jest.advanceTimersByTime(150); + + expect(nlpFocus).toHaveBeenCalled(); + expect(dateFocus).not.toHaveBeenCalled(); + }); + + it("falls back to the date input when NLP is disabled", () => { + const onSelect = jest.fn(); + const plugin = { + settings: { enableNaturalLanguageInput: false }, + } as unknown as TaskNotesPlugin; + + const modal = new DateTimePickerModal({} as any, { + currentDate: null, + onSelect, + plugin, + }); + + modal.open(); + + const nlpInput = modal.contentEl.querySelector( + "input.date-time-picker-modal__nlp-input" + ); + const dateInput = modal.contentEl.querySelector( + 'input[type="date"].date-time-picker-modal__date-input' + ); + expect(nlpInput).toBeFalsy(); + expect(dateInput).toBeTruthy(); + + const dateFocus = jest.spyOn(dateInput!, "focus"); + + jest.advanceTimersByTime(150); + + expect(dateFocus).toHaveBeenCalled(); + }); + + it("does not throw if NLP is enabled but the input ref is not assigned", () => { + // Defensive: if the render pipeline ever changes and the ref is not + // assigned, focus should still fall back to the date input rather than + // crashing the modal. + const onSelect = jest.fn(); + const plugin = makeNLPEnabledPlugin(); + + const modal = new DateTimePickerModal({} as any, { + currentDate: null, + onSelect, + plugin, + }); + + modal.open(); + + (modal as unknown as { naturalLanguageInput: HTMLInputElement | null }).naturalLanguageInput = null; + + const dateInput = modal.contentEl.querySelector( + 'input[type="date"].date-time-picker-modal__date-input' + ); + const dateFocus = jest.spyOn(dateInput!, "focus"); + + expect(() => jest.advanceTimersByTime(150)).not.toThrow(); + expect(dateFocus).toHaveBeenCalled(); + }); +}); \ No newline at end of file