瀏覽代碼

Merge branch 'develop' into bug/#1911-current-page-menu

namnguyen 7 月之前
父節點
當前提交
50780e7824
共有 32 個文件被更改,包括 1132 次插入262 次删除
  1. 11 13
      CODE_OF_CONDUCT.md
  2. 205 64
      CONTRIBUTING.md
  3. 128 116
      INSTALLATION.md
  4. 27 19
      README.md
  5. 0 20
      contributors.txt
  6. 25 0
      doc/gui/examples/controls/time_analog_picker.py
  7. 25 0
      doc/gui/examples/controls/time_format.py
  8. 25 0
      doc/gui/examples/controls/time_simple.py
  9. 31 0
      doc/gui/examples/controls/time_styling.py
  10. 1 1
      frontend/taipy-gui/base/src/packaging/package.json
  11. 1 1
      frontend/taipy-gui/dom/package.json
  12. 1 1
      frontend/taipy-gui/package.json
  13. 1 1
      frontend/taipy-gui/packaging/package.json
  14. 10 0
      frontend/taipy-gui/src/components/Taipy/DateRange.spec.tsx
  15. 11 1
      frontend/taipy-gui/src/components/Taipy/DateRange.tsx
  16. 9 0
      frontend/taipy-gui/src/components/Taipy/DateSelector.spec.tsx
  17. 26 13
      frontend/taipy-gui/src/components/Taipy/DateSelector.tsx
  18. 235 0
      frontend/taipy-gui/src/components/Taipy/TimeSelector.spec.tsx
  19. 122 0
      frontend/taipy-gui/src/components/Taipy/TimeSelector.tsx
  20. 2 0
      frontend/taipy-gui/src/components/Taipy/index.ts
  21. 1 1
      frontend/taipy-gui/src/components/Taipy/tableUtils.tsx
  22. 11 0
      frontend/taipy-gui/src/utils/index.ts
  23. 1 1
      frontend/taipy/package.json
  24. 23 0
      taipy/gui/_renderers/factory.py
  25. 2 0
      taipy/gui/types.py
  26. 1 0
      taipy/gui/utils/__init__.py
  27. 17 0
      taipy/gui/utils/types.py
  28. 57 0
      taipy/gui/viselements.json
  29. 17 10
      tests/conftest.py
  30. 13 0
      tests/gui/control/test_date.py
  31. 16 0
      tests/gui/control/test_date_range.py
  32. 77 0
      tests/gui/control/test_time.py

+ 11 - 13
CODE_OF_CONDUCT.md

@@ -1,6 +1,4 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
+# Our Pledge
 
 We as members, contributors, and leaders pledge to make participation in our
 community a harassment-free experience for everyone, regardless of age, body
@@ -12,7 +10,7 @@ and orientation.
 We pledge to act and interact in ways that contribute to an open, welcoming,
 diverse, inclusive, and healthy community.
 
-## Our Standards
+# Our Standards
 
 Examples of behavior that contributes to a positive environment for our
 community include:
@@ -36,7 +34,7 @@ Examples of unacceptable behavior include:
 * Other conduct which could reasonably be considered inappropriate in a
   professional setting.
 
-## Enforcement Responsibilities
+# Enforcement Responsibilities
 
 Community leaders are responsible for clarifying and enforcing our standards of
 acceptable behavior and will take appropriate and fair corrective action in
@@ -48,7 +46,7 @@ comments, commits, code, wiki edits, issues, and other contributions that are
 not aligned to this Code of Conduct, and will communicate reasons for moderation
 decisions when appropriate.
 
-## Scope
+# Scope
 
 This Code of Conduct applies within all community spaces, and also applies when
 an individual is officially representing the community in public spaces.
@@ -56,7 +54,7 @@ Examples of representing our community include using an official e-mail address,
 posting via an official social media account, or acting as an appointed
 representative at an online or offline event.
 
-## Enforcement
+# Enforcement
 
 Instances of abusive, harassing, or otherwise unacceptable behavior may be
 reported to the community leaders responsible for enforcement at
@@ -66,12 +64,12 @@ All complaints will be reviewed and investigated promptly and fairly.
 All community leaders are obligated to respect the privacy and security of the
 reporter of any incident.
 
-## Enforcement Guidelines
+# Enforcement Guidelines
 
 Community leaders will follow these Community Impact Guidelines in determining
 the consequences for any action they deem in violation of this Code of Conduct:
 
-### 1. Correction
+## 1. Correction
 
 **Community Impact**: Use of inappropriate language or other behavior deemed
 unprofessional or unwelcome in the community.
@@ -80,7 +78,7 @@ unprofessional or unwelcome in the community.
 clarity about the nature of the violation and an explanation of why the
 behavior was inappropriate. A public apology may be requested.
 
-### 2. Warning
+## 2. Warning
 
 **Community Impact**: A violation through a single incident or series
 of actions.
@@ -92,7 +90,7 @@ includes avoiding interactions in community spaces as well as external channels
 like social media. Violating these terms may lead to a temporary or
 permanent ban.
 
-### 3. Temporary Ban
+## 3. Temporary Ban
 
 **Community Impact**: A serious violation of community standards, including
 sustained inappropriate behavior.
@@ -103,7 +101,7 @@ private interaction with the people involved, including unsolicited interaction
 with those enforcing the Code of Conduct, is allowed during this period.
 Violating these terms may lead to a permanent ban.
 
-### 4. Permanent Ban
+## 4. Permanent Ban
 
 **Community Impact**: Demonstrating a pattern of violation of community
 standards, including sustained inappropriate behavior,  harassment of an
@@ -112,7 +110,7 @@ individual, or aggression toward or disparagement of classes of individuals.
 **Consequence**: A permanent ban on any sort of public interaction within
 the community.
 
-## Attribution
+# Attribution
 
 This Code of Conduct is adapted from the [Contributor Covenant][homepage],
 version 2.0, available at

+ 205 - 64
CONTRIBUTING.md

@@ -1,38 +1,42 @@
-# Contributions
+# General contributions
 
-Thanks for your interest in helping improve Taipy! Contributions are welcome, and they are greatly appreciated!
-Every little help and credit will always be given.
+Thanks for your interest in helping improve Taipy! Contributions are welcome, and they are greatly
+appreciated! Every little help and credit will always be given.
 
-There are multiple ways to contribute to Taipy: code, but also reporting bugs, creating feature requests, helping
-other users in our forums, [stack**overflow**](https://stackoverflow.com/), etc.
+There are multiple ways to contribute to Taipy: code, but also reporting bugs, creating feature
+requests, helping other users in our forums, [stack**overflow**](https://stackoverflow.com/), etc.
 
-For questions, please get in touch on [Discord](https://discord.com/invite/SJyz2VJGxV) or on GitHub with a discussion or an issue.
+For questions, please get in touch on [Discord](https://discord.com/invite/SJyz2VJGxV) or on GitHub
+with a discussion or an issue.
 
-## Code organisation
+## Project organisation
 
 Taipy is organised in two main repositories:
 
-- [taipy](https://github.com/Avaiga/taipy) is the main repository that contains the code of Taipy packages.
+- [taipy](https://github.com/Avaiga/taipy) is the main repository that contains the code of Taipy
+    packages.
 - [taipy-doc](https://github.com/Avaiga/taipy-doc) is the documentation repository.
 
 ## Never contributed to an open source project before ?
 
-Have a look at this [GitHub documentation](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
+Have a look at this
+[GitHub documentation](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
 
 ## Report bugs
 
 Reporting bugs is through [GitHub issues](https://github.com/Avaiga/taipy/issues).
 
-Please report relevant information and preferably code that exhibits the problem. We provide templates to help you
-describe the issue.
+Please report relevant information and preferably code that exhibits the problem. We provide
+templates to help you describe the issue.
 
-The Taipy team will analyze and try to reproduce the bug to provide feedback. If confirmed, we will add a priority
-to the issue and add it in our backlog. Feel free to propose a pull request to fix it.
+The Taipy team will analyze and try to reproduce the bug to provide feedback. If confirmed,
+we will add a priority to the issue and add it in our backlog. Feel free to propose a pull
+request to fix it.
 
 ## Issue reporting, feedback, proposal, design or any other comment
 
-Any feedback or proposal is greatly appreciated! Do not hesitate to create an issue with the appropriate template on
-[GitHub](https://github.com/Avaiga/taipy/issues).
+Any feedback or proposal is greatly appreciated! Do not hesitate to create an issue with the
+appropriate template on [GitHub](https://github.com/Avaiga/taipy/issues).
 
 The Taipy team will analyse your issue and return to you as soon as possible.
 
@@ -41,19 +45,35 @@ The Taipy team will analyse your issue and return to you as soon as possible.
 Do not hesitate to create an issue or pull request directly on the
 [taipy-doc repository](https://github.com/Avaiga/taipy-doc).
 
-## Implement Features
+# Code contributions
 
+## Code organization
+
+The Taipy source code is located in the [taipy](https://github.com/Avaiga/taipy)
+repository, in the `taipy` directory.
+
+Packages sources are organized in subdirectories from there:
+
+- `taipy-common`
+- `taipy-core`
+- `taipy-gui`
+- `taipy-rest`
+- `taipy-templates`
+
+## Process and workflow
+
+### Issue assignment
 The Taipy team manages its backlog in private. Each issue that is or is going to be engaged by the
-Taipy team is attached to the "🔒 Staff only" label or has already been assigned to a Taipy team member.
-Please, do not work on it, the Taipy team is on it.
+Taipy team is attached to the "🔒 Staff only" label or has already been assigned to a Taipy team
+member. Please, do not work on it, the Taipy team is on it.
 
 All other issues are sorted by labels and are available for a contribution. If you are new to the
-project, you can start with the "good first issue" or "🆘 Help wanted" label. You can also start with
-issues with higher priority, like "Critical" or "High". The higher the priority, the more value it
-will bring to Taipy.
+project, you can start with the "good first issue" or "🆘 Help wanted" label. You can also start
+with issues with higher priority, like "Critical" or "High". The higher the priority, the more
+value it will bring to Taipy.
 
-If you want to work on an issue, please add a comment and wait to be assigned to the issue to inform
-the community that you are working on it.
+If you want to work on an issue, please add a comment and wait to be assigned to the issue to
+inform the community that you are working on it. Then, follow the steps below:
 
 ### Contribution workflow
 
@@ -61,36 +81,48 @@ the community that you are working on it.
    targeted by the issue. Clone it on your local machine, then go inside the directory.
 
 2. We are working with [Pipenv](https://github.com/pypa/pipenv) for our virtualenv.
-   Create a local env and install development package by running `$ pipenv install --dev`, then run tests with
-   `$ pipenv run pytest` to verify your setup.
+   Create a local env and install development package by running `$ pipenv install --dev`, then
+   run tests with `$ pipenv run pytest` to verify your setup.
 
 3. For convention help, we provide a [pre-commit](https://pre-commit.com/hooks.html) file.
-   This tool will run before each commit and will automatically reformat code or raise warnings and errors based on the
-   code format or Python typing.
-   You can install and set it up by doing:
+   This tool will run before each commit and will automatically reformat code or raise warnings
+   and errors based on the code format or Python typing. You can install and set it up by doing:
    ```bash
    $ pipenv install pre-commit
    $ pipenv run python -m pre-commit install
    ```
 
-4. Make the changes.<br/>
-   You may want to also add your GitHub login as a new line of the `contributors.txt` file located at the root
-   of this repository. We are using it to list our contributors in the Taipy documentation
-   (see the "Contributing > Contributors" section) and thank them.
-
-5. Create a [pull request from your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork).<br/>
+4. Create a [pull request from your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork).<br/>
    Keep your pull request as __draft__ until your work is finished.
    Do not hesitate to add a comment for help or questions.
-   Before you submit a pull request for review from your forked repo, check that it meets these guidelines:
-     - The code and the branch name follow the [Taipy coding style](#coding-style-and-best-practices).
+   Before you submit a pull request for review from your forked repo, check that it meets these
+   guidelines:
+     - The code and the branch name follow the
+         [Taipy coding style](#coding-style-and-best-practices).
      - Include tests.
      - Code is [rebased](http://stackoverflow.com/a/7244456/1110993).
      - License is present.
      - pre-commit works - without mypy errors.
      - Taipy tests are passing.
 
-6. The Taipy team will have a look at your Pull Request and will give feedback. If every requirement is valid, your
-   work will be added in the next release, congratulations!
+5. The Taipy team will have a look at your Pull Request and will give feedback. If every
+    requirement is valid, your work will be added in the next release, congratulations!
+
+### Issues or Pull requests inactivity
+
+- If your PR is not created or there is no other activity within 14 days of being assigned to
+    the issue, a warning message will appear on the issue, and the issue will be marked as
+    "🥶Waiting for contributor".
+- If your issue is marked as "🥶Waiting for contributor", you will be unassigned after 14
+    days of inactivity.
+- Similarly, if there is no activity within 14 days of your PR, the PR will be marked as
+    "🥶Waiting for contributor".
+- If your PR is marked as "🥶Waiting for contributor", it will be closed after 14 days of
+    inactivity.
+
+We do this in order to keep our backlog moving quickly. Please don't take it personally if your
+issue or PR gets closed because of this 14-day inactivity time limit. You can always reopen the
+issue or PR if you're still interested in working on it.
 
 ## Coding style and best practices
 
@@ -99,15 +131,16 @@ the community that you are working on it.
 Taipy's repositories follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) and
 [PEP 484](https://www.python.org/dev/peps/pep-0484/) coding convention.
 
-## TypeScript
+### TypeScript
 
 Taipy's repositories use the [ESLint](https://eslint.org/) and
 [TypeScript ESLint](https://github.com/typescript-eslint/typescript-eslint) plugin to ensure a common set of rules.
 
 ### Git branches
 
-All new development happens in the `develop` branch. All pull requests should target that branch.
-We are following a strict branch naming convention based on the pattern: `<type>/#<issueId>[IssueSummary]`.
+All new development happens in the `develop` branch. All pull requests should target that
+branch. We are following a strict branch naming convention based on the pattern:
+`<type>/#<issueId>[IssueSummary]`.
 
 Where:
 
@@ -118,31 +151,139 @@ Where:
     - refactor: refactor of a piece of code.
     - doc: doc changes (complement or typo fixes…).
     - build: in relation with the build process.
-- `<issueId>` is the processed issue identifier. The advantage of explicitly indicating the issue number is that in
-  GitHub, a pull request page shows a direct link to the issue description.
-- `[IssueSummary]` is a short summary of the issue topic, not including spaces, using Camel case or lower-case,
-  dash-separated words. This summary, with its dash (‘-’) symbol prefix, is optional.
-
-## Important Notes
-
-- If your PR is not created or there is no other activity within 14 days of being assigned to the issue, a warning message will appear on the issue, and the issue will be marked as "🥶Waiting for contributor".
-- If your issue is marked as "🥶Waiting for contributor", you will be unassigned after 14 days of inactivity.
-- Similarly, if there is no activity within 14 days of your PR, the PR will be marked as "🥶Waiting for contributor".
-- If your PR is marked as "🥶Waiting for contributor", it will be closed after 14 days of inactivity.
-
-We do this in order to keep our backlog moving quickly. Please don't take it personally if your issue or PR gets closed
-because of this 14-day inactivity time limit. You can always reopen the issue or PR if you're still interested in working
-on it.
+- `<issueId>` is the processed issue identifier. The advantage of explicitly indicating the issue
+    number is that inGitHub, a pull request page shows a direct link to the issue description.
+- `[IssueSummary]` is a short summary of the issue topic, not including spaces, using Camel case
+    or lower-case, dash-separated words. This summary, with its dash (‘-’) symbol prefix, is
+    optional.
 
 ## Dependency management
 
-Taipy comes with multiple optional packages. You can find the list directly in the product or Taipy's packages.
-The back-end Pipfile does not install optional packages by default due to `pyodbc` requiring a driver's manual
-installation. This is not the behaviour for the front-end that installs all optional packages through its Pipfile.
+Taipy comes with multiple optional packages. You can find the list directly in the product or
+Taipy's packages. The back-end Pipfile does not install optional packages by default due to
+`pyodbc` requiring a driver's manual installation. This is not the behaviour for the front-end
+that installs all optional packages through its Pipfile.
+
+If you are a contributor on Taipy, be careful with dependencies, do not forget to install or
+uninstall depending on your issue.
+
+If you need to add a new dependency to Taipy, do not forget to add it in the `Pipfile` and the
+`setup.py`. Keep in mind that dependency is a vector of attack. The Taipy team limits the usage
+of external dependencies at the minimum.
+
+## Installing the development kit
+
+If you need the source code for Taipy on your system to see how things are done or maybe
+contribute to the improvement of the packages, you can set your environment up by following
+the steps below.
 
-If you are a contributor on Taipy, be careful with dependencies, do not forget to install or uninstall depending on
-your issue.
+### Prerequisites
+Before installing the Taipy development kit, ensure you have
+[Python](http://docs.python-guide.org/en/latest/starting/installation/) (**version 3.9 or later**),
+[pip](https://pip.pypa.io/en/latest/installation/), and
+[git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed on your system.
+
+??? note "On Mac OS M1 pro"
 
-If you need to add a new dependency to Taipy, do not forget to add it in the `Pipfile` and the `setup.py`.
-Keep in mind that dependency is a vector of attack. The Taipy team limits the usage of external dependencies at the
-minimum.
+    If you are using a Mac OS M1 pro, you may need to install the `libmagic` library before.
+    Please run the commands below:
+    ```bash
+    brew install libmagic
+    pip install python-libmagic
+    ```
+
+### Cloning the repository
+
+First, clone the Taipy repository from GitHub using the following command:
+
+```bash
+git clone https://github.com/Avaiga/taipy.git
+```
+
+This creates the 'taipy' directory holding all the package's source code.
+
+### Building the JavaScript bundles
+
+Taipy (and Taipy GUI) includes client-side code for web applications, written in
+[TypeScript](https://www.typescriptlang.org/), and uses [React](https://reactjs.org/).
+The code is packaged into JavaScript bundles that are sent to browsers when accessing
+Taipy applications with a graphical interface.
+
+There are two main JavaScript bundles to build:
+- Taipy GUI: Contains the web application, the pages and all standard visual elements.
+- Taipy: Contains specific visual elements for Taipy back-end functionalities
+    (Scenario Management).
+
+**Prerequisites**: To build the JavaScript bundles, ensure you have [Node.js](https://nodejs.org/)
+version 18 or higher installed. Node.js includes the
+[`npm` package manager](https://www.npmjs.com/).
+
+The build process is explained in the
+[Taipy GUI front-end](https://github.com/Avaiga/taipy/blob/develop/frontend/taipy-gui/README.md)
+and
+[Taipy front-end](https://github.com/Avaiga/taipy/blob/develop/frontend/taipy/README.md) README
+files. Build the Taipy GUI bundle first, as the Taipy front-end depends on it.
+
+**Build instructions:** Run the following commands from the root directory of the repository:
+
+```bash
+# Build the Taipy GUI bundle
+cd frontend/taipy-gui
+cd dom
+npm i
+cd ..
+npm i
+npm run build
+#
+# Build the Taipy front-end bundle
+cd ../taipy # Current directory is [taipy-dir]/frontend/taipy
+npm i
+npm run build
+```
+
+These commands will create the `taipy/gui/webapp` and `taipy/gui_core/lib` directories in the root
+folder of the taipy repository.
+
+### Debugging the JavaScript bundles
+
+If you plan to modify the front-end code and need to debug the TypeScript code, you must use the
+following instead of the *standard* build option:
+```bash
+npm run build:dev
+```
+
+This will preserve the debugging symbols, and you will be able to navigate in the TypeScript code
+from your debugger.
+
+!!!note "Web application location"
+    When you are developing front-end code for the Taipy GUI package, it may be cumbersome to have
+    to install the package over and over when you know that all that has changed is the JavaScript
+    bundle that makes the Taipy web app.
+
+    By default, the Taipy GUI application searches for the front-end code in the
+    `[taipy-gui-package-dir]/taipy/gui/webapp` directory.
+    You can, however, set the environment variable `TAIPY_GUI_WEBAPP_PATH` to the location of your
+    choice, and Taipy GUI will look for the web app in that directory.
+    If you set this variable to the location where you build the web app repeatedly, you will no
+    longer have to reinstall Taipy GUI before you try your code again.
+
+### Running the tests
+
+The Taipy package includes a test suite to ensure the package's functionality is correct.
+The tests are written using the [pytest](https://docs.pytest.org/en/latest/) framework.
+They are located in the `tests` directory of the package.
+
+To run the tests, you need to install the required development packages. We recommend using
+[Pipenv](https://pipenv.pypa.io/en/latest/) to create a virtual environment and install the
+development packages.
+
+```bash
+pip install pipenv
+pipenv install --dev
+```
+
+Then you can run the tests with the following command:
+
+```bash
+pipenv run pytest
+```

+ 128 - 116
INSTALLATION.md

@@ -1,159 +1,171 @@
-# Taipy Installation Guide
+Welcome to the installation section of the Taipy web application builder! This section will
+guide you through the seamless and straightforward process of setting up and deploying your
+own powerful web applications.
 
-Taipy can be installed in various ways depending on your needs. 
+!!! note "Installation for Contributing to Taipy"
 
-If you aim to modify the code or contribute to its development, refer to the 
+    If you aim to modify the Taipy source code or contribute to its development, please refer
+    to the contributing page to get more information.
 
-[source installation](#installing-for-development) section. 
+# Installing Taipy library
 
-Ensure that you have Python 3.9 or above installed on your system.
+## Prerequisite
 
-## Installing Taipy from PyPI
+Before installing Taipy, ensure you have Python (**version 3.9 or later**) and
+[pip](https://pip.pypa.io) installed on your system. If you don't have pip installed, you can
+follow these steps to install it:
 
-The easiest way to install Taipy is through the 
+1. **[Python Installation Guide](http://docs.python-guide.org/en/latest/starting/installation/)**:
+    Follow the installation guide to set up Python on your system.
+    After the installation, you can open the Command Prompt and type `python --version` to check
+    the installed Python version.
 
-[PyPI software repository](https://pypi.org/project/taipy/). 
+2. **[Installing pip](https://pip.pypa.io/en/latest/installation/)**: Pip is included by default
+    if you use Python 3.4 or later. Otherwise, you can follow the official
+    installation page of pip to install it. To verify the installation, type `pip --version` or
+    `pip3 --version`.
 
-You can do this by running the following command:
+Alternatively, if you are using a Conda environment, you can install pip using the following
+command:
+```console
+conda install pip
+```
 
-```bash
+## The latest stable release from Pypi
+
+### Pip
+The preferred method to install Taipy is by using **pip**. After downloading Taipy package
+from **[PyPI repository](https://pypi.org/project/taipy/)** the following commands install
+it in the Python environment with all its dependencies. Open your terminal or command prompt
+and run the following command:
+```console
 pip install taipy
 ```
 
-If you are using a virtual environment, use:
-
-```bash
+### Pipenv
+If you are using a virtual environment with **[Pipenv](https://pipenv.pypa.io/en/latest/)**,
+use the following command:
+```console
 pipenv install taipy
 ```
 
-Alternatively, you can use `venv` to create a virtual environment:
-```bash
-python -m venv myenv
-source myenv/bin/activate  # On Windows use `myenv\Scripts\activate`
+### Venv
+Alternatively, you can use `venv` to create a virtual environment. Please run the following
+commands replacing `<myenv>` (twice) with your desired environment name:
+```console
+python -m venv <myenv>
+source myenv/bin/activate  # On Windows use `<myenv>\Scripts\activate`
 pip install taipy
 ```
 
-These commands install the `taipy` package in the Python environment with all its
-dependencies.
+### Conda
+If you prefer to work within a [Conda](https://docs.conda.io/projects/conda/en/latest/index.html)
+environment, you can install Taipy using the following commands replacing `<myenv>` with your
+desired environment name:
+```console
+conda create -n <myenv>
+conda activate <myenv>
+pip install taipy
+```
 
-## Installing a Specific Version from PyPI
+## A specific version from PyPI
 
-To install a specific version of Taipy, use the following command:
-```bash
+### Pip
+To install a specific version of Taipy, use the following command replacing `<version>` with a
+specific version number of Taipy among the
+**[list of all released Taipy versions](https://pypi.org/project/taipy/#history)**:
+```console
 pip install taipy==<version>
 ```
-Replace `<version>` with a specific version number of Taipy.
-The list of all released Taipy versions can be found [here](https://pypi.org/project/taipy/#history).
-
-## Installing from GitHub
-
-The development version of Taipy is updated daily with changes from the Taipy R&D and external
-contributors whom we praise for their input.
 
-The development version of Taipy can be installed using *pip* and *git*:
-
-```bash
-pip install git+https://git@github.com/Avaiga/taipy
+### Pipenv
+If you are using a virtual environment with **[Pipenv](https://pipenv.pypa.io/en/latest/)**,
+use the following command:
+```console
+pipenv install taipy==<version>
 ```
 
-## Installing for development
-
-If you need the source code for Taipy on your system so you can see how things are done or maybe
-participate in the improvement of the packages, you can clone the GitHub repository:
+### Venv
+Alternatively, you can use `venv` to create a virtual environment:
+```console
+python -m venv myenv
+source myenv/bin/activate  # On Windows use `myenv\Scripts\activate`
+pip install taipy==<version>
+```
 
-```bash
-git clone https://github.com/Avaiga/taipy.git
+### Conda
+If you prefer to work within a [Conda](https://docs.conda.io/projects/conda/en/latest/index.html)
+environment, you can install Taipy using the following commands replacing `<myenv>` with your
+desired environment name:
+```console
+conda create -n <myenv>
+conda activate <myenv>
+pip install taipy==<version>
 ```
 
-This creates the 'taipy' directory holding all the package's source code.
+## A development version from GitHub
 
-### Building the JavaScript bundles
+### Pip
+The development version of Taipy is hosted on
+**[GitHub repository](https://git@github.com/Avaiga/taipy)** using the `develop` branch. This
+branch is updated daily with changes from the Taipy R&D team and external contributors whom we
+praise for their input.
 
-Taipy (and Taipy GUI that it embeds) has some code dealing with the client side of the web
-applications.<br/>
-This code is written in [TypeScript](https://www.typescriptlang.org/), relies on
-[React](https://reactjs.org/) components, and is packaged into JavaScript bundles that are sent to
-browsers when they connect to all Taipy applications that have a graphical interface.
+The development version of Taipy can be installed using **pip**. After downloading Taipy source
+code from the **[GitHub repository](https://git@github.com/Avaiga/taipy)** the following commands
+build the package, and install it in the Python environment with all its dependencies.
 
-There are two main JavaScript bundles that can be built:
-- Taipy GUI: All the graphical interfaces that Taipy GUI can generate are based on a set of
-  generated files, including the web application and all the predefined visual elements.
-- Taipy: A set of visual elements dedicated to Scenario Management.
+Open your terminal or command prompt and run the following command:
 
-**Prerequisites**: If you need to build the JavaScript bundle, you need to make sure that the
-[Node.js](https://nodejs.org/) JavaScript runtime version 18 or above is installed on your
-machine.<br/>
-Note that Node.js comes with the [`npm` package manager](https://www.npmjs.com/) as part
-of the standard installation.
+```bash
+pip install git+https://git@github.com/Avaiga/taipy
+```
 
-The build process is described in the [Taipy GUI front-end](frontend/taipy-gui/README.md) and
- [Taipy front-end](frontend/taipy/README.md) README files.<br/>
- The Taipy GUI bundle must be built first, as the Taipy front-end code depends on it.
+### Pipenv
+If you are using a virtual environment with **[Pipenv](https://pipenv.pypa.io/en/latest/)**,
+use the following command:
+```console
+pipenv install git+https://git@github.com/Avaiga/taipy
+```
 
-Here is the sequence of commands that can be issued to build both sets of files:
+### Venv
+Alternatively, you can use `venv` to create a virtual environment:
+```console
+python -m venv myenv
+source myenv/bin/activate  # On Windows use `myenv\Scripts\activate`
+pip install git+https://git@github.com/Avaiga/taipy
+```
 
-```bash
-# Current directory is the repository's root directory
-#
-# Build the Taipy GUI bundle
-cd frontend/taipy-gui
-cd dom
-npm i
-cd ..
-npm i
-npm run build
-#
-# Build the Taipy front-end bundle
-cd ../taipy # Current directory is [taipy-dir]/frontend/taipy
-npm i
-npm run build
+### Conda
+If you prefer to work within a [Conda](https://docs.conda.io/projects/conda/en/latest/index.html)
+environment, you can install Taipy using the following commands replacing `<myenv>` with your
+desired environment name:
+```console
+conda create -n <myenv>
+conda activate <myenv>
+pip install git+https://git@github.com/Avaiga/taipy
 ```
 
-These commands should create the directories `taipy/gui/webapp` and `taipy/gui_core/lib` in the
-root directory of the taipy repository.
+# Installing Taipy with Colab
 
-### Debugging the JavaScript bundles
+Google Colab is a popular and free Jupyter notebook environment that requires no setup
+and runs entirely in the cloud. To install Taipy in Google Colab, follow these simple
+steps:
 
-If you plan to modify the front-end code and need to debug the TypeScript
-code, you must use the following:
-```bash
-npm run build:dev
-```
+1. **Open a new Colab notebook**: Visit [Google Colab](https://colab.research.google.com)
+    and start a new notebook.
 
-instead of the *standard* build option.
-
-This will preserve the debugging symbols, and you will be able to navigate in the
-TypeScript code from your debugger.
-
-> **Note:** Web application location
->
-> When you are developing front-end code for the Taipy GUI package, it may
-> be cumbersome to have to install the package over and over when you know
-> that all that has changed is the JavaScript bundle that makes the Taipy
-> web app.
->
-> By default, the Taipy GUI application searches for the front-end code
-> in the `[taipy-gui-package-dir]/taipy/gui/webapp` directory.<br/>
-> You can, however, set the environment variable `TAIPY_GUI_WEBAPP_PATH`
-> to the location of your choice, and Taipy GUI will look for the web
-> app in that directory.<br/>
-> If you set this variable to the location where you build the web app
-> repeatedly, you will no longer have to reinstall Taipy GUI before you
-> try your code again.
-
-## Running the tests
-
-To run the tests on the package, you need to install the required development packages.
-We recommend using [Pipenv](https://pipenv.pypa.io/en/latest/) to create a virtual environment
-and install the development packages.
+2. **Run the installation command**: In a new cell, enter the following command and run
+    the cell to install the latest stable release of Taipy in your Colab environment:
 
-```bash
-pip install pipenv
-pipenv install --dev
-```
+    ```python
+    !pip install --ignore-installed taipy
+    ```
 
-Then you can run the tests with the following command:
+3. **Start building your app**: Follow this
+    [tutorial](https://docs.taipy.io/en/latest/tutorials/articles/colab_with_ngrok/) to build
+    and run your Taipy web application directly within the Colab notebook.
 
-```bash
-pipenv run pytest
-```
+!!! tip
+    Remember that Google Colab environments are ephemeral. If you disconnect or restart
+    your Colab session, you will need to reinstall Taipy.

+ 27 - 19
README.md

@@ -11,7 +11,7 @@
 </div>
 
 <h1 align="center">
-Build Python Data & AI web applications
+    Build Python Data & AI web applications
 </h1>
 
 <div align="center">
@@ -21,8 +21,8 @@ No more compromises on performance, customization, and scalability.
 
 <br />
 
-<div align="center">
-**Go beyond existing libraries**
+<div align="center"> 
+     <strong> Go beyond existing libraries  </strong>
 </div>
 
 <p align="center"><h4>
@@ -61,12 +61,12 @@ Taipy is designed for data scientists and machine learning engineers to build da
 &nbsp;
 
 <h4 align="left">
-Taipy is a Two-in-One Tool for UI Generation and Scenario/Data Management
+Taipy is a Two-in-One Tool for UI Generation and Scenario & Data Management
 </h4>
 
 <br />
 
-| User Interface Generation                                                                       | Scenario and Data Management                                                                        |
+| User Interface Generation                                                                       | Scenario & Data Management                                                                        |
 | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
 | <img src="readme_img/taipy_github_GUI_video.gif" alt="Interface Animation"  width="100%" /> | <img src="readme_img/taipy_github_scenarios_video.gif" alt="Back-End Animation"  width="100%"/> |
 
@@ -74,8 +74,8 @@ Taipy is a Two-in-One Tool for UI Generation and Scenario/Data Management
 
 ## ✨ Key Features
 
-<img src="readme_img/taipy_github_scenario.png" alt="Scenario Banner"  width="49%" />  <img src="readme_img/taipy-github-optimized.png" alt="Back-End Animation"  width="49.7%"/>
-<img src="readme_img/taipy_github_data_support.png" alt="Back-End Animation"  width="49.7%" />
+<img src="readme_img/taipy_github_scenario.png" alt="Scenario Banner"  width="49%" />  <img src="readme_img/taipy-github-optimized.png" alt="Front-End Animation"  width="49%"/>
+<img src="readme_img/taipy_github_data_support.png" alt="Back-End Animation"  width="49%" />
 
 &nbsp;
 
@@ -87,22 +87,23 @@ To install the stable release of Taipy, run:
 pip install taipy
 ```
 
+### Ready to Install Taipy? 🚀
 
-**Ready to Install Taipy?** 🚀<br>
 Get everything set up in no time! Whether you're using a Conda environment or installing from
-source, follow our [Installation Guide](https://docs.taipy.io/en/latest/installation/) for
+source, follow our [Installation Guide](https://docs.taipy.io/en/latest/tutorials/getting_started/installation/) for
 step-by-step instructions.<br>
 
-**Excited to Dive In?** 💡 <br>
+### Excited to Dive In? 💡
+
 Start building with Taipy today! Our
-[Getting Started Guide](https://docs.taipy.io/en/latest/tutorials/getting_started/) is the
-perfect place to begin your journey and unlock the full potential of Taipy.
+[Getting Started Guide](https://docs.taipy.io/en/develop/tutorials/getting_started/)
+is the perfect place to begin your journey and unlock the full potential of Taipy.
 
 &nbsp;
 
-## 🔌 Scenario and Data Management
+## 🔌 Scenario & Data Management
 
-Let's create a scenario in Taipy that allows you to filter movie data based on your chosen genre.<br />
+Let's create a simple scenario in Taipy that allows you to filter movie data based on your chosen genre.<br />
 This scenario is designed as a straightforward pipeline.<br />
 Every time you change your genre selection, the scenario runs to process your request.<br />
 It then displays the top seven most popular movies in that genre.
@@ -133,12 +134,14 @@ This is the execution graph of the scenario we are implementing:
 
 You can use the Taipy Studio extension in Visual Studio Code to configure your scenario with no code.<br />
 Your configuration is automatically saved as a TOML file.<br />
-Check out the Taipy Studio [Documentation](https://docs.taipy.io/en/latest/manuals/studio/).
+Check out the Taipy Studio [Documentation](https://docs.taipy.io/en/latest/userman/ecosystem/studio/).
 
 For more advanced use cases or if you prefer coding your configurations instead of using Taipy Studio,<br />
-check out the movie genre demo scenario creation with this [Demo](https://docs.taipy.io/en/latest/gallery/other/movie_genre_selector/).
+check out the movie genre demo scenario creation with this [Demo](https://docs.taipy.io/en/latest/gallery/articles/movie_genre_selector/).
 
-![TaipyStudio](https://github.com/Avaiga/taipy/raw/develop/readme_img/readme_demo_studio.gif)
+<p align="center">
+<img src="https://github.com/Avaiga/taipy/raw/develop/readme_img/readme_demo_studio.gif" alt="Back-End Animation"  width="80%" align="center" />
+</p>
 
 &nbsp;
 
@@ -148,6 +151,8 @@ This simple Taipy application demonstrates how to create a basic film recommenda
 The application filters a dataset of films based on the user's selected genre and displays the top seven films in that genre by popularity.
 Here is the full code for both the front end and back end of the application.
 
+<p align="center" width=80% >
+    
 ```python
 import taipy as tp
 import pandas as pd
@@ -211,9 +216,12 @@ if __name__ == "__main__":
 
     Gui(page=my_page).run()
 ```
+</p>
 
 And the final result:
-<img src="readme_img/readme_app.gif" />
+<p align="center">
+<img src="readme_img/readme_app.gif"  width="70%" align="center" />
+</p>
 
 &nbsp;
 
@@ -223,7 +231,7 @@ Want to help build Taipy? Check out our [**Contributing Guide**](https://github.
 
 ## 🪄 Code of Conduct
 
-Want to be part of the Taipy community? Check out our **[Code of Conduct](https://github.com/Avaiga/taipy/blob/develop/CODE_OF_CONDUCT.md)**.
+Want to be part of the Taipy community? Check out our [**Code of Conduct**](https://github.com/Avaiga/taipy/blob/develop/CODE_OF_CONDUCT.md)
 
 ## 🪪 License
 

+ 0 - 20
contributors.txt

@@ -1,20 +0,0 @@
-jrobinAV
-FabienLelaquais
-florian-vuillemot
-FredLL-Avaiga
-dinhlongviolin1
-joaoandre-avaiga
-toan-quach
-trgiangdo
-gmarabout
-tsuu2092
-arcanaxion
-Dr-Irv
-enarroied
-bobbyshermi
-Forchapeatl
-yarikoptic
-Luke-0162
-Satoshi-Sh
-kushal34712
-DeepanshuProgrammer

+ 25 - 0
doc/gui/examples/controls/time_analog_picker.py

@@ -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 datetime import datetime
+
+from taipy.gui import Gui
+
+time = datetime.today()
+
+page = "<|{time}|time|analogic|>"
+
+if __name__ == "__main__":
+    Gui(page).run(title="Time - Analog Picker")

+ 25 - 0
doc/gui/examples/controls/time_format.py

@@ -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 datetime import datetime
+
+from taipy.gui import Gui
+
+time = datetime.today()
+
+page = "<|{time}|time|format=hh aa>"
+
+if __name__ == "__main__":
+    Gui(page).run(title="Time - Format")

+ 25 - 0
doc/gui/examples/controls/time_simple.py

@@ -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 datetime import datetime
+
+from taipy.gui import Gui
+
+time = datetime.today()
+
+page = "<|{time}|time|>"
+
+if __name__ == "__main__":
+    Gui(page).run(title="Time - Simple")

+ 31 - 0
doc/gui/examples/controls/time_styling.py

@@ -0,0 +1,31 @@
+# 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 datetime import datetime
+
+from taipy.gui import Gui, Markdown
+
+time = datetime.today()
+color = {"color": "yellow"}
+
+page = Markdown(
+    "<|{time}|time|>",
+    style={
+        ".taipy-time": {".MuiInputBase-root": {"&": color, ".MuiIconButton-root": color}}
+    },
+)
+
+if __name__ == "__main__":
+    Gui(page).run(title="Time - Styling")

+ 1 - 1
frontend/taipy-gui/base/src/packaging/package.json

@@ -1,6 +1,6 @@
 {
   "name": "taipy-gui-base",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "private": true,
   "main": "./taipy-gui-base.js",
   "types": "./taipy-gui-base.d.ts"

+ 1 - 1
frontend/taipy-gui/dom/package.json

@@ -1,6 +1,6 @@
 {
   "name": "taipy-gui-dom",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "private": true,
   "dependencies": {
     "react": "^18.2.0",

+ 1 - 1
frontend/taipy-gui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "taipy-gui",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "private": true,
   "dependencies": {
     "@emotion/react": "^11.10.0",

+ 1 - 1
frontend/taipy-gui/packaging/package.json

@@ -1,6 +1,6 @@
 {
   "name": "taipy-gui",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "private": true,
   "main": "./taipy-gui.js",
   "types": "./taipy-gui.d.ts"

+ 10 - 0
frontend/taipy-gui/src/components/Taipy/DateRange.spec.tsx

@@ -81,6 +81,16 @@ describe("DateRange Component", () => {
         expect(elts).toHaveLength(2);
         expect(elts[0].parentElement?.tagName).toBe("BUTTON");
     });
+    it("renders with analog time picker", async () => {
+        const { getAllByTestId } = render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <DateRange dates={curDates} withTime={true} analogic={true}/>
+            </LocalizationProvider>
+        );
+        const elts = getAllByTestId("CalendarIcon");
+        expect(elts).toHaveLength(2);
+        expect(elts[0].parentElement?.tagName).toBe("BUTTON");
+    });
     it("displays the right info for string", async () => {
         const { getAllByTestId } = render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>

+ 11 - 1
frontend/taipy-gui/src/components/Taipy/DateRange.tsx

@@ -18,6 +18,7 @@ import Typography from "@mui/material/Typography";
 import { DatePicker, DatePickerProps } from "@mui/x-date-pickers/DatePicker";
 import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
 import { DateTimePicker, DateTimePickerProps } from "@mui/x-date-pickers/DateTimePicker";
+import { renderTimeViewClock } from '@mui/x-date-pickers/timeViewRenderers';
 import { isValid } from "date-fns";
 import { ErrorBoundary } from "react-error-boundary";
 
@@ -40,6 +41,7 @@ interface DateRangeProps extends TaipyActiveProps, TaipyChangeProps {
     labelEnd?: string;
     separator?: string;
     width?: string | number;
+    analogic?: boolean;
 }
 
 const textFieldProps = { textField: { margin: "dense" } } as BaseDateTimePickerSlotProps<Date>;
@@ -65,8 +67,14 @@ const getRangeDateTime = (
     return [null, null];
 };
 
+const analogicRenderers = {
+    hours: renderTimeViewClock,
+    minutes: renderTimeViewClock,
+    seconds: renderTimeViewClock
+}
+
 const DateRange = (props: DateRangeProps) => {
-    const { updateVarName, withTime = false, id, propagate = true, separator = "-" } = props;
+    const { updateVarName, withTime = false, id, propagate = true, separator = "-", analogic = false } = props;
     const dispatch = useDispatch();
     const formatConfig = useFormatConfig();
     const tz = formatConfig.timeZone;
@@ -157,6 +165,7 @@ const DateRange = (props: DateRangeProps) => {
                                     label={props.labelStart}
                                     format={props.format}
                                     sx={dateSx}
+                                    viewRenderers={ analogic ? analogicRenderers : undefined }
                                 />
                                 <Typography>{separator}</Typography>
                                 <DateTimePicker
@@ -173,6 +182,7 @@ const DateRange = (props: DateRangeProps) => {
                                     label={props.labelEnd}
                                     format={props.format}
                                     sx={dateSx}
+                                    viewRenderers={ analogic ? analogicRenderers : undefined }
                                 />
                             </>
                         ) : (

+ 9 - 0
frontend/taipy-gui/src/components/Taipy/DateSelector.spec.tsx

@@ -207,6 +207,15 @@ describe("DateSelector with time Component", () => {
         const elt = getByTestId("CalendarIcon");
         expect(elt.parentElement?.tagName).toBe("BUTTON");
     });
+    it("renders with analog time picker", async () => {
+        const { getByTestId } = render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <DateSelector date={curDateStr} withTime={true} analogic={true}/>
+            </LocalizationProvider>
+        );
+        const elt = getByTestId("CalendarIcon");
+        expect(elt.parentElement?.tagName).toBe("BUTTON");
+    });
     it("displays the right info for string", async () => {
         const { getByTestId } = render(
             <LocalizationProvider dateAdapter={AdapterDateFns}>

+ 26 - 13
frontend/taipy-gui/src/components/Taipy/DateSelector.tsx

@@ -14,9 +14,14 @@
 import React, { useState, useEffect, useCallback, useMemo } from "react";
 import Box from "@mui/material/Box";
 import Tooltip from "@mui/material/Tooltip";
+
 import { DatePicker, DatePickerProps } from "@mui/x-date-pickers/DatePicker";
-import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
+
 import { DateTimePicker, DateTimePickerProps } from "@mui/x-date-pickers/DateTimePicker";
+import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
+
+import { renderTimeViewClock } from '@mui/x-date-pickers/timeViewRenderers';
+
 import { isValid } from "date-fns";
 import { ErrorBoundary } from "react-error-boundary";
 
@@ -41,13 +46,20 @@ interface DateSelectorProps extends TaipyActiveProps, TaipyChangeProps {
     editable?: boolean;
     label?: string;
     width?: string | number;
+    analogic? :boolean;
 }
 
 const boxSx = { display: "inline-block" };
 const textFieldProps = { textField: { margin: "dense" } } as BaseDateTimePickerSlotProps<Date>;
 
+const analogicRenderers = {
+    hours: renderTimeViewClock,
+    minutes: renderTimeViewClock,
+    seconds: renderTimeViewClock
+}
+
 const DateSelector = (props: DateSelectorProps) => {
-    const { updateVarName, withTime = false, id, propagate = true } = props;
+    const { updateVarName, withTime = false, id, propagate = true, analogic = false } = props;
     const dispatch = useDispatch();
     const formatConfig = useFormatConfig();
     const tz = formatConfig.timeZone;
@@ -110,17 +122,18 @@ const DateSelector = (props: DateSelectorProps) => {
                     {editable ? (
                         withTime ? (
                             <DateTimePicker
-                                {...(startProps as DateTimePickerProps<Date>)}
-                                {...(endProps as DateTimePickerProps<Date>)}
-                                value={value}
-                                onChange={handleChange}
-                                className={getSuffixedClassNames(className, "-picker")}
-                                disabled={!active}
-                                slotProps={textFieldProps}
-                                label={props.label}
-                                format={props.format}
-                                sx={dateSx}
-                            />
+                                    {...(startProps as DateTimePickerProps<Date>)}
+                                    {...(endProps as DateTimePickerProps<Date>)}
+                                    value={value}
+                                    onChange={handleChange}
+                                    className={getSuffixedClassNames(className, "-picker")}
+                                    disabled={!active}
+                                    slotProps={textFieldProps}
+                                    label={props.label}
+                                    format={props.format}
+                                    sx={dateSx}
+                                    viewRenderers={ analogic ? analogicRenderers : undefined }
+                                />
                         ) : (
                             <DatePicker
                                 {...(startProps as DatePickerProps<Date>)}

+ 235 - 0
frontend/taipy-gui/src/components/Taipy/TimeSelector.spec.tsx

@@ -0,0 +1,235 @@
+/*
+ * 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.
+ */
+
+import React from "react";
+import { render, fireEvent, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import "@testing-library/jest-dom";
+
+import { LocalizationProvider } from "@mui/x-date-pickers";
+import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
+
+import TimeSelector from "./TimeSelector";
+import { TaipyContext } from "../../context/taipyContext";
+import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers";
+import { getClientServerTimeZoneOffset } from "../../utils";
+
+
+jest.mock("../../utils", () => {
+    const originalModule = jest.requireActual("../../utils");
+
+    //Mock getClientServerTimeZoneOffset
+    return {
+        __esModule: true,
+        ...originalModule,
+        getClientServerTimeZoneOffset: () => 0,
+    };
+});
+
+beforeEach(() => {
+    // add window.matchMedia
+    // this is necessary for the date picker to be rendered in desktop mode.
+    // if this is not provided, the mobile mode is rendered, which might lead to unexpected behavior
+    Object.defineProperty(window, "matchMedia", {
+        writable: true,
+        value: (query: string): MediaQueryList => ({
+            media: query,
+            // this is the media query that @material-ui/pickers uses to determine if a device is a desktop device
+            matches: query === "(pointer: fine)",
+            onchange: () => {},
+            addEventListener: () => {},
+            removeEventListener: () => {},
+            addListener: () => {},
+            removeListener: () => {},
+            dispatchEvent: () => false,
+        }),
+    });
+});
+
+afterEach(() => {
+    // @ts-ignore
+    delete window.matchMedia;
+});
+
+const curDate = new Date();
+curDate.setHours(1, 1, 1, 1);
+const curDateStr = curDate.toISOString();
+
+const cleanText = (val: string) => val.replace(/\u200e|\u2066|\u2067|\u2068|\u2069/g, "");
+
+describe("TimeSelector component with digital time picker", () => {
+    it("renders", async () => {
+        const { getByTestId } = render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} />
+            </LocalizationProvider>
+        );
+        const elt = getByTestId("ClockIcon");
+        expect(elt.parentElement?.tagName).toBe("BUTTON");
+    });
+
+    it("displays the right info for string", async () => {
+        const { getByTestId } = render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} className="taipy-time" />
+            </LocalizationProvider>
+        );
+        const elt = getByTestId("ClockIcon");
+        expect(elt.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass("taipy-time-picker");
+        expect(elt.parentElement?.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass("taipy-time");
+    });
+
+    it("displays the right value after supplied by picker ", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector
+                    time={undefined as unknown as string}
+                />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        fireEvent.change(screen.getByRole("textbox"), {target: {value: '10:20 AM'}});
+        expect(screen.getByRole('textbox')).toHaveValue('10:20 AM');
+    });
+
+    it("displays the default value", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector
+                    defaultTime="2001-01-01T01:01:01"
+                    time={undefined as unknown as string}
+                />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(cleanText(input?.value || "").toLocaleLowerCase()).toEqual("01:01 am");
+    });
+    it("displays the default value with format", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector
+                    defaultTime="2001-01-01T14:20:01"
+                    time={undefined as unknown as string}
+                    format="hh aa"
+                />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(cleanText(input?.value || "")).toEqual("02 PM");
+    });
+    it("is disabled", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} active={false} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(input).toBeDisabled();
+    });
+    it("is enabled by default", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(input).not.toBeDisabled();
+    });
+    it("is enabled by active", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} active={true} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(input).not.toBeDisabled();
+    });
+});
+
+describe("TimeSelector component with analogue time picker", () => {
+    it("renders", async () => {
+        const { getByTestId } = render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} analogic={true} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+    });
+    it("displays the default value", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector
+                    defaultTime="2001-01-01T01:01:01"
+                    time={undefined as unknown as string}
+                    analogic={true}
+                />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(cleanText(input?.value || "").toLocaleLowerCase()).toEqual("01:01 am");
+    });
+    it("displays the default value with format", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector
+                    defaultTime="2001-01-01T14:20:01"
+                    time={undefined as unknown as string}
+                    analogic={true}
+                    format="hh aa"
+                />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(cleanText(input?.value || "")).toEqual("02 PM");
+    });
+
+    it("is disabled", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} active={false} analogic={true} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(input).toBeDisabled();
+    });
+    it("is enabled by default", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} analogic={true} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(input).not.toBeDisabled();
+    });
+    it("is enabled by active", async () => {
+        render(
+            <LocalizationProvider dateAdapter={AdapterDateFns}>
+                <TimeSelector time={curDateStr} active={true} analogic={true} />
+            </LocalizationProvider>
+        );
+        const input = document.querySelector("input");
+        expect(input).toBeInTheDocument();
+        expect(input).not.toBeDisabled();
+    });
+});

+ 122 - 0
frontend/taipy-gui/src/components/Taipy/TimeSelector.tsx

@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+import React, { useState, useMemo, useCallback, useEffect } from "react";
+import Box from "@mui/material/Box";
+import Tooltip from "@mui/material/Tooltip";
+import { ErrorBoundary } from "react-error-boundary";
+import { TimePicker } from '@mui/x-date-pickers/TimePicker';
+import { isValid, format } from "date-fns";
+import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker';
+import { getSuffixedClassNames, TaipyActiveProps, TaipyChangeProps, getCssSize } from "./utils";
+import { createSendUpdateAction } from "../../context/taipyReducers";
+import { useClassNames, useDispatch, useDynamicProperty, useModule } from "../../utils/hooks";
+import ErrorFallback from "../../utils/ErrorBoundary";
+import Field from "./Field";
+import { getTime } from "../../utils";
+
+interface TimeSelectorProps extends TaipyActiveProps, TaipyChangeProps {
+  analogic?: boolean;
+  format?: string;
+  defaultTime?: string;
+  defaultEditable?: boolean;
+  editable?: boolean;
+  label?: string;
+  time: string;
+  width?: string | number;
+}
+
+const boxSx = { display: "inline-block" };
+const TimeSelector = (props: TimeSelectorProps) => {
+  const { analogic = false, id, updateVarName, propagate = true } = props;
+  const [value, setValue] = useState(() => getTime(props.defaultTime));
+  const dispatch = useDispatch();
+  const module = useModule();
+  const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
+  const active = useDynamicProperty(props.active, props.defaultActive, true);
+  const editable = useDynamicProperty(props.editable, props.defaultEditable, true);
+  const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
+  const timeSx = useMemo(() => (props.width ? { maxWidth: getCssSize(props.width) } : undefined), [props.width]);
+
+  const handleChange = useCallback(
+    (v: Date | null) => {
+        setValue(v);
+        if (v !== null && isValid(v)) {
+            // Have to format the picked time value since it comes with timezone
+            const newDateTime = format(v, "yyyy-MM-dd'T'HH:mm:ss")
+
+            dispatch(
+              createSendUpdateAction(
+                  updateVarName,
+                  newDateTime,
+                  module,
+                  props.onChange,
+                  propagate
+              )
+            );
+        }
+    },
+    [updateVarName, dispatch, propagate, props.onChange, module]
+  );
+
+  useEffect(() => {
+    try {
+        if (props.time !== undefined) {
+            setValue(getTime(props.time));
+        }
+    } catch (error) {
+        console.error(error);
+    }
+  }, [props.time]);
+
+  return (
+    <ErrorBoundary FallbackComponent={ErrorFallback}>
+      <Tooltip title={hover || ""}>
+        <Box id={id} className={className} sx={boxSx}>
+          {editable ? (
+            analogic ? (
+              <MobileTimePicker
+                value={value}
+                onChange={handleChange}
+                className={getSuffixedClassNames(className, "-picker")}
+                disabled={!active}
+                label={props.label}
+                format={props.format}
+                sx={timeSx}
+              />
+            ):(
+              <TimePicker 
+                value={value}
+                onChange={handleChange}
+                className={getSuffixedClassNames(className, "-picker")}
+                disabled={!active}
+                label={props.label}
+                format={props.format}
+                sx={timeSx}
+              />
+            )
+          ):(
+            <Field 
+              dataType="time"
+              defaultValue={props.defaultTime}
+              id={id && id + "-field"}
+              value={props.time}
+              className={getSuffixedClassNames(className, "-text")}
+              format={props.format}
+            />
+          )}
+        </Box>
+      </Tooltip>
+    </ErrorBoundary>
+  );
+}
+export default TimeSelector

+ 2 - 0
frontend/taipy-gui/src/components/Taipy/index.ts

@@ -42,6 +42,7 @@ import StatusList from "./StatusList";
 import Table from "./Table";
 import TaipyStyle from "./TaipyStyle";
 import Toggle from "./Toggle";
+import TimeSelector from "./TimeSelector";
 import TreeView from "./TreeView";
 
 const registeredComponents: Record<string, ComponentType> = {};
@@ -76,6 +77,7 @@ export const getRegisteredComponents = () => {
             Status: StatusList,
             Table,
             TaipyStyle,
+            TimeSelector,
             Toggle,
             TreeView,
             Progress,

+ 1 - 1
frontend/taipy-gui/src/components/Taipy/tableUtils.tsx

@@ -304,7 +304,7 @@ export const getPageKey = (
         order,
         aggregates?.length
             ? cols.reduce((pv, col, idx) => {
-                  if (aggregates.includes(columns[col].dfid)) {
+                  if (aggregates.includes(col)) {
                       return `${pv}${idx}`;
                   }
                   return pv;

+ 11 - 0
frontend/taipy-gui/src/utils/index.ts

@@ -98,6 +98,17 @@ export const getDateTime = (value: string | null | undefined, tz?: string, withT
     }
 };
 
+export const getTime = (value: string | null | undefined): Date | null => {
+    if (value === null || value === undefined) {
+        return null;
+    }
+    try {
+        return new Date(value);
+    } catch {
+        return null;
+    }
+};
+
 export const getDateTimeString = (
     value: string,
     datetimeformat: string | undefined,

+ 1 - 1
frontend/taipy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "taipy-gui-core",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "private": true,
   "devDependencies": {
     "@testing-library/jest-dom": "^6.5.0",

+ 23 - 0
taipy/gui/_renderers/factory.py

@@ -57,6 +57,7 @@ class _Factory:
         "status": "value",
         "table": "data",
         "text": "value",
+        "time": "time",
         "toggle": "value",
         "tree": "value",
     }
@@ -142,6 +143,7 @@ class _Factory:
             [
                 ("with_time", PropertyType.boolean),
                 ("active", PropertyType.dynamic_boolean, True),
+                ("analogic", PropertyType.boolean),
                 ("min", PropertyType.dynamic_date),
                 ("max", PropertyType.dynamic_date),
                 ("editable", PropertyType.dynamic_boolean, True),
@@ -164,6 +166,7 @@ class _Factory:
             [
                 ("with_time", PropertyType.boolean),
                 ("active", PropertyType.dynamic_boolean, True),
+                ("analogic", PropertyType.boolean),
                 ("editable", PropertyType.dynamic_boolean, True),
                 ("hover_text", PropertyType.dynamic_string),
                 ("label_start",),
@@ -575,6 +578,26 @@ class _Factory:
                 ("width", PropertyType.string_or_number),
             ]
         ),
+        "time": lambda gui, control_type, attrs: _Builder(
+            gui=gui,
+            control_type=control_type,
+            element_name="TimeSelector",
+            attributes=attrs,
+            default_value=datetime.today().time(),
+        )
+        .set_value_and_default(var_type=PropertyType.time)
+        .set_attributes(
+            [
+                ("active", PropertyType.dynamic_boolean, True),
+                ("analogic", PropertyType.boolean),
+                ("editable", PropertyType.dynamic_boolean, True),
+                ("hover_text", PropertyType.dynamic_string),
+                ("label",),
+                ("format",),
+                ("width", PropertyType.string_or_number),
+            ]
+        )
+        ._set_propagate(),
         "toggle": lambda gui, control_type, attrs: _Builder(
             gui=gui, control_type=control_type, element_name="Toggle", attributes=attrs, default_value=None
         )

+ 2 - 0
taipy/gui/types.py

@@ -28,6 +28,7 @@ from .utils import (
     _TaipyLov,
     _TaipyLovValue,
     _TaipyNumber,
+    _TaipyTime,
     _TaipyToJson,
 )
 
@@ -80,6 +81,7 @@ class PropertyType(Enum):
     date = _TaipyDate
     date_range = _TaipyDateRange
     dict = "dict"
+    time = _TaipyTime
     """
     The property holds a dictionary.
     """

+ 1 - 0
taipy/gui/utils/__init__.py

@@ -53,6 +53,7 @@ from .types import (
     _TaipyLov,
     _TaipyLovValue,
     _TaipyNumber,
+    _TaipyTime,
     _TaipyToJson,
 )
 from .varnamefromcontent import _varname_from_content

+ 17 - 0
taipy/gui/utils/types.py

@@ -197,6 +197,23 @@ class _TaipyDict(_TaipyBase):
     def get_hash():
         return _TaipyBase._HOLDER_PREFIX + "Di"
 
+class _TaipyTime(_TaipyBase):
+    def get(self):
+        val = super().get()
+        if isinstance(val, datetime):
+            val = _date_to_string(val)
+        elif val is not None:
+            val = str(val)
+        return val
+
+    def cast_value(self, value: t.Any):
+        if isinstance(value, str):
+            return datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
+        return super().cast_value(value)
+
+    @staticmethod
+    def get_hash():
+        return _TaipyBase._HOLDER_PREFIX + "Tm"
 
 class _TaipyToJson(_TaipyBase):
     def get(self):

+ 57 - 0
taipy/gui/viselements.json

@@ -351,6 +351,57 @@
                         "type": "Union[str,int]",
                         "default_value": "None",
                         "doc": "The width of the date element."
+                    },
+                    {
+                        "name": "analogic",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "Whether or not to show timepicker as a clock."
+                    }
+                ]
+            }
+        ],
+        [
+            "time",
+            {
+                "inherits": [
+                    "on_change",
+                    "propagate"
+                ],
+                "properties": [
+                    {
+                        "name": "analogic",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "Whether or not to show timepicker as a clock."
+                    },
+                    {
+                        "name": "format",
+                        "type": "str",
+                        "doc": "The format to apply to the value. See below."
+                    },
+                    {
+                        "name": "editable",
+                        "type": "dynamic(bool)",
+                        "default_value": "True",
+                        "doc": "Shows the time as a formatted string if not editable."
+                    },
+                    {
+                        "name": "time",
+                        "default_property": true,
+                        "type": "dynamic(str)",
+                        "doc": "The time that this control represents and can modify"
+                    },
+                    {
+                        "name": "label",
+                        "type": "str",
+                        "doc": "The label associated with the input."
+                    },
+                    {
+                        "name": "width",
+                        "type": "Union[str,int]",
+                        "default_value": "None",
+                        "doc": "The width of the time element."
                     }
                 ]
             }
@@ -401,6 +452,12 @@
                         "type": "Union[str,int]",
                         "default_value": "None",
                         "doc": "The width of the date_range element."
+                    },
+                    {
+                        "name": "analogic",
+                        "type": "bool",
+                        "default_value": "False",
+                        "doc": "Whether or not to show timepicker as a clock."
                     }
                 ]
             }

+ 17 - 10
tests/conftest.py

@@ -10,6 +10,7 @@
 # specific language governing permissions and limitations under the License.
 
 import argparse
+import typing as t
 
 import pytest
 
@@ -23,22 +24,25 @@ from taipy.common.config.checker.issue_collector import IssueCollector
 from taipy.core.config import CoreSection, DataNodeConfig, JobConfig, ScenarioConfig, TaskConfig
 
 
-def pytest_addoption(parser):
+def pytest_addoption(parser: pytest.Parser) -> None:
+    """Add custom command line options for pytest."""
     parser.addoption("--e2e-base-url", action="store", default="/", help="base url for e2e testing")
     parser.addoption("--e2e-port", action="store", default="5000", help="port for e2e testing")
 
 
 @pytest.fixture(scope="session")
-def e2e_base_url(request):
+def e2e_base_url(request: pytest.FixtureRequest) -> str:
+    """Fixture to get the base URL for e2e testing."""
     return request.config.getoption("--e2e-base-url")
 
 
 @pytest.fixture(scope="session")
-def e2e_port(request):
+def e2e_port(request: pytest.FixtureRequest) -> str:
+    """Fixture to get the port for e2e testing."""
     return request.config.getoption("--e2e-port")
 
 
-def remove_subparser(name: str):
+def remove_subparser(name: str) -> None:
     """Remove a subparser from argparse."""
     _TaipyParser._sub_taipyparsers.pop(name, None)
 
@@ -51,8 +55,9 @@ def remove_subparser(name: str):
 
 
 @pytest.fixture
-def clean_argparser():
-    def _clean_argparser():
+def clean_argparser() -> t.Callable:
+    """Fixture to clean the argument parser."""
+    def _clean_argparser() -> None:
         _TaipyParser._parser = argparse.ArgumentParser(conflict_handler="resolve")
         _TaipyParser._subparser_action = None
         _TaipyParser._arg_groups = {}
@@ -64,8 +69,9 @@ def clean_argparser():
 
 
 @pytest.fixture
-def reset_configuration_singleton():
-    def _reset_configuration_singleton():
+def reset_configuration_singleton() -> t.Callable:
+    """Fixture to reset the configuration singleton."""
+    def _reset_configuration_singleton() -> None:
         Config.unblock_update()
 
         Config._default_config = _Config()._default_config()
@@ -82,8 +88,9 @@ def reset_configuration_singleton():
 
 
 @pytest.fixture
-def inject_core_sections():
-    def _inject_core_sections():
+def inject_core_sections() -> t.Callable:
+    """Fixture to inject core sections into the configuration."""
+    def _inject_core_sections() -> None:
         _inject_section(
             JobConfig,
             "job_config",

+ 13 - 0
tests/gui/control/test_date.py

@@ -39,6 +39,19 @@ def test_date_md_2(gui: Gui, test_client, helpers):
     ]
     helpers.test_control_md(gui, md_string, expected_list)
 
+def test_date_md_3(gui: Gui, test_client, helpers):
+    gui._bind_var_val("date", datetime.strptime("15 Dec 2020", "%d %b %Y"))
+    md_string = "<|{date}|date|with_time|analogic|label=a label|>"
+    expected_list = [
+        "<DateSelector",
+        'defaultDate="2020-12-',
+        'updateVarName="_TpDt_tpec_TpExPr_date_TPMDL_0"',
+        "date={_TpDt_tpec_TpExPr_date_TPMDL_0}",
+        "withTime={true}",
+        "analogic={true}",
+        'label="a label"',
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
 
 def test_date_md_width(gui: Gui, test_client, helpers):
     gui._bind_var_val("date", datetime.strptime("15 Dec 2020", "%d %b %Y"))

+ 16 - 0
tests/gui/control/test_date_range.py

@@ -44,6 +44,22 @@ def test_date_range_md_2(gui: Gui, test_client, helpers):
     ]
     helpers.test_control_md(gui, md_string, expected_list)
 
+def test_date_range_md_3(gui: Gui, test_client, helpers):
+    gui._bind_var_val(
+        "dates", [datetime.strptime("15 Dec 2020", "%d %b %Y"), datetime.strptime("31 Dec 2020", "%d %b %Y")]
+    )
+    md_string = "<|{dates}|date_range|with_time|analogic|label_start=start|label_end=end|>"
+    expected_list = [
+        "<DateRange",
+        'defaultDates="[&quot;2020-12-',
+        'updateVarName="_TpDr_tpec_TpExPr_dates_TPMDL_0"',
+        "dates={_TpDr_tpec_TpExPr_dates_TPMDL_0}",
+        "withTime={true}",
+        "analogic={true}",
+        'labelStart="start"',
+        'labelEnd="end"',
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
 
 def test_date_range_md_width(gui: Gui, test_client, helpers):
     # do not remove test_client: it brings an app context needed for this test

+ 77 - 0
tests/gui/control/test_time.py

@@ -0,0 +1,77 @@
+# 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.
+
+from datetime import datetime
+
+from taipy.gui import Gui
+
+
+def test_date_md_1(gui: Gui, test_client, helpers):
+    gui._bind_var_val("time", datetime.strptime("15 Dec 2020 18:18:18", "%d %b %Y %H:%M:%S"))
+    md_string = "<|{time}|time|>"
+    expected_list = [
+        "<TimeSelector",
+        'defaultTime="2020-12-15T18:18:18',
+        'updateVarName="_TpTm_tpec_TpExPr_time_TPMDL_0"',
+        "time={_TpTm_tpec_TpExPr_time_TPMDL_0}",
+    ]
+
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
+def test_date_md_2(gui: Gui, test_client, helpers):
+    gui._bind_var_val("time", datetime.strptime("15 Dec 2020 18:18:18", "%d %b %Y %H:%M:%S"))
+    md_string = "<|{time}|time|label=a label|>"
+    expected_list = [
+        "<TimeSelector",
+        'defaultTime="2020-12-15T18:18:18',
+        'updateVarName="_TpTm_tpec_TpExPr_time_TPMDL_0"',
+        "time={_TpTm_tpec_TpExPr_time_TPMDL_0}",
+        'label="a label"',
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
+def test_date_md_width(gui: Gui, test_client, helpers):
+    gui._bind_var_val("time", datetime.strptime("15 Dec 2020 18:18:18", "%d %b %Y %H:%M:%S"))
+    md_string = "<|{time}|time|width=70%|>"
+    expected_list = [
+        "<TimeSelector",
+        'defaultTime="2020-12-15T18:18:18',
+        'updateVarName="_TpTm_tpec_TpExPr_time_TPMDL_0"',
+        'width="70%"',
+        "time={_TpTm_tpec_TpExPr_time_TPMDL_0}",
+    ]
+    helpers.test_control_md(gui, md_string, expected_list)
+
+
+def test_date_html_1(gui: Gui, test_client, helpers):
+    gui._bind_var_val("time", datetime.strptime("15 Dec 2020 18:18:18", "%d %b %Y %H:%M:%S"))
+    html_string = '<taipy:time time="{time}" />'
+    expected_list = [
+        "<TimeSelector",
+        'defaultTime="2020-12-15T18:18:18',
+        'updateVarName="_TpTm_tpec_TpExPr_time_TPMDL_0"',
+        "time={_TpTm_tpec_TpExPr_time_TPMDL_0}",
+    ]
+    helpers.test_control_html(gui, html_string, expected_list)
+
+
+def test_date_html_2(gui: Gui, test_client, helpers):
+    gui._bind_var_val("time", datetime.strptime("15 Dec 2020 18:18:18", "%d %b %Y %H:%M:%S"))
+    html_string = "<taipy:time>{time}</taipy:time>"
+    expected_list = [
+        "<TimeSelector",
+        'defaultTime="2020-12-15T18:18:18',
+        'updateVarName="_TpTm_tpec_TpExPr_time_TPMDL_0"',
+        "time={_TpTm_tpec_TpExPr_time_TPMDL_0}",
+    ]
+    helpers.test_control_html(gui, html_string, expected_list)