@lowdefy/blocks-aggrid 5.4.0 → 5.5.1

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,192 @@
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, { useMemo } from 'react';
16
+ import { ConfigProvider, Select, Tag } from 'antd';
17
+ import { renderHtml } from '@lowdefy/block-utils';
18
+ import { get, type } from '@lowdefy/helpers';
19
+ import getSelectorOptions from '@lowdefy/blocks-antd/getSelectorOptions.js';
20
+ import getSelectedIndex from '@lowdefy/blocks-antd/getSelectedIndex.js';
21
+ import getContrastTextColor from '@lowdefy/blocks-antd/getContrastTextColor.js';
22
+ import getOptionColorStyle from '@lowdefy/blocks-antd/getOptionColorStyle.js';
23
+ import NullCell from './NullCell.js';
24
+ const Option = Select.Option;
25
+ // Maps an option index (the antd value) back to the real option value, the same way the
26
+ // Selector / MultipleSelector blocks do.
27
+ function indexToValue(options, index) {
28
+ const opt = options[index];
29
+ return type.isPrimitive(opt) ? opt : opt.value;
30
+ }
31
+ // Keep the dropdown open when clicking a tag's close icon.
32
+ function preventMouseDown(e) {
33
+ e.preventDefault();
34
+ e.stopPropagation();
35
+ }
36
+ function SelectorCell(params) {
37
+ const { value, data, cellConfig, methods } = params;
38
+ const multiple = cellConfig?.type === 'multipleSelector';
39
+ // Reuse the block option-normalisation util. Static options only (no per-cell `setData`),
40
+ // so call the pure helper directly rather than the block's `useSelectorOptions` hook.
41
+ const options = useMemo(()=>getSelectorOptions({
42
+ properties: cellConfig ?? {}
43
+ }), [
44
+ cellConfig
45
+ ]);
46
+ if (!type.isArray(options) || options.length === 0) return /*#__PURE__*/ React.createElement(NullCell, null);
47
+ // Display grid is one-way: drive the displayed value from the cell value. The chosen value is
48
+ // written back into ag-grid's own row node (not the Lowdefy block value) for immediate feedback
49
+ // that survives re-renders; the app persists via the event chain.
50
+ const selected = getSelectedIndex(value, options, {
51
+ properties: cellConfig,
52
+ multiple
53
+ });
54
+ // Variant handling mirrors the Selector / MultipleSelector blocks: `solid` is not a real antd
55
+ // Select variant — render an outlined frame and (for single-select) fill it via ConfigProvider;
56
+ // `bordered: false` maps to the borderless variant.
57
+ const isSolid = cellConfig.variant === 'solid';
58
+ let antdVariant = cellConfig.variant;
59
+ if (isSolid) antdVariant = 'outlined';
60
+ if (cellConfig.bordered === false) antdVariant = 'borderless';
61
+ const isOutline = antdVariant === 'outlined';
62
+ // Single-select: tint the whole control with the selected option's colour.
63
+ const selectedOption = multiple || type.isNone(selected) ? undefined : options[selected];
64
+ const selectedColor = type.isObject(selectedOption) ? selectedOption.color : undefined;
65
+ let selectTheme;
66
+ if (selectedColor) {
67
+ const token = {
68
+ colorPrimary: selectedColor,
69
+ colorBorder: selectedColor
70
+ };
71
+ if (isSolid) token.colorBgContainer = selectedColor;
72
+ selectTheme = {
73
+ token
74
+ };
75
+ }
76
+ function onChange(index) {
77
+ let newValue;
78
+ if (multiple) {
79
+ newValue = (type.isArray(index) ? index : []).map((i)=>indexToValue(options, i));
80
+ } else {
81
+ newValue = type.isNone(index) ? undefined : indexToValue(options, index);
82
+ }
83
+ const colId = params.column?.getColId?.();
84
+ if (params.node && colId) params.node.setDataValue(colId, newValue);
85
+ if (type.isString(cellConfig.eventName)) {
86
+ methods?.triggerEvent?.({
87
+ name: cellConfig.eventName,
88
+ event: {
89
+ row: data,
90
+ value,
91
+ newValue
92
+ }
93
+ });
94
+ }
95
+ }
96
+ // Colour the selected label (single-select), following the block: solid fill → contrast text.
97
+ function labelRender(labelProps) {
98
+ const opt = options[labelProps.value];
99
+ const color = type.isPrimitive(opt) ? undefined : opt?.color;
100
+ if (type.isNone(color)) return labelProps.label;
101
+ const textColor = isSolid ? getContrastTextColor(color) ?? '#fff' : color;
102
+ return /*#__PURE__*/ React.createElement("span", {
103
+ style: {
104
+ color: textColor
105
+ }
106
+ }, labelProps.label);
107
+ }
108
+ // Colour the selected tags (multi-select), following the block: a hex colour gets an explicit
109
+ // solid/outlined style (dark-mode safe), an antd preset name is handed to antd's Tag `color`.
110
+ function tagRender(tagProps) {
111
+ const { label, value: idx, closable, onClose } = tagProps;
112
+ const opt = options[idx];
113
+ const color = type.isPrimitive(opt) ? undefined : opt?.color;
114
+ const contrast = type.isNone(color) ? undefined : getContrastTextColor(color);
115
+ const colorStyle = contrast ? getOptionColorStyle({
116
+ color,
117
+ isOutline
118
+ }) : {};
119
+ return /*#__PURE__*/ React.createElement(Tag, {
120
+ closable: closable,
121
+ onClose: onClose,
122
+ onMouseDown: preventMouseDown,
123
+ color: contrast || type.isNone(color) ? undefined : color,
124
+ style: {
125
+ marginInlineEnd: 4,
126
+ ...colorStyle
127
+ }
128
+ }, label);
129
+ }
130
+ const hasColouredOptions = options.some((opt)=>!type.isPrimitive(opt) && opt.color);
131
+ return(// The wrapper must fill the cell — the antd cell layout is flex, so without an explicit width
132
+ // the Select shrinks to its content instead of spanning the column. Synthetic stopPropagation
133
+ // keeps a stray click from bubbling within React; ag-grid uses native listeners, so a grid that
134
+ // also wires onCellClick / onRowClick may still see selector clicks (same as the buttons cell).
135
+ /*#__PURE__*/ React.createElement("div", {
136
+ style: {
137
+ width: '100%'
138
+ },
139
+ onClick: (e)=>e.stopPropagation()
140
+ }, /*#__PURE__*/ React.createElement(ConfigProvider, {
141
+ theme: selectTheme
142
+ }, /*#__PURE__*/ React.createElement(Select, {
143
+ style: {
144
+ width: '100%',
145
+ minWidth: 'min(150px, 100%)'
146
+ },
147
+ size: cellConfig.size ?? 'small',
148
+ variant: antdVariant,
149
+ mode: multiple ? 'multiple' : undefined,
150
+ value: selected,
151
+ onChange: onChange,
152
+ allowClear: cellConfig.allowClear !== false,
153
+ showSearch: get(cellConfig, 'showSearch', {
154
+ default: true
155
+ }),
156
+ showArrow: cellConfig.showArrow,
157
+ autoFocus: cellConfig.autoFocus,
158
+ maxTagCount: cellConfig.maxTagCount,
159
+ autoClearSearchValue: cellConfig.autoClearSearchValue,
160
+ placeholder: cellConfig.placeholder,
161
+ disabled: cellConfig.disabled === true,
162
+ labelRender: multiple ? undefined : labelRender,
163
+ tagRender: multiple && hasColouredOptions ? tagRender : undefined,
164
+ // Render the dropdown to the body so it is not clipped by the ag-grid cell.
165
+ getPopupContainer: ()=>document.body,
166
+ filterOption: (input, option)=>(option.filterstring || option.children.props.html || '').toLowerCase().indexOf(input.toLowerCase()) >= 0
167
+ }, options.map((opt, i)=>type.isPrimitive(opt) ? /*#__PURE__*/ React.createElement(Option, {
168
+ key: i,
169
+ value: `${i}`
170
+ }, renderHtml({
171
+ html: `${opt}`,
172
+ methods
173
+ })) : /*#__PURE__*/ React.createElement(Option, {
174
+ key: i,
175
+ value: `${i}`,
176
+ disabled: opt.disabled,
177
+ filterstring: opt.filterString,
178
+ style: {
179
+ ...opt.style,
180
+ ...opt.color ? {
181
+ color: opt.color
182
+ } : {}
183
+ }
184
+ }, type.isNone(opt.label) ? renderHtml({
185
+ html: `${opt.value}`,
186
+ methods
187
+ }) : renderHtml({
188
+ html: opt.label,
189
+ methods
190
+ })))))));
191
+ }
192
+ export default SelectorCell;
@@ -0,0 +1,74 @@
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 { ConfigProvider, Switch } from 'antd';
17
+ import { serializer, type } from '@lowdefy/helpers';
18
+ function normaliseIcon(icon) {
19
+ const copy = serializer.copy(icon);
20
+ return type.isString(copy) ? {
21
+ name: copy
22
+ } : copy;
23
+ }
24
+ function SwitchCell(params) {
25
+ const { value, data, cellConfig, methods, components } = params;
26
+ const Icon = components?.Icon;
27
+ function onChange(newValue) {
28
+ const colId = params.column?.getColId?.();
29
+ if (params.node && colId) params.node.setDataValue(colId, newValue);
30
+ if (type.isString(cellConfig.eventName)) {
31
+ methods?.triggerEvent?.({
32
+ name: cellConfig.eventName,
33
+ event: {
34
+ row: data,
35
+ value,
36
+ newValue
37
+ }
38
+ });
39
+ }
40
+ }
41
+ const checkedIcon = normaliseIcon(cellConfig.checkedIcon);
42
+ const uncheckedIcon = normaliseIcon(cellConfig.uncheckedIcon);
43
+ function children(text, icon, key) {
44
+ if (!type.isNone(text)) return /*#__PURE__*/ React.createElement("span", null, text);
45
+ if (icon && Icon) return /*#__PURE__*/ React.createElement(Icon, {
46
+ blockId: `switchcell_${key}`,
47
+ events: {},
48
+ properties: icon
49
+ });
50
+ return undefined;
51
+ }
52
+ const switchEl = /*#__PURE__*/ React.createElement(Switch, {
53
+ checked: !!value,
54
+ size: cellConfig.size ?? 'small',
55
+ disabled: cellConfig.disabled === true,
56
+ autoFocus: cellConfig.autoFocus,
57
+ checkedChildren: children(cellConfig.checkedText, checkedIcon, 'checked'),
58
+ unCheckedChildren: children(cellConfig.uncheckedText, uncheckedIcon, 'unchecked'),
59
+ onChange: onChange
60
+ });
61
+ return(// Synthetic stopPropagation keeps a stray click from bubbling within React (see SelectorCell).
62
+ /*#__PURE__*/ React.createElement("div", {
63
+ onClick: (e)=>e.stopPropagation()
64
+ }, cellConfig.color ? /*#__PURE__*/ React.createElement(ConfigProvider, {
65
+ theme: {
66
+ components: {
67
+ Switch: {
68
+ colorPrimary: cellConfig.color
69
+ }
70
+ }
71
+ }
72
+ }, switchEl) : switchEl));
73
+ }
74
+ export default SwitchCell;
@@ -0,0 +1,66 @@
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, { useState, useEffect } from 'react';
16
+ import { Input } from 'antd';
17
+ import { type } from '@lowdefy/helpers';
18
+ function TextInputCell(params) {
19
+ const { value, data, cellConfig, methods } = params;
20
+ // Keep the typed text local: firing the change event per keystroke would re-render the grid,
21
+ // remount the cell and lose focus. Commit once on blur / Enter instead. Resync when the row
22
+ // value changes externally (e.g. after the app persists).
23
+ const [text, setText] = useState(value ?? '');
24
+ useEffect(()=>setText(value ?? ''), [
25
+ value
26
+ ]);
27
+ function commit() {
28
+ if ((value ?? '') === text) return;
29
+ const colId = params.column?.getColId?.();
30
+ if (params.node && colId) params.node.setDataValue(colId, text);
31
+ if (type.isString(cellConfig.eventName)) {
32
+ methods?.triggerEvent?.({
33
+ name: cellConfig.eventName,
34
+ event: {
35
+ row: data,
36
+ value,
37
+ newValue: text
38
+ }
39
+ });
40
+ }
41
+ }
42
+ return(// Width fills the cell; synthetic stopPropagation keeps clicks from bubbling (see SelectorCell).
43
+ /*#__PURE__*/ React.createElement("div", {
44
+ style: {
45
+ width: '100%'
46
+ },
47
+ onClick: (e)=>e.stopPropagation()
48
+ }, /*#__PURE__*/ React.createElement(Input, {
49
+ style: {
50
+ width: '100%'
51
+ },
52
+ size: cellConfig.size ?? 'small',
53
+ value: text,
54
+ onChange: (e)=>setText(e.target.value),
55
+ onBlur: commit,
56
+ onPressEnter: commit,
57
+ allowClear: cellConfig.allowClear,
58
+ placeholder: cellConfig.placeholder,
59
+ maxLength: cellConfig.maxLength,
60
+ showCount: cellConfig.showCount,
61
+ variant: cellConfig.bordered === false ? 'borderless' : cellConfig.variant,
62
+ type: cellConfig.inputType,
63
+ disabled: cellConfig.disabled === true
64
+ })));
65
+ }
66
+ export default TextInputCell;
@@ -20,6 +20,10 @@ 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 SelectorCell from './SelectorCell.js';
24
+ import SwitchCell from './SwitchCell.js';
25
+ import TextInputCell from './TextInputCell.js';
26
+ import ParagraphInputCell from './ParagraphInputCell.js';
23
27
  const CELL_RENDERERS = {
24
28
  tag: TagCell,
25
29
  avatar: AvatarCell,
@@ -28,7 +32,12 @@ const CELL_RENDERERS = {
28
32
  boolean: BooleanCell,
29
33
  progress: ProgressCell,
30
34
  number: NumberCell,
31
- buttons: ButtonsCell
35
+ buttons: ButtonsCell,
36
+ selector: SelectorCell,
37
+ multipleSelector: SelectorCell,
38
+ switch: SwitchCell,
39
+ textInput: TextInputCell,
40
+ paragraphInput: ParagraphInputCell
32
41
  };
33
42
  function getCellRenderer(type) {
34
43
  return CELL_RENDERERS[type];