in packages/unitables/src/bee/UnitablesBeeTable.tsx [261:594]
accessor: getColumnAccessor(insideProperty),
dataType: insideProperty.dataType,
isRowIndexColumn: false,
width,
setWidth: setColumnWidth(insideProperty.joinedName),
minWidth,
};
}),
};
} else {
let minWidth = column.width;
let width = (getObjectValueByPath(configs, column.name) as UnitablesCellConfigs)?.width ?? column.width;
if (column.type === "array") {
width = calculateListFieldWidth(column.joinedName);
minWidth = calculateListFieldWidth(column.joinedName);
}
return {
originalId: uuid + `field-${column.name}-parent`,
label: "",
accessor: getColumnAccessor(column) + "-parent",
dataType: undefined as any,
isRowIndexColumn: false,
width: undefined,
columns: [
{
originalId: uuid + `field-${column.name}`,
label: column.name,
accessor: getColumnAccessor(column),
dataType: column.dataType,
isRowIndexColumn: false,
width,
setWidth: setColumnWidth(column.name),
minWidth,
},
],
};
}
});
}, [columns, uuid, configs, setColumnWidth, calculateListFieldWidth]);
const getColumnKey = useCallback((column: ReactTable.ColumnInstance<ROWTYPE>) => {
return column.originalId ?? column.id;
}, []);
const getRowKey = useCallback((row: ReactTable.Row<ROWTYPE>) => {
return row.original.id;
}, []);
return (
<StandaloneBeeTable
cellComponentByColumnAccessor={cellComponentByColumnAccessor}
scrollableParentRef={scrollableParentRef}
getColumnKey={getColumnKey}
getRowKey={getRowKey}
tableId={id}
isEditableHeader={false}
headerLevelCountForAppendingRowIndexColumn={1}
headerVisibility={BeeTableHeaderVisibility.AllLevels}
operationConfig={beeTableOperationConfig}
allowedOperations={allowedOperations}
columns={beeTableColumns}
rows={rows}
enableKeyboardNavigation={true}
shouldRenderRowIndexColumn={true}
shouldShowRowsInlineControls={true}
shouldShowColumnsInlineControls={false}
onRowAdded={onRowAdded}
onRowDuplicated={onRowDuplicated}
onRowReset={onRowReset}
onRowDeleted={onRowDeleted}
rowWrapper={rowWrapper}
resizerStopBehavior={ResizerStopBehavior.SET_WIDTH_ALWAYS}
/>
);
}
function getColumnAccessor(c: UnitablesColumnType) {
return `field-${c.joinedName}`;
}
function UnitablesBeeTableCell({
joinedName,
rowCount,
columnCount,
}: BeeTableCellProps<ROWTYPE> & {
joinedName: string;
rowCount: number;
columnCount: number;
}) {
const [{ field, onChange: onFieldChange, name: fieldName }] = useField(joinedName, {});
const cellRef = useRef<HTMLDivElement | null>(null);
const [autoFieldKey, forceUpdate] = useReducer((x) => x + 1, 0);
const { containerCellCoordinates } = useBeeTableCoordinates();
const { isBeeTableChange } = useUnitablesContext();
const { submitRow, rowInputs } = useUnitablesRow(containerCellCoordinates?.rowIndex ?? 0);
const fieldInput = useMemo(() => getObjectValueByPath(rowInputs, fieldName), [rowInputs, fieldName]);
const [isSelectFieldOpen, setIsSelectFieldOpen] = useState(false);
const fieldCharacteristics = useMemo(() => {
if (!field) {
return;
}
return {
xDmnType: field["x-dmn-type"] as X_DMN_TYPE,
isEnum: !!field.enum,
isList: field.type === "array",
};
}, [field]);
const previousFieldInput = useRef(fieldInput);
// keep previous updated;
useEffect(() => {
previousFieldInput.current = fieldInput;
}, [fieldInput]);
// FIXME: Decouple from DMN --> https://github.com/apache/incubator-kie-issues/issues/166
const setValue = useCallback(
(newValue?: string) => {
isBeeTableChange.current = true;
const newValueWithoutSymbols = newValue?.replace(/\r/g, "") ?? "";
if (field.enum) {
if (field.enum.findIndex((value: unknown) => value === newValueWithoutSymbols) >= 0) {
onFieldChange(newValueWithoutSymbols);
} else {
onFieldChange(field.placeholder);
}
// Changing the values using onChange will not re-render <select> nodes;
// This ensure a re-render of the SelectField;
forceUpdate();
} else if (field.type === "string") {
if (field.format === "time") {
if (moment(newValueWithoutSymbols, [moment.HTML5_FMT.TIME, moment.HTML5_FMT.TIME_SECONDS], true).isValid()) {
onFieldChange(newValueWithoutSymbols);
} else {
onFieldChange("");
}
} else if (field.format === "date") {
if (moment(newValueWithoutSymbols, [moment.HTML5_FMT.DATE]).isValid()) {
onFieldChange(newValueWithoutSymbols);
} else {
onFieldChange("");
}
} else if (field.format === "date-time") {
const valueAsNumber = Date.parse(newValueWithoutSymbols);
if (!isNaN(valueAsNumber)) {
onFieldChange(newValueWithoutSymbols);
} else {
onFieldChange("");
}
} else {
onFieldChange(newValueWithoutSymbols);
}
} else if (field.type === "number") {
const numberValue = parseFloat(newValueWithoutSymbols);
onFieldChange(isNaN(numberValue) ? undefined : numberValue);
} else if (field.type === "boolean") {
onFieldChange(newValueWithoutSymbols === "true");
} else if (field.type === "array") {
// FIXME: Support lists --> https://github.com/apache/incubator-kie-issues/issues/167
} else if (field.type === "object" && typeof newValue !== "object") {
// objects are flattened in a single row - this case shouldn't happen;
} else {
onFieldChange(newValue);
}
submitRow();
},
[isBeeTableChange, field.enum, field.type, field.placeholder, field.format, submitRow, onFieldChange]
);
const { isActive, isEditing } = useBeeTableSelectableCellRef(
containerCellCoordinates?.rowIndex ?? 0,
containerCellCoordinates?.columnIndex ?? 0,
setValue,
useCallback(() => `${fieldInput ?? ""}`, [fieldInput])
);
const { mutateSelection } = useBeeTableSelectionDispatch();
const navigateVertically = useCallback(
(args: { isShiftPressed: boolean }) => {
mutateSelection({
part: SelectionPart.ActiveCell,
columnCount: () => columnCount,
rowCount,
deltaColumns: 0,
deltaRows: args.isShiftPressed ? -1 : 1,
isEditingActiveCell: false,
keepInsideSelection: true,
});
},
[mutateSelection, rowCount, columnCount]
);
const setEditingCell = useCallback(
(isEditing: boolean) => {
mutateSelection({
part: SelectionPart.ActiveCell,
columnCount: () => columnCount,
rowCount,
deltaColumns: 0,
deltaRows: 0,
isEditingActiveCell: isEditing,
keepInsideSelection: true,
});
},
[mutateSelection, rowCount, columnCount]
);
const onKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
// TAB
if (e.key.toLowerCase() === "tab") {
// ListField - START
if (fieldCharacteristics?.isList) {
// Get all uniforms components inside the ListField;
const uniformsComponents = cellRef.current?.querySelectorAll('[id^="uniforms-"]');
if (uniformsComponents === undefined) {
setEditingCell(false);
return;
}
const uniformComponentTargetIndex = Array.from(uniformsComponents ?? []).findIndex(
(component) => component.id === (e.target as HTMLElement).id
);
// If it wasn't possible to retrieve the index, it should focus on the first button;
if (uniformComponentTargetIndex < 0) {
(uniformsComponents?.item(1) as HTMLElement).parentElement?.focus();
setEditingCell(true);
e.stopPropagation();
return;
}
const nextUniformsComponent = e.shiftKey
? uniformsComponents[uniformComponentTargetIndex - 1]
: uniformsComponents[uniformComponentTargetIndex + 1];
// Should leave ListField if nextUniformsComponent doesn't exist
if (nextUniformsComponent === undefined) {
setEditingCell(false);
return;
}
// TextField, BoolField, DateTimeField, NumField, ListAddField, ListDelField
if (
nextUniformsComponent.tagName.toLowerCase() === "input" ||
nextUniformsComponent.tagName.toLowerCase() === "button"
) {
(nextUniformsComponent as HTMLButtonElement | HTMLInputElement).parentElement?.focus();
setEditingCell(true);
submitRow();
e.stopPropagation();
return;
}
// Nested ListFields or SelectField
if (nextUniformsComponent.tagName.toLowerCase() === "div") {
(nextUniformsComponent as HTMLElement)?.focus();
setEditingCell(true);
submitRow();
e.stopPropagation();
return;
}
} // ListField - END
submitRow();
setEditingCell(false);
if (fieldCharacteristics?.isEnum) {
setIsSelectFieldOpen((prev) => {
if (prev) {
cellRef.current?.getElementsByTagName("button")?.[0]?.click();
}
return false;
});
}
return;
}
// ESC
if (e.key.toLowerCase() === "escape") {
e.stopPropagation();
onFieldChange(previousFieldInput.current);
cellRef.current?.focus();
setEditingCell(false);
if (fieldCharacteristics?.isEnum) {
setIsSelectFieldOpen((prev) => {
if (prev) {
cellRef.current?.getElementsByTagName("button")?.[0]?.click();
}
return false;
});
}
return;
}
// ENTER
if (e.key.toLowerCase() === "enter") {
// ListField - START
if (fieldCharacteristics?.isList) {
e.stopPropagation();
e.preventDefault();
const uniformsComponents = cellRef.current?.querySelectorAll('[id^="uniforms-"]');
if (!uniformsComponents) {
return;
}
// To search the uniforms components avoiding returning the top ListField
// we search backwards;
const reversedUniformsComponents = Array.from(uniformsComponents).reverse();
const reversedUniformComponentTargetIndex = reversedUniformsComponents.findIndex((component) =>
component.contains(e.target as HTMLElement)
);
const uniformsComponent = reversedUniformsComponents[reversedUniformComponentTargetIndex];
// If field is selected, and the target is not present
if (!uniformsComponent) {
// check if it's a button from a SelectField
const selectFieldUl = document.querySelectorAll(`ul[name^="${fieldName}."]`)?.item(0);
if (selectFieldUl && selectFieldUl.contains(e.target as HTMLElement)) {
setIsSelectFieldOpen(false);
submitRow();
(cellRef.current?.querySelector(`[id=${selectFieldUl.id}]`) as HTMLDivElement)
?.getElementsByTagName("button")
?.item(0)
?.focus();
} else if (uniformsComponents[1].tagName.toLowerCase() === "button") {
(uniformsComponents[1] as HTMLButtonElement)?.focus();
}
} else {
// A button is the ListAddField or ListDelField
if (uniformsComponent.tagName.toLowerCase() === "button") {
(uniformsComponent as HTMLButtonElement)?.click();
// The ListField ListDelField is the last element
if (reversedUniformComponentTargetIndex === 0) {