diff --git a/canvas_modules/common-canvas/__tests__/common-canvas/common-canvas-test.js b/canvas_modules/common-canvas/__tests__/common-canvas/common-canvas-test.js index 42f2a19d52..ff78170185 100644 --- a/canvas_modules/common-canvas/__tests__/common-canvas/common-canvas-test.js +++ b/canvas_modules/common-canvas/__tests__/common-canvas/common-canvas-test.js @@ -65,7 +65,8 @@ describe("CommonCanvas renders correctly", () => { const config = {}; const canvasParams = { showRightFlyout: false }; const wrapper = createCommonCanvas(config, canvasController, canvasParams); - expect(wrapper.find(CommonCanvasRightFlyout)).to.have.length(1); + // When showRightFlyout is false then Right Flyout should not be visible + expect(wrapper.find(CommonCanvasRightFlyout)).to.have.length(0); expect(canvasController.isRightFlyoutOpen() === false).to.be.true; }); diff --git a/canvas_modules/common-canvas/__tests__/common-properties/controls/oneofselect-test.js b/canvas_modules/common-canvas/__tests__/common-properties/controls/oneofselect-test.js index abfe27e70a..69f2cb397c 100644 --- a/canvas_modules/common-canvas/__tests__/common-properties/controls/oneofselect-test.js +++ b/canvas_modules/common-canvas/__tests__/common-properties/controls/oneofselect-test.js @@ -318,8 +318,8 @@ describe("oneofselect paramDef works correctly", () => { // combobox should have aria-label const comboboxWrapper = container.querySelector("div[data-id='properties-ctrl-oneofselect_custom_value']"); - const comboboxAriaLabel = comboboxWrapper.querySelector(".cds--list-box__menu").getAttribute("aria-label"); - expect(comboboxAriaLabel).to.equal("oneofselect custom value allowed"); + const comboboxAriaLabelledby = comboboxWrapper.querySelector(".cds--list-box__menu").getAttribute("aria-labelledby"); + expect(comboboxWrapper.querySelector(`label[id='${comboboxAriaLabelledby}']`).textContent).to.equal("oneofselect custom value allowed(required)"); }); }); diff --git a/canvas_modules/common-canvas/__tests__/common-properties/controls/structurelisteditor-test.js b/canvas_modules/common-canvas/__tests__/common-properties/controls/structurelisteditor-test.js index 637ab46c62..929a4044ff 100644 --- a/canvas_modules/common-canvas/__tests__/common-properties/controls/structurelisteditor-test.js +++ b/canvas_modules/common-canvas/__tests__/common-properties/controls/structurelisteditor-test.js @@ -498,7 +498,7 @@ describe("StructureListEditor render from paramdef", () => { expect(tableUtilsRTL.getTableRows(container)).to.have.length(2); // select the first row in the table - tableUtilsRTL.clickTableRows(container, [0]); + tableUtilsRTL.selectCheckboxes(container, [0]); // ensure table toolbar has Delete button and select it const tableToolbar = container.querySelector("div.properties-table-toolbar"); @@ -525,7 +525,7 @@ describe("StructureListEditor render from paramdef", () => { expect(tableData).to.have.length(3); // set the error in the last row - const checkboxCell = summaryPanel.querySelectorAll("input[type='checkbox']")[3]; + const checkboxCell = summaryPanel.querySelectorAll("input[type='checkbox']")[7]; checkboxCell.setAttribute("checked", false); fireEvent.click(checkboxCell); @@ -544,10 +544,10 @@ describe("StructureListEditor render from paramdef", () => { expect(errorMessage).to.eql(actual); // remove the first row and ensure the error message is associated with the correct row. - tableUtilsRTL.clickTableRows(summaryPanel, [0]); + tableUtilsRTL.selectCheckboxes(container, [0]); const tableToolbar = container.querySelector("div.properties-table-toolbar"); - let deleteButton = tableToolbar.querySelector("button.properties-action-delete"); - fireEvent.click(deleteButton); + let deleteButton = tableToolbar.querySelectorAll("button.properties-action-delete"); + fireEvent.click(deleteButton[0]); const messages = renderedController.getAllErrorMessages(); const rowErrorMsg = { "1": { "3": { propertyId: { @@ -561,9 +561,9 @@ describe("StructureListEditor render from paramdef", () => { summaryPanel = container.querySelector("div.properties-wf-content.show"); tableData = tableUtilsRTL.getTableRows(summaryPanel); expect(tableData).to.have.length(2); - tableUtilsRTL.clickTableRows(summaryPanel, [1]); - deleteButton = container.querySelector("div.properties-table-toolbar").querySelector("button.properties-action-delete"); - fireEvent.click(deleteButton); + tableUtilsRTL.selectCheckboxes(container, [1]); + deleteButton = container.querySelectorAll("button.properties-action-delete"); + fireEvent.click(deleteButton[0]); actual = renderedController.getErrorMessage({ name: "inlineEditingTableError" }); expect(actual).to.equal(null); }); @@ -630,7 +630,7 @@ describe("StructureListEditor render from paramdef", () => { expect(errorMessage).to.eql(actual); // select the first row and move it to the bottom and make sure the error messages stay aligned. - tableUtilsRTL.clickTableRows(summaryPanel, [0]); + tableUtilsRTL.selectCheckboxes(summaryPanel, [0]); summaryPanel = container.querySelector("div.properties-wf-content.show"); const moveRowBottom = container.querySelector("div.properties-table-toolbar").querySelector("button.table-row-move-bottom-button"); fireEvent.click(moveRowBottom); @@ -656,7 +656,7 @@ describe("StructureListEditor render from paramdef", () => { // select the second from the last row and move it to the top and make sure the error messages stay aligned. tableData = tableUtilsRTL.getTableRows(summaryPanel); expect(tableData).to.have.length(5); - tableUtilsRTL.clickTableRows(summaryPanel, [3]); + tableUtilsRTL.selectCheckboxes(summaryPanel, [3]); summaryPanel = container.querySelector("div.properties-wf-content.show"); const moveRowTop = container.querySelector("div.properties-table-toolbar").querySelector("button.table-row-move-top-button"); fireEvent.click(moveRowTop); @@ -780,6 +780,49 @@ describe("StructureListEditor render from paramdef", () => { }); }); +describe("StructureListEditor single select table renders and functions correctly", () => { + let wrapper; + + beforeEach(() => { + const renderedObject = propertyUtilsRTL.flyoutEditorForm(structureListEditorParamDef); + wrapper = renderedObject.wrapper; + }); + + afterEach(() => { + wrapper.unmount(); + }); + + // Removed table toolbar from single select tables. + it("Should not render table toolbar", () => { + const { container } = wrapper; + propertyUtilsRTL.openSummaryPanel(wrapper, "inlineEditingTableWarning-summary-panel"); + tableUtilsRTL.clickTableRows(container, [0]); + const tableToolbar = container.querySelectorAll("div.properties-table-toolbar"); + expect(tableToolbar).to.have.length(0); + }); + + // Testing delete icons for single select table + it("should delete rows correctly", async() => { + const { container } = wrapper; + const summaryPanel = propertyUtilsRTL.openSummaryPanel(wrapper, "inlineEditingTableWarning-summary-panel"); + + // Adding a row + const addColumnButton = summaryPanel.querySelectorAll("button.properties-add-fields-button"); + expect(addColumnButton).to.have.length(1); + fireEvent.click(addColumnButton[0]); + + expect(tableUtilsRTL.getTableRows(container)).to.have.length(2); + + // Testing delete row icon + const deleteButtons = container.querySelectorAll(".delete-button"); + expect(deleteButtons).to.have.length(2); + tableUtilsRTL.clickTableRows(container, [0]); // need to click on the table that we want to delete + fireEvent.click(deleteButtons[0]); + + expect(tableUtilsRTL.getTableRows(container)).to.have.length(1); + }); +}); + describe("StructureListEditor renders correctly with nested controls", () => { let wrapper; let renderedController; diff --git a/canvas_modules/common-canvas/__tests__/common-properties/panels/summary-test.js b/canvas_modules/common-canvas/__tests__/common-properties/panels/summary-test.js index 57a57bdcc6..2e0d1bfcab 100644 --- a/canvas_modules/common-canvas/__tests__/common-properties/panels/summary-test.js +++ b/canvas_modules/common-canvas/__tests__/common-properties/panels/summary-test.js @@ -106,19 +106,18 @@ describe("summary panel renders error/warning status correctly", () => { // ensure table toolbar has Delete button and click it wideflyout = wrapper.find("div.properties-wf-content.show"); let tableWrapper = wideflyout.find("div[data-id='properties-expressionCellTable']"); - let tableToolbar = tableWrapper.find("div.properties-table-toolbar"); - let deleteButton = tableToolbar.find("button.properties-action-delete"); - expect(deleteButton).to.have.length(1); - deleteButton.simulate("click"); + let deleteButtons = tableWrapper.find("button.delete-button"); + expect(deleteButtons).to.have.length(2); + deleteButtons.at(0).simulate("click"); // remove second row tableUtils.clickTableRows(wideflyout, [0]); wideflyout = wrapper.find("div.properties-wf-content.show"); tableWrapper = wideflyout.find("div[data-id='properties-expressionCellTable']"); - tableToolbar = tableWrapper.find("div.properties-table-toolbar"); - deleteButton = tableToolbar.find("button.properties-action-delete"); - expect(deleteButton).to.have.length(1); - deleteButton.simulate("click"); + deleteButtons = tableWrapper.find("button.delete-button"); + expect(deleteButtons).to.have.length(1); + deleteButtons.at(0).simulate("click"); + // close fly-out wideflyout.find("button.properties-apply-button").simulate("click"); @@ -162,18 +161,16 @@ describe("summary panel renders error/warning status correctly", () => { wideflyout = wrapper.find("div.properties-wf-content.show"); // ensure table toolbar has Delete button and click it let tableWrapper = wideflyout.find("div[data-id='properties-expressionCellTable']"); - const tableToolbar = tableWrapper.find("div.properties-table-toolbar"); - let deleteButton = tableToolbar.find("button.properties-action-delete"); - expect(deleteButton).to.have.length(1); - deleteButton.simulate("click"); + let deleteButtons = tableWrapper.find("button.delete-button"); + deleteButtons.at(0).simulate("click"); // remove second row tableUtils.clickTableRows(wideflyout, [0]); wideflyout = wrapper.find("div.properties-wf-content.show"); tableWrapper = wideflyout.find("div[data-id='properties-expressionCellTable']"); - deleteButton = tableWrapper.find("div.properties-table-toolbar").find("button.properties-action-delete"); - expect(deleteButton).to.have.length(1); - deleteButton.simulate("click"); + deleteButtons = tableWrapper.find("button.delete-button"); + deleteButtons.at(0).simulate("click"); + // check that all rows were removed wideflyout = wrapper.find("div.properties-wf-content.show"); diff --git a/canvas_modules/common-canvas/__tests__/common-properties/panels/tearsheet-test.js b/canvas_modules/common-canvas/__tests__/common-properties/panels/tearsheet-test.js index 2f2188cad1..76a40f15ea 100644 --- a/canvas_modules/common-canvas/__tests__/common-properties/panels/tearsheet-test.js +++ b/canvas_modules/common-canvas/__tests__/common-properties/panels/tearsheet-test.js @@ -46,7 +46,7 @@ describe("tearsheet tests", () => { it("should have title and description set", () => { controller.setActiveTearsheet("tearsheet1"); wrapper.update(); - expect(wrapper.find("div.properties-tearsheet-panel .properties-tearsheet-header h3").text()).to.equal("Python"); + expect(wrapper.find("div.properties-tearsheet-panel .properties-tearsheet-header h2").text()).to.equal("Python"); expect(wrapper.find("div.properties-tearsheet-panel .properties-tearsheet-header p").text()).to.equal("Your change is automatically saved."); }); it("should be hidden but not removed from DOM on the tearsheet close button", () => { @@ -68,7 +68,7 @@ describe("tearsheet tests", () => { wrapper.find("div[data-id='properties-ctrl-code_rows'] button.maximize").simulate("click"); wrapper.update(); expect(wrapper.find("div.properties-tearsheet-panel.is-visible")).to.have.length(1); - expect(wrapper.find("div.properties-tearsheet-panel .properties-tearsheet-header h3").text()).to.equal("Python 2"); + expect(wrapper.find("div.properties-tearsheet-panel .properties-tearsheet-header h2").text()).to.equal("Python 2"); expect(wrapper.find("div.properties-tearsheet-panel div[data-id='properties-ctrl-code_rows']")).to.have.length(1); }); }); @@ -88,7 +88,7 @@ describe("Tearsheet renders correctly", () => { const tearsheet = wrapper.find("div.properties-tearsheet-panel"); expect(tearsheet).to.have.length(1); expect(tearsheet.find("div.properties-tearsheet-header")).to.have.length(1); - expect(tearsheet.find("div.properties-tearsheet-header > h3").text()).to.equal("test title"); + expect(tearsheet.find("div.properties-tearsheet-header > h2").text()).to.equal("test title"); expect(tearsheet.find("div.properties-tearsheet-body")).to.have.length(1); expect(tearsheet.find("div.properties-tearsheet-body").text()).to.equal("test content"); expect(tearsheet.find("div.properties-tearsheet-body.with-buttons")).to.have.length(0); diff --git a/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/structurelisteditor_paramDef.json b/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/structurelisteditor_paramDef.json index 73fcfc1abc..20f80d50cc 100644 --- a/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/structurelisteditor_paramDef.json +++ b/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/structurelisteditor_paramDef.json @@ -1216,7 +1216,7 @@ }, { "complex_type_ref": "inlineEditingTableError", - "row_selection": "single", + "row_selection": "multiple-edit", "moveable_rows": true, "parameters": [ { diff --git a/canvas_modules/common-canvas/locales/common-properties/locales/en.json b/canvas_modules/common-canvas/locales/common-properties/locales/en.json index 96081ecffb..644fe60889 100644 --- a/canvas_modules/common-canvas/locales/common-properties/locales/en.json +++ b/canvas_modules/common-canvas/locales/common-properties/locales/en.json @@ -114,5 +114,6 @@ "label.indicator.optional": "(optional)", "slider.numberInput.label": "Slider number input", "editorForm.tabList.label": "Primary Tabs", - "subTabs.tabList.label": "Tab List" + "subTabs.tabList.label": "Tab List", + "table.deleteIcon.tooltip": "Delete" } diff --git a/canvas_modules/common-canvas/locales/common-properties/locales/eo.json b/canvas_modules/common-canvas/locales/common-properties/locales/eo.json index 22449a7e90..fc4af3eb85 100644 --- a/canvas_modules/common-canvas/locales/common-properties/locales/eo.json +++ b/canvas_modules/common-canvas/locales/common-properties/locales/eo.json @@ -112,5 +112,6 @@ "label.indicator.optional": "[Esperanto~(optional)~~~~~~eo]", "slider.numberInput.label": "[Esperanto~Slider number input~~~~~eo]", "editorForm.tabList.label": "[Esperanto~Primary Tabs~~~~eo]", - "subTabs.tabList.label": "[Esperanto~Tab List~~~~eo]" + "subTabs.tabList.label": "[Esperanto~Tab List~~~~eo]", + "table.deleteIcon.tooltip": "[Esperanto~Delete~~~~eo]" } diff --git a/canvas_modules/common-canvas/package.json b/canvas_modules/common-canvas/package.json index 47bff42c37..dcda6038f0 100644 --- a/canvas_modules/common-canvas/package.json +++ b/canvas_modules/common-canvas/package.json @@ -59,7 +59,7 @@ "@babel/plugin-transform-runtime": "7.24.7", "@babel/preset-env": "7.25.3", "@babel/preset-react": "7.24.7", - "@carbon/react": "1.62.0-rc.0", + "@carbon/react": "1.71.0", "@rollup/plugin-babel": "5.3.0", "@rollup/plugin-commonjs": "21.0.1", "@rollup/plugin-json": "4.1.0", diff --git a/canvas_modules/common-canvas/src/common-canvas/canvas-controller.js b/canvas_modules/common-canvas/src/common-canvas/canvas-controller.js index a505e793bc..35a9799d02 100644 --- a/canvas_modules/common-canvas/src/common-canvas/canvas-controller.js +++ b/canvas_modules/common-canvas/src/common-canvas/canvas-controller.js @@ -1641,6 +1641,18 @@ export default class CanvasController { this.objectModel.setBottomPanelHeight(ht); } + isLeftFlyoutOpen() { + return this.objectModel.isLeftFlyoutOpen(); + } + + setRightFlyoutWidth(wd) { + this.objectModel.setRightFlyoutWidth(wd); + } + + isRightFlyoutOpen() { + return this.objectModel.isRightFlyoutOpen(); + } + isTopPanelOpen() { return this.getObjectModel().isTopPanelOpen(); } @@ -1705,14 +1717,6 @@ export default class CanvasController { this.objectModel.toggleNotificationPanel(); } - isLeftFlyoutOpen() { - return this.objectModel.isLeftFlyoutOpen(); - } - - isRightFlyoutOpen() { - return this.objectModel.isRightFlyoutOpen(); - } - isDisplayingFullPageSubFlow() { const breadcrumbs = this.objectModel.getBreadcrumbs(); return breadcrumbs.length > 1; diff --git a/canvas_modules/common-canvas/src/common-canvas/cc-bottom-panel.jsx b/canvas_modules/common-canvas/src/common-canvas/cc-bottom-panel.jsx index b6525de839..1a36281f31 100644 --- a/canvas_modules/common-canvas/src/common-canvas/cc-bottom-panel.jsx +++ b/canvas_modules/common-canvas/src/common-canvas/cc-bottom-panel.jsx @@ -19,8 +19,11 @@ import PropTypes from "prop-types"; import { connect } from "react-redux"; import Logger from "../logging/canvas-logger.js"; -const MARGIN_TOP = 100; +// Margin must be equal to or greater than the toolbar height +// plus the minimum allowed height for the canvas which is 150px. +const MARGIN_TOP = 200; const MIN_HEIGHT = 75; +const TOP_PANEL_CLASSNAME = "top-panel"; class CanvasBottomPanel extends React.Component { constructor(props) { @@ -61,13 +64,21 @@ class CanvasBottomPanel extends React.Component { // Returns a new height for the bottom panel limited by the need to enforce // a minimum and maximum height. limitHeight(ht) { - const canvasContainer = document.getElementById(this.props.containingDivId); + const containingDiv = document.getElementById(this.props.containingDivId); + const topPanelDiv = document.getElementsByClassName(TOP_PANEL_CLASSNAME); let height = ht; + let topPanelHeight = 0; - // canvasContainer may not be available in some test situations - if (canvasContainer) { - const canvasHeight = canvasContainer.getBoundingClientRect().height; - const maxHeight = canvasHeight - MARGIN_TOP; + // Consider top panel height while calculating maxHeight to disable scroll + // in right flyout. + if (topPanelDiv.length > 0) { + topPanelHeight = topPanelDiv[0].getBoundingClientRect().height; + } + + // containingDiv may not be available in some test situations + if (containingDiv) { + const containingDivHt = containingDiv.getBoundingClientRect().height; + const maxHeight = containingDivHt - MARGIN_TOP - topPanelHeight; height = Math.min(Math.max(height, MIN_HEIGHT), maxHeight); } return height; diff --git a/canvas_modules/common-canvas/src/common-canvas/cc-panels.jsx b/canvas_modules/common-canvas/src/common-canvas/cc-panels.jsx index 68b77d5b9c..524da00665 100644 --- a/canvas_modules/common-canvas/src/common-canvas/cc-panels.jsx +++ b/canvas_modules/common-canvas/src/common-canvas/cc-panels.jsx @@ -97,7 +97,7 @@ class CommonCanvasPanels extends React.Component { // indicated by setting the config field enablePaletteLayout to // "None" and providing some JSX in the leftFlyoutContent field // of . - isLeftPanelOpen() { + isLeftFlyoutOpen() { if (this.props.enablePaletteLayout === PALETTE_LAYOUT_DIALOG) { return false; } @@ -114,6 +114,11 @@ class CommonCanvasPanels extends React.Component { return false; } + // Returns true if the right flyout should be displayed. + isRightFlyoutOpen() { + return this.props.rightFlyoutIsOpen; + } + // Returns a JSX object for the contents of the left panel. If the application // sets enablePaletteLayout to None this indicates the app wamts its own content // to go into the left panel provided by leftFlyoutContent provided to @@ -129,6 +134,11 @@ class CommonCanvasPanels extends React.Component { return null; } + // Returns a JSX object for the contents of the right panel. + generateRightFlyout() { + return (); + } + render() { this.logger.log("render"); @@ -138,8 +148,9 @@ class CommonCanvasPanels extends React.Component { containingDivId={this.props.containingDivId} /> ); - const rightFlyout = (); - const leftFlyoutIsOpen = this.isLeftPanelOpen(); + const rightFlyoutIsOpen = this.isRightFlyoutOpen(); + const rightFlyout = rightFlyoutIsOpen ? this.generateRightFlyout() : null; + const leftFlyoutIsOpen = this.isLeftFlyoutOpen(); const leftFlyout = leftFlyoutIsOpen ? this.generateLeftFlyout() : null; const topCenterBottom = this.generateTopCenterBottom(); @@ -288,6 +299,7 @@ const mapStateToProps = (state, ownProps) => ({ enableNarrowPalette: state.canvasconfig.enableNarrowPalette, enableLeftFlyoutUnderToolbar: state.canvasconfig.enableLeftFlyoutUnderToolbar, enableRightFlyoutUnderToolbar: state.canvasconfig.enableRightFlyoutUnderToolbar, + enableRightFlyoutDragToResize: state.canvasconfig.enableRightFlyoutDragToResize, toolbarIsOpen: (state.canvasconfig.enableToolbarLayout !== PALETTE_LAYOUT_NONE), paletteIsOpen: state.palette.isOpen, topPanelIsOpen: state.toppanel.isOpen, diff --git a/canvas_modules/common-canvas/src/common-canvas/cc-right-flyout.jsx b/canvas_modules/common-canvas/src/common-canvas/cc-right-flyout.jsx index f5a2e6e8ea..4857f55319 100644 --- a/canvas_modules/common-canvas/src/common-canvas/cc-right-flyout.jsx +++ b/canvas_modules/common-canvas/src/common-canvas/cc-right-flyout.jsx @@ -19,24 +19,129 @@ import { connect } from "react-redux"; import PropTypes from "prop-types"; import Logger from "../logging/canvas-logger.js"; +// Should cover at most 70% of available width. +const MAX_WIDTH_EXTEND_PERCENT = 0.7; +// Should have a minimum width of 0 to hide flyout if there is no content. +const MIN_WIDTH = 0; +// If flyout content width is not set occupy entire width to strecth content. +const DEFAULT_WIDTH = "100%"; class CommonCanvasRightFlyout extends React.Component { constructor(props) { super(props); + + // Intial width of the flyout when opened to track resize. + this.initialWidth = MIN_WIDTH; + this.logger = new Logger("CC-RightFlyout"); + + this.rightFlyoutRef = React.createRef(); + + this.onMouseUp = this.onMouseUp.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseMoveX = this.onMouseMoveX.bind(this); + this.getCurrentWidth = this.getCurrentWidth.bind(this); + + this.getRightFlyoutResizeContent = this.getRightFlyoutResizeContent.bind(this); + } + + componentDidMount() { + // Since flyout content width can be dynamic set the initial width as per width of the content + const currentWidth = this.getCurrentWidth(); + this.initialWidth = currentWidth; + } + + componentWillUnmount() { + // Reset the flyout width to adjust as per content width in next render + this.props.canvasController.setRightFlyoutWidth(0); + } + + onMouseDown(e) { + // Set width as soon as resize is detected to accurately move the flyout with the drag. + const currentWidth = this.getCurrentWidth(); + this.props.canvasController.setRightFlyoutWidth(this.limitWidth(currentWidth)); + + if (e.button === 0) { + document.addEventListener("mousemove", this.onMouseMoveX, true); + document.addEventListener("mouseup", this.onMouseUp, true); + this.posX = e.clientX; + // Prevent panel contents being dragged in the test harness (which can + // happen even though draggable is false for the contents!) + e.preventDefault(); + } + } + + onMouseUp() { + document.removeEventListener("mousemove", this.onMouseMoveX, true); + document.removeEventListener("mouseup", this.onMouseUp, true); + } + + onMouseMoveX(e) { + if (e.clientX) { + const diff = e.clientX - this.posX; + const wth = this.props.panelWidth - diff; + this.props.canvasController.setRightFlyoutWidth(this.limitWidth(wth)); + this.posX = e.clientX; + } + } + + // Returns content to enable/disable resize based on feature flag. + getRightFlyoutResizeContent() { + let resizeContent = null; + + if (this.props.enableRightFlyoutDragToResize) { + resizeContent = (
); + } + + return resizeContent; + } + + // Returns present width of the flyout. + getCurrentWidth() { + let currentWidth = MIN_WIDTH; + const el = this.rightFlyoutRef?.current; + if (el) { + currentWidth = el?.offsetWidth; + } + return currentWidth; + } + + // Returns a new width for right panel limited by the need to enforce + // a minimum and maximum width + limitWidth(wd) { + const canvasContainer = document.getElementById(this.props.containingDivId); + let width = wd; + + if (canvasContainer) { + // Max Width should be 70% of the total available width (canvas + rightflyout) + const canvasWidth = canvasContainer.getBoundingClientRect().width; + const maxWidth = (canvasWidth + this.props.panelWidth) * MAX_WIDTH_EXTEND_PERCENT; + width = Math.min(Math.max(width, this.initialWidth), maxWidth); + } + + return width; } render() { this.logger.log("render"); let rightFlyout =
; // For no content, return empty
so grid siziing for parent
work correctly. + const rightFlyoutDragContent = this.getRightFlyoutResizeContent(); if (this.props.content && this.props.isOpen) { + let widthPx = this.limitWidth(this.props.panelWidth) + "px"; + // Apply 100% width if content has no width to occupy flyout. + if (this.props.panelWidth === MIN_WIDTH) { + widthPx = DEFAULT_WIDTH; + } const rfClass = this.props.enableRightFlyoutUnderToolbar ? "right-flyout-panel under-toolbar" : "right-flyout-panel"; rightFlyout = ( -
- {this.props.content} +
+ {rightFlyoutDragContent} +
+ {this.props.content} +
); } @@ -46,16 +151,24 @@ class CommonCanvasRightFlyout extends React.Component { } CommonCanvasRightFlyout.propTypes = { + // Provided by Common Canvas + canvasController: PropTypes.object, + containingDivId: PropTypes.string, + // Provided by Redux isOpen: PropTypes.bool, content: PropTypes.object, - enableRightFlyoutUnderToolbar: PropTypes.bool + enableRightFlyoutUnderToolbar: PropTypes.bool, + enableRightFlyoutDragToResize: PropTypes.bool, + panelWidth: PropTypes.number }; const mapStateToProps = (state, ownProps) => ({ isOpen: state.rightflyout.isOpen, content: state.rightflyout.content, - enableRightFlyoutUnderToolbar: state.canvasconfig.enableRightFlyoutUnderToolbar + enableRightFlyoutUnderToolbar: state.canvasconfig.enableRightFlyoutUnderToolbar, + enableRightFlyoutDragToResize: state.canvasconfig.enableRightFlyoutDragToResize, + panelWidth: state.rightflyout.panelWidth }); export default connect(mapStateToProps)(CommonCanvasRightFlyout); diff --git a/canvas_modules/common-canvas/src/common-canvas/common-canvas.scss b/canvas_modules/common-canvas/src/common-canvas/common-canvas.scss index ea10e388c6..742c845c11 100644 --- a/canvas_modules/common-canvas/src/common-canvas/common-canvas.scss +++ b/canvas_modules/common-canvas/src/common-canvas/common-canvas.scss @@ -44,6 +44,13 @@ $panel-border-color: $border-subtle-01; position: relative; // This allows the State Tag to have positio: absolute } +.right-flyout-container { + display: flex; + border-left: $panel-border-width solid $panel-border-color; + min-height: fit-content; + min-width: fit-content; +} + .left-flyout-panel, .right-flyout-panel { // This combination of height and min-height enable scrolling in the child panel, @@ -51,11 +58,29 @@ $panel-border-color: $border-subtle-01; // so the contents of the panel can adjust themselves to the height accordingly. height: 0; min-height: 100%; +} +// Allow content in child to expand on drag using flex +.right-flyout-panel { + min-width: fit-content; + display: flex; + flex: 1; user-select: none; /* Prevent elements from being selectable */ background-color: $layer-01; } +.right-flyout-drag { + border-left: $panel-border-color; + cursor: col-resize; + flex: 0 0 $spacing-01; + border-left-width: 1px; + background: $layer-01; + transition: all 0.2s ease-in; + &:hover { + background: $button-tertiary; + } +} + .bottom-panel { display: flex; flex-direction: column; diff --git a/canvas_modules/common-canvas/src/common-properties/common-properties.jsx b/canvas_modules/common-canvas/src/common-properties/common-properties.jsx index b9f41f3fc3..b3ba8019b5 100644 --- a/canvas_modules/common-canvas/src/common-properties/common-properties.jsx +++ b/canvas_modules/common-canvas/src/common-properties/common-properties.jsx @@ -43,6 +43,7 @@ class CommonProperties extends React.Component { } componentDidCatch(error, info) { + console.error(error); this.setState({ hasError: true, error: error, diff --git a/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.jsx b/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.jsx index b7edb83a91..237d7b3d04 100644 --- a/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.jsx +++ b/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.jsx @@ -523,7 +523,8 @@ class FlexibleTable extends React.Component { const containerClass = this.props.showHeader ? "properties-ft-container-absolute " : "properties-ft-container-absolute-noheader "; const messageClass = (!this.props.messageInfo) ? containerClass + STATES.INFO : containerClass; - const ftHeaderClassname = "properties-ft-table-header"; + const ftHeaderClassname = classNames("properties-ft-table-header", + { "single-row-selection-table": this.props.rowSelection === ROW_SELECTION.SINGLE }); // When topRightPanel has Add button, it has this.props.topRightPanel.props.className = "properties-at-buttons-container" const topRightPanelHasTableToolbar = (typeof this.props.topRightPanel !== "undefined" && this.props.topRightPanel !== null && typeof this.props.topRightPanel.props.className === "undefined"); diff --git a/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.scss b/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.scss index f09700bbf7..8a46af3912 100644 --- a/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.scss +++ b/canvas_modules/common-canvas/src/common-properties/components/flexible-table/flexible-table.scss @@ -39,6 +39,11 @@ $row-left-padding: $spacing-02; height: $spacing-07; } +.single-row-selection-table { + display: flex; + justify-content: right; +} + .properties-ft-container-panel { height: calc(100% - #{$spacing-07}); // adjust height for table header } diff --git a/canvas_modules/common-canvas/src/common-properties/components/virtualized-table/virtualized-table.jsx b/canvas_modules/common-canvas/src/common-properties/components/virtualized-table/virtualized-table.jsx index d38252cfc4..cdd7e22ae2 100644 --- a/canvas_modules/common-canvas/src/common-properties/components/virtualized-table/virtualized-table.jsx +++ b/canvas_modules/common-canvas/src/common-properties/components/virtualized-table/virtualized-table.jsx @@ -378,7 +378,6 @@ class VirtualizedTable extends React.Component { let selectOption = ""; let selectedRow = false; const rowDisabled = typeof rowData.disabled === "boolean" ? rowData.disabled : false; - if (typeof this.props.rowHeight === "function" && this.props.rowHeight({ index }) === 0) { return null; } diff --git a/canvas_modules/common-canvas/src/common-properties/constants/constants.js b/canvas_modules/common-canvas/src/common-properties/constants/constants.js index 44cc39bd6e..45305b6cbc 100644 --- a/canvas_modules/common-canvas/src/common-properties/constants/constants.js +++ b/canvas_modules/common-canvas/src/common-properties/constants/constants.js @@ -127,6 +127,7 @@ export const MESSAGE_KEYS = { TABLE_TOOLBAR_BUTTON_DELETE: "table.toolbar.button.delete", TABLE_TOOLBAR_BUTTON_EDIT: "table.toolbar.button.edit", TABLE_TOOLBAR_BUTTON_CANCEL: "table.toolbar.button.cancel", + TABLE_DELETEICON_TOOLTIP: "table.deleteIcon.tooltip" }; diff --git a/canvas_modules/common-canvas/src/common-properties/controls/abstract-table.jsx b/canvas_modules/common-canvas/src/common-properties/controls/abstract-table.jsx index 4982014155..0681b3c04d 100644 --- a/canvas_modules/common-canvas/src/common-properties/controls/abstract-table.jsx +++ b/canvas_modules/common-canvas/src/common-properties/controls/abstract-table.jsx @@ -18,9 +18,11 @@ import React from "react"; import PropTypes from "prop-types"; import { Button, Checkbox } from "@carbon/react"; +import { TrashCan } from "@carbon/react/icons"; import FlexibleTable from "./../components/flexible-table"; import TableButtons from "./../components/table-buttons"; import SubPanelCell from "./../panels/sub-panel/cell.jsx"; +import Tooltip from "../../tooltip/tooltip.jsx"; import ReadonlyControl from "./readonly"; import * as PropertyUtils from "./../util/property-utils"; import classNames from "classnames"; @@ -429,7 +431,9 @@ export default class AbstractTable extends React.Component { } makeTableToolbar(selectedRows) { - if ((this.props.addRemoveRows || this.props.control?.moveableRows || this.isSelectSummaryEdit(selectedRows)) && selectedRows?.length > 0) { + if ((this.props.addRemoveRows || this.props.control?.moveableRows || this.isSelectSummaryEdit(selectedRows)) && + selectedRows?.length > 0 && + this.props.control.rowSelection !== ROW_SELECTION.SINGLE) { const multiSelectEditRowPropertyId = { name: this.selectSummaryPropertyName, row: 0 @@ -704,6 +708,9 @@ export default class AbstractTable extends React.Component { // set to specific size. Exclude this column from resizing. headers.push({ "key": "subpanel", "label": "", "width": TABLE_SUBPANEL_BUTTON_WIDTH, "staticWidth": true }); } + if (this.props.control.rowSelection === ROW_SELECTION.SINGLE) { + headers.push({ "key": "deleteRow", "label": "", "width": TABLE_SUBPANEL_BUTTON_WIDTH, "staticWidth": true }); + } return headers; } @@ -733,6 +740,34 @@ export default class AbstractTable extends React.Component { const cell = this.buildChildItem(propertyName, rowIndex, tableState); columns.push(cell); } + if (this.props.control.rowSelection === ROW_SELECTION.SINGLE) { + const toolTip = PropertyUtils.formatMessage(this.reactIntl, MESSAGE_KEYS.TABLE_DELETEICON_TOOLTIP); + const tooltipId = "tooltip-delete-row"; + const deleteOption = ( + +