@flowtomic/logic 0.1.2 → 0.1.4

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.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * useDashboardLayout - Headless UI Hook
3
+ *
4
+ * Fornece apenas: lógica, estado, processamento e API
5
+ * NÃO fornece: markup, styles ou implementações pré-construídas
6
+ *
7
+ * Padrão Headless UI: você controla o markup e styles
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function MyDashboard() {
12
+ * const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
13
+ * const gridConfig: GridConfig = { columns: 12, cellSize: 50, gap: 16 };
14
+ *
15
+ * const {
16
+ * getWidgetGridPosition,
17
+ * moveWidget,
18
+ * resizeWidget,
19
+ * findNextAvailablePosition,
20
+ * } = useDashboardLayout({
21
+ * widgetsLayout: widgets,
22
+ * gridConfig,
23
+ * onUpdateLayout: setWidgets,
24
+ * });
25
+ *
26
+ * return (
27
+ * <div>
28
+ * {widgets.map(widget => (
29
+ * <div style={getWidgetGridPosition(widget)}>
30
+ * Widget Content
31
+ * </div>
32
+ * ))}
33
+ * </div>
34
+ * );
35
+ * }
36
+ * ```
37
+ */
38
+ export interface WidgetLayout {
39
+ id: string;
40
+ type: string;
41
+ x: number;
42
+ y: number;
43
+ w: number;
44
+ h: number;
45
+ config?: Record<string, unknown>;
46
+ minimized?: boolean;
47
+ }
48
+ export interface GridConfig {
49
+ columns: number;
50
+ cellSize?: number;
51
+ gap?: number;
52
+ }
53
+ export interface UseDashboardLayoutProps {
54
+ /**
55
+ * Layout atual de widgets
56
+ */
57
+ widgetsLayout: WidgetLayout[];
58
+ /**
59
+ * Configuração do grid
60
+ */
61
+ gridConfig: GridConfig;
62
+ /**
63
+ * Callback para atualizar o layout completo
64
+ */
65
+ onUpdateLayout: (layout: WidgetLayout[]) => void;
66
+ }
67
+ export interface UseDashboardLayoutReturn {
68
+ /**
69
+ * Configuração do grid (normalizada com defaults)
70
+ */
71
+ gridConfig: GridConfig;
72
+ /**
73
+ * Calcula posição CSS Grid para um widget
74
+ */
75
+ getWidgetGridPosition: (widget: WidgetLayout) => {
76
+ gridColumnStart: number;
77
+ gridColumnEnd: number;
78
+ gridRowStart: number;
79
+ gridRowEnd: number;
80
+ };
81
+ /**
82
+ * Verifica se uma posição está ocupada
83
+ */
84
+ isPositionOccupied: (x: number, y: number, w: number, h: number, excludeWidgetId?: string) => boolean;
85
+ /**
86
+ * Encontra próxima posição disponível
87
+ */
88
+ findNextAvailablePosition: (w: number, h: number) => {
89
+ x: number;
90
+ y: number;
91
+ } | null;
92
+ /**
93
+ * Valida se um widget pode ser movido para uma posição
94
+ */
95
+ canMoveToPosition: (widgetId: string, x: number, y: number) => boolean;
96
+ /**
97
+ * Valida se um widget pode ser redimensionado
98
+ */
99
+ canResize: (widgetId: string, newW: number, newH: number) => boolean;
100
+ /**
101
+ * Move widget para nova posição
102
+ */
103
+ moveWidget: (widgetId: string, x: number, y: number) => void;
104
+ /**
105
+ * Redimensiona widget
106
+ */
107
+ resizeWidget: (widgetId: string, w: number, h: number) => void;
108
+ /**
109
+ * Atualiza layout completo
110
+ */
111
+ updateLayout: (layout: WidgetLayout[]) => void;
112
+ }
113
+ /**
114
+ * Hook para gerenciar layout do dashboard
115
+ *
116
+ * Hook headless que fornece apenas lógica de layout, sem UI.
117
+ * Você controla o markup e styles.
118
+ */
119
+ export declare function useDashboardLayout({ widgetsLayout, gridConfig, onUpdateLayout, }: UseDashboardLayoutProps): UseDashboardLayoutReturn;
120
+ //# sourceMappingURL=useDashboardLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDashboardLayout.d.ts","sourceRoot":"","sources":["../../../src/hooks/useDashboardLayout/useDashboardLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAOH,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,uBAAuB;IACvC;;OAEG;IACH,aAAa,EAAE,YAAY,EAAE,CAAC;IAE9B;;OAEG;IACH,UAAU,EAAE,UAAU,CAAC;IAEvB;;OAEG;IACH,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IACxC;;OAEG;IACH,UAAU,EAAE,UAAU,CAAC;IAEvB;;OAEG;IACH,qBAAqB,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK;QAChD,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACnB,CAAC;IAEF;;OAEG;IACH,kBAAkB,EAAE,CACnB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,eAAe,CAAC,EAAE,MAAM,KACpB,OAAO,CAAC;IAEb;;OAEG;IACH,yBAAyB,EAAE,CAC1B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,KACL;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAErC;;OAEG;IACH,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvE;;OAEG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAErE;;OAEG;IACH,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7D;;OAEG;IACH,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/D;;OAEG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;CAC/C;AAQD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAClC,aAAa,EACb,UAAU,EACV,cAAc,GACd,EAAE,uBAAuB,GAAG,wBAAwB,CA+KpD"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * useDashboardLayout - Headless UI Hook
3
+ *
4
+ * Fornece apenas: lógica, estado, processamento e API
5
+ * NÃO fornece: markup, styles ou implementações pré-construídas
6
+ *
7
+ * Padrão Headless UI: você controla o markup e styles
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function MyDashboard() {
12
+ * const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
13
+ * const gridConfig: GridConfig = { columns: 12, cellSize: 50, gap: 16 };
14
+ *
15
+ * const {
16
+ * getWidgetGridPosition,
17
+ * moveWidget,
18
+ * resizeWidget,
19
+ * findNextAvailablePosition,
20
+ * } = useDashboardLayout({
21
+ * widgetsLayout: widgets,
22
+ * gridConfig,
23
+ * onUpdateLayout: setWidgets,
24
+ * });
25
+ *
26
+ * return (
27
+ * <div>
28
+ * {widgets.map(widget => (
29
+ * <div style={getWidgetGridPosition(widget)}>
30
+ * Widget Content
31
+ * </div>
32
+ * ))}
33
+ * </div>
34
+ * );
35
+ * }
36
+ * ```
37
+ */
38
+ import { useCallback, useMemo } from "react";
39
+ const DEFAULT_GRID_CONFIG = {
40
+ columns: 12,
41
+ cellSize: 50,
42
+ gap: 16,
43
+ };
44
+ /**
45
+ * Hook para gerenciar layout do dashboard
46
+ *
47
+ * Hook headless que fornece apenas lógica de layout, sem UI.
48
+ * Você controla o markup e styles.
49
+ */
50
+ export function useDashboardLayout({ widgetsLayout, gridConfig, onUpdateLayout, }) {
51
+ // Normaliza gridConfig com defaults
52
+ const normalizedGridConfig = useMemo(() => {
53
+ return {
54
+ ...DEFAULT_GRID_CONFIG,
55
+ ...gridConfig,
56
+ };
57
+ }, [gridConfig]);
58
+ /**
59
+ * Calcula posição CSS Grid para um widget
60
+ */
61
+ const getWidgetGridPosition = useCallback((widget) => {
62
+ return {
63
+ gridColumnStart: widget.x + 1, // CSS Grid é 1-indexed
64
+ gridColumnEnd: widget.x + widget.w + 1,
65
+ gridRowStart: widget.y + 1,
66
+ gridRowEnd: widget.y + widget.h + 1,
67
+ };
68
+ }, []);
69
+ /**
70
+ * Verifica se uma posição está ocupada
71
+ */
72
+ const isPositionOccupied = useCallback((x, y, w, h, excludeWidgetId) => {
73
+ return widgetsLayout.some((widget) => {
74
+ if (excludeWidgetId && widget.id === excludeWidgetId) {
75
+ return false;
76
+ }
77
+ // Verifica sobreposição
78
+ const widgetRight = widget.x + widget.w;
79
+ const widgetBottom = widget.y + widget.h;
80
+ const newRight = x + w;
81
+ const newBottom = y + h;
82
+ return !(widgetRight <= x ||
83
+ widget.x >= newRight ||
84
+ widgetBottom <= y ||
85
+ widget.y >= newBottom);
86
+ });
87
+ }, [widgetsLayout]);
88
+ /**
89
+ * Encontra próxima posição disponível
90
+ */
91
+ const findNextAvailablePosition = useCallback((w, h) => {
92
+ const maxColumns = normalizedGridConfig.columns;
93
+ const maxRows = 100; // Limite razoável
94
+ for (let y = 0; y < maxRows; y++) {
95
+ for (let x = 0; x <= maxColumns - w; x++) {
96
+ if (!isPositionOccupied(x, y, w, h)) {
97
+ return { x, y };
98
+ }
99
+ }
100
+ }
101
+ return null;
102
+ }, [normalizedGridConfig.columns, isPositionOccupied]);
103
+ /**
104
+ * Valida se um widget pode ser movido para uma posição
105
+ */
106
+ const canMoveToPosition = useCallback((widgetId, x, y) => {
107
+ const widget = widgetsLayout.find((w) => w.id === widgetId);
108
+ if (!widget)
109
+ return false;
110
+ // Valida limites do grid
111
+ if (x < 0 || y < 0)
112
+ return false;
113
+ if (x + widget.w > normalizedGridConfig.columns)
114
+ return false;
115
+ // Verifica colisões
116
+ return !isPositionOccupied(x, y, widget.w, widget.h, widgetId);
117
+ }, [widgetsLayout, normalizedGridConfig.columns, isPositionOccupied]);
118
+ /**
119
+ * Valida se um widget pode ser redimensionado
120
+ */
121
+ const canResize = useCallback((widgetId, newW, newH) => {
122
+ const widget = widgetsLayout.find((w) => w.id === widgetId);
123
+ if (!widget)
124
+ return false;
125
+ // Valida tamanhos mínimos e máximos
126
+ if (newW < 2 || newH < 2)
127
+ return false; // Mínimo 2x2
128
+ if (newW > normalizedGridConfig.columns)
129
+ return false;
130
+ if (newH > 20)
131
+ return false; // Máximo 20 linhas
132
+ // Valida limites do grid
133
+ if (widget.x + newW > normalizedGridConfig.columns)
134
+ return false;
135
+ // Verifica colisões
136
+ return !isPositionOccupied(widget.x, widget.y, newW, newH, widgetId);
137
+ }, [widgetsLayout, normalizedGridConfig.columns, isPositionOccupied]);
138
+ /**
139
+ * Move widget para nova posição
140
+ */
141
+ const moveWidget = useCallback((widgetId, x, y) => {
142
+ if (!canMoveToPosition(widgetId, x, y)) {
143
+ return;
144
+ }
145
+ const updatedLayout = widgetsLayout.map((widget) => widget.id === widgetId ? { ...widget, x, y } : widget);
146
+ onUpdateLayout(updatedLayout);
147
+ }, [widgetsLayout, canMoveToPosition, onUpdateLayout]);
148
+ /**
149
+ * Redimensiona widget
150
+ */
151
+ const resizeWidget = useCallback((widgetId, w, h) => {
152
+ if (!canResize(widgetId, w, h)) {
153
+ return;
154
+ }
155
+ const updatedLayout = widgetsLayout.map((widget) => widget.id === widgetId ? { ...widget, w, h } : widget);
156
+ onUpdateLayout(updatedLayout);
157
+ }, [widgetsLayout, canResize, onUpdateLayout]);
158
+ /**
159
+ * Atualiza layout completo
160
+ */
161
+ const updateLayout = useCallback((layout) => {
162
+ onUpdateLayout(layout);
163
+ }, [onUpdateLayout]);
164
+ return {
165
+ gridConfig: normalizedGridConfig,
166
+ getWidgetGridPosition,
167
+ isPositionOccupied,
168
+ findNextAvailablePosition,
169
+ canMoveToPosition,
170
+ canResize,
171
+ moveWidget,
172
+ resizeWidget,
173
+ updateLayout,
174
+ };
175
+ }
@@ -16,8 +16,9 @@ function calculateLevels(hierarchy) {
16
16
  const visited = new Set();
17
17
  // Função recursiva para calcular nível
18
18
  const calculateLevel = (entityId) => {
19
- if (levels.has(entityId)) {
20
- return levels.get(entityId);
19
+ const cachedLevel = levels.get(entityId);
20
+ if (cachedLevel !== undefined) {
21
+ return cachedLevel;
21
22
  }
22
23
  if (visited.has(entityId)) {
23
24
  // Ciclo detectado, retornar 0 como fallback
@@ -46,7 +47,7 @@ function calculateLevels(hierarchy) {
46
47
  /**
47
48
  * Organiza entidades por nível (geração) e calcula posições hierárquicas
48
49
  */
49
- function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
50
+ function calculateHierarchicalPositions(_data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
50
51
  const positions = new Map();
51
52
  const levels = calculateLevels(hierarchy);
52
53
  // Agrupar entidades por nível
@@ -55,12 +56,14 @@ function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight,
55
56
  if (!entitiesByLevel.has(level)) {
56
57
  entitiesByLevel.set(level, []);
57
58
  }
58
- entitiesByLevel.get(level).push(entityId);
59
+ entitiesByLevel.get(level)?.push(entityId);
59
60
  });
60
61
  // Calcular posições para cada nível, começando do nível 0 (raiz)
61
62
  const sortedLevels = Array.from(entitiesByLevel.keys()).sort((a, b) => a - b);
62
63
  sortedLevels.forEach((level) => {
63
64
  const entityIds = entitiesByLevel.get(level);
65
+ if (!entityIds)
66
+ return;
64
67
  // Ordenar entidades do mesmo nível (por ID para consistência)
65
68
  entityIds.sort();
66
69
  // Calcular posição Y baseada no nível (pais acima, filhos abaixo)
@@ -91,10 +94,13 @@ function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight,
91
94
  // Ajustar posições para evitar sobreposição
92
95
  entityIds.forEach((entityId, index) => {
93
96
  const currentPos = positions.get(entityId);
94
- const minX = currentPos.x;
97
+ if (!currentPos)
98
+ return;
95
99
  // Verificar se há sobreposição com outros nós do mesmo nível
96
100
  entityIds.slice(0, index).forEach((otherId) => {
97
101
  const otherPos = positions.get(otherId);
102
+ if (!otherPos)
103
+ return;
98
104
  if (Math.abs(currentPos.x - otherPos.x) < nodeWidth + horizontalSpacing) {
99
105
  // Ajustar para evitar sobreposição
100
106
  currentPos.x = otherPos.x + nodeWidth + horizontalSpacing;
package/dist/index.d.ts CHANGED
@@ -15,4 +15,6 @@ export * from "./hooks/useResizable";
15
15
  export * from "./hooks/useScriptEditor";
16
16
  export * from "./hooks/useStatCard";
17
17
  export * from "./hooks/useTimeTracker";
18
+ export * from "./hooks/useAutocomplete";
19
+ export * from "./hooks/useDashboardLayout";
18
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AAErC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AAErC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC"}