cx 26.1.13 → 26.2.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.
Files changed (130) hide show
  1. package/build/charts/Marker.d.ts +1 -1
  2. package/build/charts/Marker.d.ts.map +1 -1
  3. package/build/charts/Marker.js +16 -6
  4. package/build/charts/MouseTracker.d.ts +2 -0
  5. package/build/charts/MouseTracker.d.ts.map +1 -1
  6. package/build/charts/helpers/PointReducer.d.ts +2 -2
  7. package/build/charts/helpers/PointReducer.d.ts.map +1 -1
  8. package/build/data/View.d.ts +5 -3
  9. package/build/data/View.d.ts.map +1 -1
  10. package/build/data/View.js +3 -1
  11. package/build/data/ops/findTreeNode.d.ts +20 -1
  12. package/build/data/ops/findTreeNode.d.ts.map +1 -1
  13. package/build/data/ops/findTreeNode.js +19 -0
  14. package/build/data/ops/findTreePath.d.ts +1 -1
  15. package/build/data/ops/findTreePath.d.ts.map +1 -1
  16. package/build/data/ops/findTreePath.js +1 -1
  17. package/build/data/ops/removeTreeNodes.d.ts +14 -1
  18. package/build/data/ops/removeTreeNodes.d.ts.map +1 -1
  19. package/build/data/ops/removeTreeNodes.js +13 -0
  20. package/build/data/ops/updateArray.d.ts +1 -1
  21. package/build/data/ops/updateArray.d.ts.map +1 -1
  22. package/build/data/ops/updateArray.js +1 -1
  23. package/build/data/ops/updateTree.d.ts +20 -1
  24. package/build/data/ops/updateTree.d.ts.map +1 -1
  25. package/build/data/ops/updateTree.js +19 -0
  26. package/build/jsx-runtime.d.ts +1 -0
  27. package/build/jsx-runtime.d.ts.map +1 -1
  28. package/build/jsx-runtime.js +3 -1
  29. package/build/svg/Rectangle.d.ts +6 -4
  30. package/build/svg/Rectangle.d.ts.map +1 -1
  31. package/build/svg/Rectangle.js +9 -7
  32. package/build/ui/Instance.d.ts +1 -1
  33. package/build/ui/Instance.d.ts.map +1 -1
  34. package/build/ui/Instance.js +18 -8
  35. package/build/ui/IsolatedScope.d.ts +2 -1
  36. package/build/ui/IsolatedScope.d.ts.map +1 -1
  37. package/build/ui/Prop.d.ts +1 -1
  38. package/build/ui/Prop.d.ts.map +1 -1
  39. package/build/ui/Widget.d.ts +2 -0
  40. package/build/ui/Widget.d.ts.map +1 -1
  41. package/build/ui/Widget.js +4 -0
  42. package/build/ui/adapter/GroupAdapter.d.ts +4 -4
  43. package/build/ui/adapter/GroupAdapter.d.ts.map +1 -1
  44. package/build/ui/adapter/GroupAdapter.js +4 -4
  45. package/build/ui/adapter/TreeAdapter.d.ts +5 -3
  46. package/build/ui/adapter/TreeAdapter.d.ts.map +1 -1
  47. package/build/ui/adapter/TreeAdapter.js +12 -5
  48. package/build/ui/app/startAppLoop.d.ts +2 -2
  49. package/build/ui/app/startAppLoop.d.ts.map +1 -1
  50. package/build/ui/app/startHotAppLoop.d.ts +4 -4
  51. package/build/ui/app/startHotAppLoop.d.ts.map +1 -1
  52. package/build/ui/app/startHotAppLoop.js +1 -1
  53. package/build/ui/batchUpdates.d.ts.map +1 -1
  54. package/build/ui/batchUpdates.js +3 -4
  55. package/build/widgets/Button.d.ts +0 -7
  56. package/build/widgets/Button.d.ts.map +1 -1
  57. package/build/widgets/HtmlElement.d.ts +2 -2
  58. package/build/widgets/HtmlElement.d.ts.map +1 -1
  59. package/build/widgets/form/Checkbox.d.ts +3 -3
  60. package/build/widgets/form/Checkbox.d.ts.map +1 -1
  61. package/build/widgets/form/Checkbox.js +11 -6
  62. package/build/widgets/form/DateTimeField.d.ts +4 -0
  63. package/build/widgets/form/DateTimeField.d.ts.map +1 -1
  64. package/build/widgets/form/LookupField.d.ts +14 -27
  65. package/build/widgets/form/LookupField.d.ts.map +1 -1
  66. package/build/widgets/form/TextField.d.ts +2 -2
  67. package/build/widgets/form/TextField.d.ts.map +1 -1
  68. package/build/widgets/grid/Grid.d.ts +20 -16
  69. package/build/widgets/grid/Grid.d.ts.map +1 -1
  70. package/build/widgets/grid/Grid.js +200 -86
  71. package/build/widgets/nav/Menu.d.ts +2 -0
  72. package/build/widgets/nav/Menu.d.ts.map +1 -1
  73. package/build/widgets/nav/Route.js +1 -1
  74. package/build/widgets/overlay/FlyweightTooltipTracker.d.ts +6 -4
  75. package/build/widgets/overlay/FlyweightTooltipTracker.d.ts.map +1 -1
  76. package/build/widgets/overlay/FlyweightTooltipTracker.js +3 -0
  77. package/build/widgets/overlay/Overlay.d.ts +2 -2
  78. package/build/widgets/overlay/Overlay.d.ts.map +1 -1
  79. package/dist/data.js +52 -1
  80. package/dist/jsx-runtime.js +4 -2
  81. package/dist/manifest.js +879 -873
  82. package/dist/svg.js +3 -0
  83. package/dist/ui.js +1548 -1544
  84. package/dist/widgets.css +1 -1
  85. package/dist/widgets.js +395 -4
  86. package/package.json +2 -2
  87. package/src/charts/Marker.tsx +448 -394
  88. package/src/charts/MouseTracker.tsx +3 -0
  89. package/src/charts/helpers/PointReducer.ts +2 -2
  90. package/src/data/View.ts +76 -19
  91. package/src/data/ops/findTreeNode.ts +20 -1
  92. package/src/data/ops/findTreePath.ts +7 -2
  93. package/src/data/ops/removeTreeNodes.ts +14 -1
  94. package/src/data/ops/updateArray.ts +4 -4
  95. package/src/data/ops/updateTree.ts +32 -6
  96. package/src/index.scss +6 -6
  97. package/src/jsx-runtime.spec.tsx +40 -0
  98. package/src/jsx-runtime.ts +87 -84
  99. package/src/svg/Rectangle.tsx +80 -73
  100. package/src/ui/DataProxy.ts +55 -55
  101. package/src/ui/Instance.ts +142 -45
  102. package/src/ui/IsolatedScope.ts +4 -2
  103. package/src/ui/Prop.ts +141 -141
  104. package/src/ui/Rescope.ts +50 -50
  105. package/src/ui/Widget.tsx +292 -234
  106. package/src/ui/adapter/ArrayAdapter.ts +229 -229
  107. package/src/ui/adapter/GroupAdapter.ts +8 -10
  108. package/src/ui/adapter/TreeAdapter.ts +75 -15
  109. package/src/ui/app/Url.spec.ts +1 -1
  110. package/src/ui/app/startAppLoop.tsx +56 -45
  111. package/src/ui/app/startHotAppLoop.ts +4 -4
  112. package/src/ui/batchUpdates.ts +16 -21
  113. package/src/ui/exprHelpers.ts +96 -96
  114. package/src/widgets/Button.tsx +0 -8
  115. package/src/widgets/HtmlElement.spec.tsx +100 -72
  116. package/src/widgets/HtmlElement.tsx +11 -10
  117. package/src/widgets/Sandbox.ts +104 -104
  118. package/src/widgets/Section.scss +55 -55
  119. package/src/widgets/drag-drop/DropZone.scss +74 -74
  120. package/src/widgets/form/Checkbox.tsx +296 -243
  121. package/src/widgets/form/DateTimeField.tsx +6 -0
  122. package/src/widgets/form/LookupField.tsx +70 -73
  123. package/src/widgets/form/TextField.tsx +2 -2
  124. package/src/widgets/grid/Grid.scss +43 -10
  125. package/src/widgets/grid/Grid.tsx +4401 -3848
  126. package/src/widgets/nav/Menu.tsx +3 -0
  127. package/src/widgets/nav/Route.ts +1 -1
  128. package/src/widgets/overlay/FlyweightTooltipTracker.ts +15 -4
  129. package/src/widgets/overlay/Overlay.tsx +2 -1
  130. package/src/widgets/overlay/index.d.ts +11 -11
@@ -24,6 +24,9 @@ export interface MouseTrackerConfig extends BoundedObjectConfig {
24
24
 
25
25
  /** Name of the y-axis. Default is 'y'. */
26
26
  yAxis?: string;
27
+
28
+ /** Tooltip configuration. */
29
+ tooltip?: any;
27
30
  }
28
31
 
29
32
  export interface MouseTrackerInstance extends BoundedObjectInstance, TooltipParentInstance {
@@ -28,7 +28,7 @@ export interface PointReducerInstance<TAccumulator extends PointReducerAccumulat
28
28
 
29
29
  export interface PointReducerConfig extends PureContainerConfig {
30
30
  /** A callback function used to initialize the accumulator. */
31
- onInitAccumulator?: string | ((accumulator: DataRecord, instance?: Instance) => void);
31
+ onInitAccumulator?: string | ((accumulator: DataRecord, instance: Instance) => void);
32
32
 
33
33
  /** A callback function used to collect information about all data points. */
34
34
  onMap?:
@@ -36,7 +36,7 @@ export interface PointReducerConfig extends PureContainerConfig {
36
36
  | ((accumulator: DataRecord, x?: any, y?: any, name?: string, data?: any, array?: any[], index?: number) => void);
37
37
 
38
38
  /** A callback function used to process accumulated information and write results. */
39
- onReduce?: string | ((accumulator: DataRecord, instance?: Instance) => void);
39
+ onReduce?: string | ((accumulator: DataRecord, instance: Instance) => void);
40
40
 
41
41
  /** Parameters that trigger filter predicate re-creation. */
42
42
  filterParams?: StructuredProp;
package/src/data/View.ts CHANGED
@@ -42,25 +42,44 @@ export interface ViewMethods<D = Record<string, any>> {
42
42
  delete(paths: Path[]): boolean;
43
43
  delete(...paths: Path[]): boolean;
44
44
  delete<V>(path: AccessorChain<V>): boolean;
45
- delete<T extends any[]>(...paths: { [K in keyof T]: AccessorChain<T[K]> }): boolean;
46
- delete<T extends any[]>(paths: { [K in keyof T]: AccessorChain<T[K]> }): boolean;
45
+ delete<T extends any[]>(
46
+ ...paths: { [K in keyof T]: AccessorChain<T[K]> }
47
+ ): boolean;
48
+ delete<T extends any[]>(paths: {
49
+ [K in keyof T]: AccessorChain<T[K]>;
50
+ }): boolean;
47
51
 
48
52
  toggle(path: Path): boolean;
49
53
  toggle(path: AccessorChain<boolean>): boolean;
50
54
 
51
- update(updateFn: (currentValue: D, ...args: any[]) => any, ...args: any): boolean;
52
- update<A extends any[]>(path: Path, updateFn: (currentValue: any, ...args: A) => any, ...args: A): boolean;
55
+ update<V>(path: AccessorChain<V>, updateFn: (currentValue: V) => V): boolean;
53
56
  update<V, A extends any[]>(
54
57
  path: AccessorChain<V>,
55
58
  updateFn: (currentValue: V, ...args: A) => V,
56
59
  ...args: A
57
60
  ): boolean;
61
+ update<A extends any[]>(
62
+ updateFn: (currentValue: D, ...args: A) => D,
63
+ ...args: any
64
+ ): boolean;
65
+ update<A extends any[]>(
66
+ path: Path,
67
+ updateFn: (currentValue: any, ...args: A) => any,
68
+ ...args: A
69
+ ): boolean;
58
70
 
59
71
  /**
60
72
  * Mutates the content of the store using Immer
61
73
  */
62
- mutate(updateFn: (currentValue: D, ...args: any[]) => void, ...args: any[]): boolean;
63
- mutate<A extends any[]>(path: Path, updateFn: (currentValue: any, ...args: A) => void, ...args: A): boolean;
74
+ mutate(
75
+ updateFn: (currentValue: D, ...args: any[]) => void,
76
+ ...args: any[]
77
+ ): boolean;
78
+ mutate<A extends any[]>(
79
+ path: Path,
80
+ updateFn: (currentValue: any, ...args: A) => void,
81
+ ...args: A
82
+ ): boolean;
64
83
  mutate<V, A extends any[]>(
65
84
  path: AccessorChain<V>,
66
85
  updateFn: (currentValue: V, ...args: A) => void,
@@ -74,7 +93,14 @@ export class View<D = any> implements ViewMethods<D> {
74
93
  declare store?: View;
75
94
  declare meta: any;
76
95
  declare sealed: boolean;
77
- cache: { version: number; data?: any; result?: any; itemIndex?: number; key?: string; parentStoreData?: any };
96
+ cache: {
97
+ version: number;
98
+ data?: any;
99
+ result?: any;
100
+ itemIndex?: number;
101
+ key?: string;
102
+ parentStoreData?: any;
103
+ };
78
104
  notificationsSuspended: number = 0;
79
105
  dirty: boolean = false;
80
106
 
@@ -97,11 +123,17 @@ export class View<D = any> implements ViewMethods<D> {
97
123
  if (typeof path == "object" && path != null) {
98
124
  let changed = false;
99
125
  for (let key in path)
100
- if (path.hasOwnProperty(key) && this.get(key) === undefined && this.setItem(key, path[key])) changed = true;
126
+ if (
127
+ path.hasOwnProperty(key) &&
128
+ this.get(key) === undefined &&
129
+ this.setItem(key, path[key])
130
+ )
131
+ changed = true;
101
132
  return changed;
102
133
  }
103
134
  let binding = Binding.get(path);
104
- if (this.get(binding.path) === undefined) return this.setItem(binding.path, value);
135
+ if (this.get(binding.path) === undefined)
136
+ return this.setItem(binding.path, value);
105
137
  return false;
106
138
  }
107
139
 
@@ -111,7 +143,9 @@ export class View<D = any> implements ViewMethods<D> {
111
143
  set(path: any, value?: any): boolean {
112
144
  if (typeof path == "object" && path != null) {
113
145
  let changed = false;
114
- for (let key in path) if (path.hasOwnProperty(key) && this.setItem(key, path[key])) changed = true;
146
+ for (let key in path)
147
+ if (path.hasOwnProperty(key) && this.setItem(key, path[key]))
148
+ changed = true;
115
149
  return changed;
116
150
  }
117
151
  let binding = Binding.get(path);
@@ -144,11 +178,16 @@ export class View<D = any> implements ViewMethods<D> {
144
178
  delete(paths: Path[]): boolean;
145
179
  delete(...paths: Path[]): boolean;
146
180
  delete<V>(path: AccessorChain<V>): boolean;
147
- delete<T extends any[]>(...paths: { [K in keyof T]: AccessorChain<T[K]> }): boolean;
148
- delete<T extends any[]>(paths: { [K in keyof T]: AccessorChain<T[K]> }): boolean;
181
+ delete<T extends any[]>(
182
+ ...paths: { [K in keyof T]: AccessorChain<T[K]> }
183
+ ): boolean;
184
+ delete<T extends any[]>(paths: {
185
+ [K in keyof T]: AccessorChain<T[K]>;
186
+ }): boolean;
149
187
  delete(path?: any): boolean {
150
188
  if (arguments.length > 1) path = Array.from(arguments);
151
- if (isArray(path)) return path.map((arg) => this.delete(arg as Path)).some(Boolean);
189
+ if (isArray(path))
190
+ return path.map((arg) => this.delete(arg as Path)).some(Boolean);
152
191
 
153
192
  let binding = Binding.get(path);
154
193
  return this.deleteItem(binding.path);
@@ -178,7 +217,8 @@ export class View<D = any> implements ViewMethods<D> {
178
217
 
179
218
  if (arguments.length > 1) path = Array.from(arguments);
180
219
 
181
- if (isArray(path)) return path.map((arg) => Binding.get(arg as any).value(storeData));
220
+ if (isArray(path))
221
+ return path.map((arg) => Binding.get(arg as any).value(storeData));
182
222
 
183
223
  return Binding.get(path).value(storeData);
184
224
  }
@@ -189,21 +229,38 @@ export class View<D = any> implements ViewMethods<D> {
189
229
  return this.set(path, !this.get(path));
190
230
  }
191
231
 
192
- update(updateFn: (currentValue: D, ...args: any[]) => any, ...args: any): boolean;
193
- update<A extends any[]>(path: Path, updateFn: (currentValue: any, ...args: A) => any, ...args: A): boolean;
232
+ update<V>(path: AccessorChain<V>, updateFn: (currentValue: V) => V): boolean;
194
233
  update<V, A extends any[]>(
195
234
  path: AccessorChain<V>,
196
235
  updateFn: (currentValue: V, ...args: A) => V,
197
236
  ...args: A
198
237
  ): boolean;
238
+ update(
239
+ updateFn: (currentValue: D, ...args: any[]) => any,
240
+ ...args: any
241
+ ): boolean;
242
+ update<A extends any[]>(
243
+ path: Path,
244
+ updateFn: (currentValue: any, ...args: A) => any,
245
+ ...args: A
246
+ ): boolean;
199
247
  update(path: any, updateFn?: any, ...args: any[]): boolean {
200
248
  if (arguments.length == 1 && isFunction(path))
201
- return this.load(path.apply(null, [this.getData(), updateFn, ...args]));
249
+ return this.load(
250
+ path.apply(null, [this.getData(), updateFn, ...args]),
251
+ );
202
252
  return this.set(path, updateFn.apply(null, [this.get(path), ...args]));
203
253
  }
204
254
 
205
- mutate(updateFn: (currentValue: D, ...args: any[]) => void, ...args: any[]): boolean;
206
- mutate<A extends any[]>(path: Path, updateFn: (currentValue: any, ...args: A) => void, ...args: A): boolean;
255
+ mutate(
256
+ updateFn: (currentValue: D, ...args: any[]) => void,
257
+ ...args: any[]
258
+ ): boolean;
259
+ mutate<A extends any[]>(
260
+ path: Path,
261
+ updateFn: (currentValue: any, ...args: A) => void,
262
+ ...args: A
263
+ ): boolean;
207
264
  mutate<V, A extends any[]>(
208
265
  path: AccessorChain<V>,
209
266
  updateFn: (currentValue: V, ...args: A) => void,
@@ -1,4 +1,23 @@
1
- export function findTreeNode<T = any>(array: T[], criteria: (node: T) => boolean, childrenField: keyof T): T | false {
1
+ /**
2
+ * Recursively searches a tree structure for a node matching the criteria.
3
+ *
4
+ * @param array - The tree data array to search.
5
+ * @param criteria - Predicate that returns `true` when a matching node is found.
6
+ * @param childrenField - Name of the property containing child nodes.
7
+ * @returns The first matching node, or `false` if not found.
8
+ *
9
+ * @example
10
+ * // Find node by ID
11
+ * const node = findTreeNode(data, n => n.id === targetId, "$children");
12
+ *
13
+ * @example
14
+ * // Check if selected node is a leaf
15
+ * const node = findTreeNode(data, n => n.id === selectedId, "$children");
16
+ * if (node && node.$leaf) {
17
+ * // Cannot add children to a leaf node
18
+ * }
19
+ */
20
+ export function findTreeNode<T = any>(array: T[], criteria: (node: T) => boolean, childrenField: NoInfer<keyof T>): T | false {
2
21
  if (!Array.isArray(array)) return false;
3
22
 
4
23
  for (const node of array) {
@@ -1,7 +1,7 @@
1
1
  export function findTreePath<T = any>(
2
2
  array: T[] | undefined,
3
3
  criteria: (node: T) => boolean,
4
- childrenField: keyof T = "$children" as keyof T,
4
+ childrenField: NoInfer<keyof T>,
5
5
  currentPath: T[] = [],
6
6
  ): T[] | false {
7
7
  if (!Array.isArray(array)) return false;
@@ -13,7 +13,12 @@ export function findTreePath<T = any>(
13
13
 
14
14
  const children = node[childrenField] as T[] | undefined;
15
15
 
16
- const childPath = findTreePath(children, criteria, childrenField, currentPath);
16
+ const childPath = findTreePath(
17
+ children,
18
+ criteria,
19
+ childrenField,
20
+ currentPath,
21
+ );
17
22
  if (childPath) return childPath;
18
23
 
19
24
  currentPath.pop();
@@ -1,9 +1,22 @@
1
1
  import { updateTree } from "./updateTree";
2
2
 
3
+ /**
4
+ * Recursively removes nodes from a tree structure that match the criteria.
5
+ * Returns a new tree without the matching nodes, or the original tree if no nodes were removed.
6
+ *
7
+ * @param array - The tree data array to filter.
8
+ * @param criteria - Predicate that returns `true` for nodes to remove.
9
+ * @param childrenField - Name of the property containing child nodes.
10
+ * @returns A new tree with matching nodes removed, or the original array if unchanged.
11
+ *
12
+ * @example
13
+ * // Remove node by ID
14
+ * removeTreeNodes(data, node => node.id === targetId, "$children");
15
+ */
3
16
  export function removeTreeNodes<T = any>(
4
17
  array: T[] | undefined,
5
18
  criteria: (node: T) => boolean,
6
- childrenField: keyof T,
19
+ childrenField: NoInfer<keyof T>,
7
20
  ): T[] | undefined {
8
21
  return updateTree(
9
22
  array,
@@ -1,9 +1,9 @@
1
- export function updateArray<T = any>(
2
- array: T[] | undefined,
1
+ export function updateArray<T = any, A extends T[] | undefined = T[] | undefined>(
2
+ array: A,
3
3
  updateCallback: (item: T, index: number) => T,
4
4
  itemFilter?: null | ((item: T, index: number) => boolean),
5
5
  removeFilter?: (item: T, index: number) => boolean,
6
- ): T[] | undefined {
6
+ ): A {
7
7
  if (!array) return array;
8
8
 
9
9
  const newArray: T[] = [];
@@ -27,5 +27,5 @@ export function updateArray<T = any>(
27
27
  }
28
28
  }
29
29
 
30
- return dirty ? newArray : array;
30
+ return (dirty ? newArray : array) as A;
31
31
  }
@@ -1,20 +1,46 @@
1
1
  import { updateArray } from "./updateArray";
2
2
 
3
- export function updateTree<T = any>(
4
- array: T[] | undefined,
3
+ /**
4
+ * Recursively updates nodes in a tree structure that match a filter.
5
+ * Returns a new tree with updated nodes, or the original tree if no changes were made.
6
+ *
7
+ * @param array - The tree data array to update.
8
+ * @param updateCallback - Function that receives a node and returns the updated node.
9
+ * @param itemFilter - Predicate that returns `true` for nodes to update. If `null`, all nodes are updated.
10
+ * @param childrenField - Name of the property containing child nodes.
11
+ * @param removeFilter - Optional predicate that returns `true` for nodes to remove.
12
+ * @returns A new tree with updates applied, or the original array if unchanged.
13
+ *
14
+ * @example
15
+ * // Expand all non-leaf nodes
16
+ * updateTree(data, node => ({ ...node, $expanded: true }), node => !node.$leaf, "$children");
17
+ *
18
+ * @example
19
+ * // Add a child to a specific node
20
+ * updateTree(data, node => ({ ...node, $children: [...node.$children, newChild] }), node => node.id === parentId, "$children");
21
+ */
22
+ export function updateTree<T = any, A extends T[] | undefined = T[] | undefined>(
23
+ array: A,
5
24
  updateCallback: (item: T) => T,
6
25
  itemFilter: ((item: T) => boolean) | null,
7
- childrenField: keyof T,
26
+ childrenField: NoInfer<keyof T>,
8
27
  removeFilter?: (item: T) => boolean,
9
- ): T[] | undefined {
28
+ ): A {
10
29
  return updateArray(
11
30
  array,
12
31
  (item: T) => {
13
32
  if (!itemFilter || itemFilter(item)) item = updateCallback(item);
14
33
  const children = item[childrenField];
15
34
  if (!Array.isArray(children)) return item;
16
- const updatedChildren = updateTree(children, updateCallback, itemFilter, childrenField, removeFilter);
17
- if (updatedChildren != children) return { ...item, [childrenField]: updatedChildren };
35
+ const updatedChildren = updateTree(
36
+ children,
37
+ updateCallback,
38
+ itemFilter,
39
+ childrenField,
40
+ removeFilter,
41
+ );
42
+ if (updatedChildren != children)
43
+ return { ...item, [childrenField]: updatedChildren };
18
44
  return item;
19
45
  },
20
46
  null,
package/src/index.scss CHANGED
@@ -1,6 +1,6 @@
1
- @import "global";
2
- @import "util/index";
3
- @import "ui/index";
4
- @import "widgets/index";
5
- @import "svg/index";
6
- @import "charts/index";
1
+ @import "global";
2
+ @import "util/index";
3
+ @import "ui/index";
4
+ @import "widgets/index";
5
+ @import "svg/index";
6
+ @import "charts/index";
@@ -465,4 +465,44 @@ describe("jsx-runtime type inference", () => {
465
465
  assert.ok(widget.jsxAttributes.includes("key"));
466
466
  });
467
467
  });
468
+
469
+ describe("Fragment", () => {
470
+ it("returns single child directly", () => {
471
+ const widget = (
472
+ <cx>
473
+ <>
474
+ <div text="Hello" />
475
+ </>
476
+ </cx>
477
+ );
478
+ assert.ok(widget);
479
+ assert.equal(widget.$type.name, "HtmlElement");
480
+ });
481
+
482
+ it("returns array of children", () => {
483
+ const widget = (
484
+ <cx>
485
+ <>
486
+ <div text="Hello" />
487
+ <div text="World" />
488
+ </>
489
+ </cx>
490
+ );
491
+ assert.ok(Array.isArray(widget));
492
+ assert.equal(widget.length, 2);
493
+ });
494
+
495
+ it("works with widgets inside fragment", () => {
496
+ const widget = (
497
+ <cx>
498
+ <>
499
+ <Button text="Click" />
500
+ <TextField value={bind("name")} />
501
+ </>
502
+ </cx>
503
+ );
504
+ assert.ok(Array.isArray(widget));
505
+ assert.equal(widget.length, 2);
506
+ });
507
+ });
468
508
  });
@@ -1,84 +1,87 @@
1
- import type { ComponentClass, FunctionComponent, JSX as ReactJSX } from "react";
2
- import type { CxFunctionalComponent } from "./ui/createFunctionalComponent";
3
- import { Widget } from "./ui/Widget";
4
- import { isArray } from "./util/isArray";
5
- import { isString } from "./util/isString";
6
- import { HtmlElement, HtmlElementConfig } from "./widgets/HtmlElement";
7
- import { ReactElementWrapper, ReactElementWrapperConfig } from "./widgets/ReactElementWrapper";
8
-
9
- export function jsx(typeName: any, props: any, key?: string): any {
10
- if (isArray(typeName)) return typeName;
11
-
12
- if (key) {
13
- // key is allowed in CxJS, i.e. Sandbox use it
14
- props = {
15
- ...props,
16
- key,
17
- };
18
- }
19
-
20
- if (typeName.type || typeName.$type) return typeName;
21
-
22
- if (props.children && isArray(props.children) && props.children.length == 0) props.children = null;
23
-
24
- if (props.children && props.children.length == 1) props.children = props.children[0];
25
-
26
- if (typeName == "cx") return props.children;
27
-
28
- if (isString(typeName) && typeName[0] == typeName[0].toLowerCase()) {
29
- props.tag = typeName;
30
- typeName = HtmlElement;
31
- }
32
- // React components (functions/classes without isComponentType and not CxJS functional components) should go through ReactElementWrapper
33
- else if (typeof typeName === "function" && !typeName.isComponentType && !typeName.$isComponentFactory) {
34
- props.componentType = typeName;
35
- typeName = ReactElementWrapper;
36
- }
37
-
38
- return {
39
- $type: typeName,
40
- ...props,
41
- jsxAttributes: props && Object.keys(props),
42
- };
43
- }
44
-
45
- export const jsxs = jsx;
46
-
47
- type ReactIntrinsicElements = ReactJSX.IntrinsicElements;
48
-
49
- type CxIntrinsicElements = {
50
- [K in keyof ReactIntrinsicElements]: HtmlElementConfig<K>;
51
- };
52
-
53
- // Helper type to transform props for React components (but not CxJS functional components)
54
- // Uses a workaround to avoid TypeScript inference issues with conditional types in LibraryManagedAttributes
55
- // We use Omit to exclude componentType since it's added automatically by the jsx-runtime
56
- type TransformReactComponentProps<C, P> = [C] extends [CxFunctionalComponent<any>]
57
- ? P // CxJS functional components already have proper types
58
- : [C] extends [FunctionComponent<any>]
59
- ? ReactElementWrapperConfig<P>
60
- : [C] extends [ComponentClass<any>]
61
- ? ReactElementWrapperConfig<P>
62
- : P;
63
-
64
- export namespace JSX {
65
- /**
66
- * Base class for JSX element instances.
67
- * All widgets must extend from Widget class or React.Component.
68
- */
69
- export type ElementClass = Widget<any> | React.Component<any, any>;
70
-
71
- /**
72
- * Intrinsic JSX elements (HTML-like tags).
73
- * Supports HTML elements that map to HtmlElement widgets.
74
- */
75
- export interface IntrinsicElements extends CxIntrinsicElements {
76
- cx: any;
77
- }
78
-
79
- /**
80
- * Transform props for React function components used in CxJS JSX.
81
- * Adds standard WidgetConfig properties and transforms React props to Prop<X>.
82
- */
83
- export type LibraryManagedAttributes<C, P> = TransformReactComponentProps<C, P>;
84
- }
1
+ import type { ComponentClass, FunctionComponent, JSX as ReactJSX } from "react";
2
+ import type { CxFunctionalComponent } from "./ui/createFunctionalComponent";
3
+ import { Widget } from "./ui/Widget";
4
+ import { isArray } from "./util/isArray";
5
+ import { isString } from "./util/isString";
6
+ import { HtmlElement, HtmlElementConfig } from "./widgets/HtmlElement";
7
+ import { ReactElementWrapper, ReactElementWrapperConfig } from "./widgets/ReactElementWrapper";
8
+
9
+ // Fragment symbol for supporting <>...</> syntax
10
+ export const Fragment = Symbol.for("cx.fragment");
11
+
12
+ export function jsx(typeName: any, props: any, key?: string): any {
13
+ if (isArray(typeName)) return typeName;
14
+
15
+ if (key) {
16
+ // key is allowed in CxJS, i.e. Sandbox use it
17
+ props = {
18
+ ...props,
19
+ key,
20
+ };
21
+ }
22
+
23
+ if (typeName.type || typeName.$type) return typeName;
24
+
25
+ if (props.children && isArray(props.children) && props.children.length == 0) props.children = null;
26
+
27
+ if (props.children && props.children.length == 1) props.children = props.children[0];
28
+
29
+ if (typeName == "cx" || typeName === Fragment) return props.children;
30
+
31
+ if (isString(typeName) && typeName[0] == typeName[0].toLowerCase()) {
32
+ props.tag = typeName;
33
+ typeName = HtmlElement;
34
+ }
35
+ // React components (functions/classes without isComponentType and not CxJS functional components) should go through ReactElementWrapper
36
+ else if (typeof typeName === "function" && !typeName.isComponentType && !typeName.$isComponentFactory) {
37
+ props.componentType = typeName;
38
+ typeName = ReactElementWrapper;
39
+ }
40
+
41
+ return {
42
+ $type: typeName,
43
+ ...props,
44
+ jsxAttributes: props && Object.keys(props),
45
+ };
46
+ }
47
+
48
+ export const jsxs = jsx;
49
+
50
+ type ReactIntrinsicElements = ReactJSX.IntrinsicElements;
51
+
52
+ type CxIntrinsicElements = {
53
+ [K in keyof ReactIntrinsicElements]: HtmlElementConfig<K>;
54
+ };
55
+
56
+ // Helper type to transform props for React components (but not CxJS functional components)
57
+ // Uses a workaround to avoid TypeScript inference issues with conditional types in LibraryManagedAttributes
58
+ // We use Omit to exclude componentType since it's added automatically by the jsx-runtime
59
+ type TransformReactComponentProps<C, P> = [C] extends [CxFunctionalComponent<any>]
60
+ ? P // CxJS functional components already have proper types
61
+ : [C] extends [FunctionComponent<any>]
62
+ ? ReactElementWrapperConfig<P>
63
+ : [C] extends [ComponentClass<any>]
64
+ ? ReactElementWrapperConfig<P>
65
+ : P;
66
+
67
+ export namespace JSX {
68
+ /**
69
+ * Base class for JSX element instances.
70
+ * All widgets must extend from Widget class or React.Component.
71
+ */
72
+ export type ElementClass = Widget<any> | React.Component<any, any>;
73
+
74
+ /**
75
+ * Intrinsic JSX elements (HTML-like tags).
76
+ * Supports HTML elements that map to HtmlElement widgets.
77
+ */
78
+ export interface IntrinsicElements extends CxIntrinsicElements {
79
+ cx: any;
80
+ }
81
+
82
+ /**
83
+ * Transform props for React function components used in CxJS JSX.
84
+ * Adds standard WidgetConfig properties and transforms React props to Prop<X>.
85
+ */
86
+ export type LibraryManagedAttributes<C, P> = TransformReactComponentProps<C, P>;
87
+ }