This is an automated email from the ASF dual-hosted git repository.
tiagobento 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 236931cf7a3 kie-issues#237: Implement DMN 1.4 Boxed iterator
expression (#2243)
236931cf7a3 is described below
commit 236931cf7a3299ac81d1fd84c7228cb1033496ae
Author: Daniel José dos Santos <[email protected]>
AuthorDate: Wed Apr 17 15:10:57 2024 -0300
kie-issues#237: Implement DMN 1.4 Boxed iterator expression (#2243)
Co-authored-by: Tiago Bento <[email protected]>
---
.../src/api/BoxedExpression.ts | 2 +
.../ExpressionDefinitionLogicTypeSelector.tsx | 37 ++-
.../IteratorExpression/IteratorExpressionCell.tsx | 80 +++++
.../IteratorExpressionComponent.tsx | 341 +++++++++++++++++++++
.../resizing/BeeTableResizableColumnsContext.tsx | 113 +++----
.../src/resizing/WidthConstants.ts | 4 +
.../src/resizing/WidthMaths.ts | 31 ++
.../src/table/BeeTable/BeeTable.tsx | 79 +++--
.../src/table/BeeTable/BeeTableHeader.tsx | 8 +-
.../src/table/BeeTable/InlineEditableTextInput.tsx | 31 +-
10 files changed, 632 insertions(+), 94 deletions(-)
diff --git a/packages/boxed-expression-component/src/api/BoxedExpression.ts
b/packages/boxed-expression-component/src/api/BoxedExpression.ts
index ae5192d5c35..f9e0239d4c6 100644
--- a/packages/boxed-expression-component/src/api/BoxedExpression.ts
+++ b/packages/boxed-expression-component/src/api/BoxedExpression.ts
@@ -51,6 +51,8 @@ export enum BoxedFunctionKind {
Pmml = "PMML",
}
+export type BoxedIterator = BoxedFor | BoxedEvery | BoxedSome;
+
export type BoxedExpression =
| BoxedLiteral
| BoxedRelation
diff --git
a/packages/boxed-expression-component/src/expressions/ExpressionDefinitionRoot/ExpressionDefinitionLogicTypeSelector.tsx
b/packages/boxed-expression-component/src/expressions/ExpressionDefinitionRoot/ExpressionDefinitionLogicTypeSelector.tsx
index 36911234637..c997c59b639 100644
---
a/packages/boxed-expression-component/src/expressions/ExpressionDefinitionRoot/ExpressionDefinitionLogicTypeSelector.tsx
+++
b/packages/boxed-expression-component/src/expressions/ExpressionDefinitionRoot/ExpressionDefinitionLogicTypeSelector.tsx
@@ -29,6 +29,9 @@ import { CutIcon } from
"@patternfly/react-icons/dist/js/icons/cut-icon";
import { ListIcon } from "@patternfly/react-icons/dist/js/icons/list-icon";
import { PasteIcon } from "@patternfly/react-icons/dist/js/icons/paste-icon";
import { TableIcon } from "@patternfly/react-icons/dist/js/icons/table-icon";
+import { RebootingIcon } from
"@patternfly/react-icons/dist/js/icons/rebooting-icon";
+import { ResourcesAlmostEmptyIcon } from
"@patternfly/react-icons/dist/js/icons/resources-almost-empty-icon";
+import { ResourcesFullIcon } from
"@patternfly/react-icons/dist/js/icons/resources-full-icon";
import * as React from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { BoxedExpression } from "../../api";
@@ -47,13 +50,14 @@ import { LiteralExpression } from
"../LiteralExpression/LiteralExpression";
import { RelationExpression } from "../RelationExpression/RelationExpression";
import {
BoxedExpressionClipboard,
- DMN_BOXED_EXPRESSION_CLIPBOARD_MIME_TYPE,
buildClipboardFromExpression,
+ DMN_BOXED_EXPRESSION_CLIPBOARD_MIME_TYPE,
} from "../../clipboard/clipboard";
import { findAllIdsDeep, mutateExpressionRandomizingIds } from "../../ids/ids";
import "./ExpressionDefinitionLogicTypeSelector.css";
import { NavigationKeysUtils } from "../../keysUtils/keyUtils";
import { ConditionalExpression } from
"../ConditionalExpression/ConditionalExpression";
+import { IteratorExpressionComponent } from
"../IteratorExpression/IteratorExpressionComponent";
export interface ExpressionDefinitionLogicTypeSelectorProps {
/** Expression properties */
@@ -95,9 +99,9 @@ export function ExpressionDefinitionLogicTypeSelector({
"invocation",
...(isNested ? (["functionDefinition"] as const) : []),
...(!hideDmn14BoxedExpressions ? (["conditional"] as const) : []),
- // "for",
- // "every",
- // "some",
+ "for",
+ "every",
+ "some",
// "filter",
],
[hideDmn14BoxedExpressions, isNested]
@@ -134,6 +138,9 @@ export function ExpressionDefinitionLogicTypeSelector({
case "for":
case "every":
case "some":
+ return (
+ <IteratorExpressionComponent expression={expression}
isNested={isNested} parentElementId={parentElementId} />
+ );
case "filter":
return <></>;
default:
@@ -235,8 +242,11 @@ export function ExpressionDefinitionLogicTypeSelector({
</span>
);
case "for":
+ return <RebootingIcon />;
case "every":
+ return <ResourcesFullIcon />;
case "some":
+ return <ResourcesAlmostEmptyIcon />;
case "filter":
return <></>;
default:
@@ -330,6 +340,25 @@ export function ExpressionDefinitionLogicTypeSelector({
return "A boxed list expression in DMN represents a FEEL list of
items. You use boxed lists to define lists of relevant items for a particular
node in a decision.";
case "conditional":
return 'A boxed conditional offers a visual representation of an if
statement using three rows. The expression in the "if" part MUST resolve to a
boolean.';
+ case "for":
+ return (
+ "A boxed iterator offers a visual representation of an iterator
statement. " +
+ 'For the for loop, the right part of the "for" displays the iterator
variable name. The second row holds an expression representing the collection
that will be iterated over. The expression in the "in" row MUST resolve to a
collection.' +
+ " The last row contains the expression that will process each
element of the collection."
+ );
+
+ case "every":
+ return (
+ "A boxed iterator offers a visual representation of an iterator
statement. " +
+ 'For the "every" loop, the right part of the "every" displays the
iterator variable name. The second row holds an expression representing the
collection that will be iterated over. The expression in the "in" row MUST
resolve to a collection.' +
+ "The last line is an expression that will be evaluated on each item.
The expression defined in the satisfies MUST resolve to a boolean."
+ );
+ case "some":
+ return (
+ "A boxed iterator offers a visual representation of an iterator
statement. " +
+ 'For the "some" loop, the right part of the "some" displays the
iterator variable name. The second row holds an expression representing the
collection that will be iterated over. The expression in the "in" row MUST
resolve to a collection. ' +
+ "The last line is an expression that will be evaluated on each item.
The expression defined in the satisfies MUST resolve to a boolean."
+ );
default:
return "";
}
diff --git
a/packages/boxed-expression-component/src/expressions/IteratorExpression/IteratorExpressionCell.tsx
b/packages/boxed-expression-component/src/expressions/IteratorExpression/IteratorExpressionCell.tsx
new file mode 100644
index 00000000000..99c0c7e5ab6
--- /dev/null
+++
b/packages/boxed-expression-component/src/expressions/IteratorExpression/IteratorExpressionCell.tsx
@@ -0,0 +1,80 @@
+import { BoxedIterator } from "../../api";
+
+import {
+ NestedExpressionDispatchContextProvider,
+ useBoxedExpressionEditorDispatch,
+} from "../../BoxedExpressionEditorContext";
+import * as React from "react";
+import { useCallback, useMemo } from "react";
+import { ExpressionContainer } from
"../ExpressionDefinitionRoot/ExpressionContainer";
+import { IteratorClause } from "./IteratorExpressionComponent";
+
+export interface IteratorExpressionCellExpressionCellProps {
+ iteratorClause: IteratorClause;
+ rowIndex: number;
+ columnIndex: number;
+ columnId: string;
+}
+
+export function IteratorExpressionCell({
+ rowIndex,
+ columnIndex,
+ parentElementId,
+ iteratorClause,
+}: IteratorExpressionCellExpressionCellProps & { parentElementId: string }) {
+ const { setExpression } = useBoxedExpressionEditorDispatch();
+
+ const onSetExpression = useCallback(
+ ({ getNewExpression }) => {
+ setExpression((prev: BoxedIterator) => {
+ switch (rowIndex) {
+ case 1:
+ return {
+ ...prev,
+ in: {
+ expression: getNewExpression(prev.in.expression),
+ },
+ };
+ case 2:
+ default:
+ if (prev.__$$element === "for") {
+ return {
+ ...prev,
+ return: {
+ expression: getNewExpression(prev.return.expression),
+ },
+ };
+ } else {
+ return {
+ ...prev,
+ satisfies: {
+ expression: getNewExpression(prev.satisfies.expression),
+ },
+ };
+ }
+ }
+ });
+ },
+ [rowIndex, setExpression]
+ );
+
+ const currentExpression = useMemo(() => {
+ if (typeof iteratorClause.child !== "string") {
+ return iteratorClause.child?.expression;
+ }
+ }, [iteratorClause.child]);
+
+ return (
+ <NestedExpressionDispatchContextProvider onSetExpression={onSetExpression}>
+ <ExpressionContainer
+ expression={currentExpression}
+ isResetSupported={true}
+ isNested={true}
+ rowIndex={rowIndex}
+ columnIndex={columnIndex}
+ parentElementId={parentElementId}
+ parentElementTypeRef={undefined}
+ />
+ </NestedExpressionDispatchContextProvider>
+ );
+}
diff --git
a/packages/boxed-expression-component/src/expressions/IteratorExpression/IteratorExpressionComponent.tsx
b/packages/boxed-expression-component/src/expressions/IteratorExpression/IteratorExpressionComponent.tsx
new file mode 100644
index 00000000000..86d118fe2a6
--- /dev/null
+++
b/packages/boxed-expression-component/src/expressions/IteratorExpression/IteratorExpressionComponent.tsx
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ BeeTableHeaderVisibility,
+ BeeTableOperation,
+ BeeTableOperationConfig,
+ BeeTableProps,
+ BoxedFor,
+ BoxedIterator,
+ DmnBuiltInDataType,
+} from "../../api";
+import { BeeTable, BeeTableColumnUpdate, BeeTableRef } from
"../../table/BeeTable";
+import { ResizerStopBehavior } from "../../resizing/ResizingWidthsContext";
+import React, { useCallback, useMemo, useRef } from "react";
+import {
+ DMN15__tChildExpression,
+ DMN15__tTypedChildExpression,
+} from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
+import * as ReactTable from "react-table";
+import { useBoxedExpressionEditorI18n } from "../../i18n";
+import { BeeTableReadOnlyCell } from
"../../table/BeeTable/BeeTableReadOnlyCell";
+import { IteratorExpressionCell } from "./IteratorExpressionCell";
+
+import { useBoxedExpressionEditor, useBoxedExpressionEditorDispatch } from
"../../BoxedExpressionEditorContext";
+import { NestedExpressionContainerContext } from
"../../resizing/NestedExpressionContainerContext";
+import { useNestedExpressionContainerWithNestedExpressions } from
"../../resizing/Hooks";
+import {
+ ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH,
+ ITERATOR_EXPRESSION_EXTRA_WIDTH,
+ ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH,
+} from "../../resizing/WidthConstants";
+import { DEFAULT_EXPRESSION_VARIABLE_NAME } from
"../../expressionVariable/ExpressionVariableMenu";
+import { InlineEditableTextInput } from
"../../table/BeeTable/InlineEditableTextInput";
+
+type ROWTYPE = IteratorClause;
+
+export type IteratorClause = {
+ child: DMN15__tTypedChildExpression | DMN15__tChildExpression | string |
undefined;
+ label: string;
+};
+
+export function IteratorExpressionComponent({
+ isNested,
+ parentElementId,
+ expression: expression,
+}: {
+ expression: BoxedIterator;
+ isNested: boolean;
+ parentElementId: string;
+}) {
+ const { i18n } = useBoxedExpressionEditorI18n();
+ const { expressionHolderId, widthsById } = useBoxedExpressionEditor();
+ const { setExpression } = useBoxedExpressionEditorDispatch();
+
+ const id = expression["@_id"]!;
+
+ const tableColumns = useMemo<ReactTable.Column<ROWTYPE>[]>(() => {
+ return [
+ {
+ accessor: expressionHolderId as any, // FIXME:
https://github.com/kiegroup/kie-issues/issues/169
+ label: expression["@_label"] ?? DEFAULT_EXPRESSION_VARIABLE_NAME,
+ isRowIndexColumn: false,
+ dataType: expression["@_typeRef"] ?? DmnBuiltInDataType.Undefined,
+ width: undefined,
+ columns: [
+ {
+ accessor: "label",
+ label: "label",
+ width: ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH,
+ minWidth: ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH,
+ isInlineEditable: false,
+ isRowIndexColumn: false,
+ isWidthPinned: true,
+ isWidthConstant: true,
+ dataType: undefined as any,
+ },
+ {
+ accessor: "child",
+ label: "child",
+ width: undefined,
+ minWidth: ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH,
+ isInlineEditable: false,
+ isRowIndexColumn: false,
+ dataType: undefined as any,
+ },
+ ],
+ },
+ ];
+ }, [expression, expressionHolderId]);
+
+ const headerVisibility = useMemo(() => {
+ return isNested ? BeeTableHeaderVisibility.None :
BeeTableHeaderVisibility.SecondToLastLevel;
+ }, [isNested]);
+
+ const beeTableOperationConfig = useMemo<BeeTableOperationConfig>(() => {
+ return [
+ {
+ group: i18n.contextEntry,
+ items: [{ name: i18n.rowOperations.reset, type:
BeeTableOperation.RowReset }],
+ },
+ {
+ group: i18n.terms.selection.toUpperCase(),
+ items: [{ name: i18n.terms.copy, type: BeeTableOperation.SelectionCopy
}],
+ },
+ ];
+ }, [i18n]);
+
+ const getIterableRowLabel = useCallback(
+ (rowNumber: number) => {
+ if (rowNumber === 0) {
+ if (expression.__$$element === "for") {
+ return "for";
+ } else if (expression.__$$element === "some") {
+ return "some";
+ } else if (expression.__$$element === "every") {
+ return "every";
+ } else {
+ throw new Error("Unknown IteratorExpression element");
+ }
+ } else if (rowNumber === 1) {
+ return "in";
+ } else if (rowNumber === 2) {
+ if (expression.__$$element === "for") {
+ return "return";
+ } else if (expression.__$$element === "some" || expression.__$$element
=== "every") {
+ return "satisfies";
+ } else {
+ throw new Error("Unknown IteratorExpression element");
+ }
+ } else {
+ throw new Error("IteratorExpression can't have more than 3 rows.");
+ }
+ },
+ [expression.__$$element]
+ );
+
+ const getIterableRowElement = useCallback(
+ (rowNumber: number) => {
+ if (rowNumber === 0) {
+ return expression["@_iteratorVariable"] ?? "";
+ } else if (rowNumber === 1) {
+ return expression.in;
+ } else {
+ switch (expression.__$$element) {
+ case "for":
+ return expression.return;
+ case "every":
+ case "some":
+ return expression.satisfies;
+ }
+ }
+ },
+ [expression]
+ );
+
+ const tableRows = useMemo(() => {
+ return [
+ { label: getIterableRowLabel(0), child: getIterableRowElement(0) },
+ { label: getIterableRowLabel(1), child: getIterableRowElement(1) },
+ { label: getIterableRowLabel(2), child: getIterableRowElement(2) },
+ ];
+ }, [getIterableRowElement, getIterableRowLabel]);
+
+ const allowedOperations = useCallback(() => {
+ return [BeeTableOperation.SelectionCopy, BeeTableOperation.RowReset];
+ }, []);
+
+ const beeTableRef = useRef<BeeTableRef>(null);
+
+ const cellComponentByColumnAccessor:
BeeTableProps<ROWTYPE>["cellComponentByColumnAccessor"] = useMemo(() => {
+ return {
+ label: (props) => {
+ return <BeeTableReadOnlyCell value={props.data[props.rowIndex].label}
/>;
+ },
+ child: (props) => {
+ if (props.rowIndex === 0) {
+ return (
+ <div
+ style={{
+ minHeight: "60px",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ }}
+ >
+ <InlineEditableTextInput
+ {...props}
+ value={props.data[props.rowIndex].child as string}
+ onChange={(updatedValue) => {
+ setExpression((prev: BoxedIterator) => {
+ // Do not inline this variable for type safety. See
https://github.com/microsoft/TypeScript/issues/241
+ const ret: BoxedIterator = {
+ ...prev,
+ "@_iteratorVariable": updatedValue,
+ };
+ return ret;
+ });
+ }}
+ setActiveCellEditing={(value) => {
+ beeTableRef.current?.setActiveCellEditing(value);
+ }}
+ />
+ </div>
+ );
+ } else if (props.rowIndex === 2 || props.rowIndex === 3) {
+ return (
+ <IteratorExpressionCell
+ iteratorClause={props.data[props.rowIndex]}
+ {...props}
+ parentElementId={parentElementId}
+ />
+ );
+ } else {
+ throw new Error("IteratorExpression can't have more than 3 rows.");
+ }
+ },
+ };
+ }, [parentElementId, setExpression]);
+
+ const { nestedExpressionContainerValue, onColumnResizingWidthChange } =
+ useNestedExpressionContainerWithNestedExpressions(
+ useMemo(() => {
+ const nestedExpressions = [
+ expression.in.expression,
+ expression.__$$element === "for" ? expression.return.expression :
expression.satisfies.expression,
+ ];
+
+ return {
+ nestedExpressions: nestedExpressions,
+ fixedColumnActualWidth: ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH,
+ fixedColumnResizingWidth: { value:
ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH, isPivoting: false },
+ fixedColumnMinWidth: ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH,
+ nestedExpressionMinWidth:
ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH,
+ extraWidth: ITERATOR_EXPRESSION_EXTRA_WIDTH,
+ expression: expression,
+ flexibleColumnIndex: 2,
+ widthsById: widthsById,
+ };
+ }, [expression, widthsById])
+ );
+
+ const onColumnUpdates = useCallback(
+ ([{ name, typeRef }]: BeeTableColumnUpdate<ROWTYPE>[]) => {
+ setExpression((prev: BoxedIterator) => {
+ // Do not inline this variable for type safety. See
https://github.com/microsoft/TypeScript/issues/241
+ const ret: BoxedIterator = {
+ ...prev,
+ "@_label": name,
+ "@_typeRef": typeRef,
+ };
+
+ return ret;
+ });
+ },
+ [setExpression]
+ );
+ const onRowReset = useCallback(
+ (args: { rowIndex: number }) => {
+ setExpression((prev: BoxedIterator) => {
+ if (args.rowIndex === 0) {
+ // Do not inline this variable for type safety. See
https://github.com/microsoft/TypeScript/issues/241
+ const ret: BoxedIterator = {
+ ...prev,
+ "@_iteratorVariable": undefined,
+ };
+ return ret;
+ } else if (args.rowIndex === 1) {
+ // Do not inline this variable for type safety. See
https://github.com/microsoft/TypeScript/issues/241
+ const ret: BoxedIterator = {
+ ...prev,
+ in: { expression: undefined! }, // SPEC DISCREPANCY
+ };
+ return ret;
+ } else if (args.rowIndex === 2) {
+ if (prev.__$$element === "for") {
+ // Do not inline this variable for type safety. See
https://github.com/microsoft/TypeScript/issues/241
+ const ret: BoxedFor = {
+ ...prev,
+ return: { expression: undefined! }, // SPEC DISCREPANCY
+ };
+ return ret;
+ } else if (prev.__$$element === "some" || prev.__$$element ===
"every") {
+ // Do not inline this variable for type safety. See
https://github.com/microsoft/TypeScript/issues/241
+ const iterator: BoxedIterator = {
+ ...prev,
+ satisfies: { expression: undefined! }, // SPEC DISCREPANCY
+ };
+ return iterator;
+ } else {
+ throw new Error("Nested expression type not supported in
IteratorExpression.");
+ }
+ } else {
+ throw new Error("IteratorExpression shouldn't have more than 3
rows.");
+ }
+ });
+ },
+ [setExpression]
+ );
+
+ return (
+ <NestedExpressionContainerContext.Provider
value={nestedExpressionContainerValue}>
+ <div>
+ <BeeTable<ROWTYPE>
+ forwardRef={beeTableRef}
+ resizerStopBehavior={ResizerStopBehavior.SET_WIDTH_WHEN_SMALLER}
+ tableId={id}
+ headerLevelCountForAppendingRowIndexColumn={1}
+ headerVisibility={headerVisibility}
+ cellComponentByColumnAccessor={cellComponentByColumnAccessor}
+ columns={tableColumns}
+ rows={tableRows}
+ operationConfig={beeTableOperationConfig}
+ allowedOperations={allowedOperations}
+ onColumnUpdates={onColumnUpdates}
+ onRowReset={onRowReset}
+ onColumnResizingWidthChange={onColumnResizingWidthChange}
+ shouldRenderRowIndexColumn={false}
+ shouldShowRowsInlineControls={false}
+ shouldShowColumnsInlineControls={false}
+ />
+ </div>
+ </NestedExpressionContainerContext.Provider>
+ );
+}
diff --git
a/packages/boxed-expression-component/src/resizing/BeeTableResizableColumnsContext.tsx
b/packages/boxed-expression-component/src/resizing/BeeTableResizableColumnsContext.tsx
index 5f9b13d9ae5..c13f5e14dc6 100644
---
a/packages/boxed-expression-component/src/resizing/BeeTableResizableColumnsContext.tsx
+++
b/packages/boxed-expression-component/src/resizing/BeeTableResizableColumnsContext.tsx
@@ -18,7 +18,6 @@
*/
import * as React from "react";
-import * as ReactTable from "react-table";
import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from
"react";
import { ResizerStopBehavior, ResizingWidth, useResizerRef,
useResizingWidthsDispatch } from "./ResizingWidthsContext";
import { BEE_TABLE_ROW_INDEX_COLUMN_WIDTH } from "./WidthConstants";
@@ -31,7 +30,9 @@ export type BeeTableResizableColumnsContextType = {
export interface BeeTableResizableColumnsDispatchContextType {
updateColumnResizingWidths(newColumnResizingWidths: Map<number,
ResizingWidth | undefined>): void;
+
registerResizableCellRef(columnIndex: number, ref:
BeeTableResizableCellRef): BeeTableResizableCellRef;
+
deregisterResizableCellRef(columnIndex: number, ref:
BeeTableResizableCellRef): void;
}
@@ -46,69 +47,69 @@ export interface BeeTableResizableCellRef {
// PROVIDER
-type Props = React.PropsWithChildren<{
+export type BeeTableResizingRef = BeeTableResizableColumnsDispatchContextType;
+
+export const BeeTableResizableColumnsContextProvider = ({
+ children,
+ onChange,
+ resizingRef,
+}: React.PropsWithChildren<{
onChange?: (args: Map<number, ResizingWidth | undefined>) => void;
- columns: ReactTable.Column<any>[];
-}>;
+ resizingRef: React.RefObject<BeeTableResizingRef>;
+}>) => {
+ const refs = React.useRef<Map<number, Set<BeeTableResizableCellRef>>>(new
Map());
-type MyRef = BeeTableResizableColumnsDispatchContextType;
+ const [columnResizingWidths, setColumnResizingWidths] = useState<Map<number,
ResizingWidth>>(new Map());
-export const BeeTableResizableColumnsContextProvider = React.forwardRef<MyRef,
Props>(
- ({ children, onChange, columns }, forwardRef) => {
- const refs = React.useRef<Map<number, Set<BeeTableResizableCellRef>>>(new
Map());
+ const onColumnResizingWidthChange = useCallback((args: Map<number,
ResizingWidth | undefined>) => {
+ setColumnResizingWidths((prev) => {
+ const n = new Map(prev);
+ for (const [columnIndex, newResizingWidth] of args.entries()) {
+ if (newResizingWidth) {
+ n.set(columnIndex, newResizingWidth);
+ }
+ }
+ return n;
+ });
+ }, []);
- const [columnResizingWidths, setColumnResizingWidths] =
useState<Map<number, ResizingWidth>>(new Map());
+ const value = useMemo(() => {
+ return { columnResizingWidths };
+ }, [columnResizingWidths]);
- const onColumnResizingWidthChange = useCallback((args: Map<number,
ResizingWidth | undefined>) => {
- setColumnResizingWidths((prev) => {
- const n = new Map(prev);
- for (const [columnIndex, newResizingWidth] of args.entries()) {
- if (newResizingWidth) {
- n.set(columnIndex, newResizingWidth);
+ const dispatch = useMemo<BeeTableResizableColumnsDispatchContextType>(() => {
+ return {
+ updateColumnResizingWidths: (newColumnResizingWidths) => {
+ for (const [columnIndex, newResizingWidth] of
newColumnResizingWidths.entries()) {
+ for (const ref of refs.current.get(columnIndex) ?? []) {
+ ref.setResizingWidth?.(newResizingWidth);
}
}
- return n;
- });
- }, []);
-
- const value = useMemo(() => {
- return { columnResizingWidths };
- }, [columnResizingWidths]);
-
- const dispatch = useMemo<BeeTableResizableColumnsDispatchContextType>(()
=> {
- return {
- updateColumnResizingWidths: (newColumnResizingWidths) => {
- for (const [columnIndex, newResizingWidth] of
newColumnResizingWidths.entries()) {
- for (const ref of refs.current.get(columnIndex) ?? []) {
- ref.setResizingWidth?.(newResizingWidth);
- }
- }
- onColumnResizingWidthChange(newColumnResizingWidths);
- onChange?.(newColumnResizingWidths);
- },
- registerResizableCellRef: (columnIndex, ref) => {
- const prev = refs.current?.get(columnIndex) ?? new Set();
- refs.current?.set(columnIndex, new Set([...prev, ref]));
- return ref;
- },
- deregisterResizableCellRef: (columnIndex, ref) => {
- refs.current?.get(columnIndex)?.delete(ref);
- },
- };
- }, [onChange, onColumnResizingWidthChange]);
-
- useImperativeHandle(forwardRef, () => dispatch, [dispatch]);
-
- return (
- <BeeTableResizableColumnsContext.Provider value={value}>
- <BeeTableResizableColumnsDispatchContext.Provider value={dispatch}>
- <>{children}</>
- </BeeTableResizableColumnsDispatchContext.Provider>
- </BeeTableResizableColumnsContext.Provider>
- );
- }
-);
+ onColumnResizingWidthChange(newColumnResizingWidths);
+ onChange?.(newColumnResizingWidths);
+ },
+ registerResizableCellRef: (columnIndex, ref) => {
+ const prev = refs.current?.get(columnIndex) ?? new Set();
+ refs.current?.set(columnIndex, new Set([...prev, ref]));
+ return ref;
+ },
+ deregisterResizableCellRef: (columnIndex, ref) => {
+ refs.current?.get(columnIndex)?.delete(ref);
+ },
+ };
+ }, [onChange, onColumnResizingWidthChange]);
+
+ useImperativeHandle(resizingRef, () => dispatch, [dispatch]);
+
+ return (
+ <BeeTableResizableColumnsContext.Provider value={value}>
+ <BeeTableResizableColumnsDispatchContext.Provider value={dispatch}>
+ <>{children}</>
+ </BeeTableResizableColumnsDispatchContext.Provider>
+ </BeeTableResizableColumnsContext.Provider>
+ );
+};
export function useBeeTableResizableColumnsDispatch() {
return React.useContext(BeeTableResizableColumnsDispatchContext);
diff --git a/packages/boxed-expression-component/src/resizing/WidthConstants.ts
b/packages/boxed-expression-component/src/resizing/WidthConstants.ts
index 0e307d20eef..06d98a78d26 100644
--- a/packages/boxed-expression-component/src/resizing/WidthConstants.ts
+++ b/packages/boxed-expression-component/src/resizing/WidthConstants.ts
@@ -71,3 +71,7 @@ export const JAVA_FUNCTION_EXPRESSION_EXTRA_WIDTH =
BEE_TABLE_ROW_INDEX_COLUMN_W
export const CONDITIONAL_EXPRESSION_LABEL_COLUMN_WIDTH = 80;
export const CONDITIONAL_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH = 210;
export const CONDITIONAL_EXPRESSION_EXTRA_WIDTH = 2; // 2px for borders of
context entry expression // It's a mistery why to this cell is counting the
borders.
+
+export const ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH = 80;
+export const ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH = 210;
+export const ITERATOR_EXPRESSION_EXTRA_WIDTH = 2; // 2px for borders of
context entry expression // It's a mistery why to this cell is counting the
borders.
diff --git a/packages/boxed-expression-component/src/resizing/WidthMaths.ts
b/packages/boxed-expression-component/src/resizing/WidthMaths.ts
index ba4430b3d34..f18fbe11080 100644
--- a/packages/boxed-expression-component/src/resizing/WidthMaths.ts
+++ b/packages/boxed-expression-component/src/resizing/WidthMaths.ts
@@ -51,6 +51,9 @@ import {
PMML_FUNCTION_EXPRESSION_LABEL_MIN_WIDTH,
PMML_FUNCTION_EXPRESSION_VALUES_MIN_WIDTH,
RELATION_EXPRESSION_COLUMN_MIN_WIDTH,
+ ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH,
+ ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH,
+ ITERATOR_EXPRESSION_EXTRA_WIDTH,
} from "./WidthConstants";
export function getExpressionMinWidth(expression?: BoxedExpression): number {
@@ -328,6 +331,34 @@ export function getExpressionResizingWidth(
);
}
+ // For
+ else if (expression.__$$element === "for") {
+ const nestedExpressions = [expression.in.expression,
expression.return.expression];
+ return (
+ resizingWidth ??
+ ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH +
+ Math.max(
+ ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH,
+ ...nestedExpressions.map((e) => getExpressionResizingWidth(e,
resizingWidths, widthsById))
+ ) +
+ ITERATOR_EXPRESSION_EXTRA_WIDTH
+ );
+ }
+
+ // Every and Some
+ else if (expression.__$$element === "every" || expression.__$$element ===
"some") {
+ const nestedExpressions = [expression.in.expression,
expression.satisfies.expression];
+ return (
+ resizingWidth ??
+ ITERATOR_EXPRESSION_LABEL_COLUMN_WIDTH +
+ Math.max(
+ ITERATOR_EXPRESSION_CLAUSE_COLUMN_MIN_WIDTH,
+ ...nestedExpressions.map((e) => getExpressionResizingWidth(e,
resizingWidths, widthsById))
+ ) +
+ ITERATOR_EXPRESSION_EXTRA_WIDTH
+ );
+ }
+
// Others
else {
throw new Error(`Can't determine resizing width for expression of unknown
type '${expression.__$$element}'`);
diff --git
a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx
b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx
index b3fe39be607..44dc827e1cd 100644
--- a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx
+++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx
@@ -19,7 +19,7 @@
import _ from "lodash";
import * as React from "react";
-import { useCallback, useMemo, useRef } from "react";
+import { useCallback, useImperativeHandle, useMemo, useRef } from "react";
import * as ReactTable from "react-table";
import { BeeTableHeaderVisibility, BeeTableProps, InsertRowColumnsDirection }
from "../../api";
import { useBoxedExpressionEditor } from "../../BoxedExpressionEditorContext";
@@ -29,7 +29,7 @@ import { BEE_TABLE_ROW_INDEX_COLUMN_WIDTH } from
"../../resizing/WidthConstants"
import { BeeTableBody } from "./BeeTableBody";
import {
BeeTableResizableColumnsContextProvider,
- BeeTableResizableColumnsDispatchContextType,
+ BeeTableResizingRef,
} from "../../resizing/BeeTableResizableColumnsContext";
import { BeeTableContextMenuHandler } from "./BeeTableContextMenuHandler";
import { BeeTableDefaultCell } from "./BeeTableDefaultCell";
@@ -62,7 +62,12 @@ export function getColumnsAtLastLevel<R extends
ReactTable.Column<any> | ReactTa
});
}
+export type BeeTableSelectionRef = {
+ setActiveCellEditing: (isEditing: boolean) => void;
+};
+
export function BeeTableInternal<R extends object>({
+ selectionRef,
tableId,
additionalRow,
editColumnLabel,
@@ -98,7 +103,9 @@ export function BeeTableInternal<R extends object>({
resizerStopBehavior,
lastColumnMinWidth,
rowWrapper,
-}: BeeTableProps<R>) {
+}: BeeTableProps<R> & {
+ selectionRef?: React.RefObject<BeeTableSelectionRef>;
+}) {
const { resetSelectionAt, erase, copy, cut, paste, adaptSelection,
mutateSelection, setCurrentDepth } =
useBeeTableSelectionDispatch();
const tableComposableRef = useRef<HTMLTableElement>(null);
@@ -172,7 +179,7 @@ export function BeeTableInternal<R extends object>({
[hasAdditionalRow]
);
- const _setEditing = useCallback(
+ const _setActiveCellEditing = useCallback(
(rowCount: number, columnCount: (rowIndex: number) => number) =>
(isEditing: boolean) => {
mutateSelection({
part: SelectionPart.ActiveCell,
@@ -239,7 +246,7 @@ export function BeeTableInternal<R extends object>({
cellProps={cellProps}
onCellUpdates={onCellUpdates}
isReadOnly={isReadOnly}
- setEditing={_setEditing(cellProps.rows.length, () =>
cellProps.allColumns.length)}
+ setEditing={_setActiveCellEditing(cellProps.rows.length, () =>
cellProps.allColumns.length)}
navigateHorizontally={_navigateHorizontally(cellProps.rows.length, () =>
cellProps.allColumns.length)}
navigateVertically={_navigateVertically(cellProps.rows.length,
() => cellProps.allColumns.length)}
/>
@@ -247,7 +254,14 @@ export function BeeTableInternal<R extends object>({
}
},
}),
- [cellComponentByColumnAccessor, onCellUpdates, isReadOnly, _setEditing,
_navigateHorizontally, _navigateVertically]
+ [
+ cellComponentByColumnAccessor,
+ onCellUpdates,
+ isReadOnly,
+ _setActiveCellEditing,
+ _navigateHorizontally,
+ _navigateVertically,
+ ]
);
const reactTableInstance = ReactTable.useTable<R>(
@@ -588,9 +602,17 @@ export function BeeTableInternal<R extends object>({
[adaptSelection, onColumnDeleted]
);
- const setEditing = useMemo(() => {
- return _setEditing(reactTableInstance.rows.length, () =>
reactTableInstance.allColumns.length);
- }, [_setEditing, reactTableInstance.allColumns.length,
reactTableInstance.rows.length]);
+ const setActiveCellEditing = useMemo(() => {
+ return _setActiveCellEditing(reactTableInstance.rows.length, () =>
reactTableInstance.allColumns.length);
+ }, [_setActiveCellEditing, reactTableInstance.allColumns.length,
reactTableInstance.rows.length]);
+
+ useImperativeHandle(
+ selectionRef,
+ () => ({
+ setActiveCellEditing: (isEditing) => setActiveCellEditing(isEditing),
+ }),
+ [setActiveCellEditing]
+ );
return (
<div className={`table-component ${tableId}`} ref={tableRef}
onKeyDown={onKeyDown}>
@@ -615,7 +637,7 @@ export function BeeTableInternal<R extends object>({
onHeaderClick={onHeaderClick}
onHeaderKeyUp={onHeaderKeyUp}
lastColumnMinWidth={lastColumnMinWidth}
- setEditing={setEditing}
+ setActiveCellEditing={setActiveCellEditing}
/>
<BeeTableBody<R>
rowWrapper={rowWrapper}
@@ -649,22 +671,41 @@ export function BeeTableInternal<R extends object>({
);
}
-export type BeeTableRef = BeeTableResizableColumnsDispatchContextType;
+export type BeeTableRef = BeeTableResizingRef & BeeTableSelectionRef;
-export type ForwardRefBeeTableProps<R extends object> = BeeTableProps<R> & {
forwardRef?: React.Ref<BeeTableRef> } & {
+export type ForwardRefBeeTableProps<R extends object> = BeeTableProps<R> & {
+ forwardRef?: React.Ref<BeeTableRef | null>;
+} & {
onColumnResizingWidthChange?: (args: Map<number, ResizingWidth | undefined>)
=> void;
};
-export const BeeTable = <R extends object>(props: ForwardRefBeeTableProps<R>)
=> {
+export const BeeTable = <R extends object>({
+ forwardRef,
+ onColumnResizingWidthChange,
+ ...props
+}: ForwardRefBeeTableProps<R>) => {
+ const beeTableResizingRef = useRef<BeeTableResizingRef>(null);
+ const beeTableSelectionRef = useRef<BeeTableSelectionRef>(null);
+
+ useImperativeHandle(
+ forwardRef,
+ () => {
+ if (!beeTableResizingRef.current || !beeTableSelectionRef.current) {
+ return null;
+ }
+
+ return {
+ ...beeTableSelectionRef.current!,
+ ...beeTableResizingRef.current!,
+ };
+ },
+ []
+ );
return (
<BeeTableSelectionContextProvider>
- <BeeTableResizableColumnsContextProvider
- ref={props.forwardRef}
- onChange={props.onColumnResizingWidthChange}
- columns={props.columns}
- >
+ <BeeTableResizableColumnsContextProvider
resizingRef={beeTableResizingRef} onChange={onColumnResizingWidthChange}>
<BeeTableCellWidthsToFitDataContextProvider>
- <BeeTableInternal {...props} />
+ <BeeTableInternal {...props} selectionRef={beeTableSelectionRef} />
</BeeTableCellWidthsToFitDataContextProvider>
</BeeTableResizableColumnsContextProvider>
</BeeTableSelectionContextProvider>
diff --git
a/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx
b/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx
index 0549a1b224a..4c0057aaa7f 100644
--- a/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx
+++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx
@@ -81,7 +81,7 @@ export interface BeeTableHeaderProps<R extends object> {
resizerStopBehavior: ResizerStopBehavior;
lastColumnMinWidth?: number;
- setEditing: React.Dispatch<React.SetStateAction<boolean>>;
+ setActiveCellEditing: (isEditing: boolean) => void;
}
export function BeeTableHeader<R extends object>({
@@ -99,7 +99,7 @@ export function BeeTableHeader<R extends object>({
shouldShowRowsInlineControls,
resizerStopBehavior,
lastColumnMinWidth,
- setEditing,
+ setActiveCellEditing,
}: BeeTableHeaderProps<R>) {
const getColumnLabel: (groupType: string) => string | undefined =
useCallback(
(groupType) => {
@@ -235,7 +235,7 @@ export function BeeTableHeader<R extends object>({
column.headerCellElement
) : column.isInlineEditable ? (
<InlineEditableTextInput
- setEditing={setEditing}
+ setActiveCellEditing={setActiveCellEditing}
columnIndex={columnIndex}
rowIndex={rowIndex}
value={column.label}
@@ -275,7 +275,7 @@ export function BeeTableHeader<R extends object>({
getColumnLabel,
onColumnAdded,
lastColumnMinWidth,
- setEditing,
+ setActiveCellEditing,
onExpressionHeaderUpdated,
]
);
diff --git
a/packages/boxed-expression-component/src/table/BeeTable/InlineEditableTextInput.tsx
b/packages/boxed-expression-component/src/table/BeeTable/InlineEditableTextInput.tsx
index 7df524d9dfc..fe62b621845 100644
---
a/packages/boxed-expression-component/src/table/BeeTable/InlineEditableTextInput.tsx
+++
b/packages/boxed-expression-component/src/table/BeeTable/InlineEditableTextInput.tsx
@@ -27,7 +27,7 @@ import { useBeeTableSelectableCellRef } from
"../../selection/BeeTableSelectionC
export interface InlineEditableTextInputProps {
value: string;
onChange: (updatedValue: string) => void;
- setEditing: React.Dispatch<React.SetStateAction<boolean>>;
+ setActiveCellEditing: (isEditing: boolean) => void;
rowIndex: number;
columnIndex: number;
}
@@ -36,21 +36,26 @@ export const InlineEditableTextInput:
React.FunctionComponent<InlineEditableText
rowIndex,
columnIndex,
value,
- setEditing,
+ setActiveCellEditing,
onChange,
}) => {
const { i18n } = useBoxedExpressionEditorI18n();
const inputRef = useRef<HTMLInputElement>(null);
- const { isEditing } = useBeeTableSelectableCellRef(rowIndex, columnIndex,
undefined, undefined);
+ const { isEditing } = useBeeTableSelectableCellRef(
+ rowIndex,
+ columnIndex,
+ onChange,
+ useCallback(() => value, [value])
+ );
const stopEditingPersistingValue = useCallback(() => {
const newValue = inputRef.current?.value;
if (newValue && newValue !== value) {
onChange(newValue);
}
- setEditing(false);
- }, [onChange, setEditing, value]);
+ setActiveCellEditing(false);
+ }, [onChange, setActiveCellEditing, value]);
const onInputKeyDown = useMemo(
() => (e: React.KeyboardEvent) => {
@@ -61,17 +66,17 @@ export const InlineEditableTextInput:
React.FunctionComponent<InlineEditableText
}
if (NavigationKeysUtils.isEsc(e.key)) {
- setEditing(false);
+ setActiveCellEditing(false);
}
},
- [setEditing, stopEditingPersistingValue]
+ [setActiveCellEditing, stopEditingPersistingValue]
);
const onLabelClick = useCallback(() => {
- setEditing(true);
- }, [setEditing]);
+ setActiveCellEditing(true);
+ }, [setActiveCellEditing]);
- const getTextStyle = useMemo(() => {
+ const textStyle = useMemo(() => {
if (_.isEmpty(value)) {
return { fontStyle: "italic", cursor: "pointer", color: "gray" };
} else {
@@ -87,7 +92,11 @@ export const InlineEditableTextInput:
React.FunctionComponent<InlineEditableText
});
return !isEditing ? (
- <p className={"inline-editable-preview pf-u-text-truncate"}
style={getTextStyle} onClick={onLabelClick}>
+ <p
+ className={"inline-editable-preview pf-u-text-truncate"}
+ style={{ ...textStyle, width: "100%" }}
+ onClick={onLabelClick}
+ >
{value || i18n.enterText}
</p>
) : (
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]