├── .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 |
4 |
5 |
6 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
6 |
7 |
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 | [](https://img.shields.io/github/v/release/eoda-dev/py-tabulator)
4 | [](https://pypi.python.org/pypi/pytabulator)
5 | [](https://img.shields.io/github/actions/workflow/status/eoda-dev/py-tabulator/pytest.yml?branch=main)
6 | [](https://img.shields.io/github/license/eoda-dev/py-tabulator)
7 | [](https://img.shields.io/github/license/eoda-dev/py-maplibregl)
8 | [](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 | 
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 | 
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 |
--------------------------------------------------------------------------------