From f67b93a7913eac9ed4b828c76128b8d08397b0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fred=20Lef=C3=A9v=C3=A8re-Laoide?= <90181748+FredLL-Avaiga@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:24:34 +0100 Subject: [PATCH] reset file selector input after upload (#2280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * reset file selector input after upload disable number input arrows resolves #2103 * lint --------- Co-authored-by: Fred Lefévère-Laoide --- .../src/components/Taipy/FileSelector.tsx | 42 +++++++++++++------ .../src/components/Taipy/Input.spec.tsx | 30 ++++++------- .../taipy-gui/src/components/Taipy/Input.tsx | 37 ++++++++-------- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx b/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx index eada2b37c6..14facaca66 100644 --- a/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx +++ b/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx @@ -22,20 +22,19 @@ import React, { useRef, useState, } from "react"; +import UploadFile from "@mui/icons-material/UploadFile"; import Button from "@mui/material/Button"; import LinearProgress from "@mui/material/LinearProgress"; import Tooltip from "@mui/material/Tooltip"; -import UploadFile from "@mui/icons-material/UploadFile"; +import { SxProps } from "@mui/material"; +import { nanoid } from "nanoid"; import { TaipyContext } from "../../context/taipyContext"; import { createNotificationAction, createSendActionNameAction } from "../../context/taipyReducers"; import { useClassNames, useDynamicProperty, useModule } from "../../utils/hooks"; -import { expandSx, getCssSize, noDisplayStyle, TaipyActiveProps } from "./utils"; import { uploadFile } from "../../workers/fileupload"; -import { SxProps } from "@mui/material"; import { getComponentClassName } from "./TaipyStyle"; - - +import { expandSx, getCssSize, noDisplayStyle, TaipyActiveProps } from "./utils"; interface FileSelectorProps extends TaipyActiveProps { onAction?: string; @@ -75,22 +74,27 @@ const FileSelector = (props: FileSelectorProps) => { notify = true, withBorder = true, } = props; - const directoryProps = ["d", "dir", "directory", "folder"].includes(selectionType?.toLowerCase()) ? - {webkitdirectory: "", directory: "", mozdirectory: "", nwdirectory: ""} : - undefined; const [dropLabel, setDropLabel] = useState(""); const [dropSx, setDropSx] = useState(defaultSx); const [upload, setUpload] = useState(false); const [progress, setProgress] = useState(0); const { state, dispatch } = useContext(TaipyContext); const butRef = useRef(null); - const inputId = useMemo(() => (id || `tp-${Date.now()}-${Math.random()}`) + "-upload-file", [id]); + const inputId = useMemo(() => (id || `tp-${nanoid()}`) + "-upload-file", [id]); const module = useModule(); const className = useClassNames(props.libClassName, props.dynamicClassName, props.className); const active = useDynamicProperty(props.active, props.defaultActive, true); const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined); + const directoryProps = useMemo( + () => + selectionType.toLowerCase().startsWith("d") || "folder" == selectionType.toLowerCase() + ? { webkitdirectory: "", directory: "", mozdirectory: "", nwdirectory: "" } + : undefined, + [selectionType] + ); + useEffect( () => setDropSx((sx: SxProps | undefined) => @@ -123,20 +127,34 @@ const FileSelector = (props: FileSelectorProps) => { onAction && dispatch(createSendActionNameAction(id, module, onAction)); notify && dispatch( - createNotificationAction({ atype: "success", message: value, system: false, duration: 3000 }) + createNotificationAction({ + atype: "success", + message: value, + system: false, + duration: 3000, + }) ); + const fileInput = document.getElementById(inputId) as HTMLInputElement; + fileInput && (fileInput.value = ""); }, (reason) => { setUpload(false); notify && dispatch( - createNotificationAction({ atype: "error", message: reason, system: false, duration: 3000 }) + createNotificationAction({ + atype: "error", + message: reason, + system: false, + duration: 3000, + }) ); + const fileInput = document.getElementById(inputId) as HTMLInputElement; + fileInput && (fileInput.value = ""); } ); } }, - [state.id, id, onAction, props.onUploadAction, props.uploadData, notify, updateVarName, dispatch, module] + [state.id, id, onAction, props.onUploadAction, props.uploadData, notify, updateVarName, dispatch, module, inputId] ); const handleChange = useCallback( diff --git a/frontend/taipy-gui/src/components/Taipy/Input.spec.tsx b/frontend/taipy-gui/src/components/Taipy/Input.spec.tsx index 0c2df8cc3a..322117c8de 100644 --- a/frontend/taipy-gui/src/components/Taipy/Input.spec.tsx +++ b/frontend/taipy-gui/src/components/Taipy/Input.spec.tsx @@ -173,6 +173,19 @@ describe("Input Component", () => { const visibilityButton = getByLabelText("toggle password visibility"); expect(visibilityButton).toBeInTheDocument(); }); + it("should prevent default action when mouse down event occurs on password visibility button", async () => { + const { getByLabelText } = render(); + const visibilityButton = getByLabelText("toggle password visibility"); + const keyDown = createEvent.mouseDown(visibilityButton); + fireEvent(visibilityButton, keyDown); + expect(keyDown.defaultPrevented).toBe(true); + }); + it("parses actionKeys correctly", () => { + const { rerender } = render(); + rerender(); + rerender(); + rerender(); + }); }); describe("Number Component", () => { @@ -195,9 +208,11 @@ describe("Number Component", () => { getByDisplayValue("1"); }); it("is disabled", async () => { - const { getByDisplayValue } = render(); + const { getByDisplayValue, getByLabelText } = render(); const elt = getByDisplayValue("33"); expect(elt).toBeDisabled(); + const upSpinner = getByLabelText("Increment value"); + expect(upSpinner).toBeDisabled(); }); it("is enabled by default", async () => { const { getByDisplayValue } = render(); @@ -309,12 +324,6 @@ describe("Number Component", () => { await user.keyboard("[ArrowDown]"); expect(elt.value).toBe("0"); }); - it("parses actionKeys correctly", () => { - const { rerender } = render(); - rerender(); - rerender(); - rerender(); - }); it("it should not decrement below the min value", () => { const { getByLabelText } = render(); const downSpinner = getByLabelText("Decrement value"); @@ -333,11 +342,4 @@ describe("Number Component", () => { expect(inputElement.value).toBe("20"); }); }); - it("should prevent default action when mouse down event occurs on password visibility button", async () => { - const { getByLabelText } = render(); - const visibilityButton = getByLabelText("toggle password visibility"); - const keyDown = createEvent.mouseDown(visibilityButton); - fireEvent(visibilityButton, keyDown); - expect(keyDown.defaultPrevented).toBe(true); - }); }); diff --git a/frontend/taipy-gui/src/components/Taipy/Input.tsx b/frontend/taipy-gui/src/components/Taipy/Input.tsx index dd4ec43473..436669193c 100644 --- a/frontend/taipy-gui/src/components/Taipy/Input.tsx +++ b/frontend/taipy-gui/src/components/Taipy/Input.tsx @@ -277,6 +277,7 @@ const Input = (props: TaipyInputProps) => { aria-label="Increment value" size="small" onMouseDown={handleUpStepperMouseDown} + disabled={!active} > @@ -284,6 +285,7 @@ const Input = (props: TaipyInputProps) => { aria-label="Decrement value" size="small" onMouseDown={handleDownStepperMouseDown} + disabled={!active} > @@ -309,6 +311,7 @@ const Input = (props: TaipyInputProps) => { } : undefined, [ + active, type, step, min, @@ -330,23 +333,23 @@ const Input = (props: TaipyInputProps) => { return ( <> - - {props.children} + + {props.children} );