lumut 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lumut-0.1.0/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ *.egg-info/
5
+ node_modules/
6
+ dist/
7
+ build/
8
+ site/
lumut-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lumut contributors
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.
lumut-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.5
2
+ Name: lumut
3
+ Version: 0.1.0
4
+ Summary: Experimental AnyWidget data editor for notebook environments
5
+ Project-URL: Homepage, https://github.com/banditelol/lumut
6
+ Project-URL: Repository, https://github.com/banditelol/lumut
7
+ Project-URL: Issues, https://github.com/banditelol/lumut/issues
8
+ Author: lumut contributors
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 lumut contributors
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: anywidget,data-editor,jupyter,marimo,notebook
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3 :: Only
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Topic :: Software Development :: User Interfaces
40
+ Requires-Python: >=3.11
41
+ Requires-Dist: anywidget>=0.11.0
42
+ Requires-Dist: traitlets>=5.14.0
43
+ Provides-Extra: docs
44
+ Requires-Dist: mkdocstrings[python]>=0.25.1; extra == 'docs'
45
+ Requires-Dist: zensical>=0.0.38; extra == 'docs'
46
+ Provides-Extra: test
47
+ Requires-Dist: pytest>=8.0; extra == 'test'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # lumut
51
+
52
+ An experimental, MIT-licensed AnyWidget data editor for notebook runtimes. It
53
+ uses TanStack Table for table state and TanStack Virtual for lazy, measured row
54
+ heights.
55
+
56
+ ## Gallery
57
+
58
+ <div align="center">
59
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">
60
+ <img src="docs/assets/gallery/exp-data-editor.svg" alt="The exp_data_editor showing wrapped text rows" width="640">
61
+ </a>
62
+ <br>
63
+ <strong>exp_data_editor</strong><br>
64
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">molab</a> ·
65
+ <a href="https://adityarp.com/lumut/reference/exp-data-editor/">API</a> ·
66
+ <a href="docs/reference/exp-data-editor.md">Markdown</a>
67
+ </div>
68
+
69
+ <div align="center">
70
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">
71
+ <img src="docs/assets/gallery/data-editor-enchance.svg" alt="The data_editor_enchance Glide editor showing wrapped text rows" width="640">
72
+ </a>
73
+ <br>
74
+ <strong>data_editor_enchance</strong> (Glide rough resize experiment)<br>
75
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">molab</a> ·
76
+ <a href="https://adityarp.com/lumut/reference/data-editor-enchance/">API</a> ·
77
+ <a href="docs/reference/data-editor-enchance.md">Markdown</a>
78
+ </div>
79
+
80
+ The preview is an illustration of the widget. The MoLab demo runs the actual
81
+ AnyWidget from this repository.
82
+
83
+ ## Why it exists
84
+
85
+ `lumut.exp_data_editor` explores a small, marimo-compatible data-editor
86
+ contract without inheriting a grid library's entire feature surface. It accepts
87
+ row-oriented data, column-oriented data, or pandas-like dataframes; honours
88
+ `editable_columns`; and synchronizes fully edited rows as `value`.
89
+
90
+ Rows are measured only after they are rendered. Wrapped content can grow a row,
91
+ but never beyond `max_row_height`. Resizing a column triggers a remeasurement
92
+ of rendered rows, rather than an O(N) pass over all data.
93
+
94
+ ## Why this does not use Glide Data Grid
95
+
96
+ Glide Data Grid remains a capable canvas-based editor, but its public
97
+ `rowHeight` API is either one constant or a callback indexed by row. In the
98
+ current implementation, the callback path requires the grid to walk row
99
+ heights to derive total height and map a scroll offset to a row. A cached
100
+ callback therefore does not make exact variable wrapped-row height suitable for
101
+ very large datasets. The upstream "fit content" request was discussed as a
102
+ consumer-side canvas-measurement workaround rather than delivered as a native
103
+ auto-height API.
104
+
105
+ Lumut instead uses TanStack Virtual's dynamic measurement model: unseen rows
106
+ have an estimate, rendered rows are measured, and the virtualizer adjusts
107
+ geometry as measurements arrive. `max_row_height` makes that refinement
108
+ bounded and predictable after column resizing.
109
+
110
+ Maintenance is a second consideration. Glide's most recent stable GitHub
111
+ release is 6.0.3 (February 2024), while development since then has appeared as
112
+ intermittent 6.0.4 alpha commits. That does not mean the project is abandoned,
113
+ but it makes an upstream change to its scroll geometry a higher-risk dependency
114
+ for an experimental component. Lumut depends on the MIT-licensed TanStack
115
+ Table and TanStack Virtual projects instead, and owns only the narrow editor
116
+ surface it needs.
117
+
118
+ References:
119
+
120
+ - [Glide `rowHeight` API](https://github.com/glideapps/glide-data-grid/blob/main/packages/core/API.md)
121
+ - [Glide issue #581: Make Row Height Fit Content](https://github.com/glideapps/glide-data-grid/issues/581)
122
+ - [Glide releases](https://github.com/glideapps/glide-data-grid/releases)
123
+ - [TanStack Virtual dynamic measurement example](https://tanstack.com/virtual/latest/docs/framework/react/examples/dynamic)
124
+
125
+ ## Install and use
126
+
127
+ Install the published package with:
128
+
129
+ ```bash
130
+ uv pip install lumut
131
+ ```
132
+
133
+ For local development, build the JavaScript bundle before using the editable
134
+ package:
135
+
136
+ ```bash
137
+ uv pip install -e .
138
+ npm install
139
+ npm run build
140
+ ```
141
+
142
+ ```python
143
+ import marimo as mo
144
+ from lumut import exp_data_editor
145
+
146
+ editor = mo.ui.anywidget(exp_data_editor(
147
+ [
148
+ {"name": "Ada", "notes": "A long value that can wrap naturally."},
149
+ {"name": "Grace", "notes": "Another editable row."},
150
+ ],
151
+ label="People",
152
+ editable_columns=["notes"],
153
+ max_row_height=160,
154
+ ))
155
+
156
+ editor
157
+ ```
158
+
159
+ `editor.value` is the edited list of row dictionaries.
160
+
161
+ ## Glide rough resize experiment
162
+
163
+ `data_editor_enchance` intentionally retains the spelling in its public name.
164
+ It is a Glide Data Grid implementation of the same small input/value contract
165
+ as `exp_data_editor` and `mo.ui.data_editor`, with `wrapped_columns` added.
166
+ During a wrapped-column resize it estimates a single capped row height from the
167
+ visible row window plus a 20-row buffer. On pointer release it applies that
168
+ sampled height to all rows. This avoids an O(N) measurement pass while dragging
169
+ but is an approximation, not content-fit auto-height: off-screen rows can be
170
+ over- or under-sized.
171
+
172
+ It deliberately pins the browser bundle to React 18: Glide 6.0.3 declares
173
+ React 16–18 peer support, whereas the TanStack-only editor has no such Glide
174
+ constraint.
175
+
176
+ Use this only for experimentation with eager small-to-medium data. Glide's
177
+ variable `rowHeight` callback still makes its scroll geometry a poor fit for a
178
+ million-row exact-auto-height editor. For a measured, bounded-height design,
179
+ use `exp_data_editor` and continue the planned windowed-data work in
180
+ [issue #1](https://github.com/banditelol/lumut/issues/1).
181
+
182
+ ```python
183
+ from lumut import data_editor_enchance
184
+
185
+ glide_editor = data_editor_enchance(
186
+ [{"notes": "Resize the notes column to try rough wrapping."}],
187
+ editable_columns=["notes"],
188
+ wrapped_columns=["notes"],
189
+ max_row_height=160,
190
+ )
191
+ ```
192
+
193
+ ## Scope
194
+
195
+ This first experiment intentionally includes typed cell editing, column resize,
196
+ wrapped text, capped dynamic heights, and the AnyWidget value bridge. It does
197
+ not yet implement spreadsheet range selection, fill handles, structural
198
+ row/column operations, search, or server/windowed data.
199
+
200
+ ## Project conventions
201
+
202
+ The project follows the useful parts of [wigglystuff](https://github.com/koaning/wigglystuff): a Python AnyWidget class, a bundled JavaScript entry point,
203
+ static assets packaged by Hatchling, a `Makefile`, and focused Python tests.
204
+
205
+ `AGENTS.md` is the Codex-native replacement for wigglystuff's Claude-facing
206
+ instructions. `.codex/workflows.md` records the equivalent development
207
+ commands. Conductor's declarative workspace schema (`.conductor/settings.toml`)
208
+ has no Codex equivalent, so it is intentionally not copied; the command mapping
209
+ is documented in `.codex/workflows.md` instead.
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ make install
215
+ make build
216
+ make test
217
+ make dev
218
+ ```
219
+
220
+ To inspect the widget in marimo after installing the package locally, run
221
+ `uv run --with marimo marimo edit demos/exp_data_editor.py`.
222
+
223
+ ## Releasing to PyPI
224
+
225
+ Releases use GitHub Actions and [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so a PyPI API token is not stored in this repository.
226
+
227
+ One-time setup:
228
+
229
+ 1. If `lumut` is not yet on PyPI, add a **pending** Trusted Publisher in your PyPI account settings. If it already exists, add a Trusted Publisher in that project's Publishing settings. In both cases use project name `lumut`, owner `banditelol`, repository `lumut`, workflow file `publish.yml`, and environment `pypi`.
230
+ 2. The first successful run of a pending publisher creates the PyPI project; its configured project name must exactly match `project.name`.
231
+ 3. In GitHub, create the protected `pypi` environment if you want approvals before publication. The workflow works without protection too.
232
+
233
+ For each release, update `version` in `pyproject.toml`, add release notes, and create a GitHub release whose tag is `v<version>` (for example, `v0.1.0`). The publishing workflow rebuilds the frontend, builds and checks the wheel and source distribution, then uploads those exact artifacts to PyPI. To verify a build locally before creating the release, run `make package` and `uvx twine check dist/*`.
lumut-0.1.0/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # lumut
2
+
3
+ An experimental, MIT-licensed AnyWidget data editor for notebook runtimes. It
4
+ uses TanStack Table for table state and TanStack Virtual for lazy, measured row
5
+ heights.
6
+
7
+ ## Gallery
8
+
9
+ <div align="center">
10
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">
11
+ <img src="docs/assets/gallery/exp-data-editor.svg" alt="The exp_data_editor showing wrapped text rows" width="640">
12
+ </a>
13
+ <br>
14
+ <strong>exp_data_editor</strong><br>
15
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">molab</a> ·
16
+ <a href="https://adityarp.com/lumut/reference/exp-data-editor/">API</a> ·
17
+ <a href="docs/reference/exp-data-editor.md">Markdown</a>
18
+ </div>
19
+
20
+ <div align="center">
21
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">
22
+ <img src="docs/assets/gallery/data-editor-enchance.svg" alt="The data_editor_enchance Glide editor showing wrapped text rows" width="640">
23
+ </a>
24
+ <br>
25
+ <strong>data_editor_enchance</strong> (Glide rough resize experiment)<br>
26
+ <a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">molab</a> ·
27
+ <a href="https://adityarp.com/lumut/reference/data-editor-enchance/">API</a> ·
28
+ <a href="docs/reference/data-editor-enchance.md">Markdown</a>
29
+ </div>
30
+
31
+ The preview is an illustration of the widget. The MoLab demo runs the actual
32
+ AnyWidget from this repository.
33
+
34
+ ## Why it exists
35
+
36
+ `lumut.exp_data_editor` explores a small, marimo-compatible data-editor
37
+ contract without inheriting a grid library's entire feature surface. It accepts
38
+ row-oriented data, column-oriented data, or pandas-like dataframes; honours
39
+ `editable_columns`; and synchronizes fully edited rows as `value`.
40
+
41
+ Rows are measured only after they are rendered. Wrapped content can grow a row,
42
+ but never beyond `max_row_height`. Resizing a column triggers a remeasurement
43
+ of rendered rows, rather than an O(N) pass over all data.
44
+
45
+ ## Why this does not use Glide Data Grid
46
+
47
+ Glide Data Grid remains a capable canvas-based editor, but its public
48
+ `rowHeight` API is either one constant or a callback indexed by row. In the
49
+ current implementation, the callback path requires the grid to walk row
50
+ heights to derive total height and map a scroll offset to a row. A cached
51
+ callback therefore does not make exact variable wrapped-row height suitable for
52
+ very large datasets. The upstream "fit content" request was discussed as a
53
+ consumer-side canvas-measurement workaround rather than delivered as a native
54
+ auto-height API.
55
+
56
+ Lumut instead uses TanStack Virtual's dynamic measurement model: unseen rows
57
+ have an estimate, rendered rows are measured, and the virtualizer adjusts
58
+ geometry as measurements arrive. `max_row_height` makes that refinement
59
+ bounded and predictable after column resizing.
60
+
61
+ Maintenance is a second consideration. Glide's most recent stable GitHub
62
+ release is 6.0.3 (February 2024), while development since then has appeared as
63
+ intermittent 6.0.4 alpha commits. That does not mean the project is abandoned,
64
+ but it makes an upstream change to its scroll geometry a higher-risk dependency
65
+ for an experimental component. Lumut depends on the MIT-licensed TanStack
66
+ Table and TanStack Virtual projects instead, and owns only the narrow editor
67
+ surface it needs.
68
+
69
+ References:
70
+
71
+ - [Glide `rowHeight` API](https://github.com/glideapps/glide-data-grid/blob/main/packages/core/API.md)
72
+ - [Glide issue #581: Make Row Height Fit Content](https://github.com/glideapps/glide-data-grid/issues/581)
73
+ - [Glide releases](https://github.com/glideapps/glide-data-grid/releases)
74
+ - [TanStack Virtual dynamic measurement example](https://tanstack.com/virtual/latest/docs/framework/react/examples/dynamic)
75
+
76
+ ## Install and use
77
+
78
+ Install the published package with:
79
+
80
+ ```bash
81
+ uv pip install lumut
82
+ ```
83
+
84
+ For local development, build the JavaScript bundle before using the editable
85
+ package:
86
+
87
+ ```bash
88
+ uv pip install -e .
89
+ npm install
90
+ npm run build
91
+ ```
92
+
93
+ ```python
94
+ import marimo as mo
95
+ from lumut import exp_data_editor
96
+
97
+ editor = mo.ui.anywidget(exp_data_editor(
98
+ [
99
+ {"name": "Ada", "notes": "A long value that can wrap naturally."},
100
+ {"name": "Grace", "notes": "Another editable row."},
101
+ ],
102
+ label="People",
103
+ editable_columns=["notes"],
104
+ max_row_height=160,
105
+ ))
106
+
107
+ editor
108
+ ```
109
+
110
+ `editor.value` is the edited list of row dictionaries.
111
+
112
+ ## Glide rough resize experiment
113
+
114
+ `data_editor_enchance` intentionally retains the spelling in its public name.
115
+ It is a Glide Data Grid implementation of the same small input/value contract
116
+ as `exp_data_editor` and `mo.ui.data_editor`, with `wrapped_columns` added.
117
+ During a wrapped-column resize it estimates a single capped row height from the
118
+ visible row window plus a 20-row buffer. On pointer release it applies that
119
+ sampled height to all rows. This avoids an O(N) measurement pass while dragging
120
+ but is an approximation, not content-fit auto-height: off-screen rows can be
121
+ over- or under-sized.
122
+
123
+ It deliberately pins the browser bundle to React 18: Glide 6.0.3 declares
124
+ React 16–18 peer support, whereas the TanStack-only editor has no such Glide
125
+ constraint.
126
+
127
+ Use this only for experimentation with eager small-to-medium data. Glide's
128
+ variable `rowHeight` callback still makes its scroll geometry a poor fit for a
129
+ million-row exact-auto-height editor. For a measured, bounded-height design,
130
+ use `exp_data_editor` and continue the planned windowed-data work in
131
+ [issue #1](https://github.com/banditelol/lumut/issues/1).
132
+
133
+ ```python
134
+ from lumut import data_editor_enchance
135
+
136
+ glide_editor = data_editor_enchance(
137
+ [{"notes": "Resize the notes column to try rough wrapping."}],
138
+ editable_columns=["notes"],
139
+ wrapped_columns=["notes"],
140
+ max_row_height=160,
141
+ )
142
+ ```
143
+
144
+ ## Scope
145
+
146
+ This first experiment intentionally includes typed cell editing, column resize,
147
+ wrapped text, capped dynamic heights, and the AnyWidget value bridge. It does
148
+ not yet implement spreadsheet range selection, fill handles, structural
149
+ row/column operations, search, or server/windowed data.
150
+
151
+ ## Project conventions
152
+
153
+ The project follows the useful parts of [wigglystuff](https://github.com/koaning/wigglystuff): a Python AnyWidget class, a bundled JavaScript entry point,
154
+ static assets packaged by Hatchling, a `Makefile`, and focused Python tests.
155
+
156
+ `AGENTS.md` is the Codex-native replacement for wigglystuff's Claude-facing
157
+ instructions. `.codex/workflows.md` records the equivalent development
158
+ commands. Conductor's declarative workspace schema (`.conductor/settings.toml`)
159
+ has no Codex equivalent, so it is intentionally not copied; the command mapping
160
+ is documented in `.codex/workflows.md` instead.
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ make install
166
+ make build
167
+ make test
168
+ make dev
169
+ ```
170
+
171
+ To inspect the widget in marimo after installing the package locally, run
172
+ `uv run --with marimo marimo edit demos/exp_data_editor.py`.
173
+
174
+ ## Releasing to PyPI
175
+
176
+ Releases use GitHub Actions and [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so a PyPI API token is not stored in this repository.
177
+
178
+ One-time setup:
179
+
180
+ 1. If `lumut` is not yet on PyPI, add a **pending** Trusted Publisher in your PyPI account settings. If it already exists, add a Trusted Publisher in that project's Publishing settings. In both cases use project name `lumut`, owner `banditelol`, repository `lumut`, workflow file `publish.yml`, and environment `pypi`.
181
+ 2. The first successful run of a pending publisher creates the PyPI project; its configured project name must exactly match `project.name`.
182
+ 3. In GitHub, create the protected `pypi` environment if you want approvals before publication. The workflow works without protection too.
183
+
184
+ For each release, update `version` in `pyproject.toml`, add release notes, and create a GitHub release whose tag is `v<version>` (for example, `v0.1.0`). The publishing workflow rebuilds the frontend, builds and checks the wheel and source distribution, then uploads those exact artifacts to PyPI. To verify a build locally before creating the release, run `make package` and `uvx twine check dist/*`.
@@ -0,0 +1,11 @@
1
+ """Experimental AnyWidget components for notebook environments."""
2
+
3
+ from .exp_data_editor import ExpDataEditor, exp_data_editor
4
+ from .data_editor_enhance import DataEditorEnchance, data_editor_enchance
5
+
6
+ __all__ = [
7
+ "DataEditorEnchance",
8
+ "ExpDataEditor",
9
+ "data_editor_enchance",
10
+ "exp_data_editor",
11
+ ]
@@ -0,0 +1,100 @@
1
+ """An experimental Glide Data Grid AnyWidget with rough wrapped-row sizing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping, Sequence
6
+ from pathlib import Path
7
+ from typing import Any, Literal
8
+
9
+ import anywidget
10
+ import traitlets
11
+
12
+ from .exp_data_editor import (
13
+ EditableColumns,
14
+ Row,
15
+ WrappedColumns,
16
+ _column_names,
17
+ _infer_field_types,
18
+ _to_rows,
19
+ _validate_columns,
20
+ )
21
+
22
+
23
+ class DataEditorEnchance(anywidget.AnyWidget):
24
+ """Edit tabular data with Glide Data Grid and approximate wrapped heights.
25
+
26
+ This is deliberately separate from :class:`lumut.ExpDataEditor`. It carries
27
+ the experimental Glide strategy from marimo's ``feat/data-editor-wrap``:
28
+ when a wrapped column is resized, it estimates the maximum height in the
29
+ visible window (plus a small buffer), temporarily applies it there, then
30
+ applies that sampled height to all data rows when the pointer is released.
31
+
32
+ The method avoids measuring every row during a resize, but it is not exact:
33
+ rows outside the sample can be too tall or too short. Glide's variable-row
34
+ callback also has poor scroll-geometry characteristics for huge datasets,
35
+ so this widget is for experimentation and small-to-medium eager data only.
36
+ """
37
+
38
+ _esm = Path(__file__).parent / "static" / "data-editor-enchance.js"
39
+
40
+ data = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
41
+ value = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
42
+ editable_columns = traitlets.Any("all").tag(sync=True)
43
+ wrapped_columns = traitlets.Any(default_value=[]).tag(sync=True)
44
+ field_types = traitlets.Dict(default_value={}).tag(sync=True)
45
+ label = traitlets.Unicode("").tag(sync=True)
46
+ width = traitlets.Unicode("100%").tag(sync=True)
47
+ height = traitlets.Int(450).tag(sync=True)
48
+ max_row_height = traitlets.Int(240).tag(sync=True)
49
+ estimated_row_height = traitlets.Int(34).tag(sync=True)
50
+ wrapped_row_height_strategy = traitlets.Unicode(
51
+ "approxIncrementalRough"
52
+ ).tag(sync=True)
53
+
54
+ def __init__(
55
+ self,
56
+ data: Sequence[Mapping[str, Any]] | Mapping[str, Sequence[Any]] | Any,
57
+ *,
58
+ label: str = "",
59
+ editable_columns: EditableColumns = "all",
60
+ wrapped_columns: WrappedColumns = (),
61
+ max_row_height: int = 240,
62
+ estimated_row_height: int = 34,
63
+ width: str = "100%",
64
+ height: int = 450,
65
+ ) -> None:
66
+ if max_row_height < estimated_row_height:
67
+ raise ValueError("max_row_height must be at least estimated_row_height")
68
+ if height <= 0:
69
+ raise ValueError("height must be positive")
70
+
71
+ rows: list[Row] = _to_rows(data)
72
+ columns = list(rows[0]) if rows else _column_names(data)
73
+ _validate_columns(editable_columns, columns, "editable_columns")
74
+ _validate_columns(wrapped_columns, columns, "wrapped_columns")
75
+
76
+ super().__init__(
77
+ data=rows,
78
+ value=rows,
79
+ editable_columns=(
80
+ "all" if editable_columns == "all" else list(editable_columns)
81
+ ),
82
+ wrapped_columns=(
83
+ "all" if wrapped_columns == "all" else list(wrapped_columns)
84
+ ),
85
+ field_types=_infer_field_types(rows, columns),
86
+ label=label,
87
+ width=width,
88
+ height=height,
89
+ max_row_height=max_row_height,
90
+ estimated_row_height=estimated_row_height,
91
+ )
92
+
93
+
94
+ def data_editor_enchance(
95
+ data: Sequence[Mapping[str, Any]] | Mapping[str, Sequence[Any]] | Any,
96
+ **kwargs: Any,
97
+ ) -> DataEditorEnchance:
98
+ """Create a :class:`DataEditorEnchance` (name retained intentionally)."""
99
+
100
+ return DataEditorEnchance(data, **kwargs)