├── .gitattributes ├── .github └── workflows │ └── pytest.yml ├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── py-tabulator.iml ├── vcs.xml └── watcherTasks.xml ├── LICENSE ├── README.md ├── _table_options.txt ├── docs ├── api.md ├── changelog.md ├── columns.md ├── concept │ └── themes.md ├── events.md ├── example.md ├── examples │ ├── edit_data │ │ ├── app.py │ │ └── index.md │ ├── exports │ │ ├── app.py │ │ └── index.md │ ├── getting_started │ │ ├── app.py │ │ ├── shiny_core_basic.py │ │ ├── shiny_core_multi_row_headers.py │ │ ├── shiny_express.py │ │ ├── shiny_express_all.py │ │ ├── shiny_express_basic.py │ │ ├── shiny_express_filters.py │ │ └── shiny_express_readme.py │ └── themes │ │ ├── app.py │ │ └── index.md ├── images │ └── shiny-express-detailed-example.png ├── index.md └── table.md ├── mkdocs.yml ├── package-lock.json ├── package.json ├── poetry.lock ├── pyproject.toml ├── pytabulator ├── __init__.py ├── _table_options_dc.py ├── _table_options_pydantic.py ├── _types.py ├── _utils.py ├── experimental.py ├── shiny_bindings.py ├── srcjs │ ├── get-tabulator.sh │ ├── get-themes.sh │ ├── tabulator-bindings.js │ ├── tabulator.min.css │ ├── tabulator.min.js │ ├── tabulator_bootstrap3.min.css │ ├── tabulator_bootstrap4.min.css │ ├── tabulator_bootstrap5.min.css │ ├── tabulator_bulma.min.css │ ├── tabulator_materialize.min.css │ ├── tabulator_midnight.min.css │ ├── tabulator_modern.min.css │ ├── tabulator_semanticui.min.css │ ├── tabulator_simple.min.css │ └── tabulator_site.min.css ├── tabulator.py ├── tabulator_context.py ├── theme.py ├── ui.py └── utils.py ├── pytest.ini ├── srcjs ├── events.js ├── index.js ├── utils.js └── widget.js └── tests ├── __init__.py ├── test_create_columns.py ├── test_snake_to_camel_case.py ├── test_table.py └── test_table_options.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.py linguist-vendored=false 3 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ["3.9", "3.10", "3.11", "3.12"] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | # You can test your matrix by printing the current Python version 20 | - name: Display Python version 21 | run: python -c "import sys; print(sys.version)" 22 | - name: Install Poetry and pytest 23 | run: pip install poetry pytest 24 | - name: Install package 25 | run: poetry install --extras all 26 | - name: Test package 27 | run: | 28 | poetry run pytest 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | node_modules/ 163 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | # Zeppelin ignored files 10 | /ZeppelinRemoteNotebooks/ 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 45 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/py-tabulator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 eoda GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-tabulator: Tabulator for Python 2 | 3 | [![Release](https://img.shields.io/github/v/release/eoda-dev/py-tabulator)](https://img.shields.io/github/v/release/eoda-dev/py-tabulator) 4 | [![pypi](https://img.shields.io/pypi/v/pytabulator.svg)](https://pypi.python.org/pypi/pytabulator) 5 | [![Build status](https://img.shields.io/github/actions/workflow/status/eoda-dev/py-tabulator/pytest.yml?branch=main)](https://img.shields.io/github/actions/workflow/status/eoda-dev/py-tabulator/pytest.yml?branch=main) 6 | [![License](https://img.shields.io/github/license/eoda-dev/py-tabulator)](https://img.shields.io/github/license/eoda-dev/py-tabulator) 7 | [![License](https://img.shields.io/github/license/eoda-dev/py-maplibregl)](https://img.shields.io/github/license/eoda-dev/py-maplibregl) 8 | [![Tabulator](https://img.shields.io/badge/Tabulator-v6.2.1-blue.svg)](https://github.com/olifolkerd/tabulator/releases/tag/6.2.1) 9 | 10 | [Shiny for Python](https://shiny.posit.co/py/) bindings for [Tabulator JS](https://tabulator.info/) 11 | 12 | ## Features 13 | 14 | * Filtering 15 | * Grouping 16 | * Editing 17 | * Input validation 18 | * History with undo and redo actions 19 | * Pagination 20 | * Layout 21 | * Column formatters 22 | * Column calculations 23 | * Multi column headers 24 | * Packaged themes 25 | * Spreadsheets supporting multiple sheets 26 | * Download data 27 | * Freeze data 28 | 29 | To learn more about pytabulator, see the documentation at https://eoda-dev.github.io/py-tabulator/. 30 | 31 | Bindings for R are available at https://github.com/eoda-dev/rtabulator. 32 | 33 | ## Installation 34 | 35 | You can install the released version of pytabulator from [PyPI](https://pypi.org/) with: 36 | 37 | ```bash 38 | pip install pytabulator 39 | ``` 40 | 41 | You can install the development version of pytabulator like so: 42 | 43 | ```bash 44 | pip install git+https://github.com/eoda-dev/py-tabulator 45 | ``` 46 | 47 | ## Basic usage 48 | 49 | [Shiny Express](https://shiny.posit.co/blog/posts/shiny-express/): 50 | 51 | ```python 52 | import pandas as pd 53 | from pytabulator import TableOptions, render_data_frame 54 | from shiny import render 55 | from shiny.express import input, ui 56 | 57 | ui.div("Click on row to print name", style="padding: 10px;") 58 | 59 | 60 | @render.code 61 | async def txt(): 62 | print(input.tabulator_row_clicked()) 63 | return input.tabulator_row_clicked()["Name"] 64 | 65 | 66 | @render_data_frame(table_options=TableOptions(height=500)) 67 | def tabulator(): 68 | return pd.read_csv( 69 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 70 | ) 71 | ``` 72 | 73 | [Shiny core](https://shiny.posit.co/py/): 74 | 75 | ```python 76 | # uvicorn docs.examples.getting_started.shiny_core_basic:app 77 | 78 | import pandas as pd 79 | from pytabulator import TableOptions, Tabulator, output_tabulator, render_tabulator 80 | from shiny import App, render, ui 81 | 82 | app_ui = ui.page_fluid( 83 | ui.output_text_verbatim("txt", placeholder=True), 84 | output_tabulator("tabulator"), 85 | ) 86 | 87 | 88 | def server(input, output, session): 89 | @render_tabulator 90 | def tabulator(): 91 | df = pd.read_csv( 92 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 93 | ) 94 | return Tabulator(df, table_options=TableOptions(height=311)) 95 | 96 | @render.code 97 | async def txt(): 98 | print(input.tabulator_row_clicked()) 99 | return str(input.tabulator_row_clicked()) 100 | 101 | 102 | app = App(app_ui, server) 103 | ``` 104 | 105 | Run detailed example: 106 | 107 | ```bash 108 | shiny run docs/examples/getting_started/shiny_express_all.py 109 | ``` 110 | 111 | ![](docs/images/shiny-express-detailed-example.png) 112 | 113 | ## Development 114 | 115 | ### Python 116 | 117 | ```bash 118 | poetry install 119 | 120 | poetry run pytest 121 | ``` 122 | 123 | ### JavaScript 124 | 125 | ```bash 126 | npm install 127 | 128 | npm run prettier 129 | 130 | npm run build 131 | ``` 132 | -------------------------------------------------------------------------------- /_table_options.txt: -------------------------------------------------------------------------------- 1 | add_row_pos: Literal["bottom", "top"] = Field("bottom", serialization_alias="addRowPos") 2 | columns: list = None 3 | frozen_rows: int = Field(None, serialization_alias="frozenRows") 4 | group_by: Union[str, list] = Field(None, serialization_alias="groupBy") 5 | header_visible: bool = Field(True, serialization_alias="headerVisible") 6 | height: Union[int, str] = None 7 | history: bool = False 8 | index: str = "id" 9 | layout: Literal["fitData", "fitDataFill", "fitDataStretch", "fitDataTable", "fitColumns"] = "fitColumns" 10 | movable_rows: bool = Field(False, serialization_alias="movableRows") 11 | pagination_add_row: Literal["page", "table"] = Field("page", serialization_alias="paginationAddRow") 12 | pagination: bool = False 13 | pagination_counter: str = Field("rows", serialization_alias="paginationCounter") 14 | resizable_column_fit: bool = Field(False, serialization_alias="resizableColumnFit") 15 | row_height: int = Field(None, serialization_alias="rowHeight") 16 | selectable: Union[str, bool, int] = "highlight" 17 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | ## Basic usage 2 | 3 | ```python 4 | -8<-- "getting_started/shiny_express_readme.py" 5 | ``` 6 | 7 | See also [detailed example](example.md). 8 | 9 | ::: pytabulator.shiny_bindings 10 | 11 | ::: pytabulator.utils 12 | 13 | ::: pytabulator.tabulator 14 | 15 | ::: pytabulator.tabulator_context 16 | 17 | ::: pytabulator.TableOptions 18 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog for Tabulator for Python 2 | 3 | ## pytabulator v0.2.4 (2024-04-09) 4 | 5 | * Shiny >= 0.7.0 6 | 7 | 8 | ## pytabulator v0.2.3 (2024-02-06) 9 | 10 | * Make Pydantic optional 11 | 12 | 13 | ## pytabulator v0.2.2 (2024-02-04) 14 | 15 | * Add themes 16 | * Add optional support for xlsx exports via sheetjs 17 | 18 | 19 | ## pytabulator v0.2.1 (2024-02-03) 20 | 21 | * Set version of pandas requirement to `>=1.5.3` 22 | * Allow extra arguments in `TableOptions` 23 | 24 | 25 | ## pytabulator v0.2.0 (2024-02-02) 26 | 27 | Initial PyPI release 28 | 29 | * Add docs 30 | * Rename `TabulatorOptions` to `TableOptions` 31 | * Use Pydantic for `TableOptions` 32 | * Add more `input` events 33 | * Add detailed example 34 | * Add utility function to create columns configuration from data frame 35 | 36 | 37 | ## pytabulator v0.1.0 (2024-01-31, Pre-release) 38 | 39 | Initial release 40 | -------------------------------------------------------------------------------- /docs/columns.md: -------------------------------------------------------------------------------- 1 | # Columns and filters 2 | 3 | With the `columns` argument of `TableOptions` you can configure the columns of the table. 4 | 5 | See [Tabulator JS columns docs](https://tabulator.info/docs/5.5/columns) for a complete list of available setup options. 6 | 7 | ## Default definition 8 | 9 | If no `columns` arguments is provided, `title` and `field` is set to the column name of the data frame. 10 | Furthermore, the alignment is set to `right` for numeric columns. 11 | 12 | ```python 13 | from pandas import DataFrame 14 | from pytabulator import render_data_frame 15 | 16 | data = [["Peter", 10], ["Hans", 12]] 17 | df = DataFrame(data, columns=["Name", "Age"]) 18 | 19 | @render_data_frame 20 | def tabulator(): 21 | return df 22 | ``` 23 | 24 | The following definition is created by default for the above data frame: 25 | 26 | ```python 27 | columns = [ 28 | {"title": "Name", "field": "Name", "horizAlign": "left"}, 29 | {"title": "Age", "field": "Age", "horizAlign": "right"} 30 | ] 31 | ``` 32 | 33 | ## Customize default configuration 34 | 35 | With `create_columns` you can customize the default configuration: 36 | 37 | ```python 38 | from pandas import DataFrame 39 | from pytabulator import TableOptions 40 | from pytabulator.utils import create_columns 41 | 42 | data = [["Peter", 10, 102.5], ["Hans", 12, 200.9]] 43 | df = DataFrame(data, columns=["Name", "Age", "JustANumber"]) 44 | 45 | table_options = TableOptions( 46 | columns=create_columns( 47 | df, 48 | default_filter=True, 49 | default_editor=True, 50 | updates={"JustANumber": {"formatter": "progress", "horizAlign": "left"}}) 51 | ) 52 | ``` 53 | 54 | In the example above with `default_editor=True` all columns are set to editable and with `default_filter=True` a header filter is added to all columns. 55 | For numeric columns the editor and filter mode is set to `number`. 56 | 57 | The `updates` arguments allows you to overwrite any defaults set for a column. In this case the `formatter` of the numeric column `JustANumber` is set to `progress` 58 | and the alignment is changed from `right` to `left`. 59 | 60 | ## Calculations 61 | 62 | Calculations can be set with the `bottomCalc` parameter: 63 | 64 | ```python 65 | from pytabulator import TableOptions 66 | 67 | columns = [ 68 | {"title": "Name", "field": "Name", "horizAlign": "left"}, 69 | {"title": "Age", "field": "Age", "horizAlign": "right", "bottomCalc": "avg"} 70 | ] 71 | 72 | table_options = TableOptions(columns=columns) 73 | ``` 74 | 75 | ## Filters 76 | 77 | You can add a filter to the columns with the `headerFilter` parameter: 78 | 79 | ```python 80 | from pytabulator import TableOptions 81 | 82 | columns = [ 83 | { 84 | "title": "Name", 85 | "field": "Name", 86 | "horizAlign": "left", 87 | "headerFilter": True 88 | }, 89 | { 90 | "title": "Age", 91 | "field": "Age", 92 | "horizAlign": "right", 93 | "bottomCalc": "avg", 94 | "headerFilter": "number" 95 | } 96 | ] 97 | 98 | table_options = TableOptions(columns=columns) 99 | ``` 100 | 101 | [Shiny Express](https://shiny.posit.co/blog/posts/shiny-express/) example: 102 | 103 | ```python 104 | -8<-- "getting_started/shiny_express_filters.py" 105 | ``` 106 | 107 | ## Editor 108 | 109 | Set `editor` to `True`, `"input"` or `"number"` to make the cells of a column editable: 110 | 111 | ```python 112 | columns = [ 113 | {"title": "Name", "field": "Name", "horizAlign": "left", "editor": True}, 114 | {"title": "Age", "field": "Age", "horizAlign": "right", "editor": "number"} 115 | ] 116 | ``` 117 | -------------------------------------------------------------------------------- /docs/concept/themes.md: -------------------------------------------------------------------------------- 1 | # Themes 2 | 3 | See [Tabulator JS Themes](https://tabulator.info/docs/5.5/theme) for details. 4 | 5 | Pytabulator comes with a number of pre-packaged theme stylesheets to make styling your table really simple. 6 | To use one of these instead of the default theme simply include the matching function before you render the table: 7 | 8 | ```python 9 | from pytabulator import theme 10 | 11 | theme.tabulator_midnight() 12 | ``` 13 | 14 | ## Standard themes 15 | 16 | ::: pytabulator.theme 17 | options: 18 | show_source: false 19 | show_root_heading: false 20 | show_root_toc_entry: false 21 | show_symbol_type_toc: true 22 | members: 23 | - tabulator_midnight 24 | - tabulator_modern 25 | - tabulator_simple 26 | - tabulator_site 27 | 28 | 29 | ## Framework themes 30 | 31 | ::: pytabulator.theme 32 | options: 33 | show_source: false 34 | show_root_heading: false 35 | show_root_toc_entry: false 36 | show_symbol_type_toc: true 37 | members: 38 | - tabulator_bootstrap3 39 | - tabulator_bootstrap4 40 | - tabulator_bootstrap5 41 | - tabulator_semanticui 42 | - tabulator_bulma 43 | - tabulator_materialize 44 | -------------------------------------------------------------------------------- /docs/events.md: -------------------------------------------------------------------------------- 1 | # Events and triggers 2 | 3 | ## Events 4 | 5 | Tabulator for Python provides the following reactive inputs: 6 | 7 | - `input.{output_id}_row_clicked` event: Sends the data of the clicked row. 8 | - `input.{output_id}_row_edited` event: Sends the data of the edited row. This event is fired each time a cell of the row is edited. 9 | - `input.{output_id}_rows_selected` event: Sends the data of all selected rows. This event is fired each time a new row is selected. 10 | - `input.{output_id}_data` event: Sends the complete data of the table. This event must be triggered from Shiny. 11 | - `input.{output_id}_data_filtered` event: Sends data of filtered rows. This event is triggered each time a filter is applied. 12 | 13 | ```python 14 | from shiny import render 15 | from pandas import read_csv 16 | from pytabulator import render_data_frame 17 | 18 | 19 | # in this case (Shiny Express) the function name corresponds to the 'output_id' 20 | # output_id = "tabulator" 21 | # 22 | # on-row-clicked event: input.tabulator_row_clicked 23 | # on-row-edited event: input.tabulator_row_edited 24 | # 25 | @render_data_frame 26 | def tabulator(): 27 | return read_csv("https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv") 28 | 29 | 30 | # row-on-click event 31 | # 32 | @render.code 33 | async def txt(): 34 | print(input.tabulator_row_clicked()) 35 | return input.tabulator_row_clicked()["Name"] 36 | 37 | 38 | # row-edited event 39 | # 40 | @render.code 41 | def row_edited(): 42 | data = input.tabulator_row_edited() 43 | print(data) 44 | return f"{data['Name']}, {data['Sex']}" 45 | ``` 46 | 47 | ## Triggers 48 | 49 | With `TabulatorContext` you can trigger events on the `table` object. `TabulatorContext` must be used in an async function: 50 | 51 | ```python 52 | from shiny import reactive 53 | from shiny.express import ui 54 | from pytabulator import TabulatorContext 55 | 56 | ui.input_action_button("trigger_download", "Download") 57 | ui.input_action_button("add_row", "Add row") 58 | 59 | 60 | # Trigger download of csv file 61 | # 62 | @reactive.Effect 63 | @reactive.event(input.trigger_download) 64 | async def trigger_download(): 65 | print("download triggered") 66 | async with TabulatorContext("tabulator") as table: 67 | table.trigger_download("csv") 68 | 69 | 70 | # Add a row to the table 71 | # 72 | @reactive.Effect 73 | @reactive.event(input.add_row) 74 | async def add_row(): 75 | async with TabulatorContext("tabulator") as table: 76 | table.add_row({"Name": "Hans", "Sex": "male"}) 77 | ``` 78 | 79 | ## Detailed example 80 | 81 | ```python 82 | -8<-- "getting_started/shiny_express_all.py" 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/example.md: -------------------------------------------------------------------------------- 1 | ![](images/shiny-express-detailed-example.png) 2 | 3 | This example uses [Shiny Express](https://shiny.posit.co/blog/posts/shiny-express/). 4 | 5 | ```bash 6 | shiny run docs/examples/getting_started/shiny_express_all.py 7 | ``` 8 | 9 | ```python 10 | -8<-- "getting_started/shiny_express_all.py" 11 | ``` -------------------------------------------------------------------------------- /docs/examples/edit_data/app.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import ( 3 | TableOptions, 4 | Tabulator, 5 | TabulatorContext, 6 | output_tabulator, 7 | render_tabulator, 8 | ) 9 | from pytabulator.utils import create_columns 10 | from shiny import App, reactive, ui 11 | 12 | df = pd.DataFrame({"id": [1, 2, 3], "name": ["Hans", "Peter", "Hanna"]}) 13 | table_options = TableOptions(columns=create_columns(df, default_editor=True)) 14 | 15 | app_ui = ui.page_auto( 16 | ui.h1("Edit data and submit changes", style="padding-top: 10px;"), 17 | output_tabulator("tabulator"), 18 | ui.div(ui.input_action_button("submit", "Submit data"), style="padding-top: 10px;"), 19 | ) 20 | 21 | 22 | def server(input, output, session): 23 | @render_tabulator 24 | def tabulator(): 25 | return Tabulator(df, table_options) 26 | 27 | @reactive.Effect 28 | @reactive.event(input.submit) 29 | async def trigger_get_data(): 30 | async with TabulatorContext("tabulator") as table: 31 | print("get data") 32 | table.trigger_get_data() 33 | 34 | @reactive.Effect 35 | @reactive.event(input.tabulator_data) 36 | def tabulator_data(): 37 | df_submitted = pd.DataFrame(input.tabulator_data()) 38 | print(df_submitted) 39 | 40 | 41 | app = App(app_ui, server) 42 | -------------------------------------------------------------------------------- /docs/examples/edit_data/index.md: -------------------------------------------------------------------------------- 1 | ```python 2 | -8<-- "edit_data/app.py" 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/examples/exports/app.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import ( 3 | TableOptions, 4 | Tabulator, 5 | TabulatorContext, 6 | render_tabulator, 7 | theme, 8 | ) 9 | from pytabulator.ui import use_sheetjs 10 | from shiny import reactive 11 | from shiny.express import input, ui 12 | 13 | # Include sheetjs to support xlsx downloads 14 | # 15 | use_sheetjs() 16 | 17 | with ui.div(style="padding-top: 10px;"): 18 | ui.input_action_button("trigger_download", "Download") 19 | 20 | with ui.div(style="padding-top: 10px;"): 21 | ui.input_select("data_type", label="Data type", choices=["csv", "json", "xlsx"]) 22 | 23 | 24 | theme.tabulator_site() 25 | 26 | 27 | @render_tabulator 28 | def tabulator(): 29 | df = pd.read_csv( 30 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 31 | ) 32 | return Tabulator( 33 | df, 34 | TableOptions( 35 | height=600, 36 | pagination=True, 37 | layout="fitColumns", 38 | ), 39 | ) 40 | 41 | 42 | @reactive.Effect 43 | @reactive.event(input.trigger_download) 44 | async def trigger_download(): 45 | print("download triggered") 46 | async with TabulatorContext("tabulator") as table: 47 | table.trigger_download(input.data_type()) 48 | -------------------------------------------------------------------------------- /docs/examples/exports/index.md: -------------------------------------------------------------------------------- 1 | ```python 2 | -8<-- "exports/app.py" 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/examples/getting_started/app.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator.shiny_bindings import output_tabulator, render_tabulator 3 | from pytabulator.tabulator import Tabulator 4 | from shiny import App, render, ui 5 | 6 | app_ui = ui.page_fluid( 7 | ui.output_text_verbatim("txt", placeholder=True), 8 | output_tabulator("tabulator"), 9 | ) 10 | 11 | 12 | def server(input, output, session): 13 | @render_tabulator 14 | def tabulator(): 15 | df = pd.read_csv( 16 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 17 | ) 18 | return Tabulator(df, table_options={"height": 311}) 19 | 20 | @render.code 21 | async def txt(): 22 | print(input.tabulator_row_clicked()) 23 | return str(input.tabulator_row_clicked()) 24 | 25 | 26 | app = App(app_ui, server) 27 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_core_basic.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, Tabulator, output_tabulator, render_tabulator 3 | from shiny import App, render, ui 4 | 5 | app_ui = ui.page_fluid( 6 | ui.output_text_verbatim("txt", placeholder=True), 7 | output_tabulator("tabulator"), 8 | ) 9 | 10 | 11 | def server(input, output, session): 12 | @render_tabulator 13 | def tabulator(): 14 | df = pd.read_csv( 15 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 16 | ) 17 | return Tabulator(df, table_options=TableOptions(height=311)) 18 | 19 | @render.code 20 | async def txt(): 21 | print(input.tabulator_row_clicked()) 22 | return str(input.tabulator_row_clicked()) 23 | 24 | 25 | app = App(app_ui, server) 26 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_core_multi_row_headers.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, Tabulator, output_tabulator, render_tabulator 3 | from shiny import App, render, ui 4 | 5 | app_ui = ui.page_fluid( 6 | ui.output_text_verbatim("txt", placeholder=True), 7 | output_tabulator("tabulator"), 8 | ) 9 | 10 | df = pd.DataFrame( 11 | dict( 12 | a=[1, 2, 3], 13 | b=[11, 22, 33], 14 | x=["Hans", "Peter", "Jochem"], 15 | y=["hungry", "proud", "joyful"], 16 | ) 17 | ) 18 | 19 | columns = [ 20 | { 21 | "title": "Numbers", 22 | "columns": [ 23 | {"title": "A number", "field": "a", "hozAlign": "right"}, 24 | {"title": "Another number", "field": "b", "hozAlign": "right"}, 25 | ], 26 | }, 27 | { 28 | "title": "Personal info", 29 | "columns": [ 30 | {"title": "Name", "field": "x"}, 31 | {"title": "Attribute", "field": "y"}, 32 | ], 33 | }, 34 | ] 35 | 36 | 37 | def server(input, output, session): 38 | @render_tabulator 39 | def tabulator(): 40 | return Tabulator(df, table_options=TableOptions(columns=columns)) 41 | 42 | @render.code 43 | async def txt(): 44 | return str(input.tabulator_row_clicked()) 45 | 46 | 47 | app = App(app_ui, server) 48 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_express.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, Tabulator, TabulatorContext, render_tabulator 3 | from shiny import reactive, render 4 | from shiny.express import input, ui 5 | 6 | ui.input_action_button("trigger_get_data", "Get data") 7 | 8 | 9 | @render.code 10 | async def txt(): 11 | print(input.tabulator_row_clicked()) 12 | return input.tabulator_row_clicked()["Name"] 13 | 14 | 15 | @render.code 16 | def row_edited(): 17 | data = input.tabulator_row_edited() 18 | print(data) 19 | return f"{data['Name']}, {data['Sex']}" 20 | 21 | 22 | @render_tabulator 23 | def tabulator(): 24 | df = pd.read_csv( 25 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 26 | ) 27 | return Tabulator( 28 | df, 29 | TableOptions( 30 | header_visible=True, 31 | movable_rows=True, 32 | group_by=["Sex", "Age"], 33 | height=500, 34 | pagination=True, 35 | selectable=3, 36 | resizableColumnFit=False, 37 | columns=[ 38 | { 39 | "title": "Name", 40 | "field": "Name", 41 | "editor": True, 42 | "frozen": True, 43 | "resizable": False, 44 | "headerFilter": True, 45 | "headerFilterParams": {"starts": True}, 46 | }, 47 | { 48 | "title": "AgeP", 49 | "field": "Age", 50 | "formatter": "progress", 51 | }, 52 | { 53 | "title": "Age", 54 | "field": "Age", 55 | "bottomCalc": "avg", 56 | "headerFilter": "number", 57 | }, 58 | { 59 | "title": "Gender", 60 | "field": "Sex", 61 | "editor": "list", 62 | "editorParams": {"values": ["male", "female"]}, 63 | "width": 200, 64 | "headerFilter": True, 65 | "headerFilterParams": { 66 | "values": ["male", "female"], 67 | "clearable": True, 68 | "starts": True, 69 | }, 70 | }, 71 | ], 72 | layout="fitDataTable", 73 | # layout="fitColumns", 74 | frozenRows=3, 75 | ), 76 | ) 77 | 78 | 79 | @reactive.Effect 80 | @reactive.event(input.trigger_get_data) 81 | async def trigger_get_data(): 82 | print("triggered") 83 | async with TabulatorContext("tabulator") as table: 84 | table.trigger_get_data() 85 | 86 | 87 | @reactive.Effect 88 | @reactive.event(input.tabulator_data) 89 | async def get_data(): 90 | data = input.tabulator_data() 91 | print("data", data[0], data[1]) 92 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_express_all.py: -------------------------------------------------------------------------------- 1 | from random import randrange 2 | 3 | import pandas as pd 4 | from pytabulator import TableOptions, Tabulator, TabulatorContext, render_tabulator 5 | from pytabulator.utils import create_columns 6 | from shiny import reactive, render 7 | from shiny.express import input, ui 8 | 9 | # Fetch data 10 | # 11 | df = pd.read_csv( 12 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 13 | )[["PassengerId", "Name", "Pclass", "Sex", "Age", "Fare", "Survived"]] 14 | 15 | # Setup 16 | # 17 | table_options = TableOptions( 18 | columns=create_columns( 19 | df, 20 | default_filter=True, 21 | default_editor=True, 22 | updates={ 23 | "Pclass": { 24 | "formatter": "star", 25 | "formatterParams": {"stars": 3}, 26 | "hozAlign": "center", 27 | }, 28 | "Survived": {"formatter": "tickCross"}, 29 | "Fare": {"formatter": "progress", "hozAlign": "left"}, 30 | }, 31 | ), 32 | height=413, 33 | pagination=True, 34 | pagination_add_row="table", 35 | layout="fitColumns", 36 | index="PassengerId", 37 | add_row_pos="top", 38 | selectable_rows=True, 39 | history=True, 40 | ) 41 | 42 | # Shiny Express App 43 | # 44 | with ui.div(style="padding-top: 0px;"): 45 | ui.input_action_button("trigger_download", "Download") 46 | ui.input_action_button("add_row", "Add row") 47 | ui.input_action_button("delete_selected_rows", "Delete selected rows") 48 | ui.input_action_button("undo", "Undo") 49 | ui.input_action_button("redo", "Redo") 50 | ui.input_action_button("trigger_get_data", "Submit data") 51 | 52 | ui.div( 53 | ui.input_text("name", "Click on 'Add row' to add the Person to the table."), 54 | style="padding-top: 20px;", 55 | ) 56 | ui.div("Click on a row to print the name of the person.", style="padding: 10px;"), 57 | 58 | 59 | @render.code 60 | async def txt(): 61 | print(input.tabulator_row_clicked()) 62 | return input.tabulator_row_clicked()["Name"] 63 | 64 | 65 | ui.div( 66 | "Select multiple rows to print the names of the selected persons.", 67 | style="padding: 10px;", 68 | ), 69 | 70 | 71 | @render.code 72 | def selected_rows(): 73 | data = input.tabulator_rows_selected() 74 | output = [item["Name"] for item in data] 75 | return "\n".join(output) 76 | 77 | 78 | @render_tabulator 79 | def tabulator(): 80 | return Tabulator(df, table_options).options( 81 | editTriggerEvent="dblclick" 82 | ) # .options(selectableRows=True) 83 | 84 | 85 | @reactive.Effect 86 | @reactive.event(input.trigger_download) 87 | async def trigger_download(): 88 | print("download triggered") 89 | async with TabulatorContext("tabulator") as table: 90 | table.trigger_download("csv") 91 | 92 | 93 | @reactive.Effect 94 | @reactive.event(input.add_row) 95 | async def add_row(): 96 | async with TabulatorContext("tabulator") as table: 97 | table.add_row( 98 | { 99 | "Name": input.name() or "Hans", 100 | "Age": randrange(55), 101 | "Survived": randrange(2), 102 | "PassengerId": randrange(10000, 20000, 1), 103 | "SibSp": randrange(9), 104 | } 105 | ) 106 | 107 | 108 | @reactive.Effect 109 | @reactive.event(input.delete_selected_rows) 110 | async def delete_selected_rows(): 111 | async with TabulatorContext("tabulator") as table: 112 | table.delete_selected_rows() 113 | 114 | 115 | @reactive.Effect 116 | @reactive.event(input.undo) 117 | async def undo(): 118 | async with TabulatorContext("tabulator") as table: 119 | table.undo() 120 | 121 | 122 | @reactive.Effect 123 | @reactive.event(input.redo) 124 | async def redo(): 125 | async with TabulatorContext("tabulator") as table: 126 | table.redo() 127 | 128 | 129 | @reactive.Effect 130 | @reactive.event(input.trigger_get_data) 131 | async def trigger_get_data(): 132 | async with TabulatorContext("tabulator") as table: 133 | table.trigger_get_data() 134 | 135 | 136 | @reactive.Effect 137 | @reactive.event(input.tabulator_data) 138 | def tabulator_data(): 139 | print(input.tabulator_data()[0]) 140 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_express_basic.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, Tabulator, theme 3 | from pytabulator.shiny_bindings import render_tabulator 4 | from pytabulator.utils import create_columns 5 | from shiny import render 6 | from shiny.express import input, ui 7 | 8 | ui.div("Click on row to print name.", style="padding: 10px;") 9 | 10 | 11 | @render.code 12 | async def txt(): 13 | print(input.tabulator_row_clicked()) 14 | return input.tabulator_row_clicked()["Name"] 15 | 16 | 17 | theme.tabulator_site() 18 | 19 | 20 | @render_tabulator 21 | def tabulator(): 22 | df = pd.read_csv( 23 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 24 | ) 25 | return Tabulator( 26 | df, 27 | TableOptions( 28 | # height=700, 29 | # height=None, 30 | # columns=create_columns(df), 31 | pagination=True, 32 | pagination_counter="rows", 33 | layout="fitColumns", 34 | # columnDefaults={"tooltip": True}, 35 | ), 36 | ) 37 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_express_filters.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, Tabulator, TabulatorContext, render_tabulator 3 | from shiny import reactive, render 4 | from shiny.express import input, ui 5 | 6 | df = pd.read_csv( 7 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 8 | ) 9 | 10 | # Setup 11 | # 12 | columns = [ 13 | { 14 | "title": "Name", 15 | "field": "Name", 16 | "headerFilter": True, 17 | "headerFilterPlaceholder": "Find a Person...", 18 | # "headerFilterLiveFilter": False, 19 | }, 20 | { 21 | "title": "Survived", 22 | "field": "Survived", 23 | "hozAlign": "right", 24 | "headerFilter": "list", 25 | "headerFilterParams": { 26 | "values": { 27 | "1": "Survived", 28 | "0": "Died", 29 | } 30 | }, 31 | }, 32 | ] 33 | 34 | table_options = TableOptions( 35 | height=600, pagination=True, layout="fitDataTable", columns=columns 36 | ) 37 | 38 | # Shiny Express app 39 | # 40 | ui.div( 41 | ui.input_action_button("clear_filter", "Clear Filter"), 42 | style="padding-bottom: 10px; padding-top: 10px;", 43 | ) 44 | 45 | 46 | @reactive.Effect 47 | @reactive.event(input.clear_filter) 48 | async def clear_filter(): 49 | async with TabulatorContext("tabulator") as table: 50 | table.add_call("clearHeaderFilter") 51 | 52 | 53 | @render.code 54 | async def txt(): 55 | print(input.tabulator_data_filtered()) 56 | return f"Number of search result: {len(input.tabulator_data_filtered())}" 57 | 58 | 59 | @render_tabulator 60 | def tabulator(): 61 | return Tabulator(df, table_options) 62 | -------------------------------------------------------------------------------- /docs/examples/getting_started/shiny_express_readme.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, render_data_frame 3 | from shiny import render 4 | from shiny.express import input, ui 5 | 6 | ui.div("Click on row to print name", style="padding: 10px;") 7 | 8 | 9 | @render.code 10 | async def txt(): 11 | print(input.tabulator_row_clicked()) 12 | return input.tabulator_row_clicked()["Name"] 13 | 14 | 15 | @render_data_frame(table_options=TableOptions(height=500)) 16 | def tabulator(): 17 | return pd.read_csv( 18 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 19 | ) 20 | -------------------------------------------------------------------------------- /docs/examples/themes/app.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pytabulator import TableOptions, Tabulator, render_tabulator, theme 3 | from shiny import render 4 | from shiny.express import input, ui 5 | 6 | table_options = TableOptions( 7 | height=600, 8 | pagination=True, 9 | layout="fitColumns", 10 | ) 11 | 12 | # Set theme 13 | # 14 | theme.tabulator_midnight() 15 | 16 | ui.div("Click on row to print name.", style="padding: 10px;") 17 | 18 | 19 | @render.code 20 | async def txt(): 21 | print(input.tabulator_row_clicked()) 22 | return input.tabulator_row_clicked()["Name"] 23 | 24 | 25 | @render_tabulator 26 | def tabulator(): 27 | df = pd.read_csv( 28 | "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" 29 | ) 30 | return Tabulator(df, table_options) 31 | -------------------------------------------------------------------------------- /docs/examples/themes/index.md: -------------------------------------------------------------------------------- 1 | ```python 2 | -8<-- "themes/app.py" 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/images/shiny-express-detailed-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eoda-dev/py-tabulator/50dc391d5f23a50fb31e96d81dbc5ec8948a7b58/docs/images/shiny-express-detailed-example.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to Tabulator for Python 2 | 3 | Tabulator for Python provides [Shiny for Python](https://shiny.posit.co/py/) bindings for [Tabulator JS](https://tabulator.info/). 4 | 5 | ## Installation 6 | 7 | ```bash 8 | # Stable 9 | pip install pytabulator 10 | 11 | # Dev 12 | pip install git+https://github.com/eodaGmbH/py-tabulator 13 | ``` 14 | 15 | ## Basic usage 16 | 17 | [Shiny Express](https://shiny.posit.co/blog/posts/shiny-express/): 18 | 19 | ```python 20 | -8<-- "getting_started/shiny_express_readme.py" 21 | ``` 22 | 23 | [Shiny core](https://shiny.posit.co/py/): 24 | 25 | ```python 26 | -8<-- "getting_started/shiny_core_basic.py" 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/table.md: -------------------------------------------------------------------------------- 1 | # Table 2 | 3 | The table configuration is set with `TableOptions`: 4 | 5 | ```python 6 | from pytabulator import TableOptions 7 | 8 | table_options = TableOptions( 9 | layout="fitData", 10 | height="600px", 11 | pagination=True, 12 | selectable=True 13 | ) 14 | ``` 15 | 16 | The table options can either be passed to the render decorator: 17 | 18 | ```python 19 | from pandas import read_csv 20 | from pytabulator import render_data_frame, TableOptions 21 | 22 | df = read_csv("titanic.csv") 23 | 24 | table_options = TableOptions( 25 | height="600px", 26 | pagination=True 27 | ) 28 | 29 | @render_data_frame(table_options=table_options) 30 | def tabulator(): 31 | return df 32 | ``` 33 | 34 | Or to the `Tablulator` object: 35 | 36 | ```python 37 | from pandas import read_csv 38 | from pytabulator import render_tabulator, TableOptions, Tabulator 39 | 40 | df = read_csv("titanic.csv") 41 | 42 | table_options = TableOptions( 43 | height="600px", 44 | pagination=True 45 | ) 46 | 47 | @render_tabulator 48 | def tabulator(): 49 | return Tabulator(df, table_options=table_options) 50 | ``` 51 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Tabulator for Python 2 | 3 | theme: 4 | name: material 5 | palette: 6 | primary: "green" 7 | features: 8 | - navigation.tabs 9 | - navigation.tabs.sticky 10 | 11 | nav: 12 | - Get started: 13 | - Welcome to Tabulator for Python: index.md 14 | - Detailed example: example.md 15 | - Changelog: changelog.md 16 | - Concepts: 17 | - Table: table.md 18 | - Columns and filters: columns.md 19 | - Events and triggers: events.md 20 | - Themes: concept/themes.md 21 | - Examples: 22 | - Showcase: example.md 23 | - Themes: examples/themes/index.md 24 | - Edit data: examples/edit_data/index.md 25 | - Downloads: examples/exports/index.md 26 | - API Documentation: api.md 27 | 28 | repo_name: py-tabulator 29 | repo_url: https://github.com/eodaGmbH/py-tabulator 30 | 31 | markdown_extensions: 32 | - attr_list 33 | - md_in_html 34 | - pymdownx.highlight: 35 | anchor_linenums: true 36 | line_spans: __span 37 | pygments_lang_class: true 38 | - pymdownx.inlinehilite: 39 | - pymdownx.superfences: 40 | - pymdownx.snippets: 41 | check_paths: true 42 | base_path: [docs/examples, "."] 43 | 44 | plugins: 45 | - search: 46 | - mkdocstrings: 47 | handlers: 48 | python: 49 | options: 50 | docstring_style: google 51 | docstring_section_style: table 52 | show_root_heading: true 53 | show_source: true 54 | 55 | watch: 56 | - pytabulator -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "py-tabulator", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "py-tabulator", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "esbuild": "^0.20.0", 13 | "prettier": "^3.2.4" 14 | } 15 | }, 16 | "node_modules/@esbuild/aix-ppc64": { 17 | "version": "0.20.0", 18 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz", 19 | "integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==", 20 | "cpu": [ 21 | "ppc64" 22 | ], 23 | "dev": true, 24 | "optional": true, 25 | "os": [ 26 | "aix" 27 | ], 28 | "engines": { 29 | "node": ">=12" 30 | } 31 | }, 32 | "node_modules/@esbuild/android-arm": { 33 | "version": "0.20.0", 34 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz", 35 | "integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==", 36 | "cpu": [ 37 | "arm" 38 | ], 39 | "dev": true, 40 | "optional": true, 41 | "os": [ 42 | "android" 43 | ], 44 | "engines": { 45 | "node": ">=12" 46 | } 47 | }, 48 | "node_modules/@esbuild/android-arm64": { 49 | "version": "0.20.0", 50 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz", 51 | "integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==", 52 | "cpu": [ 53 | "arm64" 54 | ], 55 | "dev": true, 56 | "optional": true, 57 | "os": [ 58 | "android" 59 | ], 60 | "engines": { 61 | "node": ">=12" 62 | } 63 | }, 64 | "node_modules/@esbuild/android-x64": { 65 | "version": "0.20.0", 66 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz", 67 | "integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==", 68 | "cpu": [ 69 | "x64" 70 | ], 71 | "dev": true, 72 | "optional": true, 73 | "os": [ 74 | "android" 75 | ], 76 | "engines": { 77 | "node": ">=12" 78 | } 79 | }, 80 | "node_modules/@esbuild/darwin-arm64": { 81 | "version": "0.20.0", 82 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz", 83 | "integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==", 84 | "cpu": [ 85 | "arm64" 86 | ], 87 | "dev": true, 88 | "optional": true, 89 | "os": [ 90 | "darwin" 91 | ], 92 | "engines": { 93 | "node": ">=12" 94 | } 95 | }, 96 | "node_modules/@esbuild/darwin-x64": { 97 | "version": "0.20.0", 98 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz", 99 | "integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==", 100 | "cpu": [ 101 | "x64" 102 | ], 103 | "dev": true, 104 | "optional": true, 105 | "os": [ 106 | "darwin" 107 | ], 108 | "engines": { 109 | "node": ">=12" 110 | } 111 | }, 112 | "node_modules/@esbuild/freebsd-arm64": { 113 | "version": "0.20.0", 114 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz", 115 | "integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==", 116 | "cpu": [ 117 | "arm64" 118 | ], 119 | "dev": true, 120 | "optional": true, 121 | "os": [ 122 | "freebsd" 123 | ], 124 | "engines": { 125 | "node": ">=12" 126 | } 127 | }, 128 | "node_modules/@esbuild/freebsd-x64": { 129 | "version": "0.20.0", 130 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz", 131 | "integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==", 132 | "cpu": [ 133 | "x64" 134 | ], 135 | "dev": true, 136 | "optional": true, 137 | "os": [ 138 | "freebsd" 139 | ], 140 | "engines": { 141 | "node": ">=12" 142 | } 143 | }, 144 | "node_modules/@esbuild/linux-arm": { 145 | "version": "0.20.0", 146 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz", 147 | "integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==", 148 | "cpu": [ 149 | "arm" 150 | ], 151 | "dev": true, 152 | "optional": true, 153 | "os": [ 154 | "linux" 155 | ], 156 | "engines": { 157 | "node": ">=12" 158 | } 159 | }, 160 | "node_modules/@esbuild/linux-arm64": { 161 | "version": "0.20.0", 162 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz", 163 | "integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==", 164 | "cpu": [ 165 | "arm64" 166 | ], 167 | "dev": true, 168 | "optional": true, 169 | "os": [ 170 | "linux" 171 | ], 172 | "engines": { 173 | "node": ">=12" 174 | } 175 | }, 176 | "node_modules/@esbuild/linux-ia32": { 177 | "version": "0.20.0", 178 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz", 179 | "integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==", 180 | "cpu": [ 181 | "ia32" 182 | ], 183 | "dev": true, 184 | "optional": true, 185 | "os": [ 186 | "linux" 187 | ], 188 | "engines": { 189 | "node": ">=12" 190 | } 191 | }, 192 | "node_modules/@esbuild/linux-loong64": { 193 | "version": "0.20.0", 194 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz", 195 | "integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==", 196 | "cpu": [ 197 | "loong64" 198 | ], 199 | "dev": true, 200 | "optional": true, 201 | "os": [ 202 | "linux" 203 | ], 204 | "engines": { 205 | "node": ">=12" 206 | } 207 | }, 208 | "node_modules/@esbuild/linux-mips64el": { 209 | "version": "0.20.0", 210 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz", 211 | "integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==", 212 | "cpu": [ 213 | "mips64el" 214 | ], 215 | "dev": true, 216 | "optional": true, 217 | "os": [ 218 | "linux" 219 | ], 220 | "engines": { 221 | "node": ">=12" 222 | } 223 | }, 224 | "node_modules/@esbuild/linux-ppc64": { 225 | "version": "0.20.0", 226 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz", 227 | "integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==", 228 | "cpu": [ 229 | "ppc64" 230 | ], 231 | "dev": true, 232 | "optional": true, 233 | "os": [ 234 | "linux" 235 | ], 236 | "engines": { 237 | "node": ">=12" 238 | } 239 | }, 240 | "node_modules/@esbuild/linux-riscv64": { 241 | "version": "0.20.0", 242 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz", 243 | "integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==", 244 | "cpu": [ 245 | "riscv64" 246 | ], 247 | "dev": true, 248 | "optional": true, 249 | "os": [ 250 | "linux" 251 | ], 252 | "engines": { 253 | "node": ">=12" 254 | } 255 | }, 256 | "node_modules/@esbuild/linux-s390x": { 257 | "version": "0.20.0", 258 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz", 259 | "integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==", 260 | "cpu": [ 261 | "s390x" 262 | ], 263 | "dev": true, 264 | "optional": true, 265 | "os": [ 266 | "linux" 267 | ], 268 | "engines": { 269 | "node": ">=12" 270 | } 271 | }, 272 | "node_modules/@esbuild/linux-x64": { 273 | "version": "0.20.0", 274 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz", 275 | "integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==", 276 | "cpu": [ 277 | "x64" 278 | ], 279 | "dev": true, 280 | "optional": true, 281 | "os": [ 282 | "linux" 283 | ], 284 | "engines": { 285 | "node": ">=12" 286 | } 287 | }, 288 | "node_modules/@esbuild/netbsd-x64": { 289 | "version": "0.20.0", 290 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz", 291 | "integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==", 292 | "cpu": [ 293 | "x64" 294 | ], 295 | "dev": true, 296 | "optional": true, 297 | "os": [ 298 | "netbsd" 299 | ], 300 | "engines": { 301 | "node": ">=12" 302 | } 303 | }, 304 | "node_modules/@esbuild/openbsd-x64": { 305 | "version": "0.20.0", 306 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz", 307 | "integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==", 308 | "cpu": [ 309 | "x64" 310 | ], 311 | "dev": true, 312 | "optional": true, 313 | "os": [ 314 | "openbsd" 315 | ], 316 | "engines": { 317 | "node": ">=12" 318 | } 319 | }, 320 | "node_modules/@esbuild/sunos-x64": { 321 | "version": "0.20.0", 322 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz", 323 | "integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==", 324 | "cpu": [ 325 | "x64" 326 | ], 327 | "dev": true, 328 | "optional": true, 329 | "os": [ 330 | "sunos" 331 | ], 332 | "engines": { 333 | "node": ">=12" 334 | } 335 | }, 336 | "node_modules/@esbuild/win32-arm64": { 337 | "version": "0.20.0", 338 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz", 339 | "integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==", 340 | "cpu": [ 341 | "arm64" 342 | ], 343 | "dev": true, 344 | "optional": true, 345 | "os": [ 346 | "win32" 347 | ], 348 | "engines": { 349 | "node": ">=12" 350 | } 351 | }, 352 | "node_modules/@esbuild/win32-ia32": { 353 | "version": "0.20.0", 354 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz", 355 | "integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==", 356 | "cpu": [ 357 | "ia32" 358 | ], 359 | "dev": true, 360 | "optional": true, 361 | "os": [ 362 | "win32" 363 | ], 364 | "engines": { 365 | "node": ">=12" 366 | } 367 | }, 368 | "node_modules/@esbuild/win32-x64": { 369 | "version": "0.20.0", 370 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz", 371 | "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", 372 | "cpu": [ 373 | "x64" 374 | ], 375 | "dev": true, 376 | "optional": true, 377 | "os": [ 378 | "win32" 379 | ], 380 | "engines": { 381 | "node": ">=12" 382 | } 383 | }, 384 | "node_modules/esbuild": { 385 | "version": "0.20.0", 386 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz", 387 | "integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==", 388 | "dev": true, 389 | "hasInstallScript": true, 390 | "bin": { 391 | "esbuild": "bin/esbuild" 392 | }, 393 | "engines": { 394 | "node": ">=12" 395 | }, 396 | "optionalDependencies": { 397 | "@esbuild/aix-ppc64": "0.20.0", 398 | "@esbuild/android-arm": "0.20.0", 399 | "@esbuild/android-arm64": "0.20.0", 400 | "@esbuild/android-x64": "0.20.0", 401 | "@esbuild/darwin-arm64": "0.20.0", 402 | "@esbuild/darwin-x64": "0.20.0", 403 | "@esbuild/freebsd-arm64": "0.20.0", 404 | "@esbuild/freebsd-x64": "0.20.0", 405 | "@esbuild/linux-arm": "0.20.0", 406 | "@esbuild/linux-arm64": "0.20.0", 407 | "@esbuild/linux-ia32": "0.20.0", 408 | "@esbuild/linux-loong64": "0.20.0", 409 | "@esbuild/linux-mips64el": "0.20.0", 410 | "@esbuild/linux-ppc64": "0.20.0", 411 | "@esbuild/linux-riscv64": "0.20.0", 412 | "@esbuild/linux-s390x": "0.20.0", 413 | "@esbuild/linux-x64": "0.20.0", 414 | "@esbuild/netbsd-x64": "0.20.0", 415 | "@esbuild/openbsd-x64": "0.20.0", 416 | "@esbuild/sunos-x64": "0.20.0", 417 | "@esbuild/win32-arm64": "0.20.0", 418 | "@esbuild/win32-ia32": "0.20.0", 419 | "@esbuild/win32-x64": "0.20.0" 420 | } 421 | }, 422 | "node_modules/prettier": { 423 | "version": "3.2.4", 424 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", 425 | "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", 426 | "dev": true, 427 | "bin": { 428 | "prettier": "bin/prettier.cjs" 429 | }, 430 | "engines": { 431 | "node": ">=14" 432 | }, 433 | "funding": { 434 | "url": "https://github.com/prettier/prettier?sponsor=1" 435 | } 436 | } 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "py-tabulator", 3 | "version": "0.2.5", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "devDependencies": { 10 | "esbuild": "^0.20.0", 11 | "prettier": "^3.2.4" 12 | }, 13 | "scripts": { 14 | "build": "esbuild srcjs/index.js --bundle --minify --outfile=pytabulator/srcjs/tabulator-bindings.js", 15 | "build-dev": "esbuild srcjs/index.js --bundle --outfile=pytabulator/srcjs/tabulator-bindings.js", 16 | "prettier": "prettier srcjs --write", 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "author": "Stefan Kuethe", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pytabulator" 3 | version = "0.2.5" 4 | description = "Shiny bindings for Tabulator JS" 5 | authors = ["Stefan Kuethe "] 6 | readme = "README.md" 7 | license = "MIT" 8 | exclude = ["pytabulator/srcjs/get-themes.sh"] 9 | 10 | [tool.poetry.dependencies] 11 | python = ">=3.9,<4.0" 12 | shiny = ">=0.7.0" 13 | pandas = "*" 14 | pydantic = {version = "^2.6.1", optional = true} 15 | 16 | [tool.poetry.extras] 17 | all = ["pydantic"] 18 | pydantic = ["pydantic"] 19 | 20 | [tool.poetry.group.dev.dependencies] 21 | black = "^24.1.1" 22 | isort = "^5.13.2" 23 | pytest = "^8.0.0" 24 | mkdocs = "^1.5.3" 25 | mkdocs-material = "^9.5.6" 26 | mkdocstrings = {extras = ["python"], version = "^0.24.0"} 27 | 28 | [build-system] 29 | requires = ["poetry-core"] 30 | build-backend = "poetry.core.masonry.api" 31 | -------------------------------------------------------------------------------- /pytabulator/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib.metadata import PackageNotFoundError, version 2 | 3 | try: 4 | pydantic_version = [int(i) for i in version("pydantic").split(".")] 5 | if pydantic_version[0] == 1: 6 | raise PackageNotFoundError() 7 | 8 | from ._table_options_pydantic import TableOptionsPydantic as TableOptions 9 | 10 | # print("pydantic") 11 | except PackageNotFoundError: 12 | from ._table_options_dc import TableOptionsDC as TableOptions 13 | 14 | # print("dataclass") 15 | 16 | # from ._table_options_pydantic import TableOptionsPydantic as TableOptions 17 | from .shiny_bindings import output_tabulator, render_data_frame, render_tabulator 18 | from .tabulator import Tabulator 19 | from .tabulator_context import TabulatorContext 20 | 21 | # __all__ = [] 22 | -------------------------------------------------------------------------------- /pytabulator/_table_options_dc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import asdict, dataclass 4 | from typing import Literal, Union 5 | 6 | from ._types import TableOptions 7 | from ._utils import snake_to_camel_case 8 | 9 | 10 | @dataclass 11 | class TableOptionsDC(TableOptions): 12 | """Table options 13 | 14 | Attributes: 15 | add_row_pos: Where to add rows. Defaults to `"bottom"`. 16 | columns: Column definitions. 17 | frozen_rows: Number of frozen rows. Defaults to `Ǹone`. 18 | group_by: Columns to group by. Defaults to `None`. 19 | header_visible: Whether to display the header of the table. Defaults to `True`. 20 | height: The height of the table in pixels. Defaults to `311`. 21 | history: Whether to enable history. Must be set if `undo` and `redo` is used. Defaults to `False`. 22 | index: The field to be used as a unique index for each row. Defaults to `"id"`. 23 | layout: The layout of the table. Defaults to `"fitColumns"`. 24 | movable_rows: Whether rows are movable. Defaults to `False`. 25 | pagination_add_row: Where to add rows when pagination is enabled. Defaults to `"page"`. 26 | pagination: Whether to enable pagination. Defaults to `False`. 27 | pagination_counter: Whether to display counted rows in footer. Defaults to `"rows"`. 28 | resizable_column_fit: Maintain total column width when resizing a column. Defaults to `False`. 29 | row_height: Fixed height for rows. Defaults to `None`. 30 | selectable_rows: Whether a row is selectable. An integer value sets the maximum number of rows, that can be selected. 31 | If set to `"highlight"`, rows do not change their state when they are clicked. Defaults to `"highlight"`. 32 | """ 33 | 34 | add_row_pos: Literal["bottom", "top"] = "bottom" 35 | columns: list = None 36 | frozen_rows: int = None 37 | group_by: Union[str, list] = None 38 | header_visible: bool = True 39 | height: Union[int, None] = 311 40 | history: bool = False 41 | index: str = "id" 42 | layout: Literal[ 43 | "fitData", "fitDataFill", "fitDataStretch", "fitDataTable", "fitColumns" 44 | ] = "fitColumns" 45 | movable_rows: bool = False 46 | pagination_add_row: Literal["page", "table"] = "page" 47 | pagination: bool = False 48 | pagination_counter: str = "rows" 49 | resizable_column_fit: bool = False 50 | row_height: int = None 51 | selectable_rows: Union[str, bool, int] = "highlight" 52 | 53 | def to_dict(self): 54 | return asdict( 55 | self, 56 | dict_factory=lambda x: { 57 | snake_to_camel_case(k): v for (k, v) in x if v is not None 58 | }, 59 | ) 60 | -------------------------------------------------------------------------------- /pytabulator/_table_options_pydantic.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Literal, Union 4 | 5 | from pydantic import BaseModel, ConfigDict, Field, field_validator 6 | 7 | from ._types import TableOptions 8 | 9 | 10 | class TableOptionsPydantic(TableOptions, BaseModel): 11 | """Table options 12 | 13 | Attributes: 14 | index (str, optional): The index of the table. Defaults to `id`. 15 | header_visible (bool, optional): Whether to display the header of the table. Defaults to `True`. 16 | movable_rows (bool, optional): Whether rows are movable or not. Defaults to `False`. 17 | group_by: Columns to group by. Defaults to `None`. 18 | height (int, optional): Height in px. Defaults to `300`. 19 | pagination (bool, optional): Whether to enable pagination. Defaults to `False`. 20 | pagination_counter (str, optional): Whether to display counted rows in footer. Defaults to `rows`. 21 | pagination_add_row: Where to add rows when pagination is enabled. Defaults to `page`. 22 | selectable_rows: Whether a row is selectable. An integer value sets the maximum number of rows, that can be selected. 23 | If set to `highlight`, rows do not change state when clicked. Defaults to `highlight`. 24 | columns (list, optional): Columns configuration. Defaults to `None`, 25 | which means that the default configuration is used. 26 | layout: The layout of the table. Defaults to `fitColumns`. 27 | add_row_pos: Where to add rows. Defaults to `bottom`. 28 | frozen_rows (int, optional): Number of frozen rows. Defaults to `Ǹone`. 29 | row_height: Fixed height of rows. Defaults to `None`. 30 | history (bool, optional): Whether to enable history. Must be set if `undo` and `redo` is used. Defaults to `False`. 31 | 32 | Note: 33 | See [Tabulator Setup Options](https://tabulator.info/docs/5.5/options) for details. 34 | 35 | Examples: 36 | >>> from pytabulator import TableOptionsPydantic 37 | 38 | >>> table_options = TableOptions(height=500, pagination=True) 39 | """ 40 | 41 | index: str = "id" 42 | header_visible: bool = Field(True, serialization_alias="headerVisible") 43 | movable_rows: bool = Field(False, serialization_alias="movableRows") 44 | group_by: Union[str, list] = Field(None, serialization_alias="groupBy") 45 | height: Union[int, str] = None 46 | pagination: bool = False 47 | pagination_counter: str = Field("rows", serialization_alias="paginationCounter") 48 | pagination_add_row: Literal["page", "table"] = Field( 49 | "page", serialization_alias="paginationAddRow" 50 | ) 51 | selectable_rows: Union[str, bool, int] = Field( 52 | "highlight", serialization_alias="selectableRows" 53 | ) 54 | columns: list = None 55 | layout: Literal[ 56 | "fitData", "fitDataFill", "fitDataStretch", "fitDataTable", "fitColumns" 57 | ] = "fitColumns" 58 | add_row_pos: Literal["bottom", "top"] = Field( 59 | "bottom", serialization_alias="addRowPos" 60 | ) 61 | frozen_rows: int = Field(None, serialization_alias="frozenRows") 62 | row_height: int = Field(None, serialization_alias="rowHeight") 63 | resizable_column_fit: bool = Field(False, serialization_alias="resizableColumnFit") 64 | history: bool = False 65 | 66 | # New features to be added in the next release 67 | """ 68 | responsiveLayout: str = "hide" 69 | columnDefaults: dict = {"tooltip": True} 70 | """ 71 | 72 | model_config = ConfigDict( 73 | validate_assignment=True, 74 | extra="allow", 75 | # use_enum_values=True 76 | ) 77 | 78 | @field_validator("height") 79 | def validate_height(cls, v): 80 | if isinstance(v, int): 81 | return f"{v}px" 82 | 83 | return v 84 | 85 | def to_dict(self) -> dict: 86 | return self.model_dump(by_alias=True, exclude_none=True) 87 | -------------------------------------------------------------------------------- /pytabulator/_types.py: -------------------------------------------------------------------------------- 1 | class TableOptions(object): 2 | pass 3 | -------------------------------------------------------------------------------- /pytabulator/_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from pandas import DataFrame 5 | 6 | 7 | def df_to_dict(df: DataFrame) -> dict: 8 | return json.loads(df.to_json(orient="table", index=False)) 9 | 10 | 11 | def set_theme(stylesheet): 12 | os.environ["PY_TABULATOR_STYLESHEET"] = stylesheet 13 | 14 | 15 | def snake_to_camel_case(snake_str: str) -> str: 16 | return snake_str[0].lower() + snake_str.title()[1:].replace("_", "") 17 | 18 | # return "".join( 19 | # [item if not i else item.title() for i, item in enumerate(snake_str.split("_"))] 20 | # ) 21 | -------------------------------------------------------------------------------- /pytabulator/experimental.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import join 3 | from pathlib import Path 4 | 5 | from htmltools import HTMLDependency, Tag 6 | from shiny import ui 7 | from shiny.ui import head_content, include_css 8 | 9 | os.environ["PY_TABULATOR_STYLESHEET"] = "" 10 | 11 | external_dep = ui.div( 12 | HTMLDependency( 13 | name="tabulator-theme", 14 | version="5.5.4", 15 | source={"href": "https://unpkg.com/tabulator-tables@5.5.4/dist/"}, 16 | stylesheet={"href": "css/tabulator_bootstrap5.min.css"}, 17 | ) 18 | ) 19 | 20 | midnight_theme_url = "https://unpkg.com/browse/tabulator-tables@5.5.4/dist/css/tabulator_midnight.min.css" 21 | 22 | tag = Tag( 23 | "link", 24 | { 25 | "href": "https://unpkg.com/tabulator-tables@5.5.4/dist/css/tabulator_midnight.min.css", 26 | "rel": "stylesheet", 27 | "type": "text/css", 28 | }, 29 | ) 30 | 31 | 32 | def get_theme_css(name): 33 | return head_content( 34 | include_css( 35 | join(Path(__file__).parent, "srcjs", f"tabulator_{name}.min.css"), 36 | method="link", 37 | ) 38 | ) 39 | 40 | 41 | def simple_theme(): 42 | return get_theme_css("simple") 43 | 44 | 45 | def midnight_theme(): 46 | return get_theme_css("midnight") 47 | 48 | 49 | # 50 | # https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.mini.min.js 51 | def use_xlsx() -> HTMLDependency: 52 | pass 53 | 54 | 55 | # 56 | # 57 | def use_jspdf() -> HTMLDependency: 58 | pass 59 | -------------------------------------------------------------------------------- /pytabulator/shiny_bindings.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from htmltools import HTMLDependency, Tag 6 | from pandas import DataFrame 7 | from shiny import ui 8 | from shiny.module import resolve_id 9 | from shiny.render.renderer import Jsonifiable, Renderer, ValueFn 10 | 11 | from ._types import TableOptions 12 | from ._utils import df_to_dict 13 | from .tabulator import Tabulator, jsonifiable_table_options 14 | 15 | # from . import TableOptions 16 | 17 | 18 | # -- 19 | # UI 20 | # -- 21 | 22 | 23 | def tabulator_dep() -> HTMLDependency: 24 | return HTMLDependency( 25 | "tabulator", 26 | "6.2.1", 27 | source={"package": "pytabulator", "subdir": "srcjs"}, 28 | script={"src": "tabulator.min.js", "type": "module"}, 29 | stylesheet={"href": os.getenv("PY_TABULATOR_STYLESHEET", "tabulator.min.css")}, 30 | all_files=False, 31 | ) 32 | 33 | 34 | tabulator_bindings_dep = HTMLDependency( 35 | "tabulator-bindings", 36 | "0.1.0", 37 | source={"package": "pytabulator", "subdir": "srcjs"}, 38 | script={"src": "tabulator-bindings.js", "type": "module"}, 39 | all_files=False, 40 | ) 41 | 42 | 43 | def output_tabulator(id: str): 44 | """Create an output container for a `Tabulator` table 45 | 46 | Args: 47 | id (str): An output id of a `Tabulator` table. 48 | """ 49 | return ui.div( 50 | tabulator_dep(), 51 | tabulator_bindings_dep, 52 | id=resolve_id(id), 53 | class_="shiny-tabulator-output", 54 | ) 55 | 56 | 57 | # ------ 58 | # Render 59 | # ------ 60 | 61 | 62 | class render_tabulator(Renderer[Tabulator]): 63 | """A decorator for a function that returns a `Tabulator` table""" 64 | 65 | def auto_output_ui(self) -> Tag: 66 | return output_tabulator(self.output_id) 67 | 68 | async def transform(self, value: Tabulator) -> Jsonifiable: 69 | # return {"values": value.values.tolist(), "columns": value.columns.tolist()} 70 | # TODO: convert with js 71 | return value.to_dict() 72 | 73 | 74 | class render_data_frame(Renderer[DataFrame]): 75 | """A decorator for a function that returns a `DataFrame` 76 | 77 | Args: 78 | table_options (TableOptions): Table options. 79 | """ 80 | 81 | def auto_output_ui(self) -> Tag: 82 | return output_tabulator(self.output_id) 83 | 84 | def __init__( 85 | self, 86 | _fn: ValueFn[DataFrame] = None, 87 | *, 88 | table_options: TableOptions | dict = {}, 89 | ) -> None: 90 | super().__init__(_fn) 91 | self.table_options = table_options 92 | 93 | async def render(self) -> Jsonifiable: 94 | df = await self.fn() 95 | # return {"values": value.values.tolist(), "columns": value.columns.tolist()} 96 | # TODO: convert with js 97 | data = df_to_dict(df) 98 | data["options"] = jsonifiable_table_options(self.table_options) 99 | return data 100 | -------------------------------------------------------------------------------- /pytabulator/srcjs/get-tabulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | VERSION=6.2.1 3 | wget https://unpkg.com/tabulator-tables@${VERSION}/dist/css/tabulator.min.css 4 | wget https://unpkg.com/tabulator-tables@${VERSION}/dist/js/tabulator.min.js 5 | -------------------------------------------------------------------------------- /pytabulator/srcjs/get-themes.sh: -------------------------------------------------------------------------------- 1 | for base_theme in midnight modern simple site; do 2 | echo $base_theme 3 | # rm tabulator_${base_theme}.min.css 4 | # wget https://unpkg.com/tabulator-tables@5.5.4/dist/css/tabulator_${base_theme}.min.css 5 | done 6 | 7 | for framework_theme in bootstrap3 bootstrap4 bootstrap5 bulma materialize semanticui; do 8 | echo $framework_theme 9 | rm tabulator_${framework_theme}.min.css 10 | wget https://unpkg.com/tabulator-tables@5.5.4/dist/css/tabulator_${framework_theme}.min.css 11 | done 12 | 13 | # wget https://unpkg.com/tabulator-tables@5.5.4/dist/css/tabulator_midnight.min.css 14 | # wget https://unpkg.com/tabulator-tables@5.5.4/dist/css/tabulator_simple.min.css 15 | -------------------------------------------------------------------------------- /pytabulator/srcjs/tabulator-bindings.js: -------------------------------------------------------------------------------- 1 | (()=>{function c(s,e){s.on("rowClick",function(n,t){let o=`${e.id}_row_clicked`;console.log(o,t.getData()),Shiny.onInputChange(o,t.getData())}),s.on("rowClick",(n,t)=>{let o=`${e.id}_rows_selected`,a=s.getSelectedRows().map(i=>i.getData());console.log(o,a),Shiny.onInputChange(o,a)}),s.on("cellEdited",function(n){let t=`${e.id}_row_edited`;console.log(t,n.getData()),Shiny.onInputChange(t,n.getData())}),s.on("dataFiltered",function(n,t){let o=`${e.id}_data_filtered`,a=t.map(i=>i.getData());console.log(o,a),Shiny.onInputChange(o,a)})}function r(s,e,n){n.forEach(([t,o])=>{if(t==="getData"){console.log("custom call"),Shiny.onInputChange(`${s.id}_data`,e.getData());return}if(t==="deleteSelectedRows"){console.log("custom call"),e.getSelectedRows().forEach(i=>{console.log(i.getIndex()),e.deleteRow(i.getIndex())});return}console.log(t,o),e[t](...o)})}var l=class{constructor(e,n,t){t.data=n,this._container=e,console.log("columns",t.columns),t.columns==null&&(t.autoColumns=!0),this._table=new Tabulator(this._container,t),typeof Shiny=="object"&&(c(this._table,this._container),this._addShinyMessageHandler())}_addShinyMessageHandler(){let e=`tabulator-${this._container.id}`;Shiny.addCustomMessageHandler(e,n=>{console.log(n),r(this._container,this._table,n.calls)})}getTable(){return this._table}};var u=class extends Shiny.OutputBinding{find(e){return e.find(".shiny-tabulator-output")}renderValue(e,n){console.log("payload",n),new l(e,n.data,n.options).getTable().on("tableBuilt",function(){n.options.columnUpdates!=null&&console.log("column updates",n.options.columnUpdates)})}};Shiny.outputBindings.register(new u,"shiny-tabulator-output");})(); 2 | -------------------------------------------------------------------------------- /pytabulator/srcjs/tabulator.min.css: -------------------------------------------------------------------------------- 1 | .tabulator{background-color:#888;border:1px solid #999;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none}.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:10}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;position:absolute;right:-3px;width:6px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;left:-3px;position:absolute;right:auto;width:6px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px} 2 | /*# sourceMappingURL=tabulator.min.css.map */ -------------------------------------------------------------------------------- /pytabulator/srcjs/tabulator_bulma.min.css: -------------------------------------------------------------------------------- 1 | .tabulator{background-color:#fff;border:1px solid #999;font-size:16px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select{user-select:none}.tabulator .tabulator-header{background-color:transparent;border-bottom:1px solid #999;box-sizing:border-box;color:#363636;font-weight:700;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:transparent;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:transparent;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:transparent;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#363636}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #363636;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#363636}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #363636;color:#363636}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{background:hsla(0,0%,5%,0)!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:hsla(0,0%,5%,0)!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle,.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%;min-width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:transparent;color:#363636;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#ededed!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-footer{background-color:transparent;border-top:1px solid #999;color:#363636;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder{background:hsla(0,0%,5%,0)!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:hsla(0,0%,5%,0)!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#363636;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #dbdbdb;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:10}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:transparent;box-sizing:border-box;min-height:24px;position:relative}.tabulator-row.tabulator-row-even{background-color:#fafafa}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#fafafa;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#00d1b2}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:16px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #363636;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#363636;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#363636;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#363636;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:transparent;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:transparent}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-right:1px solid #aaa;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #363636;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #363636;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:transparent;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:16px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#fafafa;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:16px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#363636;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:transparent}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid rgba(0,0,0,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:transparent;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#363636;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#363636;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-right:1px solid #aaa;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #363636;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #363636;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #363636;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#363636;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#363636;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#363636;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator{border:none}.tabulator .tabulator-header{border:solid #dbdbdb;border-width:0 0 2px}.tabulator .tabulator-header .tabulator-col{border-right:none}.tabulator .tabulator-header .tabulator-col.tabulator-moving{border:none}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{padding:.5em .75em}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{right:0}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input{border:1px solid #dbdbdb}.tabulator .tabulator-header .tabulator-calcs-holder{border:solid #dbdbdb;border-width:2px 0 0}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-cell{border-bottom-width:0}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border:solid #dbdbdb;border-width:0 0 2px}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border:solid #dbdbdb;border-width:2px 0 0}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs .tabulator-cell{border-bottom-width:0}.tabulator .tabulator-footer{border:solid #dbdbdb;border-width:2px 0 0;padding:.5em .75em}.tabulator .tabulator-footer .tabulator-calcs-holder{border:solid #dbdbdb;border-width:0 0 2px;margin:-5px -10px 10px}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-cell{border-bottom-width:0}.tabulator .tabulator-footer .tabulator-page{border:1px solid #dbdbdb;font-size:16px;margin:0 .1875em;padding:calc(.375em - 1px) .75em}.tabulator .tabulator-footer .tabulator-page.active{border-color:#4a4a4a;color:#363636;font-weight:700}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(.disabled):hover{background:inherit;border-color:#b5b5b5;color:inherit;cursor:pointer}}.tabulator.is-striped .tabulator-row:nth-child(2n){background-color:#fafafa}.tabulator.is-bordered{border:1px solid #dbdbdb}.tabulator.is-bordered .tabulator-header .tabulator-col,.tabulator.is-bordered .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell{border-right:1px solid #dbdbdb}.tabulator.is-narrow .tabulator-header .tabulator-col .tabulator-col-content,.tabulator.is-narrow .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell{padding:.25em .5em}.tabulator-row{min-height:22px}.tabulator-row.tabulator-row-even{background-color:inherit}.tabulator-row.tabulator-selected{background-color:#00d1b2!important}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#009e86!important}}.tabulator-row .tabulator-cell{border:solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em}.tabulator-print-table .tabulator-print-table-group,.tabulator-row.tabulator-group{border-bottom:1px solid #999;border-right:none;border-top:1px solid #999;color:#363636}.tabulator-print-table .tabulator-print-table-group{box-sizing:border-box}.tabulator-popup-container{background:#fff}.tabulator-edit-list .tabulator-edit-list-item.active{color:#fff}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{color:#fff}} 2 | /*# sourceMappingURL=tabulator_bulma.min.css.map */ -------------------------------------------------------------------------------- /pytabulator/srcjs/tabulator_midnight.min.css: -------------------------------------------------------------------------------- 1 | .tabulator{border:1px solid #333;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select{user-select:none}.tabulator .tabulator-header{background-color:#333;border-bottom:1px solid #999;box-sizing:border-box;color:#fff;font-weight:700;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#333;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#1a1a1a;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#1a1a1a;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #888}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #888}.tabulator .tabulator-header .tabulator-calcs-holder{background:#404040!important;border-bottom:1px solid #aaa;border-top:1px solid #888;box-sizing:border-box}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#404040!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle,.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%;min-width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#666;color:#fff;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#373737!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #888}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #888}.tabulator .tabulator-footer{background-color:#333;border-top:1px solid #999;color:#333;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#404040!important;border-bottom:1px solid #888;border-top:1px solid #888;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#404040!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#333;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#fff}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:10}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#666;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#444}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#999;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#000}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#888;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #888;border-top:1px solid #888;pointer-events:none;position:absolute;z-index:15}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #888;border-top:1px solid #888;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #888;box-sizing:border-box;display:inline-block;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #888}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #888}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #999;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #888;border-bottom-left-radius:1px;border-left:2px solid #888;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #fff;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#fff;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#fff;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#fff;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#666;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#666}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #888;border-top:1px solid #999;box-sizing:border-box;font-weight:700;padding:5px 5px 5px 10px}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#666;border:1px solid #888;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#444;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#888;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #888}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#fff;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#999;color:#666}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,40%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #999}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#999;color:#666;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#fff;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #888;color:#fff;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #888;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #888;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #888}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #888}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #888;border-bottom-left-radius:1px;border-left:2px solid #888;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #888;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #fff;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#fff;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#fff;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#fff;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator{background-color:#222}.tabulator .tabulator-header .tabulator-col{background-color:#333}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input,.tabulator .tabulator-header .tabulator-col .tabulator-header-filter select{background:#444;border:1px solid #999;color:#fff}.tabulator .tabulator-header .tabulator-calcs-holder,.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#1a1a1a!important}.tabulator .tabulator-footer .tabulator-calcs-holder,.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#262626!important}.tabulator .tabulator-footer .tabulator-page-counter,.tabulator .tabulator-footer .tabulator-paginator label{color:#fff}.tabulator .tabulator-footer .tabulator-page{color:#333;font-family:inherit;font-size:inherit;font-weight:inherit}.tabulator-row.tabulator-group{color:#333;min-width:100%}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group span{color:#666}.tabulator-edit-select-list{background:#fff}.tabulator-edit-select-list .tabulator-edit-select-list-item{color:#666}.tabulator-edit-select-list .tabulator-edit-select-list-item.active{background:#444;color:#999}.tabulator-edit-select-list .tabulator-edit-select-list-item.active.focused{outline:1px solid hsla(0,0%,60%,.5)}.tabulator-edit-select-list .tabulator-edit-select-list-item.focused{outline:1px solid #444}@media (hover:hover) and (pointer:fine){.tabulator-edit-select-list .tabulator-edit-select-list-item:hover{background:#666;color:#999}}.tabulator-print-table .tabulator-print-table-group{color:#333} 2 | /*# sourceMappingURL=tabulator_midnight.min.css.map */ -------------------------------------------------------------------------------- /pytabulator/srcjs/tabulator_simple.min.css: -------------------------------------------------------------------------------- 1 | .tabulator{border:1px solid #999;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select{user-select:none}.tabulator .tabulator-header{background-color:#fff;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#fff;border-right:1px solid #ddd;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#e6e6e6;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #ddd;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#e6e6e6;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #ddd}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #ddd}.tabulator .tabulator-header .tabulator-calcs-holder{background:#fff!important;border-bottom:1px solid #ddd;border-top:1px solid #ddd;box-sizing:border-box}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#fff!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle,.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%;min-width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#f2f2f2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #ddd}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #ddd}.tabulator .tabulator-footer{background-color:#fff;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#fff!important;border-bottom:1px solid #ddd;border-top:1px solid #ddd;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#fff!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:10}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{box-sizing:border-box;min-height:22px;position:relative}.tabulator-row,.tabulator-row.tabulator-row-even{background-color:#fff}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #ddd;border-top:1px solid #ddd;pointer-events:none;position:absolute;z-index:15}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #ddd;border-top:1px solid #ddd;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #ddd;box-sizing:border-box;display:inline-block;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #ddd}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #ddd}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #ddd;border-bottom-left-radius:1px;border-left:2px solid #ddd;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #ddd;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #ddd;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#fff;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#ddd;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #ddd}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #ddd;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #ddd;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #ddd;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #ddd;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #ddd}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #ddd}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #ddd;border-bottom-left-radius:1px;border-left:2px solid #ddd;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #ddd;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator{background-color:#fff;border:none}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f2f2f2!important;border-bottom:1px solid #999}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f2f2f2!important}.tabulator .tabulator-tableholder .tabulator-placeholder span{color:#000}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f2f2f2!important;border-bottom:1px solid #fff}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f2f2f2!important}.tabulator-row{border-bottom:1px solid #ddd}.tabulator-row .tabulator-cell:last-of-type{border-right:none}.tabulator-row.tabulator-group span{color:#666}.tabulator-print-table .tabulator-print-table-group span{color:#666;margin-left:10px} 2 | /*# sourceMappingURL=tabulator_simple.min.css.map */ -------------------------------------------------------------------------------- /pytabulator/srcjs/tabulator_site.min.css: -------------------------------------------------------------------------------- 1 | .tabulator{background-color:#fff;border:1px solid #222;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select{user-select:none}.tabulator .tabulator-header{background-color:#222;border-bottom:1px solid #3fb449;box-sizing:border-box;color:#fff;font-weight:700;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#222;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#090909;border:1px solid #3fb449;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#090909;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#3fb449}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #3fb449;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#3fb449}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #3fb449;color:#3fb449}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{background:#2f2f2f!important;border-bottom:1px solid #aaa;box-sizing:border-box}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#2f2f2f!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle,.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%;min-width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-footer{background-color:#222;border-top:1px solid #3fb449;color:#222;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#2f2f2f!important;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#2f2f2f!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#222;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#3fb449}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:10}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #3fb449;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #3fb449;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #3fb449;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #3fb449;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator{border:none;border-bottom:5px solid #222}.tabulator[tabulator-layout=fitColumns] .tabulator-row .tabulator-cell:last-of-type{border-right:none}.tabulator .tabulator-header{border-bottom:3px solid #3fb449}.tabulator .tabulator-header .tabulator-col{background-color:#222}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{padding:8px}.tabulator .tabulator-header .tabulator-calcs-holder{background:#3c3c3c!important;border-bottom:none;border-top:1px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#3c3c3c!important}.tabulator .tabulator-tableholder .tabulator-placeholder span{color:#3fb449}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#484848!important;color:#fff;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs-top{border-bottom:none}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs-bottom{border-top:none}.tabulator .tabulator-footer{border-top:3px solid #3fb449;padding:8px 10px 5px}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#3c3c3c!important;border-bottom:1px solid #aaa;border-top:none;margin:-8px -10px 8px}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#3c3c3c!important;color:#fff!important}.tabulator .tabulator-footer .tabulator-page-counter,.tabulator .tabulator-footer .tabulator-paginator label{color:#fff}.tabulator .tabulator-footer .tabulator-page{background-color:#fff;color:#222;font-family:inherit;font-size:inherit;font-weight:inherit}.tabulator-row .tabulator-cell{padding:6px}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#3fb449}.tabulator-row.tabulator-group{background:#222;border-bottom:2px solid #3fb449;border-right:1px solid #aaa;border-top:1px solid #000;color:#fff}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:#090909}}.tabulator-row.tabulator-group span{color:#3fb449}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-print-table-group{background:#222;border-bottom:2px solid #3fb449;color:#fff}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:#090909}}.tabulator-print-table .tabulator-print-table-group span{color:#3fb449} 2 | /*# sourceMappingURL=tabulator_site.min.css.map */ -------------------------------------------------------------------------------- /pytabulator/tabulator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pandas import DataFrame 4 | 5 | from ._types import TableOptions 6 | from ._utils import df_to_dict 7 | 8 | 9 | # TODO: Move somewhere else!? 10 | def jsonifiable_table_options( 11 | table_options: TableOptions | dict, 12 | ) -> dict: 13 | if isinstance(table_options, TableOptions): 14 | return table_options.to_dict() 15 | 16 | return table_options 17 | 18 | 19 | class Tabulator(object): 20 | """Tabulator 21 | 22 | Args: 23 | df (DataFrame): A data frame. 24 | table_options (TableOptions): Table options. 25 | """ 26 | 27 | def __init__( 28 | self, 29 | df: DataFrame, 30 | table_options: TableOptions | dict = {}, 31 | ) -> None: 32 | self.df = df 33 | # self.table_options = table_options 34 | self._table_options = jsonifiable_table_options(table_options) 35 | 36 | def options(self, **kwargs) -> Tabulator: 37 | self._table_options.update(kwargs) 38 | return self 39 | 40 | def to_dict(self) -> dict: 41 | data = df_to_dict(self.df) 42 | # data["options"] = jsonifiable_table_options(self.table_options) 43 | data["options"] = self._table_options 44 | return data 45 | -------------------------------------------------------------------------------- /pytabulator/tabulator_context.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Literal 4 | 5 | from shiny.session import Session, require_active_session 6 | 7 | 8 | class TabulatorContext(object): 9 | """Table context""" 10 | 11 | def __init__(self, id: str, session: Session = None) -> None: 12 | self.id = id 13 | self._session = require_active_session(session) 14 | self._message_queue = [] 15 | 16 | async def __aenter__(self): 17 | return self 18 | 19 | async def __aexit__(self, exc_type, exc_val, exc_tb): 20 | await self.render() 21 | 22 | async def render(self): 23 | await self._session.send_custom_message( 24 | f"tabulator-{self.id}", {"id": self.id, "calls": self._message_queue} 25 | ) 26 | 27 | def add_call(self, method_name: str, *args) -> None: 28 | """Add a method call that is executed on the table instance 29 | 30 | Args: 31 | method_name (str): The name of the method to be executed. 32 | *args (any): The arguments to be passed to the table method. 33 | """ 34 | call = [method_name, args] 35 | self._message_queue.append(call) 36 | 37 | def trigger_download( 38 | self, type: Literal["csv", "json", "xlsx"] = "csv", file_name: str = None, *args 39 | ) -> None: 40 | """Trigger download 41 | 42 | Args: 43 | type (str): The data type of the file to be downloaded. 44 | file_name (str): The file name. 45 | *args (any): The arguments to be passed to the `table.download` method. 46 | """ 47 | if not file_name: 48 | file_name = f"tabulator-data.{type}" 49 | 50 | self.add_call("download", type, file_name, *args) 51 | 52 | def add_row(self, row: dict = {}) -> None: 53 | """Add a row to the table 54 | 55 | Args: 56 | row (dict): Row data to add. 57 | """ 58 | self.add_call("addRow", row) 59 | 60 | def delete_row(self, index: int | str) -> None: 61 | """Delete a row from the table 62 | 63 | Args: 64 | index: The index of the row to delete. 65 | """ 66 | self.add_call("deleteRow", index) 67 | 68 | def delete_selected_rows(self) -> None: 69 | """Delete selected rows from table""" 70 | self.add_call("deleteSelectedRows") 71 | 72 | def undo(self) -> None: 73 | """Trigger undo""" 74 | self.add_call("undo") 75 | 76 | def redo(self) -> None: 77 | """Trigger redo""" 78 | self.add_call("redo") 79 | 80 | def trigger_get_data(self) -> None: 81 | """Trigger sending data""" 82 | self.add_call("getData") 83 | -------------------------------------------------------------------------------- /pytabulator/theme.py: -------------------------------------------------------------------------------- 1 | from ._utils import set_theme 2 | 3 | # Standard Themes 4 | 5 | 6 | def tabulator_simple(): 7 | """Simple 8 | 9 | A plain, simplistic layout showing only basic grid lines. 10 | """ 11 | set_theme("tabulator_simple.min.css") 12 | 13 | 14 | def tabulator_midnight(): 15 | """Midnight 16 | 17 | A dark, stylish layout using simple shades of grey. 18 | """ 19 | set_theme("tabulator_midnight.min.css") 20 | 21 | 22 | def tabulator_modern(): 23 | """Modern 24 | 25 | A neat, stylish layout using one primary color. 26 | """ 27 | set_theme("tabulator_modern.min.css") 28 | 29 | 30 | def tabulator_site(): 31 | """Site 32 | 33 | The theme used for tables on the docs website of Tabulator JS.""" 34 | set_theme("tabulator_site.min.css") 35 | 36 | 37 | # Framework Themes 38 | 39 | 40 | def tabulator_bootstrap3(): 41 | """Bootstrap 3 42 | 43 | A Bootstrap 3 compatible theme. 44 | """ 45 | set_theme("tabulator_bootstrap3.min.css") 46 | 47 | 48 | def tabulator_bootstrap4(): 49 | """Bootstrap 4 50 | 51 | A Bootstrap 4 compatible theme. 52 | """ 53 | set_theme("tabulator_bootstrap4.min.css") 54 | 55 | 56 | def tabulator_bootstrap5(): 57 | """Bootstrap 5 58 | 59 | A Bootstrap 5 compatible theme. 60 | """ 61 | set_theme("tabulator_bootstrap5.min.css") 62 | 63 | 64 | def tabulator_semanticui(): 65 | """Semantic UI 66 | 67 | A Semantic UI compatible theme. 68 | """ 69 | set_theme("tabulator_semanticui.min.css") 70 | 71 | 72 | def tabulator_bulma(): 73 | """Bulma 74 | 75 | A Bulma compatible theme. 76 | """ 77 | set_theme("tabulator_bulma.min.css") 78 | 79 | 80 | def tabulator_materialize(): 81 | """Materialize 82 | 83 | A Materialize compatible theme. 84 | """ 85 | set_theme("tabulator_materialize.min.css") 86 | -------------------------------------------------------------------------------- /pytabulator/ui.py: -------------------------------------------------------------------------------- 1 | from htmltools import HTMLDependency, Tag 2 | from shiny import ui as shiny_ui 3 | 4 | # 5 | # https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.mini.min.js 6 | XLSX_VERSION = "0.20.1" 7 | 8 | sheetjs_dep = HTMLDependency( 9 | name="sheetjs", 10 | version=XLSX_VERSION, 11 | source={"href": f"https://cdn.sheetjs.com/xlsx-{XLSX_VERSION}/package/dist/"}, 12 | script={"src": "xlsx.mini.min.js", "type": "module"}, 13 | ) 14 | 15 | 16 | def use_sheetjs() -> Tag: 17 | return shiny_ui.div(sheetjs_dep) 18 | -------------------------------------------------------------------------------- /pytabulator/utils.py: -------------------------------------------------------------------------------- 1 | from pandas import DataFrame 2 | 3 | 4 | def create_columns( 5 | df: DataFrame, 6 | default_filter: bool = False, 7 | default_editor: bool = False, 8 | updates: dict = {}, 9 | ) -> list: 10 | """Create columns configuration from a data frame 11 | 12 | Args: 13 | df (DataFrame): The data frame to create columns from. 14 | default_filter (bool): Whether to add a default header filter to each column. 15 | default_editor (bool): Whether to add a default editor to each column. 16 | updates (dict): Dictionary of updates that overwrite the default settings or add additional settings the columns. 17 | """ 18 | # (hozAlign, headerFilter, editor) 19 | setup = [ 20 | ( 21 | ("right", "number", "number") 22 | if dtype in [int, float] 23 | else ("left", "input", "input") 24 | ) 25 | for dtype in df.dtypes.tolist() 26 | ] 27 | columns = [ 28 | {"title": column, "field": column, "hozAlign": setup[i][0]} 29 | for i, column in enumerate(df.columns) 30 | ] 31 | 32 | if default_filter: 33 | for i, column in enumerate(columns): 34 | column["headerFilter"] = setup[i][1] 35 | 36 | if default_editor: 37 | for i, column in enumerate(columns): 38 | column["editor"] = setup[i][2] 39 | 40 | for key in updates: 41 | for column in columns: 42 | if column["field"] == key: 43 | column.update(updates[key]) 44 | 45 | return columns 46 | 47 | 48 | # {Age: {}} 49 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = ignore::DeprecationWarning -------------------------------------------------------------------------------- /srcjs/events.js: -------------------------------------------------------------------------------- 1 | export default function addEventListeners(table, el) { 2 | // console.log("table", table); 3 | table.on("rowClick", function (e, row) { 4 | const inputName = `${el.id}_row_clicked`; 5 | console.log(inputName, row.getData()); 6 | Shiny.onInputChange(inputName, row.getData()); 7 | }); 8 | 9 | table.on("rowClick", (e, row) => { 10 | const inputName = `${el.id}_rows_selected`; 11 | const data = table.getSelectedRows().map((row) => row.getData()); 12 | console.log(inputName, data); 13 | Shiny.onInputChange(inputName, data); 14 | }); 15 | 16 | table.on("cellEdited", function (cell) { 17 | const inputName = `${el.id}_row_edited`; 18 | console.log(inputName, cell.getData()); 19 | Shiny.onInputChange(inputName, cell.getData()); 20 | }); 21 | 22 | table.on("dataFiltered", function (filters, rows) { 23 | const inputName = `${el.id}_data_filtered`; 24 | const data = rows.map((row) => row.getData()); 25 | console.log(inputName, data); 26 | Shiny.onInputChange(inputName, data); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /srcjs/index.js: -------------------------------------------------------------------------------- 1 | import { TabulatorWidget } from "./widget"; 2 | 3 | class TabulatorOutputBinding extends Shiny.OutputBinding { 4 | find(scope) { 5 | return scope.find(".shiny-tabulator-output"); 6 | } 7 | 8 | renderValue(el, payload) { 9 | console.log("payload", payload); 10 | const widget = new TabulatorWidget(el, payload.data, payload.options); 11 | const table = widget.getTable(); 12 | 13 | // Jus a test move to widget as well. 14 | table.on("tableBuilt", function () { 15 | if (payload.options.columnUpdates != null) { 16 | console.log("column updates", payload.options.columnUpdates); 17 | } 18 | }); 19 | } 20 | } 21 | 22 | // Register the binding 23 | Shiny.outputBindings.register( 24 | new TabulatorOutputBinding(), 25 | "shiny-tabulator-output", 26 | ); 27 | -------------------------------------------------------------------------------- /srcjs/utils.js: -------------------------------------------------------------------------------- 1 | function createDownloadButton(el, table) { 2 | const container = document.createElement("div"); 3 | container.id = "download-data"; 4 | container.style.padding = "10px"; 5 | const button = document.createElement("button"); 6 | button.textContent = "Download"; 7 | button.addEventListener("click", () => { 8 | table.download("csv", "data.csv"); 9 | }); 10 | container.appendChild(button); 11 | el.before(container); 12 | } 13 | -------------------------------------------------------------------------------- /srcjs/widget.js: -------------------------------------------------------------------------------- 1 | import addEventListeners from "./events"; 2 | 3 | function run_calls(el, table, calls) { 4 | calls.forEach(([method_name, options]) => { 5 | if (method_name === "getData") { 6 | console.log("custom call"); 7 | Shiny.onInputChange(`${el.id}_data`, table.getData()); 8 | return; 9 | } 10 | 11 | if (method_name === "deleteSelectedRows") { 12 | console.log("custom call"); 13 | const rows = table.getSelectedRows(); 14 | rows.forEach((row) => { 15 | console.log(row.getIndex()); 16 | table.deleteRow(row.getIndex()); 17 | }); 18 | return; 19 | } 20 | 21 | console.log(method_name, options); 22 | table[method_name](...options); 23 | }); 24 | } 25 | 26 | class TabulatorWidget { 27 | constructor(container, data, options) { 28 | options.data = data; 29 | this._container = container; 30 | console.log("columns", options.columns); 31 | if (options.columns == null) options.autoColumns = true; 32 | this._table = new Tabulator(this._container, options); 33 | if (typeof Shiny === "object") { 34 | addEventListeners(this._table, this._container); 35 | this._addShinyMessageHandler(); 36 | } 37 | } 38 | 39 | _addShinyMessageHandler() { 40 | // This must be inside table.on("tableBuilt") 41 | const messageHandlerName = `tabulator-${this._container.id}`; 42 | Shiny.addCustomMessageHandler(messageHandlerName, (payload) => { 43 | console.log(payload); 44 | run_calls(this._container, this._table, payload.calls); 45 | }); 46 | } 47 | 48 | getTable() { 49 | return this._table; 50 | } 51 | } 52 | 53 | export { run_calls, TabulatorWidget }; 54 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eoda-dev/py-tabulator/50dc391d5f23a50fb31e96d81dbc5ec8948a7b58/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_create_columns.py: -------------------------------------------------------------------------------- 1 | from pandas import DataFrame 2 | from pytabulator.utils import create_columns 3 | 4 | 5 | def test_create_columns(): 6 | # Prepare 7 | data = [["Peter", 10, 10.5], ["Hans", 12, 13.7]] 8 | df = DataFrame(data, columns=["Name", "Age", "JustANumber"]) 9 | 10 | # Act 11 | columns = create_columns(df, default_filter=True, default_editor=True) 12 | print(columns) 13 | 14 | # Assert 15 | assert len(columns) == 3 16 | assert [column["hozAlign"] for column in columns] == ["left", "right", "right"] 17 | assert [column["headerFilter"] for column in columns] == [ 18 | "input", 19 | "number", 20 | "number", 21 | ] 22 | -------------------------------------------------------------------------------- /tests/test_snake_to_camel_case.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import asdict, dataclass 4 | 5 | from pytabulator._utils import snake_to_camel_case 6 | 7 | 8 | @dataclass 9 | class TableOptions(object): 10 | group_by: str | list = None 11 | header_visible: bool = True 12 | movable_rows: bool = False 13 | 14 | def to_dict(self): 15 | return asdict( 16 | self, 17 | dict_factory=lambda x: { 18 | snake_to_camel_case(k): v for (k, v) in x if v is not None 19 | }, 20 | ) 21 | 22 | 23 | def test_snake_to_camel_case(): 24 | table_options = TableOptions(header_visible=False) 25 | print(table_options.to_dict()) 26 | 27 | assert not table_options.to_dict()["headerVisible"] 28 | -------------------------------------------------------------------------------- /tests/test_table.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pandas import DataFrame 3 | from pydantic import BaseModel 4 | from pytabulator import Tabulator 5 | from pytabulator._table_options_dc import TableOptionsDC as TableOptionsDC 6 | 7 | # from pytabulator import TableOptions as TableOptionsPydantic 8 | from pytabulator._table_options_pydantic import TableOptionsPydantic 9 | 10 | 11 | @pytest.fixture 12 | def df() -> DataFrame: 13 | data = [["Hans", "22"], ["Peter", [23]]] 14 | return DataFrame(data, columns=["Name", "Age"]) 15 | 16 | 17 | def test_table_dc(df: DataFrame) -> None: 18 | # Prepare 19 | table_options = TableOptionsDC(selectable_rows=3) 20 | 21 | # Act 22 | table = Tabulator(df, table_options=table_options) 23 | table_dict = table.to_dict() 24 | print(table_dict) 25 | 26 | # Assert 27 | assert list(table_dict.keys()) == ["schema", "data", "options"] 28 | assert isinstance(table_dict["options"], dict) 29 | # assert hasattr(table.table_options, "__dataclass_fields__") 30 | 31 | 32 | def test_table_pydantic(df: DataFrame) -> None: 33 | # Prepare 34 | table_options = TableOptionsPydantic(selectable_rows=3) 35 | 36 | # Act 37 | table = Tabulator(df, table_options=table_options) 38 | table_dict = table.to_dict() 39 | print(table_dict) 40 | 41 | # assert isinstance(table.table_options, BaseModel) 42 | # print("pydantic", type(table.table_options)) 43 | assert list(table_dict.keys()) == ["schema", "data", "options"] 44 | assert isinstance(table_dict["options"], dict) 45 | -------------------------------------------------------------------------------- /tests/test_table_options.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # from pytabulator import TableOptions 4 | from pytabulator._table_options_dc import TableOptionsDC 5 | from pytabulator._table_options_dc import TableOptionsDC as TableOptionsDC 6 | from pytabulator._table_options_pydantic import TableOptionsPydantic as TableOptions 7 | 8 | 9 | @pytest.fixture 10 | def some_table_options(): 11 | return { 12 | "history": True, 13 | "pagination_counter": "rows", 14 | "index": "PassengerId", 15 | "pagination_add_row": "table", 16 | } 17 | 18 | 19 | def test_table_options(some_table_options): 20 | # Prepare 21 | table_options_pydantic = TableOptions(**some_table_options) 22 | print("pydantic", table_options_pydantic) 23 | 24 | table_options_dc = TableOptionsDC(**some_table_options) 25 | print("dc", table_options_dc) 26 | 27 | # Act 28 | table_options_pydantic_dict = table_options_pydantic.to_dict() 29 | table_options_dc_dict = table_options_dc.to_dict() 30 | 31 | # Assert 32 | assert list(table_options_pydantic_dict.items()).sort( 33 | key=lambda item: item[0] 34 | ) == list(table_options_dc_dict.items()).sort(key=lambda item: item[0]) 35 | --------------------------------------------------------------------------------