Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Number visual element #1442

25 changes: 25 additions & 0 deletions doc/gui/examples/controls/number-step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed 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.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

value = 50

page = """
<|{value}|number|step=1|step_multiplier=6|>
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
"""

Gui(page).run()

81 changes: 71 additions & 10 deletions frontend/taipy-gui/src/components/Taipy/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,34 @@
* specific language governing permissions and limitations under the License.
*/

import React, { useState, useEffect, useCallback, useRef, KeyboardEvent } from "react";
import React, {useState, useEffect, useCallback, useRef, KeyboardEvent, useMemo} from "react";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import {styled} from '@mui/material/styles';
import IconButton from "@mui/material/IconButton";

import { createSendActionNameAction, createSendUpdateAction } from "../../context/taipyReducers";
import { TaipyInputProps } from "./utils";
import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
import {createSendActionNameAction, createSendUpdateAction} from "../../context/taipyReducers";
import {TaipyInputProps} from "./utils";
import {useClassNames, useDispatch, useDynamicProperty, useModule} from "../../utils/hooks";

const AUTHORIZED_KEYS = ["Enter", "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"];

const StyledTextField = styled(TextField)({
'& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button': {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we want to use WebKit or mozilla specific css...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do if this is mandatory for specific browsers.
I don't like it either though.

display: 'none',
},
'& input[type=number]': {
'-moz-appearance': 'textfield',
},
});

const getActionKeys = (keys?: string): string[] => {
const ak = (
keys
? keys
.split(";")
.map((v) => v.trim().toLowerCase())
.filter((v) => AUTHORIZED_KEYS.some((k) => k.toLowerCase() === v))
.split(";")
.map((v) => v.trim().toLowerCase())
.filter((v) => AUTHORIZED_KEYS.some((k) => k.toLowerCase() === v))
: []
).map((v) => AUTHORIZED_KEYS.find((k) => k.toLowerCase() == v) as string);
return ak.length > 0 ? ak : [AUTHORIZED_KEYS[0]];
Expand Down Expand Up @@ -79,6 +90,12 @@

const handleAction = useCallback(
(evt: KeyboardEvent<HTMLDivElement>) => {
if (evt.shiftKey && evt.key === 'ArrowUp') {
setValue(((Number(evt.currentTarget.querySelector("input")?.value || 0) + (props.step || 1) * (props.stepMultiplier || 10) - (props.step || 1)).toString()));
}
if (evt.shiftKey && evt.key === 'ArrowDown') {
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
setValue(((Number(evt.currentTarget.querySelector("input")?.value || 0) - (props.step || 1) * (props.stepMultiplier || 10) + (props.step || 1)).toString()));
}
if (!evt.shiftKey && !evt.ctrlKey && !evt.altKey && actionKeys.includes(evt.key)) {
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
const val = evt.currentTarget.querySelector("input")?.value;
if (changeDelay > 0 && delayCall.current > 0) {
Expand All @@ -92,9 +109,32 @@
evt.preventDefault();
}
},
[actionKeys, updateVarName, onAction, id, dispatch, onChange, changeDelay, propagate, module]
[actionKeys, props.step, props.stepMultiplier, changeDelay, onAction, dispatch, id, module, updateVarName, onChange, propagate]
);

const roundBasedOnStep = useMemo(() => {
const stepString = (props.step || 1).toString();
const decimalPlaces = stepString.includes('.') ? stepString.split('.')[1].length : 0;

Check warning on line 117 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
const multiplier = Math.pow(10, decimalPlaces);
return (value: number) => Math.round(value * multiplier) / multiplier;

Check warning on line 119 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 119 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
}, [props.step]);

const handleUpStepperMouseDown = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {

Check warning on line 122 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
if (event.shiftKey) {
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
setValue(prevValue => roundBasedOnStep(Number(prevValue) + (props.step || 1) * (props.stepMultiplier || 10)).toString());

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 124 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
} else {
setValue(prevValue => roundBasedOnStep(Number(prevValue) + (props.step || 1)).toString())

Check warning on line 126 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 126 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 126 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 126 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 126 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
}

Check warning on line 127 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 127 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 127 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}, [props.step, props.stepMultiplier, roundBasedOnStep])

const handleDownStepperMouseDown = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {

Check warning on line 130 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
if (event.shiftKey) {
setValue(prevValue => roundBasedOnStep(Number(prevValue) - (props.step || 1) * (props.stepMultiplier || 10)).toString());

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 132 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
} else {
setValue(prevValue => roundBasedOnStep(Number(prevValue) - (props.step || 1)).toString())

Check warning on line 134 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 134 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 134 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 134 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 134 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
}

Check warning on line 135 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 135 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 135 in frontend/taipy-gui/src/components/Taipy/Input.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}, [props.step, props.stepMultiplier, roundBasedOnStep])

useEffect(() => {
if (props.value !== undefined) {
setValue(props.value);
Expand All @@ -103,13 +143,34 @@

return (
<Tooltip title={hover || ""}>
<TextField
<StyledTextField
margin="dense"
hiddenLabel
value={value ?? ""}
value={value}
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
className={className}
type={type}
id={id}
inputProps={{
step: props.step ? props.step : 1,
}}
InputProps={{
FredLL-Avaiga marked this conversation as resolved.
Show resolved Hide resolved
endAdornment: (
<>
<IconButton
size="small"
onMouseDown={handleUpStepperMouseDown}
>
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
</IconButton>
<IconButton
size="small"
onMouseDown={handleDownStepperMouseDown}
>
</IconButton>
</>
),
}}
label={props.label}
onChange={handleInput}
disabled={!active}
Expand Down
2 changes: 2 additions & 0 deletions frontend/taipy-gui/src/components/Taipy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export interface TaipyInputProps extends TaipyActiveProps, TaipyChangeProps, Tai
type: string;
value: string;
defaultValue?: string;
step?: number;
stepMultiplier?: number;
changeDelay?: number;
onAction?: string;
actionKeys?: string;
Expand Down
2 changes: 2 additions & 0 deletions taipy/gui/_renderers/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ class _Factory:
.set_attributes(
[
("active", PropertyType.dynamic_boolean, True),
("step", PropertyType.number),
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
("step_multiplier", PropertyType.number),
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
("hover_text", PropertyType.dynamic_string),
("on_change", PropertyType.function),
("on_action", PropertyType.function),
Expand Down
12 changes: 12 additions & 0 deletions taipy/gui/viselements.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@
"type": "str",
"default_value": "None",
"doc": "The label associated with the input."
},
{
"name": "step",
"type": "int|float",
"default_value": "1",
"doc": "The amount that the value changes on each increment or decrement."
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
},
{
"name": "step_multiplier",
"type": "int|float",
"default_value": "10",
"doc": "The amount that the value changes on each increment or decrement when the shift key is pressed."
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
}
]
}
Expand Down
Loading