@lowdefy/blocks-aggrid 5.1.0 → 5.3.0

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/dist/AgGrid.js CHANGED
@@ -19,8 +19,8 @@ import { CsvExportModule } from '@ag-grid-community/csv-export';
19
19
  import processColDefs from './processColDefs.js';
20
20
  import assignRowId from './assignRowId.js';
21
21
  import LoadingOverlay from './LoadingOverlay.js';
22
- const AgGrid = ({ properties, methods, loading, events })=>{
23
- const { quickFilterValue, columnDefs, defaultColDef, rowData: newRowData, ...someProperties } = properties;
22
+ const AgGrid = ({ properties, methods, loading, events, components })=>{
23
+ const { quickFilterValue, columnDefs, defaultColDef, rowData: newRowData, suppressCellFocus = true, ...someProperties } = properties;
24
24
  const [rowData, setRowData] = useState(newRowData ?? []);
25
25
  const gridRef = useRef();
26
26
  const memoDefaultColDef = useMemo(()=>defaultColDef);
@@ -82,12 +82,17 @@ const AgGrid = ({ properties, methods, loading, events })=>{
82
82
  });
83
83
  }
84
84
  }, []);
85
+ const getDisplayedRows = (api)=>{
86
+ const rows = [];
87
+ api.forEachNodeAfterFilterAndSort((node)=>rows.push(node.data));
88
+ return rows;
89
+ };
85
90
  const onFilterChanged = useCallback((event)=>{
86
91
  if (events.onFilterChanged) {
87
92
  methods.triggerEvent({
88
93
  name: 'onFilterChanged',
89
94
  event: {
90
- rows: event.api.rowModel.rowsToDisplay.map((row)=>row.data),
95
+ rows: getDisplayedRows(event.api),
91
96
  filter: gridRef.current.api.getFilterModel()
92
97
  }
93
98
  });
@@ -98,8 +103,8 @@ const AgGrid = ({ properties, methods, loading, events })=>{
98
103
  methods.triggerEvent({
99
104
  name: 'onSortChanged',
100
105
  event: {
101
- rows: event.api.rowModel.rowsToDisplay.map((row)=>row.data),
102
- sort: event.columnApi.getColumnState().filter((col)=>Boolean(col.sort))
106
+ rows: getDisplayedRows(event.api),
107
+ sort: event.api.getColumnState().filter((col)=>Boolean(col.sort))
103
108
  }
104
109
  });
105
110
  }
@@ -108,16 +113,16 @@ const AgGrid = ({ properties, methods, loading, events })=>{
108
113
  methods.registerMethod('exportDataAsCsv', (args)=>gridRef.current.api.exportDataAsCsv(args));
109
114
  methods.registerMethod('sizeColumnsToFit', ()=>gridRef.current.api.sizeColumnsToFit());
110
115
  methods.registerMethod('setFilterModel', (model)=>gridRef.current.api.setFilterModel(model));
111
- methods.registerMethod('setQuickFilter', (value)=>gridRef.current.api.setQuickFilter(value));
116
+ methods.registerMethod('setQuickFilter', (value)=>gridRef.current.api.setGridOption('quickFilterText', value));
112
117
  methods.registerMethod('autoSize', (args = {})=>{
113
118
  const { skipHeader, colIds } = args;
114
119
  const allColumnIds = colIds || [];
115
120
  if (!colIds) {
116
- gridRef.current.columnApi.getAllColumns().forEach((column)=>{
121
+ gridRef.current.api.getColumns().forEach((column)=>{
117
122
  allColumnIds.push(column.getId());
118
123
  });
119
124
  }
120
- gridRef.current.columnApi.autoSizeColumns(allColumnIds, skipHeader);
125
+ gridRef.current.api.autoSizeColumns(allColumnIds, skipHeader);
121
126
  });
122
127
  }, []);
123
128
  useEffect(()=>{
@@ -128,7 +133,7 @@ const AgGrid = ({ properties, methods, loading, events })=>{
128
133
  newRowData
129
134
  ]);
130
135
  if (quickFilterValue && quickFilterValue === '') {
131
- gridRef.current.api.setQuickFilter(quickFilterValue); // check if empty string matches all
136
+ gridRef.current.api.setGridOption('quickFilterText', quickFilterValue); // check if empty string matches all
132
137
  }
133
138
  return /*#__PURE__*/ React.createElement("div", {
134
139
  style: {
@@ -137,7 +142,9 @@ const AgGrid = ({ properties, methods, loading, events })=>{
137
142
  height: '100%'
138
143
  }
139
144
  }, /*#__PURE__*/ React.createElement(AgGridReact, {
145
+ columnMenu: "legacy",
140
146
  ...someProperties,
147
+ suppressCellFocus: suppressCellFocus,
141
148
  rowData: rowData,
142
149
  defaultColDef: memoDefaultColDef,
143
150
  onFilterChanged: onFilterChanged,
@@ -150,7 +157,7 @@ const AgGrid = ({ properties, methods, loading, events })=>{
150
157
  ClientSideRowModelModule,
151
158
  CsvExportModule
152
159
  ],
153
- columnDefs: processColDefs(columnDefs, methods),
160
+ columnDefs: processColDefs(columnDefs, methods, components),
154
161
  ref: gridRef,
155
162
  getRowId: getRowId,
156
163
  suppressLoadingOverlay: true
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2021 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -82,12 +82,17 @@ const AgGridInput = ({ properties, methods, loading, events, value })=>{
82
82
  });
83
83
  }
84
84
  }, []);
85
+ const getDisplayedRows = (api)=>{
86
+ const rows = [];
87
+ api.forEachNodeAfterFilterAndSort((node)=>rows.push(node.data));
88
+ return rows;
89
+ };
85
90
  const onFilterChanged = useCallback((event)=>{
86
91
  if (events.onFilterChanged) {
87
92
  methods.triggerEvent({
88
93
  name: 'onFilterChanged',
89
94
  event: {
90
- rows: event.api.rowModel.rowsToDisplay.map((row)=>row.data),
95
+ rows: getDisplayedRows(event.api),
91
96
  filter: gridRef.current.api.getFilterModel()
92
97
  }
93
98
  });
@@ -98,8 +103,8 @@ const AgGridInput = ({ properties, methods, loading, events, value })=>{
98
103
  methods.triggerEvent({
99
104
  name: 'onSortChanged',
100
105
  event: {
101
- rows: event.api.rowModel.rowsToDisplay.map((row)=>row.data),
102
- sort: event.columnApi.getColumnState().filter((col)=>Boolean(col.sort))
106
+ rows: getDisplayedRows(event.api),
107
+ sort: event.api.getColumnState().filter((col)=>Boolean(col.sort))
103
108
  }
104
109
  });
105
110
  }
@@ -135,7 +140,7 @@ const AgGridInput = ({ properties, methods, loading, events, value })=>{
135
140
  newRowData.splice(toIndex, 0, element);
136
141
  methods.setValue(newRowData);
137
142
  setRowData(rowData);
138
- gridRef.current.api.setRowData(value);
143
+ gridRef.current.api.setGridOption('rowData', value);
139
144
  gridRef.current.api.clearFocusedCell();
140
145
  methods.triggerEvent({
141
146
  name: 'onRowDragEnd',
@@ -153,20 +158,19 @@ const AgGridInput = ({ properties, methods, loading, events, value })=>{
153
158
  value
154
159
  ]);
155
160
  useEffect(()=>{
156
- methods.registerMethod('exportDataAsCsv', (args)=>gridRef.current.api.exportDataAsCsv(args));
157
161
  methods.registerMethod('exportDataAsCsv', (args)=>gridRef.current.api.exportDataAsCsv(args));
158
162
  methods.registerMethod('sizeColumnsToFit', ()=>gridRef.current.api.sizeColumnsToFit());
159
163
  methods.registerMethod('setFilterModel', (model)=>gridRef.current.api.setFilterModel(model));
160
- methods.registerMethod('setQuickFilter', (filter)=>gridRef.current.api.setQuickFilter(filter));
164
+ methods.registerMethod('setQuickFilter', (filter)=>gridRef.current.api.setGridOption('quickFilterText', filter));
161
165
  methods.registerMethod('autoSize', (args = {})=>{
162
166
  const { skipHeader, colIds } = args;
163
167
  const allColumnIds = colIds || [];
164
168
  if (!colIds) {
165
- gridRef.current.columnApi.getAllColumns().forEach((column)=>{
169
+ gridRef.current.api.getColumns().forEach((column)=>{
166
170
  allColumnIds.push(column.getId());
167
171
  });
168
172
  }
169
- gridRef.current.columnApi.autoSizeColumns(allColumnIds, skipHeader);
173
+ gridRef.current.api.autoSizeColumns(allColumnIds, skipHeader);
170
174
  });
171
175
  }, []);
172
176
  useEffect(()=>{
@@ -177,7 +181,7 @@ const AgGridInput = ({ properties, methods, loading, events, value })=>{
177
181
  value
178
182
  ]);
179
183
  if (quickFilterValue && quickFilterValue === '') {
180
- gridRef.current.api.setQuickFilter(quickFilterValue); // check if empty string matches all
184
+ gridRef.current.api.setGridOption('quickFilterText', quickFilterValue); // check if empty string matches all
181
185
  }
182
186
  return /*#__PURE__*/ React.createElement("div", {
183
187
  style: {
@@ -186,6 +190,7 @@ const AgGridInput = ({ properties, methods, loading, events, value })=>{
186
190
  height: '100%'
187
191
  }
188
192
  }, /*#__PURE__*/ React.createElement(AgGridReact, {
193
+ columnMenu: "legacy",
189
194
  ...someProperties,
190
195
  rowData: rowData,
191
196
  onCellClicked: onCellClicked,
@@ -73,6 +73,8 @@
73
73
  display: flex;
74
74
  align-items: center;
75
75
  line-height: var(--ant-line-height, 1.5);
76
+ overflow: hidden;
77
+ min-width: 0;
76
78
  }
77
79
 
78
80
  .antdTheme :global(.lf-ellipsis-1),
@@ -18,7 +18,7 @@ import '@ag-grid-community/styles/ag-grid.css';
18
18
  import '@ag-grid-community/styles/ag-theme-alpine.css';
19
19
  import antdStyles from '../../ag-grid-antd.module.css';
20
20
  import AgGrid from '../../AgGrid.js';
21
- const AgGridAlpine = ({ blockId, events, loading, methods, properties, styles })=>{
21
+ const AgGridAlpine = ({ blockId, components, events, loading, methods, properties, styles })=>{
22
22
  return /*#__PURE__*/ React.createElement("div", {
23
23
  id: blockId,
24
24
  className: `ag-theme-alpine ${antdStyles.antdTheme}`,
@@ -28,6 +28,7 @@ const AgGridAlpine = ({ blockId, events, loading, methods, properties, styles })
28
28
  ...styles?.element
29
29
  }
30
30
  }, /*#__PURE__*/ React.createElement(AgGrid, {
31
+ components: components,
31
32
  events: events,
32
33
  loading: loading,
33
34
  methods: methods,
@@ -73,6 +73,15 @@
73
73
  row: 'The row data.',
74
74
  value: 'The cell value.'
75
75
  }
76
+ },
77
+ onCellButton: {
78
+ description: 'Documentation reference — the actual event name fired is the `eventName` string declared on each `cell.buttons[]` entry. Wire any number of named events on the block (e.g. `onApprove`, `onDelete`).',
79
+ event: {
80
+ row: 'The row data.',
81
+ value: 'The cell value.',
82
+ button: 'The clicked button: { eventName, title }.',
83
+ buttonIndex: 'Zero-based index in the buttons array.'
84
+ }
76
85
  }
77
86
  },
78
87
  properties: {
@@ -100,6 +109,11 @@
100
109
  default: false,
101
110
  description: "Set to `true` to use the browser native `title` attribute tooltips instead of AG Grid's styled tooltip component."
102
111
  },
112
+ suppressCellFocus: {
113
+ type: 'boolean',
114
+ default: true,
115
+ description: 'When `true` (default), clicking a cell does not draw the AG Grid cell-focus border. Set to `false` to enable spreadsheet-style cell focus and keyboard navigation.'
116
+ },
103
117
  tooltipShowDelay: {
104
118
  type: 'number',
105
119
  default: 2000,
@@ -191,13 +205,14 @@
191
205
  'date',
192
206
  'boolean',
193
207
  'progress',
194
- 'number'
208
+ 'number',
209
+ 'buttons'
195
210
  ],
196
211
  description: 'The built-in renderer to use.'
197
212
  },
198
213
  colorMap: {
199
214
  type: 'object',
200
- description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`.'
215
+ description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`. The cell value may be a single string or an array of strings; arrays render one tag per item. If neither `colorMap`, `colorFrom`, nor `default` is set, tag values are auto-coloured from a stable hash for consistency across rows.'
201
216
  },
202
217
  colorFrom: {
203
218
  type: 'string',
@@ -396,6 +411,123 @@
396
411
  'right'
397
412
  ],
398
413
  description: 'Cell horizontal alignment. Defaults to `right` for `cell.type: number`. Sets `cellStyle.justifyContent` and `ag-*-aligned-header` on the header.'
414
+ },
415
+ buttons: {
416
+ type: 'array',
417
+ description: 'Buttons cell: list of buttons rendered per row. Each button triggers its own block-level event (declared in `events:`). Per-button properties mirror the `Button` block schema. `*Field` variants (`titleField`, `iconField`, `disabledField`, `hiddenField`) are row-data paths.',
418
+ items: {
419
+ type: 'object',
420
+ required: [
421
+ 'eventName'
422
+ ],
423
+ properties: {
424
+ eventName: {
425
+ type: 'string',
426
+ description: 'Block-level event name to trigger on click.'
427
+ },
428
+ title: {
429
+ type: 'string',
430
+ description: 'Title text on the button - supports html.'
431
+ },
432
+ titleField: {
433
+ type: 'string',
434
+ description: 'Row-data path for the title.'
435
+ },
436
+ icon: {
437
+ type: [
438
+ 'string',
439
+ 'object'
440
+ ],
441
+ description: 'Name of a React-Icon or Icon block config.',
442
+ docs: {
443
+ displayType: 'icon'
444
+ }
445
+ },
446
+ iconField: {
447
+ type: 'string',
448
+ description: 'Row-data path for the icon name or config.'
449
+ },
450
+ type: {
451
+ type: 'string',
452
+ enum: [
453
+ 'primary',
454
+ 'default',
455
+ 'dashed',
456
+ 'link',
457
+ 'text'
458
+ ],
459
+ description: 'antd Button type.'
460
+ },
461
+ variant: {
462
+ type: 'string',
463
+ enum: [
464
+ 'solid',
465
+ 'outlined',
466
+ 'dashed',
467
+ 'filled',
468
+ 'text',
469
+ 'link'
470
+ ],
471
+ description: 'antd Button variant. Takes precedence over `type` when set.'
472
+ },
473
+ color: {
474
+ type: 'string',
475
+ description: 'Button color (antd preset or hex).',
476
+ docs: {
477
+ displayType: 'color'
478
+ }
479
+ },
480
+ size: {
481
+ type: 'string',
482
+ enum: [
483
+ 'small',
484
+ 'default',
485
+ 'large'
486
+ ],
487
+ default: 'small',
488
+ description: 'Button size. Defaults to `small` inside cells.'
489
+ },
490
+ shape: {
491
+ type: 'string',
492
+ enum: [
493
+ 'circle',
494
+ 'round',
495
+ 'square'
496
+ ],
497
+ default: 'square'
498
+ },
499
+ danger: {
500
+ type: 'boolean',
501
+ default: false
502
+ },
503
+ ghost: {
504
+ type: 'boolean',
505
+ default: false
506
+ },
507
+ hideTitle: {
508
+ type: 'boolean',
509
+ default: false,
510
+ description: "Hide the button's title (icon-only)."
511
+ },
512
+ disabled: {
513
+ type: 'boolean',
514
+ default: false
515
+ },
516
+ disabledField: {
517
+ type: 'string',
518
+ description: 'Row-data path → boolean.'
519
+ },
520
+ hidden: {
521
+ type: 'boolean',
522
+ default: false,
523
+ description: 'Hide the button entirely.'
524
+ },
525
+ hiddenField: {
526
+ type: 'string',
527
+ description: 'Row-data path → boolean.'
528
+ }
529
+ }
530
+ }
399
531
  }
400
532
  }
401
533
  }
@@ -18,7 +18,7 @@ import '@ag-grid-community/styles/ag-grid.css';
18
18
  import '@ag-grid-community/styles/ag-theme-balham.css';
19
19
  import antdStyles from '../../ag-grid-antd.module.css';
20
20
  import AgGrid from '../../AgGrid.js';
21
- const AgGridBalham = ({ blockId, events, loading, methods, properties, styles })=>{
21
+ const AgGridBalham = ({ blockId, components, events, loading, methods, properties, styles })=>{
22
22
  return /*#__PURE__*/ React.createElement("div", {
23
23
  id: blockId,
24
24
  className: `ag-theme-balham ${antdStyles.antdTheme}`,
@@ -28,6 +28,7 @@ const AgGridBalham = ({ blockId, events, loading, methods, properties, styles })
28
28
  ...styles?.element
29
29
  }
30
30
  }, /*#__PURE__*/ React.createElement(AgGrid, {
31
+ components: components,
31
32
  events: events,
32
33
  loading: loading,
33
34
  methods: methods,
@@ -73,6 +73,15 @@
73
73
  row: 'The row data.',
74
74
  value: 'The cell value.'
75
75
  }
76
+ },
77
+ onCellButton: {
78
+ description: 'Documentation reference — the actual event name fired is the `eventName` string declared on each `cell.buttons[]` entry. Wire any number of named events on the block (e.g. `onApprove`, `onDelete`).',
79
+ event: {
80
+ row: 'The row data.',
81
+ value: 'The cell value.',
82
+ button: 'The clicked button: { eventName, title }.',
83
+ buttonIndex: 'Zero-based index in the buttons array.'
84
+ }
76
85
  }
77
86
  },
78
87
  properties: {
@@ -100,6 +109,11 @@
100
109
  default: false,
101
110
  description: "Set to `true` to use the browser native `title` attribute tooltips instead of AG Grid's styled tooltip component."
102
111
  },
112
+ suppressCellFocus: {
113
+ type: 'boolean',
114
+ default: true,
115
+ description: 'When `true` (default), clicking a cell does not draw the AG Grid cell-focus border. Set to `false` to enable spreadsheet-style cell focus and keyboard navigation.'
116
+ },
103
117
  tooltipShowDelay: {
104
118
  type: 'number',
105
119
  default: 2000,
@@ -191,13 +205,14 @@
191
205
  'date',
192
206
  'boolean',
193
207
  'progress',
194
- 'number'
208
+ 'number',
209
+ 'buttons'
195
210
  ],
196
211
  description: 'The built-in renderer to use.'
197
212
  },
198
213
  colorMap: {
199
214
  type: 'object',
200
- description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`.'
215
+ description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`. The cell value may be a single string or an array of strings; arrays render one tag per item. If neither `colorMap`, `colorFrom`, nor `default` is set, tag values are auto-coloured from a stable hash for consistency across rows.'
201
216
  },
202
217
  colorFrom: {
203
218
  type: 'string',
@@ -396,6 +411,123 @@
396
411
  'right'
397
412
  ],
398
413
  description: 'Cell horizontal alignment. Defaults to `right` for `cell.type: number`. Sets `cellStyle.justifyContent` and `ag-*-aligned-header` on the header.'
414
+ },
415
+ buttons: {
416
+ type: 'array',
417
+ description: 'Buttons cell: list of buttons rendered per row. Each button triggers its own block-level event (declared in `events:`). Per-button properties mirror the `Button` block schema. `*Field` variants (`titleField`, `iconField`, `disabledField`, `hiddenField`) are row-data paths.',
418
+ items: {
419
+ type: 'object',
420
+ required: [
421
+ 'eventName'
422
+ ],
423
+ properties: {
424
+ eventName: {
425
+ type: 'string',
426
+ description: 'Block-level event name to trigger on click.'
427
+ },
428
+ title: {
429
+ type: 'string',
430
+ description: 'Title text on the button - supports html.'
431
+ },
432
+ titleField: {
433
+ type: 'string',
434
+ description: 'Row-data path for the title.'
435
+ },
436
+ icon: {
437
+ type: [
438
+ 'string',
439
+ 'object'
440
+ ],
441
+ description: 'Name of a React-Icon or Icon block config.',
442
+ docs: {
443
+ displayType: 'icon'
444
+ }
445
+ },
446
+ iconField: {
447
+ type: 'string',
448
+ description: 'Row-data path for the icon name or config.'
449
+ },
450
+ type: {
451
+ type: 'string',
452
+ enum: [
453
+ 'primary',
454
+ 'default',
455
+ 'dashed',
456
+ 'link',
457
+ 'text'
458
+ ],
459
+ description: 'antd Button type.'
460
+ },
461
+ variant: {
462
+ type: 'string',
463
+ enum: [
464
+ 'solid',
465
+ 'outlined',
466
+ 'dashed',
467
+ 'filled',
468
+ 'text',
469
+ 'link'
470
+ ],
471
+ description: 'antd Button variant. Takes precedence over `type` when set.'
472
+ },
473
+ color: {
474
+ type: 'string',
475
+ description: 'Button color (antd preset or hex).',
476
+ docs: {
477
+ displayType: 'color'
478
+ }
479
+ },
480
+ size: {
481
+ type: 'string',
482
+ enum: [
483
+ 'small',
484
+ 'default',
485
+ 'large'
486
+ ],
487
+ default: 'small',
488
+ description: 'Button size. Defaults to `small` inside cells.'
489
+ },
490
+ shape: {
491
+ type: 'string',
492
+ enum: [
493
+ 'circle',
494
+ 'round',
495
+ 'square'
496
+ ],
497
+ default: 'square'
498
+ },
499
+ danger: {
500
+ type: 'boolean',
501
+ default: false
502
+ },
503
+ ghost: {
504
+ type: 'boolean',
505
+ default: false
506
+ },
507
+ hideTitle: {
508
+ type: 'boolean',
509
+ default: false,
510
+ description: "Hide the button's title (icon-only)."
511
+ },
512
+ disabled: {
513
+ type: 'boolean',
514
+ default: false
515
+ },
516
+ disabledField: {
517
+ type: 'string',
518
+ description: 'Row-data path → boolean.'
519
+ },
520
+ hidden: {
521
+ type: 'boolean',
522
+ default: false,
523
+ description: 'Hide the button entirely.'
524
+ },
525
+ hiddenField: {
526
+ type: 'string',
527
+ description: 'Row-data path → boolean.'
528
+ }
529
+ }
530
+ }
399
531
  }
400
532
  }
401
533
  }
@@ -231,7 +231,7 @@
231
231
  },
232
232
  colorMap: {
233
233
  type: 'object',
234
- description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`.'
234
+ description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`. The cell value may be a single string or an array of strings; arrays render one tag per item. If neither `colorMap`, `colorFrom`, nor `default` is set, tag values are auto-coloured from a stable hash for consistency across rows.'
235
235
  },
236
236
  colorFrom: {
237
237
  type: 'string',
@@ -231,7 +231,7 @@
231
231
  },
232
232
  colorMap: {
233
233
  type: 'object',
234
- description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`.'
234
+ description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`. The cell value may be a single string or an array of strings; arrays render one tag per item. If neither `colorMap`, `colorFrom`, nor `default` is set, tag values are auto-coloured from a stable hash for consistency across rows.'
235
235
  },
236
236
  colorFrom: {
237
237
  type: 'string',
@@ -231,7 +231,7 @@
231
231
  },
232
232
  colorMap: {
233
233
  type: 'object',
234
- description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`.'
234
+ description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`. The cell value may be a single string or an array of strings; arrays render one tag per item. If neither `colorMap`, `colorFrom`, nor `default` is set, tag values are auto-coloured from a stable hash for consistency across rows.'
235
235
  },
236
236
  colorFrom: {
237
237
  type: 'string',
@@ -18,7 +18,7 @@ import '@ag-grid-community/styles/ag-grid.css';
18
18
  import '@ag-grid-community/styles/ag-theme-material.css';
19
19
  import antdStyles from '../../ag-grid-antd.module.css';
20
20
  import AgGrid from '../../AgGrid.js';
21
- const AgGridMaterial = ({ blockId, events, loading, methods, properties, styles })=>{
21
+ const AgGridMaterial = ({ blockId, components, events, loading, methods, properties, styles })=>{
22
22
  return /*#__PURE__*/ React.createElement("div", {
23
23
  id: blockId,
24
24
  className: `ag-theme-material ${antdStyles.antdTheme}`,
@@ -28,6 +28,7 @@ const AgGridMaterial = ({ blockId, events, loading, methods, properties, styles
28
28
  ...styles?.element
29
29
  }
30
30
  }, /*#__PURE__*/ React.createElement(AgGrid, {
31
+ components: components,
31
32
  events: events,
32
33
  loading: loading,
33
34
  methods: methods,
@@ -73,6 +73,15 @@
73
73
  row: 'The row data.',
74
74
  value: 'The cell value.'
75
75
  }
76
+ },
77
+ onCellButton: {
78
+ description: 'Documentation reference — the actual event name fired is the `eventName` string declared on each `cell.buttons[]` entry. Wire any number of named events on the block (e.g. `onApprove`, `onDelete`).',
79
+ event: {
80
+ row: 'The row data.',
81
+ value: 'The cell value.',
82
+ button: 'The clicked button: { eventName, title }.',
83
+ buttonIndex: 'Zero-based index in the buttons array.'
84
+ }
76
85
  }
77
86
  },
78
87
  properties: {
@@ -100,6 +109,11 @@
100
109
  default: false,
101
110
  description: "Set to `true` to use the browser native `title` attribute tooltips instead of AG Grid's styled tooltip component."
102
111
  },
112
+ suppressCellFocus: {
113
+ type: 'boolean',
114
+ default: true,
115
+ description: 'When `true` (default), clicking a cell does not draw the AG Grid cell-focus border. Set to `false` to enable spreadsheet-style cell focus and keyboard navigation.'
116
+ },
103
117
  tooltipShowDelay: {
104
118
  type: 'number',
105
119
  default: 2000,
@@ -191,13 +205,14 @@
191
205
  'date',
192
206
  'boolean',
193
207
  'progress',
194
- 'number'
208
+ 'number',
209
+ 'buttons'
195
210
  ],
196
211
  description: 'The built-in renderer to use.'
197
212
  },
198
213
  colorMap: {
199
214
  type: 'object',
200
- description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`.'
215
+ description: 'Tag: map of cell value → color (antd tag color name or hex). Used when `cell.type: tag`. The cell value may be a single string or an array of strings; arrays render one tag per item. If neither `colorMap`, `colorFrom`, nor `default` is set, tag values are auto-coloured from a stable hash for consistency across rows.'
201
216
  },
202
217
  colorFrom: {
203
218
  type: 'string',
@@ -396,6 +411,123 @@
396
411
  'right'
397
412
  ],
398
413
  description: 'Cell horizontal alignment. Defaults to `right` for `cell.type: number`. Sets `cellStyle.justifyContent` and `ag-*-aligned-header` on the header.'
414
+ },
415
+ buttons: {
416
+ type: 'array',
417
+ description: 'Buttons cell: list of buttons rendered per row. Each button triggers its own block-level event (declared in `events:`). Per-button properties mirror the `Button` block schema. `*Field` variants (`titleField`, `iconField`, `disabledField`, `hiddenField`) are row-data paths.',
418
+ items: {
419
+ type: 'object',
420
+ required: [
421
+ 'eventName'
422
+ ],
423
+ properties: {
424
+ eventName: {
425
+ type: 'string',
426
+ description: 'Block-level event name to trigger on click.'
427
+ },
428
+ title: {
429
+ type: 'string',
430
+ description: 'Title text on the button - supports html.'
431
+ },
432
+ titleField: {
433
+ type: 'string',
434
+ description: 'Row-data path for the title.'
435
+ },
436
+ icon: {
437
+ type: [
438
+ 'string',
439
+ 'object'
440
+ ],
441
+ description: 'Name of a React-Icon or Icon block config.',
442
+ docs: {
443
+ displayType: 'icon'
444
+ }
445
+ },
446
+ iconField: {
447
+ type: 'string',
448
+ description: 'Row-data path for the icon name or config.'
449
+ },
450
+ type: {
451
+ type: 'string',
452
+ enum: [
453
+ 'primary',
454
+ 'default',
455
+ 'dashed',
456
+ 'link',
457
+ 'text'
458
+ ],
459
+ description: 'antd Button type.'
460
+ },
461
+ variant: {
462
+ type: 'string',
463
+ enum: [
464
+ 'solid',
465
+ 'outlined',
466
+ 'dashed',
467
+ 'filled',
468
+ 'text',
469
+ 'link'
470
+ ],
471
+ description: 'antd Button variant. Takes precedence over `type` when set.'
472
+ },
473
+ color: {
474
+ type: 'string',
475
+ description: 'Button color (antd preset or hex).',
476
+ docs: {
477
+ displayType: 'color'
478
+ }
479
+ },
480
+ size: {
481
+ type: 'string',
482
+ enum: [
483
+ 'small',
484
+ 'default',
485
+ 'large'
486
+ ],
487
+ default: 'small',
488
+ description: 'Button size. Defaults to `small` inside cells.'
489
+ },
490
+ shape: {
491
+ type: 'string',
492
+ enum: [
493
+ 'circle',
494
+ 'round',
495
+ 'square'
496
+ ],
497
+ default: 'square'
498
+ },
499
+ danger: {
500
+ type: 'boolean',
501
+ default: false
502
+ },
503
+ ghost: {
504
+ type: 'boolean',
505
+ default: false
506
+ },
507
+ hideTitle: {
508
+ type: 'boolean',
509
+ default: false,
510
+ description: "Hide the button's title (icon-only)."
511
+ },
512
+ disabled: {
513
+ type: 'boolean',
514
+ default: false
515
+ },
516
+ disabledField: {
517
+ type: 'string',
518
+ description: 'Row-data path → boolean.'
519
+ },
520
+ hidden: {
521
+ type: 'boolean',
522
+ default: false,
523
+ description: 'Hide the button entirely.'
524
+ },
525
+ hiddenField: {
526
+ type: 'string',
527
+ description: 'Row-data path → boolean.'
528
+ }
529
+ }
530
+ }
399
531
  }
400
532
  }
401
533
  }
@@ -0,0 +1,77 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import React from 'react';
16
+ import { Button, Space } from 'antd';
17
+ import { type } from '@lowdefy/helpers';
18
+ import { renderHtml } from '@lowdefy/block-utils';
19
+ import NullCell from './NullCell.js';
20
+ import { resolvePath } from './resolveFieldRefs.js';
21
+ function resolveField(literalKey, fieldKey, btn, data) {
22
+ if (type.isString(btn?.[fieldKey])) return resolvePath(btn[fieldKey], data);
23
+ return btn?.[literalKey];
24
+ }
25
+ function ButtonsCell(params) {
26
+ const { value, data, cellConfig, methods, components } = params;
27
+ const Icon = components?.Icon;
28
+ const buttons = type.isArray(cellConfig?.buttons) ? cellConfig.buttons : [];
29
+ if (buttons.length === 0) return /*#__PURE__*/ React.createElement(NullCell, null);
30
+ return /*#__PURE__*/ React.createElement(Space, {
31
+ size: 4
32
+ }, buttons.map((btn, idx)=>{
33
+ if (!type.isObject(btn) || !type.isString(btn.eventName)) return null;
34
+ if (resolveField('hidden', 'hiddenField', btn, data) === true) return null;
35
+ const title = resolveField('title', 'titleField', btn, data);
36
+ const iconConfig = resolveField('icon', 'iconField', btn, data);
37
+ const disabled = resolveField('disabled', 'disabledField', btn, data) === true;
38
+ function onClick(e) {
39
+ e.stopPropagation();
40
+ methods?.triggerEvent?.({
41
+ name: btn.eventName,
42
+ event: {
43
+ row: data,
44
+ value,
45
+ button: {
46
+ eventName: btn.eventName,
47
+ title
48
+ },
49
+ buttonIndex: idx
50
+ }
51
+ });
52
+ }
53
+ const iconNode = iconConfig && Icon ? /*#__PURE__*/ React.createElement(Icon, {
54
+ blockId: `btnscell_${idx}_icon`,
55
+ events: {},
56
+ properties: iconConfig
57
+ }) : undefined;
58
+ const showTitle = btn.hideTitle !== true && !type.isNone(title);
59
+ return /*#__PURE__*/ React.createElement(Button, {
60
+ key: idx,
61
+ size: btn.size ?? 'small',
62
+ type: btn.type,
63
+ variant: btn.variant,
64
+ color: btn.color,
65
+ shape: btn.shape ?? 'square',
66
+ danger: btn.danger === true,
67
+ ghost: btn.ghost === true,
68
+ disabled: disabled,
69
+ icon: iconNode,
70
+ onClick: onClick
71
+ }, showTitle && renderHtml({
72
+ html: String(title),
73
+ methods
74
+ }));
75
+ }));
76
+ }
77
+ export default ButtonsCell;
@@ -31,24 +31,36 @@ const ANTD_TAG_COLOR_TOKENS = {
31
31
  magenta: 'var(--ant-color-magenta, var(--ant-color-error))',
32
32
  default: 'var(--ant-color-text-secondary)'
33
33
  };
34
+ const SEED_PALETTE = [
35
+ 'red',
36
+ 'volcano',
37
+ 'orange',
38
+ 'gold',
39
+ 'yellow',
40
+ 'lime',
41
+ 'green',
42
+ 'cyan',
43
+ 'blue',
44
+ 'geekblue',
45
+ 'purple',
46
+ 'magenta'
47
+ ];
48
+ function colorSeed(s) {
49
+ if (type.isNone(s)) return 0;
50
+ const str = String(s);
51
+ let hash = 0;
52
+ for(let i = 0; i < str.length; i += 1)hash = hash * 31 + str.charCodeAt(i) >>> 0;
53
+ return hash;
54
+ }
55
+ function seededColor(item) {
56
+ return SEED_PALETTE[colorSeed(item) % SEED_PALETTE.length];
57
+ }
34
58
  function resolveColor(value) {
35
59
  if (type.isNone(value)) return ANTD_TAG_COLOR_TOKENS.default;
36
60
  return ANTD_TAG_COLOR_TOKENS[value] ?? value;
37
61
  }
38
- function TagCell(params) {
39
- const { value, data, cellConfig } = params;
40
- if (type.isNone(value) || value === '') {
41
- return /*#__PURE__*/ React.createElement(NullCell, null);
42
- }
43
- const { colorMap, colorFrom, default: defaultColor } = cellConfig ?? {};
44
- let color;
45
- if (type.isString(colorFrom)) {
46
- color = resolvePath(colorFrom, data);
47
- } else if (type.isObject(colorMap)) {
48
- color = colorMap[value];
49
- }
50
- const resolved = resolveColor(color ?? defaultColor);
51
- const style = {
62
+ function tagStyle(resolved) {
63
+ return {
52
64
  display: 'inline-flex',
53
65
  alignItems: 'center',
54
66
  padding: 'var(--ant-padding-xxs, 4px) var(--ant-padding-xs, 8px)',
@@ -60,8 +72,48 @@ function TagCell(params) {
60
72
  background: `color-mix(in srgb, ${resolved} 12%, transparent)`,
61
73
  border: `1px solid color-mix(in srgb, ${resolved} 30%, transparent)`
62
74
  };
75
+ }
76
+ function TagCell(params) {
77
+ const { value, data, cellConfig } = params;
78
+ if (type.isNone(value) || value === '') {
79
+ return /*#__PURE__*/ React.createElement(NullCell, null);
80
+ }
81
+ const { colorMap, colorFrom, default: defaultColor } = cellConfig ?? {};
82
+ const useColorFrom = type.isString(colorFrom);
83
+ const useColorMap = type.isObject(colorMap);
84
+ const fromColor = useColorFrom ? resolvePath(colorFrom, data) : undefined;
85
+ const seedingActive = !useColorFrom && !useColorMap && type.isNone(defaultColor);
86
+ function colorFor(item) {
87
+ if (useColorFrom) return fromColor;
88
+ if (useColorMap) return colorMap[item];
89
+ return undefined;
90
+ }
91
+ function pickColor(item) {
92
+ return colorFor(item) ?? defaultColor ?? (seedingActive ? seededColor(item) : undefined);
93
+ }
94
+ if (type.isArray(value)) {
95
+ const items = value.filter((item)=>!type.isNone(item) && item !== '');
96
+ if (items.length === 0) {
97
+ return /*#__PURE__*/ React.createElement(NullCell, null);
98
+ }
99
+ const containerStyle = {
100
+ display: 'inline-flex',
101
+ flexWrap: 'wrap',
102
+ gap: 4
103
+ };
104
+ return /*#__PURE__*/ React.createElement("span", {
105
+ style: containerStyle
106
+ }, items.map((item, index)=>{
107
+ const resolved = resolveColor(pickColor(item));
108
+ return /*#__PURE__*/ React.createElement("span", {
109
+ key: `${index}-${item}`,
110
+ style: tagStyle(resolved)
111
+ }, String(item));
112
+ }));
113
+ }
114
+ const resolved = resolveColor(pickColor(value));
63
115
  return /*#__PURE__*/ React.createElement("span", {
64
- style: style
116
+ style: tagStyle(resolved)
65
117
  }, String(value));
66
118
  }
67
119
  export default TagCell;
@@ -19,6 +19,7 @@ import DateCell from './DateCell.js';
19
19
  import BooleanCell from './BooleanCell.js';
20
20
  import ProgressCell from './ProgressCell.js';
21
21
  import NumberCell from './NumberCell.js';
22
+ import ButtonsCell from './ButtonsCell.js';
22
23
  const CELL_RENDERERS = {
23
24
  tag: TagCell,
24
25
  avatar: AvatarCell,
@@ -26,7 +27,8 @@ const CELL_RENDERERS = {
26
27
  date: DateCell,
27
28
  boolean: BooleanCell,
28
29
  progress: ProgressCell,
29
- number: NumberCell
30
+ number: NumberCell,
31
+ buttons: ButtonsCell
30
32
  };
31
33
  function getCellRenderer(type) {
32
34
  return CELL_RENDERERS[type];
@@ -66,7 +66,7 @@ function applyAlignment(colDef, cell) {
66
66
  headerClass
67
67
  };
68
68
  }
69
- function buildCellRenderer({ cell, methods }) {
69
+ function buildCellRenderer({ cell, methods, components }) {
70
70
  const Renderer = getCellRenderer(cell?.type);
71
71
  if (!Renderer) return undefined;
72
72
  // ag-grid calls the renderer as a React function component when returned directly.
@@ -74,20 +74,22 @@ function buildCellRenderer({ cell, methods }) {
74
74
  return Renderer({
75
75
  ...params,
76
76
  cellConfig: cell,
77
- methods
77
+ methods,
78
+ components
78
79
  });
79
80
  };
80
81
  }
81
- function recProcessColDefs(columnDefs, methods) {
82
+ function recProcessColDefs(columnDefs, methods, components) {
82
83
  return columnDefs.map((col)=>{
83
84
  const newColDef = {};
84
85
  if (type.isArray(col.children)) {
85
- newColDef.children = recProcessColDefs(col.children, methods);
86
+ newColDef.children = recProcessColDefs(col.children, methods, components);
86
87
  }
87
88
  if (type.isObject(col.cell) && type.isString(col.cell.type)) {
88
89
  const renderer = buildCellRenderer({
89
90
  cell: col.cell,
90
- methods
91
+ methods,
92
+ components
91
93
  });
92
94
  if (renderer) {
93
95
  newColDef.cellRenderer = renderer;
@@ -111,7 +113,7 @@ function recProcessColDefs(columnDefs, methods) {
111
113
  return applyEllipsis(aligned, col.ellipsis);
112
114
  });
113
115
  }
114
- function processColDefs(columnDefs = [], methods) {
115
- return recProcessColDefs(columnDefs, methods);
116
+ function processColDefs(columnDefs = [], methods, components) {
117
+ return recProcessColDefs(columnDefs, methods, components);
116
118
  }
117
119
  export default processColDefs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/blocks-aggrid",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "AgGrid Blocks for Lowdefy.",
6
6
  "homepage": "https://lowdefy.com",
@@ -44,13 +44,13 @@
44
44
  "dist/*"
45
45
  ],
46
46
  "dependencies": {
47
- "@ag-grid-community/client-side-row-model": "30.2.0",
48
- "@ag-grid-community/core": "30.2.0",
49
- "@ag-grid-community/csv-export": "30.2.0",
50
- "@ag-grid-community/react": "30.2.0",
51
- "@ag-grid-community/styles": "30.2.0",
52
- "@lowdefy/block-utils": "5.1.0",
53
- "@lowdefy/helpers": "5.1.0"
47
+ "@ag-grid-community/client-side-row-model": "32.3.9",
48
+ "@ag-grid-community/core": "32.3.9",
49
+ "@ag-grid-community/csv-export": "32.3.9",
50
+ "@ag-grid-community/react": "32.3.9",
51
+ "@ag-grid-community/styles": "32.3.9",
52
+ "@lowdefy/block-utils": "5.3.0",
53
+ "@lowdefy/helpers": "5.3.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@ant-design/icons": ">=6",
@@ -60,8 +60,8 @@
60
60
  "react-dom": ">=18"
61
61
  },
62
62
  "devDependencies": {
63
- "@lowdefy/block-dev-e2e": "5.1.0",
64
- "@lowdefy/e2e-utils": "5.1.0",
63
+ "@lowdefy/block-dev-e2e": "5.3.0",
64
+ "@lowdefy/e2e-utils": "5.3.0",
65
65
  "@playwright/test": "1.50.1",
66
66
  "@swc/cli": "0.8.0",
67
67
  "@swc/core": "1.15.18",