From df5cce8e2f621268115cda5d91e593e864df3b62 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Mon, 18 Sep 2023 17:05:55 -0400 Subject: [PATCH] Autofocus PeopleSelects in the sidebar (#333) * Autofocus PeopleSelects in the sidebar * Remove console.log --- web/app/components/custom-editable-field.hbs | 1 + web/app/components/document/sidebar.hbs | 2 + web/app/components/inputs/people-select.ts | 2 + web/app/modifiers/autofocus.ts | 55 +++++++++++++++++++ .../acceptance/authenticated/document-test.ts | 34 ++++++++++++ .../integration/modifiers/autofocus-test.ts | 44 +++++++++++++++ 6 files changed, 138 insertions(+) create mode 100644 web/app/modifiers/autofocus.ts create mode 100644 web/tests/integration/modifiers/autofocus-test.ts diff --git a/web/app/components/custom-editable-field.hbs b/web/app/components/custom-editable-field.hbs index be7c5f777..30088d065 100644 --- a/web/app/components/custom-editable-field.hbs +++ b/web/app/components/custom-editable-field.hbs @@ -55,6 +55,7 @@ <:editing as |F|> <:editing as |F|> <:editing as |F|> void; + renderInPlace?: boolean; + disabled?: boolean; }; } diff --git a/web/app/modifiers/autofocus.ts b/web/app/modifiers/autofocus.ts new file mode 100644 index 000000000..0e6865d83 --- /dev/null +++ b/web/app/modifiers/autofocus.ts @@ -0,0 +1,55 @@ +import { assert } from "@ember/debug"; +import { action } from "@ember/object"; +import { tracked } from "@glimmer/tracking"; +import Modifier from "ember-modifier"; +import { FOCUSABLE } from "hermes/components/editable-field"; + +interface AutofocusModifierSignature { + Args: { + Element: Element; + Positional: []; + Named: { + targetChildren?: boolean; + }; + }; +} + +export default class AutofocusModifier extends Modifier { + @tracked private _element: Element | null = null; + @tracked private targetChildren = false; + + private get element(): Element { + assert("element must exist", this._element); + return this._element; + } + + @action private maybeAutofocus() { + if (this.targetChildren) { + const target = this.element.querySelector(FOCUSABLE); + if (target instanceof HTMLElement) { + target.focus(); + } + } else { + if (this.element instanceof HTMLElement) { + this.element.focus(); + } + } + } + + modify( + element: Element, + _positional: [], + named: AutofocusModifierSignature["Args"]["Named"] + ) { + this._element = element; + this.targetChildren = named.targetChildren ?? false; + + this.maybeAutofocus(); + } +} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + autofocus: typeof AutofocusModifier; + } +} diff --git a/web/tests/acceptance/authenticated/document-test.ts b/web/tests/acceptance/authenticated/document-test.ts index 826a06774..cd7d53baa 100644 --- a/web/tests/acceptance/authenticated/document-test.ts +++ b/web/tests/acceptance/authenticated/document-test.ts @@ -524,4 +524,38 @@ module("Acceptance | authenticated/document", function (hooks) { assert.dom(SUMMARY_SELECTOR).hasText("Enter a summary"); }); + + test('"people" inputs receive focus on click', async function (this: AuthenticatedDocumentRouteTestContext, assert) { + this.server.create("document", { + objectID: 1, + title: "Test Document", + isDraft: true, + customEditableFields: { + Stakeholders: { + displayName: "Stakeholders", + type: "PEOPLE", + }, + }, + }); + + await visit("/document/1?draft=true"); + + await click(`${CONTRIBUTORS_SELECTOR} .field-toggle`); + + assert.true( + document.activeElement === find(`${CONTRIBUTORS_SELECTOR} input`) + ); + + await click(`${APPROVERS_SELECTOR} .field-toggle`); + + assert.true(document.activeElement === find(`${APPROVERS_SELECTOR} input`)); + + const stakeholdersSelector = "[data-test-custom-people-field]"; + + await click(`${stakeholdersSelector} .field-toggle`); + + assert.true( + document.activeElement === find(`${stakeholdersSelector} input`) + ); + }); }); diff --git a/web/tests/integration/modifiers/autofocus-test.ts b/web/tests/integration/modifiers/autofocus-test.ts new file mode 100644 index 000000000..7b32a2aa2 --- /dev/null +++ b/web/tests/integration/modifiers/autofocus-test.ts @@ -0,0 +1,44 @@ +import { TestContext, find, render } from "@ember/test-helpers"; +import { module, test } from "qunit"; +import { hbs } from "ember-cli-htmlbars"; +import { setupRenderingTest } from "ember-qunit"; + +interface AutofocusModifierTestContext extends TestContext { + buttonIsShown: boolean; +} +module("Integration | Modifier | autofocus", function (hooks) { + setupRenderingTest(hooks); + + test("it autofocuses a focusable element", async function (this: AutofocusModifierTestContext, assert) { + this.set("buttonIsShown", false); + + await render(hbs` + + + {{#if this.buttonIsShown}} + + {{/if}} + `); + + assert.true(find("input") === document.activeElement); + + this.set("buttonIsShown", true); + + assert.true(find("button") === document.activeElement); + }); + + test("it can target focusable children of an element", async function (this: AutofocusModifierTestContext, assert) { + await render(hbs` +
+ + +
+ `); + + assert.true( + find("input") === document.activeElement, + "the first focusable child is focused" + ); + }); +});