@object-ui/core 3.3.0 → 3.3.2

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 (101) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +20 -1
  3. package/dist/actions/ActionRunner.d.ts +9 -0
  4. package/dist/actions/ActionRunner.js +41 -4
  5. package/dist/adapters/ValueDataSource.js +3 -1
  6. package/dist/registry/Registry.d.ts +47 -0
  7. package/dist/registry/Registry.js +92 -0
  8. package/dist/utils/filter-converter.js +25 -5
  9. package/package.json +32 -8
  10. package/.turbo/turbo-build.log +0 -4
  11. package/src/__benchmarks__/core.bench.ts +0 -64
  12. package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
  13. package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
  14. package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
  15. package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
  16. package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
  17. package/src/actions/ActionEngine.ts +0 -268
  18. package/src/actions/ActionRunner.ts +0 -717
  19. package/src/actions/TransactionManager.ts +0 -521
  20. package/src/actions/UndoManager.ts +0 -215
  21. package/src/actions/__tests__/ActionEngine.test.ts +0 -206
  22. package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
  23. package/src/actions/__tests__/ActionRunner.test.ts +0 -711
  24. package/src/actions/__tests__/TransactionManager.test.ts +0 -447
  25. package/src/actions/__tests__/UndoManager.test.ts +0 -320
  26. package/src/actions/index.ts +0 -12
  27. package/src/adapters/ApiDataSource.ts +0 -376
  28. package/src/adapters/README.md +0 -180
  29. package/src/adapters/ValueDataSource.ts +0 -459
  30. package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
  31. package/src/adapters/__tests__/ValueDataSource.test.ts +0 -571
  32. package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
  33. package/src/adapters/index.ts +0 -15
  34. package/src/adapters/resolveDataSource.ts +0 -79
  35. package/src/builder/__tests__/schema-builder.test.ts +0 -235
  36. package/src/builder/schema-builder.ts +0 -584
  37. package/src/data-scope/DataScopeManager.ts +0 -269
  38. package/src/data-scope/ViewDataProvider.ts +0 -282
  39. package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
  40. package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
  41. package/src/data-scope/index.ts +0 -24
  42. package/src/errors/__tests__/errors.test.ts +0 -292
  43. package/src/errors/index.ts +0 -269
  44. package/src/evaluator/ExpressionCache.ts +0 -206
  45. package/src/evaluator/ExpressionContext.ts +0 -118
  46. package/src/evaluator/ExpressionEvaluator.ts +0 -315
  47. package/src/evaluator/FormulaFunctions.ts +0 -398
  48. package/src/evaluator/SafeExpressionParser.ts +0 -893
  49. package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
  50. package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
  51. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -558
  52. package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
  53. package/src/evaluator/index.ts +0 -13
  54. package/src/index.ts +0 -38
  55. package/src/protocols/DndProtocol.ts +0 -168
  56. package/src/protocols/KeyboardProtocol.ts +0 -181
  57. package/src/protocols/NotificationProtocol.ts +0 -150
  58. package/src/protocols/ResponsiveProtocol.ts +0 -210
  59. package/src/protocols/SharingProtocol.ts +0 -185
  60. package/src/protocols/index.ts +0 -13
  61. package/src/query/__tests__/query-ast.test.ts +0 -211
  62. package/src/query/__tests__/window-functions.test.ts +0 -275
  63. package/src/query/index.ts +0 -7
  64. package/src/query/query-ast.ts +0 -341
  65. package/src/registry/PluginScopeImpl.ts +0 -259
  66. package/src/registry/PluginSystem.ts +0 -206
  67. package/src/registry/Registry.ts +0 -219
  68. package/src/registry/WidgetRegistry.ts +0 -316
  69. package/src/registry/__tests__/PluginSystem.test.ts +0 -309
  70. package/src/registry/__tests__/Registry.test.ts +0 -293
  71. package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
  72. package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
  73. package/src/theme/ThemeEngine.ts +0 -530
  74. package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
  75. package/src/theme/index.ts +0 -24
  76. package/src/types/index.ts +0 -21
  77. package/src/utils/__tests__/debug-collector.test.ts +0 -102
  78. package/src/utils/__tests__/debug.test.ts +0 -134
  79. package/src/utils/__tests__/expand-fields.test.ts +0 -120
  80. package/src/utils/__tests__/extract-records.test.ts +0 -50
  81. package/src/utils/__tests__/filter-converter.test.ts +0 -118
  82. package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
  83. package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
  84. package/src/utils/debug-collector.ts +0 -100
  85. package/src/utils/debug.ts +0 -148
  86. package/src/utils/expand-fields.ts +0 -76
  87. package/src/utils/extract-records.ts +0 -33
  88. package/src/utils/filter-converter.ts +0 -133
  89. package/src/utils/merge-views-into-objects.ts +0 -36
  90. package/src/utils/normalize-quick-filter.ts +0 -78
  91. package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
  92. package/src/validation/__tests__/schema-validator.test.ts +0 -118
  93. package/src/validation/__tests__/validation-engine.test.ts +0 -102
  94. package/src/validation/index.ts +0 -10
  95. package/src/validation/schema-validator.ts +0 -344
  96. package/src/validation/validation-engine.ts +0 -528
  97. package/src/validation/validators/index.ts +0 -25
  98. package/src/validation/validators/object-validation-engine.ts +0 -722
  99. package/tsconfig.json +0 -15
  100. package/tsconfig.tsbuildinfo +0 -1
  101. package/vitest.config.ts +0 -2
@@ -1,447 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect } from 'vitest';
10
- import { FormulaFunctions } from '../FormulaFunctions';
11
- import { ExpressionEvaluator, evaluateExpression as evalExprFn } from '../ExpressionEvaluator';
12
-
13
- describe('FormulaFunctions', () => {
14
- describe('Registry', () => {
15
- it('should register and retrieve custom functions', () => {
16
- const formulas = new FormulaFunctions();
17
- formulas.register('DOUBLE', (x: number) => x * 2);
18
- expect(formulas.has('DOUBLE')).toBe(true);
19
- expect(formulas.get('DOUBLE')!(5)).toBe(10);
20
- });
21
-
22
- it('should be case-insensitive', () => {
23
- const formulas = new FormulaFunctions();
24
- expect(formulas.has('sum')).toBe(true);
25
- expect(formulas.has('SUM')).toBe(true);
26
- expect(formulas.has('Sum')).toBe(true);
27
- });
28
-
29
- it('should list all registered function names', () => {
30
- const formulas = new FormulaFunctions();
31
- const names = formulas.getNames();
32
- expect(names).toContain('SUM');
33
- expect(names).toContain('AVG');
34
- expect(names).toContain('IF');
35
- expect(names).toContain('TODAY');
36
- expect(names).toContain('CONCAT');
37
- });
38
-
39
- it('should export functions as an object', () => {
40
- const formulas = new FormulaFunctions();
41
- const obj = formulas.toObject();
42
- expect(typeof obj.SUM).toBe('function');
43
- expect(typeof obj.IF).toBe('function');
44
- });
45
- });
46
-
47
- describe('Aggregation Functions', () => {
48
- it('SUM should sum numeric values', () => {
49
- const formulas = new FormulaFunctions();
50
- const SUM = formulas.get('SUM')!;
51
- expect(SUM(1, 2, 3)).toBe(6);
52
- expect(SUM(10, 20)).toBe(30);
53
- });
54
-
55
- it('SUM should handle arrays', () => {
56
- const formulas = new FormulaFunctions();
57
- const SUM = formulas.get('SUM')!;
58
- expect(SUM([1, 2, 3])).toBe(6);
59
- expect(SUM([10], [20])).toBe(30);
60
- });
61
-
62
- it('SUM should handle empty args', () => {
63
- const formulas = new FormulaFunctions();
64
- const SUM = formulas.get('SUM')!;
65
- expect(SUM()).toBe(0);
66
- });
67
-
68
- it('AVG should calculate average', () => {
69
- const formulas = new FormulaFunctions();
70
- const AVG = formulas.get('AVG')!;
71
- expect(AVG(10, 20, 30)).toBe(20);
72
- expect(AVG([2, 4, 6])).toBe(4);
73
- });
74
-
75
- it('AVG should return 0 for empty args', () => {
76
- const formulas = new FormulaFunctions();
77
- const AVG = formulas.get('AVG')!;
78
- expect(AVG()).toBe(0);
79
- });
80
-
81
- it('COUNT should count non-null values', () => {
82
- const formulas = new FormulaFunctions();
83
- const COUNT = formulas.get('COUNT')!;
84
- expect(COUNT(1, 2, 3)).toBe(3);
85
- expect(COUNT(1, null, undefined, 4)).toBe(2);
86
- expect(COUNT([1, null, 3])).toBe(2);
87
- });
88
-
89
- it('MIN should return minimum value', () => {
90
- const formulas = new FormulaFunctions();
91
- const MIN = formulas.get('MIN')!;
92
- expect(MIN(5, 3, 8, 1)).toBe(1);
93
- expect(MIN([10, 2, 7])).toBe(2);
94
- });
95
-
96
- it('MIN should return 0 for empty args', () => {
97
- const formulas = new FormulaFunctions();
98
- const MIN = formulas.get('MIN')!;
99
- expect(MIN()).toBe(0);
100
- });
101
-
102
- it('MAX should return maximum value', () => {
103
- const formulas = new FormulaFunctions();
104
- const MAX = formulas.get('MAX')!;
105
- expect(MAX(5, 3, 8, 1)).toBe(8);
106
- expect(MAX([10, 2, 7])).toBe(10);
107
- });
108
-
109
- it('MAX should return 0 for empty args', () => {
110
- const formulas = new FormulaFunctions();
111
- const MAX = formulas.get('MAX')!;
112
- expect(MAX()).toBe(0);
113
- });
114
- });
115
-
116
- describe('Date Functions', () => {
117
- it('TODAY should return current date string', () => {
118
- const formulas = new FormulaFunctions();
119
- const TODAY = formulas.get('TODAY')!;
120
- const result = TODAY();
121
- // Should be YYYY-MM-DD format
122
- expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
123
- });
124
-
125
- it('NOW should return ISO timestamp', () => {
126
- const formulas = new FormulaFunctions();
127
- const NOW = formulas.get('NOW')!;
128
- const result = NOW();
129
- // Should be a valid ISO 8601 date
130
- expect(new Date(result).toISOString()).toBe(result);
131
- });
132
-
133
- it('DATEADD should add days', () => {
134
- const formulas = new FormulaFunctions();
135
- const DATEADD = formulas.get('DATEADD')!;
136
- const result = DATEADD('2025-01-01T00:00:00.000Z', 5, 'days');
137
- expect(new Date(result).getUTCDate()).toBe(6);
138
- });
139
-
140
- it('DATEADD should add months', () => {
141
- const formulas = new FormulaFunctions();
142
- const DATEADD = formulas.get('DATEADD')!;
143
- const result = DATEADD('2025-01-15T00:00:00.000Z', 2, 'months');
144
- expect(new Date(result).getUTCMonth()).toBe(2); // March (0-indexed)
145
- });
146
-
147
- it('DATEADD should add years', () => {
148
- const formulas = new FormulaFunctions();
149
- const DATEADD = formulas.get('DATEADD')!;
150
- const result = DATEADD('2025-06-15T00:00:00.000Z', 3, 'years');
151
- expect(new Date(result).getUTCFullYear()).toBe(2028);
152
- });
153
-
154
- it('DATEADD should throw for invalid date', () => {
155
- const formulas = new FormulaFunctions();
156
- const DATEADD = formulas.get('DATEADD')!;
157
- expect(() => DATEADD('invalid', 1, 'days')).toThrow('Invalid date');
158
- });
159
-
160
- it('DATEADD should throw for unsupported unit', () => {
161
- const formulas = new FormulaFunctions();
162
- const DATEADD = formulas.get('DATEADD')!;
163
- expect(() => DATEADD('2025-01-01', 1, 'weeks')).toThrow('Unsupported unit');
164
- });
165
-
166
- it('DATEDIFF should calculate day difference', () => {
167
- const formulas = new FormulaFunctions();
168
- const DATEDIFF = formulas.get('DATEDIFF')!;
169
- expect(DATEDIFF('2025-01-01', '2025-01-11', 'days')).toBe(10);
170
- });
171
-
172
- it('DATEDIFF should calculate month difference', () => {
173
- const formulas = new FormulaFunctions();
174
- const DATEDIFF = formulas.get('DATEDIFF')!;
175
- expect(DATEDIFF('2025-01-15', '2025-04-15', 'months')).toBe(3);
176
- });
177
-
178
- it('DATEDIFF should calculate year difference', () => {
179
- const formulas = new FormulaFunctions();
180
- const DATEDIFF = formulas.get('DATEDIFF')!;
181
- expect(DATEDIFF('2020-06-01', '2025-06-01', 'years')).toBe(5);
182
- });
183
-
184
- it('DATEDIFF should throw for invalid date', () => {
185
- const formulas = new FormulaFunctions();
186
- const DATEDIFF = formulas.get('DATEDIFF')!;
187
- expect(() => DATEDIFF('invalid', '2025-01-01', 'days')).toThrow('Invalid date');
188
- });
189
- });
190
-
191
- describe('Logic Functions', () => {
192
- it('IF should return trueValue when condition is truthy', () => {
193
- const formulas = new FormulaFunctions();
194
- const IF = formulas.get('IF')!;
195
- expect(IF(true, 'yes', 'no')).toBe('yes');
196
- expect(IF(1, 'yes', 'no')).toBe('yes');
197
- });
198
-
199
- it('IF should return falseValue when condition is falsy', () => {
200
- const formulas = new FormulaFunctions();
201
- const IF = formulas.get('IF')!;
202
- expect(IF(false, 'yes', 'no')).toBe('no');
203
- expect(IF(0, 'yes', 'no')).toBe('no');
204
- expect(IF(null, 'yes', 'no')).toBe('no');
205
- });
206
-
207
- it('AND should return true only when all args are truthy', () => {
208
- const formulas = new FormulaFunctions();
209
- const AND = formulas.get('AND')!;
210
- expect(AND(true, true, true)).toBe(true);
211
- expect(AND(true, false, true)).toBe(false);
212
- expect(AND(1, 'hello', true)).toBe(true);
213
- });
214
-
215
- it('OR should return true when any arg is truthy', () => {
216
- const formulas = new FormulaFunctions();
217
- const OR = formulas.get('OR')!;
218
- expect(OR(false, false, true)).toBe(true);
219
- expect(OR(false, false, false)).toBe(false);
220
- expect(OR(0, '', null)).toBe(false);
221
- });
222
-
223
- it('NOT should negate a value', () => {
224
- const formulas = new FormulaFunctions();
225
- const NOT = formulas.get('NOT')!;
226
- expect(NOT(true)).toBe(false);
227
- expect(NOT(false)).toBe(true);
228
- expect(NOT(0)).toBe(true);
229
- expect(NOT(1)).toBe(false);
230
- });
231
-
232
- it('SWITCH should match cases and return result', () => {
233
- const formulas = new FormulaFunctions();
234
- const SWITCH = formulas.get('SWITCH')!;
235
- expect(SWITCH('a', 'a', 1, 'b', 2, 'c', 3)).toBe(1);
236
- expect(SWITCH('b', 'a', 1, 'b', 2, 'c', 3)).toBe(2);
237
- });
238
-
239
- it('SWITCH should return default when no match', () => {
240
- const formulas = new FormulaFunctions();
241
- const SWITCH = formulas.get('SWITCH')!;
242
- expect(SWITCH('x', 'a', 1, 'b', 2, 'default')).toBe('default');
243
- });
244
-
245
- it('SWITCH should return undefined when no match and no default', () => {
246
- const formulas = new FormulaFunctions();
247
- const SWITCH = formulas.get('SWITCH')!;
248
- expect(SWITCH('x', 'a', 1, 'b', 2)).toBeUndefined();
249
- });
250
- });
251
-
252
- describe('String Functions', () => {
253
- it('CONCAT should join strings', () => {
254
- const formulas = new FormulaFunctions();
255
- const CONCAT = formulas.get('CONCAT')!;
256
- expect(CONCAT('Hello', ' ', 'World')).toBe('Hello World');
257
- expect(CONCAT('a', 'b', 'c')).toBe('abc');
258
- });
259
-
260
- it('CONCAT should handle null/undefined', () => {
261
- const formulas = new FormulaFunctions();
262
- const CONCAT = formulas.get('CONCAT')!;
263
- expect(CONCAT('Hello', null, 'World')).toBe('HelloWorld');
264
- });
265
-
266
- it('LEFT should return leftmost characters', () => {
267
- const formulas = new FormulaFunctions();
268
- const LEFT = formulas.get('LEFT')!;
269
- expect(LEFT('Hello World', 5)).toBe('Hello');
270
- expect(LEFT('Hi', 10)).toBe('Hi');
271
- });
272
-
273
- it('RIGHT should return rightmost characters', () => {
274
- const formulas = new FormulaFunctions();
275
- const RIGHT = formulas.get('RIGHT')!;
276
- expect(RIGHT('Hello World', 5)).toBe('World');
277
- expect(RIGHT('Hi', 10)).toBe('Hi');
278
- });
279
-
280
- it('TRIM should remove whitespace', () => {
281
- const formulas = new FormulaFunctions();
282
- const TRIM = formulas.get('TRIM')!;
283
- expect(TRIM(' hello ')).toBe('hello');
284
- expect(TRIM(' test ')).toBe('test');
285
- });
286
-
287
- it('UPPER should convert to uppercase', () => {
288
- const formulas = new FormulaFunctions();
289
- const UPPER = formulas.get('UPPER')!;
290
- expect(UPPER('hello')).toBe('HELLO');
291
- });
292
-
293
- it('LOWER should convert to lowercase', () => {
294
- const formulas = new FormulaFunctions();
295
- const LOWER = formulas.get('LOWER')!;
296
- expect(LOWER('HELLO')).toBe('hello');
297
- });
298
- });
299
-
300
- describe('String Search Functions', () => {
301
- const formulas = new FormulaFunctions();
302
-
303
- it('should find substring position with FIND', () => {
304
- const FIND = formulas.get('FIND')!;
305
- expect(FIND('world', 'hello world')).toBe(6);
306
- expect(FIND('xyz', 'hello world')).toBe(-1);
307
- });
308
-
309
- it('should find substring from start position', () => {
310
- const FIND = formulas.get('FIND')!;
311
- expect(FIND('l', 'hello world', 4)).toBe(9);
312
- });
313
-
314
- it('should replace all occurrences with REPLACE', () => {
315
- const REPLACE = formulas.get('REPLACE')!;
316
- expect(REPLACE('hello world', 'o', '0')).toBe('hell0 w0rld');
317
- });
318
-
319
- it('should extract substring with SUBSTRING', () => {
320
- const SUBSTRING = formulas.get('SUBSTRING')!;
321
- expect(SUBSTRING('hello world', 6)).toBe('world');
322
- expect(SUBSTRING('hello world', 0, 5)).toBe('hello');
323
- });
324
-
325
- it('should test regex pattern with REGEX', () => {
326
- const REGEX = formulas.get('REGEX')!;
327
- expect(REGEX('hello123', '\\d+')).toBe(true);
328
- expect(REGEX('hello', '\\d+')).toBe(false);
329
- expect(REGEX('Hello', '^hello$', 'i')).toBe(true);
330
- });
331
-
332
- it('should get string length with LEN', () => {
333
- const LEN = formulas.get('LEN')!;
334
- expect(LEN('hello')).toBe(5);
335
- expect(LEN('')).toBe(0);
336
- });
337
- });
338
-
339
- describe('Statistical Functions', () => {
340
- const formulas = new FormulaFunctions();
341
-
342
- it('should calculate MEDIAN for odd count', () => {
343
- const MEDIAN = formulas.get('MEDIAN')!;
344
- expect(MEDIAN(3, 1, 2)).toBe(2);
345
- });
346
-
347
- it('should calculate MEDIAN for even count', () => {
348
- const MEDIAN = formulas.get('MEDIAN')!;
349
- expect(MEDIAN(1, 2, 3, 4)).toBe(2.5);
350
- });
351
-
352
- it('should return 0 for empty MEDIAN', () => {
353
- const MEDIAN = formulas.get('MEDIAN')!;
354
- expect(MEDIAN()).toBe(0);
355
- });
356
-
357
- it('should calculate STDEV', () => {
358
- const STDEV = formulas.get('STDEV')!;
359
- const result = STDEV(2, 4, 4, 4, 5, 5, 7, 9);
360
- // Sample standard deviation (Bessel's correction, divides by n-1)
361
- expect(result).toBeCloseTo(2.138, 2);
362
- });
363
-
364
- it('should return 0 for STDEV with less than 2 values', () => {
365
- const STDEV = formulas.get('STDEV')!;
366
- expect(STDEV(5)).toBe(0);
367
- expect(STDEV()).toBe(0);
368
- });
369
-
370
- it('should calculate VARIANCE', () => {
371
- const VARIANCE = formulas.get('VARIANCE')!;
372
- const result = VARIANCE(2, 4, 4, 4, 5, 5, 7, 9);
373
- // Sample variance (Bessel's correction, divides by n-1)
374
- expect(result).toBeCloseTo(4.571, 2);
375
- });
376
-
377
- it('should calculate PERCENTILE', () => {
378
- const PERCENTILE = formulas.get('PERCENTILE')!;
379
- expect(PERCENTILE(0, 1, 2, 3, 4, 5)).toBe(1);
380
- expect(PERCENTILE(100, 1, 2, 3, 4, 5)).toBe(5);
381
- expect(PERCENTILE(50, 1, 2, 3, 4, 5)).toBe(3);
382
- });
383
-
384
- it('should handle PERCENTILE with empty values', () => {
385
- const PERCENTILE = formulas.get('PERCENTILE')!;
386
- expect(PERCENTILE(50)).toBe(0);
387
- });
388
- });
389
-
390
- describe('DATEFORMAT Function', () => {
391
- const formulas = new FormulaFunctions();
392
-
393
- it('should format date with DATEFORMAT', () => {
394
- const DATEFORMAT = formulas.get('DATEFORMAT')!;
395
- const result = DATEFORMAT('2026-02-10T00:00:00.000Z', 'YYYY-MM-DD');
396
- expect(result).toBe('2026-02-10');
397
- });
398
-
399
- it('should throw for invalid date in DATEFORMAT', () => {
400
- const DATEFORMAT = formulas.get('DATEFORMAT')!;
401
- expect(() => DATEFORMAT('invalid', 'YYYY')).toThrow('DATEFORMAT: Invalid date');
402
- });
403
- });
404
- });
405
-
406
- describe('ExpressionEvaluator with Formula Functions', () => {
407
- it('should use SUM in expressions', () => {
408
- const evaluator = new ExpressionEvaluator({ values: [10, 20, 30] });
409
- expect(evaluator.evaluateExpression('SUM(values)')).toBe(60);
410
- });
411
-
412
- it('should use AVG in expressions', () => {
413
- const evaluator = new ExpressionEvaluator({ values: [10, 20, 30] });
414
- expect(evaluator.evaluateExpression('AVG(values)')).toBe(20);
415
- });
416
-
417
- it('should use IF in expressions', () => {
418
- const evaluator = new ExpressionEvaluator({ data: { age: 25 } });
419
- expect(evaluator.evaluateExpression('IF(data.age >= 18, "adult", "minor")')).toBe('adult');
420
- });
421
-
422
- it('should use CONCAT in template expressions', () => {
423
- const evaluator = new ExpressionEvaluator({ first: 'John', last: 'Doe' });
424
- expect(evaluator.evaluate('${CONCAT(first, " ", last)}')).toBe('John Doe');
425
- });
426
-
427
- it('should use UPPER in expressions', () => {
428
- const evaluator = new ExpressionEvaluator({ name: 'hello' });
429
- expect(evaluator.evaluateExpression('UPPER(name)')).toBe('HELLO');
430
- });
431
-
432
- it('should support nested formula calls', () => {
433
- const evaluator = new ExpressionEvaluator({ items: [10, 20, 30] });
434
- expect(evaluator.evaluateExpression('IF(SUM(items) > 50, "high", "low")')).toBe('high');
435
- });
436
-
437
- it('should support registering custom functions', () => {
438
- const evaluator = new ExpressionEvaluator({ x: 5 });
439
- evaluator.registerFunction('DOUBLE', (n: number) => n * 2);
440
- expect(evaluator.evaluateExpression('DOUBLE(x)')).toBe(10);
441
- });
442
-
443
- it('should use convenience function with formulas', () => {
444
- expect(evalExprFn('${SUM(1, 2, 3)}')).toBe(6);
445
- expect(evalExprFn('${IF(true, "yes", "no")}')).toBe('yes');
446
- });
447
- });
@@ -1,13 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- export * from './ExpressionContext.js';
10
- export * from './ExpressionEvaluator.js';
11
- export * from './ExpressionCache.js';
12
- export * from './FormulaFunctions.js';
13
- export * from './SafeExpressionParser.js';
package/src/index.ts DELETED
@@ -1,38 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- export type { SchemaNode, ComponentRendererProps } from './types/index.js';
10
- export * from './registry/Registry.js';
11
- export * from './registry/PluginSystem.js';
12
- export * from './registry/PluginScopeImpl.js';
13
- export * from './registry/WidgetRegistry.js';
14
- export * from './validation/index.js';
15
- export * from './builder/schema-builder.js';
16
- export * from './utils/filter-converter.js';
17
- export * from './utils/normalize-quick-filter.js';
18
- export * from './utils/extract-records.js';
19
- export * from './utils/expand-fields.js';
20
- export * from './evaluator/index.js';
21
- export * from './actions/index.js';
22
- export * from './query/index.js';
23
- export * from './adapters/index.js';
24
- export * from './theme/index.js';
25
- export * from './data-scope/index.js';
26
- export * from './errors/index.js';
27
- export * from './utils/debug.js';
28
- export * from './utils/debug-collector.js';
29
- export * from './utils/merge-views-into-objects.js';
30
- export * from './protocols/index.js';
31
-
32
- /**
33
- * @deprecated Import `composeStacks` from `@objectstack/spec` instead.
34
- *
35
- * This re-export is kept only for backward compatibility and will be removed
36
- * in the next major version of `@object-ui/core`.
37
- */
38
- export { composeStacks } from '@objectstack/spec';
@@ -1,168 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- /**
10
- * @object-ui/core - DnD Protocol Bridge
11
- *
12
- * Converts spec-aligned DnD configuration schemas into runtime-usable
13
- * component props and CSS constraint styles for drag-and-drop interactions.
14
- *
15
- * @module protocols/DndProtocol
16
- * @packageDocumentation
17
- */
18
-
19
- import type { DndConfig, DragItem, DropZone, DragConstraint } from '@object-ui/types';
20
-
21
- // ============================================================================
22
- // Resolved Types
23
- // ============================================================================
24
-
25
- /** Fully resolved DnD configuration with all defaults applied. */
26
- export interface ResolvedDndConfig {
27
- enabled: boolean;
28
- sortable: boolean;
29
- autoScroll: boolean;
30
- touchDelay: number;
31
- dragItem?: DragItem;
32
- dropZone?: DropZone;
33
- }
34
-
35
- /** Component props for a draggable element. */
36
- export interface DragItemProps {
37
- draggable: boolean;
38
- 'aria-roledescription': string;
39
- 'aria-label'?: string;
40
- 'aria-describedby'?: string;
41
- role: string;
42
- 'data-drag-type': string;
43
- 'data-drag-handle': string;
44
- 'data-drag-disabled': string;
45
- }
46
-
47
- /** Component props for a droppable area. */
48
- export interface DropZoneProps {
49
- 'aria-dropeffect': string;
50
- 'aria-label'?: string;
51
- 'aria-describedby'?: string;
52
- role: string;
53
- 'data-drop-accept': string;
54
- 'data-drop-max-items'?: number;
55
- 'data-drop-highlight': string;
56
- }
57
-
58
- /** CSS constraint styles for drag movement. */
59
- export interface DragConstraintStyles {
60
- position: string;
61
- touchAction: string;
62
- [key: string]: string | number | undefined;
63
- }
64
-
65
- // ============================================================================
66
- // DnD Config Resolution
67
- // ============================================================================
68
-
69
- /**
70
- * Resolve a DnD configuration by applying spec defaults to missing fields.
71
- *
72
- * @param config - Partial DnD configuration from the spec
73
- * @returns Fully resolved DnD configuration
74
- */
75
- export function resolveDndConfig(config: DndConfig): ResolvedDndConfig {
76
- return {
77
- enabled: config.enabled ?? false,
78
- sortable: config.sortable ?? false,
79
- autoScroll: config.autoScroll ?? true,
80
- touchDelay: config.touchDelay ?? 200,
81
- dragItem: config.dragItem,
82
- dropZone: config.dropZone,
83
- };
84
- }
85
-
86
- // ============================================================================
87
- // Drag Item Props
88
- // ============================================================================
89
-
90
- /**
91
- * Convert a spec DragItem to component props suitable for a draggable element.
92
- * Produces `draggable`, ARIA attributes, and data attributes for DnD libraries.
93
- *
94
- * @param item - DragItem configuration from the spec
95
- * @returns Component props object for a draggable element
96
- */
97
- export function createDragItemProps(item: DragItem): DragItemProps {
98
- return {
99
- draggable: !(item.disabled ?? false),
100
- 'aria-roledescription': 'draggable',
101
- 'aria-label': item.ariaLabel ?? item.label,
102
- 'aria-describedby': item.ariaDescribedBy,
103
- role: item.role ?? 'listitem',
104
- 'data-drag-type': item.type,
105
- 'data-drag-handle': item.handle ?? 'element',
106
- 'data-drag-disabled': String(item.disabled ?? false),
107
- };
108
- }
109
-
110
- // ============================================================================
111
- // Drop Zone Props
112
- // ============================================================================
113
-
114
- /**
115
- * Convert a spec DropZone to component props suitable for a droppable area.
116
- * Produces ARIA attributes and data attributes for DnD libraries.
117
- *
118
- * @param zone - DropZone configuration from the spec
119
- * @returns Component props object for a droppable area
120
- */
121
- export function createDropZoneProps(zone: DropZone): DropZoneProps {
122
- return {
123
- 'aria-dropeffect': zone.dropEffect ?? 'move',
124
- 'aria-label': zone.ariaLabel ?? zone.label,
125
- 'aria-describedby': zone.ariaDescribedBy,
126
- role: zone.role ?? 'list',
127
- 'data-drop-accept': zone.accept.join(','),
128
- 'data-drop-max-items': zone.maxItems,
129
- 'data-drop-highlight': String(zone.highlightOnDragOver ?? true),
130
- };
131
- }
132
-
133
- // ============================================================================
134
- // Drag Constraint Styles
135
- // ============================================================================
136
-
137
- /**
138
- * Resolve a DragConstraint into CSS style properties that restrict
139
- * drag movement along an axis or within bounds.
140
- *
141
- * @param constraint - DragConstraint configuration from the spec
142
- * @returns CSS styles object for constraining drag movement
143
- */
144
- export function resolveDragConstraints(constraint: DragConstraint): DragConstraintStyles {
145
- const styles: DragConstraintStyles = {
146
- position: 'relative',
147
- touchAction: 'none',
148
- };
149
-
150
- const axis = constraint.axis ?? 'both';
151
- if (axis === 'x') {
152
- styles.touchAction = 'pan-y';
153
- } else if (axis === 'y') {
154
- styles.touchAction = 'pan-x';
155
- }
156
-
157
- const bounds = constraint.bounds ?? 'none';
158
- if (bounds === 'parent') {
159
- styles.overflow = 'hidden';
160
- }
161
-
162
- if (constraint.grid) {
163
- styles['--drag-grid-x'] = `${constraint.grid[0]}px`;
164
- styles['--drag-grid-y'] = `${constraint.grid[1]}px`;
165
- }
166
-
167
- return styles;
168
- }