Skip to content
Closed
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
27 changes: 18 additions & 9 deletions frontend/discourse/app/form-kit/components/fk/field-data.gjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Component from "@glimmer/component";
import { cached } from "@glimmer/tracking";
import { action } from "@ember/object";
import { getOwner } from "@ember/owner";
import curryComponent from "ember-curry-component";
Expand All @@ -24,6 +23,9 @@ export default class FKFieldData extends Component {
*/
errorId = uniqueId();

#controlType;
#controlCurried;

// Set by legacy controls in their constructor (during render),
// read by the applyControlType modifier (post-render)
_legacyControlType;
Expand Down Expand Up @@ -102,15 +104,22 @@ export default class FKFieldData extends Component {
this._legacyControlType = value;
}

/**
* Contextual component for the control set by `@type`.
* @type {Component}
*/
@cached
get Control() {
const { component, args } = resolveFieldControl(this.type);
#updateControlCache(type) {
this.#controlType = type;
const { component, args } = resolveFieldControl(type);
this.#controlCurried = curryComponent(
component,
{ field: this, ...args },
getOwner(this)
);
}

return curryComponent(component, { field: this, ...args }, getOwner(this));
get Control() {
const type = this.type;
if (type !== this.#controlType || !this.#controlCurried) {
this.#updateControlCache(type);
}
return this.#controlCurried;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { get, hash } from "@ember/helper";
import { focus, render, typeIn } from "@ember/test-helpers";
import { module, test } from "qunit";
import Form from "discourse/components/form";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";

function getFieldMeta() {
return {
region: { type: "text" },
name: { type: "text" },
};
}

function fieldType(type) {
return type === "checkbox" ? "checkbox" : "input";
}

function fieldKeys(obj) {
return obj ? Object.keys(obj) : [];
}

module(
"Integration | Component | FormKit | Object | Focus retention",
function (hooks) {
setupRenderingTest(hooks);

test("retains focus when @type depends on yielded data inside form.Object", async function (assert) {
await render(
<template>
<Form
@data={{hash
provider="aws_bedrock"
params=(hash region="" name="test")
}}
as |form data|
>
<form.Object @name="params" as |object objectData|>
{{#each (fieldKeys objectData) as |key|}}
{{#let (get (getFieldMeta data.provider) key) as |params|}}
<object.Field
@type={{fieldType params.type}}
@name={{key}}
@title={{key}}
as |field|
>
<field.Control />
</object.Field>
{{/let}}
{{/each}}
</form.Object>
</Form>
</template>
);

const input = document.querySelector("[data-name='params.region'] input");
await focus(input);
await typeIn(input, "u", { delay: 0 });

assert.strictEqual(document.activeElement, input, "focus retained");
});

test("retains focus when @type depends on yielded data on top-level field", async function (assert) {
await render(
<template>
<Form @data={{hash provider="aws_bedrock" region=""}} as |form data|>
<form.Field
@type={{fieldType data.provider}}
@name="region"
@title="region"
as |field|
>
<field.Control />
</form.Field>
</Form>
</template>
);

const input = document.querySelector("[data-name='region'] input");
await focus(input);
await typeIn(input, "u", { delay: 0 });

assert.strictEqual(document.activeElement, input, "focus retained");
});
}
);
Loading