@object-ui/core 3.3.0 → 3.3.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 (99) hide show
  1. package/CHANGELOG.md +6 -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/utils/filter-converter.js +25 -5
  7. package/package.json +32 -8
  8. package/.turbo/turbo-build.log +0 -4
  9. package/src/__benchmarks__/core.bench.ts +0 -64
  10. package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
  11. package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
  12. package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
  13. package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
  14. package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
  15. package/src/actions/ActionEngine.ts +0 -268
  16. package/src/actions/ActionRunner.ts +0 -717
  17. package/src/actions/TransactionManager.ts +0 -521
  18. package/src/actions/UndoManager.ts +0 -215
  19. package/src/actions/__tests__/ActionEngine.test.ts +0 -206
  20. package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
  21. package/src/actions/__tests__/ActionRunner.test.ts +0 -711
  22. package/src/actions/__tests__/TransactionManager.test.ts +0 -447
  23. package/src/actions/__tests__/UndoManager.test.ts +0 -320
  24. package/src/actions/index.ts +0 -12
  25. package/src/adapters/ApiDataSource.ts +0 -376
  26. package/src/adapters/README.md +0 -180
  27. package/src/adapters/ValueDataSource.ts +0 -459
  28. package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
  29. package/src/adapters/__tests__/ValueDataSource.test.ts +0 -571
  30. package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
  31. package/src/adapters/index.ts +0 -15
  32. package/src/adapters/resolveDataSource.ts +0 -79
  33. package/src/builder/__tests__/schema-builder.test.ts +0 -235
  34. package/src/builder/schema-builder.ts +0 -584
  35. package/src/data-scope/DataScopeManager.ts +0 -269
  36. package/src/data-scope/ViewDataProvider.ts +0 -282
  37. package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
  38. package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
  39. package/src/data-scope/index.ts +0 -24
  40. package/src/errors/__tests__/errors.test.ts +0 -292
  41. package/src/errors/index.ts +0 -269
  42. package/src/evaluator/ExpressionCache.ts +0 -206
  43. package/src/evaluator/ExpressionContext.ts +0 -118
  44. package/src/evaluator/ExpressionEvaluator.ts +0 -315
  45. package/src/evaluator/FormulaFunctions.ts +0 -398
  46. package/src/evaluator/SafeExpressionParser.ts +0 -893
  47. package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
  48. package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
  49. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -558
  50. package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
  51. package/src/evaluator/index.ts +0 -13
  52. package/src/index.ts +0 -38
  53. package/src/protocols/DndProtocol.ts +0 -168
  54. package/src/protocols/KeyboardProtocol.ts +0 -181
  55. package/src/protocols/NotificationProtocol.ts +0 -150
  56. package/src/protocols/ResponsiveProtocol.ts +0 -210
  57. package/src/protocols/SharingProtocol.ts +0 -185
  58. package/src/protocols/index.ts +0 -13
  59. package/src/query/__tests__/query-ast.test.ts +0 -211
  60. package/src/query/__tests__/window-functions.test.ts +0 -275
  61. package/src/query/index.ts +0 -7
  62. package/src/query/query-ast.ts +0 -341
  63. package/src/registry/PluginScopeImpl.ts +0 -259
  64. package/src/registry/PluginSystem.ts +0 -206
  65. package/src/registry/Registry.ts +0 -219
  66. package/src/registry/WidgetRegistry.ts +0 -316
  67. package/src/registry/__tests__/PluginSystem.test.ts +0 -309
  68. package/src/registry/__tests__/Registry.test.ts +0 -293
  69. package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
  70. package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
  71. package/src/theme/ThemeEngine.ts +0 -530
  72. package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
  73. package/src/theme/index.ts +0 -24
  74. package/src/types/index.ts +0 -21
  75. package/src/utils/__tests__/debug-collector.test.ts +0 -102
  76. package/src/utils/__tests__/debug.test.ts +0 -134
  77. package/src/utils/__tests__/expand-fields.test.ts +0 -120
  78. package/src/utils/__tests__/extract-records.test.ts +0 -50
  79. package/src/utils/__tests__/filter-converter.test.ts +0 -118
  80. package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
  81. package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
  82. package/src/utils/debug-collector.ts +0 -100
  83. package/src/utils/debug.ts +0 -148
  84. package/src/utils/expand-fields.ts +0 -76
  85. package/src/utils/extract-records.ts +0 -33
  86. package/src/utils/filter-converter.ts +0 -133
  87. package/src/utils/merge-views-into-objects.ts +0 -36
  88. package/src/utils/normalize-quick-filter.ts +0 -78
  89. package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
  90. package/src/validation/__tests__/schema-validator.test.ts +0 -118
  91. package/src/validation/__tests__/validation-engine.test.ts +0 -102
  92. package/src/validation/index.ts +0 -10
  93. package/src/validation/schema-validator.ts +0 -344
  94. package/src/validation/validation-engine.ts +0 -528
  95. package/src/validation/validators/index.ts +0 -25
  96. package/src/validation/validators/object-validation-engine.ts +0 -722
  97. package/tsconfig.json +0 -15
  98. package/tsconfig.tsbuildinfo +0 -1
  99. package/vitest.config.ts +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @object-ui/core
2
2
 
3
+ ## 3.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @object-ui/types@3.3.1
8
+
3
9
  ## 3.3.0
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -79,6 +79,25 @@ This allows the core types and logic to be used in:
79
79
 
80
80
  See [full documentation](https://objectui.org/api/core) for detailed API reference.
81
81
 
82
+ <!-- release-metadata:v3.3.0 -->
83
+
84
+ ## Compatibility
85
+
86
+ - **Node.js:** ≥ 18
87
+ - **TypeScript:** ≥ 5.0 (strict mode)
88
+ - **`@objectstack/spec`:** ^3.3.0
89
+ - **`@objectstack/client`:** ^3.3.0
90
+ - **Tailwind CSS:** ≥ 3.4 (for packages with UI)
91
+
92
+ ## Links
93
+
94
+ - 📚 [Documentation](https://www.objectui.org/docs/core)
95
+ - 📦 [npm package](https://www.npmjs.com/package/@object-ui/core)
96
+ - 📝 [Changelog](./CHANGELOG.md)
97
+ - 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
98
+ - 🤝 [Contributing Guide](https://github.com/objectstack-ai/objectui/blob/main/CONTRIBUTING.md)
99
+ - 🗺️ [Roadmap](https://github.com/objectstack-ai/objectui/blob/main/ROADMAP.md)
100
+
82
101
  ## License
83
102
 
84
- MIT
103
+ MIT — see [LICENSE](./LICENSE).
@@ -178,6 +178,7 @@ export interface ActionParamDef {
178
178
  }
179
179
  export declare class ActionRunner {
180
180
  private handlers;
181
+ private scripts;
181
182
  private evaluator;
182
183
  private context;
183
184
  private confirmHandler;
@@ -209,6 +210,14 @@ export declare class ActionRunner {
209
210
  setParamCollectionHandler(handler: ParamCollectionHandler): void;
210
211
  registerHandler(actionName: string, handler: ActionHandler): void;
211
212
  unregisterHandler(actionName: string): void;
213
+ /**
214
+ * Register a named script handler. When a `script` action's
215
+ * `target`/`execute` matches the registered name, the handler runs
216
+ * instead of the expression evaluator. Lets dashboards/views wire
217
+ * symbolic action names (e.g. 'export_dashboard_pdf') to JS callbacks.
218
+ */
219
+ registerScript(scriptName: string, handler: ActionHandler): void;
220
+ unregisterScript(scriptName: string): void;
212
221
  execute(action: ActionDef): Promise<ActionResult>;
213
222
  /**
214
223
  * Execute multiple actions in sequence or parallel.
@@ -22,6 +22,12 @@ export class ActionRunner {
22
22
  writable: true,
23
23
  value: new Map()
24
24
  });
25
+ Object.defineProperty(this, "scripts", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: new Map()
30
+ });
25
31
  Object.defineProperty(this, "evaluator", {
26
32
  enumerable: true,
27
33
  configurable: true,
@@ -110,6 +116,18 @@ export class ActionRunner {
110
116
  unregisterHandler(actionName) {
111
117
  this.handlers.delete(actionName);
112
118
  }
119
+ /**
120
+ * Register a named script handler. When a `script` action's
121
+ * `target`/`execute` matches the registered name, the handler runs
122
+ * instead of the expression evaluator. Lets dashboards/views wire
123
+ * symbolic action names (e.g. 'export_dashboard_pdf') to JS callbacks.
124
+ */
125
+ registerScript(scriptName, handler) {
126
+ this.scripts.set(scriptName, handler);
127
+ }
128
+ unregisterScript(scriptName) {
129
+ this.scripts.delete(scriptName);
130
+ }
113
131
  async execute(action) {
114
132
  try {
115
133
  // Resolve the action type
@@ -143,14 +161,21 @@ export class ActionRunner {
143
161
  }
144
162
  // Param collection: if the action defines ActionParam[] to collect,
145
163
  // show a dialog to gather user input before executing.
146
- if (action.actionParams && Array.isArray(action.actionParams) && action.actionParams.length > 0) {
164
+ // Spec defines this as `params: ActionParam[]`; ActionRunner historically
165
+ // used `actionParams` to disambiguate from the static-params object that
166
+ // some custom handlers consume. Accept both — when `params` is an array,
167
+ // treat it as the input-collection definition.
168
+ const paramDefs = action.actionParams && Array.isArray(action.actionParams) ? action.actionParams
169
+ : (Array.isArray(action.params) ? action.params : undefined);
170
+ if (paramDefs && paramDefs.length > 0) {
147
171
  if (this.paramCollectionHandler) {
148
- const collected = await this.paramCollectionHandler(action.actionParams);
172
+ const collected = await this.paramCollectionHandler(paramDefs);
149
173
  if (collected === null) {
150
174
  return { success: false, error: 'Action cancelled by user (params)' };
151
175
  }
152
- // Merge collected params into action.params
153
- action.params = { ...(action.params || {}), ...collected };
176
+ // Merge collected params into action.params as a values map for downstream consumers.
177
+ // (Replace the array form with a values object once collected.)
178
+ action.params = { ...(Array.isArray(action.params) ? {} : (action.params || {})), ...collected };
154
179
  }
155
180
  }
156
181
  // Check for a registered custom handler first
@@ -293,6 +318,18 @@ export class ActionRunner {
293
318
  if (!script) {
294
319
  return { success: false, error: 'No script provided for script action' };
295
320
  }
321
+ // Named script registry wins over the expression evaluator. This lets
322
+ // dashboards/views bind a symbolic action name (e.g. 'export_dashboard_pdf')
323
+ // to a JS callback without piping the literal through ExpressionEvaluator.
324
+ const named = this.scripts.get(script);
325
+ if (named) {
326
+ try {
327
+ return await named(action, this.context);
328
+ }
329
+ catch (error) {
330
+ return { success: false, error: `Script execution failed: ${error.message}` };
331
+ }
332
+ }
296
333
  try {
297
334
  const result = this.evaluator.evaluate(`\${${script}}`);
298
335
  return { success: true, data: result };
@@ -53,7 +53,9 @@ function matchesASTFilter(record, filterNode) {
53
53
  case 'in':
54
54
  return Array.isArray(target) && target.includes(value);
55
55
  case 'not in':
56
- case 'notin': // alias used by convertFiltersToAST
56
+ case 'not_in':
57
+ case 'nin': // canonical (per spec)
58
+ case 'notin': // legacy alias
57
59
  return Array.isArray(target) && !target.includes(value);
58
60
  case 'contains': {
59
61
  const lv = typeof value === 'string' ? value.toLowerCase() : '';
@@ -12,6 +12,9 @@
12
12
  * @returns ObjectStack operator or null if not recognized
13
13
  */
14
14
  export function convertOperatorToAST(operator) {
15
+ // Spec reference: framework/packages/spec/src/data/filter.zod.ts
16
+ // Canonical MongoDB-style keys are camelCase ($startsWith, $endsWith, $notContains).
17
+ // Lowercase aliases are accepted for tolerance.
15
18
  const operatorMap = {
16
19
  '$eq': '=',
17
20
  '$ne': '!=',
@@ -20,11 +23,16 @@ export function convertOperatorToAST(operator) {
20
23
  '$lt': '<',
21
24
  '$lte': '<=',
22
25
  '$in': 'in',
23
- '$nin': 'notin',
24
- '$notin': 'notin',
26
+ '$nin': 'nin',
27
+ '$notin': 'nin',
28
+ '$between': 'between',
25
29
  '$contains': 'contains',
30
+ '$notContains': 'notcontains',
31
+ '$notcontains': 'notcontains',
32
+ '$startsWith': 'startswith',
26
33
  '$startswith': 'startswith',
27
- '$between': 'between',
34
+ '$endsWith': 'endswith',
35
+ '$endswith': 'endswith',
28
36
  };
29
37
  return operatorMap[operator] || null;
30
38
  }
@@ -66,10 +74,21 @@ export function convertFiltersToAST(filter) {
66
74
  console.warn(`[ObjectUI] Warning: $regex operator is not fully supported. ` +
67
75
  `Converting to 'contains' which only supports substring matching, not regex patterns. ` +
68
76
  `Field: '${field}', Value: ${JSON.stringify(operatorValue)}. ` +
69
- `Consider using $contains or $startswith instead.`);
77
+ `Consider using $contains or $startsWith instead.`);
70
78
  conditions.push([field, 'contains', operatorValue]);
71
79
  continue;
72
80
  }
81
+ // $null / $exists translate based on their boolean value (per spec semantics).
82
+ // $null: true → IS NULL | $null: false → IS NOT NULL
83
+ // $exists: true → IS NOT NULL | $exists: false → IS NULL
84
+ if (operator === '$null') {
85
+ conditions.push([field, operatorValue ? 'is_null' : 'is_not_null', true]);
86
+ continue;
87
+ }
88
+ if (operator === '$exists') {
89
+ conditions.push([field, operatorValue ? 'is_not_null' : 'is_null', true]);
90
+ continue;
91
+ }
73
92
  const astOperator = convertOperatorToAST(operator);
74
93
  if (astOperator) {
75
94
  conditions.push([field, astOperator, operatorValue]);
@@ -77,7 +96,8 @@ export function convertFiltersToAST(filter) {
77
96
  else {
78
97
  // Unknown operator - throw error to avoid silent failure
79
98
  throw new Error(`[ObjectUI] Unknown filter operator '${operator}' for field '${field}'. ` +
80
- `Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $startswith, $between. ` +
99
+ `Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $between, ` +
100
+ `$contains, $notContains, $startsWith, $endsWith, $null, $exists. ` +
81
101
  `If you need exact object matching, use the value directly without an operator.`);
82
102
  }
83
103
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@object-ui/core",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
7
7
  "description": "Core logic, types, and validation for Object UI. Zero React dependencies.",
8
- "homepage": "https://www.objectui.org",
8
+ "homepage": "https://www.objectui.org/docs/core",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/objectstack-ai/objectui.git",
11
+ "url": "git+https://github.com/objectstack-ai/objectui.git",
12
12
  "directory": "packages/core"
13
13
  },
14
14
  "bugs": {
@@ -25,15 +25,39 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@objectstack/spec": "^4.0.3",
28
+ "@objectstack/spec": "^4.0.4",
29
29
  "lodash": "^4.18.1",
30
- "zod": "^4.3.6",
31
- "@object-ui/types": "3.3.0"
30
+ "zod": "^4.4.3",
31
+ "@object-ui/types": "3.3.1"
32
32
  },
33
33
  "devDependencies": {
34
- "typescript": "^6.0.2",
35
- "vitest": "^4.1.4"
34
+ "typescript": "^6.0.3",
35
+ "vitest": "^4.1.5"
36
36
  },
37
+ "keywords": [
38
+ "objectui",
39
+ "sdui",
40
+ "schema-driven-ui",
41
+ "react",
42
+ "tailwind",
43
+ "shadcn",
44
+ "objectstack",
45
+ "core",
46
+ "engine",
47
+ "expression-engine",
48
+ "action-engine",
49
+ "registry"
50
+ ],
51
+ "author": "ObjectStack Team <team@objectstack.ai>",
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "files": [
56
+ "dist",
57
+ "README.md",
58
+ "CHANGELOG.md",
59
+ "LICENSE"
60
+ ],
37
61
  "scripts": {
38
62
  "build": "tsc",
39
63
  "test": "vitest run",
@@ -1,4 +0,0 @@
1
-
2
- > @object-ui/core@3.3.0 build /home/runner/work/objectui/objectui/packages/core
3
- > tsc
4
-
@@ -1,64 +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
- * Performance benchmark suite for @object-ui/core.
11
- *
12
- * Part of Q1 2026 roadmap §1.4 Test Coverage Improvement.
13
- *
14
- * Run with: npx vitest bench packages/core/src/__benchmarks__/
15
- */
16
-
17
- import { bench, describe } from 'vitest';
18
- import { ExpressionEvaluator } from '@object-ui/core';
19
- import { ComponentRegistry } from '@object-ui/core';
20
- import { contrastRatio, meetsContrastLevel, hexToHSL } from '@object-ui/core';
21
-
22
- describe('ExpressionEvaluator performance', () => {
23
- const evaluator = new ExpressionEvaluator({ data: { name: 'Alice', age: 30, active: true } });
24
-
25
- bench('evaluate simple string', () => {
26
- evaluator.evaluate('Hello ${data.name}');
27
- });
28
-
29
- bench('evaluate 100 expressions', () => {
30
- for (let i = 0; i < 100; i++) {
31
- evaluator.evaluate('Hello ${data.name}');
32
- }
33
- });
34
- });
35
-
36
- describe('ComponentRegistry performance', () => {
37
- bench('get registered component', () => {
38
- ComponentRegistry.get('button');
39
- });
40
-
41
- bench('has check', () => {
42
- ComponentRegistry.has('button');
43
- });
44
- });
45
-
46
- describe('Theme utilities performance', () => {
47
- bench('hexToHSL conversion', () => {
48
- hexToHSL('#336699');
49
- });
50
-
51
- bench('contrastRatio calculation', () => {
52
- contrastRatio('#000000', '#ffffff');
53
- });
54
-
55
- bench('meetsContrastLevel check', () => {
56
- meetsContrastLevel('#000000', '#ffffff', 'AA');
57
- });
58
-
59
- bench('100 contrast checks', () => {
60
- for (let i = 0; i < 100; i++) {
61
- meetsContrastLevel('#336699', '#ffffff', 'AA');
62
- }
63
- });
64
- });
@@ -1,186 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import type { DndConfig, DragItem, DropZone, DragConstraint } from '@object-ui/types';
3
- import {
4
- resolveDndConfig,
5
- createDragItemProps,
6
- createDropZoneProps,
7
- resolveDragConstraints,
8
- } from '../../protocols/DndProtocol';
9
-
10
- describe('DndProtocol', () => {
11
- // ==========================================================================
12
- // resolveDndConfig
13
- // ==========================================================================
14
- describe('resolveDndConfig', () => {
15
- it('should apply all defaults for minimal config', () => {
16
- const config = {} as DndConfig;
17
- const resolved = resolveDndConfig(config);
18
-
19
- expect(resolved.enabled).toBe(false);
20
- expect(resolved.sortable).toBe(false);
21
- expect(resolved.autoScroll).toBe(true);
22
- expect(resolved.touchDelay).toBe(200);
23
- expect(resolved.dragItem).toBeUndefined();
24
- expect(resolved.dropZone).toBeUndefined();
25
- });
26
-
27
- it('should preserve explicit values', () => {
28
- const config = {
29
- enabled: true,
30
- sortable: true,
31
- autoScroll: false,
32
- touchDelay: 500,
33
- } as DndConfig;
34
- const resolved = resolveDndConfig(config);
35
-
36
- expect(resolved.enabled).toBe(true);
37
- expect(resolved.sortable).toBe(true);
38
- expect(resolved.autoScroll).toBe(false);
39
- expect(resolved.touchDelay).toBe(500);
40
- });
41
-
42
- it('should pass through dragItem and dropZone', () => {
43
- const dragItem = { type: 'card' } as DragItem;
44
- const dropZone = { accept: ['card'] } as DropZone;
45
- const config = { dragItem, dropZone } as DndConfig;
46
- const resolved = resolveDndConfig(config);
47
-
48
- expect(resolved.dragItem).toBe(dragItem);
49
- expect(resolved.dropZone).toBe(dropZone);
50
- });
51
- });
52
-
53
- // ==========================================================================
54
- // createDragItemProps
55
- // ==========================================================================
56
- describe('createDragItemProps', () => {
57
- it('should return correct props for a basic drag item', () => {
58
- const item = { type: 'card', label: 'Task card' } as DragItem;
59
- const props = createDragItemProps(item);
60
-
61
- expect(props.draggable).toBe(true);
62
- expect(props['aria-roledescription']).toBe('draggable');
63
- expect(props['aria-label']).toBe('Task card');
64
- expect(props.role).toBe('listitem');
65
- expect(props['data-drag-type']).toBe('card');
66
- expect(props['data-drag-handle']).toBe('element');
67
- expect(props['data-drag-disabled']).toBe('false');
68
- });
69
-
70
- it('should set draggable false when disabled', () => {
71
- const item = { type: 'card', disabled: true } as DragItem;
72
- const props = createDragItemProps(item);
73
-
74
- expect(props.draggable).toBe(false);
75
- expect(props['data-drag-disabled']).toBe('true');
76
- });
77
-
78
- it('should use ariaLabel over label when both provided', () => {
79
- const item = { type: 'card', label: 'Label', ariaLabel: 'Aria Label' } as DragItem;
80
- const props = createDragItemProps(item);
81
-
82
- expect(props['aria-label']).toBe('Aria Label');
83
- });
84
-
85
- it('should use ariaLabel when provided as string', () => {
86
- const item = {
87
- type: 'card',
88
- ariaLabel: 'Translated label',
89
- } as unknown as DragItem;
90
- const props = createDragItemProps(item);
91
-
92
- expect(props['aria-label']).toBe('Translated label');
93
- });
94
-
95
- it('should use custom role and handle when provided', () => {
96
- const item = { type: 'row', role: 'option', handle: 'grip' } as DragItem;
97
- const props = createDragItemProps(item);
98
-
99
- expect(props.role).toBe('option');
100
- expect(props['data-drag-handle']).toBe('grip');
101
- });
102
- });
103
-
104
- // ==========================================================================
105
- // createDropZoneProps
106
- // ==========================================================================
107
- describe('createDropZoneProps', () => {
108
- it('should return correct props for a basic drop zone', () => {
109
- const zone = { accept: ['card', 'task'], label: 'Column' } as unknown as DropZone;
110
- const props = createDropZoneProps(zone);
111
-
112
- expect(props['aria-dropeffect']).toBe('move');
113
- expect(props['aria-label']).toBe('Column');
114
- expect(props.role).toBe('list');
115
- expect(props['data-drop-accept']).toBe('card,task');
116
- expect(props['data-drop-highlight']).toBe('true');
117
- });
118
-
119
- it('should use explicit dropEffect and role', () => {
120
- const zone = { accept: ['item'], dropEffect: 'copy', role: 'region' } as unknown as DropZone;
121
- const props = createDropZoneProps(zone);
122
-
123
- expect(props['aria-dropeffect']).toBe('copy');
124
- expect(props.role).toBe('region');
125
- });
126
-
127
- it('should set maxItems when provided', () => {
128
- const zone = { accept: ['card'], maxItems: 10 } as unknown as DropZone;
129
- const props = createDropZoneProps(zone);
130
-
131
- expect(props['data-drop-max-items']).toBe(10);
132
- });
133
-
134
- it('should use ariaLabel string when provided', () => {
135
- const zone = {
136
- accept: ['card'],
137
- ariaLabel: 'Drop here',
138
- } as unknown as DropZone;
139
- const props = createDropZoneProps(zone);
140
-
141
- expect(props['aria-label']).toBe('Drop here');
142
- });
143
- });
144
-
145
- // ==========================================================================
146
- // resolveDragConstraints
147
- // ==========================================================================
148
- describe('resolveDragConstraints', () => {
149
- it('should return base styles for default axis (both)', () => {
150
- const constraint = {} as DragConstraint;
151
- const styles = resolveDragConstraints(constraint);
152
-
153
- expect(styles.position).toBe('relative');
154
- expect(styles.touchAction).toBe('none');
155
- });
156
-
157
- it('should set touchAction to pan-y for x axis', () => {
158
- const constraint = { axis: 'x' } as DragConstraint;
159
- const styles = resolveDragConstraints(constraint);
160
-
161
- expect(styles.touchAction).toBe('pan-y');
162
- });
163
-
164
- it('should set touchAction to pan-x for y axis', () => {
165
- const constraint = { axis: 'y' } as DragConstraint;
166
- const styles = resolveDragConstraints(constraint);
167
-
168
- expect(styles.touchAction).toBe('pan-x');
169
- });
170
-
171
- it('should set overflow hidden for parent bounds', () => {
172
- const constraint = { bounds: 'parent' } as DragConstraint;
173
- const styles = resolveDragConstraints(constraint);
174
-
175
- expect(styles.overflow).toBe('hidden');
176
- });
177
-
178
- it('should set grid CSS custom properties', () => {
179
- const constraint = { grid: [20, 30] } as unknown as DragConstraint;
180
- const styles = resolveDragConstraints(constraint);
181
-
182
- expect(styles['--drag-grid-x']).toBe('20px');
183
- expect(styles['--drag-grid-y']).toBe('30px');
184
- });
185
- });
186
- });