This is an automated email from the ASF dual-hosted git repository.
ljmotta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git
The following commit(s) were added to refs/heads/main by this push:
new 334d9ff6e9b kie-issues#2244: KIE Sandbox: DMN Runner Outputs table
stealing the focus from Inputs table (#3440)
334d9ff6e9b is described below
commit 334d9ff6e9b7f6bdc376f3ef588ab963eca81068
Author: Luiz João Motta <[email protected]>
AuthorDate: Wed Feb 18 06:18:20 2026 -0300
kie-issues#2244: KIE Sandbox: DMN Runner Outputs table stealing the focus
from Inputs table (#3440)
---
.../src/selection/BeeTableSelectionContext.tsx | 17 ++-
.../table/BeeTable/BeeTableEditableCellContent.tsx | 17 ++-
.../tests-e2e/api/expressionContainer.ts | 4 +
.../api/expressions/contextExpressionElement.ts | 8 ++
.../tests-e2e/features/keyboard/keyboard.spec.ts | 121 ++++++++++++++++++---
packages/unitables/src/Unitables.css | 4 +
packages/unitables/src/Unitables.tsx | 2 +-
packages/unitables/src/bee/UnitablesBeeTable.tsx | 10 ++
8 files changed, 167 insertions(+), 16 deletions(-)
diff --git
a/packages/boxed-expression-component/src/selection/BeeTableSelectionContext.tsx
b/packages/boxed-expression-component/src/selection/BeeTableSelectionContext.tsx
index 56744e90f23..1ebab272b2a 100644
---
a/packages/boxed-expression-component/src/selection/BeeTableSelectionContext.tsx
+++
b/packages/boxed-expression-component/src/selection/BeeTableSelectionContext.tsx
@@ -1105,7 +1105,22 @@ export function useBeeTableSelectableCell(
useLayoutEffect(() => {
if (isActive && !isEditing) {
- cellRef.current?.focus();
+ const cellElement = cellRef.current;
+ if (!cellElement) {
+ return;
+ }
+
+ // Find the boxed-expression-provider container (top-level container for
this component)
+ const activeElement = document.activeElement;
+ const boxedExpressionProvider =
cellElement.closest(".boxed-expression-provider");
+
+ // Don't steal focus if the active element is outside this
boxed-expression-provider.
+ // This prevents stealing focus from input fields in other
tables/components when the table re-renders.
+ if (activeElement && boxedExpressionProvider &&
!boxedExpressionProvider.contains(activeElement)) {
+ return;
+ }
+
+ cellElement.focus();
}
}, [columnIndex, isActive, isEditing, rowIndex, cellRef]);
diff --git
a/packages/boxed-expression-component/src/table/BeeTable/BeeTableEditableCellContent.tsx
b/packages/boxed-expression-component/src/table/BeeTable/BeeTableEditableCellContent.tsx
index db0f597d8d4..fe54bd1cfcc 100644
---
a/packages/boxed-expression-component/src/table/BeeTable/BeeTableEditableCellContent.tsx
+++
b/packages/boxed-expression-component/src/table/BeeTable/BeeTableEditableCellContent.tsx
@@ -174,7 +174,22 @@ export function BeeTableEditableCellContent({
const editableCellRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isActive && !isEditing) {
- editableCellRef.current?.focus();
+ const cellElement = editableCellRef.current;
+ if (!cellElement) {
+ return;
+ }
+
+ // Find the boxed-expression-provider container (top-level container for
this component)
+ const activeElement = document.activeElement;
+ const boxedExpressionProvider =
cellElement.closest(".boxed-expression-provider");
+
+ // Don't steal focus if the active element is outside this
boxed-expression-provider.
+ // This prevents stealing focus from input fields in other
tables/components when the table re-renders.
+ if (activeElement && boxedExpressionProvider &&
!boxedExpressionProvider.contains(activeElement)) {
+ return;
+ }
+
+ cellElement.focus();
}
}, [isActive, isEditing]);
diff --git
a/packages/boxed-expression-component/tests-e2e/api/expressionContainer.ts
b/packages/boxed-expression-component/tests-e2e/api/expressionContainer.ts
index 22121236cea..60256b5a158 100644
--- a/packages/boxed-expression-component/tests-e2e/api/expressionContainer.ts
+++ b/packages/boxed-expression-component/tests-e2e/api/expressionContainer.ts
@@ -116,6 +116,10 @@ export class ExpressionCell {
await this.content.click({ position: { x: 1, y: 1 } });
}
+ // public async isSelected() {
+ // return this.content.locator(("data-cell.active")).isVisible()
+ // }
+
public get content() {
return this.locator.nth(0);
}
diff --git
a/packages/boxed-expression-component/tests-e2e/api/expressions/contextExpressionElement.ts
b/packages/boxed-expression-component/tests-e2e/api/expressions/contextExpressionElement.ts
index 7e7b05f7e0a..351ae63f931 100644
---
a/packages/boxed-expression-component/tests-e2e/api/expressions/contextExpressionElement.ts
+++
b/packages/boxed-expression-component/tests-e2e/api/expressions/contextExpressionElement.ts
@@ -40,6 +40,10 @@ export class ContextExpressionElement {
return new
ChildExpression(this.locator.getByTestId(`kie-tools--bee--additional-row`).nth(0),
this.monaco);
}
+ get resultExpressionContainer() {
+ return this.result.elementCell.locator("..");
+ }
+
public async entriesCount() {
return (await this.locator.getByRole("row").count()) - 2;
}
@@ -98,6 +102,10 @@ export class ContextExpressionEntry {
return this.childExpression.expression;
}
+ get contextExpressionContainer() {
+ return this.childExpression.elementCell.locator("..").locator("..");
+ }
+
get selectExpressionMenu() {
return this.childExpression.selectExpressionMenu;
}
diff --git
a/packages/boxed-expression-component/tests-e2e/features/keyboard/keyboard.spec.ts
b/packages/boxed-expression-component/tests-e2e/features/keyboard/keyboard.spec.ts
index 7e5c8b8d9c1..aca62ca4391 100644
---
a/packages/boxed-expression-component/tests-e2e/features/keyboard/keyboard.spec.ts
+++
b/packages/boxed-expression-component/tests-e2e/features/keyboard/keyboard.spec.ts
@@ -17,25 +17,120 @@
* under the License.
*/
-import { test } from "../../__fixtures__/base";
+import { expect, test } from "../../__fixtures__/base";
+
+const ACTIVE_CLASS_REGEXP = /(^|\s)active(\s|$)/;
test.describe("Keyboard", () => {
- test.skip(true, "https://github.com/apache/incubator-kie-issues/issues/542");
test.describe("Navigation", () => {
- test("should correctly navigate", async () => {
- // enter, shift+enter, tab, shift+tab, escape
- });
+ test("should correctly navigate", async ({ bee, page, useCases }) => {
+ await useCases.openLoanOriginations("bureau-strategy-decision-service",
"bureau-call-type");
+ const decisionTable = bee.expression.asDecisionTable();
+ await decisionTable.cellAt({ row: 1, column: 1 }).select();
+
+ await page.keyboard.press("ArrowRight");
+ await expect(decisionTable.cellAt({ row: 1, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("ArrowDown");
+ await expect(decisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("ArrowUp");
+ await expect(decisionTable.cellAt({ row: 1, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("ArrowLeft");
+ await expect(decisionTable.cellAt({ row: 1, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.type(`"test"`);
+ await expect(decisionTable.cellAt({ row: 1, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Enter");
+ await expect(decisionTable.cellAt({ row: 1, column: 1
}).content).toContainText(`"test"`);
+ await expect(decisionTable.cellAt({ row: 2, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
- test.describe("Arrow keys", () => {
- test("should correctly navigate", async () => {
- // arrow up/down/left/right
- });
+ await page.keyboard.type(`"test2"`);
+ await expect(decisionTable.cellAt({ row: 2, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Escape");
+ await expect(decisionTable.cellAt({ row: 2, column: 1
}).content).not.toContainText(`"test2"`);
+ await expect(decisionTable.cellAt({ row: 2, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Tab");
+ await expect(decisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Tab");
+ await expect(decisionTable.cellAt({ row: 2, column: 3
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Shift+Tab");
+ await expect(decisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Escape");
+ await expect(decisionTable.cellAt({ row: 2, column: 2
}).content).not.toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Enter");
+ await expect(decisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
});
+ });
+
+ test.describe("Nested stories", () => {
+ test("should correctly navigate", async ({ bee, page, useCases }) => {
+ await useCases.openLoanOriginations("bureau-strategy-decision-service",
"pre-bureau-risk-category");
+ const contextExpression = bee.expression.asContext();
+ const literalExpression =
contextExpression.entry(0).expression.asLiteral();
+ const resultDecisionTable =
contextExpression.result.expression.asDecisionTable();
+ await resultDecisionTable.cellAt({ row: 1, column: 1 }).select();
+
+ // Check nested decision table
+ await page.keyboard.press("ArrowRight");
+ await expect(resultDecisionTable.cellAt({ row: 1, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("ArrowDown");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("ArrowUp");
+ await expect(resultDecisionTable.cellAt({ row: 1, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("ArrowLeft");
+ await expect(resultDecisionTable.cellAt({ row: 1, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.type(`"test"`);
+ await expect(resultDecisionTable.cellAt({ row: 1, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Enter");
+ await expect(resultDecisionTable.cellAt({ row: 1, column: 1
}).content).toContainText(`"test"`);
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.type(`"test2"`);
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Escape");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 1
}).content).not.toContainText(`"test2"`);
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 1
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Tab");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Tab");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 3
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Shift+Tab");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Escape");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 2
}).content).not.toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ await page.keyboard.press("Enter");
+ await expect(resultDecisionTable.cellAt({ row: 2, column: 2
}).content).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ // Selecting context expression result cell (decision table container)
+ await page.keyboard.press("Escape");
+ await
expect(contextExpression.resultExpressionContainer).toHaveClass(ACTIVE_CLASS_REGEXP);
+
+ // Selecting context expression entry 0 cell (literal expression
container)
+ await page.keyboard.press("ArrowUp");
+ await
expect(contextExpression.entry(0).contextExpressionContainer).toHaveClass(ACTIVE_CLASS_REGEXP);
- test.describe("Nested stories", () => {
- test("should correctly navigate", async () => {
- // enter, shift+enter, tab, shift+tab, escape
- });
+ await page.keyboard.press("Enter");
+ await expect(literalExpression.content).toHaveClass(ACTIVE_CLASS_REGEXP);
});
});
});
diff --git a/packages/unitables/src/Unitables.css
b/packages/unitables/src/Unitables.css
index 39d683b4bbb..aa143ed04a0 100644
--- a/packages/unitables/src/Unitables.css
+++ b/packages/unitables/src/Unitables.css
@@ -17,6 +17,10 @@
* under the License.
*/
+.unitables-container {
+ display: flex;
+}
+
.standalone-bee-table .pf-v5-c-select .pf-v5-c-select__toggle::before {
border-style: hidden;
}
diff --git a/packages/unitables/src/Unitables.tsx
b/packages/unitables/src/Unitables.tsx
index e6fdc3ad82b..d9c12d7d05b 100644
--- a/packages/unitables/src/Unitables.tsx
+++ b/packages/unitables/src/Unitables.tsx
@@ -240,7 +240,7 @@ export const Unitables = ({
))}
</div>
{unitablesColumns.length > 0 && rows.length > 0 && formsDivRendered ? (
- <div style={{ display: "flex" }} ref={containerRef}>
+ <div className="unitables-container" ref={containerRef}>
<UnitablesBeeTable
rowWrapper={rowWrapper}
scrollableParentRef={scrollableParentRef}
diff --git a/packages/unitables/src/bee/UnitablesBeeTable.tsx
b/packages/unitables/src/bee/UnitablesBeeTable.tsx
index 4f1bf7835b4..bb546829af5 100644
--- a/packages/unitables/src/bee/UnitablesBeeTable.tsx
+++ b/packages/unitables/src/bee/UnitablesBeeTable.tsx
@@ -692,6 +692,16 @@ function UnitablesBeeTableCell({
return;
}
+ // Check if focus is outside the unitables container to prevent stealing
focus from external inputs
+ const activeElement = document.activeElement;
+ const cellElement = cellRef.current;
+ if (cellElement) {
+ const unitablesContainer = cellElement.closest(".unitables-container");
+ if (activeElement && unitablesContainer &&
!unitablesContainer.contains(activeElement)) {
+ return;
+ }
+ }
+
if (fieldCharacteristics?.isList) {
if (isSelectFieldOpen) {
setTimeout(() => {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]