@lowdefy/blocks-aggrid 5.6.0 → 6.0.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.
@@ -0,0 +1,310 @@
1
+ # Copyright 2020-2026 Lowdefy, Inc
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ id: aggridmaterial
16
+ type: Box
17
+
18
+ blocks:
19
+ # ============================================
20
+ # BASIC RENDERING
21
+ # ============================================
22
+
23
+ - id: aggridmaterial_basic
24
+ type: AgGridMaterial
25
+ properties:
26
+ columnDefs:
27
+ - headerName: Name
28
+ field: name
29
+ - headerName: Age
30
+ field: age
31
+ rowData:
32
+ - name: Alice
33
+ age: 25
34
+ - name: Bob
35
+ age: 30
36
+ - name: Charlie
37
+ age: 35
38
+
39
+ # ============================================
40
+ # PROPERTY TESTS
41
+ # ============================================
42
+
43
+ - id: aggridmaterial_height
44
+ type: AgGridMaterial
45
+ properties:
46
+ height: 300
47
+ columnDefs:
48
+ - headerName: Name
49
+ field: name
50
+ rowData:
51
+ - name: Alice
52
+ - name: Bob
53
+
54
+ - id: aggridmaterial_default_col_def
55
+ type: AgGridMaterial
56
+ properties:
57
+ defaultColDef:
58
+ sortable: true
59
+ resizable: true
60
+ filter: true
61
+ columnDefs:
62
+ - headerName: Name
63
+ field: name
64
+ - headerName: Age
65
+ field: age
66
+ rowData:
67
+ - name: Alice
68
+ age: 25
69
+ - name: Bob
70
+ age: 30
71
+
72
+ - id: aggridmaterial_empty
73
+ type: AgGridMaterial
74
+ properties:
75
+ columnDefs:
76
+ - headerName: Name
77
+ field: name
78
+ - headerName: Age
79
+ field: age
80
+ rowData: []
81
+
82
+ # ============================================
83
+ # EVENT TESTS
84
+ # ============================================
85
+
86
+ - id: aggridmaterial_onrowclick
87
+ type: AgGridMaterial
88
+ properties:
89
+ columnDefs:
90
+ - headerName: Name
91
+ field: name
92
+ - headerName: Age
93
+ field: age
94
+ rowData:
95
+ - name: Alice
96
+ age: 25
97
+ - name: Bob
98
+ age: 30
99
+ events:
100
+ onRowClick:
101
+ - id: set_row_click
102
+ type: SetState
103
+ params:
104
+ aggridmaterial_row_clicked:
105
+ _event: row.name
106
+
107
+ - id: aggridmaterial_row_click_display
108
+ type: Span
109
+ properties:
110
+ content:
111
+ _if:
112
+ test:
113
+ _ne:
114
+ - _state: aggridmaterial_row_clicked
115
+ - null
116
+ then:
117
+ _string.concat:
118
+ - 'Row: '
119
+ - _state: aggridmaterial_row_clicked
120
+ else: ''
121
+
122
+ - id: aggridmaterial_oncellclick
123
+ type: AgGridMaterial
124
+ properties:
125
+ columnDefs:
126
+ - headerName: Name
127
+ field: name
128
+ - headerName: Age
129
+ field: age
130
+ rowData:
131
+ - name: Alice
132
+ age: 25
133
+ - name: Bob
134
+ age: 30
135
+ events:
136
+ onCellClick:
137
+ - id: set_cell_click
138
+ type: SetState
139
+ params:
140
+ aggridmaterial_cell_clicked:
141
+ _event: cell.value
142
+
143
+ - id: aggridmaterial_cell_click_display
144
+ type: Span
145
+ properties:
146
+ content:
147
+ _if:
148
+ test:
149
+ _ne:
150
+ - _state: aggridmaterial_cell_clicked
151
+ - null
152
+ then:
153
+ _string.concat:
154
+ - 'Cell: '
155
+ - _state: aggridmaterial_cell_clicked
156
+ else: ''
157
+
158
+ - id: aggridmaterial_onrowselected
159
+ type: AgGridMaterial
160
+ properties:
161
+ rowSelection:
162
+ mode: singleRow
163
+ checkboxes: false
164
+ enableClickSelection: true
165
+ columnDefs:
166
+ - headerName: Name
167
+ field: name
168
+ - headerName: Age
169
+ field: age
170
+ rowData:
171
+ - name: Alice
172
+ age: 25
173
+ - name: Bob
174
+ age: 30
175
+ events:
176
+ onRowSelected:
177
+ - id: set_row_selected
178
+ type: SetState
179
+ params:
180
+ aggridmaterial_row_selected:
181
+ _event: row.name
182
+
183
+ - id: aggridmaterial_row_selected_display
184
+ type: Span
185
+ properties:
186
+ content:
187
+ _if:
188
+ test:
189
+ _ne:
190
+ - _state: aggridmaterial_row_selected
191
+ - null
192
+ then:
193
+ _string.concat:
194
+ - 'Selected: '
195
+ - _state: aggridmaterial_row_selected
196
+ else: ''
197
+
198
+ # ============================================
199
+ # SORTING & FILTERING
200
+ # ============================================
201
+
202
+ - id: aggridmaterial_sortable
203
+ type: AgGridMaterial
204
+ properties:
205
+ columnDefs:
206
+ - headerName: Name
207
+ field: name
208
+ sortable: true
209
+ - headerName: Age
210
+ field: age
211
+ sortable: true
212
+ rowData:
213
+ - name: Charlie
214
+ age: 35
215
+ - name: Alice
216
+ age: 25
217
+ - name: Bob
218
+ age: 30
219
+ events:
220
+ onSortChanged:
221
+ - id: set_sort_changed
222
+ type: SetState
223
+ params:
224
+ aggridmaterial_sort_changed: true
225
+
226
+ - id: aggridmaterial_sort_changed_display
227
+ type: Span
228
+ properties:
229
+ content:
230
+ _if:
231
+ test:
232
+ _eq:
233
+ - _state: aggridmaterial_sort_changed
234
+ - true
235
+ then: Sort changed
236
+ else: ''
237
+
238
+ - id: aggridmaterial_filterable
239
+ type: AgGridMaterial
240
+ properties:
241
+ columnDefs:
242
+ - headerName: Name
243
+ field: name
244
+ filter: true
245
+ - headerName: Age
246
+ field: age
247
+ filter: true
248
+ rowData:
249
+ - name: Alice
250
+ age: 25
251
+ - name: Bob
252
+ age: 30
253
+ - name: Charlie
254
+ age: 35
255
+ events:
256
+ onFilterChanged:
257
+ - id: set_filter_changed
258
+ type: SetState
259
+ params:
260
+ aggridmaterial_filter_changed: true
261
+
262
+ - id: aggridmaterial_filter_changed_display
263
+ type: Span
264
+ properties:
265
+ content:
266
+ _if:
267
+ test:
268
+ _eq:
269
+ - _state: aggridmaterial_filter_changed
270
+ - true
271
+ then: Filter changed
272
+ else: ''
273
+
274
+ # ============================================
275
+ # BUILT-IN CELL RENDERERS — cross-theme smoke
276
+ # ============================================
277
+
278
+ - id: aggridmaterial_cell_types
279
+ type: AgGridMaterial
280
+ properties:
281
+ columnDefs:
282
+ - headerName: Status
283
+ field: status
284
+ cell:
285
+ type: tag
286
+ colorMap:
287
+ Active: green
288
+ Blocked: red
289
+ - headerName: Assignee
290
+ field: name
291
+ cell:
292
+ type: avatar
293
+ nameField: name
294
+ idField: id
295
+ - headerName: Revenue
296
+ field: revenue
297
+ cell:
298
+ type: number
299
+ format: currency
300
+ currency: USD
301
+ decimals: 0
302
+ rowData:
303
+ - id: u1
304
+ status: Active
305
+ name: Alice Johnson
306
+ revenue: 12500
307
+ - id: u2
308
+ status: Blocked
309
+ name: Bob Smith
310
+ revenue: -4200
@@ -0,0 +1,110 @@
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, Dropdown } 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, item, data) {
22
+ if (type.isString(item?.[fieldKey])) return resolvePath(item[fieldKey], data);
23
+ return item?.[literalKey];
24
+ }
25
+ function MenuCell(params) {
26
+ const { value, data, cellConfig, methods, components } = params;
27
+ const Icon = components?.Icon;
28
+ const items = type.isArray(cellConfig?.items) ? cellConfig.items : [];
29
+ // Resolve every item against the row before antd sees it, and drop hidden items
30
+ // rather than disabling them — so a row whose every item is hidden renders no
31
+ // trigger at all, instead of a button that opens an empty menu.
32
+ const resolved = items.map((item, idx)=>{
33
+ if (!type.isObject(item) || !type.isString(item.eventName)) return null;
34
+ if (resolveField('hidden', 'hiddenField', item, data) === true) return null;
35
+ return {
36
+ idx,
37
+ item,
38
+ title: resolveField('title', 'titleField', item, data),
39
+ iconConfig: resolveField('icon', 'iconField', item, data),
40
+ disabled: resolveField('disabled', 'disabledField', item, data) === true
41
+ };
42
+ }).filter(Boolean);
43
+ if (resolved.length === 0) return /*#__PURE__*/ React.createElement(NullCell, null);
44
+ const menuItems = resolved.map(({ idx, item, title, iconConfig, disabled })=>({
45
+ // Keyed by the item's index in the AUTHORED array, not by its position in the
46
+ // resolved one, so onClick can find it back after hidden items have shifted
47
+ // everything.
48
+ key: String(idx),
49
+ label: type.isNone(title) ? undefined : renderHtml({
50
+ html: String(title),
51
+ methods
52
+ }),
53
+ icon: iconConfig && Icon ? /*#__PURE__*/ React.createElement(Icon, {
54
+ blockId: `menucell_${idx}_icon`,
55
+ events: {},
56
+ properties: iconConfig
57
+ }) : undefined,
58
+ disabled,
59
+ danger: item.danger === true
60
+ }));
61
+ function onClick({ key, domEvent }) {
62
+ domEvent?.stopPropagation?.();
63
+ const entry = resolved.find((r)=>String(r.idx) === key);
64
+ if (!entry) return;
65
+ methods?.triggerEvent?.({
66
+ name: entry.item.eventName,
67
+ event: {
68
+ row: data,
69
+ value,
70
+ item: {
71
+ eventName: entry.item.eventName,
72
+ title: entry.title
73
+ },
74
+ itemIndex: entry.idx
75
+ }
76
+ });
77
+ }
78
+ // The trigger is icon-only: a row menu is an affordance, not a labelled action.
79
+ const triggerIcon = Icon ? /*#__PURE__*/ React.createElement(Icon, {
80
+ blockId: "menucell_trigger_icon",
81
+ events: {},
82
+ properties: cellConfig.icon ?? 'AiOutlineMore'
83
+ }) : undefined;
84
+ return(// stopPropagation lives on a wrapper, not on the Button: antd's Dropdown clones its
85
+ // child to attach the trigger handler, so an onClick of ours on the Button is the
86
+ // one that gets shadowed. It keeps a stray click from bubbling within React;
87
+ // ag-grid uses native listeners, so a grid that also wires onCellClick /
88
+ // onRowClick may still see the click — the same as the buttons cell.
89
+ /*#__PURE__*/ React.createElement("div", {
90
+ onClick: (e)=>e.stopPropagation()
91
+ }, /*#__PURE__*/ React.createElement(Dropdown, {
92
+ menu: {
93
+ items: menuItems,
94
+ onClick
95
+ },
96
+ trigger: [
97
+ 'click'
98
+ ],
99
+ placement: cellConfig.placement ?? 'bottomRight',
100
+ // Render the menu to the body so it is not clipped by the cell, which hides
101
+ // its overflow — the same reason SelectorCell does it.
102
+ getPopupContainer: ()=>document.body
103
+ }, /*#__PURE__*/ React.createElement(Button, {
104
+ size: "small",
105
+ type: "text",
106
+ shape: "square",
107
+ icon: triggerIcon
108
+ }))));
109
+ }
110
+ export default MenuCell;
@@ -20,6 +20,7 @@ import BooleanCell from './BooleanCell.js';
20
20
  import ProgressCell from './ProgressCell.js';
21
21
  import NumberCell from './NumberCell.js';
22
22
  import ButtonsCell from './ButtonsCell.js';
23
+ import MenuCell from './MenuCell.js';
23
24
  import SelectorCell from './SelectorCell.js';
24
25
  import SwitchCell from './SwitchCell.js';
25
26
  import TextInputCell from './TextInputCell.js';
@@ -33,6 +34,7 @@ const CELL_RENDERERS = {
33
34
  progress: ProgressCell,
34
35
  number: NumberCell,
35
36
  buttons: ButtonsCell,
37
+ menu: MenuCell,
36
38
  selector: SelectorCell,
37
39
  multipleSelector: SelectorCell,
38
40
  switch: SwitchCell,
@@ -87,6 +87,15 @@ function createDisplayMeta(blockName, { size = false } = {}) {
87
87
  button: 'The clicked button: { eventName, title }.',
88
88
  buttonIndex: 'Zero-based index in the buttons array.'
89
89
  }
90
+ },
91
+ onCellMenuItem: {
92
+ description: 'Documentation reference — the actual event name fired is the `eventName` string declared on each `cell.items[]` entry of a `cell.type: menu` cell. Wire any number of named events on the block (e.g. `onRename`, `onDelete`).',
93
+ event: {
94
+ row: 'The row data.',
95
+ value: 'The cell value.',
96
+ item: 'The clicked item: { eventName, title }.',
97
+ itemIndex: 'Zero-based index in the authored items array (unshifted by hidden items).'
98
+ }
90
99
  }
91
100
  },
92
101
  properties: {
@@ -228,6 +237,7 @@ function createDisplayMeta(blockName, { size = false } = {}) {
228
237
  'progress',
229
238
  'number',
230
239
  'buttons',
240
+ 'menu',
231
241
  'selector',
232
242
  'multipleSelector',
233
243
  'switch',
@@ -555,6 +565,90 @@ function createDisplayMeta(blockName, { size = false } = {}) {
555
565
  }
556
566
  }
557
567
  },
568
+ items: {
569
+ type: 'array',
570
+ description: 'Menu cell: items in the row action menu, rendered in a dropdown behind a single trigger button. Each item triggers its own block-level event (declared in `events:`). `*Field` variants (`titleField`, `iconField`, `disabledField`, `hiddenField`) are row-data paths. A hidden item is dropped, not disabled — a row on which every item is hidden renders no trigger at all.',
571
+ items: {
572
+ type: 'object',
573
+ required: [
574
+ 'eventName'
575
+ ],
576
+ properties: {
577
+ eventName: {
578
+ type: 'string',
579
+ description: 'Block-level event name to trigger on click. Event payload is `{ row, value, item: { eventName, title }, itemIndex }`.'
580
+ },
581
+ title: {
582
+ type: 'string',
583
+ description: 'Item label - supports html.'
584
+ },
585
+ titleField: {
586
+ type: 'string',
587
+ description: 'Row-data path for the label.'
588
+ },
589
+ icon: {
590
+ type: [
591
+ 'string',
592
+ 'object'
593
+ ],
594
+ description: 'Name of a React-Icon or Icon block config.',
595
+ docs: {
596
+ displayType: 'icon'
597
+ }
598
+ },
599
+ iconField: {
600
+ type: 'string',
601
+ description: 'Row-data path for the icon name or config.'
602
+ },
603
+ danger: {
604
+ type: 'boolean',
605
+ default: false,
606
+ description: 'Render the item in the danger colour.'
607
+ },
608
+ disabled: {
609
+ type: 'boolean',
610
+ default: false
611
+ },
612
+ disabledField: {
613
+ type: 'string',
614
+ description: 'Row-data path → boolean.'
615
+ },
616
+ hidden: {
617
+ type: 'boolean',
618
+ default: false,
619
+ description: 'Hide the item entirely.'
620
+ },
621
+ hiddenField: {
622
+ type: 'string',
623
+ description: 'Row-data path → boolean.'
624
+ }
625
+ }
626
+ }
627
+ },
628
+ icon: {
629
+ type: [
630
+ 'string',
631
+ 'object'
632
+ ],
633
+ default: 'AiOutlineMore',
634
+ description: 'Menu cell: the trigger icon. Name of a React-Icon or Icon block config. The trigger is icon-only.',
635
+ docs: {
636
+ displayType: 'icon'
637
+ }
638
+ },
639
+ placement: {
640
+ type: 'string',
641
+ enum: [
642
+ 'bottomLeft',
643
+ 'bottom',
644
+ 'bottomRight',
645
+ 'topLeft',
646
+ 'top',
647
+ 'topRight'
648
+ ],
649
+ default: 'bottomRight',
650
+ description: 'Menu cell: where the dropdown opens relative to its trigger.'
651
+ },
558
652
  options: {
559
653
  type: 'array',
560
654
  description: 'Selector / Multiple selector cell: dropdown options. An array of primitives, or objects mirroring the `Selector` block options: `{ label, value, disabled, color, filterString, style }`. `label` supports html.'