dash-aggrid-js 0.2.1__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.
File without changes
@@ -0,0 +1,12 @@
1
+ include dash_aggrid_js/dash_aggrid.min.js
2
+ include dash_aggrid_js/dash_aggrid.min.js.map
3
+ include dash_aggrid_js/async-*.js
4
+ include dash_aggrid_js/async-*.js.map
5
+ include dash_aggrid_js/*-shared.js
6
+ include dash_aggrid_js/*-shared.js.map
7
+ include dash_aggrid_js/metadata.json
8
+ include dash_aggrid_js/package-info.json
9
+ include dash_aggrid/__init__.py
10
+ include README.md
11
+ include LICENSE
12
+ include package.json
@@ -0,0 +1,619 @@
1
+ Metadata-Version: 2.4
2
+ Name: dash-aggrid-js
3
+ Version: 0.2.1
4
+ Summary: Tiny Dash wrapper that mounts AgGridReact using a JS config registry
5
+ Home-page: https://github.com/ScottTpirate/dash-aggrid
6
+ Author: Gulf of Analytics <dev@goa.local>
7
+ License: MIT
8
+ Project-URL: Source, https://github.com/ScottTpirate/dash-aggrid
9
+ Project-URL: Tracker, https://github.com/ScottTpirate/dash-aggrid/issues
10
+ Classifier: Framework :: Dash
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: dash>=2.0.0
15
+ Dynamic: author
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: license
21
+ Dynamic: license-file
22
+ Dynamic: project-url
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
26
+
27
+ # dash-aggrid-js
28
+
29
+ dash-aggrid-js (Python import: `dash_aggrid_js`) is a deliberately thin Dash wrapper around **AgGridReact**. It mounts the AG Grid React component directly, so you can copy examples from the AG Grid docs, drop them into a browser-side config registry, and the grid just works inside Dash.
30
+
31
+ > ℹ️ The legacy `dash_aggrid` import still works and simply re-exports `dash_aggrid_js`, so existing apps keep running while you upgrade.
32
+
33
+ > ⚠️ **Pick one wrapper per app.** AgGridJS is not meant to run alongside `dash-ag-grid`; loading both introduces duplicate CSS, overlapping themes, and conflicting event glue. Choose one approach per Dash project.
34
+
35
+ ---
36
+
37
+ ## Table of contents
38
+
39
+ - [Why this wrapper?](#why-this-wrapper)
40
+ - [Quick start](#quick-start)
41
+ - [Installation](#installation)
42
+ - [Creating the config registry](#creating-the-config-registry)
43
+ - [Using `AgGridJS` in Dash](#using-aggridjs-in-dash)
44
+ - [Dash props & event bridge](#dash-props--event-bridge)
45
+ - [Passing arguments (`configArgs`)](#passing-arguments-configargs)
46
+ - [Styling & theming](#styling--theming)
47
+ - [Enterprise support](#enterprise-support)
48
+ - [Advanced patterns](#advanced-patterns)
49
+ - [Server-side row model (SSRM)](#server-side-row-model-ssrm)
50
+ - [Managing asset size](#managing-asset-size)
51
+ - [Developing the component](#developing-the-component)
52
+ - [Testing](#testing)
53
+ - [Known quirks](#known-quirks)
54
+ - [Migration checklist (dash-ag-grid → AgGridJS)](#migration-checklist-dash-ag-grid--aggridjs)
55
+ - [AI assistant playbook](#ai-assistant-playbook)
56
+ - [Packaging & distribution](#packaging--distribution)
57
+ - [Future enhancements](#future-enhancements)
58
+ - [FAQ](#faq)
59
+
60
+ ---
61
+
62
+ ## Why this wrapper?
63
+
64
+ Dash ships with Python-to-JS property mappings for many components. AG Grid already provides a rich React interface; we wanted a wrapper that stays out of the way:
65
+
66
+ - **No prop translation.** The exact object you pass to `AgGridReact` in the docs is what the wrapper forwards.
67
+ - **Pure JavaScript configs.** Keep complex row models, value formatters, renderers, and callbacks in JavaScript without serialising through Python.
68
+ - **Minimal Dash glue.** Only a handful of grid events (selection, filters, sorting, edits) are mirrored back to Dash for callbacks.
69
+ - **AG Grid v34.x** compatible (Community + optional Enterprise).
70
+
71
+ ---
72
+
73
+ ## Quick start
74
+
75
+ Clone the repo and run the sample app:
76
+
77
+ ```bash
78
+ git clone https://github.com/ScottTpirate/dash-aggrid.git
79
+ cd dash-aggrid
80
+ npm install
81
+ npm run build
82
+ python -m pip install -e .
83
+ python app.py
84
+ ```
85
+
86
+ Visit http://127.0.0.1:8050 for the sample app. `demo_app.py` renders three grids (including an integrated chart) and a standalone AG Charts example powered by `AgChartsJS`. The configs live in `assets/aggrid-configs.js` and `assets/agcharts-configs.js`.
87
+
88
+ > Reusing in another project? Install the package into that Dash app, copy the asset, and you’re ready to go.
89
+
90
+ ---
91
+
92
+ ## Installation
93
+
94
+ ### In an existing Dash project
95
+
96
+ 1. Install the Python package (local path shown; swap in the PyPI name `dash-aggrid-js` when using the published wheel):
97
+
98
+ ```bash
99
+ python -m pip install /path/to/dash_aggrid_js
100
+ ```
101
+
102
+ 2. If working from source, build the JS bundle once:
103
+
104
+ ```bash
105
+ cd /path/to/dash-aggrid
106
+ npm install
107
+ npm run build
108
+ ```
109
+
110
+ 3. Copy `assets/aggrid-configs.js` into your Dash app’s `assets/` folder (or create your own registry script).
111
+
112
+ 4. Import and use the component:
113
+
114
+ ```python
115
+ from dash_aggrid_js import AgGridJS
116
+ ```
117
+
118
+ Dash will serve the JS bundle automatically when the component is requested.
119
+
120
+ ---
121
+
122
+ ## Creating the config registry
123
+
124
+ `AgGridJS` looks up options from `window.AGGRID_CONFIGS`. Keep `assets/aggrid-configs.js` as your registry file: each key represents a grid and returns either a plain object or a factory function that the wrapper will call.
125
+
126
+ ```javascript
127
+ // assets/aggrid-configs.js
128
+ (function registerAgGridConfigs() {
129
+ const { themeQuartz } = window.AgGridJsThemes || {};
130
+
131
+ const configs = {
132
+ // Static config
133
+ 'feature-grid': {
134
+ columnDefs: [
135
+ { field: 'make' },
136
+ { field: 'model' },
137
+ { field: 'price', valueFormatter: (p) => Intl.NumberFormat().format(p.value) },
138
+ ],
139
+ defaultColDef: { sortable: true, filter: true, resizable: true },
140
+ rowData: window.FEATURE_GRID_ROWS || [],
141
+ theme: themeQuartz?.withParams?.({ borderRadius: 12 }),
142
+ },
143
+
144
+ // Factory config – reads configArgs/dashProps
145
+ 'sales-grid': ({ configArgs }) => ({
146
+ columnDefs: getSalesColumns(configArgs),
147
+ defaultColDef: { sortable: true, filter: true, resizable: true },
148
+ rowData: configArgs?.rowData ?? [],
149
+ }),
150
+
151
+ // SSRM grid – demonstrates server-side datasource wiring
152
+ 'ssrm-grid': function ssrmGrid(context) {
153
+ const gridId = context?.id || 'ssrm-grid';
154
+ const ssrmArgs = context?.configArgs?.ssrm || {};
155
+ const baseEndpoint = String(ssrmArgs.endpoint || '/_aggrid/ssrm').replace(/\/$/, '');
156
+
157
+ const datasource = {
158
+ getRows(params) {
159
+ fetch(`${baseEndpoint}/${encodeURIComponent(gridId)}`, {
160
+ method: 'POST',
161
+ headers: { 'Content-Type': 'application/json' },
162
+ body: JSON.stringify(params.request || {}),
163
+ })
164
+ .then(async (response) => {
165
+ const payload = await response.json().catch(() => null);
166
+ if (!response.ok || !payload || typeof payload !== 'object') {
167
+ throw new Error(payload?.error || `HTTP ${response.status}`);
168
+ }
169
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
170
+ const rowCount = typeof payload.rowCount === 'number' ? payload.rowCount : undefined;
171
+ params.success({ rowData: rows, rowCount });
172
+ })
173
+ .catch((err) => {
174
+ console.error('AgGridJS SSRM request failed', err);
175
+ params.fail();
176
+ });
177
+ },
178
+ };
179
+
180
+ return {
181
+ columnDefs: buildSsrMColumns(),
182
+ defaultColDef: baseColumnDefaults(),
183
+ autoGroupColumnDef: { minWidth: 220 },
184
+ rowModelType: 'serverSide',
185
+ cacheBlockSize: 100,
186
+ sideBar: ['columns', 'filters'],
187
+ rowGroupPanelShow: 'always',
188
+ serverSideDatasource: datasource,
189
+ theme: themeQuartz,
190
+ };
191
+ },
192
+ };
193
+
194
+ window.AGGRID_CONFIGS = { ...(window.AGGRID_CONFIGS || {}), ...configs };
195
+ }());
196
+ ```
197
+
198
+ Registry entries can be either:
199
+
200
+ - **Static** – an object literal (useful for simple read-only grids).
201
+ - **Factory** – a function receiving `{ id, configKey, configArgs, dashProps }`. Use factories when you need runtime arguments (locale toggles, SSRM metadata, chart options) or when wiring server-side datasources.
202
+
203
+ The demo app follows this structure for every grid (`feature-grid`, `sales-grid`, `analytics-grid`, `ssrm-grid`) and does the same for charts via `assets/agcharts-configs.js`. Keeping everything in these registries keeps the asset declarative and makes it simple to share helpers across grids.
204
+
205
+ ---
206
+
207
+ ## Using `AgGridJS` in Dash
208
+
209
+ ```python
210
+ from dash import Dash, html, Output, Input
211
+ from dash_aggrid_js import AgGridJS
212
+
213
+ app = Dash(__name__) # serves ./assets/aggrid-configs.js automatically
214
+
215
+ app.layout = html.Div(
216
+ [
217
+ AgGridJS(id="sales", configKey="sales-grid", style={"height": 420}),
218
+ html.Pre(id="debug", style={"marginTop": "1rem"}),
219
+ ]
220
+ )
221
+
222
+
223
+ @app.callback(Output("debug", "children"), Input("sales", "selectedRows"))
224
+ def display_selected(rows):
225
+ if not rows:
226
+ return "No selection"
227
+ return f"Selected rows ({len(rows)}):\n{rows}"
228
+
229
+
230
+ if __name__ == "__main__":
231
+ app.run(debug=True)
232
+ ```
233
+
234
+ `configKey` is the only required prop. The wrapper will fetch the config, mount `AgGridReact`, and mirror key events back to Dash.
235
+
236
+ ---
237
+
238
+ ## Dash props & event bridge
239
+
240
+ | Prop | Direction | Description |
241
+ |-----------------|-----------|-------------|
242
+ | `configKey` | Dash → JS | Required. Looks up `window.AGGRID_CONFIGS[configKey]`. |
243
+ | `configArgs` | Dash → JS | Optional JSON-serialisable payload passed to config factory functions. |
244
+ | `className` | Dash → JS | Container class (defaults to `ag-theme-quartz`). |
245
+ | `style` | Dash → JS | Inline styles for sizing/positioning. |
246
+ | `rowData` | Dash → JS | Optional row data array supplied directly from Dash; overrides `rowData` defined in the JS config. |
247
+ | `selectedRows` | JS → Dash | Array of selected rows (`api.getSelectedRows()`). |
248
+ | `filterModel` | JS → Dash | Current filter model (`api.getFilterModel()`). |
249
+ | `sortModel` | JS → Dash | Simplified array of columns with active sorting (`colId`, `sort`, `sortIndex`). |
250
+ | `editedCells` | JS → Dash | Single-element array describing the most recent cell edit (`rowId`, `colId`, `oldValue`, `newValue`). |
251
+
252
+ The wrapper:
253
+
254
+ - Runs user-defined AG Grid handlers first, then calls `setProps`.
255
+ - Stores `gridApi` on `onGridReady` so other events can read selection/filter/sort state.
256
+ - Emits initial `selectedRows`, `filterModel`, and `sortModel` after the grid becomes ready.
257
+
258
+ Need more events? Add them to your config and call `setProps` manually.
259
+
260
+ ---
261
+
262
+ ## Passing arguments (`configArgs`)
263
+
264
+ Use `configArgs` when Dash needs to parameterise the grid:
265
+
266
+ ```python
267
+ @app.callback(Output("sales", "configArgs"), Input("region-dropdown", "value"))
268
+ def choose_region(region):
269
+ return {"region": region}
270
+ ```
271
+
272
+ In JS:
273
+
274
+ ```javascript
275
+ window.AGGRID_CONFIGS['sales-grid'] = ({ configArgs }) => ({
276
+ columnDefs: getColumnsFor(configArgs?.region),
277
+ rowData: [],
278
+ });
279
+ ```
280
+
281
+ `configArgs` must be JSON-serialisable. For functions or class instances, keep them inside the registry instead.
282
+
283
+ ---
284
+
285
+ ## Styling & theming
286
+
287
+ AG Grid v34 defaults to the **Theming API**. The wrapper exposes common themes on `window.AgGridJsThemes` so your asset can pick them up on a per-grid basis:
288
+
289
+ ```javascript
290
+ const { themeQuartz, themeAlpine } = window.AgGridJsThemes || {};
291
+
292
+ window.AGGRID_CONFIGS['sales-grid'] = {
293
+ rowData: [...],
294
+ columnDefs: [...],
295
+ theme: themeQuartz.withParams({
296
+ accentColor: '#2563eb',
297
+ headerBackgroundColor: '#0f172a',
298
+ headerTextColor: '#e2e8f0'
299
+ })
300
+ };
301
+
302
+ window.AGGRID_CONFIGS['inventory-grid'] = ({ configArgs }) => ({
303
+ rowData: [...],
304
+ columnDefs: [...],
305
+ theme: themeAlpine.withParams({
306
+ accentColor: configArgs?.locale === 'ja-JP' ? '#f97316' : '#14b8a6',
307
+ borderRadius: 10
308
+ })
309
+ });
310
+ ```
311
+
312
+ - `style` still controls the container height/width. Set the height explicitly so the grid has room to render.
313
+ - `className` is optional and can be used for extra layout styling. Theme classes are no longer required when using the theming API.
314
+ - If you want to keep the legacy CSS themes instead, set `theme: 'legacy'` in your registry entry and include the old CSS manually.
315
+ - Integrated charts rely on AG Grid Enterprise. If the module is not present, the grid will skip chart creation automatically.
316
+
317
+ ## Standalone AG Charts
318
+
319
+ `AgChartsJS` wraps the charting engine from `ag-charts-community` without the React helper. Define chart options in `window.AGCHART_CONFIGS` and point the component at a key:
320
+
321
+ ```javascript
322
+ // assets/agcharts-configs.js
323
+ window.AGCHART_CONFIGS = window.AGCHART_CONFIGS || {};
324
+
325
+ window.AGCHART_CONFIGS['revenue-chart'] = ({ configArgs }) => ({
326
+ data: [...],
327
+ title: { text: 'Quarterly Revenue' },
328
+ series: [
329
+ { type: 'bar', direction: 'vertical', xKey: 'quarter', yKey: 'north' },
330
+ { type: 'bar', direction: 'vertical', xKey: 'quarter', yKey: 'emea' },
331
+ ],
332
+ theme: {
333
+ baseTheme: 'ag-default',
334
+ palette: {
335
+ fills: [configArgs?.accentColor || '#2563eb', '#f97316'],
336
+ strokes: ['#1e3a8a', '#9a3412'],
337
+ },
338
+ },
339
+ });
340
+ ```
341
+
342
+ In Dash:
343
+
344
+ ```python
345
+ from dash_aggrid_js import AgChartsJS
346
+
347
+ AgChartsJS(id="revenue", optionsKey="revenue-chart", style={"height": 360})
348
+ ```
349
+
350
+ The wrapper takes either `options` (inline chart definition) or an `optionsKey` that resolves to `window.AGCHART_CONFIGS[key]`. Options factories receive `{ optionsKey, configArgs, dashProps }` just like grid configs.
351
+
352
+ ---
353
+
354
+ ## Enterprise support
355
+
356
+ Enterprise features are fully supported. To enable:
357
+
358
+ 1. Ensure `ag-grid-enterprise` is installed (this package already includes it).
359
+ 2. Expose the license key on the window before configs load:
360
+
361
+ ```javascript
362
+ window.AGGRID_LICENSE_KEY = "<YOUR KEY>";
363
+ ```
364
+
365
+ The sample asset calls `LicenseManager.setLicenseKey` if the key exists. Remember: the key is visible client-side. Inject it dynamically if you prefer not to store it in source control.
366
+
367
+ ---
368
+
369
+ ## Advanced patterns
370
+
371
+ - **Server-side row model:** See the [dedicated SSRM walkthrough](#server-side-row-model-ssrm) for the SQL helper, distinct endpoints, and asset patch.
372
+ - **Multiple variants:** Store each variant under its own key (`window.AGGRID_CONFIGS['sales/daily']`, `['sales/monthly']`), or return different objects inside a single factory.
373
+ - **Custom Dash outputs:** Pass `context` or other callbacks through the config and call `setProps` yourself for bespoke events (row drag, clipboard, etc.).
374
+ - **Sharing state between grids:** Keep shared data on `window` or a dedicated JS module; use Dash callbacks on `selectedRows`/`filterModel` to sync across components.
375
+
376
+ ---
377
+
378
+ ## Server-side row model (SSRM)
379
+
380
+ AgGridJS pairs a DuckDB-aware SQL builder with Dash hooks so server-side row-model grids work without manual Flask routes. Any grid that declares `configArgs={"ssrm": ...}` automatically registers JSON endpoints for data blocks and Set Filter values.
381
+
382
+ ### 1. Configure the grid in Dash
383
+
384
+ ```python
385
+ from pathlib import Path
386
+
387
+ from dash_aggrid_js import AgGridJS
388
+
389
+ AgGridJS(
390
+ id="orders-grid",
391
+ configKey="orders-ssrm",
392
+ style={"height": 420},
393
+ configArgs={
394
+ "ssrm": {
395
+ "duckdb_path": str(Path("data/orders.duckdb")),
396
+ "table": "public.orders", # or provide `builder` for custom SQL
397
+ # "builder": lambda req: sql_for(req, "(SELECT * FROM ... ) AS src"),
398
+ # "endpoint": "/_aggrid/ssrm" # optional route prefix override
399
+ }
400
+ },
401
+ )
402
+ ```
403
+
404
+ - `duckdb_path` is required and is opened read-only for each request.
405
+ - Supply either a `table`/sub-query string or a custom `builder(request) -> SQL` callable (advanced scenarios).
406
+ - Optional `endpoint` lets you change the HTTP prefix (defaults to `/_aggrid/ssrm`).
407
+
408
+ ### 2. Wire the asset config
409
+
410
+ The JS registry just needs to opt the grid into SSRM so the datasource can post to the generated endpoint; the component takes care of Set Filter values automatically.
411
+
412
+ ```javascript
413
+ // assets/aggrid-configs.js
414
+ (function () {
415
+ const { themeQuartz } = window.AgGridJsThemes || {};
416
+
417
+ window.AGGRID_CONFIGS['orders-ssrm'] = (context) => {
418
+ const gridId = context?.id || 'orders-grid';
419
+ const ssrmArgs = context?.configArgs?.ssrm || {};
420
+ const baseEndpoint = (ssrmArgs.endpoint || '/_aggrid/ssrm').replace(/\/$/, '');
421
+
422
+ return {
423
+ columnDefs: [
424
+ { field: 'order_id', headerName: 'Order ID', maxWidth: 130 },
425
+ { field: 'region', filter: 'agSetColumnFilter', rowGroup: true },
426
+ { field: 'product', minWidth: 160, filter: 'agSetColumnFilter' },
427
+ { field: 'category', minWidth: 150, filter: 'agSetColumnFilter' },
428
+ { field: 'quarter', maxWidth: 140, filter: 'agSetColumnFilter' },
429
+ { field: 'units', type: 'numericColumn', aggFunc: 'sum' },
430
+ {
431
+ field: 'revenue',
432
+ type: 'numericColumn',
433
+ aggFunc: 'sum',
434
+ valueFormatter: (params) => Intl.NumberFormat('en-US', {
435
+ style: 'currency',
436
+ currency: 'USD',
437
+ maximumFractionDigits: 0,
438
+ }).format(params.value || 0),
439
+ },
440
+ ],
441
+ defaultColDef: {
442
+ flex: 1,
443
+ sortable: true,
444
+ filter: true,
445
+ resizable: true,
446
+ enableRowGroup: true,
447
+ enablePivot: true,
448
+ enableValue: true,
449
+ },
450
+ autoGroupColumnDef: { minWidth: 220 },
451
+ rowModelType: 'serverSide',
452
+ cacheBlockSize: 100,
453
+ sideBar: ['columns', 'filters'],
454
+ rowGroupPanelShow: 'always',
455
+ serverSideDatasource: {
456
+ getRows(params) {
457
+ fetch(`${baseEndpoint}/${encodeURIComponent(gridId)}`, {
458
+ method: 'POST',
459
+ headers: { 'Content-Type': 'application/json' },
460
+ body: JSON.stringify(params.request || {}),
461
+ })
462
+ .then(async (response) => {
463
+ const payload = await response.json().catch(() => null);
464
+ if (!response.ok || !payload || typeof payload !== 'object') {
465
+ throw new Error(payload?.error || `HTTP ${response.status}`);
466
+ }
467
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
468
+ const rowCount = typeof payload.rowCount === 'number' ? payload.rowCount : undefined;
469
+ params.success({ rowData: rows, rowCount });
470
+ })
471
+ .catch((err) => {
472
+ console.error('AgGridJS SSRM request failed', err);
473
+ params.fail();
474
+ });
475
+ },
476
+ },
477
+ theme: themeQuartz,
478
+ };
479
+ };
480
+ }());
481
+ ```
482
+
483
+ ### 3. Notes
484
+
485
+ - Multiple SSRM grids can coexist; give each a unique `id` and `configKey`. Routes are registered automatically per grid/table pair via Dash hooks—no manual Flask code required.
486
+ - Set Filters pick up values via `/distinct` only for columns using `agSetColumnFilter` (or a Multi Filter that includes it). Add that filter type to any columns where you want distinct lookups.
487
+ - Pivot mode currently falls back to basic SQL; if you need pivoted results you’ll need to extend `sql_for` (grouping and aggregation are supported today).
488
+ - Custom builders receive the raw AG Grid request payload. Use `sql_for` inside the builder if you need to compose additional joins or filters.
489
+ - You can still call `sql_for`/`distinct_sql` manually (for exports, reports, etc.) — they mirror the autogenerated queries.
490
+
491
+ ---
492
+ ## Managing asset size
493
+
494
+ `assets/aggrid-configs.js` ships as a single registry for ease of onboarding. Browser caching and HTTP/2 keep it efficient, but for very large apps you can:
495
+
496
+ - Split configs across multiple asset files (one per page/feature) and guard them with early returns (`if (window.location.pathname !== '/reports') return;`).
497
+ - Lazily attach large `rowData` arrays by fetching from the server (`fetch('/api/data').then(...)`) instead of embedding huge literals.
498
+ - Minify/treeshake custom helpers via your build pipeline if you generate assets programmatically.
499
+
500
+ The sample file stays small—only configuration objects and lightweight factories—so no additional bundling is required by default.
501
+
502
+ ---
503
+
504
+ ## Developing the component
505
+
506
+ ```bash
507
+ npm install # install JS dependencies (React, AG Grid, tooling)
508
+ npm run build # build the production bundle + regenerate Dash bindings
509
+ python -m pip install -e . # expose the component as an editable install
510
+ python app.py # run the demo Dash app
511
+ ```
512
+
513
+ - Component source lives in `src/lib/components/AgGridJS.jsx`.
514
+ - Webpack output goes to `dash_aggrid_js/dash_aggrid.min.js`.
515
+ - Python/R/Julia wrappers are regenerated via `dash-generate-components` during `npm run build`.
516
+
517
+ Need to tweak bundling? Edit `webpack.config.js`.
518
+
519
+ ---
520
+
521
+ ## Testing
522
+
523
+ Current repo ships with a simple smoke test:
524
+
525
+ ```bash
526
+ python -m pytest tests/test_usage.py
527
+ ```
528
+
529
+ For end-to-end tests, reintroduce a `dash_duo` Selenium test—just make sure Chrome/Chromedriver (or another WebDriver) is available.
530
+
531
+ ---
532
+
533
+ ## Known quirks
534
+
535
+ - Chrome will log "Added non-passive event listener" when AG Charts attaches wheel handlers. The library has not yet marked them as passive; there is no functional impact.
536
+ - React 18 warns about deprecated `componentWillMount`/`componentWillReceiveProps` lifecycles inside AG Grid Enterprise. They are harmless and scheduled for removal upstream.
537
+
538
+ ---
539
+
540
+ ## Migration checklist (dash-ag-grid → AgGridJS)
541
+
542
+ - Install the new wrapper (`pip install dash-aggrid-js`) and rebuild once (`npm install && npm run build`) so Dash can serve the bundled assets.
543
+ - Move grid definitions out of Dash props and into `assets/aggrid-configs.js`. Copy each grid’s `columnDefs`, default column settings, and event handlers into a registry entry (object or factory) so the asset controls configuration, not Python.
544
+ - Mirror the same approach for charts (`assets/agcharts-configs.js`) so chart options live alongside grid configs and can react to `configArgs`.
545
+ - Swap Python `gridOptions`/`columnState` assignments for `configKey`/`configArgs`. Any runtime values (locale, SSRM metadata, user selections) travel through `configArgs` and the JS factory can honour them.
546
+ - Use the component’s `rowData` prop when you truly need to push data from Dash; otherwise keep data fetching in JS (e.g., SSRM datasource, fetches).
547
+ - Port Python-defined renderers/formatters/value parsers to JavaScript functions—AgGridJS does not serialise functions across the bridge.
548
+ - Re-implement dash-ag-grid conveniences (persistence, context menus, quick filters) using native AG Grid APIs in the asset, calling `setProps` only when you need to notify Dash.
549
+ - Replace theme classes with the theming API: grab `themeQuartz`/`themeAlpine` from `window.AgGridJsThemes` and call `.withParams(...)` where needed.
550
+ - Continue handling Enterprise licensing by setting `window.AGGRID_LICENSE_KEY` before configs execute; the sample helper still applies it automatically.
551
+
552
+ ---
553
+
554
+ ## AI assistant playbook
555
+
556
+ - **Understand the registry**: Every grid/chart lives in `assets/aggrid-configs.js` (and `agcharts-configs.js`). Each exported key should be a self-contained config or factory.
557
+ - **When adding a grid**: Duplicate a pattern, update the key, tweak `columnDefs`, defaults, and any custom logic. Keep everything serialisable and let the registry own datasource setup (e.g., SSRM).
558
+ - **Migrations**: Move Python `gridOptions`/callbacks into JS; pass runtime values via `configArgs`. Use the component’s `rowData` only when Dash truly needs to push data.
559
+ - **SSRM**: Provide `configArgs={ ssrm: { duckdb_path, table | builder, endpoint? } }`. The helper auto-registers routes and set-filter values.
560
+ - **Charts**: Follow the same registry pattern in `agcharts-configs.js`; use factories when you need options derived from `configArgs`.
561
+ - **Bundle hygiene**: Run `npm run build` after editing component source or assets so Dash serves the latest bundle.
562
+
563
+ ---
564
+
565
+ ## Packaging & distribution
566
+
567
+ 1. Run `npm run build` to guarantee the JS bundle matches the source.
568
+ 2. Build Python artefacts (requires network access to fetch build-system deps):
569
+
570
+ ```bash
571
+ python -m build # creates dist/*.tar.gz and dist/*.whl
572
+ ```
573
+
574
+ 3. Publish:
575
+ - PyPI: `twine upload dist/*`
576
+ - npm (optional): `npm publish`
577
+
578
+ `MANIFEST.in` already lists the built bundles so Dash can serve them when installed from a wheel/sdist.
579
+
580
+ For large apps you can drop page-specific config scripts into `assets/` and guard them by pathname (e.g., `if (window.location.pathname === '/reports') { ... }`). Each script is still global, but the early-return keeps unrelated pages lightweight.
581
+
582
+ ---
583
+
584
+ ## Future enhancements
585
+
586
+ - Add pytest coverage for `sql_for` and `distinct_sql` across filter combinations, grouping hierarchies, and pagination paths.
587
+ - Provide extension hooks so callers can override identifier quoting or literal casting without monkeypatching the helper.
588
+ - Document or automate SSRM export row-count strategies once production usage surfaces common patterns.
589
+ - Extend `sql_for` to generate pivot SQL so the SSRM demo supports AG Grid pivot mode end-to-end.
590
+
591
+ ---
592
+
593
+ ## FAQ
594
+
595
+ **How do I load configs from TypeScript?**
596
+ Compile them to JS (e.g., via `tsc`) and expose the resulting objects/functions on `window.AGGRID_CONFIGS`.
597
+
598
+ **Can I update data from Dash callbacks?**
599
+ Yes. Return new data through `configArgs` or store it in a registry the config reads from. You can also hit custom HTTP endpoints from within the config (server-side row model).
600
+
601
+ **What if I need more events back in Dash?**
602
+ Add the AG Grid handler in the registry and call `setProps` manually. Example:
603
+
604
+ ```javascript
605
+ window.AGGRID_CONFIGS['editable-grid'] = {
606
+ onRowDragEnd: (params) => {
607
+ params.api.refreshCells();
608
+ params.context?.setProps?.({ lastDragged: params.node.data });
609
+ },
610
+ context: { setProps: window.dash_clientside.set_props }, // optional helper
611
+ };
612
+ ```
613
+
614
+ **Does this conflict with dash-ag-grid?**
615
+ No; they serve different audiences. This package is intentionally thin and expects you to manage grid options in JS. `dash-ag-grid` provides Python prop mapping and many Dash-first conveniences.
616
+
617
+ ---
618
+
619
+ Questions or ideas? Open an issue or PR—this wrapper aims to stay lightweight while keeping the AG Grid React API fully accessible in Dash.