@object-ui/plugin-grid 3.0.3 → 3.1.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 (45) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.js +2173 -922
  4. package/dist/index.umd.cjs +9 -3
  5. package/dist/plugin-grid/src/FormulaBar.d.ts +29 -0
  6. package/dist/plugin-grid/src/GroupRow.d.ts +23 -0
  7. package/dist/plugin-grid/src/ImportWizard.d.ts +29 -0
  8. package/dist/plugin-grid/src/ObjectGrid.d.ts +1 -0
  9. package/dist/plugin-grid/src/SplitPaneGrid.d.ts +22 -0
  10. package/dist/plugin-grid/src/components/BulkActionBar.d.ts +12 -0
  11. package/dist/plugin-grid/src/components/RowActionMenu.d.ts +23 -0
  12. package/dist/plugin-grid/src/index.d.ts +22 -2
  13. package/dist/plugin-grid/src/useCellClipboard.d.ts +47 -0
  14. package/dist/plugin-grid/src/useColumnSummary.d.ts +25 -0
  15. package/dist/plugin-grid/src/useGradientColor.d.ts +37 -0
  16. package/dist/plugin-grid/src/useGroupReorder.d.ts +34 -0
  17. package/dist/plugin-grid/src/useGroupedData.d.ts +24 -3
  18. package/package.json +10 -10
  19. package/src/FormulaBar.tsx +151 -0
  20. package/src/GroupRow.tsx +69 -0
  21. package/src/ImportWizard.tsx +412 -0
  22. package/src/ListColumnExtensions.test.tsx +4 -5
  23. package/src/ObjectGrid.tsx +1002 -139
  24. package/src/SplitPaneGrid.tsx +120 -0
  25. package/src/VirtualGrid.tsx +2 -2
  26. package/src/__tests__/GroupRow.test.tsx +206 -0
  27. package/src/__tests__/ImportPreview.test.tsx +171 -0
  28. package/src/__tests__/accessorKey-inference.test.tsx +132 -0
  29. package/src/__tests__/airtable-style.test.tsx +508 -0
  30. package/src/__tests__/column-features.test.tsx +490 -0
  31. package/src/__tests__/grid-export.test.tsx +121 -0
  32. package/src/__tests__/mobile-card-view.test.tsx +355 -0
  33. package/src/__tests__/objectdef-enrichment.test.tsx +566 -0
  34. package/src/__tests__/phase11-features.test.tsx +418 -0
  35. package/src/__tests__/row-bulk-actions.test.tsx +413 -0
  36. package/src/__tests__/row-height.test.tsx +160 -0
  37. package/src/__tests__/useGroupedData.test.ts +165 -0
  38. package/src/components/BulkActionBar.tsx +66 -0
  39. package/src/components/RowActionMenu.tsx +91 -0
  40. package/src/index.tsx +46 -2
  41. package/src/useCellClipboard.ts +136 -0
  42. package/src/useColumnSummary.ts +128 -0
  43. package/src/useGradientColor.ts +103 -0
  44. package/src/useGroupReorder.ts +123 -0
  45. package/src/useGroupedData.ts +69 -4
@@ -0,0 +1,37 @@
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
+ /** A single stop in a colour gradient. */
9
+ export interface GradientStop {
10
+ /** Position between 0 and 1. */
11
+ position: number;
12
+ /** Tailwind background class applied at this stop (e.g. "bg-green-100"). */
13
+ className: string;
14
+ }
15
+ export interface UseGradientColorOptions {
16
+ /** The numeric field to evaluate on each row. */
17
+ field: string;
18
+ /** Flat data array used to derive min/max when not provided. */
19
+ data: Record<string, any>[];
20
+ /** Optional explicit minimum value. */
21
+ min?: number;
22
+ /** Optional explicit maximum value. */
23
+ max?: number;
24
+ /**
25
+ * Ordered gradient stops (position 0 → 1).
26
+ * When omitted a default green→yellow→red palette is used.
27
+ */
28
+ stops?: GradientStop[];
29
+ }
30
+ /**
31
+ * Hook that returns a row → Tailwind class resolver based on a numeric
32
+ * field's value mapped onto a configurable colour gradient.
33
+ *
34
+ * @param options - gradient configuration
35
+ * @returns `(row) => className | undefined`
36
+ */
37
+ export declare function useGradientColor(options: UseGradientColorOptions): (row: Record<string, any>) => string | undefined;
@@ -0,0 +1,34 @@
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
+ export interface UseGroupReorderOptions {
9
+ /** Initial ordered list of group keys. */
10
+ groupKeys: string[];
11
+ }
12
+ export interface UseGroupReorderResult {
13
+ /** Current ordered list of group keys. */
14
+ groupOrder: string[];
15
+ /** Move a group from one index to another. */
16
+ moveGroup: (fromIndex: number, toIndex: number) => void;
17
+ /** Drag-start handler – stores the dragged group key. */
18
+ onDragStart: (e: React.DragEvent, key: string) => void;
19
+ /** Drag-over handler – must be attached to allow drop. */
20
+ onDragOver: (e: React.DragEvent) => void;
21
+ /** Drop handler – reorders the group under the cursor. */
22
+ onDrop: (e: React.DragEvent, targetKey: string) => void;
23
+ /** Drag-end handler – cleans up drag state. */
24
+ onDragEnd: () => void;
25
+ /** The key currently being dragged (null when idle). */
26
+ draggingKey: string | null;
27
+ }
28
+ /**
29
+ * Hook for drag-and-drop reordering of grouped sections.
30
+ *
31
+ * Attach the returned handlers to the group header elements to enable
32
+ * reordering via native HTML drag-and-drop.
33
+ */
34
+ export declare function useGroupReorder({ groupKeys }: UseGroupReorderOptions): UseGroupReorderResult;
@@ -1,4 +1,22 @@
1
1
  import { GroupingConfig } from '../../types/src';
2
+ /** Supported aggregation function types. */
3
+ export type AggregationType = 'sum' | 'count' | 'avg' | 'min' | 'max';
4
+ /** Describes a single aggregation to compute per group. */
5
+ export interface AggregationConfig {
6
+ /** The field to aggregate. */
7
+ field: string;
8
+ /** The aggregation function. */
9
+ type: AggregationType;
10
+ }
11
+ /** Result of a computed aggregation for a group. */
12
+ export interface AggregationResult {
13
+ /** The field that was aggregated. */
14
+ field: string;
15
+ /** The aggregation function used. */
16
+ type: AggregationType;
17
+ /** The computed value. */
18
+ value: number;
19
+ }
2
20
  export interface GroupEntry {
3
21
  /** Composite key identifying this group (field values joined by ' / ') */
4
22
  key: string;
@@ -8,6 +26,8 @@ export interface GroupEntry {
8
26
  rows: any[];
9
27
  /** Whether the group section is collapsed */
10
28
  collapsed: boolean;
29
+ /** Computed aggregations for this group (empty when no aggregations configured). */
30
+ aggregations: AggregationResult[];
11
31
  }
12
32
  export interface UseGroupedDataResult {
13
33
  /** Grouped entries (empty when grouping is not configured) */
@@ -24,7 +44,8 @@ export interface UseGroupedDataResult {
24
44
  * collapsed state. Collapse state is managed internally so the consumer only
25
45
  * needs to wire `toggleGroup` to the UI.
26
46
  *
27
- * @param config - GroupingConfig from the grid schema (optional)
28
- * @param data - flat data rows
47
+ * @param config - GroupingConfig from the grid schema (optional)
48
+ * @param data - flat data rows
49
+ * @param aggregations - optional aggregation definitions to compute per group
29
50
  */
30
- export declare function useGroupedData(config: GroupingConfig | undefined, data: any[]): UseGroupedDataResult;
51
+ export declare function useGroupedData(config: GroupingConfig | undefined, data: any[], aggregations?: AggregationConfig[]): UseGroupedDataResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-grid",
3
- "version": "3.0.3",
3
+ "version": "3.1.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Grid plugin for Object UI",
@@ -15,14 +15,14 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@tanstack/react-virtual": "^3.13.18",
19
- "lucide-react": "^0.563.0",
20
- "@object-ui/components": "3.0.3",
21
- "@object-ui/core": "3.0.3",
22
- "@object-ui/fields": "3.0.3",
23
- "@object-ui/mobile": "3.0.3",
24
- "@object-ui/react": "3.0.3",
25
- "@object-ui/types": "3.0.3"
18
+ "@tanstack/react-virtual": "^3.13.19",
19
+ "lucide-react": "^0.576.0",
20
+ "@object-ui/components": "3.1.1",
21
+ "@object-ui/core": "3.1.1",
22
+ "@object-ui/fields": "3.1.1",
23
+ "@object-ui/mobile": "3.1.1",
24
+ "@object-ui/react": "3.1.1",
25
+ "@object-ui/types": "3.1.1"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": "^18.0.0 || ^19.0.0",
@@ -34,7 +34,7 @@
34
34
  "typescript": "^5.9.3",
35
35
  "vite": "^7.3.1",
36
36
  "vite-plugin-dts": "^4.5.4",
37
- "@object-ui/data-objectstack": "3.0.3"
37
+ "@object-ui/data-objectstack": "3.1.1"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "vite build",
@@ -0,0 +1,151 @@
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 React, { useState, useRef, useEffect, useCallback } from 'react';
10
+ import { cn } from '@object-ui/components';
11
+ import { FunctionSquare, Check, X } from 'lucide-react';
12
+
13
+ export interface FormulaBarProps {
14
+ /** Current cell value displayed in the bar. */
15
+ value: string;
16
+ /** Called when the user edits the value (controlled input). */
17
+ onChange?: (value: string) => void;
18
+ /** Called when the user confirms the edit (Enter or check button). */
19
+ onConfirm?: (value: string) => void;
20
+ /** Called when the user cancels the edit (Escape or X button). */
21
+ onCancel?: () => void;
22
+ /** Label describing the active cell, e.g. "A1" or "name". */
23
+ activeCell?: string;
24
+ /** Whether editing is disabled. */
25
+ disabled?: boolean;
26
+ /** Additional class names. */
27
+ className?: string;
28
+ }
29
+
30
+ /**
31
+ * Excel-like formula bar that displays and allows editing of the active cell's
32
+ * value. Press Enter (or the ✓ button) to confirm, Escape (or the ✗ button)
33
+ * to cancel.
34
+ */
35
+ export function FormulaBar({
36
+ value,
37
+ onChange,
38
+ onConfirm,
39
+ onCancel,
40
+ activeCell,
41
+ disabled = false,
42
+ className,
43
+ }: FormulaBarProps) {
44
+ const [editing, setEditing] = useState(false);
45
+ const [editValue, setEditValue] = useState(value);
46
+ const inputRef = useRef<HTMLInputElement>(null);
47
+
48
+ // Sync external value when not editing.
49
+ useEffect(() => {
50
+ if (!editing) {
51
+ setEditValue(value);
52
+ }
53
+ }, [value, editing]);
54
+
55
+ // Auto-focus on edit start.
56
+ useEffect(() => {
57
+ if (editing && inputRef.current) {
58
+ inputRef.current.focus();
59
+ inputRef.current.select();
60
+ }
61
+ }, [editing]);
62
+
63
+ const startEditing = useCallback(() => {
64
+ if (disabled) return;
65
+ setEditing(true);
66
+ setEditValue(value);
67
+ }, [disabled, value]);
68
+
69
+ const confirm = useCallback(() => {
70
+ setEditing(false);
71
+ onChange?.(editValue);
72
+ onConfirm?.(editValue);
73
+ }, [editValue, onChange, onConfirm]);
74
+
75
+ const cancel = useCallback(() => {
76
+ setEditing(false);
77
+ setEditValue(value);
78
+ onCancel?.();
79
+ }, [value, onCancel]);
80
+
81
+ const handleKeyDown = useCallback(
82
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
83
+ if (e.key === 'Enter') {
84
+ e.preventDefault();
85
+ confirm();
86
+ } else if (e.key === 'Escape') {
87
+ e.preventDefault();
88
+ cancel();
89
+ }
90
+ },
91
+ [confirm, cancel],
92
+ );
93
+
94
+ return (
95
+ <div
96
+ className={cn(
97
+ 'flex items-center gap-2 border-b border-border bg-muted/30 px-3 py-1.5',
98
+ className,
99
+ )}
100
+ >
101
+ {/* f(x) indicator */}
102
+ <FunctionSquare className="h-4 w-4 shrink-0 text-muted-foreground" />
103
+
104
+ {/* Active cell label */}
105
+ {activeCell && (
106
+ <span className="min-w-[4rem] shrink-0 rounded bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
107
+ {activeCell}
108
+ </span>
109
+ )}
110
+
111
+ {/* Value input */}
112
+ <input
113
+ ref={inputRef}
114
+ type="text"
115
+ value={editing ? editValue : value}
116
+ readOnly={!editing}
117
+ disabled={disabled}
118
+ onClick={startEditing}
119
+ onChange={(e) => setEditValue(e.target.value)}
120
+ onKeyDown={handleKeyDown}
121
+ className={cn(
122
+ 'flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground',
123
+ editing && 'rounded ring-1 ring-ring px-1',
124
+ disabled && 'cursor-not-allowed opacity-50',
125
+ )}
126
+ />
127
+
128
+ {/* Confirm / Cancel buttons (visible only while editing) */}
129
+ {editing && (
130
+ <div className="flex items-center gap-1">
131
+ <button
132
+ type="button"
133
+ onClick={confirm}
134
+ className="rounded p-0.5 text-green-600 hover:bg-green-100"
135
+ aria-label="Confirm"
136
+ >
137
+ <Check className="h-4 w-4" />
138
+ </button>
139
+ <button
140
+ type="button"
141
+ onClick={cancel}
142
+ className="rounded p-0.5 text-red-600 hover:bg-red-100"
143
+ aria-label="Cancel"
144
+ >
145
+ <X className="h-4 w-4" />
146
+ </button>
147
+ </div>
148
+ )}
149
+ </div>
150
+ );
151
+ }
@@ -0,0 +1,69 @@
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 React from 'react';
10
+ import { ChevronRight, ChevronDown } from 'lucide-react';
11
+ import type { AggregationResult } from './useGroupedData';
12
+
13
+ export interface GroupRowProps {
14
+ /** Unique key identifying this group */
15
+ groupKey: string;
16
+ /** Display label for the group (field value or "(empty)") */
17
+ label: string;
18
+ /** Number of rows in this group */
19
+ count: number;
20
+ /** Whether the group is collapsed */
21
+ collapsed: boolean;
22
+ /** Computed aggregation results for this group */
23
+ aggregations?: AggregationResult[];
24
+ /** Callback when the group header is clicked to toggle collapse */
25
+ onToggle: (key: string) => void;
26
+ /** Children to render when not collapsed (the group content) */
27
+ children: React.ReactNode;
28
+ }
29
+
30
+ /**
31
+ * GroupRow renders a collapsible group header with field value, record count,
32
+ * and optional aggregation summary. Used by ObjectGrid for grouped rendering.
33
+ */
34
+ export const GroupRow: React.FC<GroupRowProps> = ({
35
+ groupKey,
36
+ label,
37
+ count,
38
+ collapsed,
39
+ aggregations,
40
+ onToggle,
41
+ children,
42
+ }) => {
43
+ return (
44
+ <div className="border rounded-md" data-testid={`group-row-${groupKey}`}>
45
+ <button
46
+ type="button"
47
+ className="flex w-full items-center gap-2 px-3 py-2 text-sm font-medium text-left bg-muted/50 hover:bg-muted transition-colors"
48
+ onClick={() => onToggle(groupKey)}
49
+ aria-expanded={!collapsed}
50
+ >
51
+ {collapsed
52
+ ? <ChevronRight className="h-4 w-4 shrink-0" />
53
+ : <ChevronDown className="h-4 w-4 shrink-0" />}
54
+ <span className="group-label">{label}</span>
55
+ {aggregations && aggregations.length > 0 && (
56
+ <span className="ml-2 text-xs text-muted-foreground group-aggregations">
57
+ {aggregations.map((agg) => (
58
+ <span key={`${agg.field}-${agg.type}`} className="mr-2">
59
+ {agg.type}: {Number.isInteger(agg.value) ? agg.value : agg.value.toFixed(2)}
60
+ </span>
61
+ ))}
62
+ </span>
63
+ )}
64
+ <span className="ml-auto text-xs text-muted-foreground group-count">({count})</span>
65
+ </button>
66
+ {!collapsed && children}
67
+ </div>
68
+ );
69
+ };