@visualli/core 0.1.7 → 0.2.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 (127) hide show
  1. package/README.md +37 -105
  2. package/dist/chunk-25AJTPMS.js +68 -0
  3. package/dist/chunk-25AJTPMS.js.map +1 -0
  4. package/dist/{chunk-6NXADJK2.js → chunk-7NJWM7ZE.js} +84 -117
  5. package/dist/chunk-7NJWM7ZE.js.map +1 -0
  6. package/dist/chunk-KVSM3APM.js +96 -0
  7. package/dist/chunk-KVSM3APM.js.map +1 -0
  8. package/dist/{chunk-PDP5CVUL.js → chunk-LOQLNYB3.js} +2 -2
  9. package/dist/chunk-LV3EXUQY.js +753 -0
  10. package/dist/chunk-LV3EXUQY.js.map +1 -0
  11. package/dist/chunk-N7HCYULH.js +12 -0
  12. package/dist/chunk-N7HCYULH.js.map +1 -0
  13. package/dist/{chunk-I75TMOE4.js → chunk-P6PKWVYF.js} +2 -2
  14. package/dist/chunk-TPLJJKAU.js +151 -0
  15. package/dist/chunk-TPLJJKAU.js.map +1 -0
  16. package/dist/chunk-UAMEJG2C.js +267 -0
  17. package/dist/chunk-UAMEJG2C.js.map +1 -0
  18. package/dist/{chunk-LJYSKSCE.js → chunk-W2CAOOJJ.js} +6 -6
  19. package/dist/chunk-W2CAOOJJ.js.map +1 -0
  20. package/dist/config/index.cjs +250 -349
  21. package/dist/config/index.cjs.map +1 -1
  22. package/dist/config/index.d.cts +107 -66
  23. package/dist/config/index.d.ts +107 -66
  24. package/dist/config/index.js +52 -34
  25. package/dist/constants/index.cjs +14 -333
  26. package/dist/constants/index.cjs.map +1 -1
  27. package/dist/constants/index.d.cts +5 -243
  28. package/dist/constants/index.d.ts +5 -243
  29. package/dist/constants/index.js +5 -75
  30. package/dist/designSystem-BOrZHfEq.d.cts +150 -0
  31. package/dist/designSystem-BOrZHfEq.d.ts +150 -0
  32. package/dist/document-BFBwn93X.d.ts +26 -0
  33. package/dist/document-MEJEfxl3.d.cts +26 -0
  34. package/dist/ideaSize-CLDKeyqD.d.cts +11 -0
  35. package/dist/ideaSize-DxxGy2E8.d.ts +11 -0
  36. package/dist/index-BmILeKj6.d.cts +99 -0
  37. package/dist/index-CcPRwVUL.d.ts +99 -0
  38. package/dist/index.cjs +1636 -961
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +350 -27
  41. package/dist/index.d.ts +350 -27
  42. package/dist/index.js +434 -179
  43. package/dist/index.js.map +1 -1
  44. package/dist/{document-B3hnNr-F.d.cts → layer-B2A3Ki48.d.cts} +2 -16
  45. package/dist/{document-B3hnNr-F.d.ts → layer-B2A3Ki48.d.ts} +2 -16
  46. package/dist/layout/index.d.cts +2 -1
  47. package/dist/layout/index.d.ts +2 -1
  48. package/dist/mindmap-C3Qr0qKW.d.cts +182 -0
  49. package/dist/mindmap-DHeVa86G.d.ts +182 -0
  50. package/dist/parser/index.cjs +745 -179
  51. package/dist/parser/index.cjs.map +1 -1
  52. package/dist/parser/index.d.cts +23 -8
  53. package/dist/parser/index.d.ts +23 -8
  54. package/dist/parser/index.js +7 -2
  55. package/dist/performance/index.cjs +13 -0
  56. package/dist/performance/index.cjs.map +1 -1
  57. package/dist/performance/index.js +2 -2
  58. package/dist/theme/index.cjs +884 -0
  59. package/dist/theme/index.cjs.map +1 -0
  60. package/dist/theme/index.d.cts +2 -0
  61. package/dist/theme/index.d.ts +2 -0
  62. package/dist/theme/index.js +53 -0
  63. package/dist/types/index.d.cts +4 -2
  64. package/dist/types/index.d.ts +4 -2
  65. package/dist/viewport/index.cjs +17 -6
  66. package/dist/viewport/index.cjs.map +1 -1
  67. package/dist/viewport/index.d.cts +6 -4
  68. package/dist/viewport/index.d.ts +6 -4
  69. package/dist/viewport/index.js +2 -2
  70. package/licenses/visualli-design-system/LICENSE +9 -0
  71. package/licenses/visualli-design-system/NOTICE.md +6 -0
  72. package/package.json +12 -4
  73. package/src/config/README.md +2 -4
  74. package/src/config/USAGE_EXAMPLES.md +0 -4
  75. package/src/config/index.ts +3 -4
  76. package/src/constants/index.ts +1 -1
  77. package/src/constants/layout.ts +11 -0
  78. package/src/constants/performanceConstants.ts +8 -27
  79. package/src/generated/README.md +12 -0
  80. package/src/generated/designSystem.ts +758 -0
  81. package/src/generated/geometry/blob.ts +99 -0
  82. package/src/generated/geometry/blobShapes.ts +4 -0
  83. package/src/generated/geometry/color.ts +37 -0
  84. package/src/generated/geometry/container.ts +84 -0
  85. package/src/generated/geometry/detail.ts +22 -0
  86. package/src/generated/geometry/idea.ts +56 -0
  87. package/src/generated/geometry/interaction.ts +122 -0
  88. package/src/generated/geometry/motion.ts +140 -0
  89. package/src/index.ts +18 -1
  90. package/src/parser/configUtils.ts +12 -19
  91. package/src/parser/visualliConverter.ts +71 -112
  92. package/src/parser/visualliParser.ts +16 -1
  93. package/src/rendering/culling.ts +16 -12
  94. package/src/rendering/ideaSize.ts +21 -0
  95. package/src/rendering/nodeGeometry.ts +97 -0
  96. package/src/theme/index.ts +136 -0
  97. package/src/types/document.ts +9 -0
  98. package/src/types/layer.ts +1 -1
  99. package/src/types/mindmap.ts +13 -0
  100. package/src/utils/navigation.ts +33 -63
  101. package/src/viewport/viewportUtils.ts +6 -4
  102. package/dist/animations/index.cjs +0 -146
  103. package/dist/animations/index.cjs.map +0 -1
  104. package/dist/animations/index.d.cts +0 -81
  105. package/dist/animations/index.d.ts +0 -81
  106. package/dist/animations/index.js +0 -35
  107. package/dist/chunk-6NXADJK2.js.map +0 -1
  108. package/dist/chunk-F36SMC3K.js +0 -106
  109. package/dist/chunk-F36SMC3K.js.map +0 -1
  110. package/dist/chunk-G5H3P4YZ.js +0 -273
  111. package/dist/chunk-G5H3P4YZ.js.map +0 -1
  112. package/dist/chunk-LDJRGT2Q.js +0 -163
  113. package/dist/chunk-LDJRGT2Q.js.map +0 -1
  114. package/dist/chunk-LJYSKSCE.js.map +0 -1
  115. package/dist/chunk-LNTJTDB2.js +0 -349
  116. package/dist/chunk-LNTJTDB2.js.map +0 -1
  117. package/dist/mindmap-DWS0WZT_.d.cts +0 -118
  118. package/dist/mindmap-DWS0WZT_.d.ts +0 -118
  119. package/src/animations/constants.ts +0 -72
  120. package/src/animations/easing.ts +0 -70
  121. package/src/animations/index.ts +0 -2
  122. package/src/config/blobShapes.ts +0 -146
  123. package/src/config/textScaling.ts +0 -54
  124. package/src/constants/design.ts +0 -281
  125. /package/dist/{chunk-PDP5CVUL.js.map → chunk-LOQLNYB3.js.map} +0 -0
  126. /package/dist/{chunk-I75TMOE4.js.map → chunk-P6PKWVYF.js.map} +0 -0
  127. /package/dist/{animations → theme}/index.js.map +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser/visualliParser.ts","../src/parser/visualliConverter.ts","../src/parser/mindmapUtils.ts","../src/parser/configUtils.ts"],"sourcesContent":["// ─── .visualli File Parser ────────────────────────────────────────────────────\n//\n// Reads JSONL (JSON Lines) format and parses into a structured VisualliDocument.\n\nimport type { VisualliDocument, SemanticAnchor } from '../types/document.js';\nimport type { VisualliMeta } from '../types/meta.js';\nimport type { VisualliLayer } from '../types/layer.js';\nimport type { Layer } from '../types/schema.js';\n\n/**\n * Convert schema Layer to VisualliLayer by ensuring required arrays exist.\n */\nfunction toVisualliLayer(schemaLayer: Layer): VisualliLayer {\n return {\n type: 'layer',\n id: schemaLayer.id,\n level: schemaLayer.level,\n parentLayerId: schemaLayer.parentLayerId,\n parentNodeId: schemaLayer.parentNodeId,\n layout: schemaLayer.layout,\n nodes: schemaLayer.nodes ?? [],\n connections: schemaLayer.connections ?? [],\n containers: schemaLayer.containers ?? [],\n };\n}\n\nexport class VisualliParseError extends Error {\n constructor(message: string, public readonly lineNumber?: number) {\n super(message);\n this.name = 'VisualliParseError';\n }\n}\n\n/**\n * Parse a .visualli file content (JSONL format) into a VisualliDocument.\n *\n * @throws VisualliParseError on malformed input\n */\nexport function parseVisualliFile(content: string): VisualliDocument {\n const lines = content.trim().split('\\n');\n\n const doc: VisualliDocument = {\n meta: null as unknown as VisualliMeta,\n layers: new Map(),\n layersByLevel: new Map(),\n rootLayer: null,\n };\n\n let lineNumber = 0;\n\n for (const line of lines) {\n lineNumber++;\n if (!line.trim()) continue;\n\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(line) as Record<string, unknown>;\n } catch (err) {\n throw new VisualliParseError(\n `Failed to parse JSON at line ${lineNumber}: ${err}`,\n lineNumber,\n );\n }\n\n if (!obj['type']) {\n throw new VisualliParseError(\n `Missing 'type' field at line ${lineNumber}`,\n lineNumber,\n );\n }\n\n switch (obj['type']) {\n case 'meta':\n doc.meta = obj as unknown as VisualliMeta;\n break;\n\n case 'layer': {\n // Convert schema Layer to VisualliLayer\n const schemaLayer = obj as unknown as Layer;\n const layer = toVisualliLayer(schemaLayer);\n \n doc.layers.set(layer.id, layer);\n\n if (!doc.layersByLevel.has(layer.level)) {\n doc.layersByLevel.set(layer.level, []);\n }\n doc.layersByLevel.get(layer.level)!.push(layer);\n\n if (layer.level === 0) {\n doc.rootLayer = layer;\n }\n break;\n }\n\n case 'extension': {\n const id = obj['id'];\n if (typeof id === 'string') {\n (doc.extensions ??= {})[id] = Array.isArray(obj['data']) ? (obj['data'] as unknown[]) : [];\n }\n break;\n }\n\n default:\n // Unknown type — silently skip\n break;\n }\n }\n\n if (!doc.meta) throw new VisualliParseError('Missing required meta section');\n if (!doc.rootLayer) throw new VisualliParseError('Missing root layer (level 0)');\n\n return doc;\n}\n\nexport async function fetchAndParseVisualli(url: string): Promise<VisualliDocument> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return parseVisualliFile(await response.text());\n}\n\n/**\n * Parse .visualli JSONL data as returned from the MongoDB `visualli` field.\n *\n * @throws VisualliParseError\n */\nexport function parseVisualliFromMongoDB(visualliJsonl: string): VisualliDocument {\n if (!visualliJsonl || typeof visualliJsonl !== 'string') {\n throw new VisualliParseError('Invalid visualli data: expected a JSONL string');\n }\n const trimmed = visualliJsonl.trim();\n if (!trimmed) throw new VisualliParseError('Empty visualli data from MongoDB');\n return parseVisualliFile(trimmed);\n}\n\n/**\n * Parse a .visualli file from a browser `File` object.\n * (Browser-only – requires `FileReader`.)\n */\nexport function loadVisualliFileFromFile(file: File): Promise<VisualliDocument> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = e => {\n try {\n resolve(parseVisualliFile(e.target?.result as string));\n } catch (err) {\n reject(err);\n }\n };\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsText(file);\n });\n}\n\n// ── Tree Helpers ──────────────────────────────────────────────────────────────\n\n/** Return all layers whose `parentNodeId` matches the given node ID. */\n/** Terms defined by the document's `semantic-anchors` extension (empty when absent). */\nexport function getSemanticAnchors(doc: VisualliDocument): SemanticAnchor[] {\n const raw = doc.extensions?.['semantic-anchors'];\n if (!Array.isArray(raw)) return [];\n return raw.filter((a): a is SemanticAnchor => !!a && typeof (a as SemanticAnchor).word === 'string' && typeof (a as SemanticAnchor).description === 'string');\n}\n\nexport function getChildLayers(\n doc: VisualliDocument,\n nodeId: string,\n): VisualliLayer[] {\n const result: VisualliLayer[] = [];\n for (const layer of doc.layers.values()) {\n if (layer.parentNodeId === nodeId) result.push(layer);\n }\n return result;\n}\n\n/**\n * Count the maximum depth of layers beneath a node (its \"magnitude\").\n * Returns 0 when the node has no child layers.\n */\nexport function countLayersBeneath(\n doc: VisualliDocument,\n nodeId: string,\n visited = new Set<string>(),\n): number {\n if (visited.has(nodeId)) return 0;\n visited.add(nodeId);\n\n const children = getChildLayers(doc, nodeId);\n if (children.length === 0) return 0;\n\n let maxDepth = 0;\n for (const layer of children) {\n let depth = 1;\n for (const node of layer.nodes) {\n depth = Math.max(depth, 1 + countLayersBeneath(doc, node.id, visited));\n }\n maxDepth = Math.max(maxDepth, depth);\n }\n return maxDepth;\n}\n","// ─── .visualli → FlatNode Converter ──────────────────────────────────────────\n//\n// Transforms the layer-based .visualli format into the flat Map<id, FlatNode>\n// structure consumed by the canvas renderer.\n\nimport type { VisualliDocument } from '../types/document.js';\nimport type { VisualliLayer } from '../types/layer.js';\nimport type { FlatNode, NodeMap } from '../types/mindmap.js';\nimport { countLayersBeneath } from './visualliParser.js';\nimport { ideaColor } from '../theme/index.js';\nimport { approximateMeasure, ideaKindOf, ideaSize, type IdeaMeasure } from '../rendering/ideaSize.js';\nimport { applyCircularLayout, calculateOptimalRadiusPercentage } from '../layout/circularLayout.js';\nimport { applyLinearHorizontalLayout, applyLinearVerticalLayout } from '../layout/linearLayout.js';\n\n// ── Overlap Resolution ────────────────────────────────────────────────────────\n\nfunction doNodesOverlap(n1: FlatNode, n2: FlatNode, padding = 20): boolean {\n return !(\n n1.x + n1.width / 2 + padding < n2.x - n2.width / 2 - padding ||\n n1.x - n1.width / 2 - padding > n2.x + n2.width / 2 + padding ||\n n1.y + n1.height / 2 + padding < n2.y - n2.height / 2 - padding ||\n n1.y - n1.height / 2 - padding > n2.y + n2.height / 2 + padding\n );\n}\n\n/** Iteratively push overlapping nodes apart using symmetric force separation. */\nexport function resolveNodeOverlaps(nodes: FlatNode[], maxIterations = 10): void {\n const padding = 150;\n\n for (let iter = 0; iter < maxIterations; iter++) {\n let any = false;\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i + 1; j < nodes.length; j++) {\n const n1 = nodes[i];\n const n2 = nodes[j];\n if (!doNodesOverlap(n1, n2, padding)) continue;\n\n any = true;\n const dx = n2.x - n1.x;\n const dy = n2.y - n1.y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n\n if (dist < 1) {\n n2.x += n2.width / 2 + padding;\n continue;\n }\n\n const minDist = (n1.width + n2.width) / 2 + padding;\n if (dist < minDist) {\n const push = (minDist - dist) / 2;\n n1.x -= (dx / dist) * push;\n n1.y -= (dy / dist) * push;\n n2.x += (dx / dist) * push;\n n2.y += (dy / dist) * push;\n }\n }\n }\n if (!any) break;\n }\n}\n\n// ── Options ───────────────────────────────────────────────────────────────────\n\nexport interface ConvertOptions {\n /** Measures label text in the idea face (the renderer's font engine); ideas are sized with the design system's\n * ideaSize. Defaults to an approximation (half an em per character) where no font engine exists. */\n measure?: IdeaMeasure;\n /** Label scale (comfort: larger text is 1.15); ideas grow to fit the larger label. */\n labelScale?: number;\n}\n\n/** Whether a layer's ideas carry their own positions: every idea has one, and (with more than one idea) they aren't\n * all on the same spot (a placeholder). Then the file's positions are used as they are. */\nfunction hasFilePositions(layer: VisualliLayer): boolean {\n const ps = layer.nodes.map((n) => n.position);\n if (!ps.length || !ps.every((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y))) return false;\n return ps.length === 1 || ps.some((p) => p!.x !== ps[0]!.x || p!.y !== ps[0]!.y);\n}\n\n// ── Container-formation Layout ────────────────────────────────────────────────\n\ninterface Proxy { id: string; x: number; y: number; width: number; height: number }\n\nfunction applyFormation(nodes: FlatNode[], formation: string): void {\n if (nodes.length === 0) return;\n if (nodes.length === 1) { nodes[0].x = 0; nodes[0].y = 0; return; }\n if (formation === 'linear-horizontal' || formation === 'linear') {\n applyLinearHorizontalLayout(nodes, { centerX: 0, centerY: 0 });\n } else if (formation === 'linear-vertical') {\n applyLinearVerticalLayout(nodes, { centerX: 0, centerY: 0 });\n } else {\n const r = Math.max(250, nodes.length * 70);\n const step = (2 * Math.PI) / nodes.length;\n for (let i = 0; i < nodes.length; i++) {\n nodes[i].x = r * Math.cos(i * step);\n nodes[i].y = r * Math.sin(i * step);\n }\n }\n}\n\nfunction containerBBox(nodes: FlatNode[], padding: number): { width: number; height: number } {\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const n of nodes) {\n minX = Math.min(minX, n.x - n.width / 2);\n maxX = Math.max(maxX, n.x + n.width / 2);\n minY = Math.min(minY, n.y - n.height / 2);\n maxY = Math.max(maxY, n.y + n.height / 2);\n }\n return { width: maxX - minX + padding * 2, height: maxY - minY + padding * 2 };\n}\n\nfunction proxyLinearH(proxies: Proxy[], gap = 180): void {\n const total = proxies.reduce((s, p) => s + p.width, 0) + gap * (proxies.length - 1);\n let x = -total / 2;\n for (const p of proxies) { p.x = x + p.width / 2; p.y = 0; x += p.width + gap; }\n}\n\nfunction proxyLinearV(proxies: Proxy[], gap = 180): void {\n const total = proxies.reduce((s, p) => s + p.height, 0) + gap * (proxies.length - 1);\n let y = -total / 2;\n for (const p of proxies) { p.x = 0; p.y = y + p.height / 2; y += p.height + gap; }\n}\n\nfunction proxyRadial(proxies: Proxy[], radius = 500): void {\n if (proxies.length === 1) { proxies[0].x = 0; proxies[0].y = 0; return; }\n // Start at top (12 o'clock) for n>=3, horizontal for n=2 (matches circular layout)\n const startAngle = proxies.length === 2 ? 0 : -Math.PI / 2;\n const step = (2 * Math.PI) / proxies.length;\n for (let i = 0; i < proxies.length; i++) {\n const angle = startAngle + i * step;\n proxies[i].x = radius * Math.cos(angle);\n proxies[i].y = radius * Math.sin(angle);\n }\n}\n\n// ── Layer Conversion ──────────────────────────────────────────────────────────\n\nfunction makeFlatNode(\n node: VisualliLayer['nodes'][number],\n layer: VisualliLayer,\n doc: VisualliDocument,\n /** Position among the layer's ideas (file order): missing colours cycle the topics by it, and the first idea of the root radial layer is the root. */\n siblingIndex: number,\n opts: ConvertOptions,\n): FlatNode {\n const label = Array.isArray(node.data.label) ? (node.data.label as string[]).join(' ') : (node.data.label || 'Untitled');\n const branchCount = countLayersBeneath(doc, node.id);\n const kind = ideaKindOf(layer, siblingIndex);\n const size = ideaSize(kind, label, opts.measure ?? approximateMeasure, opts.labelScale ?? 1);\n const { topic, custom } = ideaColor(node.data.color, siblingIndex);\n return {\n id: node.id,\n parentId: layer.parentNodeId ?? null,\n x: node.position?.x ?? 0,\n y: node.position?.y ?? 0,\n level: layer.level,\n title: label,\n description: node.data.summary || '',\n color: node.data.color ?? '',\n ...(topic ? { topic } : {}),\n ...(custom ? { custom } : {}),\n kind,\n width: size.width,\n height: size.height,\n isExpanded: branchCount > 0,\n branchCount,\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n}\n\nfunction layoutLayerWithContainers(layer: VisualliLayer, flatNodes: FlatNode[]): void {\n const PADDING = 80;\n const byId = new Map(flatNodes.map((n: FlatNode) => [n.id, n]));\n const containerNodeIds = new Set<string>();\n const containerGroups = new Map<string, FlatNode[]>();\n\n for (const container of layer.containers) {\n const group = container.nodes.map((id: string) => byId.get(id)).filter((n: FlatNode | undefined): n is FlatNode => !!n);\n if (!group.length) continue;\n container.nodes.forEach((id: string) => containerNodeIds.add(id));\n const formation = ((container.data as unknown as Record<string, unknown> | undefined)?.['formation'] as string | undefined) ?? 'radial';\n applyFormation(group, formation);\n containerGroups.set(container.id, group);\n }\n\n const proxies: Proxy[] = [];\n for (const n of flatNodes) {\n if (!containerNodeIds.has(n.id)) proxies.push({ id: n.id, x: 0, y: 0, width: n.width, height: n.height });\n }\n for (const container of layer.containers) {\n const group = containerGroups.get(container.id);\n if (!group?.length) continue;\n const bbox = containerBBox(group, PADDING);\n proxies.push({ id: `__c__${container.id}`, x: 0, y: 0, width: bbox.width, height: bbox.height });\n }\n\n const layout = layer.layout || 'radial';\n if (layout === 'linear-horizontal') proxyLinearH(proxies, 180);\n else if (layout === 'linear-vertical') proxyLinearV(proxies, 180);\n else proxyRadial(proxies, 500);\n\n for (const proxy of proxies) {\n if (proxy.id.startsWith('__c__')) {\n const group = containerGroups.get(proxy.id.slice(5));\n group?.forEach(n => { n.x += proxy.x; n.y += proxy.y; });\n } else {\n const fn = byId.get(proxy.id);\n if (fn) { fn.x = proxy.x; fn.y = proxy.y; }\n }\n }\n}\n\nfunction layoutLayer(layer: VisualliLayer, flatNodes: FlatNode[]): void {\n const layout = layer.layout || 'radial';\n if (layout === 'linear-horizontal') {\n applyLinearHorizontalLayout(flatNodes, { centerX: 0, centerY: 0 });\n } else if (layout === 'linear-vertical') {\n applyLinearVerticalLayout(flatNodes, { centerX: 0, centerY: 0 });\n } else {\n const pct = calculateOptimalRadiusPercentage(flatNodes.length, 2000, 2000, 200, 30);\n applyCircularLayout(flatNodes, {\n radiusPercentage: pct,\n containerWidth: 2000,\n containerHeight: 2000,\n centerX: 0,\n centerY: 0,\n });\n }\n}\n\n/**\n * Convert a single layer to an array of FlatNode objects, each sized for its label\n * (the design system's idea.ts) and coloured by its colour rule (color.ts).\n * Positions: the file's own when its ideas carry them; otherwise the SDK lays the\n * layer out by `layer.layout` (default radial) and container formations.\n */\nexport function convertLayerToFlatNodes(\n layer: VisualliLayer,\n doc: VisualliDocument,\n opts: ConvertOptions = {},\n): FlatNode[] {\n const flatNodes = layer.nodes.map((node, i) => makeFlatNode(node, layer, doc, i, opts));\n if (flatNodes.length === 0 || hasFilePositions(layer)) return flatNodes;\n for (const n of flatNodes) { n.x = 0; n.y = 0; }\n if ((layer.containers ?? []).length > 0) layoutLayerWithContainers(layer, flatNodes);\n else layoutLayer(layer, flatNodes);\n resolveNodeOverlaps(flatNodes);\n return flatNodes;\n}\n\n/**\n * Convert an entire .visualli document to a flat NodeMap.\n * All layers are converted and a children-index is built for O(1) tree traversal.\n */\nexport function convertVisualliToFlatNodes(doc: VisualliDocument, opts: ConvertOptions = {}): NodeMap {\n const map: NodeMap = new Map();\n\n for (const layer of doc.layers.values()) {\n for (const node of convertLayerToFlatNodes(layer, doc, opts)) {\n map.set(node.id, node);\n }\n }\n\n // Build children index\n for (const node of map.values()) {\n node.childrenIds = [];\n for (const candidate of map.values()) {\n if (candidate.parentId === node.id) node.childrenIds.push(candidate.id);\n }\n }\n\n return map;\n}\n\n/**\n * Get flat nodes for a specific layer only.\n *\n * @param doc - Parsed .visualli document\n * @param layerId - Layer ID to extract\n * @param opts - Text measurement and label scale for sizing ideas\n * @returns FlatNode array, or empty array if layer not found\n */\nexport function getNodesForLayer(doc: VisualliDocument, layerId: string, opts: ConvertOptions = {}): FlatNode[] {\n const layer = doc.layers.get(layerId);\n return layer ? convertLayerToFlatNodes(layer, doc, opts) : [];\n}\n","// ─── Mindmap Tree Utilities ───────────────────────────────────────────────────\n\nimport type { MindMapNode, TopLevelConnection } from '../types/mindmap.js';\n\n/** Flatten a nested MindMapNode tree into a depth-first array. */\nexport function flattenNodes(nodes: MindMapNode[]): MindMapNode[] {\n const result: MindMapNode[] = [];\n const traverse = (list: MindMapNode[]) => {\n for (const n of list) {\n result.push(n);\n if (n.children) traverse(n.children);\n }\n };\n traverse(nodes);\n return result;\n}\n\n/**\n * Return nodes visible at the given zoom level.\n *\n * At lower zoom values only top-level nodes are shown; each 5% zoom increment\n * reveals one additional hierarchy level.\n */\nexport function getVisibleNodes(\n nodes: MindMapNode[],\n zoom: number,\n focusedNode?: string,\n): MindMapNode[] {\n const flat = flattenNodes(nodes);\n\n if (focusedNode) {\n const focused = flat.find(n => n.id === focusedNode);\n if (focused) {\n return focused.children ? [focused, ...focused.children] : [focused];\n }\n }\n\n // Map zoom to max visible level (level 0 at ≤70%, +1 level per 5% increment)\n if (zoom <= 0.70) return flat.filter(n => n.level === 0);\n const maxLevel = Math.floor((zoom - 0.70) / 0.05);\n return flat.filter(n => n.level <= maxLevel);\n}\n\n/** Build parent→child connection pairs from the node tree. */\nexport function getNodeConnections(\n nodes: MindMapNode[],\n): Array<{ from: MindMapNode; to: MindMapNode; label?: string }> {\n const flat = flattenNodes(nodes);\n const result: Array<{ from: MindMapNode; to: MindMapNode; label?: string }> = [];\n for (const n of flat) {\n if (!n.parent) continue;\n const parent = flat.find(p => p.id === n.parent);\n if (parent) result.push({ from: parent, to: n, label: n.relationshipLabel });\n }\n return result;\n}\n\n/** Resolve top-level cross-connections to node pairs. */\nexport function getTopLevelConnections(\n nodes: MindMapNode[],\n topLevelConnections: TopLevelConnection[],\n): Array<{ from: MindMapNode; to: MindMapNode; label: string }> {\n const flat = flattenNodes(nodes);\n const result: Array<{ from: MindMapNode; to: MindMapNode; label: string }> = [];\n for (const conn of topLevelConnections) {\n const from = flat.find(n => n.id === conn.from);\n const to = flat.find(n => n.id === conn.to);\n if (from && to) result.push({ from, to, label: conn.label ?? '' });\n }\n return result;\n}\n\n/** Euclidean distance between two world-space points. */\nexport function calculateDistance(x1: number, y1: number, x2: number, y2: number): number {\n return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);\n}\n","// ─── MindMapConfig Utilities ──────────────────────────────────────────────────\n//\n// Helpers for creating and generating MindMapConfig objects (pure data, no UI).\n\nimport type { MindMapNode, MindMapConfig, TopLevelConnection } from '../types/mindmap.js';\nimport { LAYOUT_SPACING } from '../constants/layout.js';\nimport { TOKENS, TOPICS } from '../theme/index.js';\n\n/** Light-theme fill of the topic used for a level (cycles through the design-system topics). */\nconst levelColor = (level: number): string => TOKENS.light[`topic-${TOPICS[level % TOPICS.length]}`]!;\n\ntype LevelColors = { level0: string; level1: string; level2: string; level3: string; [key: `level${number}`]: string };\nconst levelColors = (count: number): LevelColors =>\n Object.fromEntries(Array.from({ length: count }, (_, i) => [`level${i}`, levelColor(i)])) as LevelColors;\n\nexport function createMindMapConfig(\n nodes: MindMapNode[],\n topLevelConnections: TopLevelConnection[],\n title = 'Untitled Mindmap',\n): MindMapConfig {\n const now = new Date().toISOString();\n return {\n metadata: {\n title,\n description: `Interactive mindmap with ${nodes.length} nodes`,\n version: '1.0.0',\n created: now,\n lastModified: now,\n },\n nodes,\n topLevelConnections,\n settings: {\n defaultColors: levelColors(4),\n layout: {\n nodeSpacing: LAYOUT_SPACING.nodeSpacing,\n levelSpacing: LAYOUT_SPACING.levelSpacing,\n },\n },\n };\n}\n\n// ── Sample Data Generator ─────────────────────────────────────────────────────\n\nfunction generateId(prefix: string, level: number, index: number): string {\n return `${prefix}-l${level}-${index}`;\n}\n\nfunction generateLevelNodes(\n parentId: string | null,\n level: number,\n maxLevels: number,\n xOffset = 0,\n yOffset = 0,\n): MindMapNode[] {\n if (level >= maxLevels) return [];\n\n const count = 5;\n const levelSpacing = 200;\n const nodeSpacing = 300;\n const nodes: MindMapNode[] = [];\n\n for (let i = 0; i < count; i++) {\n const nodeId = parentId\n ? generateId(`${parentId}-${i}`, level, i)\n : generateId('root', level, i);\n\n const x = xOffset + (i - (count - 1) / 2) * nodeSpacing;\n const y = yOffset + level * levelSpacing;\n\n const node: MindMapNode = {\n id: nodeId,\n title: `Level ${level} Node ${i + 1}`,\n level,\n x,\n y,\n color: levelColor(level),\n relationshipLabel: level > 0 ? 'child' : undefined,\n parent: parentId ?? undefined,\n };\n\n const children = generateLevelNodes(nodeId, level + 1, maxLevels, x, y + levelSpacing);\n if (children.length > 0) node.children = children;\n\n nodes.push(node);\n }\n\n return nodes;\n}\n\n/**\n * Generate a sample MindMapConfig with `maxLevels` of hierarchy.\n * Useful for testing and demos.\n */\nexport function generateSampleConfig(maxLevels = 9): MindMapConfig {\n const nodes = generateLevelNodes(null, 0, maxLevels, 0, 0);\n\n const topLevelConnections: TopLevelConnection[] = nodes.slice(0, -1).map((n, i) => ({\n from: n.id,\n to: nodes[i + 1].id,\n label: 'related to',\n }));\n\n return {\n metadata: {\n id: `mindmap-${Date.now()}`,\n title: 'Sample Mind Map',\n description: 'A sample mind map',\n version: '1.0.0',\n created: new Date().toISOString(),\n lastModified: new Date().toISOString(),\n },\n nodes,\n topLevelConnections,\n settings: {\n defaultColors: levelColors(9),\n layout: {\n nodeSpacing: LAYOUT_SPACING.nodeSpacing,\n levelSpacing: LAYOUT_SPACING.levelSpacing,\n },\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,96 @@
1
+ import {
2
+ METRICS,
3
+ METRICS_REDUCED_MOTION,
4
+ THEME_NAMES,
5
+ TOKENS
6
+ } from "./chunk-LV3EXUQY.js";
7
+
8
+ // src/generated/geometry/color.ts
9
+ var TOPIC_ORDER = ["teal", "harbor", "iris", "berry", "coral", "amber", "sun", "stone"];
10
+ function topicFromName(color) {
11
+ if (!color) return null;
12
+ const c = color.trim().toLowerCase();
13
+ return TOPIC_ORDER.includes(c) ? c : null;
14
+ }
15
+ function topicFor(color, siblingIndex) {
16
+ if (!color || !color.trim()) return TOPIC_ORDER[(siblingIndex % TOPIC_ORDER.length + TOPIC_ORDER.length) % TOPIC_ORDER.length];
17
+ return topicFromName(color);
18
+ }
19
+ var CUSTOM = {
20
+ /** The ring is the fill with each RGB channel multiplied by this. */
21
+ ringShade: 0.78
22
+ };
23
+ function customColor(color) {
24
+ const m = /^#?([0-9a-f]{6})$/i.exec((color || "").trim());
25
+ if (!m) return { fill: color, ring: null };
26
+ const n = parseInt(m[1], 16), f = CUSTOM.ringShade;
27
+ const r = Math.round((n >> 16 & 255) * f), g = Math.round((n >> 8 & 255) * f), b = Math.round((n & 255) * f);
28
+ return { fill: color.trim(), ring: "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1) };
29
+ }
30
+
31
+ // src/theme/index.ts
32
+ var FAMILIES = /* @__PURE__ */ new Set(["focus", "colorsafe", "contrast"]);
33
+ function isThemeName(v) {
34
+ return typeof v === "string" && THEME_NAMES.includes(v);
35
+ }
36
+ function resolveTheme(input, env = {}) {
37
+ const scheme = env.prefersDark ? "dark" : "light";
38
+ if (input === void 0 || input === "auto") return env.forcedColors ? `contrast-${scheme}` : scheme;
39
+ if (FAMILIES.has(input)) return `${input}-${scheme}`;
40
+ return isThemeName(input) ? input : "light";
41
+ }
42
+ var isDarkTheme = (name) => name.endsWith("dark");
43
+ var isContrastTheme = (name) => name.startsWith("contrast");
44
+ var isFocusTheme = (name) => name.startsWith("focus");
45
+ var getTokens = (name) => TOKENS[name];
46
+ function token(name, key) {
47
+ const v = TOKENS[name][key];
48
+ if (v === void 0) throw new Error(`Unknown design-system token "${key}"`);
49
+ return v;
50
+ }
51
+ function resolveComfort(c, env = {}) {
52
+ const rm = c?.reducedMotion;
53
+ return {
54
+ readableType: !!c?.readableType,
55
+ largerText: !!c?.largerText,
56
+ reducedMotion: rm === void 0 || rm === "system" ? !!env.prefersReducedMotion : rm
57
+ };
58
+ }
59
+ function metricsFor(comfort) {
60
+ return comfort.reducedMotion ? { ...METRICS, ...METRICS_REDUCED_MOTION } : METRICS;
61
+ }
62
+ var labelScale = (c) => c.largerText ? 1.15 : 1;
63
+ function topicStyle(theme, topic) {
64
+ const t = TOKENS[theme];
65
+ return { topic, fill: t[`topic-${topic}`], ring: t[`topic-${topic}-ring`] };
66
+ }
67
+ function ideaColor(color, siblingIndex) {
68
+ const topic = topicFor(color, siblingIndex);
69
+ return topic ? { topic, custom: null } : { topic: null, custom: customColor(color) };
70
+ }
71
+ function ideaStyle(theme, c) {
72
+ if (c.custom) return { fill: c.custom.fill, ring: c.custom.ring ?? TOKENS[theme]["edge"] };
73
+ return topicStyle(theme, c.topic ?? TOPIC_ORDER[0]);
74
+ }
75
+
76
+ export {
77
+ TOPIC_ORDER,
78
+ topicFromName,
79
+ topicFor,
80
+ CUSTOM,
81
+ customColor,
82
+ isThemeName,
83
+ resolveTheme,
84
+ isDarkTheme,
85
+ isContrastTheme,
86
+ isFocusTheme,
87
+ getTokens,
88
+ token,
89
+ resolveComfort,
90
+ metricsFor,
91
+ labelScale,
92
+ topicStyle,
93
+ ideaColor,
94
+ ideaStyle
95
+ };
96
+ //# sourceMappingURL=chunk-KVSM3APM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generated/geometry/color.ts","../src/theme/index.ts"],"sourcesContent":["// Generated from visualli.ai design-system/components/src/color.ts — do not edit here.\n/* Which topic an idea is drawn in. A `.visualli` file's `data.color` may name one of the eight topics or be any\n other color, or be missing. Every renderer resolves it with this rule, so a file looks the same everywhere:\n a topic name ('teal', 'Harbor ') → that topic (case and surrounding spaces don't matter);\n no color → the topics in order, by the idea's position among its siblings (so siblings differ);\n anything else (e.g. '#b7e7f3') → null: not a topic — drawn as given (customColor): the color as the fill, and\n the fill darkened (CUSTOM.ringShade) as the ring; a custom color doesn't follow themes.\n No dependencies, so it can travel alone. */\n\n/** The eight topics, in the order missing colors cycle through. */\nexport const TOPIC_ORDER = ['teal', 'harbor', 'iris', 'berry', 'coral', 'amber', 'sun', 'stone'] as const;\nexport type TopicName = (typeof TOPIC_ORDER)[number];\n\n/** The topic a color names, or null. */\nexport function topicFromName(color: string | null | undefined): TopicName | null {\n if (!color) return null;\n const c = color.trim().toLowerCase();\n return (TOPIC_ORDER as readonly string[]).includes(c) ? (c as TopicName) : null;\n}\n\n/** The topic for an idea: its named topic; with no color, the next topic in sibling order; null for any other color. */\nexport function topicFor(color: string | null | undefined, siblingIndex: number): TopicName | null {\n if (!color || !color.trim()) return TOPIC_ORDER[((siblingIndex % TOPIC_ORDER.length) + TOPIC_ORDER.length) % TOPIC_ORDER.length];\n return topicFromName(color);\n}\n\n/** A custom (non-topic) color: drawn as given, with a darker ring. */\nexport const CUSTOM = { /** The ring is the fill with each RGB channel multiplied by this. */ ringShade: 0.78 } as const;\n\n/** Fill and ring for a custom color. `ring` is null when the color isn't a 6-digit hex (use the `edge` token then). */\nexport function customColor(color: string): { fill: string; ring: string | null } {\n const m = /^#?([0-9a-f]{6})$/i.exec((color || '').trim());\n if (!m) return { fill: color, ring: null };\n const n = parseInt(m[1], 16), f = CUSTOM.ringShade;\n const r = Math.round(((n >> 16) & 255) * f), g = Math.round(((n >> 8) & 255) * f), b = Math.round((n & 255) * f);\n return { fill: color.trim(), ring: '#' + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1) };\n}\n","// ─── Theme resolution ────────────────────────────────────────────────────────\n//\n// Everything visual comes from the generated design-system modules. This file\n// only decides WHICH of the 8 themes applies and resolves document colours with\n// the design system's colour rule. No colour literals live here.\n//\n// Dependencies: ../generated/designSystem.ts (generated from design-system/).\n\nimport {\n CANVAS_STYLE,\n DESIGN_SYSTEM_VERSION,\n METRICS,\n METRICS_REDUCED_MOTION,\n THEME_NAMES,\n TOKENS,\n TOPICS,\n TYPE_STYLES,\n type ThemeName,\n type TopicName,\n} from '../generated/designSystem.js';\nimport { TOPIC_ORDER, customColor, topicFor, topicFromName } from '../generated/geometry/color.js';\n\nexport { topicFromName, customColor };\n\nexport { CANVAS_STYLE, DESIGN_SYSTEM_VERSION, METRICS, METRICS_REDUCED_MOTION, THEME_NAMES, TOKENS, TOPICS, TYPE_STYLES };\nexport type { ThemeName, TopicName };\n\n// ── Theme selection ───────────────────────────────────────────────────────────\n\n/** Theme families: each resolves to its light or dark member. */\nexport type ThemeFamily = 'standard' | 'focus' | 'colorsafe' | 'contrast';\n\n/**\n * Anything a consumer may pass as `theme`:\n * - one of the 8 design-system themes ('light', 'dark', 'focus-light', ...)\n * - a family name ('focus' | 'colorsafe' | 'contrast') -> light/dark per the reader's scheme\n * - 'auto' -> light/dark per prefers-color-scheme (contrast under forced-colors)\n *\n * 'light' | 'dark' | 'auto' are the pre-0.2 values, kept as aliases for one release.\n */\nexport type ThemeInput = ThemeName | 'auto' | 'focus' | 'colorsafe' | 'contrast';\n\nexport interface ThemeEnv {\n /** prefers-color-scheme: dark */\n prefersDark?: boolean;\n /** forced-colors: active (Windows high contrast etc.) */\n forcedColors?: boolean;\n}\n\nconst FAMILIES: ReadonlySet<string> = new Set(['focus', 'colorsafe', 'contrast']);\n\nexport function isThemeName(v: unknown): v is ThemeName {\n return typeof v === 'string' && (THEME_NAMES as readonly string[]).includes(v);\n}\n\n/** Resolve any accepted `theme` value to one concrete design-system theme. */\nexport function resolveTheme(input: ThemeInput | undefined, env: ThemeEnv = {}): ThemeName {\n const scheme = env.prefersDark ? 'dark' : 'light';\n if (input === undefined || input === 'auto') return env.forcedColors ? (`contrast-${scheme}` as ThemeName) : scheme;\n if (FAMILIES.has(input)) return `${input}-${scheme}` as ThemeName;\n return isThemeName(input) ? input : 'light';\n}\n\nexport const isDarkTheme = (name: ThemeName): boolean => name.endsWith('dark');\nexport const isContrastTheme = (name: ThemeName): boolean => name.startsWith('contrast');\nexport const isFocusTheme = (name: ThemeName): boolean => name.startsWith('focus');\n\n/** All tokens of a theme (already merged over the light defaults). */\nexport const getTokens = (name: ThemeName): Readonly<Record<string, string>> => TOKENS[name];\n\n/** One token of a theme. Throws on an unknown key so typos fail loudly in tests. */\nexport function token(name: ThemeName, key: string): string {\n const v = TOKENS[name][key];\n if (v === undefined) throw new Error(`Unknown design-system token \"${key}\"`);\n return v;\n}\n\n// ── Comfort settings ──────────────────────────────────────────────────────────\n\nexport interface Comfort {\n /** Atkinson Hyperlegible for map labels instead of the handwritten faces (`data-type=\"readable\"`). */\n readableType?: boolean;\n /** Larger UI and label text (`data-scale=\"lg\"`). */\n largerText?: boolean;\n /** Skip transitions and reveal animations (`data-motion=\"reduced\"`). 'system' follows prefers-reduced-motion. */\n reducedMotion?: boolean | 'system';\n}\n\nexport interface ResolvedComfort { readableType: boolean; largerText: boolean; reducedMotion: boolean }\n\nexport function resolveComfort(c: Comfort | undefined, env: { prefersReducedMotion?: boolean } = {}): ResolvedComfort {\n const rm = c?.reducedMotion;\n return {\n readableType: !!c?.readableType,\n largerText: !!c?.largerText,\n reducedMotion: rm === undefined || rm === 'system' ? !!env.prefersReducedMotion : rm,\n };\n}\n\n/** Numeric design-system tokens (px / ms stripped). */\nexport type Metrics = { readonly [K in keyof typeof METRICS]: number };\n\n/** Numeric tokens with the reduced-motion overrides applied when requested. */\nexport function metricsFor(comfort: ResolvedComfort): Metrics {\n return comfort.reducedMotion ? { ...METRICS, ...METRICS_REDUCED_MOTION } : METRICS;\n}\n\n/** Multiplier the spec applies to label sizes under `data-scale=\"lg\"` (`--vi-label-scale`). */\nexport const labelScale = (c: ResolvedComfort): number => (c.largerText ? 1.15 : 1);\n\n// ── Topic palette ─────────────────────────────────────────────────────────────\n\nexport interface TopicStyle { topic: TopicName; fill: string; ring: string }\n\n/** Fill + ring colour of a topic in a theme. */\nexport function topicStyle(theme: ThemeName, topic: TopicName): TopicStyle {\n const t = TOKENS[theme];\n return { topic, fill: t[`topic-${topic}`]!, ring: t[`topic-${topic}-ring`]! };\n}\n\n/** An idea's colour by the design system's colour rule (geometry/color.ts, verbatim): a topic name → that topic; no\n * colour → the topics in sibling order; any other colour → custom, drawn as given (customColor: the colour as the fill,\n * the fill darkened by CUSTOM.ringShade as the ring). A custom colour doesn't follow themes. */\nexport interface IdeaColor { topic: TopicName | null; custom: { fill: string; ring: string | null } | null }\n\nexport function ideaColor(color: string | null | undefined, siblingIndex: number): IdeaColor {\n const topic = topicFor(color, siblingIndex);\n return topic ? { topic, custom: null } : { topic: null, custom: customColor(color!) };\n}\n\n/** Fill + ring an idea is drawn with in a theme: its topic's, or its custom colour (ring: the `edge` token when the\n * colour has no darker shade, i.e. isn't a 6-digit hex). */\nexport function ideaStyle(theme: ThemeName, c: { topic?: TopicName | null; custom?: IdeaColor['custom'] }): { fill: string; ring: string } {\n if (c.custom) return { fill: c.custom.fill, ring: c.custom.ring ?? TOKENS[theme]['edge']! };\n return topicStyle(theme, c.topic ?? TOPIC_ORDER[0]);\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  FPS_SAMPLE_WINDOW,
3
3
  FPS_TARGET,
4
4
  MEMORY_CHECK_INTERVAL
5
- } from "./chunk-LDJRGT2Q.js";
5
+ } from "./chunk-UAMEJG2C.js";
6
6
 
7
7
  // src/performance/performanceMonitor.ts
8
8
  var FPSMonitor = class {
@@ -136,4 +136,4 @@ export {
136
136
  MemoryMonitor,
137
137
  PerformanceProfiler
138
138
  };
139
- //# sourceMappingURL=chunk-PDP5CVUL.js.map
139
+ //# sourceMappingURL=chunk-LOQLNYB3.js.map