@mhamz.01/easyflow-whiteboard 2.168.0 → 2.170.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 (34) hide show
  1. package/dist/components/node/custom-node-overlay-layer.d.ts +2 -43
  2. package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -1
  3. package/dist/components/node/custom-node-overlay-layer.js +89 -568
  4. package/dist/components/node/custom-node.d.ts +2 -5
  5. package/dist/components/node/custom-node.d.ts.map +1 -1
  6. package/dist/components/node/custom-node.js +11 -22
  7. package/dist/components/node/document-node.d.ts +2 -5
  8. package/dist/components/node/document-node.d.ts.map +1 -1
  9. package/dist/components/node/document-node.js +25 -42
  10. package/dist/components/node/hooks/useFabricSync.d.ts +30 -0
  11. package/dist/components/node/hooks/useFabricSync.d.ts.map +1 -0
  12. package/dist/components/node/hooks/useFabricSync.js +89 -0
  13. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts +22 -8
  14. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts.map +1 -1
  15. package/dist/components/node/hooks/useKeyboardShortcuts.js +30 -21
  16. package/dist/components/node/hooks/useNodeDrag.d.ts +31 -18
  17. package/dist/components/node/hooks/useNodeDrag.d.ts.map +1 -1
  18. package/dist/components/node/hooks/useNodeDrag.js +128 -78
  19. package/dist/components/node/hooks/useNodeSelection.d.ts +28 -0
  20. package/dist/components/node/hooks/useNodeSelection.d.ts.map +1 -0
  21. package/dist/components/node/hooks/useNodeSelection.js +55 -0
  22. package/dist/components/node/hooks/useNodeState.d.ts +15 -0
  23. package/dist/components/node/hooks/useNodeState.d.ts.map +1 -0
  24. package/dist/components/node/hooks/useNodeState.js +24 -0
  25. package/dist/components/node/hooks/useSelectionBox.d.ts +14 -3
  26. package/dist/components/node/hooks/useSelectionBox.d.ts.map +1 -1
  27. package/dist/components/node/hooks/useSelectionBox.js +39 -18
  28. package/dist/components/node/hooks/useWheelZoom.d.ts +16 -6
  29. package/dist/components/node/hooks/useWheelZoom.d.ts.map +1 -1
  30. package/dist/components/node/hooks/useWheelZoom.js +41 -44
  31. package/dist/components/node/types/overlay-types.d.ts +11 -8
  32. package/dist/components/node/types/overlay-types.d.ts.map +1 -1
  33. package/dist/styles.css +0 -3
  34. package/package.json +1 -1
@@ -1,14 +1,11 @@
1
- interface TaskNodeProps {
2
- id: string;
1
+ import type { BaseNodeProps } from "./types/overlay-types";
2
+ interface TaskNodeProps extends BaseNodeProps {
3
3
  title: string;
4
4
  status: "todo" | "in-progress" | "done";
5
5
  assignee?: string;
6
6
  project?: string;
7
7
  priority?: "low" | "medium" | "high";
8
8
  dueDate?: string;
9
- isSelected?: boolean;
10
- onSelect?: (id: string, e?: React.MouseEvent) => void;
11
- onDragStart?: (id: string, e: React.MouseEvent | React.TouchEvent) => void;
12
9
  onStatusChange?: (id: string, newStatus: "todo" | "in-progress" | "done") => void;
13
10
  zoom?: number;
14
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"custom-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node.tsx"],"names":[],"mappings":"AAYA,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACtD,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC3E,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,KAAK,IAAI,CAAC;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,IAAQ,GACT,EAAE,aAAa,2CAgJf"}
1
+ {"version":3,"file":"custom-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,aAAc,SAAQ,aAAa;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,KAAK,IAAI,CAAC;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,IAAQ,GACT,EAAE,aAAa,2CAoIf"}
@@ -14,35 +14,24 @@ export default function TaskNode({ id, title, status, assignee, project, priorit
14
14
  // Logic: Increase border thickness if zoomed out to keep it visible
15
15
  const dynamicBorderWidth = zoom < 0.6 ? "2px" : "1px";
16
16
  const focusRingSize = isSelected ? (zoom < 0.5 ? "6px" : "3px") : "0px";
17
- // 2. Update the internal handler
18
17
  const handleStart = (e) => {
19
18
  const target = e.target;
20
- // Don't drag if clicking buttons or the scrollable text
21
- if (target.closest('.interactive-element'))
19
+ if (target.closest(".interactive-element"))
22
20
  return;
23
- // For touch events, we don't want the browser to scroll the page
24
- // but we only preventDefault if it's not an interactive element
25
- if (e.type === 'touchstart' && e.cancelable) {
26
- // We don't preventDefault here because it might block clicks on
27
- // internal non-interactive elements, let handleDragStart handle it.
28
- }
29
21
  setIsDragging(true);
30
- if (onDragStart)
31
- onDragStart(id, e);
32
- if (onSelect)
33
- onSelect(id, e); // Cast for compatibility
22
+ onDragStart?.(id, e);
23
+ onSelect?.(id, e);
34
24
  };
35
- // 3. Update the cleanup effect
36
25
  useEffect(() => {
26
+ if (!isDragging)
27
+ return;
37
28
  const handleUp = () => setIsDragging(false);
38
- if (isDragging) {
39
- window.addEventListener("mouseup", handleUp);
40
- window.addEventListener("touchend", handleUp); // Add touch cleanup
41
- return () => {
42
- window.removeEventListener("mouseup", handleUp);
43
- window.removeEventListener("touchend", handleUp);
44
- };
45
- }
29
+ window.addEventListener("mouseup", handleUp);
30
+ window.addEventListener("touchend", handleUp);
31
+ return () => {
32
+ window.removeEventListener("mouseup", handleUp);
33
+ window.removeEventListener("touchend", handleUp);
34
+ };
46
35
  }, [isDragging]);
47
36
  return (_jsxs("div", { onMouseDown: handleStart, onTouchStart: handleStart, className: `
48
37
  relative w-[310px] bg-[#161617] rounded-2xl transition-all duration-300 ease-out
@@ -1,13 +1,10 @@
1
- interface DocumentNodeProps {
2
- id: string;
1
+ import type { BaseNodeProps } from "./types/overlay-types";
2
+ interface DocumentNodeProps extends BaseNodeProps {
3
3
  title: string;
4
4
  project: string;
5
5
  breadcrumb?: string[];
6
6
  preview: string;
7
7
  updatedAt?: string;
8
- isSelected?: boolean;
9
- onSelect?: (id: string, e?: React.MouseEvent) => void;
10
- onDragStart?: (id: string, e: React.MouseEvent | React.TouchEvent) => void;
11
8
  }
12
9
  export default function DocumentNode({ id, title, project, breadcrumb, preview, updatedAt, isSelected, onSelect, onDragStart, }: DocumentNodeProps): import("react/jsx-runtime").JSX.Element;
13
10
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"document-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/document-node.tsx"],"names":[],"mappings":"AAKE,UAAU,iBAAiB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACtD,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;CAC5E;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,OAAO,EACP,UAAe,EACf,OAAO,EACP,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,GACZ,EAAE,iBAAiB,2CAuJnB"}
1
+ {"version":3,"file":"document-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/document-node.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,iBAAkB,SAAQ,aAAa;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,OAAO,EACP,UAAe,EACf,OAAO,EACP,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,GACZ,EAAE,iBAAiB,2CAwHnB"}
@@ -5,54 +5,37 @@ import { ChevronDown, ChevronUp, Clock, ExternalLink, MoreHorizontal, Layers } f
5
5
  export default function DocumentNode({ id, title, project, breadcrumb = [], preview, updatedAt, isSelected = false, onSelect, onDragStart, }) {
6
6
  const [isDragging, setIsDragging] = useState(false);
7
7
  const [isExpanded, setIsExpanded] = useState(false);
8
- // 2. Update the internal handler
9
8
  const handleStart = (e) => {
10
9
  const target = e.target;
11
- // Don't drag if clicking buttons or the scrollable text
12
- if (target.closest('.interactive-element'))
10
+ if (target.closest(".interactive-element"))
13
11
  return;
14
- // For touch events, we don't want the browser to scroll the page
15
- // but we only preventDefault if it's not an interactive element
16
- if (e.type === 'touchstart' && e.cancelable) {
17
- // We don't preventDefault here because it might block clicks on
18
- // internal non-interactive elements, let handleDragStart handle it.
19
- }
20
12
  setIsDragging(true);
21
- if (onDragStart)
22
- onDragStart(id, e);
23
- if (onSelect)
24
- onSelect(id, e); // Cast for compatibility
13
+ onDragStart?.(id, e);
14
+ onSelect?.(id, e);
25
15
  };
26
- // 3. Update the cleanup effect
27
16
  useEffect(() => {
17
+ if (!isDragging)
18
+ return;
28
19
  const handleUp = () => setIsDragging(false);
29
- if (isDragging) {
30
- window.addEventListener("mouseup", handleUp);
31
- window.addEventListener("touchend", handleUp); // Add touch cleanup
32
- return () => {
33
- window.removeEventListener("mouseup", handleUp);
34
- window.removeEventListener("touchend", handleUp);
35
- };
36
- }
20
+ window.addEventListener("mouseup", handleUp);
21
+ window.addEventListener("touchend", handleUp);
22
+ return () => {
23
+ window.removeEventListener("mouseup", handleUp);
24
+ window.removeEventListener("touchend", handleUp);
25
+ };
37
26
  }, [isDragging]);
38
- return (_jsxs("div", { onMouseDown: handleStart, onTouchStart: handleStart, className: `
39
- relative w-[320px] bg-[#121214] rounded-2xl border transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)]
40
- ${isDragging ? "cursor-grabbing scale-[1.03] z-50 shadow-2xl" : "cursor-grab shadow-lg hover:border-[#3e3e42]"}
41
- ${isSelected ? "border-[#029AFF] ring-1 ring-[#029AFF]" : "border-[#28282b]"}
42
- ${isExpanded ? "w-[400px]" : "w-[320px]"}
43
- `, children: [isSelected && (_jsx("div", { className: "absolute -inset-px bg-gradient-to-r from-[#029AFF]/20 to-[#7000FF]/20 rounded-2xl blur-md -z-10" })), _jsxs("div", { className: "flex items-center justify-between p-3 pb-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "p-1.5 bg-[#029AFF]/10 rounded-lg", children: _jsx(Layers, { className: "w-3.5 h-3.5 text-[#029AFF]" }) }), _jsx("span", { className: "text-[10px] font-bold text-gray-500 tracking-tighter uppercase", children: project })] }), _jsx("button", { className: "interactive-element p-1 hover:bg-white/5 rounded-md text-gray-600 transition-colors", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) })] }), _jsxs("div", { className: "p-4 pt-2", children: [_jsxs("div", { onClick: () => setIsExpanded(!isExpanded), className: "interactive-element flex items-start justify-between group/title cursor-pointer", children: [_jsx("h3", { className: "text-[16px] font-semibold text-white leading-tight group-hover/title:text-[#029AFF] transition-colors pr-2", children: title }), _jsx("div", { className: "mt-1", children: isExpanded ?
44
- _jsx(ChevronUp, { className: "w-4 h-4 text-gray-500" }) :
45
- _jsx(ChevronDown, { className: "w-4 h-4 text-gray-500" }) })] }), _jsx("div", { className: "flex items-center gap-2 mt-2 overflow-x-auto no-scrollbar pb-1", children: breadcrumb.map((item, i) => (_jsx("div", { className: "flex items-center gap-2 flex-shrink-0", children: _jsx("span", { className: "text-[10px] px-2 py-0.5 bg-[#1C1C1E] border border-[#2C2C2E] text-gray-400 rounded-full", children: item }) }, i))) }), _jsxs("div", { className: `
46
- mt-4 relative transition-all duration-500 ease-in-out overflow-hidden
47
- ${isExpanded ? "h-[300px]" : "h-[100px]"}
48
- `, children: [_jsxs("div", { onMouseDown: (e) => e.stopPropagation(), className: "interactive-element h-full w-full bg-[#09090A] border border-[#232326] rounded-xl overflow-y-auto p-3 text-[13px] text-gray-400 leading-relaxed custom-scrollbar selection:bg-[#029AFF]/40", children: [preview, isExpanded && (_jsxs("div", { className: "mt-4 pt-4 border-t border-[#232326] flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] text-gray-500 italic", children: "End of document preview" }), _jsxs("button", { className: "flex items-center gap-2 text-[11px] text-[#029AFF] hover:underline font-medium", children: ["Open Full File ", _jsx(ExternalLink, { className: "w-3 h-3" })] })] }))] }), !isExpanded && (_jsx("div", { className: "absolute bottom-0 left-0 right-0 h-10 bg-gradient-to-t from-[#09090A] to-transparent pointer-events-none" }))] }), _jsxs("div", { className: "mt-4 flex items-center justify-between opacity-60", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-gray-400", children: [_jsx(Clock, { className: "w-3 h-3" }), _jsxs("span", { children: ["Updated ", updatedAt || "recently"] })] }), _jsx("div", { className: "flex -space-x-1.5", children: [1, 2].map(i => (_jsxs("div", { className: "w-5 h-5 rounded-full border-2 border-[#121214] bg-[#2C2C2E] flex items-center justify-center text-[8px] text-white", children: ["U", i] }, i))) })] })] }), _jsx("style", { children: `
49
- .no-scrollbar::-webkit-scrollbar { display: none; }
50
- .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
51
- .custom-scrollbar::-webkit-scrollbar { width: 5px; }
52
- .custom-scrollbar::-webkit-scrollbar-thumb {
53
- background: #232326;
54
- border-radius: 10px;
55
- }
56
- .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #029AFF; }
57
- ` })] }));
27
+ return (_jsxs("div", { onMouseDown: handleStart, onTouchStart: handleStart, className: `
28
+ relative w-[320px] bg-[#121214] rounded-2xl border transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)]
29
+ ${isDragging ? "cursor-grabbing scale-[1.03] z-50 shadow-2xl" : "cursor-grab shadow-lg hover:border-[#3e3e42]"}
30
+ ${isSelected ? "border-[#029AFF] ring-1 ring-[#029AFF]" : "border-[#28282b]"}
31
+ ${isExpanded ? "w-[400px]" : "w-[320px]"}
32
+ `, children: [isSelected && (_jsx("div", { className: "absolute -inset-px bg-gradient-to-r from-[#029AFF]/20 to-[#7000FF]/20 rounded-2xl blur-md -z-10" })), _jsxs("div", { className: "flex items-center justify-between p-3 pb-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "p-1.5 bg-[#029AFF]/10 rounded-lg", children: _jsx(Layers, { className: "w-3.5 h-3.5 text-[#029AFF]" }) }), _jsx("span", { className: "text-[10px] font-bold text-gray-500 tracking-tighter uppercase", children: project })] }), _jsx("button", { className: "interactive-element p-1 hover:bg-white/5 rounded-md text-gray-600 transition-colors", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) })] }), _jsxs("div", { className: "p-4 pt-2", children: [_jsxs("div", { onClick: () => setIsExpanded(!isExpanded), className: "interactive-element flex items-start justify-between group/title cursor-pointer", children: [_jsx("h3", { className: "text-[16px] font-semibold text-white leading-tight group-hover/title:text-[#029AFF] transition-colors pr-2", children: title }), _jsx("div", { className: "mt-1", children: isExpanded
33
+ ? _jsx(ChevronUp, { className: "w-4 h-4 text-gray-500" })
34
+ : _jsx(ChevronDown, { className: "w-4 h-4 text-gray-500" }) })] }), _jsx("div", { className: "flex items-center gap-2 mt-2 overflow-x-auto no-scrollbar pb-1", children: breadcrumb.map((item, i) => (_jsx("div", { className: "flex items-center gap-2 flex-shrink-0", children: _jsx("span", { className: "text-[10px] px-2 py-0.5 bg-[#1C1C1E] border border-[#2C2C2E] text-gray-400 rounded-full", children: item }) }, i))) }), _jsxs("div", { className: `mt-4 relative transition-all duration-500 ease-in-out overflow-hidden ${isExpanded ? "h-[300px]" : "h-[100px]"}`, children: [_jsxs("div", { onMouseDown: (e) => e.stopPropagation(), className: "interactive-element h-full w-full bg-[#09090A] border border-[#232326] rounded-xl overflow-y-auto p-3 text-[13px] text-gray-400 leading-relaxed custom-scrollbar selection:bg-[#029AFF]/40", children: [preview, isExpanded && (_jsxs("div", { className: "mt-4 pt-4 border-t border-[#232326] flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] text-gray-500 italic", children: "End of document preview" }), _jsxs("button", { className: "flex items-center gap-2 text-[11px] text-[#029AFF] hover:underline font-medium", children: ["Open Full File ", _jsx(ExternalLink, { className: "w-3 h-3" })] })] }))] }), !isExpanded && (_jsx("div", { className: "absolute bottom-0 left-0 right-0 h-10 bg-gradient-to-t from-[#09090A] to-transparent pointer-events-none" }))] }), _jsxs("div", { className: "mt-4 flex items-center justify-between opacity-60", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-gray-400", children: [_jsx(Clock, { className: "w-3 h-3" }), _jsxs("span", { children: ["Updated ", updatedAt || "recently"] })] }), _jsx("div", { className: "flex -space-x-1.5", children: [1, 2].map((i) => (_jsxs("div", { className: "w-5 h-5 rounded-full border-2 border-[#121214] bg-[#2C2C2E] flex items-center justify-center text-[8px] text-white", children: ["U", i] }, i))) })] })] }), _jsx("style", { children: `
35
+ .no-scrollbar::-webkit-scrollbar { display: none; }
36
+ .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
37
+ .custom-scrollbar::-webkit-scrollbar { width: 5px; }
38
+ .custom-scrollbar::-webkit-scrollbar-thumb { background: #232326; border-radius: 10px; }
39
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #029AFF; }
40
+ ` })] }));
58
41
  }
@@ -0,0 +1,30 @@
1
+ import type { RefObject, MutableRefObject, Dispatch, SetStateAction } from "react";
2
+ import type { Canvas } from "fabric";
3
+ import type { Task, Document } from "../types/overlay-types";
4
+ interface UseFabricSyncProps {
5
+ fabricCanvas?: RefObject<Canvas | null>;
6
+ canvasReady: boolean;
7
+ dragSelectedIdsRef: MutableRefObject<Set<string>>;
8
+ selectedIdsRef: MutableRefObject<Set<string>>;
9
+ isHtmlSelectingRef: MutableRefObject<boolean>;
10
+ isSelectionBoxActiveRef: MutableRefObject<boolean>;
11
+ htmlNodesSelectedByBoxRef: MutableRefObject<boolean>;
12
+ setSelectedIds: Dispatch<SetStateAction<Set<string>>>;
13
+ setLocalTasks: Dispatch<SetStateAction<Task[]>>;
14
+ setLocalDocuments: Dispatch<SetStateAction<Document[]>>;
15
+ }
16
+ /**
17
+ * SRP: listens to Fabric canvas events and propagates changes to the HTML
18
+ * overlay state. Handles four events:
19
+ *
20
+ * object:moving — mirrors Fabric object movement onto HTML node positions
21
+ * (rAF-throttled, uses dragSelectedIdsRef snapshot)
22
+ * mouse:down — clears HTML selection when clicking a Fabric object,
23
+ * respects guard refs to avoid feedback loops
24
+ * selection:created — clears HTML selection when Fabric creates a selection,
25
+ * skipped when the HTML layer initiated the selection
26
+ * selection:updated — same as above for selection updates
27
+ */
28
+ export declare function useFabricSync({ fabricCanvas, canvasReady, dragSelectedIdsRef, selectedIdsRef, isHtmlSelectingRef, isSelectionBoxActiveRef, htmlNodesSelectedByBoxRef, setSelectedIds, setLocalTasks, setLocalDocuments, }: UseFabricSyncProps): void;
29
+ export {};
30
+ //# sourceMappingURL=useFabricSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFabricSync.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useFabricSync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACnF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE7D,UAAU,kBAAkB;IAC1B,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,kBAAkB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC9C,uBAAuB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnD,yBAAyB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrD,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;CACzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,EACzB,cAAc,EACd,aAAa,EACb,iBAAiB,GAClB,EAAE,kBAAkB,QAmFpB"}
@@ -0,0 +1,89 @@
1
+ import { useEffect, useRef } from "react";
2
+ /**
3
+ * SRP: listens to Fabric canvas events and propagates changes to the HTML
4
+ * overlay state. Handles four events:
5
+ *
6
+ * object:moving — mirrors Fabric object movement onto HTML node positions
7
+ * (rAF-throttled, uses dragSelectedIdsRef snapshot)
8
+ * mouse:down — clears HTML selection when clicking a Fabric object,
9
+ * respects guard refs to avoid feedback loops
10
+ * selection:created — clears HTML selection when Fabric creates a selection,
11
+ * skipped when the HTML layer initiated the selection
12
+ * selection:updated — same as above for selection updates
13
+ */
14
+ export function useFabricSync({ fabricCanvas, canvasReady, dragSelectedIdsRef, selectedIdsRef, isHtmlSelectingRef, isSelectionBoxActiveRef, htmlNodesSelectedByBoxRef, setSelectedIds, setLocalTasks, setLocalDocuments, }) {
15
+ const fabricMoveRafRef = useRef(null);
16
+ useEffect(() => {
17
+ const canvas = fabricCanvas?.current;
18
+ if (!canvas)
19
+ return;
20
+ const handleObjectMoving = (e) => {
21
+ const target = e.transform?.target || e.target;
22
+ if (!target)
23
+ return;
24
+ const deltaX = target.left - (target._prevLeft ?? target.left);
25
+ const deltaY = target.top - (target._prevTop ?? target.top);
26
+ target._prevLeft = target.left;
27
+ target._prevTop = target.top;
28
+ if (deltaX === 0 && deltaY === 0)
29
+ return;
30
+ const sel = dragSelectedIdsRef.current;
31
+ if (fabricMoveRafRef.current !== null)
32
+ return;
33
+ fabricMoveRafRef.current = requestAnimationFrame(() => {
34
+ fabricMoveRafRef.current = null;
35
+ setLocalTasks((prev) => prev.map((t) => (sel.has(t.id) ? { ...t, x: t.x + deltaX, y: t.y + deltaY } : t)));
36
+ setLocalDocuments((prev) => prev.map((d) => (sel.has(d.id) ? { ...d, x: d.x + deltaX, y: d.y + deltaY } : d)));
37
+ });
38
+ };
39
+ const handleMouseDown = (e) => {
40
+ const target = e.target;
41
+ if (target) {
42
+ target._prevLeft = target.left;
43
+ target._prevTop = target.top;
44
+ }
45
+ const activeObjects = canvas.getActiveObjects();
46
+ const isClickingIntoActiveSelection = target && activeObjects.length > 1 && activeObjects.includes(target);
47
+ if (isClickingIntoActiveSelection) {
48
+ dragSelectedIdsRef.current = new Set(selectedIdsRef.current);
49
+ return;
50
+ }
51
+ if (isSelectionBoxActiveRef.current)
52
+ return;
53
+ dragSelectedIdsRef.current = new Set(selectedIdsRef.current);
54
+ htmlNodesSelectedByBoxRef.current = false;
55
+ setSelectedIds(new Set());
56
+ };
57
+ const handleSelectionCreated = () => {
58
+ if (isHtmlSelectingRef.current)
59
+ return;
60
+ if (isSelectionBoxActiveRef.current)
61
+ return;
62
+ if (htmlNodesSelectedByBoxRef.current)
63
+ return;
64
+ setSelectedIds(new Set());
65
+ };
66
+ const handleSelectionUpdated = () => {
67
+ if (isHtmlSelectingRef.current)
68
+ return;
69
+ if (isSelectionBoxActiveRef.current)
70
+ return;
71
+ if (htmlNodesSelectedByBoxRef.current)
72
+ return;
73
+ setSelectedIds(new Set());
74
+ };
75
+ canvas.on("object:moving", handleObjectMoving);
76
+ canvas.on("mouse:down", handleMouseDown);
77
+ canvas.on("selection:created", handleSelectionCreated);
78
+ canvas.on("selection:updated", handleSelectionUpdated);
79
+ return () => {
80
+ canvas.off("object:moving", handleObjectMoving);
81
+ canvas.off("mouse:down", handleMouseDown);
82
+ canvas.off("selection:created", handleSelectionCreated);
83
+ canvas.off("selection:updated", handleSelectionUpdated);
84
+ if (fabricMoveRafRef.current !== null) {
85
+ cancelAnimationFrame(fabricMoveRafRef.current);
86
+ }
87
+ };
88
+ }, [fabricCanvas, canvasReady]);
89
+ }
@@ -1,12 +1,26 @@
1
- import { Task, Document } from "../types/overlay-types";
1
+ import type { MutableRefObject, Dispatch, SetStateAction } from "react";
2
+ import type { Task, Document } from "../types/overlay-types";
2
3
  interface UseKeyboardShortcutsProps {
3
- selectedIds: Set<string>;
4
- localTasks: Task[];
5
- localDocuments: Document[];
6
- onSelectAll: () => void;
7
- onClearSelection: () => void;
8
- onDeleteSelected: (tasks: Task[], docs: Document[]) => void;
4
+ localTasksRef: MutableRefObject<Task[]>;
5
+ localDocumentsRef: MutableRefObject<Document[]>;
6
+ selectedIdsRef: MutableRefObject<Set<string>>;
7
+ setSelectedIds: Dispatch<SetStateAction<Set<string>>>;
8
+ setLocalTasks: Dispatch<SetStateAction<Task[]>>;
9
+ setLocalDocuments: Dispatch<SetStateAction<Document[]>>;
10
+ onTasksUpdate?: (tasks: Task[]) => void;
11
+ onDocumentsUpdate?: (documents: Document[]) => void;
9
12
  }
10
- export declare function useKeyboardShortcuts({ selectedIds, localTasks, localDocuments, onSelectAll, onClearSelection, onDeleteSelected, }: UseKeyboardShortcutsProps): void;
13
+ /**
14
+ * SRP: handles global keyboard shortcuts for the overlay.
15
+ *
16
+ * Uses refs (not raw state values) so the event listener is registered once
17
+ * and never re-registered due to selection/data changes — only stable
18
+ * callback props trigger a re-bind.
19
+ *
20
+ * Ctrl/Cmd+A — select all HTML nodes
21
+ * Escape — clear selection
22
+ * Delete/Backspace — delete selected nodes
23
+ */
24
+ export declare function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selectedIdsRef, setSelectedIds, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }: UseKeyboardShortcutsProps): void;
11
25
  export {};
12
26
  //# sourceMappingURL=useKeyboardShortcuts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAExD,UAAU,yBAAyB;IACjC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,UAAU,EAAE,IAAI,EAAE,CAAC;IACnB,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,gBAAgB,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CAC7D;AAED,wBAAgB,oBAAoB,CAAC,EACnC,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,yBAAyB,QA2C3B"}
1
+ {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE7D,UAAU,yBAAyB;IACjC,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,yBAAyB,QAwC3B"}
@@ -1,37 +1,46 @@
1
1
  import { useEffect } from "react";
2
- export function useKeyboardShortcuts({ selectedIds, localTasks, localDocuments, onSelectAll, onClearSelection, onDeleteSelected, }) {
2
+ /**
3
+ * SRP: handles global keyboard shortcuts for the overlay.
4
+ *
5
+ * Uses refs (not raw state values) so the event listener is registered once
6
+ * and never re-registered due to selection/data changes — only stable
7
+ * callback props trigger a re-bind.
8
+ *
9
+ * Ctrl/Cmd+A — select all HTML nodes
10
+ * Escape — clear selection
11
+ * Delete/Backspace — delete selected nodes
12
+ */
13
+ export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selectedIdsRef, setSelectedIds, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }) {
3
14
  useEffect(() => {
4
15
  const handleKeyDown = (e) => {
5
- // Don't trigger if typing in input
6
16
  if (e.target instanceof HTMLInputElement ||
7
- e.target instanceof HTMLTextAreaElement) {
17
+ e.target instanceof HTMLTextAreaElement)
8
18
  return;
9
- }
10
- // Select All (Ctrl+A / Cmd+A)
11
19
  if ((e.ctrlKey || e.metaKey) && e.key === "a") {
12
20
  e.preventDefault();
13
- onSelectAll();
21
+ setSelectedIds(new Set([
22
+ ...localTasksRef.current.map((t) => t.id),
23
+ ...localDocumentsRef.current.map((d) => d.id),
24
+ ]));
25
+ return;
14
26
  }
15
- // Clear selection (Escape)
16
27
  if (e.key === "Escape") {
17
- onClearSelection();
28
+ setSelectedIds(new Set());
29
+ return;
18
30
  }
19
- // Delete selected nodes (Delete / Backspace)
20
- if ((e.key === "Delete" || e.key === "Backspace") && selectedIds.size > 0) {
31
+ if ((e.key === "Delete" || e.key === "Backspace") && selectedIdsRef.current.size > 0) {
21
32
  e.preventDefault();
22
- const updatedTasks = localTasks.filter((t) => !selectedIds.has(t.id));
23
- const updatedDocs = localDocuments.filter((d) => !selectedIds.has(d.id));
24
- onDeleteSelected(updatedTasks, updatedDocs);
33
+ const sel = selectedIdsRef.current;
34
+ const updatedTasks = localTasksRef.current.filter((t) => !sel.has(t.id));
35
+ const updatedDocs = localDocumentsRef.current.filter((d) => !sel.has(d.id));
36
+ setLocalTasks(updatedTasks);
37
+ setLocalDocuments(updatedDocs);
38
+ setSelectedIds(new Set());
39
+ onTasksUpdate?.(updatedTasks);
40
+ onDocumentsUpdate?.(updatedDocs);
25
41
  }
26
42
  };
27
43
  window.addEventListener("keydown", handleKeyDown);
28
44
  return () => window.removeEventListener("keydown", handleKeyDown);
29
- }, [
30
- selectedIds,
31
- localTasks,
32
- localDocuments,
33
- onSelectAll,
34
- onClearSelection,
35
- onDeleteSelected,
36
- ]);
45
+ }, [onTasksUpdate, onDocumentsUpdate]);
37
46
  }
@@ -1,25 +1,38 @@
1
- import { FabricObject, Canvas } from "fabric";
2
- import { DragState, PointerCoords, ViewportTransform } from "../types/overlay-types";
3
- import { Task, Document } from "../types/overlay-types";
1
+ import type { RefObject, MutableRefObject, Dispatch, SetStateAction, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from "react";
2
+ import type { Canvas } from "fabric";
3
+ import type { Task, Document } from "../types/overlay-types";
4
4
  interface UseNodeDragProps {
5
- selectedIds: Set<string>;
6
- fabricCanvas?: React.RefObject<Canvas | null>;
7
- selectedCanvasObjects: FabricObject[];
8
- onDragStateChange: (dragging: {
9
- itemIds: string[];
10
- } | null) => void;
5
+ selectedIdsRef: MutableRefObject<Set<string>>;
6
+ dragSelectedIdsRef: MutableRefObject<Set<string>>;
7
+ localTasksRef: MutableRefObject<Task[]>;
8
+ localDocumentsRef: MutableRefObject<Document[]>;
9
+ fabricCanvas?: RefObject<Canvas | null>;
10
+ setLocalTasks: Dispatch<SetStateAction<Task[]>>;
11
+ setLocalDocuments: Dispatch<SetStateAction<Document[]>>;
12
+ onTasksUpdate?: (tasks: Task[]) => void;
13
+ onDocumentsUpdate?: (documents: Document[]) => void;
11
14
  }
12
- export declare function useNodeDrag({ selectedIds, fabricCanvas, selectedCanvasObjects, onDragStateChange, }: UseNodeDragProps): {
13
- dragStateRef: import("react").RefObject<DragState>;
14
- rafIdRef: import("react").RefObject<number | null>;
15
- getPointerEvent: (e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent) => PointerCoords;
16
- getViewportTransform: (canvas: Canvas) => ViewportTransform;
17
- handleDragStart: (itemId: string, getItemPosition: (id: string) => {
15
+ /**
16
+ * SRP: owns the full HTML-node drag lifecycle — start, move (rAF-throttled),
17
+ * and end — while keeping Fabric canvas objects in sync imperatively.
18
+ *
19
+ * Key design decisions:
20
+ * - All world-space math reads live viewport transform from Fabric so zoom/pan
21
+ * during a drag stays correct without extra state dependencies.
22
+ * - startPositions + anchor-offset approach prevents the "jump on drag start"
23
+ * bug that occurs when using raw mouse coords.
24
+ * - activeSelection group positions are resolved via calcTransformMatrix so
25
+ * multi-selected Fabric objects stay in sync when dragged from HTML nodes.
26
+ */
27
+ export declare function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, localTasksRef, localDocumentsRef, fabricCanvas, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }: UseNodeDragProps): {
28
+ dragging: {
29
+ itemIds: string[];
30
+ } | null;
31
+ getItemPosition: (id: string) => {
18
32
  x: number;
19
33
  y: number;
20
- } | undefined, e: React.MouseEvent | React.TouchEvent) => void;
21
- handleDragMove: (pointer: PointerCoords, onTasksUpdate: (tasks: Task[], docs: Document[]) => void) => void;
22
- handleDragEnd: () => void;
34
+ } | undefined;
35
+ handleDragStart: (itemId: string, e: ReactMouseEvent | ReactTouchEvent) => void;
23
36
  };
24
37
  export {};
25
38
  //# sourceMappingURL=useNodeDrag.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAExD,UAAU,gBAAgB;IACxB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,qBAAqB,EAAE,YAAY,EAAE,CAAC;IACtC,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;CACrE;AAED,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,GAClB,EAAE,gBAAgB;;;yBAaZ,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,GAAG,UAAU,GAAG,UAAU,KAC/D,aAAa;mCAasB,MAAM,KAAG,iBAAiB;8BAWpD,MAAM,mBACG,CAAC,EAAE,EAAE,MAAM,KAAK;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,KAClE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU;8BAqD7B,aAAa,iBACP,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI;;EAsD7D"}
1
+ {"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,UAAU,IAAI,eAAe,EAC7B,UAAU,IAAI,eAAe,EAC9B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAA4B,MAAM,wBAAwB,CAAC;AAEvF,UAAU,gBAAgB;IACxB,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAC1B,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB;;iBACmC,MAAM,EAAE;;0BAcrD,MAAM,KAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;8BAWzC,MAAM,KAAK,eAAe,GAAG,eAAe;EA2JxD"}