@comitor/ui 3.11.0 → 3.12.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.
Files changed (88) hide show
  1. package/README.md +10 -5
  2. package/dist/chat/chat-composer.d.ts +36 -0
  3. package/dist/chat/chat-composer.js +85 -0
  4. package/dist/chat/chat-composer.js.map +1 -0
  5. package/dist/chat/chat-constants.d.ts +63 -0
  6. package/dist/chat/chat-constants.js +1 -0
  7. package/dist/chat/chat-constants.js.map +1 -0
  8. package/dist/chat/chat-message.d.ts +51 -0
  9. package/dist/chat/chat-message.js +137 -0
  10. package/dist/chat/chat-message.js.map +1 -0
  11. package/dist/chat/chat-sources.d.ts +25 -0
  12. package/dist/chat/chat-sources.js +97 -0
  13. package/dist/chat/chat-sources.js.map +1 -0
  14. package/dist/chat/chat-steps.d.ts +20 -0
  15. package/dist/chat/chat-steps.js +84 -0
  16. package/dist/chat/chat-steps.js.map +1 -0
  17. package/dist/chat/chat-thread.d.ts +21 -0
  18. package/dist/chat/chat-thread.js +87 -0
  19. package/dist/chat/chat-thread.js.map +1 -0
  20. package/dist/chat/index.d.ts +12 -0
  21. package/dist/chat/index.js +14 -0
  22. package/dist/chat/index.js.map +1 -0
  23. package/dist/components/composite/gantt-constants.d.ts +127 -0
  24. package/dist/components/composite/gantt-constants.js +5 -0
  25. package/dist/components/composite/gantt-constants.js.map +1 -0
  26. package/dist/components/composite/gantt-utils.d.ts +106 -0
  27. package/dist/components/composite/gantt-utils.js +196 -0
  28. package/dist/components/composite/gantt-utils.js.map +1 -0
  29. package/dist/components/composite/gantt.d.ts +93 -0
  30. package/dist/components/composite/gantt.js +1137 -0
  31. package/dist/components/composite/gantt.js.map +1 -0
  32. package/dist/components/composite/stat-card.d.ts +1 -1
  33. package/dist/components/composite/tree-view-constants.d.ts +96 -0
  34. package/dist/components/composite/tree-view-constants.js +85 -0
  35. package/dist/components/composite/tree-view-constants.js.map +1 -0
  36. package/dist/components/composite/tree-view.d.ts +58 -0
  37. package/dist/components/composite/tree-view.js +385 -0
  38. package/dist/components/composite/tree-view.js.map +1 -0
  39. package/dist/components/ui/badge.d.ts +1 -1
  40. package/dist/components/ui/button.d.ts +1 -1
  41. package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
  42. package/dist/data-grid/data-grid-cell-wrapper.js +11 -5
  43. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  44. package/dist/data-grid/data-grid-column-header.js +12 -12
  45. package/dist/data-grid/data-grid-column-header.js.map +1 -1
  46. package/dist/data-grid/messages-en.d.ts +2 -0
  47. package/dist/data-grid/messages-en.js +3 -1
  48. package/dist/data-grid/messages-en.js.map +1 -1
  49. package/dist/data-grid/messages-vi.js +2 -1
  50. package/dist/data-grid/messages-vi.js.map +1 -1
  51. package/dist/data-grid/messages.d.ts +2 -0
  52. package/dist/data-grid/use-data-grid.d.ts +1 -0
  53. package/dist/data-grid/utils.d.ts +13 -1
  54. package/dist/data-grid/utils.js +13 -0
  55. package/dist/data-grid/utils.js.map +1 -1
  56. package/dist/editor/bubble-toolbar.d.ts +1 -1
  57. package/dist/editor/editor-core.d.ts +2 -2
  58. package/dist/editor/editor-core.js +6 -0
  59. package/dist/editor/editor-core.js.map +1 -1
  60. package/dist/editor/editor-types.d.ts +1 -1
  61. package/dist/editor/fixed-toolbar.d.ts +1 -1
  62. package/dist/editor/image-upload.d.ts +1 -1
  63. package/dist/editor/index.d.ts +1 -1
  64. package/dist/editor/markdown-editor.d.ts +1 -1
  65. package/dist/editor/markdown-extensions.d.ts +3 -1
  66. package/dist/editor/markdown-extensions.js +3 -1
  67. package/dist/editor/markdown-extensions.js.map +1 -1
  68. package/dist/editor/rich-text-editor.d.ts +1 -1
  69. package/dist/editor/slash-commands.d.ts +1 -1
  70. package/dist/editor/toolbar-actions.d.ts +1 -1
  71. package/dist/{editor-types-CSrMeuSw.d.ts → editor-types-BacXD2VN.d.ts} +7 -1
  72. package/dist/i18n/locale-provider.d.ts +3 -0
  73. package/dist/i18n/messages.d.ts +6 -0
  74. package/dist/i18n/messages.js +11 -2
  75. package/dist/i18n/messages.js.map +1 -1
  76. package/dist/i18n/namespaces/chat.d.ts +8 -0
  77. package/dist/i18n/namespaces/chat.js +47 -0
  78. package/dist/i18n/namespaces/chat.js.map +1 -0
  79. package/dist/i18n/namespaces/gantt.d.ts +10 -0
  80. package/dist/i18n/namespaces/gantt.js +57 -0
  81. package/dist/i18n/namespaces/gantt.js.map +1 -0
  82. package/dist/i18n/namespaces/tree-view.d.ts +8 -0
  83. package/dist/i18n/namespaces/tree-view.js +21 -0
  84. package/dist/i18n/namespaces/tree-view.js.map +1 -0
  85. package/dist/index.d.ts +5 -0
  86. package/dist/index.js +4 -0
  87. package/dist/index.js.map +1 -1
  88. package/package.json +9 -2
@@ -0,0 +1,196 @@
1
+ const MS_PER_DAY = 864e5;
2
+ function parseDay(value) {
3
+ if (!value) return null;
4
+ const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
5
+ if (!match) return null;
6
+ const year = Number(match[1]);
7
+ const month = Number(match[2]);
8
+ const day = Number(match[3]);
9
+ const ms = Date.UTC(year, month - 1, day);
10
+ const check = new Date(ms);
11
+ if (check.getUTCMonth() !== month - 1 || check.getUTCDate() !== day) return null;
12
+ return Math.round(ms / MS_PER_DAY);
13
+ }
14
+ function formatDay(day) {
15
+ const date = new Date(day * MS_PER_DAY);
16
+ const y = date.getUTCFullYear();
17
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
18
+ const d = String(date.getUTCDate()).padStart(2, "0");
19
+ return `${String(y).padStart(4, "0")}-${m}-${d}`;
20
+ }
21
+ function todayDay() {
22
+ const now = /* @__PURE__ */ new Date();
23
+ return Math.round(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) / MS_PER_DAY);
24
+ }
25
+ function dayToLocalDate(day) {
26
+ const date = new Date(day * MS_PER_DAY);
27
+ return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 12);
28
+ }
29
+ function weekday(day) {
30
+ return ((day + 4) % 7 + 7) % 7;
31
+ }
32
+ function parts(day) {
33
+ const date = new Date(day * MS_PER_DAY);
34
+ return { year: date.getUTCFullYear(), month: date.getUTCMonth(), date: date.getUTCDate() };
35
+ }
36
+ function fromParts(year, month, date) {
37
+ return Math.round(Date.UTC(year, month, date) / MS_PER_DAY);
38
+ }
39
+ function startOfUnit(day, unit, weekStartsOn = 1) {
40
+ switch (unit) {
41
+ case "day":
42
+ return day;
43
+ case "week":
44
+ return day - (weekday(day) - weekStartsOn + 7) % 7;
45
+ case "month": {
46
+ const p = parts(day);
47
+ return fromParts(p.year, p.month, 1);
48
+ }
49
+ case "quarter": {
50
+ const p = parts(day);
51
+ return fromParts(p.year, p.month - p.month % 3, 1);
52
+ }
53
+ case "year":
54
+ return fromParts(parts(day).year, 0, 1);
55
+ }
56
+ }
57
+ function addUnit(day, unit, amount) {
58
+ switch (unit) {
59
+ case "day":
60
+ return day + amount;
61
+ case "week":
62
+ return day + amount * 7;
63
+ case "month":
64
+ case "quarter":
65
+ case "year": {
66
+ const months = unit === "month" ? amount : unit === "quarter" ? amount * 3 : amount * 12;
67
+ const p = parts(day);
68
+ const target = fromParts(p.year, p.month + months, 1);
69
+ const lastDay = parts(fromParts(p.year, p.month + months + 1, 0)).date;
70
+ return target + Math.min(p.date, lastDay) - 1;
71
+ }
72
+ }
73
+ }
74
+ const SCALE_CONFIG = {
75
+ day: { pxPerDay: 36, major: "month", minor: "day", paddingDays: 14, bigStep: "week", shadeDays: true },
76
+ week: { pxPerDay: 14, major: "month", minor: "week", paddingDays: 42, bigStep: "week", shadeDays: true },
77
+ month: { pxPerDay: 4.5, major: "year", minor: "month", paddingDays: 120, bigStep: "month", shadeDays: false },
78
+ quarter: { pxPerDay: 1.6, major: "year", minor: "quarter", paddingDays: 365, bigStep: "month", shadeDays: false },
79
+ year: { pxPerDay: 0.5, major: null, minor: "year", paddingDays: 730, bigStep: "quarter", shadeDays: false }
80
+ };
81
+ function ticksBetween(from, to, unit, weekStartsOn = 1) {
82
+ const ticks = [];
83
+ let day = startOfUnit(from, unit, weekStartsOn);
84
+ for (let guard = 0; day < to && guard < 2e4; guard++) {
85
+ const next = addUnit(day, unit, 1);
86
+ ticks.push({ day, next });
87
+ day = next;
88
+ }
89
+ return ticks;
90
+ }
91
+ function computeRange(days, today, scale, weekStartsOn = 1, override) {
92
+ const config = SCALE_CONFIG[scale];
93
+ const align = config.major ?? config.minor;
94
+ if (override?.start != null && override.end != null && override.end >= override.start) {
95
+ return {
96
+ start: startOfUnit(override.start, config.minor, weekStartsOn),
97
+ end: addUnit(startOfUnit(override.end, config.minor, weekStartsOn), config.minor, 1)
98
+ };
99
+ }
100
+ let min = today;
101
+ let max = today;
102
+ for (const day of days) {
103
+ if (day < min) min = day;
104
+ if (day > max) max = day;
105
+ }
106
+ const start = startOfUnit(min - config.paddingDays, align, weekStartsOn);
107
+ const end = addUnit(startOfUnit(max + config.paddingDays, align, weekStartsOn), align, 1);
108
+ return { start, end };
109
+ }
110
+ const GAP = 10;
111
+ const RADIUS = 5;
112
+ function routeDependency(from, to, type, rowHeight) {
113
+ const fromFinish = type === "finish-to-start" || type === "finish-to-finish";
114
+ const toStart = type === "finish-to-start" || type === "start-to-start";
115
+ const sx = fromFinish ? from.x2 : from.x1;
116
+ const tx = toStart ? to.x1 : to.x2;
117
+ const exitDir = fromFinish ? 1 : -1;
118
+ const enterDir = toStart ? 1 : -1;
119
+ const sy = from.y;
120
+ const ty = to.y;
121
+ const points = [[sx, sy]];
122
+ if (exitDir !== enterDir) {
123
+ const vx = exitDir > 0 ? Math.max(sx, tx) + GAP : Math.min(sx, tx) - GAP;
124
+ points.push([vx, sy], [vx, ty], [tx, ty]);
125
+ } else if ((tx - sx) * exitDir >= GAP * 2 && sy !== ty) {
126
+ const vx = sx + exitDir * GAP;
127
+ points.push([vx, sy], [vx, ty], [tx, ty]);
128
+ } else if ((tx - sx) * exitDir >= GAP * 2) {
129
+ points.push([tx, ty]);
130
+ } else {
131
+ const p1x = sx + exitDir * GAP;
132
+ const p4x = tx - enterDir * GAP;
133
+ const midY = sy === ty ? sy + rowHeight / 2 : ty > sy ? ty - rowHeight / 2 : ty + rowHeight / 2;
134
+ points.push([p1x, sy], [p1x, midY], [p4x, midY], [p4x, ty], [tx, ty]);
135
+ }
136
+ return roundedPath(points);
137
+ }
138
+ function roundedPath(points) {
139
+ const pts = [];
140
+ for (const point of points) {
141
+ const prev = pts[pts.length - 1];
142
+ if (!prev || Math.abs(point[0] - prev[0]) > 0.01 || Math.abs(point[1] - prev[1]) > 0.01) pts.push(point);
143
+ }
144
+ if (pts.length < 2) return "";
145
+ const r = (n) => Math.round(n * 10) / 10;
146
+ const [x0, y0] = pts[0];
147
+ let d = `M${r(x0)},${r(y0)}`;
148
+ for (let i = 1; i < pts.length - 1; i++) {
149
+ const [px, py] = pts[i - 1];
150
+ const [cx, cy] = pts[i];
151
+ const [nx, ny] = pts[i + 1];
152
+ const inLen = Math.hypot(cx - px, cy - py);
153
+ const outLen = Math.hypot(nx - cx, ny - cy);
154
+ const radius = Math.min(RADIUS, inLen / 2, outLen / 2);
155
+ const ax = cx - (cx - px) / inLen * radius;
156
+ const ay = cy - (cy - py) / inLen * radius;
157
+ const bx = cx + (nx - cx) / outLen * radius;
158
+ const by = cy + (ny - cy) / outLen * radius;
159
+ d += ` L${r(ax)},${r(ay)} Q${r(cx)},${r(cy)} ${r(bx)},${r(by)}`;
160
+ }
161
+ const [xn, yn] = pts[pts.length - 1];
162
+ d += ` L${r(xn)},${r(yn)}`;
163
+ return d;
164
+ }
165
+ function isDependencyViolated(from, to, type) {
166
+ switch (type) {
167
+ case "finish-to-start":
168
+ return to.start <= from.end;
169
+ case "start-to-start":
170
+ return to.start < from.start;
171
+ case "finish-to-finish":
172
+ return to.end < from.end;
173
+ case "start-to-finish":
174
+ return to.end < from.start;
175
+ }
176
+ }
177
+ function shiftByUnit(start, end, unit, direction) {
178
+ const delta = addUnit(start, unit, direction) - start;
179
+ return { start: start + delta, end: end + delta };
180
+ }
181
+ export {
182
+ SCALE_CONFIG,
183
+ addUnit,
184
+ computeRange,
185
+ dayToLocalDate,
186
+ formatDay,
187
+ isDependencyViolated,
188
+ parseDay,
189
+ routeDependency,
190
+ shiftByUnit,
191
+ startOfUnit,
192
+ ticksBetween,
193
+ todayDay,
194
+ weekday
195
+ };
196
+ //# sourceMappingURL=gantt-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/composite/gantt-utils.ts"],"sourcesContent":["/**\n * Phần THUẦN của `Gantt`: toán ngày, thang trục, định tuyến mũi tên phụ thuộc. Không React, không\n * DOM — nên kiểm được bằng Node trần (`scripts/check-gantt.mjs` gọi thẳng file build).\n *\n * KHÔNG nằm trong barrel: đây là nội tạng của component, không phải API.\n *\n * ─ VÌ SAO ĐẾM NGÀY BẰNG SỐ NGUYÊN, KHÔNG BẰNG `Date` ─\n * Mỗi ngày lịch là MỘT số nguyên: số ngày kể từ 1970-01-01 theo UTC. Cộng trừ ngày thành cộng trừ\n * số, không có giờ, không có múi giờ, không có ngày 23/25 tiếng lúc đổi giờ mùa hè. `Date` cục bộ\n * chỉ xuất hiện ở lúc ĐỊNH DẠNG nhãn (xem `dayToLocalDate`).\n */\nimport type { GanttDependencyType, GanttScale } from \"./gantt-constants.js\";\n\nconst MS_PER_DAY = 86_400_000;\n\n/** `\"2026-10-30\"` → số ngày. Chuỗi hỏng → `null` (không ném: dữ liệu bẩn không được làm đổ cả lưới). */\nexport function parseDay(value: string | null | undefined): number | null {\n if (!value) return null;\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(value);\n if (!match) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n const ms = Date.UTC(year, month - 1, day);\n const check = new Date(ms);\n if (check.getUTCMonth() !== month - 1 || check.getUTCDate() !== day) return null;\n return Math.round(ms / MS_PER_DAY);\n}\n\n/** Số ngày → `\"yyyy-MM-dd\"`. */\nexport function formatDay(day: number): string {\n const date = new Date(day * MS_PER_DAY);\n const y = date.getUTCFullYear();\n const m = String(date.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(date.getUTCDate()).padStart(2, \"0\");\n return `${String(y).padStart(4, \"0\")}-${m}-${d}`;\n}\n\n/** Hôm nay theo LỊCH của máy người dùng (không phải theo UTC — 7 giờ sáng ở Hà Nội vẫn là hôm nay). */\nexport function todayDay(): number {\n const now = new Date();\n return Math.round(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) / MS_PER_DAY);\n}\n\n/** `Date` CỤC BỘ lúc 12 giờ trưa của ngày đó — chỉ để đưa cho `Intl.DateTimeFormat`. */\nexport function dayToLocalDate(day: number): Date {\n const date = new Date(day * MS_PER_DAY);\n return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 12);\n}\n\n/** Thứ trong tuần: 0 = Chủ nhật … 6 = Thứ bảy (khớp `Date#getDay`). 1970-01-01 là thứ năm. */\nexport function weekday(day: number): number {\n return (((day + 4) % 7) + 7) % 7;\n}\n\nfunction parts(day: number) {\n const date = new Date(day * MS_PER_DAY);\n return { year: date.getUTCFullYear(), month: date.getUTCMonth(), date: date.getUTCDate() };\n}\n\nfunction fromParts(year: number, month: number, date: number): number {\n return Math.round(Date.UTC(year, month, date) / MS_PER_DAY);\n}\n\nexport type GanttUnit = \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n\n/** Ngày đầu của đơn vị chứa `day`. Tuần bắt đầu ở `weekStartsOn` (1 = thứ hai, theo ISO và lịch Việt). */\nexport function startOfUnit(day: number, unit: GanttUnit, weekStartsOn = 1): number {\n switch (unit) {\n case \"day\":\n return day;\n case \"week\":\n return day - ((weekday(day) - weekStartsOn + 7) % 7);\n case \"month\": {\n const p = parts(day);\n return fromParts(p.year, p.month, 1);\n }\n case \"quarter\": {\n const p = parts(day);\n return fromParts(p.year, p.month - (p.month % 3), 1);\n }\n case \"year\":\n return fromParts(parts(day).year, 0, 1);\n }\n}\n\n/** Cộng `amount` đơn vị. Tháng/quý/năm cộng theo LỊCH (31/1 + 1 tháng = 28 hoặc 29/2, không tràn sang tháng 3). */\nexport function addUnit(day: number, unit: GanttUnit, amount: number): number {\n switch (unit) {\n case \"day\":\n return day + amount;\n case \"week\":\n return day + amount * 7;\n case \"month\":\n case \"quarter\":\n case \"year\": {\n const months = unit === \"month\" ? amount : unit === \"quarter\" ? amount * 3 : amount * 12;\n const p = parts(day);\n const target = fromParts(p.year, p.month + months, 1);\n const lastDay = parts(fromParts(p.year, p.month + months + 1, 0)).date;\n return target + Math.min(p.date, lastDay) - 1;\n }\n }\n}\n\nexport interface ScaleConfig {\n /** Bề rộng một ngày, px. */\n pxPerDay: number;\n /** Đơn vị của hàng trên của đầu trục (`null` = không có hàng trên). */\n major: GanttUnit | null;\n /** Đơn vị của hàng dưới — cũng là bước của vạch lưới. */\n minor: GanttUnit;\n /** Lề thêm hai đầu dải ngày, tính bằng ngày. */\n paddingDays: number;\n /** Bước của Shift+mũi tên. */\n bigStep: GanttUnit;\n /** Tô ngày nghỉ hay không — ở bậc tháng trở lên một ngày < 5px, tô ra chỉ thành vệt nhiễu. */\n shadeDays: boolean;\n}\n\n/**\n * Bề rộng mỗi bậc chọn sao cho ô của hàng dưới ĐỦ chỗ cho nhãn của nó: ngày 36px (số + thứ), tuần\n * 98px (\"26 thg 10\"), tháng ~135px, quý ~146px, năm ~183px. Bộ bậc ngày → năm cùng bộ với Timeline\n * của Linear (docs/gantt.md §1); con số px là của gói, không đo từ Linear.\n */\nexport const SCALE_CONFIG: Record<GanttScale, ScaleConfig> = {\n day: { pxPerDay: 36, major: \"month\", minor: \"day\", paddingDays: 14, bigStep: \"week\", shadeDays: true },\n week: { pxPerDay: 14, major: \"month\", minor: \"week\", paddingDays: 42, bigStep: \"week\", shadeDays: true },\n month: { pxPerDay: 4.5, major: \"year\", minor: \"month\", paddingDays: 120, bigStep: \"month\", shadeDays: false },\n quarter: { pxPerDay: 1.6, major: \"year\", minor: \"quarter\", paddingDays: 365, bigStep: \"month\", shadeDays: false },\n year: { pxPerDay: 0.5, major: null, minor: \"year\", paddingDays: 730, bigStep: \"quarter\", shadeDays: false }\n};\n\nexport interface Tick {\n /** Ngày đầu của ô. */\n day: number;\n /** Ngày đầu của ô KẾ TIẾP (= ngày cuối + 1). */\n next: number;\n}\n\n/** Mọi ô của `unit` giao với `[from, to)`. Ô đầu có thể bắt đầu trước `from`. */\nexport function ticksBetween(from: number, to: number, unit: GanttUnit, weekStartsOn = 1): Tick[] {\n const ticks: Tick[] = [];\n let day = startOfUnit(from, unit, weekStartsOn);\n // Chặn cứng số vòng: dải ngày do dữ liệu quyết định, và một ngày hỏng kiểu năm 9999 ở bậc \"ngày\"\n // không được treo tab.\n for (let guard = 0; day < to && guard < 20_000; guard++) {\n const next = addUnit(day, unit, 1);\n ticks.push({ day, next });\n day = next;\n }\n return ticks;\n}\n\nexport interface DayRange {\n start: number;\n /** Ngày đầu NGOÀI dải (nửa mở). */\n end: number;\n}\n\n/**\n * Dải ngày của trục: bao mọi thanh + hôm nay, nới hai đầu theo bậc, rồi căn về đầu ô của hàng dưới\n * (và của hàng trên, nếu có) để ô đầu/cuối không bị cắt dở.\n */\nexport function computeRange(\n days: number[],\n today: number,\n scale: GanttScale,\n weekStartsOn = 1,\n override?: { start: number | null; end: number | null }\n): DayRange {\n const config = SCALE_CONFIG[scale];\n const align = config.major ?? config.minor;\n if (override?.start != null && override.end != null && override.end >= override.start) {\n return {\n start: startOfUnit(override.start, config.minor, weekStartsOn),\n end: addUnit(startOfUnit(override.end, config.minor, weekStartsOn), config.minor, 1)\n };\n }\n let min = today;\n let max = today;\n for (const day of days) {\n if (day < min) min = day;\n if (day > max) max = day;\n }\n const start = startOfUnit(min - config.paddingDays, align, weekStartsOn);\n const end = addUnit(startOfUnit(max + config.paddingDays, align, weekStartsOn), align, 1);\n return { start, end };\n}\n\n/** Một thanh đã đổi ra toạ độ: `x1`/`x2` là mép trái/phải (px, tính từ đầu trục), `y` là tâm dòng. */\nexport interface BarGeometry {\n x1: number;\n x2: number;\n y: number;\n}\n\nconst GAP = 10;\nconst RADIUS = 5;\n\n/**\n * Đường đi của một mũi tên phụ thuộc — các đoạn ngang/dọc, góc bo tròn.\n *\n * Mũi tên RỜI thanh trước ở mép ứng với kiểu quan hệ (`finish` = mép phải, `start` = mép trái) và\n * CHẠM thanh sau cũng vậy. Khi thanh sau nằm đủ xa về phía trước: rời mép, đi ngang một đoạn, đổ dọc\n * xuống dòng đích, rẽ vào. Khi không đủ chỗ (thanh sau bắt đầu trước khi thanh trước xong — chính là\n * ca VI PHẠM): vòng qua khe giữa hai dòng rồi mới quay lại, để mũi tên không cắt ngang qua thanh.\n */\nexport function routeDependency(\n from: BarGeometry,\n to: BarGeometry,\n type: GanttDependencyType,\n rowHeight: number\n): string {\n const fromFinish = type === \"finish-to-start\" || type === \"finish-to-finish\";\n const toStart = type === \"finish-to-start\" || type === \"start-to-start\";\n const sx = fromFinish ? from.x2 : from.x1;\n const tx = toStart ? to.x1 : to.x2;\n const exitDir = fromFinish ? 1 : -1;\n // Hướng ĐI của mũi tên lúc chạm đích: chạm mép trái thì đang đi sang phải, và ngược lại.\n const enterDir = toStart ? 1 : -1;\n const sy = from.y;\n const ty = to.y;\n const points: Array<[number, number]> = [[sx, sy]];\n\n if (exitDir !== enterDir) {\n // start→start / finish→finish: cùng ra và vào một phía — đi thẳng ra mép ngoài cùng rồi đổ dọc.\n const vx = exitDir > 0 ? Math.max(sx, tx) + GAP : Math.min(sx, tx) - GAP;\n points.push([vx, sy], [vx, ty], [tx, ty]);\n } else if ((tx - sx) * exitDir >= GAP * 2 && sy !== ty) {\n const vx = sx + exitDir * GAP;\n points.push([vx, sy], [vx, ty], [tx, ty]);\n } else if ((tx - sx) * exitDir >= GAP * 2) {\n points.push([tx, ty]);\n } else {\n // Không đủ chỗ: đi ra, vòng qua khe giữa hai dòng, đi lùi, rồi vào.\n const p1x = sx + exitDir * GAP;\n const p4x = tx - enterDir * GAP;\n const midY = sy === ty ? sy + rowHeight / 2 : ty > sy ? ty - rowHeight / 2 : ty + rowHeight / 2;\n points.push([p1x, sy], [p1x, midY], [p4x, midY], [p4x, ty], [tx, ty]);\n }\n return roundedPath(points);\n}\n\n/** Polyline → `d` của SVG với góc bo `RADIUS` (bo nhỏ lại khi đoạn ngắn hơn hai lần bán kính). */\nfunction roundedPath(points: Array<[number, number]>): string {\n const pts: Array<[number, number]> = [];\n for (const point of points) {\n const prev = pts[pts.length - 1];\n if (!prev || Math.abs(point[0] - prev[0]) > 0.01 || Math.abs(point[1] - prev[1]) > 0.01) pts.push(point);\n }\n if (pts.length < 2) return \"\";\n const r = (n: number) => Math.round(n * 10) / 10;\n const [x0, y0] = pts[0]!;\n let d = `M${r(x0)},${r(y0)}`;\n for (let i = 1; i < pts.length - 1; i++) {\n const [px, py] = pts[i - 1]!;\n const [cx, cy] = pts[i]!;\n const [nx, ny] = pts[i + 1]!;\n const inLen = Math.hypot(cx - px, cy - py);\n const outLen = Math.hypot(nx - cx, ny - cy);\n const radius = Math.min(RADIUS, inLen / 2, outLen / 2);\n const ax = cx - ((cx - px) / inLen) * radius;\n const ay = cy - ((cy - py) / inLen) * radius;\n const bx = cx + ((nx - cx) / outLen) * radius;\n const by = cy + ((ny - cy) / outLen) * radius;\n d += ` L${r(ax)},${r(ay)} Q${r(cx)},${r(cy)} ${r(bx)},${r(by)}`;\n }\n const [xn, yn] = pts[pts.length - 1]!;\n d += ` L${r(xn)},${r(yn)}`;\n return d;\n}\n\n/**\n * Phụ thuộc có đang bị vi phạm không, theo ngày (cả hai đầu BAO GỒM):\n * finish-to-start — việc sau phải bắt đầu SAU ngày việc trước xong;\n * start-to-start — việc sau không bắt đầu trước việc trước;\n * finish-to-finish — việc sau không xong trước việc trước;\n * start-to-finish — việc sau không xong trước khi việc trước bắt đầu.\n */\nexport function isDependencyViolated(\n from: { start: number; end: number },\n to: { start: number; end: number },\n type: GanttDependencyType\n): boolean {\n switch (type) {\n case \"finish-to-start\":\n return to.start <= from.end;\n case \"start-to-start\":\n return to.start < from.start;\n case \"finish-to-finish\":\n return to.end < from.end;\n case \"start-to-finish\":\n return to.end < from.start;\n }\n}\n\n/** Dịch cả dải `[start, end]` đi một BƯỚC theo đơn vị, giữ nguyên độ dài theo ngày. */\nexport function shiftByUnit(start: number, end: number, unit: GanttUnit, direction: 1 | -1) {\n const delta = addUnit(start, unit, direction) - start;\n return { start: start + delta, end: end + delta };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,93 @@
1
+ import * as React from 'react';
2
+ import { GanttItem, GanttDependency, GanttScale, GanttItemChange, GanttDependencyDraft, GanttLabels } from './gantt-constants.js';
3
+
4
+ interface GanttProps extends Omit<React.ComponentProps<"div">, "children" | "onChange"> {
5
+ /**
6
+ * Các dòng, ĐÚNG thứ tự hiển thị. Gantt không tự sắp, không tự ẩn con của nhánh đang thu: app giữ
7
+ * cây, lọc theo `expanded`, rồi đưa danh sách phẳng vào đây (xem `onExpandedChange`).
8
+ */
9
+ items: GanttItem[];
10
+ /** Mũi tên phụ thuộc. Phụ thuộc trỏ tới việc không có trong `items` (hoặc chưa có ngày) thì bỏ qua. */
11
+ dependencies?: GanttDependency[];
12
+ /** Bậc phóng (controlled). Bỏ trống thì Gantt tự giữ, bắt đầu từ `defaultScale`. */
13
+ scale?: GanttScale;
14
+ /** Bậc phóng ban đầu khi không controlled (mặc định `week`). */
15
+ defaultScale?: GanttScale;
16
+ onScaleChange?: (scale: GanttScale) => void;
17
+ /** Hôm nay `yyyy-MM-dd` (mặc định: theo đồng hồ máy người dùng). Truyền khi cần khớp giờ server. */
18
+ today?: string;
19
+ /** Các thứ nghỉ trong tuần, 0 = Chủ nhật … 6 = Thứ bảy (mặc định `[0, 6]`). */
20
+ weekendDays?: number[];
21
+ /** Ngày nghỉ lễ / nghỉ bù của workspace, `yyyy-MM-dd`. Tô như cuối tuần. */
22
+ nonWorkingDays?: string[];
23
+ /** Ngày đầu tuần: 1 = thứ hai (mặc định, ISO), 0 = chủ nhật. */
24
+ weekStartsOn?: 0 | 1;
25
+ /**
26
+ * Dải ngày cố định của trục. Bỏ trống thì trục tự bao mọi thanh + hôm nay, nới thêm hai đầu theo bậc.
27
+ */
28
+ range?: {
29
+ start: string;
30
+ end: string;
31
+ };
32
+ /** Khoá mọi thao tác sửa (kéo, giãn, phím, nối phụ thuộc). Dòng nào cần khoá riêng: `item.readOnly`. */
33
+ readOnly?: boolean;
34
+ /**
35
+ * Người dùng đổi ngày (kéo, giãn, phím mũi tên, bấm xếp lịch). Gantt KHÔNG tự ghi: app cập nhật dữ
36
+ * liệu rồi truyền `items` mới. Không truyền = không sửa được.
37
+ */
38
+ onItemChange?: (change: GanttItemChange) => void;
39
+ /** Mở một mục: Enter trên dòng, bấm đúp thanh, bấm tên ở cột trái. */
40
+ onItemOpen?: (item: GanttItem) => void;
41
+ /** Bấm nút mở/thu (hoặc Space) trên dòng có `hasChildren`. */
42
+ onExpandedChange?: (id: string, expanded: boolean) => void;
43
+ /**
44
+ * Người dùng kéo từ chấm ở đuôi một thanh sang thanh khác. Truyền mới hiện chấm nối. Chỉ có đường
45
+ * CHUỘT/CHẠM — app phải có thêm một lối bàn phím (menu "Thêm phụ thuộc") cho cùng việc.
46
+ */
47
+ onDependencyCreate?: (dependency: GanttDependencyDraft) => void;
48
+ /** Bấm vào một mũi tên — vd mở menu xoá phụ thuộc. */
49
+ onDependencyClick?: (dependency: GanttDependency) => void;
50
+ /**
51
+ * Chiều cao dòng, px. Mặc định 36 — và 44 trên thiết bị chạm (`pointer: coarse`), để thanh và tay
52
+ * giãn đạt vùng chạm 44px.
53
+ */
54
+ rowHeight?: number;
55
+ /** Bề rộng cột trái, px (mặc định 280). */
56
+ sidebarWidth?: number;
57
+ /** Tiêu đề cột trái (mặc định: khoá `sidebarTitle`). */
58
+ sidebarTitle?: React.ReactNode;
59
+ /** Nội dung cột trái của một dòng — thay cho tên trần (vd thêm mã việc, avatar người phụ trách). */
60
+ renderSidebarItem?: (item: GanttItem) => React.ReactNode;
61
+ /**
62
+ * Thanh công cụ phía trên (chọn bậc + nút Hôm nay). `false` = ẩn (app tự dựng bằng `GANTT_SCALES`);
63
+ * truyền một node = giữ thanh mặc định và đặt node đó ở đầu bên phải.
64
+ */
65
+ toolbar?: boolean | React.ReactNode;
66
+ /** Đè từng chữ (namespace `gantt` của `ComitorLocaleProvider`). */
67
+ labels?: Partial<GanttLabels>;
68
+ }
69
+ /**
70
+ * Trục thời gian / Gantt: cột tên bên trái, thanh theo ngày bên phải, mũi tên phụ thuộc, vạch hôm nay,
71
+ * tô ngày nghỉ, làm nổi đường găng. Dùng cho Gantt việc (bậc ngày/tuần) lẫn lộ trình dự án (bậc
72
+ * tháng/quý/năm). Phần lộ trình mượn Timeline dự án của Linear; phần Gantt mức việc Linear KHÔNG có —
73
+ * nó theo quy ước Gantt thông dụng (docs/gantt.md §1).
74
+ *
75
+ * ─ CONTROLLED ─
76
+ * Gantt không giữ dữ liệu nghiệp vụ. Mọi thay đổi ngày đi ra qua `onItemChange`, mọi phụ thuộc mới
77
+ * qua `onDependencyCreate`; app ghi rồi truyền `items`/`dependencies` mới. Trong lúc KÉO, thanh chạy
78
+ * theo một bản nháp nội bộ và chỉ báo ra MỘT lần khi thả — app không nhận 60 lần gọi mỗi giây.
79
+ *
80
+ * ─ BÀN PHÍM ─ (một điểm dừng Tab cho cả lưới — dòng đang chọn)
81
+ * ↑ ↓ Home End PageUp PageDown — chuyển dòng · ← → — dời một ngày · Shift+← → — dời một bước lớn
82
+ * (tuần, hoặc tháng ở bậc tháng trở lên) · Alt+← → — đổi ngày kết thúc · Alt+Shift+← → — đổi ngày
83
+ * bắt đầu · Enter — `onItemOpen` · Space — mở/thu nhánh.
84
+ * Mỗi lần đổi bằng phím là một lần `onItemChange` (app tự gộp nếu cần), kèm lời xướng qua `aria-live`.
85
+ *
86
+ * ─ ẢO HOÁ ─
87
+ * Dòng ảo hoá bằng `@tanstack/react-virtual`; ô của đầu trục, vạch lưới và mũi tên chỉ dựng trong
88
+ * phần đang nhìn thấy — 1.000 dòng × nhiều năm ở bậc ngày vẫn chỉ vài chục dòng trong DOM. Phía
89
+ * server chưa có khung để đo nên trả về đầu trục và 0 dòng (cùng hành vi `DataGrid`).
90
+ */
91
+ declare function Gantt({ items, dependencies, scale: scaleProp, defaultScale, onScaleChange, today: todayProp, weekendDays, nonWorkingDays, weekStartsOn, range: rangeProp, readOnly, onItemChange, onItemOpen, onExpandedChange, onDependencyCreate, onDependencyClick, rowHeight: rowHeightProp, sidebarWidth, sidebarTitle, renderSidebarItem, toolbar, labels, className, "aria-label": ariaLabel, ...props }: GanttProps): React.JSX.Element;
92
+
93
+ export { Gantt, type GanttProps };