@canvas-components/gantt-table 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 canvas-components contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @canvas-components/gantt-table
2
+
3
+ 纯原生 Canvas 甘特图核心,目录组织参考 `@canvas-components/list-table`:配置位于 `schema`,布局与日期计算位于 `domain`,绘制位于 `rendering`,命中测试位于 `interaction`。
4
+
5
+ 首版支持时间轴、任务条、进度、依赖线、滚动、任务选中及拖拽平移日期。React 接入位于独立的 `@canvas-components/react-gantt-table` 适配包中。
package/dist/index.cjs ADDED
@@ -0,0 +1,262 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/domain/date.ts
8
+ var toTimestamp = (value) => {
9
+ const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();
10
+ if (!Number.isFinite(timestamp)) throw new Error(`Invalid date value: ${String(value)}`);
11
+ return timestamp;
12
+ };
13
+ var dayMs = 24 * 60 * 60 * 1e3;
14
+ function startOfDay(timestamp) {
15
+ const date = new Date(timestamp);
16
+ date.setHours(0, 0, 0, 0);
17
+ return date.getTime();
18
+ }
19
+ function formatDate(timestamp) {
20
+ return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit" }).format(new Date(timestamp));
21
+ }
22
+
23
+ // src/domain/layout.ts
24
+ function resolveDayWidth(scale) {
25
+ return scale === "month" ? 42 : scale === "week" ? 28 : 80;
26
+ }
27
+ function buildGanttLayout(options, width) {
28
+ const timestamps = options.tasks.flatMap((task) => [toTimestamp(task.start), toTimestamp(task.end)]);
29
+ const start = startOfDay(toTimestamp(options.startDate ?? Math.min(...timestamps, Date.now())));
30
+ const end = startOfDay(toTimestamp(options.endDate ?? Math.max(...timestamps, Date.now()) + dayMs));
31
+ const dayWidth = resolveDayWidth(options.scale ?? "day");
32
+ const days = Math.max(1, Math.ceil((end - start) / dayMs));
33
+ const taskColumnWidth = options.taskColumnWidth ?? 220;
34
+ const rows = options.tasks.map((task, index) => ({ task, index, y: (options.headerHeight ?? 56) + index * (options.rowHeight ?? 36) }));
35
+ return { start, end, dayWidth, rowHeight: options.rowHeight ?? 36, headerHeight: options.headerHeight ?? 56, taskColumnWidth, rows, totalWidth: taskColumnWidth + days * dayWidth, totalHeight: (options.headerHeight ?? 56) + options.tasks.length * (options.rowHeight ?? 36) };
36
+ }
37
+ function xForDate(timestamp, layout) {
38
+ return layout.taskColumnWidth + (timestamp - layout.start) / dayMs * layout.dayWidth;
39
+ }
40
+
41
+ // src/interaction/hit-test.ts
42
+ function hitTestTask(x, y, layout) {
43
+ const row = layout.rows.find((item) => y >= item.y && y < item.y + layout.rowHeight);
44
+ if (!row) return null;
45
+ const start = xForDate(toTimestamp(row.task.start), layout);
46
+ const end = xForDate(toTimestamp(row.task.end), layout);
47
+ return x >= start && x <= end ? row.task : null;
48
+ }
49
+
50
+ // src/schema/options.ts
51
+ var defaultGanttTheme = {
52
+ backgroundColor: "#ffffff",
53
+ borderColor: "#d9e1ec",
54
+ textColor: "#1f2329",
55
+ mutedTextColor: "#7a8798",
56
+ headerBackgroundColor: "#f5f8fc",
57
+ weekendBackgroundColor: "#fafafa",
58
+ gridColor: "#e7edf5",
59
+ taskColor: "#1677ff",
60
+ taskProgressColor: "#0958d9",
61
+ milestoneColor: "#722ed1",
62
+ selectionColor: "#91caff",
63
+ fontFamily: '"Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
64
+ fontSize: 14
65
+ };
66
+
67
+ // src/rendering/canvas-renderer.ts
68
+ function renderGantt(context, layout, dependencies, selectedId, theme) {
69
+ context.clearRect(0, 0, context.canvas.width, context.canvas.height);
70
+ context.fillStyle = theme.backgroundColor;
71
+ context.fillRect(0, 0, context.canvas.width, context.canvas.height);
72
+ context.font = `${theme.fontSize}px ${theme.fontFamily}`;
73
+ context.textBaseline = "middle";
74
+ context.fillStyle = theme.headerBackgroundColor;
75
+ context.fillRect(0, 0, layout.totalWidth, layout.headerHeight);
76
+ context.strokeStyle = theme.borderColor;
77
+ context.beginPath();
78
+ context.moveTo(layout.taskColumnWidth, 0);
79
+ context.lineTo(layout.taskColumnWidth, layout.totalHeight);
80
+ context.stroke();
81
+ for (let t = layout.start, day = 0; t < layout.end; t += dayMs, day++) {
82
+ const x = layout.taskColumnWidth + day * layout.dayWidth;
83
+ const date = new Date(t);
84
+ const weekend = date.getDay() === 0 || date.getDay() === 6;
85
+ if (weekend) {
86
+ context.fillStyle = theme.weekendBackgroundColor;
87
+ context.fillRect(x, layout.headerHeight, layout.dayWidth, layout.totalHeight - layout.headerHeight);
88
+ }
89
+ context.strokeStyle = theme.gridColor;
90
+ context.beginPath();
91
+ context.moveTo(x, 0);
92
+ context.lineTo(x, layout.totalHeight);
93
+ context.stroke();
94
+ context.fillStyle = theme.mutedTextColor;
95
+ context.fillText(formatDate(t), x + 6, layout.headerHeight / 2);
96
+ }
97
+ context.fillStyle = theme.textColor;
98
+ context.fillText("\u4EFB\u52A1", 16, layout.headerHeight / 2);
99
+ layout.rows.forEach(({ task, y }) => {
100
+ context.strokeStyle = theme.gridColor;
101
+ context.beginPath();
102
+ context.moveTo(0, y);
103
+ context.lineTo(layout.totalWidth, y);
104
+ context.stroke();
105
+ context.fillStyle = theme.textColor;
106
+ context.fillText(task.title, 16, y + layout.rowHeight / 2);
107
+ const start = toTimestamp(task.start);
108
+ const end = Math.max(start + dayMs, toTimestamp(task.end));
109
+ const x = xForDate(start, layout);
110
+ const w = Math.max(8, (end - start) / dayMs * layout.dayWidth - 4);
111
+ const barY = y + 8;
112
+ const barH = layout.rowHeight - 16;
113
+ context.fillStyle = task.color ?? theme.taskColor;
114
+ context.beginPath();
115
+ context.roundRect(x, barY, w, barH, 4);
116
+ context.fill();
117
+ if (task.progress) {
118
+ context.fillStyle = theme.taskProgressColor;
119
+ context.beginPath();
120
+ context.roundRect(x, barY, w * Math.min(100, Math.max(0, task.progress)) / 100, barH, 4);
121
+ context.fill();
122
+ }
123
+ if (selectedId === task.id) {
124
+ context.strokeStyle = theme.selectionColor;
125
+ context.lineWidth = 2;
126
+ context.strokeRect(x - 1, barY - 1, w + 2, barH + 2);
127
+ context.lineWidth = 1;
128
+ }
129
+ });
130
+ const rowMap = new Map(layout.rows.map((row) => [row.task.id, row]));
131
+ context.strokeStyle = theme.milestoneColor;
132
+ context.fillStyle = theme.milestoneColor;
133
+ dependencies.forEach((dependency) => {
134
+ const from = rowMap.get(dependency.from);
135
+ const to = rowMap.get(dependency.to);
136
+ if (!from || !to) return;
137
+ const fromX = xForDate(toTimestamp(from.task.end), layout);
138
+ const fromY = from.y + layout.rowHeight / 2;
139
+ const toX = xForDate(toTimestamp(to.task.start), layout);
140
+ const toY = to.y + layout.rowHeight / 2;
141
+ context.beginPath();
142
+ context.moveTo(fromX, fromY);
143
+ context.lineTo((fromX + toX) / 2, fromY);
144
+ context.lineTo((fromX + toX) / 2, toY);
145
+ context.lineTo(toX, toY);
146
+ context.stroke();
147
+ context.beginPath();
148
+ context.moveTo(toX, toY);
149
+ context.lineTo(toX - 5, toY - 4);
150
+ context.lineTo(toX - 5, toY + 4);
151
+ context.closePath();
152
+ context.fill();
153
+ });
154
+ }
155
+
156
+ // src/gantt.ts
157
+ var Gantt = class {
158
+ constructor(container, options) {
159
+ __publicField(this, "container");
160
+ __publicField(this, "canvas");
161
+ __publicField(this, "context");
162
+ __publicField(this, "options");
163
+ __publicField(this, "layout", null);
164
+ __publicField(this, "selectedId", null);
165
+ __publicField(this, "scrollLeft", 0);
166
+ __publicField(this, "scrollTop", 0);
167
+ __publicField(this, "resizeObserver", null);
168
+ __publicField(this, "drag", null);
169
+ __publicField(this, "handlePointerDown", (event) => {
170
+ if (!this.layout) return;
171
+ const point = this.point(event);
172
+ const task = hitTestTask(point.x, point.y, this.layout);
173
+ if (!task) return;
174
+ this.selectedId = task.id;
175
+ this.drag = { task, startX: point.x, originalStart: new Date(task.start).getTime(), originalEnd: new Date(task.end).getTime() };
176
+ this.canvas.setPointerCapture(event.pointerId);
177
+ this.options.onTaskClick?.(task);
178
+ this.render();
179
+ });
180
+ __publicField(this, "handlePointerMove", (event) => {
181
+ if (!this.drag || !this.layout) return;
182
+ const delta = Math.round((this.point(event).x - this.drag.startX) / this.layout.dayWidth) * 864e5;
183
+ this.drag.task.start = new Date(this.drag.originalStart + delta);
184
+ this.drag.task.end = new Date(this.drag.originalEnd + delta);
185
+ this.render();
186
+ });
187
+ __publicField(this, "handlePointerUp", (event) => {
188
+ if (!this.drag) return;
189
+ this.options.onTaskChange?.(this.drag.task);
190
+ this.drag = null;
191
+ this.canvas.releasePointerCapture(event.pointerId);
192
+ });
193
+ __publicField(this, "handleWheel", (event) => {
194
+ event.preventDefault();
195
+ this.scrollLeft = Math.max(0, this.scrollLeft + event.deltaX + (event.shiftKey ? event.deltaY : 0));
196
+ this.scrollTop = Math.max(0, this.scrollTop + (event.shiftKey ? 0 : event.deltaY));
197
+ this.render();
198
+ });
199
+ const context = document.createElement("canvas").getContext("2d");
200
+ if (!context) throw new Error("Canvas 2D context is not supported in the current environment.");
201
+ this.container = container;
202
+ this.canvas = context.canvas;
203
+ this.context = context;
204
+ this.options = options;
205
+ this.canvas.style.display = "block";
206
+ this.canvas.style.touchAction = "none";
207
+ this.container.style.overflow = "hidden";
208
+ }
209
+ /** 挂载并开始渲染。 */
210
+ mount() {
211
+ if (!this.canvas.parentElement) this.container.appendChild(this.canvas);
212
+ this.canvas.addEventListener("pointerdown", this.handlePointerDown);
213
+ this.canvas.addEventListener("pointermove", this.handlePointerMove);
214
+ this.canvas.addEventListener("pointerup", this.handlePointerUp);
215
+ this.canvas.addEventListener("wheel", this.handleWheel, { passive: false });
216
+ this.resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => this.render());
217
+ this.resizeObserver?.observe(this.container);
218
+ this.render();
219
+ }
220
+ /** 更新配置并重绘。 */
221
+ updateOptions(options) {
222
+ this.options = options;
223
+ this.selectedId = null;
224
+ this.render();
225
+ }
226
+ /** 获取当前选中任务。 */
227
+ getSelectedTask() {
228
+ return this.options.tasks.find((task) => task.id === this.selectedId) ?? null;
229
+ }
230
+ /** 销毁实例。 */
231
+ destroy() {
232
+ this.resizeObserver?.disconnect();
233
+ this.resizeObserver = null;
234
+ this.canvas.remove();
235
+ }
236
+ render() {
237
+ const width = this.container.clientWidth || this.options.taskColumnWidth || 800;
238
+ const height = this.container.clientHeight || 480;
239
+ const ratio = window.devicePixelRatio || 1;
240
+ this.layout = buildGanttLayout(this.options);
241
+ this.canvas.width = width * ratio;
242
+ this.canvas.height = height * ratio;
243
+ this.canvas.style.width = `${width}px`;
244
+ this.canvas.style.height = `${height}px`;
245
+ this.context.setTransform(ratio, 0, 0, ratio, -this.scrollLeft * ratio, -this.scrollTop * ratio);
246
+ renderGantt(this.context, this.layout, this.options.dependencies ?? [], this.selectedId, { ...defaultGanttTheme, ...this.options.theme });
247
+ }
248
+ point(event) {
249
+ const rect = this.canvas.getBoundingClientRect();
250
+ return { x: event.clientX - rect.left + this.scrollLeft, y: event.clientY - rect.top + this.scrollTop };
251
+ }
252
+ };
253
+
254
+ exports.Gantt = Gantt;
255
+ exports.buildGanttLayout = buildGanttLayout;
256
+ exports.defaultGanttTheme = defaultGanttTheme;
257
+ exports.hitTestTask = hitTestTask;
258
+ exports.renderGantt = renderGantt;
259
+ exports.resolveDayWidth = resolveDayWidth;
260
+ exports.xForDate = xForDate;
261
+ //# sourceMappingURL=index.cjs.map
262
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/domain/date.ts","../src/domain/layout.ts","../src/interaction/hit-test.ts","../src/schema/options.ts","../src/rendering/canvas-renderer.ts","../src/gantt.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const toTimestamp = (value: Date | string | number) => {\n const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();\n if (!Number.isFinite(timestamp)) throw new Error(`Invalid date value: ${String(value)}`);\n return timestamp;\n};\n\nexport const dayMs = 24 * 60 * 60 * 1000;\n\nexport function startOfDay(timestamp: number) {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\nexport function formatDate(timestamp: number) {\n return new Intl.DateTimeFormat(\"zh-CN\", { month: \"2-digit\", day: \"2-digit\" }).format(new Date(timestamp));\n}\n","import { dayMs, startOfDay, toTimestamp } from \"./date\";\nimport type { GanttOptions, GanttScale, GanttTask } from \"../schema/options\";\n\nexport interface GanttLayout {\n start: number;\n end: number;\n dayWidth: number;\n rowHeight: number;\n headerHeight: number;\n taskColumnWidth: number;\n rows: Array<{ task: GanttTask; index: number; y: number }>;\n totalWidth: number;\n totalHeight: number;\n}\n\nexport function resolveDayWidth(scale: GanttScale) {\n return scale === \"month\" ? 42 : scale === \"week\" ? 28 : 80;\n}\n\nexport function buildGanttLayout(options: GanttOptions, width: number): GanttLayout {\n const timestamps = options.tasks.flatMap((task) => [toTimestamp(task.start), toTimestamp(task.end)]);\n const start = startOfDay(toTimestamp(options.startDate ?? Math.min(...timestamps, Date.now())));\n const end = startOfDay(toTimestamp(options.endDate ?? Math.max(...timestamps, Date.now()) + dayMs));\n const dayWidth = resolveDayWidth(options.scale ?? \"day\");\n const days = Math.max(1, Math.ceil((end - start) / dayMs));\n const taskColumnWidth = options.taskColumnWidth ?? 220;\n const rows = options.tasks.map((task, index) => ({ task, index, y: (options.headerHeight ?? 56) + index * (options.rowHeight ?? 36) }));\n return { start, end, dayWidth, rowHeight: options.rowHeight ?? 36, headerHeight: options.headerHeight ?? 56, taskColumnWidth, rows, totalWidth: taskColumnWidth + days * dayWidth, totalHeight: (options.headerHeight ?? 56) + options.tasks.length * (options.rowHeight ?? 36) };\n}\n\nexport function xForDate(timestamp: number, layout: GanttLayout) {\n return layout.taskColumnWidth + ((timestamp - layout.start) / dayMs) * layout.dayWidth;\n}\n","import { toTimestamp } from \"../domain/date\";\nimport { xForDate, type GanttLayout } from \"../domain/layout\";\nimport type { GanttTask } from \"../schema/options\";\n\nexport function hitTestTask(x: number, y: number, layout: GanttLayout): GanttTask | null {\n const row = layout.rows.find((item) => y >= item.y && y < item.y + layout.rowHeight);\n if (!row) return null;\n const start = xForDate(toTimestamp(row.task.start), layout); const end = xForDate(toTimestamp(row.task.end), layout);\n return x >= start && x <= end ? row.task : null;\n}\n","/** 甘特图缩放级别。 */\nexport type GanttScale = \"day\" | \"week\" | \"month\";\n\n/** 甘特任务定义。 */\nexport interface GanttTask {\n id: string;\n title: string;\n start: Date | string | number;\n end: Date | string | number;\n progress?: number;\n color?: string;\n parentId?: string;\n collapsed?: boolean;\n}\n\n/** 任务依赖定义。 */\nexport interface GanttDependency {\n from: string;\n to: string;\n type?: \"finish-to-start\" | \"start-to-start\" | \"finish-to-finish\";\n}\n\n/** 甘特图主题。 */\nexport interface GanttTheme {\n backgroundColor: string;\n borderColor: string;\n textColor: string;\n mutedTextColor: string;\n headerBackgroundColor: string;\n weekendBackgroundColor: string;\n gridColor: string;\n taskColor: string;\n taskProgressColor: string;\n milestoneColor: string;\n selectionColor: string;\n fontFamily: string;\n fontSize: number;\n}\n\n/** 甘特图配置。 */\nexport interface GanttOptions {\n tasks: GanttTask[];\n dependencies?: GanttDependency[];\n startDate?: Date | string | number;\n endDate?: Date | string | number;\n scale?: GanttScale;\n rowHeight?: number;\n headerHeight?: number;\n taskColumnWidth?: number;\n theme?: Partial<GanttTheme>;\n onTaskClick?: (task: GanttTask) => void;\n onTaskChange?: (task: GanttTask) => void;\n}\n\nexport const defaultGanttTheme: GanttTheme = {\n backgroundColor: \"#ffffff\",\n borderColor: \"#d9e1ec\",\n textColor: \"#1f2329\",\n mutedTextColor: \"#7a8798\",\n headerBackgroundColor: \"#f5f8fc\",\n weekendBackgroundColor: \"#fafafa\",\n gridColor: \"#e7edf5\",\n taskColor: \"#1677ff\",\n taskProgressColor: \"#0958d9\",\n milestoneColor: \"#722ed1\",\n selectionColor: \"#91caff\",\n fontFamily: '\"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif',\n fontSize: 14,\n};\n","import { formatDate, dayMs, toTimestamp } from \"../domain/date\";\nimport type { GanttLayout } from \"../domain/layout\";\nimport { xForDate } from \"../domain/layout\";\nimport { defaultGanttTheme, type GanttDependency, type GanttTheme } from \"../schema/options\";\n\nexport function renderGantt(context: CanvasRenderingContext2D, layout: GanttLayout, dependencies: GanttDependency[], selectedId: string | null, theme: GanttTheme) {\n context.clearRect(0, 0, context.canvas.width, context.canvas.height);\n context.fillStyle = theme.backgroundColor; context.fillRect(0, 0, context.canvas.width, context.canvas.height);\n context.font = `${theme.fontSize}px ${theme.fontFamily}`; context.textBaseline = \"middle\";\n context.fillStyle = theme.headerBackgroundColor; context.fillRect(0, 0, layout.totalWidth, layout.headerHeight);\n context.strokeStyle = theme.borderColor; context.beginPath(); context.moveTo(layout.taskColumnWidth, 0); context.lineTo(layout.taskColumnWidth, layout.totalHeight); context.stroke();\n for (let t = layout.start, day = 0; t < layout.end; t += dayMs, day++) {\n const x = layout.taskColumnWidth + day * layout.dayWidth;\n const date = new Date(t); const weekend = date.getDay() === 0 || date.getDay() === 6;\n if (weekend) { context.fillStyle = theme.weekendBackgroundColor; context.fillRect(x, layout.headerHeight, layout.dayWidth, layout.totalHeight - layout.headerHeight); }\n context.strokeStyle = theme.gridColor; context.beginPath(); context.moveTo(x, 0); context.lineTo(x, layout.totalHeight); context.stroke();\n context.fillStyle = theme.mutedTextColor; context.fillText(formatDate(t), x + 6, layout.headerHeight / 2);\n }\n context.fillStyle = theme.textColor; context.fillText(\"任务\", 16, layout.headerHeight / 2);\n layout.rows.forEach(({ task, y }) => {\n context.strokeStyle = theme.gridColor; context.beginPath(); context.moveTo(0, y); context.lineTo(layout.totalWidth, y); context.stroke();\n context.fillStyle = theme.textColor; context.fillText(task.title, 16, y + layout.rowHeight / 2);\n const start = toTimestamp(task.start); const end = Math.max(start + dayMs, toTimestamp(task.end));\n const x = xForDate(start, layout); const w = Math.max(8, ((end - start) / dayMs) * layout.dayWidth - 4); const barY = y + 8; const barH = layout.rowHeight - 16;\n context.fillStyle = task.color ?? theme.taskColor; context.beginPath(); context.roundRect(x, barY, w, barH, 4); context.fill();\n if (task.progress) { context.fillStyle = theme.taskProgressColor; context.beginPath(); context.roundRect(x, barY, w * Math.min(100, Math.max(0, task.progress)) / 100, barH, 4); context.fill(); }\n if (selectedId === task.id) { context.strokeStyle = theme.selectionColor; context.lineWidth = 2; context.strokeRect(x - 1, barY - 1, w + 2, barH + 2); context.lineWidth = 1; }\n });\n const rowMap = new Map(layout.rows.map((row) => [row.task.id, row]));\n context.strokeStyle = theme.milestoneColor; context.fillStyle = theme.milestoneColor;\n dependencies.forEach((dependency) => { const from = rowMap.get(dependency.from); const to = rowMap.get(dependency.to); if (!from || !to) return; const fromX = xForDate(toTimestamp(from.task.end), layout); const fromY = from.y + layout.rowHeight / 2; const toX = xForDate(toTimestamp(to.task.start), layout); const toY = to.y + layout.rowHeight / 2; context.beginPath(); context.moveTo(fromX, fromY); context.lineTo((fromX + toX) / 2, fromY); context.lineTo((fromX + toX) / 2, toY); context.lineTo(toX, toY); context.stroke(); context.beginPath(); context.moveTo(toX, toY); context.lineTo(toX - 5, toY - 4); context.lineTo(toX - 5, toY + 4); context.closePath(); context.fill(); });\n}\n","import { buildGanttLayout, type GanttLayout } from \"./domain/layout\";\nimport { hitTestTask } from \"./interaction/hit-test\";\nimport { defaultGanttTheme, type GanttOptions, type GanttTask } from \"./schema/options\";\nimport { renderGantt } from \"./rendering/canvas-renderer\";\n\n/** 原生 Canvas 甘特图实例。 */\nexport class Gantt {\n private readonly container: HTMLElement;\n private readonly canvas: HTMLCanvasElement;\n private readonly context: CanvasRenderingContext2D;\n private options: GanttOptions;\n private layout: GanttLayout | null = null;\n private selectedId: string | null = null;\n private scrollLeft = 0;\n private scrollTop = 0;\n private resizeObserver: ResizeObserver | null = null;\n private drag: { task: GanttTask; startX: number; originalStart: number; originalEnd: number } | null = null;\n\n constructor(container: HTMLElement, options: GanttOptions) {\n const context = document.createElement(\"canvas\").getContext(\"2d\");\n if (!context) throw new Error(\"Canvas 2D context is not supported in the current environment.\");\n this.container = container; this.canvas = context.canvas; this.context = context; this.options = options;\n this.canvas.style.display = \"block\"; this.canvas.style.touchAction = \"none\"; this.container.style.overflow = \"hidden\";\n }\n\n /** 挂载并开始渲染。 */\n mount() {\n if (!this.canvas.parentElement) this.container.appendChild(this.canvas);\n this.canvas.addEventListener(\"pointerdown\", this.handlePointerDown); this.canvas.addEventListener(\"pointermove\", this.handlePointerMove); this.canvas.addEventListener(\"pointerup\", this.handlePointerUp); this.canvas.addEventListener(\"wheel\", this.handleWheel, { passive: false });\n this.resizeObserver = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(() => this.render()); this.resizeObserver?.observe(this.container); this.render();\n }\n\n /** 更新配置并重绘。 */\n updateOptions(options: GanttOptions) { this.options = options; this.selectedId = null; this.render(); }\n /** 获取当前选中任务。 */\n getSelectedTask() { return this.options.tasks.find((task) => task.id === this.selectedId) ?? null; }\n /** 销毁实例。 */\n destroy() { this.resizeObserver?.disconnect(); this.resizeObserver = null; this.canvas.remove(); }\n\n private render() {\n const width = this.container.clientWidth || this.options.taskColumnWidth || 800; const height = this.container.clientHeight || 480; const ratio = window.devicePixelRatio || 1;\n this.layout = buildGanttLayout(this.options, width); this.canvas.width = width * ratio; this.canvas.height = height * ratio; this.canvas.style.width = `${width}px`; this.canvas.style.height = `${height}px`; this.context.setTransform(ratio, 0, 0, ratio, -this.scrollLeft * ratio, -this.scrollTop * ratio);\n renderGantt(this.context, this.layout, this.options.dependencies ?? [], this.selectedId, { ...defaultGanttTheme, ...this.options.theme });\n }\n private point(event: PointerEvent) { const rect = this.canvas.getBoundingClientRect(); return { x: event.clientX - rect.left + this.scrollLeft, y: event.clientY - rect.top + this.scrollTop }; }\n private handlePointerDown = (event: PointerEvent) => { if (!this.layout) return; const point = this.point(event); const task = hitTestTask(point.x, point.y, this.layout); if (!task) return; this.selectedId = task.id; this.drag = { task, startX: point.x, originalStart: new Date(task.start).getTime(), originalEnd: new Date(task.end).getTime() }; this.canvas.setPointerCapture(event.pointerId); this.options.onTaskClick?.(task); this.render(); };\n private handlePointerMove = (event: PointerEvent) => { if (!this.drag || !this.layout) return; const delta = Math.round((this.point(event).x - this.drag.startX) / this.layout.dayWidth) * 86400000; this.drag.task.start = new Date(this.drag.originalStart + delta); this.drag.task.end = new Date(this.drag.originalEnd + delta); this.render(); };\n private handlePointerUp = (event: PointerEvent) => { if (!this.drag) return; this.options.onTaskChange?.(this.drag.task); this.drag = null; this.canvas.releasePointerCapture(event.pointerId); };\n private handleWheel = (event: WheelEvent) => { event.preventDefault(); this.scrollLeft = Math.max(0, this.scrollLeft + event.deltaX + (event.shiftKey ? event.deltaY : 0)); this.scrollTop = Math.max(0, this.scrollTop + (event.shiftKey ? 0 : event.deltaY)); this.render(); };\n}\n"]}
@@ -0,0 +1,104 @@
1
+ /** 甘特图缩放级别。 */
2
+ type GanttScale = "day" | "week" | "month";
3
+ /** 甘特任务定义。 */
4
+ interface GanttTask {
5
+ id: string;
6
+ title: string;
7
+ start: Date | string | number;
8
+ end: Date | string | number;
9
+ progress?: number;
10
+ color?: string;
11
+ parentId?: string;
12
+ collapsed?: boolean;
13
+ }
14
+ /** 任务依赖定义。 */
15
+ interface GanttDependency {
16
+ from: string;
17
+ to: string;
18
+ type?: "finish-to-start" | "start-to-start" | "finish-to-finish";
19
+ }
20
+ /** 甘特图主题。 */
21
+ interface GanttTheme {
22
+ backgroundColor: string;
23
+ borderColor: string;
24
+ textColor: string;
25
+ mutedTextColor: string;
26
+ headerBackgroundColor: string;
27
+ weekendBackgroundColor: string;
28
+ gridColor: string;
29
+ taskColor: string;
30
+ taskProgressColor: string;
31
+ milestoneColor: string;
32
+ selectionColor: string;
33
+ fontFamily: string;
34
+ fontSize: number;
35
+ }
36
+ /** 甘特图配置。 */
37
+ interface GanttOptions {
38
+ tasks: GanttTask[];
39
+ dependencies?: GanttDependency[];
40
+ startDate?: Date | string | number;
41
+ endDate?: Date | string | number;
42
+ scale?: GanttScale;
43
+ rowHeight?: number;
44
+ headerHeight?: number;
45
+ taskColumnWidth?: number;
46
+ theme?: Partial<GanttTheme>;
47
+ onTaskClick?: (task: GanttTask) => void;
48
+ onTaskChange?: (task: GanttTask) => void;
49
+ }
50
+ declare const defaultGanttTheme: GanttTheme;
51
+
52
+ /** 原生 Canvas 甘特图实例。 */
53
+ declare class Gantt {
54
+ private readonly container;
55
+ private readonly canvas;
56
+ private readonly context;
57
+ private options;
58
+ private layout;
59
+ private selectedId;
60
+ private scrollLeft;
61
+ private scrollTop;
62
+ private resizeObserver;
63
+ private drag;
64
+ constructor(container: HTMLElement, options: GanttOptions);
65
+ /** 挂载并开始渲染。 */
66
+ mount(): void;
67
+ /** 更新配置并重绘。 */
68
+ updateOptions(options: GanttOptions): void;
69
+ /** 获取当前选中任务。 */
70
+ getSelectedTask(): GanttTask | null;
71
+ /** 销毁实例。 */
72
+ destroy(): void;
73
+ private render;
74
+ private point;
75
+ private handlePointerDown;
76
+ private handlePointerMove;
77
+ private handlePointerUp;
78
+ private handleWheel;
79
+ }
80
+
81
+ interface GanttLayout {
82
+ start: number;
83
+ end: number;
84
+ dayWidth: number;
85
+ rowHeight: number;
86
+ headerHeight: number;
87
+ taskColumnWidth: number;
88
+ rows: Array<{
89
+ task: GanttTask;
90
+ index: number;
91
+ y: number;
92
+ }>;
93
+ totalWidth: number;
94
+ totalHeight: number;
95
+ }
96
+ declare function resolveDayWidth(scale: GanttScale): 42 | 28 | 80;
97
+ declare function buildGanttLayout(options: GanttOptions, width: number): GanttLayout;
98
+ declare function xForDate(timestamp: number, layout: GanttLayout): number;
99
+
100
+ declare function hitTestTask(x: number, y: number, layout: GanttLayout): GanttTask | null;
101
+
102
+ declare function renderGantt(context: CanvasRenderingContext2D, layout: GanttLayout, dependencies: GanttDependency[], selectedId: string | null, theme: GanttTheme): void;
103
+
104
+ export { Gantt, type GanttDependency, type GanttLayout, type GanttOptions, type GanttScale, type GanttTask, type GanttTheme, buildGanttLayout, defaultGanttTheme, hitTestTask, renderGantt, resolveDayWidth, xForDate };
@@ -0,0 +1,104 @@
1
+ /** 甘特图缩放级别。 */
2
+ type GanttScale = "day" | "week" | "month";
3
+ /** 甘特任务定义。 */
4
+ interface GanttTask {
5
+ id: string;
6
+ title: string;
7
+ start: Date | string | number;
8
+ end: Date | string | number;
9
+ progress?: number;
10
+ color?: string;
11
+ parentId?: string;
12
+ collapsed?: boolean;
13
+ }
14
+ /** 任务依赖定义。 */
15
+ interface GanttDependency {
16
+ from: string;
17
+ to: string;
18
+ type?: "finish-to-start" | "start-to-start" | "finish-to-finish";
19
+ }
20
+ /** 甘特图主题。 */
21
+ interface GanttTheme {
22
+ backgroundColor: string;
23
+ borderColor: string;
24
+ textColor: string;
25
+ mutedTextColor: string;
26
+ headerBackgroundColor: string;
27
+ weekendBackgroundColor: string;
28
+ gridColor: string;
29
+ taskColor: string;
30
+ taskProgressColor: string;
31
+ milestoneColor: string;
32
+ selectionColor: string;
33
+ fontFamily: string;
34
+ fontSize: number;
35
+ }
36
+ /** 甘特图配置。 */
37
+ interface GanttOptions {
38
+ tasks: GanttTask[];
39
+ dependencies?: GanttDependency[];
40
+ startDate?: Date | string | number;
41
+ endDate?: Date | string | number;
42
+ scale?: GanttScale;
43
+ rowHeight?: number;
44
+ headerHeight?: number;
45
+ taskColumnWidth?: number;
46
+ theme?: Partial<GanttTheme>;
47
+ onTaskClick?: (task: GanttTask) => void;
48
+ onTaskChange?: (task: GanttTask) => void;
49
+ }
50
+ declare const defaultGanttTheme: GanttTheme;
51
+
52
+ /** 原生 Canvas 甘特图实例。 */
53
+ declare class Gantt {
54
+ private readonly container;
55
+ private readonly canvas;
56
+ private readonly context;
57
+ private options;
58
+ private layout;
59
+ private selectedId;
60
+ private scrollLeft;
61
+ private scrollTop;
62
+ private resizeObserver;
63
+ private drag;
64
+ constructor(container: HTMLElement, options: GanttOptions);
65
+ /** 挂载并开始渲染。 */
66
+ mount(): void;
67
+ /** 更新配置并重绘。 */
68
+ updateOptions(options: GanttOptions): void;
69
+ /** 获取当前选中任务。 */
70
+ getSelectedTask(): GanttTask | null;
71
+ /** 销毁实例。 */
72
+ destroy(): void;
73
+ private render;
74
+ private point;
75
+ private handlePointerDown;
76
+ private handlePointerMove;
77
+ private handlePointerUp;
78
+ private handleWheel;
79
+ }
80
+
81
+ interface GanttLayout {
82
+ start: number;
83
+ end: number;
84
+ dayWidth: number;
85
+ rowHeight: number;
86
+ headerHeight: number;
87
+ taskColumnWidth: number;
88
+ rows: Array<{
89
+ task: GanttTask;
90
+ index: number;
91
+ y: number;
92
+ }>;
93
+ totalWidth: number;
94
+ totalHeight: number;
95
+ }
96
+ declare function resolveDayWidth(scale: GanttScale): 42 | 28 | 80;
97
+ declare function buildGanttLayout(options: GanttOptions, width: number): GanttLayout;
98
+ declare function xForDate(timestamp: number, layout: GanttLayout): number;
99
+
100
+ declare function hitTestTask(x: number, y: number, layout: GanttLayout): GanttTask | null;
101
+
102
+ declare function renderGantt(context: CanvasRenderingContext2D, layout: GanttLayout, dependencies: GanttDependency[], selectedId: string | null, theme: GanttTheme): void;
103
+
104
+ export { Gantt, type GanttDependency, type GanttLayout, type GanttOptions, type GanttScale, type GanttTask, type GanttTheme, buildGanttLayout, defaultGanttTheme, hitTestTask, renderGantt, resolveDayWidth, xForDate };
package/dist/index.js ADDED
@@ -0,0 +1,254 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/domain/date.ts
6
+ var toTimestamp = (value) => {
7
+ const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();
8
+ if (!Number.isFinite(timestamp)) throw new Error(`Invalid date value: ${String(value)}`);
9
+ return timestamp;
10
+ };
11
+ var dayMs = 24 * 60 * 60 * 1e3;
12
+ function startOfDay(timestamp) {
13
+ const date = new Date(timestamp);
14
+ date.setHours(0, 0, 0, 0);
15
+ return date.getTime();
16
+ }
17
+ function formatDate(timestamp) {
18
+ return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit" }).format(new Date(timestamp));
19
+ }
20
+
21
+ // src/domain/layout.ts
22
+ function resolveDayWidth(scale) {
23
+ return scale === "month" ? 42 : scale === "week" ? 28 : 80;
24
+ }
25
+ function buildGanttLayout(options, width) {
26
+ const timestamps = options.tasks.flatMap((task) => [toTimestamp(task.start), toTimestamp(task.end)]);
27
+ const start = startOfDay(toTimestamp(options.startDate ?? Math.min(...timestamps, Date.now())));
28
+ const end = startOfDay(toTimestamp(options.endDate ?? Math.max(...timestamps, Date.now()) + dayMs));
29
+ const dayWidth = resolveDayWidth(options.scale ?? "day");
30
+ const days = Math.max(1, Math.ceil((end - start) / dayMs));
31
+ const taskColumnWidth = options.taskColumnWidth ?? 220;
32
+ const rows = options.tasks.map((task, index) => ({ task, index, y: (options.headerHeight ?? 56) + index * (options.rowHeight ?? 36) }));
33
+ return { start, end, dayWidth, rowHeight: options.rowHeight ?? 36, headerHeight: options.headerHeight ?? 56, taskColumnWidth, rows, totalWidth: taskColumnWidth + days * dayWidth, totalHeight: (options.headerHeight ?? 56) + options.tasks.length * (options.rowHeight ?? 36) };
34
+ }
35
+ function xForDate(timestamp, layout) {
36
+ return layout.taskColumnWidth + (timestamp - layout.start) / dayMs * layout.dayWidth;
37
+ }
38
+
39
+ // src/interaction/hit-test.ts
40
+ function hitTestTask(x, y, layout) {
41
+ const row = layout.rows.find((item) => y >= item.y && y < item.y + layout.rowHeight);
42
+ if (!row) return null;
43
+ const start = xForDate(toTimestamp(row.task.start), layout);
44
+ const end = xForDate(toTimestamp(row.task.end), layout);
45
+ return x >= start && x <= end ? row.task : null;
46
+ }
47
+
48
+ // src/schema/options.ts
49
+ var defaultGanttTheme = {
50
+ backgroundColor: "#ffffff",
51
+ borderColor: "#d9e1ec",
52
+ textColor: "#1f2329",
53
+ mutedTextColor: "#7a8798",
54
+ headerBackgroundColor: "#f5f8fc",
55
+ weekendBackgroundColor: "#fafafa",
56
+ gridColor: "#e7edf5",
57
+ taskColor: "#1677ff",
58
+ taskProgressColor: "#0958d9",
59
+ milestoneColor: "#722ed1",
60
+ selectionColor: "#91caff",
61
+ fontFamily: '"Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
62
+ fontSize: 14
63
+ };
64
+
65
+ // src/rendering/canvas-renderer.ts
66
+ function renderGantt(context, layout, dependencies, selectedId, theme) {
67
+ context.clearRect(0, 0, context.canvas.width, context.canvas.height);
68
+ context.fillStyle = theme.backgroundColor;
69
+ context.fillRect(0, 0, context.canvas.width, context.canvas.height);
70
+ context.font = `${theme.fontSize}px ${theme.fontFamily}`;
71
+ context.textBaseline = "middle";
72
+ context.fillStyle = theme.headerBackgroundColor;
73
+ context.fillRect(0, 0, layout.totalWidth, layout.headerHeight);
74
+ context.strokeStyle = theme.borderColor;
75
+ context.beginPath();
76
+ context.moveTo(layout.taskColumnWidth, 0);
77
+ context.lineTo(layout.taskColumnWidth, layout.totalHeight);
78
+ context.stroke();
79
+ for (let t = layout.start, day = 0; t < layout.end; t += dayMs, day++) {
80
+ const x = layout.taskColumnWidth + day * layout.dayWidth;
81
+ const date = new Date(t);
82
+ const weekend = date.getDay() === 0 || date.getDay() === 6;
83
+ if (weekend) {
84
+ context.fillStyle = theme.weekendBackgroundColor;
85
+ context.fillRect(x, layout.headerHeight, layout.dayWidth, layout.totalHeight - layout.headerHeight);
86
+ }
87
+ context.strokeStyle = theme.gridColor;
88
+ context.beginPath();
89
+ context.moveTo(x, 0);
90
+ context.lineTo(x, layout.totalHeight);
91
+ context.stroke();
92
+ context.fillStyle = theme.mutedTextColor;
93
+ context.fillText(formatDate(t), x + 6, layout.headerHeight / 2);
94
+ }
95
+ context.fillStyle = theme.textColor;
96
+ context.fillText("\u4EFB\u52A1", 16, layout.headerHeight / 2);
97
+ layout.rows.forEach(({ task, y }) => {
98
+ context.strokeStyle = theme.gridColor;
99
+ context.beginPath();
100
+ context.moveTo(0, y);
101
+ context.lineTo(layout.totalWidth, y);
102
+ context.stroke();
103
+ context.fillStyle = theme.textColor;
104
+ context.fillText(task.title, 16, y + layout.rowHeight / 2);
105
+ const start = toTimestamp(task.start);
106
+ const end = Math.max(start + dayMs, toTimestamp(task.end));
107
+ const x = xForDate(start, layout);
108
+ const w = Math.max(8, (end - start) / dayMs * layout.dayWidth - 4);
109
+ const barY = y + 8;
110
+ const barH = layout.rowHeight - 16;
111
+ context.fillStyle = task.color ?? theme.taskColor;
112
+ context.beginPath();
113
+ context.roundRect(x, barY, w, barH, 4);
114
+ context.fill();
115
+ if (task.progress) {
116
+ context.fillStyle = theme.taskProgressColor;
117
+ context.beginPath();
118
+ context.roundRect(x, barY, w * Math.min(100, Math.max(0, task.progress)) / 100, barH, 4);
119
+ context.fill();
120
+ }
121
+ if (selectedId === task.id) {
122
+ context.strokeStyle = theme.selectionColor;
123
+ context.lineWidth = 2;
124
+ context.strokeRect(x - 1, barY - 1, w + 2, barH + 2);
125
+ context.lineWidth = 1;
126
+ }
127
+ });
128
+ const rowMap = new Map(layout.rows.map((row) => [row.task.id, row]));
129
+ context.strokeStyle = theme.milestoneColor;
130
+ context.fillStyle = theme.milestoneColor;
131
+ dependencies.forEach((dependency) => {
132
+ const from = rowMap.get(dependency.from);
133
+ const to = rowMap.get(dependency.to);
134
+ if (!from || !to) return;
135
+ const fromX = xForDate(toTimestamp(from.task.end), layout);
136
+ const fromY = from.y + layout.rowHeight / 2;
137
+ const toX = xForDate(toTimestamp(to.task.start), layout);
138
+ const toY = to.y + layout.rowHeight / 2;
139
+ context.beginPath();
140
+ context.moveTo(fromX, fromY);
141
+ context.lineTo((fromX + toX) / 2, fromY);
142
+ context.lineTo((fromX + toX) / 2, toY);
143
+ context.lineTo(toX, toY);
144
+ context.stroke();
145
+ context.beginPath();
146
+ context.moveTo(toX, toY);
147
+ context.lineTo(toX - 5, toY - 4);
148
+ context.lineTo(toX - 5, toY + 4);
149
+ context.closePath();
150
+ context.fill();
151
+ });
152
+ }
153
+
154
+ // src/gantt.ts
155
+ var Gantt = class {
156
+ constructor(container, options) {
157
+ __publicField(this, "container");
158
+ __publicField(this, "canvas");
159
+ __publicField(this, "context");
160
+ __publicField(this, "options");
161
+ __publicField(this, "layout", null);
162
+ __publicField(this, "selectedId", null);
163
+ __publicField(this, "scrollLeft", 0);
164
+ __publicField(this, "scrollTop", 0);
165
+ __publicField(this, "resizeObserver", null);
166
+ __publicField(this, "drag", null);
167
+ __publicField(this, "handlePointerDown", (event) => {
168
+ if (!this.layout) return;
169
+ const point = this.point(event);
170
+ const task = hitTestTask(point.x, point.y, this.layout);
171
+ if (!task) return;
172
+ this.selectedId = task.id;
173
+ this.drag = { task, startX: point.x, originalStart: new Date(task.start).getTime(), originalEnd: new Date(task.end).getTime() };
174
+ this.canvas.setPointerCapture(event.pointerId);
175
+ this.options.onTaskClick?.(task);
176
+ this.render();
177
+ });
178
+ __publicField(this, "handlePointerMove", (event) => {
179
+ if (!this.drag || !this.layout) return;
180
+ const delta = Math.round((this.point(event).x - this.drag.startX) / this.layout.dayWidth) * 864e5;
181
+ this.drag.task.start = new Date(this.drag.originalStart + delta);
182
+ this.drag.task.end = new Date(this.drag.originalEnd + delta);
183
+ this.render();
184
+ });
185
+ __publicField(this, "handlePointerUp", (event) => {
186
+ if (!this.drag) return;
187
+ this.options.onTaskChange?.(this.drag.task);
188
+ this.drag = null;
189
+ this.canvas.releasePointerCapture(event.pointerId);
190
+ });
191
+ __publicField(this, "handleWheel", (event) => {
192
+ event.preventDefault();
193
+ this.scrollLeft = Math.max(0, this.scrollLeft + event.deltaX + (event.shiftKey ? event.deltaY : 0));
194
+ this.scrollTop = Math.max(0, this.scrollTop + (event.shiftKey ? 0 : event.deltaY));
195
+ this.render();
196
+ });
197
+ const context = document.createElement("canvas").getContext("2d");
198
+ if (!context) throw new Error("Canvas 2D context is not supported in the current environment.");
199
+ this.container = container;
200
+ this.canvas = context.canvas;
201
+ this.context = context;
202
+ this.options = options;
203
+ this.canvas.style.display = "block";
204
+ this.canvas.style.touchAction = "none";
205
+ this.container.style.overflow = "hidden";
206
+ }
207
+ /** 挂载并开始渲染。 */
208
+ mount() {
209
+ if (!this.canvas.parentElement) this.container.appendChild(this.canvas);
210
+ this.canvas.addEventListener("pointerdown", this.handlePointerDown);
211
+ this.canvas.addEventListener("pointermove", this.handlePointerMove);
212
+ this.canvas.addEventListener("pointerup", this.handlePointerUp);
213
+ this.canvas.addEventListener("wheel", this.handleWheel, { passive: false });
214
+ this.resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => this.render());
215
+ this.resizeObserver?.observe(this.container);
216
+ this.render();
217
+ }
218
+ /** 更新配置并重绘。 */
219
+ updateOptions(options) {
220
+ this.options = options;
221
+ this.selectedId = null;
222
+ this.render();
223
+ }
224
+ /** 获取当前选中任务。 */
225
+ getSelectedTask() {
226
+ return this.options.tasks.find((task) => task.id === this.selectedId) ?? null;
227
+ }
228
+ /** 销毁实例。 */
229
+ destroy() {
230
+ this.resizeObserver?.disconnect();
231
+ this.resizeObserver = null;
232
+ this.canvas.remove();
233
+ }
234
+ render() {
235
+ const width = this.container.clientWidth || this.options.taskColumnWidth || 800;
236
+ const height = this.container.clientHeight || 480;
237
+ const ratio = window.devicePixelRatio || 1;
238
+ this.layout = buildGanttLayout(this.options);
239
+ this.canvas.width = width * ratio;
240
+ this.canvas.height = height * ratio;
241
+ this.canvas.style.width = `${width}px`;
242
+ this.canvas.style.height = `${height}px`;
243
+ this.context.setTransform(ratio, 0, 0, ratio, -this.scrollLeft * ratio, -this.scrollTop * ratio);
244
+ renderGantt(this.context, this.layout, this.options.dependencies ?? [], this.selectedId, { ...defaultGanttTheme, ...this.options.theme });
245
+ }
246
+ point(event) {
247
+ const rect = this.canvas.getBoundingClientRect();
248
+ return { x: event.clientX - rect.left + this.scrollLeft, y: event.clientY - rect.top + this.scrollTop };
249
+ }
250
+ };
251
+
252
+ export { Gantt, buildGanttLayout, defaultGanttTheme, hitTestTask, renderGantt, resolveDayWidth, xForDate };
253
+ //# sourceMappingURL=index.js.map
254
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/domain/date.ts","../src/domain/layout.ts","../src/interaction/hit-test.ts","../src/schema/options.ts","../src/rendering/canvas-renderer.ts","../src/gantt.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const toTimestamp = (value: Date | string | number) => {\n const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();\n if (!Number.isFinite(timestamp)) throw new Error(`Invalid date value: ${String(value)}`);\n return timestamp;\n};\n\nexport const dayMs = 24 * 60 * 60 * 1000;\n\nexport function startOfDay(timestamp: number) {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\nexport function formatDate(timestamp: number) {\n return new Intl.DateTimeFormat(\"zh-CN\", { month: \"2-digit\", day: \"2-digit\" }).format(new Date(timestamp));\n}\n","import { dayMs, startOfDay, toTimestamp } from \"./date\";\nimport type { GanttOptions, GanttScale, GanttTask } from \"../schema/options\";\n\nexport interface GanttLayout {\n start: number;\n end: number;\n dayWidth: number;\n rowHeight: number;\n headerHeight: number;\n taskColumnWidth: number;\n rows: Array<{ task: GanttTask; index: number; y: number }>;\n totalWidth: number;\n totalHeight: number;\n}\n\nexport function resolveDayWidth(scale: GanttScale) {\n return scale === \"month\" ? 42 : scale === \"week\" ? 28 : 80;\n}\n\nexport function buildGanttLayout(options: GanttOptions, width: number): GanttLayout {\n const timestamps = options.tasks.flatMap((task) => [toTimestamp(task.start), toTimestamp(task.end)]);\n const start = startOfDay(toTimestamp(options.startDate ?? Math.min(...timestamps, Date.now())));\n const end = startOfDay(toTimestamp(options.endDate ?? Math.max(...timestamps, Date.now()) + dayMs));\n const dayWidth = resolveDayWidth(options.scale ?? \"day\");\n const days = Math.max(1, Math.ceil((end - start) / dayMs));\n const taskColumnWidth = options.taskColumnWidth ?? 220;\n const rows = options.tasks.map((task, index) => ({ task, index, y: (options.headerHeight ?? 56) + index * (options.rowHeight ?? 36) }));\n return { start, end, dayWidth, rowHeight: options.rowHeight ?? 36, headerHeight: options.headerHeight ?? 56, taskColumnWidth, rows, totalWidth: taskColumnWidth + days * dayWidth, totalHeight: (options.headerHeight ?? 56) + options.tasks.length * (options.rowHeight ?? 36) };\n}\n\nexport function xForDate(timestamp: number, layout: GanttLayout) {\n return layout.taskColumnWidth + ((timestamp - layout.start) / dayMs) * layout.dayWidth;\n}\n","import { toTimestamp } from \"../domain/date\";\nimport { xForDate, type GanttLayout } from \"../domain/layout\";\nimport type { GanttTask } from \"../schema/options\";\n\nexport function hitTestTask(x: number, y: number, layout: GanttLayout): GanttTask | null {\n const row = layout.rows.find((item) => y >= item.y && y < item.y + layout.rowHeight);\n if (!row) return null;\n const start = xForDate(toTimestamp(row.task.start), layout); const end = xForDate(toTimestamp(row.task.end), layout);\n return x >= start && x <= end ? row.task : null;\n}\n","/** 甘特图缩放级别。 */\nexport type GanttScale = \"day\" | \"week\" | \"month\";\n\n/** 甘特任务定义。 */\nexport interface GanttTask {\n id: string;\n title: string;\n start: Date | string | number;\n end: Date | string | number;\n progress?: number;\n color?: string;\n parentId?: string;\n collapsed?: boolean;\n}\n\n/** 任务依赖定义。 */\nexport interface GanttDependency {\n from: string;\n to: string;\n type?: \"finish-to-start\" | \"start-to-start\" | \"finish-to-finish\";\n}\n\n/** 甘特图主题。 */\nexport interface GanttTheme {\n backgroundColor: string;\n borderColor: string;\n textColor: string;\n mutedTextColor: string;\n headerBackgroundColor: string;\n weekendBackgroundColor: string;\n gridColor: string;\n taskColor: string;\n taskProgressColor: string;\n milestoneColor: string;\n selectionColor: string;\n fontFamily: string;\n fontSize: number;\n}\n\n/** 甘特图配置。 */\nexport interface GanttOptions {\n tasks: GanttTask[];\n dependencies?: GanttDependency[];\n startDate?: Date | string | number;\n endDate?: Date | string | number;\n scale?: GanttScale;\n rowHeight?: number;\n headerHeight?: number;\n taskColumnWidth?: number;\n theme?: Partial<GanttTheme>;\n onTaskClick?: (task: GanttTask) => void;\n onTaskChange?: (task: GanttTask) => void;\n}\n\nexport const defaultGanttTheme: GanttTheme = {\n backgroundColor: \"#ffffff\",\n borderColor: \"#d9e1ec\",\n textColor: \"#1f2329\",\n mutedTextColor: \"#7a8798\",\n headerBackgroundColor: \"#f5f8fc\",\n weekendBackgroundColor: \"#fafafa\",\n gridColor: \"#e7edf5\",\n taskColor: \"#1677ff\",\n taskProgressColor: \"#0958d9\",\n milestoneColor: \"#722ed1\",\n selectionColor: \"#91caff\",\n fontFamily: '\"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif',\n fontSize: 14,\n};\n","import { formatDate, dayMs, toTimestamp } from \"../domain/date\";\nimport type { GanttLayout } from \"../domain/layout\";\nimport { xForDate } from \"../domain/layout\";\nimport { defaultGanttTheme, type GanttDependency, type GanttTheme } from \"../schema/options\";\n\nexport function renderGantt(context: CanvasRenderingContext2D, layout: GanttLayout, dependencies: GanttDependency[], selectedId: string | null, theme: GanttTheme) {\n context.clearRect(0, 0, context.canvas.width, context.canvas.height);\n context.fillStyle = theme.backgroundColor; context.fillRect(0, 0, context.canvas.width, context.canvas.height);\n context.font = `${theme.fontSize}px ${theme.fontFamily}`; context.textBaseline = \"middle\";\n context.fillStyle = theme.headerBackgroundColor; context.fillRect(0, 0, layout.totalWidth, layout.headerHeight);\n context.strokeStyle = theme.borderColor; context.beginPath(); context.moveTo(layout.taskColumnWidth, 0); context.lineTo(layout.taskColumnWidth, layout.totalHeight); context.stroke();\n for (let t = layout.start, day = 0; t < layout.end; t += dayMs, day++) {\n const x = layout.taskColumnWidth + day * layout.dayWidth;\n const date = new Date(t); const weekend = date.getDay() === 0 || date.getDay() === 6;\n if (weekend) { context.fillStyle = theme.weekendBackgroundColor; context.fillRect(x, layout.headerHeight, layout.dayWidth, layout.totalHeight - layout.headerHeight); }\n context.strokeStyle = theme.gridColor; context.beginPath(); context.moveTo(x, 0); context.lineTo(x, layout.totalHeight); context.stroke();\n context.fillStyle = theme.mutedTextColor; context.fillText(formatDate(t), x + 6, layout.headerHeight / 2);\n }\n context.fillStyle = theme.textColor; context.fillText(\"任务\", 16, layout.headerHeight / 2);\n layout.rows.forEach(({ task, y }) => {\n context.strokeStyle = theme.gridColor; context.beginPath(); context.moveTo(0, y); context.lineTo(layout.totalWidth, y); context.stroke();\n context.fillStyle = theme.textColor; context.fillText(task.title, 16, y + layout.rowHeight / 2);\n const start = toTimestamp(task.start); const end = Math.max(start + dayMs, toTimestamp(task.end));\n const x = xForDate(start, layout); const w = Math.max(8, ((end - start) / dayMs) * layout.dayWidth - 4); const barY = y + 8; const barH = layout.rowHeight - 16;\n context.fillStyle = task.color ?? theme.taskColor; context.beginPath(); context.roundRect(x, barY, w, barH, 4); context.fill();\n if (task.progress) { context.fillStyle = theme.taskProgressColor; context.beginPath(); context.roundRect(x, barY, w * Math.min(100, Math.max(0, task.progress)) / 100, barH, 4); context.fill(); }\n if (selectedId === task.id) { context.strokeStyle = theme.selectionColor; context.lineWidth = 2; context.strokeRect(x - 1, barY - 1, w + 2, barH + 2); context.lineWidth = 1; }\n });\n const rowMap = new Map(layout.rows.map((row) => [row.task.id, row]));\n context.strokeStyle = theme.milestoneColor; context.fillStyle = theme.milestoneColor;\n dependencies.forEach((dependency) => { const from = rowMap.get(dependency.from); const to = rowMap.get(dependency.to); if (!from || !to) return; const fromX = xForDate(toTimestamp(from.task.end), layout); const fromY = from.y + layout.rowHeight / 2; const toX = xForDate(toTimestamp(to.task.start), layout); const toY = to.y + layout.rowHeight / 2; context.beginPath(); context.moveTo(fromX, fromY); context.lineTo((fromX + toX) / 2, fromY); context.lineTo((fromX + toX) / 2, toY); context.lineTo(toX, toY); context.stroke(); context.beginPath(); context.moveTo(toX, toY); context.lineTo(toX - 5, toY - 4); context.lineTo(toX - 5, toY + 4); context.closePath(); context.fill(); });\n}\n","import { buildGanttLayout, type GanttLayout } from \"./domain/layout\";\nimport { hitTestTask } from \"./interaction/hit-test\";\nimport { defaultGanttTheme, type GanttOptions, type GanttTask } from \"./schema/options\";\nimport { renderGantt } from \"./rendering/canvas-renderer\";\n\n/** 原生 Canvas 甘特图实例。 */\nexport class Gantt {\n private readonly container: HTMLElement;\n private readonly canvas: HTMLCanvasElement;\n private readonly context: CanvasRenderingContext2D;\n private options: GanttOptions;\n private layout: GanttLayout | null = null;\n private selectedId: string | null = null;\n private scrollLeft = 0;\n private scrollTop = 0;\n private resizeObserver: ResizeObserver | null = null;\n private drag: { task: GanttTask; startX: number; originalStart: number; originalEnd: number } | null = null;\n\n constructor(container: HTMLElement, options: GanttOptions) {\n const context = document.createElement(\"canvas\").getContext(\"2d\");\n if (!context) throw new Error(\"Canvas 2D context is not supported in the current environment.\");\n this.container = container; this.canvas = context.canvas; this.context = context; this.options = options;\n this.canvas.style.display = \"block\"; this.canvas.style.touchAction = \"none\"; this.container.style.overflow = \"hidden\";\n }\n\n /** 挂载并开始渲染。 */\n mount() {\n if (!this.canvas.parentElement) this.container.appendChild(this.canvas);\n this.canvas.addEventListener(\"pointerdown\", this.handlePointerDown); this.canvas.addEventListener(\"pointermove\", this.handlePointerMove); this.canvas.addEventListener(\"pointerup\", this.handlePointerUp); this.canvas.addEventListener(\"wheel\", this.handleWheel, { passive: false });\n this.resizeObserver = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(() => this.render()); this.resizeObserver?.observe(this.container); this.render();\n }\n\n /** 更新配置并重绘。 */\n updateOptions(options: GanttOptions) { this.options = options; this.selectedId = null; this.render(); }\n /** 获取当前选中任务。 */\n getSelectedTask() { return this.options.tasks.find((task) => task.id === this.selectedId) ?? null; }\n /** 销毁实例。 */\n destroy() { this.resizeObserver?.disconnect(); this.resizeObserver = null; this.canvas.remove(); }\n\n private render() {\n const width = this.container.clientWidth || this.options.taskColumnWidth || 800; const height = this.container.clientHeight || 480; const ratio = window.devicePixelRatio || 1;\n this.layout = buildGanttLayout(this.options, width); this.canvas.width = width * ratio; this.canvas.height = height * ratio; this.canvas.style.width = `${width}px`; this.canvas.style.height = `${height}px`; this.context.setTransform(ratio, 0, 0, ratio, -this.scrollLeft * ratio, -this.scrollTop * ratio);\n renderGantt(this.context, this.layout, this.options.dependencies ?? [], this.selectedId, { ...defaultGanttTheme, ...this.options.theme });\n }\n private point(event: PointerEvent) { const rect = this.canvas.getBoundingClientRect(); return { x: event.clientX - rect.left + this.scrollLeft, y: event.clientY - rect.top + this.scrollTop }; }\n private handlePointerDown = (event: PointerEvent) => { if (!this.layout) return; const point = this.point(event); const task = hitTestTask(point.x, point.y, this.layout); if (!task) return; this.selectedId = task.id; this.drag = { task, startX: point.x, originalStart: new Date(task.start).getTime(), originalEnd: new Date(task.end).getTime() }; this.canvas.setPointerCapture(event.pointerId); this.options.onTaskClick?.(task); this.render(); };\n private handlePointerMove = (event: PointerEvent) => { if (!this.drag || !this.layout) return; const delta = Math.round((this.point(event).x - this.drag.startX) / this.layout.dayWidth) * 86400000; this.drag.task.start = new Date(this.drag.originalStart + delta); this.drag.task.end = new Date(this.drag.originalEnd + delta); this.render(); };\n private handlePointerUp = (event: PointerEvent) => { if (!this.drag) return; this.options.onTaskChange?.(this.drag.task); this.drag = null; this.canvas.releasePointerCapture(event.pointerId); };\n private handleWheel = (event: WheelEvent) => { event.preventDefault(); this.scrollLeft = Math.max(0, this.scrollLeft + event.deltaX + (event.shiftKey ? event.deltaY : 0)); this.scrollTop = Math.max(0, this.scrollTop + (event.shiftKey ? 0 : event.deltaY)); this.render(); };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@canvas-components/gantt-table",
3
+ "version": "0.1.1",
4
+ "description": "Framework-agnostic Canvas Gantt chart core engine.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "require": "./dist/index.cjs"
19
+ }
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.12.0",
26
+ "pnpm": ">=9.0.0"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup --config tsup.config.ts"
30
+ }
31
+ }