@cdmx/wappler_ag_grid 2.1.6 → 2.1.7

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.
package/README.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  **Major Update:** This release upgrades AG Grid to v35.1.0, bringing continued performance improvements and new features.
4
4
 
5
+ ## 🔒 What's New in v2.1.7
6
+
7
+ **Content Security Policy compliance.** The status toggle column (rendered when a grid has a
8
+ `status` field and `row_status_event` is enabled) previously emitted an inline
9
+ `onclick="handleStatusToggle(...)"` attribute per row. Inline event handlers are blocked by any
10
+ CSP without `'unsafe-inline'` in `script-src`, and they cannot be allowed by hash because the
11
+ attribute interpolates per-row data — so under a locked-down policy the toggle rendered normally
12
+ but silently stopped firing `row_status_enabled` / `row_status_disabled`.
13
+
14
+ The renderer now emits `data-status-col` / `data-status-value` / `data-status-id` attributes, and
15
+ a single delegated `click` listener on the grid container dispatches the events. Behaviour is
16
+ unchanged; no configuration or view changes are required.
17
+
18
+ - **No inline event handlers** are emitted anywhere in the grid, so `script-src` needs neither
19
+ `'unsafe-inline'` nor per-row hashes.
20
+ - The three runtime `<style>` blocks (cell centering, sticky horizontal scrollbar, pagination
21
+ layout) are static and unchanged, and can be allowed by hash in `style-src`:
22
+ ```
23
+ 'sha256-rW177u2hd7RHx4qSlJmD1nI6607/SZ01lrewgfHuj/M=' cell centering
24
+ 'sha256-Et0Yl8993s0IRJOeeDT++DYDRSXLRDRvbgCj9zG2J9A=' sticky horizontal scrollbar
25
+ 'sha256-6oQ3cIavZD3cp6igHrAN3HjrosWY41/7+bYLY+je9F0=' pagination panel
26
+ ```
27
+ These must be recomputed if that CSS text is ever edited.
28
+ - Toggle values are now attribute-escaped rather than interpolated into a JavaScript string
29
+ literal, closing a quote-breakout in the previous renderer.
30
+
31
+ Regression coverage lives in `tests/04-selection.html` (`window.__statusCheck`), which asserts the
32
+ grid emits zero `[onclick]` attributes and that the toggle still dispatches its events.
33
+
5
34
  ## 🚀 What's New in v2.1.1
6
35
 
7
36
  1. **🔥 AG Grid v35.1.0** - Latest version with all community features
package/dmx-ag-grid.js CHANGED
@@ -648,6 +648,18 @@ dmx.Component('ag-grid', {
648
648
  this.dispatchEvent('row_status_disabled')
649
649
  }
650
650
  };
651
+ // ponytail: delegated — checkboxCellRenderer emits no inline onclick (CSP script-src).
652
+ // Delegation rather than per-input binding because AG Grid virtualizes rows.
653
+ if (themeContainer) {
654
+ themeContainer.removeEventListener('click', this._statusToggleHandler);
655
+ this._statusToggleHandler = (event) => {
656
+ const input = event.target.closest('.switch-input[data-status-id]');
657
+ if (input) {
658
+ window.handleStatusToggle(event, input.dataset.statusCol, input.dataset.statusValue, input.dataset.statusId);
659
+ }
660
+ };
661
+ themeContainer.addEventListener('click', this._statusToggleHandler);
662
+ }
651
663
  function clickCellRenderer(params) {
652
664
  const idValue = params.data.id;
653
665
  const columnName = params.colDef.field;
@@ -696,13 +708,14 @@ dmx.Component('ag-grid', {
696
708
  }
697
709
  // Assuming `value` is a boolean representing the status
698
710
  const checked = value==true ? "checked" : "";
711
+ const attr = (v) => String(v).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
699
712
  return `
700
713
  <div class="col pt-2 pb-1 ps-1 pe-1 d-flex justify-content-center">
701
714
  <label class="switch switch-success">
702
- <input
715
+ <input
703
716
  type="checkbox" class="switch-input"
704
717
  ${checked}
705
- onclick="handleStatusToggle(event, '${columnName}', '${value}', '${idValue}')"
718
+ data-status-col="${attr(columnName)}" data-status-value="${attr(value)}" data-status-id="${attr(idValue)}"
706
719
  />
707
720
  <span class="switch-toggle-slider" role="status">
708
721
  </span>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdmx/wappler_ag_grid",
3
- "version": "2.1.6",
3
+ "version": "2.1.7",
4
4
  "type": "module",
5
5
  "description": "App Connect module for AG Grid v35 - Advanced data grid with enhanced editing, filtering, and tree data capabilities.",
6
6
  "license": "MIT",
@@ -28,6 +28,7 @@
28
28
 
29
29
  <main class="test-main">
30
30
  <dmx-test-data id="rows" source="employees"></dmx-test-data>
31
+ <dmx-test-data id="statusData" source="statusRows"></dmx-test-data>
31
32
  <dmx-event-log id="evLog"></dmx-event-log>
32
33
 
33
34
  <section class="feature">
@@ -69,6 +70,29 @@
69
70
  </div>
70
71
  </section>
71
72
 
73
+ <section class="feature">
74
+ <h2>Status toggle column</h2>
75
+ <p class="desc">
76
+ Data has a <code>status</code> field, so <code>row_status_event=true</code> renders
77
+ <code>checkboxCellRenderer</code>. The toggle must dispatch its event via a delegated
78
+ listener and emit <strong>no inline <code>onclick</code></strong> — inline handlers are
79
+ blocked by a CSP without <code>'unsafe-inline'</code> in <code>script-src</code>.
80
+ The puppeteer runner reads <code>window.__statusCheck</code>.
81
+ </p>
82
+ <div class="grid-wrap">
83
+ <dmx-ag-grid
84
+ id="statusGrid"
85
+ dmx-bind:data="statusData.value"
86
+ grid_theme="ag-theme-alpine"
87
+ dmx-bind:row_status_event="true"
88
+ dmx-bind:pagination="false"
89
+ dmx-on:row_status_enabled="evLog.add('row_status_enabled')"
90
+ dmx-on:row_status_disabled="evLog.add('row_status_disabled')">
91
+ </dmx-ag-grid>
92
+ </div>
93
+ <pre class="log" id="status-check"></pre>
94
+ </section>
95
+
72
96
  <section class="feature">
73
97
  <h2>Single-row selection</h2>
74
98
  <p class="desc"><code>row_selection="singleRow"</code> — clicking a row replaces selection.</p>
@@ -88,6 +112,44 @@
88
112
  const c = TestKit.grid('selGrid');
89
113
  console.log('selectedRows', c && c.get('selectedRows'));
90
114
  }
115
+ // Status-toggle assertions — exposed as window.__statusCheck for the runners.
116
+ // Guards the CSP fix: checkboxCellRenderer must emit data-status-* attributes and no
117
+ // inline onclick, with a delegated listener still dispatching row_status_*.
118
+ function runStatusChecks(tries) {
119
+ tries = tries || 0;
120
+ if (document.body.dataset.gridsReady !== 'true') return setTimeout(() => runStatusChecks(tries), 200);
121
+ const root = document.querySelector('#statusGrid-grid');
122
+ const input = root && root.querySelector('.switch-input');
123
+ if (!input) {
124
+ if (tries > 25) return publish({ toggle_rendered: { expect: true, actual: false } });
125
+ return setTimeout(() => runStatusChecks(tries + 1), 200);
126
+ }
127
+
128
+ const checks = {
129
+ // First row is status:true, so it renders checked — clicking it disables.
130
+ toggle_rendered: { expect: true, actual: true },
131
+ no_inline_onclick: { expect: 0, actual: root.querySelectorAll('[onclick]').length },
132
+ toggle_has_dataset: { expect: true, actual: !!(input.dataset.statusId && input.dataset.statusCol) }
133
+ };
134
+
135
+ const log = dmx.app.get('evLog');
136
+ input.click();
137
+ setTimeout(() => {
138
+ const last = log && log.last;
139
+ checks.toggle_dispatches = { expect: 'row_status_disabled', actual: (last && last.name) || null };
140
+ publish(checks);
141
+ }, 150);
142
+ }
143
+
144
+ function publish(checks) {
145
+ const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
146
+ window.__statusCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
147
+ document.getElementById('status-check').textContent = Object.entries(checks)
148
+ .map(([k, v]) => (v.actual === v.expect ? '✓' : '✗') + ' ' + k + ' — expected ' + v.expect + ', got ' + v.actual)
149
+ .join('\n');
150
+ }
151
+ runStatusChecks();
152
+
91
153
  (function pollLog() {
92
154
  if (!window.dmx || !dmx.app) return setTimeout(pollLog, 100);
93
155
  const log = dmx.app.get('evLog');
@@ -48,5 +48,16 @@
48
48
  { id: 112, customer: 'Wayne Ent.', region: 'NA', product: 'Gadget', qty: 11, unit_price: 99.99, total: 1099.89,order_date: '2025-05-04' }
49
49
  ];
50
50
 
51
- window.SampleData = { employees, orders, departments };
51
+ // Rows carrying a `status` field — the only shape that makes dmx-ag-grid render
52
+ // checkboxCellRenderer (needs a `status` column plus row_status_event). Kept separate
53
+ // from `employees` so the other scenarios don't grow a stray column.
54
+ const statusRows = [
55
+ { id: 1, name: 'Alpha', department: 'Engineering', status: true },
56
+ { id: 2, name: 'Bravo', department: 'Sales', status: false },
57
+ { id: 3, name: 'Charlie', department: 'Marketing', status: true },
58
+ { id: 4, name: 'Delta', department: 'HR', status: false },
59
+ { id: 5, name: 'Echo', department: 'Finance', status: true }
60
+ ];
61
+
62
+ window.SampleData = { employees, orders, departments, statusRows };
52
63
  })();
@@ -13,7 +13,7 @@ const PAGES = [
13
13
  { file: '01-basic-render.html', grids: ['basicGrid'] },
14
14
  { file: '02-themes-locale.html', grids: ['g_alpine','g_balham','g_material','g_quartz','g_custom','g_dark','g_he','g_ru','g_es'] },
15
15
  { file: '03-editing.html', grids: ['cellEdit','rowEdit','selectEdit'] },
16
- { file: '04-selection.html', grids: ['selGrid','singleSel'] },
16
+ { file: '04-selection.html', grids: ['selGrid','singleSel','statusGrid'], pageAssertion: '__statusCheck' },
17
17
  { file: '05-action-buttons.html', grids: ['actGrid'] },
18
18
  { file: '06-export.html', grids: ['expGrid','noExp'] },
19
19
  { file: '07-import.html', grids: ['importGrid'], expectEmpty: true },
package/tests/run-all.cjs CHANGED
@@ -16,7 +16,7 @@ const PAGES = [
16
16
  { file: '01-basic-render.html', grids: ['basicGrid'] },
17
17
  { file: '02-themes-locale.html', grids: ['g_alpine','g_balham','g_material','g_quartz','g_custom','g_dark','g_he','g_ru','g_es'] },
18
18
  { file: '03-editing.html', grids: ['cellEdit','rowEdit','selectEdit'] },
19
- { file: '04-selection.html', grids: ['selGrid','singleSel'] },
19
+ { file: '04-selection.html', grids: ['selGrid','singleSel','statusGrid'] },
20
20
  { file: '05-action-buttons.html', grids: ['actGrid'] },
21
21
  { file: '06-export.html', grids: ['expGrid','noExp'] },
22
22
  { file: '07-import.html', grids: ['importGrid'], expectEmpty: true },