@flowtomic/logic 0.1.0 → 0.1.2

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 (46) hide show
  1. package/dist/hooks/useAnimatedIndicator/index.d.ts +3 -0
  2. package/dist/hooks/useAnimatedIndicator/index.d.ts.map +1 -0
  3. package/dist/hooks/useAnimatedIndicator/index.js +1 -0
  4. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.d.ts +94 -0
  5. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.d.ts.map +1 -0
  6. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.js +228 -0
  7. package/dist/hooks/useGenealogy/index.d.ts +3 -0
  8. package/dist/hooks/useGenealogy/index.d.ts.map +1 -0
  9. package/dist/hooks/useGenealogy/index.js +1 -0
  10. package/dist/hooks/useGenealogy/useGenealogy.d.ts +96 -0
  11. package/dist/hooks/useGenealogy/useGenealogy.d.ts.map +1 -0
  12. package/dist/hooks/useGenealogy/useGenealogy.js +335 -0
  13. package/dist/hooks/useMoblile/index.js +1 -868
  14. package/dist/hooks/useProjectProgress/index.d.ts +3 -0
  15. package/dist/hooks/useProjectProgress/index.d.ts.map +1 -0
  16. package/dist/hooks/useProjectProgress/index.js +1 -0
  17. package/dist/hooks/useProjectProgress/useProjectProgress.d.ts +103 -0
  18. package/dist/hooks/useProjectProgress/useProjectProgress.d.ts.map +1 -0
  19. package/dist/hooks/useProjectProgress/useProjectProgress.js +124 -0
  20. package/dist/hooks/useProjectStats/index.d.ts +3 -0
  21. package/dist/hooks/useProjectStats/index.d.ts.map +1 -0
  22. package/dist/hooks/useProjectStats/index.js +1 -0
  23. package/dist/hooks/useProjectStats/useProjectStats.d.ts +130 -0
  24. package/dist/hooks/useProjectStats/useProjectStats.d.ts.map +1 -0
  25. package/dist/hooks/useProjectStats/useProjectStats.js +87 -0
  26. package/dist/hooks/useResizable/index.d.ts +1 -1
  27. package/dist/hooks/useResizable/index.d.ts.map +1 -1
  28. package/dist/hooks/useScriptEditor/index.d.ts +3 -0
  29. package/dist/hooks/useScriptEditor/index.d.ts.map +1 -0
  30. package/dist/hooks/useScriptEditor/index.js +1 -0
  31. package/dist/hooks/useScriptEditor/useScriptEditor.d.ts +147 -0
  32. package/dist/hooks/useScriptEditor/useScriptEditor.d.ts.map +1 -0
  33. package/dist/hooks/useScriptEditor/useScriptEditor.js +247 -0
  34. package/dist/hooks/useStatCard/useStatCard.d.ts +44 -5
  35. package/dist/hooks/useStatCard/useStatCard.d.ts.map +1 -1
  36. package/dist/hooks/useStatCard/useStatCard.js +81 -20
  37. package/dist/hooks/useTimeTracker/index.d.ts +3 -0
  38. package/dist/hooks/useTimeTracker/index.d.ts.map +1 -0
  39. package/dist/hooks/useTimeTracker/index.js +1 -0
  40. package/dist/hooks/useTimeTracker/useTimeTracker.d.ts +106 -0
  41. package/dist/hooks/useTimeTracker/useTimeTracker.d.ts.map +1 -0
  42. package/dist/hooks/useTimeTracker/useTimeTracker.js +143 -0
  43. package/dist/index.d.ts +8 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -5
  46. package/package.json +2 -2
@@ -0,0 +1,335 @@
1
+ /**
2
+ * useGenealogy - Headless UI Hook
3
+ *
4
+ * Fornece apenas: lógica, estado, processamento e API para árvores genealógicas
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
+ import { useCallback, useMemo, useState } from "react";
10
+ /**
11
+ * Calcula o nível (geração) de cada entidade na hierarquia
12
+ * Retorna um Map com entityId -> nível (0 = raiz, 1 = filhos da raiz, etc)
13
+ */
14
+ function calculateLevels(hierarchy) {
15
+ const levels = new Map();
16
+ const visited = new Set();
17
+ // Função recursiva para calcular nível
18
+ const calculateLevel = (entityId) => {
19
+ if (levels.has(entityId)) {
20
+ return levels.get(entityId);
21
+ }
22
+ if (visited.has(entityId)) {
23
+ // Ciclo detectado, retornar 0 como fallback
24
+ return 0;
25
+ }
26
+ visited.add(entityId);
27
+ const entityHierarchy = hierarchy.get(entityId);
28
+ if (!entityHierarchy || entityHierarchy.parents.length === 0) {
29
+ // Sem pais = raiz (nível 0)
30
+ levels.set(entityId, 0);
31
+ return 0;
32
+ }
33
+ // Nível = nível do pai mais alto + 1
34
+ const parentLevels = entityHierarchy.parents.map((parent) => calculateLevel(parent.id));
35
+ const maxParentLevel = Math.max(...parentLevels);
36
+ const level = maxParentLevel + 1;
37
+ levels.set(entityId, level);
38
+ return level;
39
+ };
40
+ // Calcular nível para todas as entidades
41
+ hierarchy.forEach((_, entityId) => {
42
+ calculateLevel(entityId);
43
+ });
44
+ return levels;
45
+ }
46
+ /**
47
+ * Organiza entidades por nível (geração) e calcula posições hierárquicas
48
+ */
49
+ function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
50
+ const positions = new Map();
51
+ const levels = calculateLevels(hierarchy);
52
+ // Agrupar entidades por nível
53
+ const entitiesByLevel = new Map();
54
+ levels.forEach((level, entityId) => {
55
+ if (!entitiesByLevel.has(level)) {
56
+ entitiesByLevel.set(level, []);
57
+ }
58
+ entitiesByLevel.get(level).push(entityId);
59
+ });
60
+ // Calcular posições para cada nível, começando do nível 0 (raiz)
61
+ const sortedLevels = Array.from(entitiesByLevel.keys()).sort((a, b) => a - b);
62
+ sortedLevels.forEach((level) => {
63
+ const entityIds = entitiesByLevel.get(level);
64
+ // Ordenar entidades do mesmo nível (por ID para consistência)
65
+ entityIds.sort();
66
+ // Calcular posição Y baseada no nível (pais acima, filhos abaixo)
67
+ const y = level * (nodeHeight + verticalSpacing);
68
+ // Para cada entidade, tentar centralizar abaixo dos pais
69
+ entityIds.forEach((entityId) => {
70
+ const entityHierarchy = hierarchy.get(entityId);
71
+ let x = 0;
72
+ if (entityHierarchy && entityHierarchy.parents.length > 0) {
73
+ // Calcular posição média dos pais
74
+ const parentPositions = entityHierarchy.parents
75
+ .map((parent) => positions.get(parent.id))
76
+ .filter((pos) => pos !== undefined);
77
+ if (parentPositions.length > 0) {
78
+ const avgX = parentPositions.reduce((sum, pos) => sum + pos.x, 0) / parentPositions.length;
79
+ x = avgX;
80
+ }
81
+ }
82
+ // Se não tem pais ou não conseguiu calcular, distribuir horizontalmente
83
+ if (x === 0 && entityIds.length > 1) {
84
+ const index = entityIds.indexOf(entityId);
85
+ const totalWidth = entityIds.length * nodeWidth + (entityIds.length - 1) * horizontalSpacing;
86
+ const startX = -totalWidth / 2 + nodeWidth / 2;
87
+ x = startX + index * (nodeWidth + horizontalSpacing);
88
+ }
89
+ positions.set(entityId, { x, y });
90
+ });
91
+ // Ajustar posições para evitar sobreposição
92
+ entityIds.forEach((entityId, index) => {
93
+ const currentPos = positions.get(entityId);
94
+ const minX = currentPos.x;
95
+ // Verificar se há sobreposição com outros nós do mesmo nível
96
+ entityIds.slice(0, index).forEach((otherId) => {
97
+ const otherPos = positions.get(otherId);
98
+ if (Math.abs(currentPos.x - otherPos.x) < nodeWidth + horizontalSpacing) {
99
+ // Ajustar para evitar sobreposição
100
+ currentPos.x = otherPos.x + nodeWidth + horizontalSpacing;
101
+ }
102
+ });
103
+ });
104
+ });
105
+ return positions;
106
+ }
107
+ /**
108
+ * Gera nodes do ReactFlow a partir dos dados de genealogia
109
+ */
110
+ function generateNodes(data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
111
+ const nodes = [];
112
+ // Calcular posições hierárquicas
113
+ const positions = calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing);
114
+ // Criar nodes com posições calculadas
115
+ data.people.forEach((entity) => {
116
+ const position = positions.get(entity.id) || { x: 0, y: 0 };
117
+ const entityHierarchy = hierarchy.get(entity.id);
118
+ nodes.push({
119
+ id: entity.id,
120
+ type: "genealogy",
121
+ position,
122
+ data: {
123
+ person: entity,
124
+ hierarchy: entityHierarchy,
125
+ isExpanded: expansionState[entity.id] ?? false,
126
+ },
127
+ });
128
+ });
129
+ return nodes;
130
+ }
131
+ /**
132
+ * Gera edges do ReactFlow a partir dos relacionamentos
133
+ * Para hierarquia vertical: source (pai) -> target (filho)
134
+ */
135
+ function generateEdges(data, hierarchy, expansionState) {
136
+ const edges = [];
137
+ const edgeIds = new Set();
138
+ data.relationships.forEach((relationship) => {
139
+ // Determinar source e target baseado no tipo de relacionamento
140
+ let source;
141
+ let target;
142
+ switch (relationship.type) {
143
+ case "father":
144
+ case "mother": {
145
+ // from é pai/mãe de to
146
+ // source = pai (from), target = filho (to)
147
+ source = relationship.from;
148
+ target = relationship.to;
149
+ break;
150
+ }
151
+ case "child": {
152
+ // from é filho de to
153
+ // source = pai (to), target = filho (from)
154
+ source = relationship.to;
155
+ target = relationship.from;
156
+ break;
157
+ }
158
+ default: {
159
+ source = relationship.from;
160
+ target = relationship.to;
161
+ }
162
+ }
163
+ // Criar edge ID único
164
+ const edgeId = `e-${source}-${target}-${relationship.type}`;
165
+ // Evitar duplicatas
166
+ if (edgeIds.has(edgeId)) {
167
+ return;
168
+ }
169
+ edgeIds.add(edgeId);
170
+ // Verificar se ambos os nós existem
171
+ const sourceHierarchy = hierarchy.get(source);
172
+ const targetHierarchy = hierarchy.get(target);
173
+ if (!sourceHierarchy || !targetHierarchy) {
174
+ return;
175
+ }
176
+ // Só criar edge se ambos os nós estiverem visíveis (expandidos ou raiz)
177
+ const sourceExpanded = expansionState[source] ?? true;
178
+ const targetExpanded = expansionState[target] ?? true;
179
+ // Para relacionamentos pai/filho, só mostrar se o nó pai estiver expandido
180
+ if (relationship.type === "father" ||
181
+ relationship.type === "mother" ||
182
+ relationship.type === "child") {
183
+ if (!sourceExpanded || !targetExpanded)
184
+ return;
185
+ }
186
+ edges.push({
187
+ id: edgeId,
188
+ source,
189
+ target,
190
+ type: "animated",
191
+ data: {
192
+ relationship,
193
+ },
194
+ });
195
+ });
196
+ return edges;
197
+ }
198
+ /**
199
+ * Constrói hierarquia de entidades a partir dos dados
200
+ */
201
+ function buildHierarchy(data) {
202
+ const hierarchy = new Map();
203
+ // Inicializar todas as entidades
204
+ data.people.forEach((entity) => {
205
+ hierarchy.set(entity.id, {
206
+ person: entity,
207
+ parents: [],
208
+ children: [],
209
+ adoptions: [],
210
+ siblings: [],
211
+ });
212
+ });
213
+ // Processar relacionamentos
214
+ data.relationships.forEach((relationship) => {
215
+ const fromHierarchy = hierarchy.get(relationship.from);
216
+ const toHierarchy = hierarchy.get(relationship.to);
217
+ if (!fromHierarchy || !toHierarchy)
218
+ return;
219
+ switch (relationship.type) {
220
+ case "father":
221
+ case "mother": {
222
+ // from é pai/mãe de to
223
+ if (!toHierarchy.parents.find((p) => p.id === relationship.from)) {
224
+ toHierarchy.parents.push(fromHierarchy.person);
225
+ }
226
+ if (relationship.type === "father") {
227
+ toHierarchy.father = fromHierarchy.person;
228
+ }
229
+ else if (relationship.type === "mother") {
230
+ toHierarchy.mother = fromHierarchy.person;
231
+ }
232
+ if (!fromHierarchy.children.find((c) => c.id === relationship.to)) {
233
+ fromHierarchy.children.push(toHierarchy.person);
234
+ }
235
+ break;
236
+ }
237
+ case "child": {
238
+ // from é filho de to
239
+ if (!toHierarchy.children.find((c) => c.id === relationship.from)) {
240
+ toHierarchy.children.push(fromHierarchy.person);
241
+ }
242
+ if (!fromHierarchy.parents.find((p) => p.id === relationship.to)) {
243
+ fromHierarchy.parents.push(toHierarchy.person);
244
+ }
245
+ break;
246
+ }
247
+ }
248
+ });
249
+ // Calcular irmãos (pessoas com os mesmos pais)
250
+ hierarchy.forEach((personHierarchy) => {
251
+ if (personHierarchy.parents.length > 0) {
252
+ const parentIds = personHierarchy.parents.map((p) => p.id);
253
+ hierarchy.forEach((otherHierarchy, otherId) => {
254
+ if (otherId !== personHierarchy.person.id) {
255
+ const hasCommonParent = otherHierarchy.parents.some((p) => parentIds.includes(p.id));
256
+ if (hasCommonParent && !personHierarchy.siblings.find((s) => s.id === otherId)) {
257
+ personHierarchy.siblings.push(otherHierarchy.person);
258
+ }
259
+ }
260
+ });
261
+ }
262
+ });
263
+ return hierarchy;
264
+ }
265
+ /**
266
+ * Hook headless para gerenciar árvores genealógicas
267
+ */
268
+ export function useGenealogy({ data, initialExpanded = [], nodeWidth = 200, nodeHeight = 100, horizontalSpacing = 250, verticalSpacing = 150, onNodeSelect, onNodeExpand, onAddRelation, }) {
269
+ // Estado de expansão dos nós
270
+ const [expansionState, setExpansionState] = useState(() => {
271
+ const state = {};
272
+ initialExpanded.forEach((id) => {
273
+ state[id] = true;
274
+ });
275
+ return state;
276
+ });
277
+ // Estado de seleção
278
+ const [selectedNodeId, setSelectedNodeId] = useState(null);
279
+ // Construir hierarquia
280
+ const hierarchy = useMemo(() => buildHierarchy(data), [data]);
281
+ // Gerar nodes e edges
282
+ const nodes = useMemo(() => generateNodes(data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing), [data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing]);
283
+ const edges = useMemo(() => generateEdges(data, hierarchy, expansionState), [data, hierarchy, expansionState]);
284
+ // Função para selecionar um nó
285
+ const selectNode = useCallback((nodeId) => {
286
+ setSelectedNodeId(nodeId);
287
+ const entity = data.people.find((p) => p.id === nodeId);
288
+ if (entity && onNodeSelect) {
289
+ onNodeSelect(nodeId, entity);
290
+ }
291
+ }, [data.people, onNodeSelect]);
292
+ // Função para expandir/colapsar um nó
293
+ const expandNode = useCallback((nodeId, expanded) => {
294
+ setExpansionState((prev) => {
295
+ const newState = { ...prev };
296
+ const isCurrentlyExpanded = prev[nodeId] ?? false;
297
+ newState[nodeId] = expanded ?? !isCurrentlyExpanded;
298
+ if (onNodeExpand) {
299
+ onNodeExpand(nodeId, newState[nodeId]);
300
+ }
301
+ return newState;
302
+ });
303
+ }, [onNodeExpand]);
304
+ // Função para colapsar um nó
305
+ const collapseNode = useCallback((nodeId) => {
306
+ expandNode(nodeId, false);
307
+ }, [expandNode]);
308
+ // Função para adicionar relacionamento
309
+ const addRelation = useCallback((fromId, toId, type) => {
310
+ if (onAddRelation) {
311
+ onAddRelation(fromId, toId, type);
312
+ }
313
+ }, [onAddRelation]);
314
+ // Função para obter hierarquia de uma pessoa
315
+ const getPersonHierarchy = useCallback((personId) => {
316
+ return hierarchy.get(personId);
317
+ }, [hierarchy]);
318
+ // Função para obter relacionamentos de uma pessoa
319
+ const getPersonRelations = useCallback((personId) => {
320
+ return data.relationships.filter((r) => r.from === personId || r.to === personId);
321
+ }, [data.relationships]);
322
+ return {
323
+ nodes,
324
+ edges,
325
+ hierarchy,
326
+ selectedNodeId,
327
+ expansionState,
328
+ selectNode,
329
+ expandNode,
330
+ collapseNode,
331
+ addRelation,
332
+ getPersonHierarchy,
333
+ getPersonRelations,
334
+ };
335
+ }