From 106bcc44f4a2d177078aea305f5fcb1e15a902e4 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 13 Feb 2026 12:15:54 -0500
Subject: [PATCH 01/23] Update data-testid attributes to use dynamic props for
better flexibility
---
.../src/components-react/editors/BooleanEditor.tsx | 2 +-
.../src/components-react/editors/DateTimeEditor.tsx | 2 +-
.../src/components-react/editors/EditorContainer.tsx | 2 +-
ui/components-react/src/components-react/editors/EnumEditor.tsx | 2 +-
ui/components-react/src/components-react/editors/IconEditor.tsx | 2 +-
.../src/components-react/editors/ImageCheckBoxEditor.tsx | 2 +-
.../src/components-react/editors/TextareaEditor.tsx | 2 +-
.../src/components-react/editors/ToggleEditor.tsx | 2 +-
.../src/imodel-components-react/editors/ColorEditor.tsx | 2 +-
.../src/imodel-components-react/editors/WeightEditor.tsx | 2 +-
10 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/ui/components-react/src/components-react/editors/BooleanEditor.tsx b/ui/components-react/src/components-react/editors/BooleanEditor.tsx
index 4466c740d03..fac3525d13c 100644
--- a/ui/components-react/src/components-react/editors/BooleanEditor.tsx
+++ b/ui/components-react/src/components-react/editors/BooleanEditor.tsx
@@ -139,7 +139,7 @@ export class BooleanEditor
this.props.propertyRecord?.isDisabled ||
this.props.propertyRecord?.isReadonly
}
- data-testid="components-checkbox-editor"
+ data-testid={this.props.itemId ?? "components-checkbox-editor"}
>
);
}
diff --git a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
index 23f54f7614a..4e54987139c 100644
--- a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
+++ b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
@@ -304,7 +304,7 @@ export class DateTimeEditor
<>
);
diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx
index 19d1d505efc..9f16e7bbc7c 100644
--- a/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx
+++ b/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx
@@ -165,7 +165,7 @@ export class ColorEditor
disabled={this.state.isDisabled ? true : false}
readonly={this.state.readonly}
onColorPick={this._onColorPick}
- data-testid="components-color-editor"
+ data-testid={this.props.itemId ?? "components-color-editor"}
/>
);
diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx
index 72d50ac17be..e8cb0bacbd7 100644
--- a/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx
+++ b/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx
@@ -160,7 +160,7 @@ export class WeightEditor
disabled={this.state.isDisabled ? true : false}
readonly={this.state.readonly}
onLineWeightPick={this._onLineWeightPick}
- data-testid="components-weight-editor"
+ data-testid={this.props.itemId ?? "components-weight-editor"}
/>
);
From 3403a38c774226110ae3a6053b21c5c574b470fa Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 13 Feb 2026 12:24:19 -0500
Subject: [PATCH 02/23] run prettier fix
---
.../src/components-react/editors/DateTimeEditor.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
index 4e54987139c..af3f84e208b 100644
--- a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
+++ b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
@@ -304,7 +304,10 @@ export class DateTimeEditor
<>
Date: Fri, 13 Feb 2026 14:19:04 -0500
Subject: [PATCH 03/23] Update data-testid attribute in EditorContainer for
consistency
---
.../src/components-react/editors/EditorContainer.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ui/components-react/src/components-react/editors/EditorContainer.tsx b/ui/components-react/src/components-react/editors/EditorContainer.tsx
index 535041c14f3..26f300ea2cd 100644
--- a/ui/components-react/src/components-react/editors/EditorContainer.tsx
+++ b/ui/components-react/src/components-react/editors/EditorContainer.tsx
@@ -295,7 +295,7 @@ export function EditorContainer(props: EditorContainerProps) {
onClick={handleClick}
onContextMenu={handleContextMenu}
title={title}
- data-testid={props.itemId ?? "components-editor-container"}
+ data-testid={props.itemId ?? "editor-container"}
role="presentation"
>
{clonedNode}
From 57617e1c97f447156b78d86cffc766f0be45fe47 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 13 Feb 2026 14:23:07 -0500
Subject: [PATCH 04/23] Add support for customizable data-testids in
PropertyEditor components
---
.changeset/sweet-islands-dance.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/sweet-islands-dance.md
diff --git a/.changeset/sweet-islands-dance.md b/.changeset/sweet-islands-dance.md
new file mode 100644
index 00000000000..52e2ed7da1e
--- /dev/null
+++ b/.changeset/sweet-islands-dance.md
@@ -0,0 +1,5 @@
+---
+"@itwin/components-react": minor
+---
+
+Provide option to define data-testids for PropertyEditor components
From 107ab08b2e48e00665a543a0c8a73a3d0dbaa7e2 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 16:50:20 -0400
Subject: [PATCH 05/23] fix(a11y): forward id to interactive elements in new
property editors
Associate each new editor's interactive element (Input, Select, Checkbox,
ToggleSwitch) with the property name via the HTML id attribute. This
ensures that the existing in
ComponentGenerator correctly targets the editor, enabling:
- getByLabelText() queries in tests
- Accessible label/input association when toolSettingsNewEditors is enabled
Resolves the a11y gap Gerardas pointed out: inputs were correctly labelled
with toolSettingsNewEditors disabled (legacy EditorContainer sets id) but
not when it was enabled (new editors omitted the id).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/components-react/new-editors/Types.ts | 6 ++++++
.../components-react/new-editors/editors/BooleanEditor.tsx | 2 ++
.../src/components-react/new-editors/editors/EnumEditor.tsx | 2 ++
.../components-react/new-editors/editors/FallbackEditor.tsx | 4 ++--
.../components-react/new-editors/editors/NumericEditor.tsx | 2 ++
.../src/components-react/new-editors/editors/TextEditor.tsx | 2 ++
.../components-react/new-editors/editors/ToggleEditor.tsx | 2 ++
.../new-editors/interop/PropertyRecordEditor.tsx | 4 ++++
8 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/ui/components-react/src/components-react/new-editors/Types.ts b/ui/components-react/src/components-react/new-editors/Types.ts
index 70deef4df00..5d583491cb8 100644
--- a/ui/components-react/src/components-react/new-editors/Types.ts
+++ b/ui/components-react/src/components-react/new-editors/Types.ts
@@ -40,6 +40,12 @@ export interface EditorProps {
cancel?: () => void;
disabled?: boolean;
size?: "small" | "large";
+ /**
+ * HTML `id` attribute forwarded to the interactive element. Used to associate
+ * the editor with a `` so that `getByLabelText` queries work and
+ * the UI meets accessibility requirements.
+ */
+ id?: string;
}
/**
diff --git a/ui/components-react/src/components-react/new-editors/editors/BooleanEditor.tsx b/ui/components-react/src/components-react/new-editors/editors/BooleanEditor.tsx
index b97ecc29cd8..9b735c93260 100644
--- a/ui/components-react/src/components-react/new-editors/editors/BooleanEditor.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/BooleanEditor.tsx
@@ -20,6 +20,7 @@ export function BooleanEditor({
onChange,
commit,
disabled,
+ id,
}: EditorProps) {
const currentValue = value?.value ?? false;
const handleChange = (e: React.ChangeEvent) => {
@@ -30,6 +31,7 @@ export function BooleanEditor({
return (
) {
const choices = metadata.choices;
const currentValue = getEnumValue(value, choices);
@@ -35,6 +36,7 @@ export function EnumEditor({
return (
;
+export function FallbackEditor({ value, size, id }: EditorProps) {
+ return ;
}
function getTextValue(value?: Value) {
diff --git a/ui/components-react/src/components-react/new-editors/editors/NumericEditor.tsx b/ui/components-react/src/components-react/new-editors/editors/NumericEditor.tsx
index 3588f114733..34d6e7e73b7 100644
--- a/ui/components-react/src/components-react/new-editors/editors/NumericEditor.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/NumericEditor.tsx
@@ -20,10 +20,12 @@ export function NumericEditor({
onChange,
size,
disabled,
+ id,
}: EditorProps) {
const currentValue = getNumericValue(value);
return (
{
const parsedValue = parseFloat(e.target.value);
diff --git a/ui/components-react/src/components-react/new-editors/editors/TextEditor.tsx b/ui/components-react/src/components-react/new-editors/editors/TextEditor.tsx
index 77f2d67a8e7..be691d5e830 100644
--- a/ui/components-react/src/components-react/new-editors/editors/TextEditor.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/TextEditor.tsx
@@ -20,11 +20,13 @@ export function TextEditor({
onChange,
size,
disabled,
+ id,
}: EditorProps) {
const currentValue = value ? value : { value: "" };
return (
onChange({ value: e.target.value })}
size={size}
diff --git a/ui/components-react/src/components-react/new-editors/editors/ToggleEditor.tsx b/ui/components-react/src/components-react/new-editors/editors/ToggleEditor.tsx
index 462b45cc2a4..7d2b1ef83b8 100644
--- a/ui/components-react/src/components-react/new-editors/editors/ToggleEditor.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/ToggleEditor.tsx
@@ -21,6 +21,7 @@ export function ToggleEditor({
commit,
disabled,
size,
+ id,
}: EditorProps) {
const currentValue = value ?? { value: false };
const handleChange = (e: React.ChangeEvent) => {
@@ -31,6 +32,7 @@ export function ToggleEditor({
return (
);
}
@@ -86,6 +87,7 @@ function CommittingEditor({
onClick,
disabled,
size,
+ id,
}: {
metadata: ValueMetadata;
initialValue?: Value;
@@ -94,6 +96,7 @@ function CommittingEditor({
onClick?: () => void;
disabled?: boolean;
size?: "small" | "large";
+ id?: string;
}) {
const { value, onChange, onKeydown, commit, cancel } = useCommittableValue({
initialValue,
@@ -116,6 +119,7 @@ function CommittingEditor({
cancel={cancel}
disabled={disabled}
size={size}
+ id={id}
/>
);
From 52453ee8492ccf76745f0630824c869477bbbadd Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 16:56:19 -0400
Subject: [PATCH 06/23] test: add coverage for id/label a11y fix in new
property editors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Adds 5 new tests across two files:
PropertyRecordEditor.test.tsx — integration:
- sets id={property.name} on the input (new editor system)
- allows getByLabelText for text properties (new editor system)
- sets id={property.name} on the select for enum properties
- guard: legacy editor system path still renders EditorContainer
EditorRenderer.test.tsx — unit:
- forwards id prop to the rendered editor element
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../test/new-editors/EditorRenderer.test.tsx | 13 +++
.../interop/PropertyRecordEditor.test.tsx | 85 ++++++++++++++++++-
2 files changed, 97 insertions(+), 1 deletion(-)
diff --git a/ui/components-react/src/test/new-editors/EditorRenderer.test.tsx b/ui/components-react/src/test/new-editors/EditorRenderer.test.tsx
index f7f4d8c049d..b27c8c23f4a 100644
--- a/ui/components-react/src/test/new-editors/EditorRenderer.test.tsx
+++ b/ui/components-react/src/test/new-editors/EditorRenderer.test.tsx
@@ -41,4 +41,17 @@ describe("EditorRenderer", () => {
expect(queryByText("Multiline")).not.toBeNull();
expect(queryByText("Test message")).not.toBeNull();
});
+
+ it("forwards id prop to the rendered editor element", () => {
+ const { container } = render(
+ {}}
+ id="my-editor-id"
+ />
+ );
+
+ expect(container.querySelector('[id="my-editor-id"]')).not.toBeNull();
+ });
});
diff --git a/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx b/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx
index 0632c997c46..aeb20447e5a 100644
--- a/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx
+++ b/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx
@@ -7,7 +7,7 @@ import * as React from "react";
import { describe, it } from "vitest";
import { render, waitFor } from "@testing-library/react";
import { PropertyRecordEditor } from "../../../components-react.js";
-import { PropertyRecord } from "@itwin/appui-abstract";
+import { PropertyRecord, PropertyValueFormat } from "@itwin/appui-abstract";
describe("PropertyRecordEditor", () => {
const propertyRecord = PropertyRecord.fromString("test");
@@ -42,4 +42,87 @@ describe("PropertyRecordEditor", () => {
rendered.container.querySelector(".components-editor-container")
).toBeNull();
});
+
+ describe("id / label association (a11y)", () => {
+ it("sets id={property.name} on the input when using new editor system", async () => {
+ const record = PropertyRecord.fromString("hello", "arcLength");
+ const { container } = render(
+ {}}
+ onCancel={() => {}}
+ editorSystem="new"
+ />
+ );
+
+ await waitFor(() =>
+ expect(container.querySelector('[id="arcLength"]')).not.toBeNull()
+ );
+ });
+
+ it("allows getByLabelText queries for text properties when using new editor system", async () => {
+ const record = PropertyRecord.fromString("hello", "arcLength");
+ const { getByLabelText } = render(
+ <>
+ Arc Length
+ {}}
+ onCancel={() => {}}
+ editorSystem="new"
+ />
+ >
+ );
+
+ await waitFor(() => expect(getByLabelText("Arc Length")).toBeDefined());
+ });
+
+ it("sets id={property.name} on the select when using new editor system with enum property", async () => {
+ const record = new PropertyRecord(
+ { valueFormat: PropertyValueFormat.Primitive, value: 0 },
+ {
+ name: "arcType",
+ typename: "enum",
+ displayLabel: "Arc Type",
+ enum: {
+ choices: [
+ { label: "Clockwise", value: 0 },
+ { label: "Counterclockwise", value: 1 },
+ ],
+ },
+ }
+ );
+
+ const { container } = render(
+ {}}
+ onCancel={() => {}}
+ editorSystem="new"
+ />
+ );
+
+ await waitFor(() =>
+ expect(container.querySelector('[id="arcType"]')).not.toBeNull()
+ );
+ });
+
+ it("does not set id when using legacy editor system", async () => {
+ const record = PropertyRecord.fromString("hello", "arcLength");
+ const { container } = render(
+ {}}
+ onCancel={() => {}}
+ />
+ );
+
+ await waitFor(() => expect(container.querySelector("input")).not.toBeNull());
+ // Legacy EditorContainer uses property.name as id too, but through a different path.
+ // This test just guards that the new-editor id is not present on the container element.
+ expect(
+ container.querySelector(".components-editor-container")
+ ).not.toBeNull();
+ });
+ });
});
From 8f6a99ffc901113590ae8a848480539faeff9ec0 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 17:05:12 -0400
Subject: [PATCH 07/23] docs: add Storybook stories for new property editor
label/a11y fix
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Three stories under 'Components/New Property Editors':
- All editor types (new system): shows Text, Numeric, Enum, Boolean,
and Toggle editors each paired with a . Clicking
any label focuses its input — verifying the a11y fix works.
- Arc Drawing — ToolSettings use case: two enum selects (Arc Type,
Draw Method) plus a numeric field, all uniquely addressable by label.
This is the concrete scenario that motivated the fix.
- Legacy vs New — label association: side-by-side comparison of the
same enum editor rendered with the legacy EditorContainer (label
click doesn't focus) vs the new system (label click focuses).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../components/NewPropertyEditors.stories.tsx | 254 ++++++++++++++++++
1 file changed, 254 insertions(+)
create mode 100644 docs/storybook/src/components/NewPropertyEditors.stories.tsx
diff --git a/docs/storybook/src/components/NewPropertyEditors.stories.tsx b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
new file mode 100644
index 00000000000..63db73727ab
--- /dev/null
+++ b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
@@ -0,0 +1,254 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
+ * See LICENSE.md in the project root for license terms and full copyright notice.
+ *--------------------------------------------------------------------------------------------*/
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { PropertyRecord, PropertyValueFormat } from "@itwin/appui-abstract";
+import { PropertyRecordEditor } from "@itwin/components-react";
+import { action } from "storybook/actions";
+import { AppUiDecorator } from "../Decorators";
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+function makeTextRecord(value: string, name: string, label: string) {
+ return new PropertyRecord(
+ { valueFormat: PropertyValueFormat.Primitive, value },
+ { name, typename: "string", displayLabel: label }
+ );
+}
+
+function makeNumericRecord(value: number, name: string, label: string) {
+ return new PropertyRecord(
+ { valueFormat: PropertyValueFormat.Primitive, value },
+ { name, typename: "number", displayLabel: label }
+ );
+}
+
+function makeEnumRecord(
+ value: number,
+ name: string,
+ label: string,
+ choices: { label: string; value: number }[]
+) {
+ return new PropertyRecord(
+ { valueFormat: PropertyValueFormat.Primitive, value },
+ { name, typename: "enum", displayLabel: label, enum: { choices } }
+ );
+}
+
+function makeBooleanRecord(value: boolean, name: string, label: string) {
+ return new PropertyRecord(
+ { valueFormat: PropertyValueFormat.Primitive, value },
+ { name, typename: "boolean", displayLabel: label }
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Shared editor row layout
+// ---------------------------------------------------------------------------
+
+interface EditorRowProps {
+ label: string;
+ propertyName: string;
+ record: PropertyRecord;
+ editorSystem?: "new" | "legacy";
+}
+
+function EditorRow({ label, propertyName, record, editorSystem }: EditorRowProps) {
+ return (
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Story components
+// ---------------------------------------------------------------------------
+
+/** All editor types with working label association (new editor system). */
+function AllEditorsNew() {
+ return (
+
+
New editor system — click any label to focus its input
+
+
+
+
+
+
+ );
+}
+
+/** The specific arc-drawing ToolSettings use case: two enum selects distinguishable by label. */
+function ArcDrawingToolSettings() {
+ const arcTypeChoices = [
+ { label: "Clockwise", value: 0 },
+ { label: "Counter-clockwise", value: 1 },
+ { label: "Shortest", value: 2 },
+ ];
+ const methodChoices = [
+ { label: "3 Points", value: 0 },
+ { label: "Center + Radius", value: 1 },
+ { label: "Start/End/Mid", value: 2 },
+ ];
+
+ return (
+
+
Arc Drawing — ToolSettings
+
+ Both selects previously shared the same data-testid, making
+ them indistinguishable in tests. With the a11y fix, each is uniquely
+ accessible via its label.
+
+
+
+
+
+ );
+}
+
+/** Side-by-side comparison: legacy system (no label focus) vs new system (label focus works). */
+function LegacyVsNew() {
+ const record = makeEnumRecord(0, "arcType", "Arc Type", [
+ { label: "Clockwise", value: 0 },
+ { label: "Counter-clockwise", value: 1 },
+ ]);
+
+ return (
+
+
+
+ ❌ Legacy system
+
+ (label click doesn't focus the select)
+
+
+
+
+
+
+ ✅ New system
+
+ (label click focuses the select)
+
+
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Meta
+// ---------------------------------------------------------------------------
+
+const meta = {
+ title: "Components/New Property Editors",
+ decorators: [AppUiDecorator],
+ parameters: {
+ docs: {
+ description: {
+ component:
+ "New property editor system (`toolSettingsNewEditors`). Each editor forwards `id={property.name}` to its interactive element, enabling `` association for both accessibility and test queries (`getByLabelText`).",
+ },
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const AllEditors: Story = {
+ name: "All editor types (new system)",
+ render: () => ,
+};
+
+export const ArcToolSettings: Story = {
+ name: "Arc Drawing — ToolSettings use case",
+ render: () => ,
+};
+
+export const LegacyVsNewComparison: Story = {
+ name: "Legacy vs New — label association",
+ render: () => ,
+};
From 13ef9cf46afb2c13614abfc838791507f9e0a979 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 17:44:35 -0400
Subject: [PATCH 08/23] docs: add legacy editor targeting strategies story
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Shows every legacy editor type (string, number, enum, boolean, toggle)
in a table annotating what selector you can use in tests:
string → id = property.name → getByLabelText / #name ✅ unique
number → no id or data-testid on the ⚠ not targetable
enum → data-testid='components-select-editor' ⚠ not unique
boolean → data-testid='components-checkbox-editor' ⚠ not unique
toggle → data-testid='components-toggle-editor' ⚠ not unique
Also renders two enum selects side-by-side to demonstrate the collision
that motivated the a11y fix (both share the same data-testid).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../components/NewPropertyEditors.stories.tsx | 136 +++++++++++++++++-
1 file changed, 131 insertions(+), 5 deletions(-)
diff --git a/docs/storybook/src/components/NewPropertyEditors.stories.tsx b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
index 63db73727ab..3c0a50832e7 100644
--- a/docs/storybook/src/components/NewPropertyEditors.stories.tsx
+++ b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
@@ -97,13 +97,13 @@ function AllEditorsNew() {
label="Description"
propertyName="description"
record={makeTextRecord("My value", "description", "Description")}
- editorSystem="new"
+ // editorSystem="new"
/>
);
@@ -177,6 +177,127 @@ function ArcDrawingToolSettings() {
);
}
+/**
+ * Shows all legacy editor types with annotated targeting strategies.
+ *
+ * Targeting summary:
+ * - string → `id` = property name → use `#name` or `getByLabelText`
+ * - number → no `id` or `data-testid` on the input itself
+ * - enum → `data-testid="components-select-editor"` (fixed, not unique)
+ * - boolean → `data-testid="components-checkbox-editor"` (fixed, not unique)
+ * - toggle → `data-testid="components-toggle-editor"` (fixed, not unique)
+ * - wrapper → `data-testid="editor-container"` (also fixed for every editor)
+ */
+function LegacyEditorTargeting() {
+ const noop = () => {};
+
+ const rows: { label: string; record: PropertyRecord; strategy: string; selector: string }[] = [
+ {
+ label: "Name (string)",
+ record: makeTextRecord("Alice", "name", "Name"),
+ strategy: "id = property name",
+ selector: '#name / getByLabelText("Name")',
+ },
+ {
+ label: "Count (number)",
+ record: makeNumericRecord(42, "count", "Count"),
+ strategy: "⚠ no id / data-testid on ",
+ selector: "no unique selector available",
+ },
+ {
+ label: "Arc Type (enum)",
+ record: makeEnumRecord(0, "arcType", "Arc Type", [
+ { label: "Clockwise", value: 0 },
+ { label: "CCW", value: 1 },
+ ]),
+ strategy: 'data-testid (hardcoded default)',
+ selector: 'getByTestId("components-select-editor")',
+ },
+ {
+ label: "Snap (boolean)",
+ record: makeBooleanRecord(false, "snap", "Snap"),
+ strategy: 'data-testid (hardcoded default)',
+ selector: 'getByTestId("components-checkbox-editor")',
+ },
+ {
+ label: "Preview (toggle)",
+ record: new PropertyRecord(
+ { valueFormat: PropertyValueFormat.Primitive, value: true },
+ { name: "preview", typename: "bool", displayLabel: "Preview", editor: { name: "toggle" } }
+ ),
+ strategy: 'data-testid (hardcoded default)',
+ selector: 'getByTestId("components-toggle-editor")',
+ },
+ ];
+
+ return (
+
+
Legacy editor system — targeting strategies
+
+ Inspect the DOM to verify the selectors. Note that enum/boolean/toggle
+ share the same data-testid — they are not unique when
+ multiple instances appear on the same page.
+
+
+
+
+ Editor
+ Rendered input
+ Strategy
+ Selector
+
+
+
+ {rows.map(({ label, record, strategy, selector }) => (
+
+
+ {label}
+
+
+
+
+
+ {strategy}
+
+
+ {selector}
+
+
+ ))}
+
+
+
+
+ Problem: two enum editors — both have the same data-testid
+
+
+
+ Both selects have data-testid="components-select-editor".{" "}
+ getByTestId would throw "Found multiple elements".
+
+
+ );
+}
+
/** Side-by-side comparison: legacy system (no label focus) vs new system (label focus works). */
function LegacyVsNew() {
const record = makeEnumRecord(0, "arcType", "Arc Type", [
@@ -252,3 +373,8 @@ export const LegacyVsNewComparison: Story = {
name: "Legacy vs New — label association",
render: () => ,
};
+
+export const LegacyEditorIds: Story = {
+ name: "Legacy system — targeting strategies",
+ render: () => ,
+};
From f320609d63441eece6464b0ec332d1247c40731d Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 17:52:34 -0400
Subject: [PATCH 09/23] docs: fix number editor targeting annotation in legacy
story
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
number typename with no specific editor name falls back to
BasicPropertyEditor → TextEditor, which sets id={property.name}.
So id-based targeting works for number just like string.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../storybook/src/components/NewPropertyEditors.stories.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/storybook/src/components/NewPropertyEditors.stories.tsx b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
index 3c0a50832e7..8cabbb14979 100644
--- a/docs/storybook/src/components/NewPropertyEditors.stories.tsx
+++ b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
@@ -182,7 +182,7 @@ function ArcDrawingToolSettings() {
*
* Targeting summary:
* - string → `id` = property name → use `#name` or `getByLabelText`
- * - number → no `id` or `data-testid` on the input itself
+ * - number → `id` = property name (default falls back to TextEditor)
* - enum → `data-testid="components-select-editor"` (fixed, not unique)
* - boolean → `data-testid="components-checkbox-editor"` (fixed, not unique)
* - toggle → `data-testid="components-toggle-editor"` (fixed, not unique)
@@ -201,8 +201,8 @@ function LegacyEditorTargeting() {
{
label: "Count (number)",
record: makeNumericRecord(42, "count", "Count"),
- strategy: "⚠ no id / data-testid on ",
- selector: "no unique selector available",
+ strategy: "id = property name (falls back to TextEditor)",
+ selector: '#count / getByLabelText("Count")',
},
{
label: "Arc Type (enum)",
From 18154f60f60fcc11ba3f6b266265dd142df5f2fb Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:09:19 -0400
Subject: [PATCH 10/23] fix: add id={property.name} to legacy enum, boolean,
and toggle editors
Mirrors what TextEditor already does. Each editor now sets
id={propertyRecord.property.name} on its native element
(, , ) alongside the existing
data-testid, enabling label association and getByLabelText queries.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/components-react/editors/BooleanEditor.tsx | 1 +
ui/components-react/src/components-react/editors/EnumEditor.tsx | 1 +
.../src/components-react/editors/ToggleEditor.tsx | 1 +
3 files changed, 3 insertions(+)
diff --git a/ui/components-react/src/components-react/editors/BooleanEditor.tsx b/ui/components-react/src/components-react/editors/BooleanEditor.tsx
index fac3525d13c..a6cc9632c6e 100644
--- a/ui/components-react/src/components-react/editors/BooleanEditor.tsx
+++ b/ui/components-react/src/components-react/editors/BooleanEditor.tsx
@@ -140,6 +140,7 @@ export class BooleanEditor
this.props.propertyRecord?.isReadonly
}
data-testid={this.props.itemId ?? "components-checkbox-editor"}
+ id={this.props.propertyRecord?.property.name}
>
);
}
diff --git a/ui/components-react/src/components-react/editors/EnumEditor.tsx b/ui/components-react/src/components-react/editors/EnumEditor.tsx
index e4d2146eba6..55e07797f60 100644
--- a/ui/components-react/src/components-react/editors/EnumEditor.tsx
+++ b/ui/components-react/src/components-react/editors/EnumEditor.tsx
@@ -181,6 +181,7 @@ export class EnumEditor
value={selectValue}
onChange={this._updateSelectValue}
data-testid={this.props.itemId ?? "components-select-editor"}
+ id={this.props.propertyRecord?.property.name}
options={this.state.options}
triggerProps={{
ref: (el) => {
diff --git a/ui/components-react/src/components-react/editors/ToggleEditor.tsx b/ui/components-react/src/components-react/editors/ToggleEditor.tsx
index 0295a6500cc..a9602cb7039 100644
--- a/ui/components-react/src/components-react/editors/ToggleEditor.tsx
+++ b/ui/components-react/src/components-react/editors/ToggleEditor.tsx
@@ -144,6 +144,7 @@ export class ToggleEditor
disabled={this.props.propertyRecord?.isDisabled}
onChange={this._updateToggleValue}
data-testid={this.props.itemId ?? "components-toggle-editor"}
+ id={this.props.propertyRecord?.property.name}
autoFocus={this.props.setFocus} // eslint-disable-line jsx-a11y/no-autofocus
/>
);
From 14c2ee2aaf69fc836cefddbd47dd1d0be40e56fb Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:09:56 -0400
Subject: [PATCH 11/23] docs: update legacy targeting story to reflect id fix
on all editor types
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../components/NewPropertyEditors.stories.tsx | 24 +++++++++----------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/docs/storybook/src/components/NewPropertyEditors.stories.tsx b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
index 8cabbb14979..0289ec320a5 100644
--- a/docs/storybook/src/components/NewPropertyEditors.stories.tsx
+++ b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
@@ -183,9 +183,9 @@ function ArcDrawingToolSettings() {
* Targeting summary:
* - string → `id` = property name → use `#name` or `getByLabelText`
* - number → `id` = property name (default falls back to TextEditor)
- * - enum → `data-testid="components-select-editor"` (fixed, not unique)
- * - boolean → `data-testid="components-checkbox-editor"` (fixed, not unique)
- * - toggle → `data-testid="components-toggle-editor"` (fixed, not unique)
+ * - enum → `id` = property name (after fix)
+ * - boolean → `id` = property name (after fix)
+ * - toggle → `id` = property name (after fix)
* - wrapper → `data-testid="editor-container"` (also fixed for every editor)
*/
function LegacyEditorTargeting() {
@@ -210,14 +210,14 @@ function LegacyEditorTargeting() {
{ label: "Clockwise", value: 0 },
{ label: "CCW", value: 1 },
]),
- strategy: 'data-testid (hardcoded default)',
- selector: 'getByTestId("components-select-editor")',
+ strategy: "id = property name",
+ selector: '#arcType / getByLabelText("Arc Type")',
},
{
label: "Snap (boolean)",
record: makeBooleanRecord(false, "snap", "Snap"),
- strategy: 'data-testid (hardcoded default)',
- selector: 'getByTestId("components-checkbox-editor")',
+ strategy: "id = property name",
+ selector: '#snap / getByLabelText("Snap")',
},
{
label: "Preview (toggle)",
@@ -225,8 +225,8 @@ function LegacyEditorTargeting() {
{ valueFormat: PropertyValueFormat.Primitive, value: true },
{ name: "preview", typename: "bool", displayLabel: "Preview", editor: { name: "toggle" } }
),
- strategy: 'data-testid (hardcoded default)',
- selector: 'getByTestId("components-toggle-editor")',
+ strategy: "id = property name",
+ selector: '#preview / getByLabelText("Preview")',
},
];
@@ -272,7 +272,7 @@ function LegacyEditorTargeting() {
- Problem: two enum editors — both have the same data-testid
+ ✅ Two enum editors — now uniquely targetable by id
- Both selects have data-testid="components-select-editor".{" "}
- getByTestId would throw "Found multiple elements".
+ #arcType and #drawMethod are now distinct.{" "}
+ Previously both shared data-testid="components-select-editor".
);
From 3acc81ebe1f26b4002447f48d405063c4efedf50 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:15:47 -0400
Subject: [PATCH 12/23] fix: add id={property.name} to legacy
ImageCheckBoxEditor
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/components-react/editors/ImageCheckBoxEditor.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx b/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx
index 79c4aa528ad..5da9ed14124 100644
--- a/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx
+++ b/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx
@@ -162,6 +162,7 @@ export class ImageCheckBoxEditor
disabled={isDisabled}
onClick={this._handleClick}
data-testid={this.props.itemId ?? "components-imagecheckbox-editor"}
+ id={this.props.propertyRecord?.property.name}
/>
);
}
From 1e5ce0b287d697b422b921f9f35587f40acece53 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:18:06 -0400
Subject: [PATCH 13/23] fix: add id={property.name} to remaining legacy editors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
TextareaEditor, IconEditor, EnumButtonGroupEditor, DateTimeEditor,
SliderEditor all now expose id={propertyRecord.property.name} on
their root/interactive element.
For popup-based editors (Textarea, DateTime, Slider) the id is
placed on the outer container div — the always-visible trigger —
since the popup content may not be in the DOM when closed.
CustomNumberEditor was already fixed in a prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/components-react/editors/DateTimeEditor.tsx | 2 +-
.../src/components-react/editors/EnumButtonGroupEditor.tsx | 1 +
ui/components-react/src/components-react/editors/IconEditor.tsx | 1 +
.../src/components-react/editors/SliderEditor.tsx | 2 +-
.../src/components-react/editors/TextareaEditor.tsx | 2 +-
5 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
index af3f84e208b..5122027920e 100644
--- a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
+++ b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
@@ -291,7 +291,7 @@ export class DateTimeEditor
);
return (
-
+
{this.state.choices &&
this.state.enumIcons.length &&
diff --git a/ui/components-react/src/components-react/editors/IconEditor.tsx b/ui/components-react/src/components-react/editors/IconEditor.tsx
index 413378b5def..3a13fa9e36d 100644
--- a/ui/components-react/src/components-react/editors/IconEditor.tsx
+++ b/ui/components-react/src/components-react/editors/IconEditor.tsx
@@ -171,6 +171,7 @@ export class IconEditor
readonly={this.props.propertyRecord?.isReadonly}
onIconChange={this._onIconChange}
data-testid={this.props.itemId ?? "components-icon-editor"}
+ id={this.props.propertyRecord?.property.name}
/>
);
diff --git a/ui/components-react/src/components-react/editors/SliderEditor.tsx b/ui/components-react/src/components-react/editors/SliderEditor.tsx
index 6ee4d451719..6174fcc77b1 100644
--- a/ui/components-react/src/components-react/editors/SliderEditor.tsx
+++ b/ui/components-react/src/components-react/editors/SliderEditor.tsx
@@ -317,7 +317,7 @@ export class SliderEditor
);
return (
-
+
+
Date: Fri, 24 Apr 2026 18:19:39 -0400
Subject: [PATCH 14/23] fix: add id prop to ImageCheckBox and wire it in
ImageCheckBoxEditor
ImageCheckBoxProps lacked an id field (CommonProps only has itemId).
Added id?: string to the interface and thread it to the native
element, consistent with all other legacy editors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
ui/core-react/src/core-react/imagecheckbox/ImageCheckBox.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/ui/core-react/src/core-react/imagecheckbox/ImageCheckBox.tsx b/ui/core-react/src/core-react/imagecheckbox/ImageCheckBox.tsx
index cc8eb258433..78fa3773ff4 100644
--- a/ui/core-react/src/core-react/imagecheckbox/ImageCheckBox.tsx
+++ b/ui/core-react/src/core-react/imagecheckbox/ImageCheckBox.tsx
@@ -39,6 +39,8 @@ export interface ImageCheckBoxProps extends CommonProps {
border?: boolean;
/** Provides ability to return reference to HTMLInputElement */
inputRef?: React.Ref;
+ /** HTML id attribute for the checkbox input element */
+ id?: string;
}
/** ImageCheckBox React component shows a checked or unchecked image
@@ -85,6 +87,7 @@ export class ImageCheckBox extends React.PureComponent {
>
Date: Fri, 24 Apr 2026 18:27:48 -0400
Subject: [PATCH 15/23] fix: propagate id prop through all new-editor
old-editor interop wrappers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
TextArea, Color, CustomNumber, EnumButtonGroup, NumericInput, Slider
were all missing id in their props destructuring and didn't forward it
to their primary element. Enum already forwarded id via {...props}.
Popup-based editors (TextArea, Color, Slider) place id on their
trigger / — the always-visible element.
Input-based editors (CustomNumber, NumericInput) place id on the .
EnumButtonGroup places id on the container.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../components-react/new-editors/interop/old-editors/Color.tsx | 3 ++-
.../new-editors/interop/old-editors/CustomNumber.tsx | 2 ++
.../new-editors/interop/old-editors/EnumButtonGroup.tsx | 3 ++-
.../new-editors/interop/old-editors/NumericInput.tsx | 3 ++-
.../new-editors/interop/old-editors/Slider.tsx | 3 ++-
.../new-editors/interop/old-editors/TextArea.tsx | 3 ++-
6 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/ui/components-react/src/components-react/new-editors/interop/old-editors/Color.tsx b/ui/components-react/src/components-react/new-editors/interop/old-editors/Color.tsx
index 95f1f405527..3c42238f639 100644
--- a/ui/components-react/src/components-react/new-editors/interop/old-editors/Color.tsx
+++ b/ui/components-react/src/components-react/new-editors/interop/old-editors/Color.tsx
@@ -48,6 +48,7 @@ function ColorEditor({
onChange,
commit,
size,
+ id,
}: EditorProps) {
const colorParams = useColorEditorParams(metadata);
const colors = colorParams?.colorValues ?? [];
@@ -81,7 +82,7 @@ function ColorEditor({
}
}}
>
-
+
diff --git a/ui/components-react/src/components-react/new-editors/interop/old-editors/CustomNumber.tsx b/ui/components-react/src/components-react/new-editors/interop/old-editors/CustomNumber.tsx
index aa0e1585be5..735f377c3ad 100644
--- a/ui/components-react/src/components-react/new-editors/interop/old-editors/CustomNumber.tsx
+++ b/ui/components-react/src/components-react/new-editors/interop/old-editors/CustomNumber.tsx
@@ -51,6 +51,7 @@ export function CustomNumberEditor({
size,
disabled,
decoration,
+ id,
}: CustomNumberEditorProps) {
const formatParams = useCustomFormattedNumberParams(metadata);
const sizeParams = useInputEditorSizeParams(metadata);
@@ -97,6 +98,7 @@ export function CustomNumberEditor({
{icon}
) : null}
) {
const enumMetadata = useEnumMetadata(metadata);
const buttonGroupParams = useButtonGroupEditorParams(metadata);
@@ -53,7 +54,7 @@ function EnumButtonGroupEditor({
const currentValue = value ? value : { choice: firstChoice?.value ?? "" };
return (
-
+
{enumMetadata.choices.map((choice) => {
const icon = findIcon(enumIcons?.get(choice.value));
return (
diff --git a/ui/components-react/src/components-react/new-editors/interop/old-editors/NumericInput.tsx b/ui/components-react/src/components-react/new-editors/interop/old-editors/NumericInput.tsx
index e99fd747417..c37e3de6bea 100644
--- a/ui/components-react/src/components-react/new-editors/interop/old-editors/NumericInput.tsx
+++ b/ui/components-react/src/components-react/new-editors/interop/old-editors/NumericInput.tsx
@@ -35,6 +35,7 @@ function NumericInputEditor({
onChange,
size,
disabled,
+ id,
}: EditorProps) {
const sizeParams = useInputEditorSizeParams(metadata);
const rangeParams = useRangeEditorParams(metadata);
@@ -63,7 +64,7 @@ function NumericInputEditor({
});
return (
-
+
);
}
diff --git a/ui/components-react/src/components-react/new-editors/interop/old-editors/Slider.tsx b/ui/components-react/src/components-react/new-editors/interop/old-editors/Slider.tsx
index 16c6ab391bb..1a79498b7f0 100644
--- a/ui/components-react/src/components-react/new-editors/interop/old-editors/Slider.tsx
+++ b/ui/components-react/src/components-react/new-editors/interop/old-editors/Slider.tsx
@@ -41,6 +41,7 @@ function SliderEditor({
onChange,
commit,
size,
+ id,
}: EditorProps) {
const sliderParams = useSliderEditorParams(metadata);
if (!sliderParams) {
@@ -98,7 +99,7 @@ function SliderEditor({
}}
applyBackground={true}
>
-
+
{currentValue}
diff --git a/ui/components-react/src/components-react/new-editors/interop/old-editors/TextArea.tsx b/ui/components-react/src/components-react/new-editors/interop/old-editors/TextArea.tsx
index ab22033d761..9de3b636de7 100644
--- a/ui/components-react/src/components-react/new-editors/interop/old-editors/TextArea.tsx
+++ b/ui/components-react/src/components-react/new-editors/interop/old-editors/TextArea.tsx
@@ -29,6 +29,7 @@ function TextAreaEditor({
commit,
size,
disabled,
+ id,
}: EditorProps) {
const currentValue = value ?? { value: "" };
@@ -50,7 +51,7 @@ function TextAreaEditor({
}
}}
>
-
+
{currentValue.value}
From 2cd20f3338bf9c526be84a8db056b54bfd45d62c Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:35:36 -0400
Subject: [PATCH 16/23] fix: propagate id through DateTimeEditor, DateEditor,
and DateInput
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
id threads from EditorProps → DateTimeEditor/DateEditor → DateInput
→ (the popup trigger, always visible in the DOM).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/components-react/new-editors/editors/DateEditor.tsx | 2 ++
.../src/components-react/new-editors/editors/DateInput.tsx | 4 +++-
.../components-react/new-editors/editors/DateTimeEditor.tsx | 2 ++
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/ui/components-react/src/components-react/new-editors/editors/DateEditor.tsx b/ui/components-react/src/components-react/new-editors/editors/DateEditor.tsx
index 7160aa59ad6..0463b9027a2 100644
--- a/ui/components-react/src/components-react/new-editors/editors/DateEditor.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/DateEditor.tsx
@@ -21,6 +21,7 @@ export function DateEditor({
commit,
size,
disabled,
+ id,
}: EditorProps) {
return (
);
}
diff --git a/ui/components-react/src/components-react/new-editors/editors/DateInput.tsx b/ui/components-react/src/components-react/new-editors/editors/DateInput.tsx
index db02219e692..0d75477e743 100644
--- a/ui/components-react/src/components-react/new-editors/editors/DateInput.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/DateInput.tsx
@@ -15,6 +15,7 @@ interface DateInputProps {
size?: "small" | "large";
disabled?: boolean;
showTimePicker?: boolean;
+ id?: string;
}
/**
@@ -27,6 +28,7 @@ export function DateInput({
size,
showTimePicker,
disabled,
+ id,
}: DateInputProps) {
const currentValue = value ?? new Date();
const dateStr = showTimePicker
@@ -50,7 +52,7 @@ export function DateInput({
}
}}
>
-
+
{dateStr}
diff --git a/ui/components-react/src/components-react/new-editors/editors/DateTimeEditor.tsx b/ui/components-react/src/components-react/new-editors/editors/DateTimeEditor.tsx
index 328340c7bd0..8f889dd123d 100644
--- a/ui/components-react/src/components-react/new-editors/editors/DateTimeEditor.tsx
+++ b/ui/components-react/src/components-react/new-editors/editors/DateTimeEditor.tsx
@@ -21,6 +21,7 @@ export function DateTimeEditor({
commit,
size,
disabled,
+ id,
}: EditorProps) {
return (
);
}
From e8e45c5bfa55426b2de7ae70e8e7bd4792799ca1 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:37:28 -0400
Subject: [PATCH 17/23] fix: add id={property.name} to legacy
NumericInputEditor
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/components-react/editors/NumericInputEditor.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/components-react/src/components-react/editors/NumericInputEditor.tsx b/ui/components-react/src/components-react/editors/NumericInputEditor.tsx
index e38aa79b4f4..4d5b387f4f6 100644
--- a/ui/components-react/src/components-react/editors/NumericInputEditor.tsx
+++ b/ui/components-react/src/components-react/editors/NumericInputEditor.tsx
@@ -190,6 +190,7 @@ export class NumericInputEditor
return (
Date: Fri, 24 Apr 2026 18:43:15 -0400
Subject: [PATCH 18/23] chore: remove NewPropertyEditors stories file to
streamline documentation
---
.../components/NewPropertyEditors.stories.tsx | 380 ------------------
1 file changed, 380 deletions(-)
delete mode 100644 docs/storybook/src/components/NewPropertyEditors.stories.tsx
diff --git a/docs/storybook/src/components/NewPropertyEditors.stories.tsx b/docs/storybook/src/components/NewPropertyEditors.stories.tsx
deleted file mode 100644
index 0289ec320a5..00000000000
--- a/docs/storybook/src/components/NewPropertyEditors.stories.tsx
+++ /dev/null
@@ -1,380 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
- * See LICENSE.md in the project root for license terms and full copyright notice.
- *--------------------------------------------------------------------------------------------*/
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { PropertyRecord, PropertyValueFormat } from "@itwin/appui-abstract";
-import { PropertyRecordEditor } from "@itwin/components-react";
-import { action } from "storybook/actions";
-import { AppUiDecorator } from "../Decorators";
-
-// ---------------------------------------------------------------------------
-// Helpers
-// ---------------------------------------------------------------------------
-
-function makeTextRecord(value: string, name: string, label: string) {
- return new PropertyRecord(
- { valueFormat: PropertyValueFormat.Primitive, value },
- { name, typename: "string", displayLabel: label }
- );
-}
-
-function makeNumericRecord(value: number, name: string, label: string) {
- return new PropertyRecord(
- { valueFormat: PropertyValueFormat.Primitive, value },
- { name, typename: "number", displayLabel: label }
- );
-}
-
-function makeEnumRecord(
- value: number,
- name: string,
- label: string,
- choices: { label: string; value: number }[]
-) {
- return new PropertyRecord(
- { valueFormat: PropertyValueFormat.Primitive, value },
- { name, typename: "enum", displayLabel: label, enum: { choices } }
- );
-}
-
-function makeBooleanRecord(value: boolean, name: string, label: string) {
- return new PropertyRecord(
- { valueFormat: PropertyValueFormat.Primitive, value },
- { name, typename: "boolean", displayLabel: label }
- );
-}
-
-// ---------------------------------------------------------------------------
-// Shared editor row layout
-// ---------------------------------------------------------------------------
-
-interface EditorRowProps {
- label: string;
- propertyName: string;
- record: PropertyRecord;
- editorSystem?: "new" | "legacy";
-}
-
-function EditorRow({ label, propertyName, record, editorSystem }: EditorRowProps) {
- return (
-
- );
-}
-
-// ---------------------------------------------------------------------------
-// Story components
-// ---------------------------------------------------------------------------
-
-/** All editor types with working label association (new editor system). */
-function AllEditorsNew() {
- return (
-
-
New editor system — click any label to focus its input
-
-
-
-
-
-
- );
-}
-
-/** The specific arc-drawing ToolSettings use case: two enum selects distinguishable by label. */
-function ArcDrawingToolSettings() {
- const arcTypeChoices = [
- { label: "Clockwise", value: 0 },
- { label: "Counter-clockwise", value: 1 },
- { label: "Shortest", value: 2 },
- ];
- const methodChoices = [
- { label: "3 Points", value: 0 },
- { label: "Center + Radius", value: 1 },
- { label: "Start/End/Mid", value: 2 },
- ];
-
- return (
-
-
Arc Drawing — ToolSettings
-
- Both selects previously shared the same data-testid, making
- them indistinguishable in tests. With the a11y fix, each is uniquely
- accessible via its label.
-
-
-
-
-
- );
-}
-
-/**
- * Shows all legacy editor types with annotated targeting strategies.
- *
- * Targeting summary:
- * - string → `id` = property name → use `#name` or `getByLabelText`
- * - number → `id` = property name (default falls back to TextEditor)
- * - enum → `id` = property name (after fix)
- * - boolean → `id` = property name (after fix)
- * - toggle → `id` = property name (after fix)
- * - wrapper → `data-testid="editor-container"` (also fixed for every editor)
- */
-function LegacyEditorTargeting() {
- const noop = () => {};
-
- const rows: { label: string; record: PropertyRecord; strategy: string; selector: string }[] = [
- {
- label: "Name (string)",
- record: makeTextRecord("Alice", "name", "Name"),
- strategy: "id = property name",
- selector: '#name / getByLabelText("Name")',
- },
- {
- label: "Count (number)",
- record: makeNumericRecord(42, "count", "Count"),
- strategy: "id = property name (falls back to TextEditor)",
- selector: '#count / getByLabelText("Count")',
- },
- {
- label: "Arc Type (enum)",
- record: makeEnumRecord(0, "arcType", "Arc Type", [
- { label: "Clockwise", value: 0 },
- { label: "CCW", value: 1 },
- ]),
- strategy: "id = property name",
- selector: '#arcType / getByLabelText("Arc Type")',
- },
- {
- label: "Snap (boolean)",
- record: makeBooleanRecord(false, "snap", "Snap"),
- strategy: "id = property name",
- selector: '#snap / getByLabelText("Snap")',
- },
- {
- label: "Preview (toggle)",
- record: new PropertyRecord(
- { valueFormat: PropertyValueFormat.Primitive, value: true },
- { name: "preview", typename: "bool", displayLabel: "Preview", editor: { name: "toggle" } }
- ),
- strategy: "id = property name",
- selector: '#preview / getByLabelText("Preview")',
- },
- ];
-
- return (
-
-
Legacy editor system — targeting strategies
-
- Inspect the DOM to verify the selectors. Note that enum/boolean/toggle
- share the same data-testid — they are not unique when
- multiple instances appear on the same page.
-
-
-
-
- Editor
- Rendered input
- Strategy
- Selector
-
-
-
- {rows.map(({ label, record, strategy, selector }) => (
-
-
- {label}
-
-
-
-
-
- {strategy}
-
-
- {selector}
-
-
- ))}
-
-
-
-
- ✅ Two enum editors — now uniquely targetable by id
-
-
-
- #arcType and #drawMethod are now distinct.{" "}
- Previously both shared data-testid="components-select-editor".
-
-
- );
-}
-
-/** Side-by-side comparison: legacy system (no label focus) vs new system (label focus works). */
-function LegacyVsNew() {
- const record = makeEnumRecord(0, "arcType", "Arc Type", [
- { label: "Clockwise", value: 0 },
- { label: "Counter-clockwise", value: 1 },
- ]);
-
- return (
-
-
-
- ❌ Legacy system
-
- (label click doesn't focus the select)
-
-
-
-
-
-
- ✅ New system
-
- (label click focuses the select)
-
-
-
-
-
- );
-}
-
-// ---------------------------------------------------------------------------
-// Meta
-// ---------------------------------------------------------------------------
-
-const meta = {
- title: "Components/New Property Editors",
- decorators: [AppUiDecorator],
- parameters: {
- docs: {
- description: {
- component:
- "New property editor system (`toolSettingsNewEditors`). Each editor forwards `id={property.name}` to its interactive element, enabling `` association for both accessibility and test queries (`getByLabelText`).",
- },
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const AllEditors: Story = {
- name: "All editor types (new system)",
- render: () => ,
-};
-
-export const ArcToolSettings: Story = {
- name: "Arc Drawing — ToolSettings use case",
- render: () => ,
-};
-
-export const LegacyVsNewComparison: Story = {
- name: "Legacy vs New — label association",
- render: () => ,
-};
-
-export const LegacyEditorIds: Story = {
- name: "Legacy system — targeting strategies",
- render: () => ,
-};
From d3e72a351d26b524d86f5a8ad626f5fcd215d2b7 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:48:46 -0400
Subject: [PATCH 19/23] chore: update core-react api.md and add beforeunload to
cspell words
- common/api/core-react.api.md: updated to include id?: string added
to ImageCheckBoxProps
- .vscode/cSpell.json: add 'beforeunload' (pre-existing CHANGELOG word)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.vscode/cSpell.json | 1 +
common/api/core-react.api.md | 1 +
2 files changed, 2 insertions(+)
diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json
index 110bea8d6cc..a73ee5f42a8 100644
--- a/.vscode/cSpell.json
+++ b/.vscode/cSpell.json
@@ -29,6 +29,7 @@
"backstageevent",
"badeditor",
"badtype",
+ "beforeunload",
"betools",
"borderless",
"boxshadow",
diff --git a/common/api/core-react.api.md b/common/api/core-react.api.md
index c6d1b1c3a09..db5c7d5f857 100644
--- a/common/api/core-react.api.md
+++ b/common/api/core-react.api.md
@@ -649,6 +649,7 @@ export interface ImageCheckBoxProps extends CommonProps {
border?: boolean;
checked?: boolean;
disabled?: boolean;
+ id?: string;
imageOff: string | React_2.ReactNode;
imageOn: string | React_2.ReactNode;
inputClassName?: string;
From 7e2871b294804b9124bdb210e5b711c5658dbf6d Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Fri, 24 Apr 2026 18:58:00 -0400
Subject: [PATCH 20/23] chore: fix prettier formatting and update
components-react api.md
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
common/api/components-react.api.md | 1 +
.../src/components-react/editors/DateTimeEditor.tsx | 6 +++++-
.../src/components-react/editors/SliderEditor.tsx | 6 +++++-
.../src/components-react/editors/TextareaEditor.tsx | 6 +++++-
.../new-editors/interop/old-editors/NumericInput.tsx | 8 +++++++-
.../new-editors/interop/PropertyRecordEditor.test.tsx | 4 +++-
6 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/common/api/components-react.api.md b/common/api/components-react.api.md
index 737071fa751..8e1b0035609 100644
--- a/common/api/components-react.api.md
+++ b/common/api/components-react.api.md
@@ -584,6 +584,7 @@ export interface EditorProps {
commit?: () => void;
// (undocumented)
disabled?: boolean;
+ id?: string;
// (undocumented)
metadata: TMetadata;
// (undocumented)
diff --git a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
index 5122027920e..41fb98cb9d1 100644
--- a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
+++ b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
@@ -291,7 +291,11 @@ export class DateTimeEditor
);
return (
-
+
+
+
+
);
}
diff --git a/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx b/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx
index aeb20447e5a..e5e67eb8aeb 100644
--- a/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx
+++ b/ui/components-react/src/test/new-editors/interop/PropertyRecordEditor.test.tsx
@@ -117,7 +117,9 @@ describe("PropertyRecordEditor", () => {
/>
);
- await waitFor(() => expect(container.querySelector("input")).not.toBeNull());
+ await waitFor(() =>
+ expect(container.querySelector("input")).not.toBeNull()
+ );
// Legacy EditorContainer uses property.name as id too, but through a different path.
// This test just guards that the new-editor id is not present on the container element.
expect(
From 1771becde6ab9aec7a72ebf9b3eb2a0f821db1ba Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:47:49 -0400
Subject: [PATCH 21/23] PR Comment: revert the data-testid changes
---
.../src/components-react/editors/BooleanEditor.tsx | 2 +-
.../src/components-react/editors/DateTimeEditor.tsx | 5 +----
.../src/components-react/editors/EditorContainer.tsx | 2 +-
.../src/components-react/editors/EnumEditor.tsx | 2 +-
.../src/components-react/editors/IconEditor.tsx | 2 +-
.../src/components-react/editors/ImageCheckBoxEditor.tsx | 2 +-
.../src/components-react/editors/TextareaEditor.tsx | 2 +-
.../src/components-react/editors/ToggleEditor.tsx | 2 +-
8 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/ui/components-react/src/components-react/editors/BooleanEditor.tsx b/ui/components-react/src/components-react/editors/BooleanEditor.tsx
index a6cc9632c6e..af70562abb3 100644
--- a/ui/components-react/src/components-react/editors/BooleanEditor.tsx
+++ b/ui/components-react/src/components-react/editors/BooleanEditor.tsx
@@ -139,7 +139,7 @@ export class BooleanEditor
this.props.propertyRecord?.isDisabled ||
this.props.propertyRecord?.isReadonly
}
- data-testid={this.props.itemId ?? "components-checkbox-editor"}
+ data-testid="components-checkbox-editor"
id={this.props.propertyRecord?.property.name}
>
);
diff --git a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
index 41fb98cb9d1..e4776bccb8d 100644
--- a/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
+++ b/ui/components-react/src/components-react/editors/DateTimeEditor.tsx
@@ -308,10 +308,7 @@ export class DateTimeEditor
<>
{clonedNode}
diff --git a/ui/components-react/src/components-react/editors/EnumEditor.tsx b/ui/components-react/src/components-react/editors/EnumEditor.tsx
index 55e07797f60..5db0a404d24 100644
--- a/ui/components-react/src/components-react/editors/EnumEditor.tsx
+++ b/ui/components-react/src/components-react/editors/EnumEditor.tsx
@@ -180,7 +180,7 @@ export class EnumEditor
style={this.props.style ? this.props.style : minWidthStyle}
value={selectValue}
onChange={this._updateSelectValue}
- data-testid={this.props.itemId ?? "components-select-editor"}
+ data-testid="components-select-editor"
id={this.props.propertyRecord?.property.name}
options={this.state.options}
triggerProps={{
diff --git a/ui/components-react/src/components-react/editors/IconEditor.tsx b/ui/components-react/src/components-react/editors/IconEditor.tsx
index 3a13fa9e36d..7d9e8db4cb2 100644
--- a/ui/components-react/src/components-react/editors/IconEditor.tsx
+++ b/ui/components-react/src/components-react/editors/IconEditor.tsx
@@ -170,7 +170,7 @@ export class IconEditor
disabled={this.props.propertyRecord?.isDisabled}
readonly={this.props.propertyRecord?.isReadonly}
onIconChange={this._onIconChange}
- data-testid={this.props.itemId ?? "components-icon-editor"}
+ data-testid="components-icon-editor"
id={this.props.propertyRecord?.property.name}
/>
diff --git a/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx b/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx
index 5da9ed14124..224e0e52ac8 100644
--- a/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx
+++ b/ui/components-react/src/components-react/editors/ImageCheckBoxEditor.tsx
@@ -161,7 +161,7 @@ export class ImageCheckBoxEditor
checked={checked}
disabled={isDisabled}
onClick={this._handleClick}
- data-testid={this.props.itemId ?? "components-imagecheckbox-editor"}
+ data-testid="components-imagecheckbox-editor"
id={this.props.propertyRecord?.property.name}
/>
);
diff --git a/ui/components-react/src/components-react/editors/TextareaEditor.tsx b/ui/components-react/src/components-react/editors/TextareaEditor.tsx
index 9b58a13c490..1f0d7d0ffca 100644
--- a/ui/components-react/src/components-react/editors/TextareaEditor.tsx
+++ b/ui/components-react/src/components-react/editors/TextareaEditor.tsx
@@ -222,7 +222,7 @@ export class TextareaEditor
From 4978487bc00cd05f959a3d7b624133eb9bcc48cf Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:48:16 -0400
Subject: [PATCH 22/23] fix: update runtimeArgs from "start" to "dev" in launch
configuration for storybook
---
.vscode/launch.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 892321a6437..4278bde17ab 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -80,7 +80,7 @@
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
- "runtimeArgs": ["run", "start"],
+ "runtimeArgs": ["run", "dev"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"serverReadyAction": {
From 8af3bf099116d40b01b07ff5e06393e998d818c5 Mon Sep 17 00:00:00 2001
From: "Omar H." <3652875+arome@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:49:54 -0400
Subject: [PATCH 23/23] fix: standardize data-testid attributes in ColorEditor
and WeightEditor components
---
.../src/imodel-components-react/editors/ColorEditor.tsx | 2 +-
.../src/imodel-components-react/editors/WeightEditor.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx
index 9f16e7bbc7c..19d1d505efc 100644
--- a/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx
+++ b/ui/imodel-components-react/src/imodel-components-react/editors/ColorEditor.tsx
@@ -165,7 +165,7 @@ export class ColorEditor
disabled={this.state.isDisabled ? true : false}
readonly={this.state.readonly}
onColorPick={this._onColorPick}
- data-testid={this.props.itemId ?? "components-color-editor"}
+ data-testid="components-color-editor"
/>
);
diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx
index e8cb0bacbd7..72d50ac17be 100644
--- a/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx
+++ b/ui/imodel-components-react/src/imodel-components-react/editors/WeightEditor.tsx
@@ -160,7 +160,7 @@ export class WeightEditor
disabled={this.state.isDisabled ? true : false}
readonly={this.state.readonly}
onLineWeightPick={this._onLineWeightPick}
- data-testid={this.props.itemId ?? "components-weight-editor"}
+ data-testid="components-weight-editor"
/>
);