@comitor/ui 3.11.1 → 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 (73) 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/editor/bubble-toolbar.d.ts +1 -1
  42. package/dist/editor/editor-core.d.ts +2 -2
  43. package/dist/editor/editor-core.js +6 -0
  44. package/dist/editor/editor-core.js.map +1 -1
  45. package/dist/editor/editor-types.d.ts +1 -1
  46. package/dist/editor/fixed-toolbar.d.ts +1 -1
  47. package/dist/editor/image-upload.d.ts +1 -1
  48. package/dist/editor/index.d.ts +1 -1
  49. package/dist/editor/markdown-editor.d.ts +1 -1
  50. package/dist/editor/markdown-extensions.d.ts +3 -1
  51. package/dist/editor/markdown-extensions.js +3 -1
  52. package/dist/editor/markdown-extensions.js.map +1 -1
  53. package/dist/editor/rich-text-editor.d.ts +1 -1
  54. package/dist/editor/slash-commands.d.ts +1 -1
  55. package/dist/editor/toolbar-actions.d.ts +1 -1
  56. package/dist/{editor-types-CSrMeuSw.d.ts → editor-types-BacXD2VN.d.ts} +7 -1
  57. package/dist/i18n/locale-provider.d.ts +3 -0
  58. package/dist/i18n/messages.d.ts +6 -0
  59. package/dist/i18n/messages.js +11 -2
  60. package/dist/i18n/messages.js.map +1 -1
  61. package/dist/i18n/namespaces/chat.d.ts +8 -0
  62. package/dist/i18n/namespaces/chat.js +47 -0
  63. package/dist/i18n/namespaces/chat.js.map +1 -0
  64. package/dist/i18n/namespaces/gantt.d.ts +10 -0
  65. package/dist/i18n/namespaces/gantt.js +57 -0
  66. package/dist/i18n/namespaces/gantt.js.map +1 -0
  67. package/dist/i18n/namespaces/tree-view.d.ts +8 -0
  68. package/dist/i18n/namespaces/tree-view.js +21 -0
  69. package/dist/i18n/namespaces/tree-view.js.map +1 -0
  70. package/dist/index.d.ts +5 -0
  71. package/dist/index.js +4 -0
  72. package/dist/index.js.map +1 -1
  73. package/package.json +9 -2
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ import { ChatStepItem, ChatLabels } from './chat-constants.js';
3
+
4
+ interface ChatStepsProps extends Omit<React.ComponentProps<"div">, "children"> {
5
+ steps: ChatStepItem[];
6
+ /**
7
+ * Mở danh sách bước (controlled). Bỏ trống thì tự giữ: MỞ khi còn bước đang làm hoặc có bước lỗi,
8
+ * tự THU lại khi mọi bước xong — câu trả lời mới là thứ cần đọc, các bước chỉ để soát lại.
9
+ */
10
+ open?: boolean;
11
+ onOpenChange?: (open: boolean) => void;
12
+ labels?: Partial<ChatLabels>;
13
+ }
14
+ /**
15
+ * Tiến trình của tác tử: các bước đang làm / xong / lỗi, gói gọn trong một dòng tóm tắt bấm được.
16
+ * Đặt vào prop `steps` của `ChatMessage`.
17
+ */
18
+ declare function ChatSteps({ steps, open: openProp, onOpenChange, labels, className, ...props }: ChatStepsProps): React.JSX.Element;
19
+
20
+ export { ChatSteps, type ChatStepsProps };
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown, Circle, CircleCheck, CircleX } from "lucide-react";
4
+ import * as React from "react";
5
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../components/ui/collapsible.js";
6
+ import { Spinner } from "../components/ui/spinner.js";
7
+ import { useComitorLabels } from "../i18n/locale-provider.js";
8
+ import { cn } from "../lib/cn.js";
9
+ function StepIcon({ status }) {
10
+ switch (status) {
11
+ case "running":
12
+ return /* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true", className: "size-4 text-muted-foreground" });
13
+ case "done":
14
+ return /* @__PURE__ */ jsx(CircleCheck, { "aria-hidden": "true", className: "size-4 text-success-ink" });
15
+ case "error":
16
+ return /* @__PURE__ */ jsx(CircleX, { "aria-hidden": "true", className: "size-4 text-destructive-ink" });
17
+ default:
18
+ return /* @__PURE__ */ jsx(Circle, { "aria-hidden": "true", className: "size-4 text-muted-foreground" });
19
+ }
20
+ }
21
+ function ChatSteps({ steps, open: openProp, onOpenChange, labels, className, ...props }) {
22
+ const t = useComitorLabels("chat", labels);
23
+ const done = steps.filter((step) => step.status === "done").length;
24
+ const running = steps.some((step) => step.status === "running");
25
+ const failed = steps.some((step) => step.status === "error");
26
+ const settled = !running && steps.every((step) => step.status === "done" || step.status === "error");
27
+ const [openState, setOpenState] = React.useState(!settled || failed);
28
+ const touchedRef = React.useRef(false);
29
+ React.useEffect(() => {
30
+ if (!touchedRef.current) setOpenState(!settled || failed);
31
+ }, [settled, failed]);
32
+ const open = openProp ?? openState;
33
+ const statusText = {
34
+ pending: t.stepPending,
35
+ running: t.stepRunning,
36
+ done: t.stepDone,
37
+ error: t.stepError
38
+ };
39
+ const summaryStatus = failed ? "error" : running ? "running" : settled ? "done" : "pending";
40
+ return /* @__PURE__ */ jsx(
41
+ Collapsible,
42
+ {
43
+ open,
44
+ onOpenChange: (next) => {
45
+ touchedRef.current = true;
46
+ setOpenState(next);
47
+ onOpenChange?.(next);
48
+ },
49
+ children: /* @__PURE__ */ jsxs("div", { "data-slot": "chat-steps", className: cn("rounded-lg border bg-card", className), ...props, children: [
50
+ /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-muted-foreground text-sm hover:text-foreground", children: [
51
+ /* @__PURE__ */ jsx(StepIcon, { status: summaryStatus }),
52
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: t.stepsSummary(done, steps.length) }),
53
+ /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true", className: cn("size-4 transition-transform", open && "rotate-180") })
54
+ ] }),
55
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-2 border-t px-3 py-2.5", children: steps.map((step) => /* @__PURE__ */ jsxs("li", { "data-slot": "chat-step", "data-status": step.status, className: "flex gap-2 text-sm", children: [
56
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 shrink-0", children: /* @__PURE__ */ jsx(StepIcon, { status: step.status }) }),
57
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
58
+ /* @__PURE__ */ jsxs("div", { className: cn(step.status === "pending" ? "text-muted-foreground" : "text-foreground"), children: [
59
+ step.label,
60
+ /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
61
+ " \u2014 ",
62
+ statusText[step.status]
63
+ ] })
64
+ ] }),
65
+ step.detail && /* @__PURE__ */ jsx(
66
+ "div",
67
+ {
68
+ className: cn(
69
+ "mt-0.5 text-xs",
70
+ step.status === "error" ? "text-destructive-ink" : "text-muted-foreground"
71
+ ),
72
+ children: step.detail
73
+ }
74
+ )
75
+ ] })
76
+ ] }, step.id)) }) })
77
+ ] })
78
+ }
79
+ );
80
+ }
81
+ export {
82
+ ChatSteps
83
+ };
84
+ //# sourceMappingURL=chat-steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/chat-steps.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronDown, Circle, CircleCheck, CircleX } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"../components/ui/collapsible.js\";\nimport { Spinner } from \"../components/ui/spinner.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport type { ChatLabels, ChatStepItem, ChatStepStatus } from \"./chat-constants.js\";\n\nexport interface ChatStepsProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n steps: ChatStepItem[];\n /**\n * Mở danh sách bước (controlled). Bỏ trống thì tự giữ: MỞ khi còn bước đang làm hoặc có bước lỗi,\n * tự THU lại khi mọi bước xong — câu trả lời mới là thứ cần đọc, các bước chỉ để soát lại.\n */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n labels?: Partial<ChatLabels>;\n}\n\nfunction StepIcon({ status }: { status: ChatStepStatus }) {\n switch (status) {\n case \"running\":\n return <Spinner aria-hidden=\"true\" className=\"size-4 text-muted-foreground\" />;\n case \"done\":\n return <CircleCheck aria-hidden=\"true\" className=\"size-4 text-success-ink\" />;\n case \"error\":\n return <CircleX aria-hidden=\"true\" className=\"size-4 text-destructive-ink\" />;\n default:\n return <Circle aria-hidden=\"true\" className=\"size-4 text-muted-foreground\" />;\n }\n}\n\n/**\n * Tiến trình của tác tử: các bước đang làm / xong / lỗi, gói gọn trong một dòng tóm tắt bấm được.\n * Đặt vào prop `steps` của `ChatMessage`.\n */\nexport function ChatSteps({ steps, open: openProp, onOpenChange, labels, className, ...props }: ChatStepsProps) {\n const t = useComitorLabels(\"chat\", labels);\n const done = steps.filter((step) => step.status === \"done\").length;\n const running = steps.some((step) => step.status === \"running\");\n const failed = steps.some((step) => step.status === \"error\");\n const settled = !running && steps.every((step) => step.status === \"done\" || step.status === \"error\");\n\n // Tự mở/thu theo tiến trình — nhưng người dùng đã tự bấm thì thôi tự động.\n const [openState, setOpenState] = React.useState(!settled || failed);\n const touchedRef = React.useRef(false);\n React.useEffect(() => {\n if (!touchedRef.current) setOpenState(!settled || failed);\n }, [settled, failed]);\n const open = openProp ?? openState;\n\n const statusText: Record<ChatStepStatus, string> = {\n pending: t.stepPending,\n running: t.stepRunning,\n done: t.stepDone,\n error: t.stepError\n };\n const summaryStatus: ChatStepStatus = failed ? \"error\" : running ? \"running\" : settled ? \"done\" : \"pending\";\n\n return (\n <Collapsible\n open={open}\n onOpenChange={(next) => {\n touchedRef.current = true;\n setOpenState(next);\n onOpenChange?.(next);\n }}\n >\n <div data-slot=\"chat-steps\" className={cn(\"rounded-lg border bg-card\", className)} {...props}>\n <CollapsibleTrigger className=\"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-muted-foreground text-sm hover:text-foreground\">\n <StepIcon status={summaryStatus} />\n <span className=\"flex-1\">{t.stepsSummary(done, steps.length)}</span>\n <ChevronDown aria-hidden=\"true\" className={cn(\"size-4 transition-transform\", open && \"rotate-180\")} />\n </CollapsibleTrigger>\n <CollapsibleContent>\n <ol className=\"flex flex-col gap-2 border-t px-3 py-2.5\">\n {steps.map((step) => (\n <li key={step.id} data-slot=\"chat-step\" data-status={step.status} className=\"flex gap-2 text-sm\">\n <span className=\"mt-0.5 shrink-0\">\n <StepIcon status={step.status} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className={cn(step.status === \"pending\" ? \"text-muted-foreground\" : \"text-foreground\")}>\n {step.label}\n <span className=\"sr-only\"> — {statusText[step.status]}</span>\n </div>\n {step.detail && (\n <div\n className={cn(\n \"mt-0.5 text-xs\",\n step.status === \"error\" ? \"text-destructive-ink\" : \"text-muted-foreground\"\n )}\n >\n {step.detail}\n </div>\n )}\n </div>\n </li>\n ))}\n </ol>\n </CollapsibleContent>\n </div>\n </Collapsible>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { ChatLabels } from './chat-constants.js';
3
+
4
+ interface ChatThreadProps extends React.ComponentProps<"div"> {
5
+ /**
6
+ * Tác tử đang trả lời. Đặt `aria-busy` lên vùng `log`: trình đọc màn hình CHỜ tới khi xong rồi mới
7
+ * đọc tin mới — không thì nó đọc lại từng mẩu chữ đang về, hàng chục lần một câu.
8
+ */
9
+ busy?: boolean;
10
+ /** Lớp cho cột tin bên trong (mặc định rộng tối đa `max-w-3xl`, căn giữa). */
11
+ contentClassName?: string;
12
+ labels?: Partial<ChatLabels>;
13
+ }
14
+ /**
15
+ * Vùng cuộn của cuộc trò chuyện: `role="log"`, tự bám đáy khi có chữ mới VÀ người dùng đang ở cuối.
16
+ * Người dùng cuộn lên đọc lại thì thôi bám (không giật xuống giữa lúc đọc), và hiện nút "về tin mới
17
+ * nhất". Con là các `ChatMessage`.
18
+ */
19
+ declare function ChatThread({ busy, className, contentClassName, labels, children, "aria-label": ariaLabel, ...props }: ChatThreadProps): React.JSX.Element;
20
+
21
+ export { ChatThread, type ChatThreadProps };
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ArrowDown } from "lucide-react";
4
+ import * as React from "react";
5
+ import { Button } from "../components/ui/button.js";
6
+ import { useComitorLabels } from "../i18n/locale-provider.js";
7
+ import { cn } from "../lib/cn.js";
8
+ import { useIsomorphicLayoutEffect } from "../lib/use-isomorphic-layout-effect.js";
9
+ const STICK_THRESHOLD = 48;
10
+ function ChatThread({
11
+ busy,
12
+ className,
13
+ contentClassName,
14
+ labels,
15
+ children,
16
+ "aria-label": ariaLabel,
17
+ ...props
18
+ }) {
19
+ const t = useComitorLabels("chat", labels);
20
+ const scrollRef = React.useRef(null);
21
+ const contentRef = React.useRef(null);
22
+ const atBottomRef = React.useRef(true);
23
+ const [atBottom, setAtBottom] = React.useState(true);
24
+ const scrollToBottom = React.useCallback((behavior = "auto") => {
25
+ const el = scrollRef.current;
26
+ if (el) el.scrollTo({ top: el.scrollHeight, behavior });
27
+ }, []);
28
+ const onScroll = () => {
29
+ const el = scrollRef.current;
30
+ if (!el) return;
31
+ const next = el.scrollHeight - el.scrollTop - el.clientHeight <= STICK_THRESHOLD;
32
+ atBottomRef.current = next;
33
+ setAtBottom(next);
34
+ };
35
+ useIsomorphicLayoutEffect(() => {
36
+ scrollToBottom();
37
+ const content = contentRef.current;
38
+ if (!content) return;
39
+ const observer = new ResizeObserver(() => {
40
+ if (atBottomRef.current) scrollToBottom();
41
+ });
42
+ observer.observe(content);
43
+ return () => observer.disconnect();
44
+ }, [scrollToBottom]);
45
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "chat-thread", className: cn("relative flex min-h-0 flex-col", className), ...props, children: [
46
+ /* @__PURE__ */ jsx(
47
+ "div",
48
+ {
49
+ ref: scrollRef,
50
+ role: "log",
51
+ "aria-label": ariaLabel ?? t.log,
52
+ "aria-busy": busy || void 0,
53
+ tabIndex: 0,
54
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain outline-hidden focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring",
55
+ onScroll,
56
+ children: /* @__PURE__ */ jsx(
57
+ "div",
58
+ {
59
+ ref: contentRef,
60
+ className: cn("mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6", contentClassName),
61
+ children
62
+ }
63
+ )
64
+ }
65
+ ),
66
+ !atBottom && /* @__PURE__ */ jsx(
67
+ Button,
68
+ {
69
+ type: "button",
70
+ variant: "outline",
71
+ size: "icon-sm",
72
+ "aria-label": t.scrollToLatest,
73
+ className: "absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full bg-card shadow-overlay",
74
+ onClick: () => {
75
+ atBottomRef.current = true;
76
+ setAtBottom(true);
77
+ scrollToBottom("smooth");
78
+ },
79
+ children: /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true", className: "size-4" })
80
+ }
81
+ )
82
+ ] });
83
+ }
84
+ export {
85
+ ChatThread
86
+ };
87
+ //# sourceMappingURL=chat-thread.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/chat-thread.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Bố cục khung hội thoại tham khảo prompt-kit (https://www.prompt-kit.com) — MIT License, Copyright\n * (c) 2025 Julien Thibeaut — và AI Elements của Vercel (Apache-2.0). Không chép mã: cả hai dựa vào\n * `use-stick-to-bottom` và `streamdown`; gói tự viết phần bám đáy (~40 dòng) và vẽ markdown bằng\n * chính `MarkdownEditor` chỉ-đọc. Lý do: docs/chat.md.\n */\nimport { ArrowDown } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"../components/ui/button.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/use-isomorphic-layout-effect.js\";\nimport type { ChatLabels } from \"./chat-constants.js\";\n\nexport interface ChatThreadProps extends React.ComponentProps<\"div\"> {\n /**\n * Tác tử đang trả lời. Đặt `aria-busy` lên vùng `log`: trình đọc màn hình CHỜ tới khi xong rồi mới\n * đọc tin mới — không thì nó đọc lại từng mẩu chữ đang về, hàng chục lần một câu.\n */\n busy?: boolean;\n /** Lớp cho cột tin bên trong (mặc định rộng tối đa `max-w-3xl`, căn giữa). */\n contentClassName?: string;\n labels?: Partial<ChatLabels>;\n}\n\n/** Khoảng cách tới đáy (px) mà vẫn coi là \"đang ở cuối\" — chữ mới về thì tự cuộn theo. */\nconst STICK_THRESHOLD = 48;\n\n/**\n * Vùng cuộn của cuộc trò chuyện: `role=\"log\"`, tự bám đáy khi có chữ mới VÀ người dùng đang ở cuối.\n * Người dùng cuộn lên đọc lại thì thôi bám (không giật xuống giữa lúc đọc), và hiện nút \"về tin mới\n * nhất\". Con là các `ChatMessage`.\n */\nexport function ChatThread({\n busy,\n className,\n contentClassName,\n labels,\n children,\n \"aria-label\": ariaLabel,\n ...props\n}: ChatThreadProps) {\n const t = useComitorLabels(\"chat\", labels);\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const contentRef = React.useRef<HTMLDivElement>(null);\n const atBottomRef = React.useRef(true);\n const [atBottom, setAtBottom] = React.useState(true);\n\n const scrollToBottom = React.useCallback((behavior: ScrollBehavior = \"auto\") => {\n const el = scrollRef.current;\n if (el) el.scrollTo({ top: el.scrollHeight, behavior });\n }, []);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const next = el.scrollHeight - el.scrollTop - el.clientHeight <= STICK_THRESHOLD;\n atBottomRef.current = next;\n setAtBottom(next);\n };\n\n // Mở ra là đứng ở tin cuối; nội dung lớn lên (tin mới, chữ đang về, ảnh tải xong) thì bám theo nếu\n // đang ở cuối. Theo dõi CỠ của cột tin chứ không theo `children`: chữ streaming đổi cỡ mà không\n // đổi số con, còn ảnh trong tin đổi cỡ mà không qua React.\n useIsomorphicLayoutEffect(() => {\n scrollToBottom();\n const content = contentRef.current;\n if (!content) return;\n const observer = new ResizeObserver(() => {\n if (atBottomRef.current) scrollToBottom();\n });\n observer.observe(content);\n return () => observer.disconnect();\n }, [scrollToBottom]);\n\n return (\n <div data-slot=\"chat-thread\" className={cn(\"relative flex min-h-0 flex-col\", className)} {...props}>\n <div\n ref={scrollRef}\n role=\"log\"\n aria-label={ariaLabel ?? t.log}\n aria-busy={busy || undefined}\n // Vùng cuộn phải tự nhận focus được — người chỉ dùng bàn phím mới cuộn được lịch sử (axe:\n // scrollable-region-focusable; Chrome tự làm việc này, Safari thì không).\n // biome-ignore lint/a11y/noNoninteractiveTabindex: vùng cuộn, không phải điều khiển — lý do ở trên\n tabIndex={0}\n className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain outline-hidden focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring\"\n onScroll={onScroll}\n >\n <div\n ref={contentRef}\n className={cn(\"mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6\", contentClassName)}\n >\n {children}\n </div>\n </div>\n {!atBottom && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t.scrollToLatest}\n className=\"absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full bg-card shadow-overlay\"\n onClick={() => {\n atBottomRef.current = true;\n setAtBottom(true);\n scrollToBottom(\"smooth\");\n }}\n >\n <ArrowDown aria-hidden=\"true\" className=\"size-4\" />\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":";AA8EI,SAaI,KAbJ;AAtEJ,SAAS,iBAAiB;AAC1B,YAAY,WAAW;AACvB,SAAS,cAAc;AACvB,SAAS,wBAAwB;AACjC,SAAS,UAAU;AACnB,SAAS,iCAAiC;AAe1C,MAAM,kBAAkB;AAOjB,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,GAAG;AACL,GAAoB;AAClB,QAAM,IAAI,iBAAiB,QAAQ,MAAM;AACzC,QAAM,YAAY,MAAM,OAAuB,IAAI;AACnD,QAAM,aAAa,MAAM,OAAuB,IAAI;AACpD,QAAM,cAAc,MAAM,OAAO,IAAI;AACrC,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,IAAI;AAEnD,QAAM,iBAAiB,MAAM,YAAY,CAAC,WAA2B,WAAW;AAC9E,UAAM,KAAK,UAAU;AACrB,QAAI,GAAI,IAAG,SAAS,EAAE,KAAK,GAAG,cAAc,SAAS,CAAC;AAAA,EACxD,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,OAAO,GAAG,eAAe,GAAG,YAAY,GAAG,gBAAgB;AACjE,gBAAY,UAAU;AACtB,gBAAY,IAAI;AAAA,EAClB;AAKA,4BAA0B,MAAM;AAC9B,mBAAe;AACf,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS;AACd,UAAM,WAAW,IAAI,eAAe,MAAM;AACxC,UAAI,YAAY,QAAS,gBAAe;AAAA,IAC1C,CAAC;AACD,aAAS,QAAQ,OAAO;AACxB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,cAAc,CAAC;AAEnB,SACE,qBAAC,SAAI,aAAU,eAAc,WAAW,GAAG,kCAAkC,SAAS,GAAI,GAAG,OAC3F;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,cAAY,aAAa,EAAE;AAAA,QAC3B,aAAW,QAAQ;AAAA,QAInB,UAAU;AAAA,QACV,WAAU;AAAA,QACV;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAW,GAAG,0DAA0D,gBAAgB;AAAA,YAEvF;AAAA;AAAA,QACH;AAAA;AAAA,IACF;AAAA,IACC,CAAC,YACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,cAAY,EAAE;AAAA,QACd,WAAU;AAAA,QACV,SAAS,MAAM;AACb,sBAAY,UAAU;AACtB,sBAAY,IAAI;AAChB,yBAAe,QAAQ;AAAA,QACzB;AAAA,QAEA,8BAAC,aAAU,eAAY,QAAO,WAAU,UAAS;AAAA;AAAA,IACnD;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,12 @@
1
+ export { ChatComposer, ChatComposerProps } from './chat-composer.js';
2
+ export { ChatLabels, ChatRole, ChatSource, ChatStepItem, ChatStepStatus } from './chat-constants.js';
3
+ export { ChatMessage, ChatMessageProps } from './chat-message.js';
4
+ export { ChatCitation, ChatCitationProps, ChatSources, ChatSourcesProps } from './chat-sources.js';
5
+ export { ChatSteps, ChatStepsProps } from './chat-steps.js';
6
+ export { ChatThread, ChatThreadProps } from './chat-thread.js';
7
+ import 'react';
8
+ import '../editor-types-BacXD2VN.js';
9
+ import '@tiptap/core';
10
+ import '../components/composite/empty-state.js';
11
+ import '../editor/markdown-editor-constants.js';
12
+ import '../editor/markdown-editor.js';
@@ -0,0 +1,14 @@
1
+ import { ChatComposer } from "./chat-composer.js";
2
+ import { ChatMessage } from "./chat-message.js";
3
+ import { ChatCitation, ChatSources } from "./chat-sources.js";
4
+ import { ChatSteps } from "./chat-steps.js";
5
+ import { ChatThread } from "./chat-thread.js";
6
+ export {
7
+ ChatCitation,
8
+ ChatComposer,
9
+ ChatMessage,
10
+ ChatSources,
11
+ ChatSteps,
12
+ ChatThread
13
+ };
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/index.ts"],"sourcesContent":["/**\n * Entry `@comitor/ui/chat` — khung hội thoại cho trợ lý AI và các tác tử: danh sách tin, bước tiến\n * trình của tác tử, nguồn trích dẫn bấm được, ô nhập có `@`, chữ trả về dần.\n *\n * ── VÌ SAO LÀ MỘT ENTRY RIÊNG ────────────────────────────────────────────────────────────────\n * Tin nhắn vẽ markdown bằng CHÍNH `MarkdownEditor` chỉ-đọc, và ô nhập là `MarkdownEditor` — nên entry\n * này kéo đúng bộ peer TUỲ CHỌN của `/editor` (13 gói `@tiptap/*` cùng một phiên bản, `lowlight`).\n * Để ở entry `.` là bắt mọi app cài Tiptap. Gộp vào `/editor` thì không tốn byte (`bundle: false`)\n * nhưng trộn hai bề mặt API khác nghề vào một entry — trình soạn là ô nhập, đây là cả một màn.\n * Cài peer: xem đầu `src/editor/index.ts`.\n *\n * Gói không giữ trạng thái hội thoại, không gọi mô hình: app giữ danh sách tin, đẩy chữ đang về\n * vào `content`, và quyết định khi nào `busy`/`streaming`. Tài liệu: docs/chat.md.\n */\n\nexport { ChatComposer, type ChatComposerProps } from \"./chat-composer.js\";\nexport type {\n ChatLabels,\n ChatRole,\n ChatSource,\n ChatStepItem,\n ChatStepStatus\n} from \"./chat-constants.js\";\nexport { ChatMessage, type ChatMessageProps } from \"./chat-message.js\";\nexport { ChatCitation, type ChatCitationProps, ChatSources, type ChatSourcesProps } from \"./chat-sources.js\";\nexport { ChatSteps, type ChatStepsProps } from \"./chat-steps.js\";\nexport { ChatThread, type ChatThreadProps } from \"./chat-thread.js\";\n"],"mappings":"AAeA,SAAS,oBAA4C;AAQrD,SAAS,mBAA0C;AACnD,SAAS,cAAsC,mBAA0C;AACzF,SAAS,iBAAsC;AAC/C,SAAS,kBAAwC;","names":[]}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Kiểu dữ liệu + hằng của `Gantt` — module THUẦN (không `"use client"`), barrel xuất THẲNG từ đây.
3
+ * Lý do: `GANTT_SCALES` là một mảng; mọi export của file `"use client"` tới Server Component là
4
+ * client-reference proxy và đọc ra `undefined` (xem `src/chart/chart-colors.ts`).
5
+ *
6
+ * ─ HỢP ĐỒNG NGÀY ─
7
+ * Mọi ngày đi qua API là chuỗi `yyyy-MM-dd` (ngày LỊCH, không giờ, không múi giờ). Gantt làm việc
8
+ * theo NGÀY: một `Date` sẽ kéo theo múi giờ của trình duyệt, và "hạn chót 31/10" mở ở Hà Nội với
9
+ * mở ở San Francisco phải là cùng một ô. `end` là ngày CUỐI CÙNG có làm (bao gồm), không phải
10
+ * "ngày sau ngày cuối" — việc một ngày có `start === end`.
11
+ */
12
+ /** Bậc phóng của trục thời gian. `day`/`week` cho Gantt việc; `month`/`quarter`/`year` cho lộ trình. */
13
+ type GanttScale = "day" | "week" | "month" | "quarter" | "year";
14
+ /** Các bậc phóng, theo thứ tự từ gần tới xa — dùng để dựng nút chọn bậc của riêng app. */
15
+ declare const GANTT_SCALES: readonly GanttScale[];
16
+ /**
17
+ * Màu của thanh. Mỗi tone là một màu có ĐỦ ba vai trò trong `styles.css` (`--x` tô thanh,
18
+ * `--x-foreground` chữ trong thanh, `--x-ink` viền + mốc) — nên thanh đổi đúng theo dark mode và
19
+ * bảng tương phản cao. Không nhận màu tự do: màu tự do không đo được tương phản (docs/diceui.md §1.4).
20
+ */
21
+ type GanttTone = "neutral" | "primary" | "blue" | "teal" | "green" | "yellow" | "orange" | "red" | "purple" | "pink" | "navy";
22
+ /** Kiểu quan hệ phụ thuộc. Mặc định `finish-to-start` — việc sau chỉ bắt đầu khi việc trước xong. */
23
+ type GanttDependencyType = "finish-to-start" | "start-to-start" | "finish-to-finish" | "start-to-finish";
24
+ interface GanttItem {
25
+ /** Khoá duy nhất — `dependencies` tham chiếu tới nó. */
26
+ id: string;
27
+ /** Tên hiển thị ở cột trái và trong thanh. */
28
+ title: string;
29
+ /** Ngày bắt đầu `yyyy-MM-dd`. Thiếu `start` hoặc `end` = việc CHƯA XẾP LỊCH: dòng không có thanh. */
30
+ start?: string | null;
31
+ /** Ngày kết thúc `yyyy-MM-dd`, BAO GỒM. Mốc (`type: "milestone"`) chỉ đọc `start`. */
32
+ end?: string | null;
33
+ /**
34
+ * `task` (mặc định) — thanh kéo được; `summary` — thanh mảnh của một nhóm (dự án, giai đoạn), dải
35
+ * ngày do app tính từ con, không kéo được; `milestone` — hình thoi ở ngày `start`, kéo được.
36
+ */
37
+ type?: "task" | "summary" | "milestone";
38
+ /** Độ sâu trong cây (0 = gốc) — chỉ để thụt lề ở cột trái và `aria-level`. */
39
+ depth?: number;
40
+ /** Có con hay không — hiện nút mở/thu. Gantt KHÔNG tự ẩn con: app lọc `items` theo `expanded`. */
41
+ hasChildren?: boolean;
42
+ /** Nhánh đang mở — chỉ có nghĩa khi `hasChildren`. */
43
+ expanded?: boolean;
44
+ /** Tiến độ 0–100. */
45
+ progress?: number;
46
+ /** Màu của thanh (mặc định `neutral`). */
47
+ tone?: GanttTone;
48
+ /**
49
+ * Nằm trên ĐƯỜNG GĂNG. Gói không tự tính đường găng — nó phụ thuộc lịch làm việc, độ trễ và luật
50
+ * của từng app; app tính rồi đánh dấu, gói chỉ tô nổi (viền đỏ đậm, mũi tên đỏ).
51
+ */
52
+ critical?: boolean;
53
+ /** Khoá kéo/giãn/bàn phím riêng dòng này (vd việc đã xong, không có quyền sửa). */
54
+ readOnly?: boolean;
55
+ }
56
+ interface GanttDependency {
57
+ id: string;
58
+ /** `id` của việc đứng TRƯỚC. */
59
+ from: string;
60
+ /** `id` của việc đứng SAU. */
61
+ to: string;
62
+ type?: GanttDependencyType;
63
+ /** Mũi tên nằm trên đường găng — tô đỏ, nét đậm. */
64
+ critical?: boolean;
65
+ }
66
+ /** Thay đổi ngày do người dùng kéo/giãn/gõ phím — app ghi vào dữ liệu của mình rồi truyền `items` mới. */
67
+ interface GanttItemChange {
68
+ id: string;
69
+ start: string;
70
+ end: string;
71
+ /** Cách thay đổi xảy ra: dời cả thanh, giãn đầu, giãn đuôi, hay xếp lịch cho việc chưa có ngày. */
72
+ kind: "move" | "resize-start" | "resize-end" | "schedule";
73
+ }
74
+ /** Phụ thuộc mới do người dùng nối từ chấm ở đuôi thanh này sang thanh khác. */
75
+ interface GanttDependencyDraft {
76
+ from: string;
77
+ to: string;
78
+ type: GanttDependencyType;
79
+ }
80
+ /**
81
+ * Mọi chữ `Gantt` tự vẽ hoặc tự đọc cho trình đọc màn hình. Chữ có tham số là HÀM. Theo
82
+ * `ComitorLocaleProvider` (namespace `gantt`), đè từng khoá qua prop `labels`.
83
+ */
84
+ interface GanttLabels {
85
+ /** Tên vùng lưới khi app không truyền `aria-label`. */
86
+ label: string;
87
+ /** Tiêu đề cột trái. */
88
+ sidebarTitle: string;
89
+ /** Tên nhóm nút chọn bậc phóng. */
90
+ scaleLabel: string;
91
+ scaleDay: string;
92
+ scaleWeek: string;
93
+ scaleMonth: string;
94
+ scaleQuarter: string;
95
+ scaleYear: string;
96
+ /** Nút cuộn về hôm nay. */
97
+ today: string;
98
+ /** Nhãn quý ở đầu trục. */
99
+ quarter: (quarter: number, year: number) => string;
100
+ /** Mô tả dải ngày của một việc cho trình đọc màn hình. */
101
+ itemRange: (start: string, end: string, days: number) => string;
102
+ /** Mô tả ngày của một mốc. */
103
+ milestoneDate: (date: string) => string;
104
+ progress: (percent: number) => string;
105
+ /** Việc chưa có ngày. */
106
+ unscheduled: string;
107
+ /** Gắn vào việc trên đường găng. */
108
+ critical: string;
109
+ /** Gắn vào việc đang vi phạm một phụ thuộc (bắt đầu trước khi việc trước xong). */
110
+ conflict: string;
111
+ /** Hướng dẫn phím, gắn qua `aria-describedby`. */
112
+ instructions: string;
113
+ /** Xướng sau khi đổi ngày bằng bàn phím. */
114
+ rescheduled: (title: string, range: string) => string;
115
+ expand: string;
116
+ collapse: string;
117
+ /** Nút ở mép trục cuộn tới một thanh đang nằm ngoài khung nhìn. */
118
+ scrollToItem: (title: string) => string;
119
+ /** Tên chấm nối phụ thuộc ở đuôi thanh. */
120
+ linkHandle: (title: string) => string;
121
+ /** Mô tả một mũi tên phụ thuộc (thẻ `<title>` của SVG). */
122
+ dependency: (from: string, to: string) => string;
123
+ /** Gợi ý ở dòng chưa có ngày khi rê chuột: bấm để xếp lịch. */
124
+ scheduleHint: string;
125
+ }
126
+
127
+ export { GANTT_SCALES, type GanttDependency, type GanttDependencyDraft, type GanttDependencyType, type GanttItem, type GanttItemChange, type GanttLabels, type GanttScale, type GanttTone };
@@ -0,0 +1,5 @@
1
+ const GANTT_SCALES = ["day", "week", "month", "quarter", "year"];
2
+ export {
3
+ GANTT_SCALES
4
+ };
5
+ //# sourceMappingURL=gantt-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/composite/gantt-constants.ts"],"sourcesContent":["/**\n * Kiểu dữ liệu + hằng của `Gantt` — module THUẦN (không `\"use client\"`), barrel xuất THẲNG từ đây.\n * Lý do: `GANTT_SCALES` là một mảng; mọi export của file `\"use client\"` tới Server Component là\n * client-reference proxy và đọc ra `undefined` (xem `src/chart/chart-colors.ts`).\n *\n * ─ HỢP ĐỒNG NGÀY ─\n * Mọi ngày đi qua API là chuỗi `yyyy-MM-dd` (ngày LỊCH, không giờ, không múi giờ). Gantt làm việc\n * theo NGÀY: một `Date` sẽ kéo theo múi giờ của trình duyệt, và \"hạn chót 31/10\" mở ở Hà Nội với\n * mở ở San Francisco phải là cùng một ô. `end` là ngày CUỐI CÙNG có làm (bao gồm), không phải\n * \"ngày sau ngày cuối\" — việc một ngày có `start === end`.\n */\n\n/** Bậc phóng của trục thời gian. `day`/`week` cho Gantt việc; `month`/`quarter`/`year` cho lộ trình. */\nexport type GanttScale = \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n\n/** Các bậc phóng, theo thứ tự từ gần tới xa — dùng để dựng nút chọn bậc của riêng app. */\nexport const GANTT_SCALES: readonly GanttScale[] = [\"day\", \"week\", \"month\", \"quarter\", \"year\"];\n\n/**\n * Màu của thanh. Mỗi tone là một màu có ĐỦ ba vai trò trong `styles.css` (`--x` tô thanh,\n * `--x-foreground` chữ trong thanh, `--x-ink` viền + mốc) — nên thanh đổi đúng theo dark mode và\n * bảng tương phản cao. Không nhận màu tự do: màu tự do không đo được tương phản (docs/diceui.md §1.4).\n */\nexport type GanttTone =\n | \"neutral\"\n | \"primary\"\n | \"blue\"\n | \"teal\"\n | \"green\"\n | \"yellow\"\n | \"orange\"\n | \"red\"\n | \"purple\"\n | \"pink\"\n | \"navy\";\n\n/** Kiểu quan hệ phụ thuộc. Mặc định `finish-to-start` — việc sau chỉ bắt đầu khi việc trước xong. */\nexport type GanttDependencyType = \"finish-to-start\" | \"start-to-start\" | \"finish-to-finish\" | \"start-to-finish\";\n\nexport interface GanttItem {\n /** Khoá duy nhất — `dependencies` tham chiếu tới nó. */\n id: string;\n /** Tên hiển thị ở cột trái và trong thanh. */\n title: string;\n /** Ngày bắt đầu `yyyy-MM-dd`. Thiếu `start` hoặc `end` = việc CHƯA XẾP LỊCH: dòng không có thanh. */\n start?: string | null;\n /** Ngày kết thúc `yyyy-MM-dd`, BAO GỒM. Mốc (`type: \"milestone\"`) chỉ đọc `start`. */\n end?: string | null;\n /**\n * `task` (mặc định) — thanh kéo được; `summary` — thanh mảnh của một nhóm (dự án, giai đoạn), dải\n * ngày do app tính từ con, không kéo được; `milestone` — hình thoi ở ngày `start`, kéo được.\n */\n type?: \"task\" | \"summary\" | \"milestone\";\n /** Độ sâu trong cây (0 = gốc) — chỉ để thụt lề ở cột trái và `aria-level`. */\n depth?: number;\n /** Có con hay không — hiện nút mở/thu. Gantt KHÔNG tự ẩn con: app lọc `items` theo `expanded`. */\n hasChildren?: boolean;\n /** Nhánh đang mở — chỉ có nghĩa khi `hasChildren`. */\n expanded?: boolean;\n /** Tiến độ 0–100. */\n progress?: number;\n /** Màu của thanh (mặc định `neutral`). */\n tone?: GanttTone;\n /**\n * Nằm trên ĐƯỜNG GĂNG. Gói không tự tính đường găng — nó phụ thuộc lịch làm việc, độ trễ và luật\n * của từng app; app tính rồi đánh dấu, gói chỉ tô nổi (viền đỏ đậm, mũi tên đỏ).\n */\n critical?: boolean;\n /** Khoá kéo/giãn/bàn phím riêng dòng này (vd việc đã xong, không có quyền sửa). */\n readOnly?: boolean;\n}\n\nexport interface GanttDependency {\n id: string;\n /** `id` của việc đứng TRƯỚC. */\n from: string;\n /** `id` của việc đứng SAU. */\n to: string;\n type?: GanttDependencyType;\n /** Mũi tên nằm trên đường găng — tô đỏ, nét đậm. */\n critical?: boolean;\n}\n\n/** Thay đổi ngày do người dùng kéo/giãn/gõ phím — app ghi vào dữ liệu của mình rồi truyền `items` mới. */\nexport interface GanttItemChange {\n id: string;\n start: string;\n end: string;\n /** Cách thay đổi xảy ra: dời cả thanh, giãn đầu, giãn đuôi, hay xếp lịch cho việc chưa có ngày. */\n kind: \"move\" | \"resize-start\" | \"resize-end\" | \"schedule\";\n}\n\n/** Phụ thuộc mới do người dùng nối từ chấm ở đuôi thanh này sang thanh khác. */\nexport interface GanttDependencyDraft {\n from: string;\n to: string;\n type: GanttDependencyType;\n}\n\n/**\n * Mọi chữ `Gantt` tự vẽ hoặc tự đọc cho trình đọc màn hình. Chữ có tham số là HÀM. Theo\n * `ComitorLocaleProvider` (namespace `gantt`), đè từng khoá qua prop `labels`.\n */\nexport interface GanttLabels {\n /** Tên vùng lưới khi app không truyền `aria-label`. */\n label: string;\n /** Tiêu đề cột trái. */\n sidebarTitle: string;\n /** Tên nhóm nút chọn bậc phóng. */\n scaleLabel: string;\n scaleDay: string;\n scaleWeek: string;\n scaleMonth: string;\n scaleQuarter: string;\n scaleYear: string;\n /** Nút cuộn về hôm nay. */\n today: string;\n /** Nhãn quý ở đầu trục. */\n quarter: (quarter: number, year: number) => string;\n /** Mô tả dải ngày của một việc cho trình đọc màn hình. */\n itemRange: (start: string, end: string, days: number) => string;\n /** Mô tả ngày của một mốc. */\n milestoneDate: (date: string) => string;\n progress: (percent: number) => string;\n /** Việc chưa có ngày. */\n unscheduled: string;\n /** Gắn vào việc trên đường găng. */\n critical: string;\n /** Gắn vào việc đang vi phạm một phụ thuộc (bắt đầu trước khi việc trước xong). */\n conflict: string;\n /** Hướng dẫn phím, gắn qua `aria-describedby`. */\n instructions: string;\n /** Xướng sau khi đổi ngày bằng bàn phím. */\n rescheduled: (title: string, range: string) => string;\n expand: string;\n collapse: string;\n /** Nút ở mép trục cuộn tới một thanh đang nằm ngoài khung nhìn. */\n scrollToItem: (title: string) => string;\n /** Tên chấm nối phụ thuộc ở đuôi thanh. */\n linkHandle: (title: string) => string;\n /** Mô tả một mũi tên phụ thuộc (thẻ `<title>` của SVG). */\n dependency: (from: string, to: string) => string;\n /** Gợi ý ở dòng chưa có ngày khi rê chuột: bấm để xếp lịch. */\n scheduleHint: string;\n}\n"],"mappings":"AAgBO,MAAM,eAAsC,CAAC,OAAO,QAAQ,SAAS,WAAW,MAAM;","names":[]}
@@ -0,0 +1,106 @@
1
+ import { GanttScale, GanttDependencyType } from './gantt-constants.js';
2
+
3
+ /**
4
+ * 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
5
+ * DOM — nên kiểm được bằng Node trần (`scripts/check-gantt.mjs` gọi thẳng file build).
6
+ *
7
+ * KHÔNG nằm trong barrel: đây là nội tạng của component, không phải API.
8
+ *
9
+ * ─ VÌ SAO ĐẾM NGÀY BẰNG SỐ NGUYÊN, KHÔNG BẰNG `Date` ─
10
+ * 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ừ
11
+ * 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ộ
12
+ * chỉ xuất hiện ở lúc ĐỊNH DẠNG nhãn (xem `dayToLocalDate`).
13
+ */
14
+
15
+ /** `"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). */
16
+ declare function parseDay(value: string | null | undefined): number | null;
17
+ /** Số ngày → `"yyyy-MM-dd"`. */
18
+ declare function formatDay(day: number): string;
19
+ /** 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). */
20
+ declare function todayDay(): number;
21
+ /** `Date` CỤC BỘ lúc 12 giờ trưa của ngày đó — chỉ để đưa cho `Intl.DateTimeFormat`. */
22
+ declare function dayToLocalDate(day: number): Date;
23
+ /** Thứ trong tuần: 0 = Chủ nhật … 6 = Thứ bảy (khớp `Date#getDay`). 1970-01-01 là thứ năm. */
24
+ declare function weekday(day: number): number;
25
+ type GanttUnit = "day" | "week" | "month" | "quarter" | "year";
26
+ /** 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). */
27
+ declare function startOfUnit(day: number, unit: GanttUnit, weekStartsOn?: number): number;
28
+ /** 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). */
29
+ declare function addUnit(day: number, unit: GanttUnit, amount: number): number;
30
+ interface ScaleConfig {
31
+ /** Bề rộng một ngày, px. */
32
+ pxPerDay: number;
33
+ /** Đơn vị của hàng trên của đầu trục (`null` = không có hàng trên). */
34
+ major: GanttUnit | null;
35
+ /** Đơn vị của hàng dưới — cũng là bước của vạch lưới. */
36
+ minor: GanttUnit;
37
+ /** Lề thêm hai đầu dải ngày, tính bằng ngày. */
38
+ paddingDays: number;
39
+ /** Bước của Shift+mũi tên. */
40
+ bigStep: GanttUnit;
41
+ /** 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. */
42
+ shadeDays: boolean;
43
+ }
44
+ /**
45
+ * 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
46
+ * 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
47
+ * của Linear (docs/gantt.md §1); con số px là của gói, không đo từ Linear.
48
+ */
49
+ declare const SCALE_CONFIG: Record<GanttScale, ScaleConfig>;
50
+ interface Tick {
51
+ /** Ngày đầu của ô. */
52
+ day: number;
53
+ /** Ngày đầu của ô KẾ TIẾP (= ngày cuối + 1). */
54
+ next: number;
55
+ }
56
+ /** Mọi ô của `unit` giao với `[from, to)`. Ô đầu có thể bắt đầu trước `from`. */
57
+ declare function ticksBetween(from: number, to: number, unit: GanttUnit, weekStartsOn?: number): Tick[];
58
+ interface DayRange {
59
+ start: number;
60
+ /** Ngày đầu NGOÀI dải (nửa mở). */
61
+ end: number;
62
+ }
63
+ /**
64
+ * 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
65
+ * (và của hàng trên, nếu có) để ô đầu/cuối không bị cắt dở.
66
+ */
67
+ declare function computeRange(days: number[], today: number, scale: GanttScale, weekStartsOn?: number, override?: {
68
+ start: number | null;
69
+ end: number | null;
70
+ }): DayRange;
71
+ /** 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. */
72
+ interface BarGeometry {
73
+ x1: number;
74
+ x2: number;
75
+ y: number;
76
+ }
77
+ /**
78
+ * Đườ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.
79
+ *
80
+ * 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à
81
+ * 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
82
+ * 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à
83
+ * 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.
84
+ */
85
+ declare function routeDependency(from: BarGeometry, to: BarGeometry, type: GanttDependencyType, rowHeight: number): string;
86
+ /**
87
+ * Phụ thuộc có đang bị vi phạm không, theo ngày (cả hai đầu BAO GỒM):
88
+ * finish-to-start — việc sau phải bắt đầu SAU ngày việc trước xong;
89
+ * start-to-start — việc sau không bắt đầu trước việc trước;
90
+ * finish-to-finish — việc sau không xong trước việc trước;
91
+ * start-to-finish — việc sau không xong trước khi việc trước bắt đầu.
92
+ */
93
+ declare function isDependencyViolated(from: {
94
+ start: number;
95
+ end: number;
96
+ }, to: {
97
+ start: number;
98
+ end: number;
99
+ }, type: GanttDependencyType): boolean;
100
+ /** Dịch cả dải `[start, end]` đi một BƯỚC theo đơn vị, giữ nguyên độ dài theo ngày. */
101
+ declare function shiftByUnit(start: number, end: number, unit: GanttUnit, direction: 1 | -1): {
102
+ start: number;
103
+ end: number;
104
+ };
105
+
106
+ export { type BarGeometry, type DayRange, type GanttUnit, SCALE_CONFIG, type ScaleConfig, type Tick, addUnit, computeRange, dayToLocalDate, formatDay, isDependencyViolated, parseDay, routeDependency, shiftByUnit, startOfUnit, ticksBetween, todayDay, weekday };