@object-ui/plugin-gantt 3.0.3 → 3.1.0

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.
@@ -22,9 +22,12 @@ export interface GanttViewProps {
22
22
  startDate?: Date;
23
23
  endDate?: Date;
24
24
  onTaskClick?: (task: GanttTask) => void;
25
+ onTaskUpdate?: (task: GanttTask, changes: Partial<Pick<GanttTask, 'title' | 'start' | 'end' | 'progress'>>) => void;
25
26
  onViewChange?: (view: GanttViewMode) => void;
26
27
  onAddClick?: () => void;
27
28
  className?: string;
29
+ /** Enable inline editing of task fields */
30
+ inlineEdit?: boolean;
28
31
  }
29
- export declare function GanttView({ tasks, viewMode, startDate, endDate, onTaskClick, onViewChange, onAddClick, className }: GanttViewProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function GanttView({ tasks, viewMode, startDate, endDate, onTaskClick, onTaskUpdate, onViewChange, onAddClick, className, inlineEdit, }: GanttViewProps): import("react/jsx-runtime").JSX.Element;
30
33
  //# sourceMappingURL=GanttView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GanttView.d.ts","sourceRoot":"","sources":["../../src/GanttView.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmCH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,YAAY,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAkB,EAClB,SAAS,EACT,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,SAAS,EACV,EAAE,cAAc,2CAqRhB"}
1
+ {"version":3,"file":"GanttView.d.ts","sourceRoot":"","sources":["../../src/GanttView.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0CH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,YAAY,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC,CAAC,KAAK,IAAI,CAAA;IACnH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAkB,EAClB,SAAS,EACT,OAAO,EACP,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAkB,GACnB,EAAE,cAAc,2CAqVhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectGantt.d.ts","sourceRoot":"","sources":["../../src/ObjectGantt.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAyB,MAAM,kBAAkB,CAAC;AAM5F,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAyFD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAyMlD,CAAC"}
1
+ {"version":3,"file":"ObjectGantt.d.ts","sourceRoot":"","sources":["../../src/ObjectGantt.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAyB,MAAM,kBAAkB,CAAC;AAO5F,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAyFD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkMlD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-gantt",
3
- "version": "3.0.3",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Gantt chart plugin for Object UI",
@@ -24,20 +24,20 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@objectstack/spec": "^3.0.4",
28
- "lucide-react": "^0.563.0",
29
- "@object-ui/components": "3.0.3",
30
- "@object-ui/core": "3.0.3",
31
- "@object-ui/fields": "3.0.3",
32
- "@object-ui/react": "3.0.3",
33
- "@object-ui/types": "3.0.3"
27
+ "@objectstack/spec": "^3.2.0",
28
+ "lucide-react": "^0.576.0",
29
+ "@object-ui/components": "3.1.0",
30
+ "@object-ui/core": "3.1.0",
31
+ "@object-ui/fields": "3.1.0",
32
+ "@object-ui/react": "3.1.0",
33
+ "@object-ui/types": "3.1.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "^18.0.0 || ^19.0.0",
37
37
  "react-dom": "^18.0.0 || ^19.0.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/react": "19.2.13",
40
+ "@types/react": "19.2.14",
41
41
  "@types/react-dom": "19.2.3",
42
42
  "@vitejs/plugin-react": "^5.1.4",
43
43
  "typescript": "^5.9.3",
package/src/GanttView.tsx CHANGED
@@ -39,6 +39,13 @@ function getResponsiveColumnWidth() {
39
39
  return 60;
40
40
  }
41
41
 
42
+ function getResponsiveTaskListWidth() {
43
+ const w = typeof window !== 'undefined' ? window.innerWidth : 1024;
44
+ if (w < 640) return 120;
45
+ if (w < 1024) return 200;
46
+ return 300;
47
+ }
48
+
42
49
  export interface GanttTask {
43
50
  id: string | number
44
51
  title: string
@@ -58,9 +65,12 @@ export interface GanttViewProps {
58
65
  startDate?: Date
59
66
  endDate?: Date
60
67
  onTaskClick?: (task: GanttTask) => void
68
+ onTaskUpdate?: (task: GanttTask, changes: Partial<Pick<GanttTask, 'title' | 'start' | 'end' | 'progress'>>) => void
61
69
  onViewChange?: (view: GanttViewMode) => void
62
70
  onAddClick?: () => void
63
71
  className?: string
72
+ /** Enable inline editing of task fields */
73
+ inlineEdit?: boolean
64
74
  }
65
75
 
66
76
  export function GanttView({
@@ -69,15 +79,19 @@ export function GanttView({
69
79
  startDate,
70
80
  endDate,
71
81
  onTaskClick,
82
+ onTaskUpdate,
72
83
  onViewChange,
73
84
  onAddClick,
74
- className
85
+ className,
86
+ inlineEdit = false,
75
87
  }: GanttViewProps) {
76
88
  const [currentDate, setCurrentDate] = React.useState(new Date());
77
89
  const [rowHeight, setRowHeight] = React.useState(
78
90
  typeof window !== 'undefined' && window.innerWidth < 640 ? 32 : 40
79
91
  );
80
92
  const [columnWidth, setColumnWidth] = React.useState(getResponsiveColumnWidth());
93
+ const [editingTask, setEditingTask] = React.useState<string | number | null>(null);
94
+ const [editValues, setEditValues] = React.useState<Record<string, string>>({});
81
95
 
82
96
  React.useEffect(() => {
83
97
  const handleResize = () => {
@@ -131,7 +145,7 @@ export function GanttView({
131
145
  return cols;
132
146
  }, [timelineRange]);
133
147
 
134
- const taskListWidth = 300;
148
+ const taskListWidth = getResponsiveTaskListWidth();
135
149
 
136
150
  const headerRef = React.useRef<HTMLDivElement>(null);
137
151
  const listRef = React.useRef<HTMLDivElement>(null);
@@ -163,7 +177,7 @@ export function GanttView({
163
177
  };
164
178
 
165
179
  return (
166
- <div className={cn("flex flex-col h-full bg-background border rounded-lg overflow-hidden", className)}>
180
+ <div className={cn("flex flex-col h-full bg-background border rounded-lg overflow-hidden min-w-0", className)}>
167
181
  {/* Toolbar */}
168
182
  <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 p-2 border-b bg-card">
169
183
  <div className="flex items-center gap-2">
@@ -173,10 +187,10 @@ export function GanttView({
173
187
  <span className="sm:hidden">New</span>
174
188
  </Button>
175
189
  <div className="h-4 w-px bg-border mx-1 sm:mx-2" />
176
- <Button variant="ghost" size="icon" className="h-8 w-8">
190
+ <Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Previous period">
177
191
  <ChevronLeft className="h-4 w-4" />
178
192
  </Button>
179
- <Button variant="ghost" size="icon" className="h-8 w-8">
193
+ <Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Next period">
180
194
  <ChevronRight className="h-4 w-4" />
181
195
  </Button>
182
196
  <span className="font-semibold text-xs sm:text-sm">
@@ -197,10 +211,10 @@ export function GanttView({
197
211
  </SelectContent>
198
212
  </Select>
199
213
  <div className="flex bg-muted rounded-md p-1">
200
- <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setColumnWidth(prev => Math.max(15, prev - 10))}>
214
+ <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setColumnWidth(prev => Math.max(15, prev - 10))} aria-label="Zoom out">
201
215
  <ZoomOut className="h-3 w-3" />
202
216
  </Button>
203
- <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setColumnWidth(prev => Math.min(120, prev + 10))}>
217
+ <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setColumnWidth(prev => Math.min(120, prev + 10))} aria-label="Zoom in">
204
218
  <ZoomIn className="h-3 w-3" />
205
219
  </Button>
206
220
  </div>
@@ -251,28 +265,90 @@ export function GanttView({
251
265
  ref={listRef}
252
266
  style={{ width: taskListWidth, minWidth: taskListWidth }}
253
267
  >
254
- {tasks.map((task) => (
268
+ {tasks.map((task) => {
269
+ const isEditing = inlineEdit && editingTask === task.id;
270
+ return (
255
271
  <div
256
272
  key={task.id}
257
273
  className="flex items-center border-b px-2 sm:px-4 hover:bg-accent/50 cursor-pointer transition-colors touch-manipulation"
258
274
  style={{ height: rowHeight }}
259
- onClick={() => onTaskClick?.(task)}
275
+ onClick={() => !isEditing && onTaskClick?.(task)}
276
+ onDoubleClick={() => {
277
+ if (inlineEdit && onTaskUpdate) {
278
+ setEditingTask(task.id);
279
+ setEditValues({
280
+ title: task.title,
281
+ start: task.start.toLocaleDateString('en-CA'),
282
+ end: task.end.toLocaleDateString('en-CA'),
283
+ progress: String(task.progress),
284
+ });
285
+ }
286
+ }}
260
287
  >
261
288
  <div className="flex-1 truncate font-medium text-xs sm:text-sm flex items-center gap-2">
262
289
  <div
263
290
  className="w-2 h-2 rounded-full shrink-0"
264
291
  style={{ backgroundColor: task.color || '#3b82f6' }}
265
292
  />
266
- {task.title}
293
+ {isEditing ? (
294
+ <input
295
+ className="border rounded px-1 py-0.5 text-xs w-full bg-background"
296
+ value={editValues.title || ''}
297
+ onChange={(e) => setEditValues(prev => ({ ...prev, title: e.target.value }))}
298
+ onKeyDown={(e) => {
299
+ if (e.key === 'Enter') {
300
+ onTaskUpdate?.(task, {
301
+ title: editValues.title,
302
+ start: new Date(editValues.start),
303
+ end: new Date(editValues.end),
304
+ progress: Number(editValues.progress) || 0,
305
+ });
306
+ setEditingTask(null);
307
+ } else if (e.key === 'Escape') {
308
+ setEditingTask(null);
309
+ }
310
+ }}
311
+ onClick={(e) => e.stopPropagation()}
312
+ autoFocus
313
+ />
314
+ ) : (
315
+ <span className="flex flex-col min-w-0">
316
+ <span className="truncate">{task.title}</span>
317
+ <span className="text-[10px] text-muted-foreground sm:hidden">
318
+ {task.start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })} → {task.end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })}
319
+ </span>
320
+ </span>
321
+ )}
267
322
  </div>
268
323
  <div className="w-16 sm:w-20 text-right text-xs text-muted-foreground hidden sm:block">
269
- {task.start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })}
324
+ {isEditing ? (
325
+ <input
326
+ type="date"
327
+ className="border rounded px-1 py-0.5 text-xs w-full bg-background"
328
+ value={editValues.start || ''}
329
+ onChange={(e) => setEditValues(prev => ({ ...prev, start: e.target.value }))}
330
+ onClick={(e) => e.stopPropagation()}
331
+ />
332
+ ) : (
333
+ task.start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })
334
+ )}
270
335
  </div>
271
336
  <div className="w-16 sm:w-20 text-right text-xs text-muted-foreground hidden sm:block">
272
- {task.end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })}
337
+ {isEditing ? (
338
+ <input
339
+ type="date"
340
+ className="border rounded px-1 py-0.5 text-xs w-full bg-background"
341
+ value={editValues.end || ''}
342
+ onChange={(e) => setEditValues(prev => ({ ...prev, end: e.target.value }))}
343
+ onClick={(e) => e.stopPropagation()}
344
+ />
345
+ ) : (
346
+ task.end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' })
347
+ )}
273
348
  </div>
274
349
  </div>
275
- ))}
350
+ );
351
+ })}
276
352
  </div>
277
353
 
278
354
  {/* Right Side: Timeline */}
@@ -27,6 +27,7 @@ import type { ObjectGridSchema, DataSource, ViewData, GanttConfig } from '@objec
27
27
  import { GanttConfigSchema } from '@objectstack/spec/ui';
28
28
  import { useNavigationOverlay } from '@object-ui/react';
29
29
  import { NavigationOverlay } from '@object-ui/components';
30
+ import { extractRecords, buildExpandFields } from '@object-ui/core';
30
31
  import { GanttView, type GanttTask } from './GanttView';
31
32
 
32
33
  export interface ObjectGanttProps {
@@ -167,27 +168,20 @@ export const ObjectGantt: React.FC<ObjectGanttProps> = ({
167
168
  return;
168
169
  }
169
170
 
170
- if (!dataSource) {
171
+ if (!dataSource || typeof dataSource.find !== 'function') {
171
172
  throw new Error('DataSource required for object/api providers');
172
173
  }
173
174
 
174
175
  if (dataConfig?.provider === 'object') {
175
176
  const objectName = dataConfig.object;
177
+ // Auto-inject $expand for lookup/master_detail fields
178
+ const expand = buildExpandFields(objectSchema?.fields);
176
179
  const result = await dataSource.find(objectName, {
177
180
  $filter: schema.filter,
178
181
  $orderby: convertSortToQueryParams(schema.sort),
182
+ ...(expand.length > 0 ? { $expand: expand } : {}),
179
183
  });
180
-
181
- let items: any[] = [];
182
- if (Array.isArray(result)) {
183
- items = result;
184
- } else if (result && typeof result === 'object') {
185
- if (Array.isArray((result as any).data)) {
186
- items = (result as any).data;
187
- } else if (Array.isArray((result as any).records)) {
188
- items = (result as any).records;
189
- }
190
- }
184
+ let items: any[] = extractRecords(result);
191
185
  setData(items);
192
186
  } else if (dataConfig?.provider === 'api') {
193
187
  console.warn('API provider not yet implemented for ObjectGantt');
@@ -202,7 +196,7 @@ export const ObjectGantt: React.FC<ObjectGanttProps> = ({
202
196
  };
203
197
 
204
198
  fetchData();
205
- }, [dataConfig, dataSource, hasInlineData, schema.filter, schema.sort]);
199
+ }, [dataConfig, dataSource, hasInlineData, schema.filter, schema.sort, objectSchema]);
206
200
 
207
201
  // Fetch object schema for field metadata
208
202
  useEffect(() => {
@@ -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 { describe, it, expect } from 'vitest';
11
+ import { render, screen } from '@testing-library/react';
12
+ import '@testing-library/jest-dom';
13
+ import { GanttView, type GanttTask } from '../GanttView';
14
+
15
+ const mockTasks: GanttTask[] = [
16
+ {
17
+ id: '1',
18
+ title: 'Design Phase',
19
+ start: new Date('2024-01-15'),
20
+ end: new Date('2024-02-15'),
21
+ progress: 75,
22
+ },
23
+ {
24
+ id: '2',
25
+ title: 'Development',
26
+ start: new Date('2024-02-01'),
27
+ end: new Date('2024-03-15'),
28
+ progress: 30,
29
+ },
30
+ ];
31
+
32
+ describe('GanttView accessibility', () => {
33
+ it('renders aria-label on navigation buttons', () => {
34
+ render(<GanttView tasks={mockTasks} />);
35
+ expect(screen.getByLabelText('Previous period')).toBeInTheDocument();
36
+ expect(screen.getByLabelText('Next period')).toBeInTheDocument();
37
+ });
38
+
39
+ it('renders aria-label on zoom buttons', () => {
40
+ render(<GanttView tasks={mockTasks} />);
41
+ expect(screen.getByLabelText('Zoom out')).toBeInTheDocument();
42
+ expect(screen.getByLabelText('Zoom in')).toBeInTheDocument();
43
+ });
44
+
45
+ it('renders aria-label on create button', () => {
46
+ render(<GanttView tasks={mockTasks} />);
47
+ expect(screen.getByLabelText('Create new task')).toBeInTheDocument();
48
+ });
49
+ });
50
+
51
+ describe('GanttView mobile date badge', () => {
52
+ it('renders date range text below task title', () => {
53
+ render(<GanttView tasks={mockTasks} />);
54
+ // The mobile date badge shows start → end dates
55
+ const startDate = mockTasks[0].start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' });
56
+ const endDate = mockTasks[0].end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' });
57
+ const dateText = `${startDate} → ${endDate}`;
58
+ expect(screen.getByText(dateText)).toBeInTheDocument();
59
+ });
60
+
61
+ it('renders mobile date badge with sm:hidden class', () => {
62
+ render(<GanttView tasks={mockTasks} />);
63
+ const startDate = mockTasks[0].start.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' });
64
+ const endDate = mockTasks[0].end.toLocaleDateString(undefined, { month: 'numeric', day: 'numeric' });
65
+ const dateText = `${startDate} → ${endDate}`;
66
+ const badge = screen.getByText(dateText);
67
+ expect(badge.className).toContain('sm:hidden');
68
+ });
69
+ });
package/src/index.tsx CHANGED
@@ -20,7 +20,7 @@ export type { GanttViewProps, GanttTask, GanttViewMode } from './GanttView';
20
20
 
21
21
  // Register component
22
22
  export const ObjectGanttRenderer: React.FC<{ schema: any }> = ({ schema }) => {
23
- const { dataSource } = useSchemaContext();
23
+ const { dataSource } = useSchemaContext() || {};
24
24
  return <ObjectGantt schema={schema} dataSource={dataSource} />;
25
25
  };
26
26