@visualli/core 0.1.7 → 0.2.1
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.
- package/README.md +37 -105
- package/dist/chunk-25AJTPMS.js +68 -0
- package/dist/chunk-25AJTPMS.js.map +1 -0
- package/dist/chunk-64PUANNH.js +151 -0
- package/dist/chunk-64PUANNH.js.map +1 -0
- package/dist/chunk-ITDZ72JE.js +96 -0
- package/dist/chunk-ITDZ72JE.js.map +1 -0
- package/dist/{chunk-PDP5CVUL.js → chunk-LOQLNYB3.js} +2 -2
- package/dist/chunk-N7HCYULH.js +12 -0
- package/dist/chunk-N7HCYULH.js.map +1 -0
- package/dist/{chunk-I75TMOE4.js → chunk-P6PKWVYF.js} +2 -2
- package/dist/chunk-RFMUATWB.js +753 -0
- package/dist/chunk-RFMUATWB.js.map +1 -0
- package/dist/chunk-UAMEJG2C.js +267 -0
- package/dist/chunk-UAMEJG2C.js.map +1 -0
- package/dist/{chunk-LJYSKSCE.js → chunk-W2CAOOJJ.js} +6 -6
- package/dist/chunk-W2CAOOJJ.js.map +1 -0
- package/dist/{chunk-6NXADJK2.js → chunk-WO4JAAKP.js} +84 -117
- package/dist/chunk-WO4JAAKP.js.map +1 -0
- package/dist/config/index.cjs +250 -349
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.d.cts +107 -66
- package/dist/config/index.d.ts +107 -66
- package/dist/config/index.js +52 -34
- package/dist/constants/index.cjs +14 -333
- package/dist/constants/index.cjs.map +1 -1
- package/dist/constants/index.d.cts +5 -243
- package/dist/constants/index.d.ts +5 -243
- package/dist/constants/index.js +5 -75
- package/dist/designSystem-D49xVfr8.d.cts +150 -0
- package/dist/designSystem-D49xVfr8.d.ts +150 -0
- package/dist/document-BFBwn93X.d.ts +26 -0
- package/dist/document-MEJEfxl3.d.cts +26 -0
- package/dist/ideaSize-BTJu6thN.d.cts +11 -0
- package/dist/ideaSize-RKbms8K6.d.ts +11 -0
- package/dist/index-CcF0Jvmb.d.ts +99 -0
- package/dist/index-Dt--T2l8.d.cts +99 -0
- package/dist/index.cjs +1636 -961
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +350 -27
- package/dist/index.d.ts +350 -27
- package/dist/index.js +434 -179
- package/dist/index.js.map +1 -1
- package/dist/{document-B3hnNr-F.d.cts → layer-B2A3Ki48.d.cts} +2 -16
- package/dist/{document-B3hnNr-F.d.ts → layer-B2A3Ki48.d.ts} +2 -16
- package/dist/layout/index.d.cts +2 -1
- package/dist/layout/index.d.ts +2 -1
- package/dist/mindmap-BO3YD5qu.d.cts +182 -0
- package/dist/mindmap-D0Of0jaZ.d.ts +182 -0
- package/dist/parser/index.cjs +745 -179
- package/dist/parser/index.cjs.map +1 -1
- package/dist/parser/index.d.cts +23 -8
- package/dist/parser/index.d.ts +23 -8
- package/dist/parser/index.js +7 -2
- package/dist/performance/index.cjs +13 -0
- package/dist/performance/index.cjs.map +1 -1
- package/dist/performance/index.js +2 -2
- package/dist/theme/index.cjs +884 -0
- package/dist/theme/index.cjs.map +1 -0
- package/dist/theme/index.d.cts +2 -0
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.js +53 -0
- package/dist/types/index.d.cts +4 -2
- package/dist/types/index.d.ts +4 -2
- package/dist/viewport/index.cjs +17 -6
- package/dist/viewport/index.cjs.map +1 -1
- package/dist/viewport/index.d.cts +6 -4
- package/dist/viewport/index.d.ts +6 -4
- package/dist/viewport/index.js +2 -2
- package/licenses/visualli-design-system/LICENSE +9 -0
- package/licenses/visualli-design-system/NOTICE.md +6 -0
- package/package.json +12 -4
- package/src/config/README.md +2 -4
- package/src/config/USAGE_EXAMPLES.md +0 -4
- package/src/config/index.ts +3 -4
- package/src/constants/index.ts +1 -1
- package/src/constants/layout.ts +11 -0
- package/src/constants/performanceConstants.ts +8 -27
- package/src/generated/README.md +12 -0
- package/src/generated/designSystem.ts +758 -0
- package/src/generated/geometry/blob.ts +99 -0
- package/src/generated/geometry/blobShapes.ts +4 -0
- package/src/generated/geometry/color.ts +37 -0
- package/src/generated/geometry/container.ts +84 -0
- package/src/generated/geometry/detail.ts +22 -0
- package/src/generated/geometry/idea.ts +56 -0
- package/src/generated/geometry/interaction.ts +122 -0
- package/src/generated/geometry/motion.ts +140 -0
- package/src/index.ts +18 -1
- package/src/parser/configUtils.ts +12 -19
- package/src/parser/visualliConverter.ts +71 -112
- package/src/parser/visualliParser.ts +16 -1
- package/src/rendering/culling.ts +16 -12
- package/src/rendering/ideaSize.ts +21 -0
- package/src/rendering/nodeGeometry.ts +97 -0
- package/src/theme/index.ts +136 -0
- package/src/types/document.ts +9 -0
- package/src/types/layer.ts +1 -1
- package/src/types/mindmap.ts +13 -0
- package/src/utils/navigation.ts +33 -63
- package/src/viewport/viewportUtils.ts +6 -4
- package/dist/animations/index.cjs +0 -146
- package/dist/animations/index.cjs.map +0 -1
- package/dist/animations/index.d.cts +0 -81
- package/dist/animations/index.d.ts +0 -81
- package/dist/animations/index.js +0 -35
- package/dist/chunk-6NXADJK2.js.map +0 -1
- package/dist/chunk-F36SMC3K.js +0 -106
- package/dist/chunk-F36SMC3K.js.map +0 -1
- package/dist/chunk-G5H3P4YZ.js +0 -273
- package/dist/chunk-G5H3P4YZ.js.map +0 -1
- package/dist/chunk-LDJRGT2Q.js +0 -163
- package/dist/chunk-LDJRGT2Q.js.map +0 -1
- package/dist/chunk-LJYSKSCE.js.map +0 -1
- package/dist/chunk-LNTJTDB2.js +0 -349
- package/dist/chunk-LNTJTDB2.js.map +0 -1
- package/dist/mindmap-DWS0WZT_.d.cts +0 -118
- package/dist/mindmap-DWS0WZT_.d.ts +0 -118
- package/src/animations/constants.ts +0 -72
- package/src/animations/easing.ts +0 -70
- package/src/animations/index.ts +0 -2
- package/src/config/blobShapes.ts +0 -146
- package/src/config/textScaling.ts +0 -54
- package/src/constants/design.ts +0 -281
- /package/dist/{chunk-PDP5CVUL.js.map → chunk-LOQLNYB3.js.map} +0 -0
- /package/dist/{chunk-I75TMOE4.js.map → chunk-P6PKWVYF.js.map} +0 -0
- /package/dist/{animations → theme}/index.js.map +0 -0
package/README.md
CHANGED
|
@@ -7,13 +7,13 @@ Framework-agnostic core logic, types, and algorithms for Visualli. Zero React /
|
|
|
7
7
|
| Module | What it provides |
|
|
8
8
|
|--------|------------------|
|
|
9
9
|
| **Types** | Full TypeScript interfaces for `VisualliDocument`, `VisualliLayer`, `FlatNode`, `ViewportState`, `RenderConfig`, `Connection`, and more |
|
|
10
|
-
| **Parser** | Parse `.visualli` JSONL files
|
|
11
|
-
| **Layout** | Circular and linear
|
|
12
|
-
| **
|
|
10
|
+
| **Parser** | Parse `.visualli` JSONL files and convert layers to flat nodes: each idea sized for its label and coloured by the design system's rules, placed where the file puts it |
|
|
11
|
+
| **Layout** | Circular and linear layouts for files whose ideas carry no positions |
|
|
12
|
+
| **Design system** | The Visualli design system's rules, copied verbatim from `design-system/` (tokens, blob geometry, motion, idea size, colours, containers, fit, peek, trail, level of detail) |
|
|
13
|
+
| **Viewport** | Pure pan/zoom/bounds math — world↔screen coordinate transforms, fit to view |
|
|
13
14
|
| **Spatial index** | RBush-backed O(log n) spatial index for viewport culling of large node graphs |
|
|
14
|
-
| **Animations** | Easing functions (cubic, quartic, sine, bezier LUT) and timing phase constants |
|
|
15
15
|
| **Performance** | rAF-based FPS monitor, memory monitor, profiler |
|
|
16
|
-
| **Constants** |
|
|
16
|
+
| **Constants** | Zoom limits, FPS targets, render config |
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
@@ -36,6 +36,10 @@ const doc = parseVisualliFile(rawJsonlString);
|
|
|
36
36
|
// Get the root layer
|
|
37
37
|
const rootLayerId = [...doc.layers.keys()][0];
|
|
38
38
|
const nodes = getNodesForLayer(doc, rootLayerId); // FlatNode[]
|
|
39
|
+
|
|
40
|
+
// Ideas are sized for their labels (the design system's idea.ts). Renderers pass their own text measurement in the
|
|
41
|
+
// label face; without one, an approximation is used.
|
|
42
|
+
const sized = getNodesForLayer(doc, rootLayerId, { measure: (text, px) => ctx.measureText(text).width, labelScale: 1 });
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
### Apply layouts
|
|
@@ -75,25 +79,15 @@ index.bulkLoad(nodes.map(n => ({
|
|
|
75
79
|
const visible = index.query(viewportBounds); // string[] — node IDs
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
###
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
import { easeInOutCubic, easeOutCubic, createCubicBezier } from '@visualli/core';
|
|
82
|
-
|
|
83
|
-
const t = easeInOutCubic(progress); // 0..1 → 0..1
|
|
84
|
-
const custom = createCubicBezier(0.4, 0, 0.2, 1); // CSS timing function
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Colors & design tokens
|
|
82
|
+
### Colours
|
|
88
83
|
|
|
89
84
|
```ts
|
|
90
|
-
import {
|
|
91
|
-
getColorForLevel, getThemeBackground,
|
|
92
|
-
LEVEL_COLOR_ARRAY, DS_COLORS, BRAND_COLORS,
|
|
93
|
-
} from '@visualli/core';
|
|
85
|
+
import { ideaColor, ideaStyle } from '@visualli/core';
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
ideaColor('teal', 0); // { topic: 'teal', custom: null } — a topic name
|
|
88
|
+
ideaColor(undefined, 2); // { topic: 'iris', custom: null } — no colour: topics in sibling order
|
|
89
|
+
ideaColor('#b7e7f3', 0); // { topic: null, custom: { fill: '#b7e7f3', ring: '#8fb4be' } } — drawn as given
|
|
90
|
+
ideaStyle('dark', node); // { fill, ring } in a theme
|
|
97
91
|
```
|
|
98
92
|
|
|
99
93
|
## Module Reference
|
|
@@ -104,7 +98,7 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
104
98
|
|--------|-------------|
|
|
105
99
|
| `VisualliDocument` | Top-level document (layers Map, extensions Map) |
|
|
106
100
|
| `VisualliLayer` | A single layer: level, nodes, connections, containers |
|
|
107
|
-
| `FlatNode` | Renderable
|
|
101
|
+
| `FlatNode` | Renderable idea: position, size for its label (`width` / `height`, `kind`), title, colour (`topic` or `custom`), level, parentId |
|
|
108
102
|
| `NodeMap` | `Map<string, FlatNode>` |
|
|
109
103
|
| `ViewportState` | `centerX/Y`, `zoomLevel`, `rotation`, `visibleBounds` |
|
|
110
104
|
| `RenderConfig` | Quality level, FPS target, culling flag, render mode |
|
|
@@ -116,15 +110,15 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
116
110
|
|--------|-------------|
|
|
117
111
|
| `parseVisualliFile(str)` | Parse JSONL → `VisualliDocument` |
|
|
118
112
|
| `loadVisualliFileFromFile(file)` | Parse a browser `File` object |
|
|
119
|
-
| `getNodesForLayer(doc, layerId)` | `FlatNode[]` for one layer |
|
|
120
|
-
| `convertVisualliToFlatNodes(doc)` | All layers → `NodeMap` |
|
|
113
|
+
| `getNodesForLayer(doc, layerId, opts?)` | `FlatNode[]` for one layer (`opts`: `measure`, `labelScale` for sizing ideas) |
|
|
114
|
+
| `convertVisualliToFlatNodes(doc, opts?)` | All layers → `NodeMap` |
|
|
121
115
|
| `getChildLayers(doc, layerId)` | Direct child layers |
|
|
122
116
|
|
|
123
117
|
### `layout/`
|
|
124
118
|
|
|
125
119
|
| Export | Description |
|
|
126
120
|
|--------|-------------|
|
|
127
|
-
| `applyCircularLayout(nodes)` | Radial arrangement |
|
|
121
|
+
| `applyCircularLayout(nodes)` | Radial arrangement (used when the file gives no positions) |
|
|
128
122
|
| `applyLinearHorizontalLayout(nodes)` | Left-to-right tree |
|
|
129
123
|
| `applyLinearVerticalLayout(nodes)` | Top-to-bottom tree |
|
|
130
124
|
| `resolveCollisions(nodes)` | Push apart overlapping nodes |
|
|
@@ -137,7 +131,7 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
137
131
|
| `zoomViewport(delta, vp, px, py, w, h)` | Zoom to cursor |
|
|
138
132
|
| `panViewport(dx, dy, vp)` | Translate camera |
|
|
139
133
|
| `setViewportCenter(x, y, vp)` | Teleport center |
|
|
140
|
-
| `clampZoom(level)` | Clamp to `[ZOOM_MIN, ZOOM_MAX]` |
|
|
134
|
+
| `clampZoom(level, fit?)` | Clamp to `[ZOOM_MIN, ZOOM_MAX]` × the layer's fit scale (the design system's limits are relative to the fit) |
|
|
141
135
|
| `worldToScreen(x, y, vp, w, h)` | World → screen px |
|
|
142
136
|
| `screenToWorld(x, y, vp, w, h)` | Screen px → world |
|
|
143
137
|
|
|
@@ -145,12 +139,11 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
145
139
|
|
|
146
140
|
| Export | Description |
|
|
147
141
|
|--------|-------------|
|
|
148
|
-
| `ZOOM_MIN` / `ZOOM_MAX` | `0.3` / `5.0` |
|
|
149
|
-
| `ZOOM_NAV_IN_THRESHOLD` | `2.7` — zoom level that triggers drill-in |
|
|
150
|
-
| `ZOOM_NAV_OUT_THRESHOLD` | `0.4` — zoom level that triggers back navigation |
|
|
151
|
-
| `LEVEL_COLOR_ARRAY` | 10-color palette indexed by node level |
|
|
142
|
+
| `ZOOM_MIN` / `ZOOM_MAX` | `0.3` / `5.0` — the design system's `VIEW`, relative to the layer's fit |
|
|
152
143
|
| `DEFAULT_RENDER_CONFIG` | Baseline quality settings |
|
|
153
|
-
|
|
144
|
+
|
|
145
|
+
Motion, idea size, colours, fit, peek placement and the depth trail are the design system's rules, exported verbatim
|
|
146
|
+
(`MOTION`, `layerReveal`, `IDEA`, `ideaSize`, `labelGrowth`, `ideaColor`, `layerBounds`, `fitView`, `peekPosition`, `TRAIL` …).
|
|
154
147
|
|
|
155
148
|
## TypeScript
|
|
156
149
|
|
|
@@ -178,86 +171,25 @@ npm run typecheck # tsc --noEmit (0 errors expected)
|
|
|
178
171
|
|
|
179
172
|
```
|
|
180
173
|
src/
|
|
181
|
-
├──
|
|
182
|
-
│ ├──
|
|
183
|
-
│
|
|
184
|
-
│ ├── layer.ts VisualliLayer, LayerNode, LayerConnection, LayerContainer …
|
|
185
|
-
│ ├── extension.ts VisualliExtension
|
|
186
|
-
│ ├── document.ts VisualliDocument
|
|
187
|
-
│ ├── schema.ts JSON-schema-aligned types + re-exports
|
|
188
|
-
│ ├── processing.ts ConnectionState, ProcessingState, type guards
|
|
189
|
-
│ └── sse.ts SSEEvent discriminated union + Zod validation + helpers
|
|
190
|
-
│
|
|
191
|
-
├── layout/ Pure layout algorithms (no side-effects)
|
|
192
|
-
│ ├── circularLayout.ts applyCircularLayout, calculateOptimalRadiusPercentage
|
|
193
|
-
│ ├── linearLayout.ts applyLinearHorizontalLayout, applyLinearVerticalLayout
|
|
194
|
-
│ └── layoutUtils.ts getNodeBounds, resolveCollisions, optimizeLayout
|
|
195
|
-
│
|
|
196
|
-
├── parser/ File parsing and data conversion
|
|
197
|
-
│ ├── visualliParser.ts parseVisualliFile, loadVisualliFile, getChildLayers …
|
|
198
|
-
│ ├── visualliConverter.ts convertVisualliToFlatNodes, resolveNodeOverlaps …
|
|
199
|
-
│ ├── mindmapUtils.ts flattenNodes, getVisibleNodes, calculateDistance …
|
|
200
|
-
│ └── configUtils.ts createMindMapConfig, generateSampleConfig
|
|
201
|
-
│
|
|
202
|
-
├── services/ Network utilities (fetch, SSE, logging)
|
|
203
|
-
│ ├── api.ts initApi, apiRequest, ApiError, mindmapApi, correlationId helpers
|
|
204
|
-
│ ├── logger.ts Logger class (buffered remote + console)
|
|
205
|
-
│ └── sseClient.ts createSSEClient (fetch ReadableStream, reconnection)
|
|
206
|
-
│
|
|
207
|
-
├── constants/ Design tokens, performance thresholds, render config
|
|
208
|
-
│ ├── performanceConstants.ts FPS targets, zoom limits, node dimensions …
|
|
209
|
-
│ ├── design.ts DS_COLORS, LEVEL_COLORS, BRAND_COLORS, helpers …
|
|
210
|
-
│ └── renderConfig.ts DEFAULT_RENDER_CONFIG, FEATURE_FLAGS, helpers …
|
|
211
|
-
│
|
|
212
|
-
├── viewport/ Coordinate math and pan/zoom operations
|
|
213
|
-
│ └── viewportUtils.ts calculateViewportBounds, worldToScreen, zoomViewport …
|
|
174
|
+
├── generated/ The Visualli design system, generated from design-system/ (never edited by hand)
|
|
175
|
+
│ ├── designSystem.ts TOKENS, METRICS, TYPE_STYLES, CANVAS_STYLE (from css/spec.css)
|
|
176
|
+
│ └── geometry/ blob, motion, idea, color, container, interaction, detail — copied verbatim
|
|
214
177
|
│
|
|
178
|
+
├── types/ TypeScript interfaces (FlatNode, VisualliDocument, VisualliLayer, ViewportState …)
|
|
179
|
+
├── parser/ parseVisualliFile; convertLayerToFlatNodes / getNodesForLayer (sizes, colours, positions)
|
|
180
|
+
├── layout/ Circular and linear layouts, for layers whose ideas carry no positions
|
|
181
|
+
├── rendering/ nodeGeometry (radii, rings, connector endpoints), ideaSize (idea.ts + measurement), culling
|
|
182
|
+
├── theme/ Theme and comfort resolution; ideaColor / ideaStyle (the design system's colour rule)
|
|
183
|
+
├── utils/ Layer navigation and fit to view (layerBounds / fitView)
|
|
184
|
+
├── viewport/ Coordinate math and pan / zoom (limits relative to the layer's fit)
|
|
185
|
+
├── constants/ Zoom limits, performance thresholds, render config
|
|
215
186
|
├── performance/ FPS / memory monitoring (browser APIs, no React)
|
|
216
|
-
│ └── performanceMonitor.ts FPSMonitor, MemoryMonitor, PerformanceProfiler
|
|
217
|
-
│
|
|
218
|
-
├── animations/ Easing functions and timing constants
|
|
219
|
-
│ ├── easing.ts easeInOutCubic, createCubicBezier …
|
|
220
|
-
│ └── constants.ts ANIMATION_DURATION, ANIMATION_PHASES, COOLDOWN …
|
|
221
|
-
│
|
|
222
187
|
└── spatial/ RBush spatial index for O(log n) viewport culling
|
|
223
|
-
└── spatialIndex.ts RBushSpatialIndex, ISpatialIndex, BoundingBox
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
## Installation
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
npm install @visualli/core
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
> **Dependencies:** `zod` (schema validation) · `rbush` (spatial index)
|
|
233
|
-
|
|
234
|
-
## Quick Start
|
|
235
|
-
|
|
236
|
-
```ts
|
|
237
|
-
// Parse a .visualli document
|
|
238
|
-
import { parseVisualliFile, convertVisualliToFlatNodes } from '@visualli/core/parser';
|
|
239
|
-
|
|
240
|
-
const doc = parseVisualliFile(rawJsonlString);
|
|
241
|
-
const nodeMap = convertVisualliToFlatNodes(doc);
|
|
242
|
-
|
|
243
|
-
// Apply a circular layout
|
|
244
|
-
import { applyCircularLayout } from '@visualli/core/layout';
|
|
245
|
-
const nodes = [...nodeMap.values()];
|
|
246
|
-
applyCircularLayout(nodes);
|
|
247
|
-
|
|
248
|
-
// Viewport math
|
|
249
|
-
import { zoomViewport } from '@visualli/core/viewport';
|
|
250
|
-
const next = zoomViewport(0.1, currentViewport, pivotX, pivotY);
|
|
251
|
-
|
|
252
|
-
// API client
|
|
253
|
-
import { initApi, mindmapApi } from '@visualli/core/services';
|
|
254
|
-
initApi({ baseUrl: 'https://api.example.com/api' });
|
|
255
|
-
const list = await mindmapApi.getAllMindmaps();
|
|
256
188
|
```
|
|
257
189
|
|
|
258
190
|
## Design Principles
|
|
259
191
|
|
|
260
192
|
- **No React** — no hooks, no JSX, no Context
|
|
261
|
-
- **No DOM manipulation** — pure data transforms;
|
|
262
|
-
- **No
|
|
193
|
+
- **No DOM manipulation** — pure data transforms; text measurement is passed in by the renderer
|
|
194
|
+
- **No design decisions of its own** — every visual rule comes from the Visualli design system (`design-system/`)
|
|
263
195
|
- **Tree-shakeable** — import from sub-paths to avoid bundling unused modules
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/generated/geometry/idea.ts
|
|
2
|
+
var LABEL_GROWTH = { idea: 1.3, connector: 1.6 };
|
|
3
|
+
var IDEA = {
|
|
4
|
+
/** Width an idea starts at… */
|
|
5
|
+
baseWidth: { root: 240, node: 200, mini: 120 },
|
|
6
|
+
/** …and the widest it gets before it grows taller instead. */
|
|
7
|
+
maxWidth: { root: 400, node: 360, mini: 200 },
|
|
8
|
+
/** Width grows in steps of this. */
|
|
9
|
+
step: 20,
|
|
10
|
+
/** Height = width × aspect (at least `minRy` × 2), more if the label needs it. */
|
|
11
|
+
aspect: 0.74,
|
|
12
|
+
minRy: 64,
|
|
13
|
+
/** The label's box is the idea's width less this (22 each side). */
|
|
14
|
+
labelInset: 44,
|
|
15
|
+
/** The idea widens until its label fits in this many lines. */
|
|
16
|
+
labelLines: 3,
|
|
17
|
+
/** Label size (before the label scale and zoom growth), weight from the canvas type styles. */
|
|
18
|
+
labelSize: { root: 30, node: 22, mini: 15 },
|
|
19
|
+
labelLineHeight: 1.15,
|
|
20
|
+
/** The label may fill at most this share of the idea's height. */
|
|
21
|
+
labelFillHeight: 0.7
|
|
22
|
+
};
|
|
23
|
+
function wrapLines(text, maxWidth, measure) {
|
|
24
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
25
|
+
const lines = [];
|
|
26
|
+
let cur = "";
|
|
27
|
+
for (const w of words) {
|
|
28
|
+
const next = cur ? `${cur} ${w}` : w;
|
|
29
|
+
if (cur && measure(next) > maxWidth) {
|
|
30
|
+
lines.push(cur);
|
|
31
|
+
cur = w;
|
|
32
|
+
} else cur = next;
|
|
33
|
+
}
|
|
34
|
+
if (cur) lines.push(cur);
|
|
35
|
+
return lines.length ? lines : [""];
|
|
36
|
+
}
|
|
37
|
+
function ideaSize(kind, label, measure, labelScale = 1) {
|
|
38
|
+
const px = IDEA.labelSize[kind] * labelScale * LABEL_GROWTH.idea;
|
|
39
|
+
const fit = (w) => wrapLines(label, w - IDEA.labelInset, (t) => measure(t, px));
|
|
40
|
+
const base = IDEA.baseWidth[kind], max = IDEA.maxWidth[kind];
|
|
41
|
+
let width = base, lines = fit(width);
|
|
42
|
+
while (lines.length > IDEA.labelLines && width < max) {
|
|
43
|
+
width = Math.min(max, width + IDEA.step);
|
|
44
|
+
lines = fit(width);
|
|
45
|
+
}
|
|
46
|
+
const rx = width / 2;
|
|
47
|
+
const ry = Math.max(IDEA.minRy, rx * IDEA.aspect, lines.length * px * IDEA.labelLineHeight / 2 / IDEA.labelFillHeight);
|
|
48
|
+
return { width, height: ry * 2, rx, ry, lines };
|
|
49
|
+
}
|
|
50
|
+
function labelGrowth(scale) {
|
|
51
|
+
const inv = 1 / Math.max(scale, 1e-4);
|
|
52
|
+
return { idea: Math.max(1, Math.min(inv, LABEL_GROWTH.idea)), connector: Math.max(1, Math.min(inv, LABEL_GROWTH.connector)) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/rendering/ideaSize.ts
|
|
56
|
+
var approximateMeasure = (text, px) => text.length * px * 0.5;
|
|
57
|
+
var ideaKindOf = (layer, index) => layer.level === 0 && (layer.layout ?? "radial") === "radial" && index === 0 ? "root" : "node";
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
LABEL_GROWTH,
|
|
61
|
+
IDEA,
|
|
62
|
+
wrapLines,
|
|
63
|
+
ideaSize,
|
|
64
|
+
labelGrowth,
|
|
65
|
+
approximateMeasure,
|
|
66
|
+
ideaKindOf
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=chunk-25AJTPMS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generated/geometry/idea.ts","../src/rendering/ideaSize.ts"],"sourcesContent":["// Generated from visualli.ai design-system/components/src/idea.ts — do not edit here.\n/* An idea's size and its label. An idea grows to show its whole label: it starts at its base width and widens in\n steps until the label fits in `labelLines` lines; at its widest it grows taller instead, so a label is never cut.\n Labels (and connector labels) grow a little when the map is zoomed out, so they stay readable. Every renderer sizes\n ideas with these rules, measuring text with its own font engine (`measure`). No dependencies, so it can travel alone. */\n\nexport type IdeaKind = 'root' | 'node' | 'mini';\n\n/** How much labels grow when zoomed out (`scale` = the map's zoom): never smaller than their size, up to these caps.\n * Idea labels and container names use `idea`; connector labels `connector`. Below 100% they grow; above, they don't. */\nexport const LABEL_GROWTH = { idea: 1.3, connector: 1.6 } as const;\n\nexport const IDEA = {\n /** Width an idea starts at… */ baseWidth: { root: 240, node: 200, mini: 120 },\n /** …and the widest it gets before it grows taller instead. */ maxWidth: { root: 400, node: 360, mini: 200 },\n /** Width grows in steps of this. */ step: 20,\n /** Height = width × aspect (at least `minRy` × 2), more if the label needs it. */ aspect: 0.74, minRy: 64,\n /** The label's box is the idea's width less this (22 each side). */ labelInset: 44,\n /** The idea widens until its label fits in this many lines. */ labelLines: 3,\n /** Label size (before the label scale and zoom growth), weight from the canvas type styles. */ labelSize: { root: 30, node: 22, mini: 15 },\n labelLineHeight: 1.15,\n /** The label may fill at most this share of the idea's height. */ labelFillHeight: 0.7,\n} as const;\n\n/** Greedy word wrap of `text` into lines no wider than `maxWidth` (a single word longer than that gets its own line). */\nexport function wrapLines(text: string, maxWidth: number, measure: (s: string) => number): string[] {\n const words = text.split(/\\s+/).filter(Boolean);\n const lines: string[] = [];\n let cur = '';\n for (const w of words) {\n const next = cur ? `${cur} ${w}` : w;\n if (cur && measure(next) > maxWidth) { lines.push(cur); cur = w; } else cur = next;\n }\n if (cur) lines.push(cur);\n return lines.length ? lines : [''];\n}\n\n/** An idea's size for its label: width, half-width / half-height for the blob, and the label's lines. The label is\n * measured at its largest drawn size — its type size × `labelScale` (Comfort: larger text) × LABEL_GROWTH.idea (zoomed\n * out) — so it fits at every zoom and the idea never changes size while zooming. `measure(text, px)` returns the\n * text's width in px in the label's face at `px`. */\nexport function ideaSize(kind: IdeaKind, label: string, measure: (text: string, px: number) => number, labelScale = 1) {\n const px = IDEA.labelSize[kind] * labelScale * LABEL_GROWTH.idea;\n const fit = (w: number) => wrapLines(label, w - IDEA.labelInset, (t) => measure(t, px));\n const base: number = IDEA.baseWidth[kind], max: number = IDEA.maxWidth[kind];\n let width = base, lines = fit(width);\n while (lines.length > IDEA.labelLines && width < max) { width = Math.min(max, width + IDEA.step); lines = fit(width); }\n const rx = width / 2;\n const ry = Math.max(IDEA.minRy, rx * IDEA.aspect, (lines.length * px * IDEA.labelLineHeight) / 2 / IDEA.labelFillHeight);\n return { width, height: ry * 2, rx, ry, lines };\n}\n\nexport function labelGrowth(scale: number): { idea: number; connector: number } {\n const inv = 1 / Math.max(scale, 0.0001);\n return { idea: Math.max(1, Math.min(inv, LABEL_GROWTH.idea)), connector: Math.max(1, Math.min(inv, LABEL_GROWTH.connector)) };\n}\n","// ─── Idea size and label growth ──────────────────────────────────────────────\n//\n// The design system's idea.ts (copied verbatim into generated/geometry): an idea\n// grows to show its whole label, and labels grow a little when zoomed out. The\n// SDK only supplies the text measurement (its font engine) and picks the kind.\n\nimport { IDEA, LABEL_GROWTH, ideaSize, labelGrowth, wrapLines, type IdeaKind } from '../generated/geometry/idea.js';\nimport type { VisualliLayer } from '../types/layer.js';\n\nexport { IDEA, LABEL_GROWTH, ideaSize, labelGrowth, wrapLines };\nexport type { IdeaKind };\n\n/** Width in px of `text` in the idea label's face at `px` (the label's weight; readable type adds its tracking). */\nexport type IdeaMeasure = (text: string, px: number) => number;\n\n/** Measurement without a font engine (server, tests): half an em per character. Renderers pass the real face. */\nexport const approximateMeasure: IdeaMeasure = (text, px) => text.length * px * 0.5;\n\n/** The size kind of the `index`-th idea of a layer: the centre of the root radial layer is the root, the rest ideas. */\nexport const ideaKindOf = (layer: Pick<VisualliLayer, 'level' | 'layout'>, index: number): IdeaKind =>\n layer.level === 0 && (layer.layout ?? 'radial') === 'radial' && index === 0 ? 'root' : 'node';\n"],"mappings":";AAUO,IAAM,eAAe,EAAE,MAAM,KAAK,WAAW,IAAI;AAEjD,IAAM,OAAO;AAAA;AAAA,EACc,WAAW,EAAE,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI;AAAA;AAAA,EACd,UAAU,EAAE,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI;AAAA;AAAA,EACtE,MAAM;AAAA;AAAA,EACwC,QAAQ;AAAA,EAAM,OAAO;AAAA;AAAA,EACnC,YAAY;AAAA;AAAA,EACjB,YAAY;AAAA;AAAA,EACoB,WAAW,EAAE,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG;AAAA,EAC1I,iBAAiB;AAAA;AAAA,EACkD,iBAAiB;AACtF;AAGO,SAAS,UAAU,MAAc,UAAkB,SAA0C;AAClG,QAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,OAAO,OAAO;AAC9C,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAM;AACV,aAAW,KAAK,OAAO;AACrB,UAAM,OAAO,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK;AACnC,QAAI,OAAO,QAAQ,IAAI,IAAI,UAAU;AAAE,YAAM,KAAK,GAAG;AAAG,YAAM;AAAA,IAAG,MAAO,OAAM;AAAA,EAChF;AACA,MAAI,IAAK,OAAM,KAAK,GAAG;AACvB,SAAO,MAAM,SAAS,QAAQ,CAAC,EAAE;AACnC;AAMO,SAAS,SAAS,MAAgB,OAAe,SAA+C,aAAa,GAAG;AACrH,QAAM,KAAK,KAAK,UAAU,IAAI,IAAI,aAAa,aAAa;AAC5D,QAAM,MAAM,CAAC,MAAc,UAAU,OAAO,IAAI,KAAK,YAAY,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC;AACtF,QAAM,OAAe,KAAK,UAAU,IAAI,GAAG,MAAc,KAAK,SAAS,IAAI;AAC3E,MAAI,QAAQ,MAAM,QAAQ,IAAI,KAAK;AACnC,SAAO,MAAM,SAAS,KAAK,cAAc,QAAQ,KAAK;AAAE,YAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI;AAAG,YAAQ,IAAI,KAAK;AAAA,EAAG;AACtH,QAAM,KAAK,QAAQ;AACnB,QAAM,KAAK,KAAK,IAAI,KAAK,OAAO,KAAK,KAAK,QAAS,MAAM,SAAS,KAAK,KAAK,kBAAmB,IAAI,KAAK,eAAe;AACvH,SAAO,EAAE,OAAO,QAAQ,KAAK,GAAG,IAAI,IAAI,MAAM;AAChD;AAEO,SAAS,YAAY,OAAoD;AAC9E,QAAM,MAAM,IAAI,KAAK,IAAI,OAAO,IAAM;AACtC,SAAO,EAAE,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,aAAa,IAAI,CAAC,GAAG,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,aAAa,SAAS,CAAC,EAAE;AAC9H;;;ACvCO,IAAM,qBAAkC,CAAC,MAAM,OAAO,KAAK,SAAS,KAAK;AAGzE,IAAM,aAAa,CAAC,OAAgD,UACzE,MAAM,UAAU,MAAM,MAAM,UAAU,cAAc,YAAY,UAAU,IAAI,SAAS;","names":[]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
METRICS
|
|
3
|
+
} from "./chunk-RFMUATWB.js";
|
|
4
|
+
|
|
5
|
+
// src/generated/geometry/blobShapes.ts
|
|
6
|
+
var BLOB_SHAPES = [[[1.12, 0], [0.86, 0.56], [0.46, 0.94], [-0.08, 1.06], [-0.68, 0.82], [-0.96, 0.08], [-0.55, -0.82], [0.04, -1.02], [0.72, -0.8], [1.08, -0.38]], [[-0.489, -0.625], [-0.384, -0.716], [-0.285, -0.792], [-0.189, -0.855], [-0.09, -0.903], [0.014, -0.932], [0.129, -0.94], [0.255, -0.924], [0.412, -0.876], [0.516, -0.832], [0.612, -0.78], [0.695, -0.725], [0.767, -0.666], [0.83, -0.606], [0.885, -0.546], [0.933, -0.486], [0.979, -0.425], [1.006, -0.385], [1.034, -0.334], [1.058, -0.276], [1.078, -0.209], [1.092, -0.133], [1.101, -0.046], [1.102, 0.052], [1.094, 0.173], [1.083, 0.244], [1.063, 0.324], [1.034, 0.401], [0.996, 0.477], [0.948, 0.549], [0.889, 0.617], [0.82, 0.681], [0.735, 0.741], [0.658, 0.783], [0.569, 0.822], [0.474, 0.855], [0.372, 0.882], [0.265, 0.904], [0.156, 0.92], [0.046, 0.931], [-0.069, 0.938], [-0.155, 0.935], [-0.25, 0.919], [-0.346, 0.89], [-0.443, 0.851], [-0.539, 0.8], [-0.633, 0.738], [-0.723, 0.667], [-0.808, 0.585], [-0.882, 0.501], [-0.944, 0.421], [-0.994, 0.343], [-1.034, 0.267], [-1.064, 0.192], [-1.085, 0.12], [-1.097, 0.042], [-1.102, 1e-3], [-1.09, -0.243], [-1.05, -0.365], [-0.99, -0.441], [-0.911, -0.479], [-0.819, -0.499], [-0.715, -0.517], [-0.605, -0.551]], [[3e-3, -0.97], [0.163, -0.984], [0.286, -1.028], [0.389, -1.096], [0.488, -1.174], [0.597, -1.21], [0.728, -1.165], [0.867, -1.036], [0.996, -0.839], [1.098, -0.59], [1.158, -0.304], [1.153, 0.012], [1.08, 0.336], [0.944, 0.639], [0.756, 0.889], [0.484, 1.072], [0.339, 1.079], [0.219, 1.051], [0.131, 0.994], [0.059, 0.939], [8e-3, 0.915], [-0.105, 0.931], [-0.176, 0.965], [-0.249, 1.005], [-0.345, 1.039], [-0.497, 1.055], [-0.672, 1.032], [-0.817, 0.952], [-0.945, 0.81], [-1.054, 0.6], [-1.16, 0.302], [-1.187, -0.011], [-1.165, -0.326], [-1.097, -0.599], [-0.991, -0.838], [-0.852, -0.912], [-0.695, -0.958], [-0.53, -0.988], [-0.357, -0.992], [-0.18, -0.983]], [[-0.734, -1.127], [-0.656, -1.146], [-0.567, -1.142], [-0.468, -1.115], [-0.358, -1.079], [-0.238, -1.034], [-0.107, -0.989], [0.035, -0.953], [0.188, -0.929], [0.325, -0.916], [0.484, -0.896], [0.65, -0.854], [0.811, -0.77], [0.955, -0.631], [1.068, -0.422], [1.138, -0.126], [1.152, 0.272], [1.121, 0.451], [1.046, 0.602], [0.928, 0.727], [0.767, 0.833], [0.565, 0.925], [0.323, 1.001], [0.042, 1.075], [-0.277, 1.142], [-0.368, 1.147], [-0.475, 1.136], [-0.589, 1.104], [-0.704, 1.05], [-0.815, 0.973], [-0.913, 0.871], [-0.993, 0.746], [-1.048, 0.593], [-1.109, 0.298], [-1.145, 0.018], [-1.154, -0.243], [-1.135, -0.482], [-1.085, -0.695], [-1.003, -0.874], [-0.886, -1.02]], [[-0.734, 1.127], [-0.656, 1.146], [-0.567, 1.142], [-0.468, 1.115], [-0.358, 1.079], [-0.238, 1.034], [-0.107, 0.989], [0.035, 0.953], [0.188, 0.929], [0.325, 0.916], [0.484, 0.896], [0.65, 0.854], [0.811, 0.77], [0.955, 0.631], [1.068, 0.422], [1.138, 0.126], [1.152, -0.272], [1.121, -0.451], [1.046, -0.602], [0.928, -0.727], [0.767, -0.833], [0.565, -0.925], [0.323, -1.001], [0.042, -1.075], [-0.277, -1.142], [-0.368, -1.147], [-0.475, -1.136], [-0.589, -1.104], [-0.704, -1.05], [-0.815, -0.973], [-0.913, -0.871], [-0.993, -0.746], [-1.048, -0.593], [-1.109, -0.298], [-1.145, -0.018], [-1.154, 0.243], [-1.135, 0.482], [-1.085, 0.695], [-1.003, 0.874], [-0.886, 1.02]], [[1e-3, -1.006], [0.134, -1.001], [0.262, -1.024], [0.382, -1.073], [0.498, -1.107], [0.614, -1.105], [0.733, -1.049], [0.85, -0.932], [0.953, -0.755], [1.03, -0.529], [1.067, -0.265], [1.054, 0.018], [0.986, 0.3], [0.868, 0.557], [0.715, 0.766], [0.55, 0.909], [0.396, 0.984], [0.262, 1.005], [0.148, 1.003], [0.037, 1.008], [-0.083, 1.04], [-0.22, 1.118], [-0.36, 1.192], [-0.5, 1.234], [-0.65, 1.175], [-0.81, 1.045], [-0.99, 0.855], [-1.14, 0.61], [-1.24, 0.33], [-1.27, 0.04], [-1.275, -0.25], [-1.225, -0.5], [-1.08, -0.72], [-0.89, -0.92], [-0.734, -1.053], [-0.614, -1.112], [-0.498, -1.127], [-0.381, -1.11], [-0.261, -1.095], [-0.133, -1.058], [1e-3, -1.01], [0.134, -1.001]]];
|
|
7
|
+
|
|
8
|
+
// src/generated/geometry/blob.ts
|
|
9
|
+
var SHAPE = { idea: 0, varyByLevel: false };
|
|
10
|
+
function shapeForLevel(level) {
|
|
11
|
+
if (!SHAPE.varyByLevel) return SHAPE.idea;
|
|
12
|
+
const n = BLOB_SHAPES.length;
|
|
13
|
+
let cur = SHAPE.idea;
|
|
14
|
+
for (let i = 1; i <= level; i++) {
|
|
15
|
+
const r = Math.floor(Math.abs(Math.sin((i + 1) * 12.9898) * 43758.5453));
|
|
16
|
+
cur = (cur + r % (n - 1) + 1) % n;
|
|
17
|
+
}
|
|
18
|
+
return cur;
|
|
19
|
+
}
|
|
20
|
+
function blobPath(shape, rx, ry, cx = 0, cy = 0) {
|
|
21
|
+
const pts = BLOB_SHAPES[(shape % BLOB_SHAPES.length + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [cx + x * rx, cy + y * ry]);
|
|
22
|
+
const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
23
|
+
const f = (n) => n.toFixed(2);
|
|
24
|
+
const start = mid(pts[pts.length - 1], pts[0]);
|
|
25
|
+
let d = `M${f(start[0])} ${f(start[1])}`;
|
|
26
|
+
for (let i = 0; i < pts.length; i++) {
|
|
27
|
+
const p = pts[i], m = mid(p, pts[(i + 1) % pts.length]);
|
|
28
|
+
d += `Q${f(p[0])} ${f(p[1])} ${f(m[0])} ${f(m[1])}`;
|
|
29
|
+
}
|
|
30
|
+
return d + "Z";
|
|
31
|
+
}
|
|
32
|
+
function drawBlob(ctx, shape, rx, ry, cx = 0, cy = 0) {
|
|
33
|
+
const pts = BLOB_SHAPES[(shape % BLOB_SHAPES.length + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [cx + x * rx, cy + y * ry]);
|
|
34
|
+
const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
35
|
+
const s = mid(pts[pts.length - 1], pts[0]);
|
|
36
|
+
ctx.beginPath();
|
|
37
|
+
ctx.moveTo(s[0], s[1]);
|
|
38
|
+
pts.forEach((p, i) => {
|
|
39
|
+
const m = mid(p, pts[(i + 1) % pts.length]);
|
|
40
|
+
ctx.quadraticCurveTo(p[0], p[1], m[0], m[1]);
|
|
41
|
+
});
|
|
42
|
+
ctx.closePath();
|
|
43
|
+
}
|
|
44
|
+
function blobRadius(shape, rx, ry, angle) {
|
|
45
|
+
const pts = BLOB_SHAPES[(shape % BLOB_SHAPES.length + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [x * rx, y * ry]);
|
|
46
|
+
const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
47
|
+
const poly = [];
|
|
48
|
+
for (let i = 0; i < pts.length; i++) {
|
|
49
|
+
const p0 = mid(pts[(i - 1 + pts.length) % pts.length], pts[i]), c = pts[i], p1 = mid(pts[i], pts[(i + 1) % pts.length]);
|
|
50
|
+
for (let k = 0; k < 8; k++) {
|
|
51
|
+
const t = k / 8, u = 1 - t;
|
|
52
|
+
poly.push([u * u * p0[0] + 2 * u * t * c[0] + t * t * p1[0], u * u * p0[1] + 2 * u * t * c[1] + t * t * p1[1]]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const dx = Math.cos(angle), dy = Math.sin(angle);
|
|
56
|
+
let best = 0;
|
|
57
|
+
for (let i = 0; i < poly.length; i++) {
|
|
58
|
+
const [ax, ay] = poly[i], [bx, by] = poly[(i + 1) % poly.length], ex = bx - ax, ey = by - ay;
|
|
59
|
+
const den = dx * ey - dy * ex;
|
|
60
|
+
if (Math.abs(den) < 1e-9) continue;
|
|
61
|
+
const r = (ax * ey - ay * ex) / den, q = (ax * dy - ay * dx) / den;
|
|
62
|
+
if (r > 0 && q >= 0 && q <= 1) best = Math.max(best, r);
|
|
63
|
+
}
|
|
64
|
+
return best || Math.max(rx, ry);
|
|
65
|
+
}
|
|
66
|
+
var RINGS = [
|
|
67
|
+
{ scale: 1.1, opacity: 0.5, rotate: -5, width: 2, dash: "" },
|
|
68
|
+
{ scale: 1.2, opacity: 0.4, rotate: -10, width: 1, dash: "" },
|
|
69
|
+
{ scale: 1.3, opacity: 0.3, rotate: -15, width: 3, dash: "6 4" }
|
|
70
|
+
];
|
|
71
|
+
function edgePath(a, b, bow = 1) {
|
|
72
|
+
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1;
|
|
73
|
+
const arc = Math.min(45, 45 / (1 + Math.max(0, (d - 200) / 400))) * bow;
|
|
74
|
+
const nx = -dy / d, ny = dx / d;
|
|
75
|
+
const mx = (a.x + b.x) / 2 + nx * arc, my = (a.y + b.y) / 2 + ny * arc;
|
|
76
|
+
const c1 = { x: a.x + (mx - a.x) * 0.66 + 0, y: a.y + (my - a.y) * 0.66 };
|
|
77
|
+
const c2 = { x: b.x + (mx - b.x) * 0.66, y: b.y + (my - b.y) * 0.66 };
|
|
78
|
+
const ang = Math.atan2(b.y - c2.y, b.x - c2.x);
|
|
79
|
+
return { d: `M${a.x.toFixed(1)} ${a.y.toFixed(1)}C${c1.x.toFixed(1)} ${c1.y.toFixed(1)} ${c2.x.toFixed(1)} ${c2.y.toFixed(1)} ${b.x.toFixed(1)} ${b.y.toFixed(1)}`, angle: ang, mid: { x: mx, y: my } };
|
|
80
|
+
}
|
|
81
|
+
function arrowPath(p, angle, len = 11) {
|
|
82
|
+
const s = Math.PI / 5.5;
|
|
83
|
+
const a1 = { x: p.x - len * Math.cos(angle - s), y: p.y - len * Math.sin(angle - s) };
|
|
84
|
+
const a2 = { x: p.x - len * Math.cos(angle + s), y: p.y - len * Math.sin(angle + s) };
|
|
85
|
+
return `M${a1.x.toFixed(1)} ${a1.y.toFixed(1)}L${p.x.toFixed(1)} ${p.y.toFixed(1)}L${a2.x.toFixed(1)} ${a2.y.toFixed(1)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/rendering/nodeGeometry.ts
|
|
89
|
+
var MAX_RINGS = RINGS.length;
|
|
90
|
+
var DEG = Math.PI / 180;
|
|
91
|
+
function nodeRadii(n) {
|
|
92
|
+
return { rx: n.width / 2, ry: n.height / 2 };
|
|
93
|
+
}
|
|
94
|
+
var ringCount = (branchCount) => Math.max(0, Math.min(branchCount ?? 0, MAX_RINGS));
|
|
95
|
+
var ringsFor = (branchCount) => RINGS.slice(0, ringCount(branchCount));
|
|
96
|
+
var shapeCache = /* @__PURE__ */ new Map();
|
|
97
|
+
function shapeOfLevel(level) {
|
|
98
|
+
let s = shapeCache.get(level);
|
|
99
|
+
if (s === void 0) {
|
|
100
|
+
s = shapeForLevel(level);
|
|
101
|
+
shapeCache.set(level, s);
|
|
102
|
+
}
|
|
103
|
+
return s;
|
|
104
|
+
}
|
|
105
|
+
var BLOB_MAX_NORM = Math.max(...BLOB_SHAPES.flatMap((s) => s.map(([x, y]) => Math.hypot(x, y))));
|
|
106
|
+
var VISUAL_MARGIN = METRICS.nodeStroke * 2 + 24;
|
|
107
|
+
function nodeBounds(n) {
|
|
108
|
+
const { rx, ry } = nodeRadii(n);
|
|
109
|
+
const rings = ringCount(n.branchCount);
|
|
110
|
+
const scale = rings > 0 ? RINGS[rings - 1].scale : 1;
|
|
111
|
+
const r = BLOB_MAX_NORM * Math.max(rx, ry) * scale + VISUAL_MARGIN;
|
|
112
|
+
return { minX: n.x - r, minY: n.y - r, maxX: n.x + r, maxY: n.y + r };
|
|
113
|
+
}
|
|
114
|
+
function outlineRadius(n, angle) {
|
|
115
|
+
const { rx, ry } = nodeRadii(n);
|
|
116
|
+
const shape = shapeOfLevel(n.level);
|
|
117
|
+
const rings = ringCount(n.branchCount);
|
|
118
|
+
if (rings === 0) return blobRadius(shape, rx, ry, angle);
|
|
119
|
+
const ring = RINGS[rings - 1];
|
|
120
|
+
return blobRadius(shape, rx * ring.scale, ry * ring.scale, angle - ring.rotate * DEG);
|
|
121
|
+
}
|
|
122
|
+
function connectorGeometry(from, to, gap = METRICS.edgeGap) {
|
|
123
|
+
const theta = Math.atan2(to.y - from.y, to.x - from.x);
|
|
124
|
+
const ra = outlineRadius(from, theta) + gap;
|
|
125
|
+
const rb = outlineRadius(to, theta + Math.PI) + gap;
|
|
126
|
+
const a = { x: from.x + Math.cos(theta) * ra, y: from.y + Math.sin(theta) * ra };
|
|
127
|
+
const b = { x: to.x - Math.cos(theta) * rb, y: to.y - Math.sin(theta) * rb };
|
|
128
|
+
const e = edgePath(a, b);
|
|
129
|
+
return { a, b, d: e.d, angle: e.angle, mid: e.mid, arrow: arrowPath(b, e.angle) };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
BLOB_SHAPES,
|
|
134
|
+
SHAPE,
|
|
135
|
+
shapeForLevel,
|
|
136
|
+
blobPath,
|
|
137
|
+
drawBlob,
|
|
138
|
+
blobRadius,
|
|
139
|
+
RINGS,
|
|
140
|
+
edgePath,
|
|
141
|
+
arrowPath,
|
|
142
|
+
MAX_RINGS,
|
|
143
|
+
nodeRadii,
|
|
144
|
+
ringCount,
|
|
145
|
+
ringsFor,
|
|
146
|
+
shapeOfLevel,
|
|
147
|
+
nodeBounds,
|
|
148
|
+
outlineRadius,
|
|
149
|
+
connectorGeometry
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=chunk-64PUANNH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generated/geometry/blobShapes.ts","../src/generated/geometry/blob.ts","../src/rendering/nodeGeometry.ts"],"sourcesContent":["// Generated from visualli.ai design-system/components/src/blobShapes.ts — do not edit here.\n// The six hand-authored Visualli node outlines, in normalized units (rx = ry = 1).\n// Copied verbatim from the web app (features/canvas/config/blobShapes.ts). Shape 0 is the root shape.\nexport const BLOB_SHAPES: number[][][] = [[[1.12,0.0],[0.86,0.56],[0.46,0.94],[-0.08,1.06],[-0.68,0.82],[-0.96,0.08],[-0.55,-0.82],[0.04,-1.02],[0.72,-0.8],[1.08,-0.38]],[[-0.489,-0.625],[-0.384,-0.716],[-0.285,-0.792],[-0.189,-0.855],[-0.09,-0.903],[0.014,-0.932],[0.129,-0.94],[0.255,-0.924],[0.412,-0.876],[0.516,-0.832],[0.612,-0.78],[0.695,-0.725],[0.767,-0.666],[0.83,-0.606],[0.885,-0.546],[0.933,-0.486],[0.979,-0.425],[1.006,-0.385],[1.034,-0.334],[1.058,-0.276],[1.078,-0.209],[1.092,-0.133],[1.101,-0.046],[1.102,0.052],[1.094,0.173],[1.083,0.244],[1.063,0.324],[1.034,0.401],[0.996,0.477],[0.948,0.549],[0.889,0.617],[0.82,0.681],[0.735,0.741],[0.658,0.783],[0.569,0.822],[0.474,0.855],[0.372,0.882],[0.265,0.904],[0.156,0.92],[0.046,0.931],[-0.069,0.938],[-0.155,0.935],[-0.25,0.919],[-0.346,0.89],[-0.443,0.851],[-0.539,0.8],[-0.633,0.738],[-0.723,0.667],[-0.808,0.585],[-0.882,0.501],[-0.944,0.421],[-0.994,0.343],[-1.034,0.267],[-1.064,0.192],[-1.085,0.12],[-1.097,0.042],[-1.102,0.001],[-1.09,-0.243],[-1.05,-0.365],[-0.99,-0.441],[-0.911,-0.479],[-0.819,-0.499],[-0.715,-0.517],[-0.605,-0.551]],[[0.003,-0.97],[0.163,-0.984],[0.286,-1.028],[0.389,-1.096],[0.488,-1.174],[0.597,-1.21],[0.728,-1.165],[0.867,-1.036],[0.996,-0.839],[1.098,-0.59],[1.158,-0.304],[1.153,0.012],[1.08,0.336],[0.944,0.639],[0.756,0.889],[0.484,1.072],[0.339,1.079],[0.219,1.051],[0.131,0.994],[0.059,0.939],[0.008,0.915],[-0.105,0.931],[-0.176,0.965],[-0.249,1.005],[-0.345,1.039],[-0.497,1.055],[-0.672,1.032],[-0.817,0.952],[-0.945,0.81],[-1.054,0.6],[-1.16,0.302],[-1.187,-0.011],[-1.165,-0.326],[-1.097,-0.599],[-0.991,-0.838],[-0.852,-0.912],[-0.695,-0.958],[-0.53,-0.988],[-0.357,-0.992],[-0.18,-0.983]],[[-0.734,-1.127],[-0.656,-1.146],[-0.567,-1.142],[-0.468,-1.115],[-0.358,-1.079],[-0.238,-1.034],[-0.107,-0.989],[0.035,-0.953],[0.188,-0.929],[0.325,-0.916],[0.484,-0.896],[0.65,-0.854],[0.811,-0.77],[0.955,-0.631],[1.068,-0.422],[1.138,-0.126],[1.152,0.272],[1.121,0.451],[1.046,0.602],[0.928,0.727],[0.767,0.833],[0.565,0.925],[0.323,1.001],[0.042,1.075],[-0.277,1.142],[-0.368,1.147],[-0.475,1.136],[-0.589,1.104],[-0.704,1.05],[-0.815,0.973],[-0.913,0.871],[-0.993,0.746],[-1.048,0.593],[-1.109,0.298],[-1.145,0.018],[-1.154,-0.243],[-1.135,-0.482],[-1.085,-0.695],[-1.003,-0.874],[-0.886,-1.02]],[[-0.734,1.127],[-0.656,1.146],[-0.567,1.142],[-0.468,1.115],[-0.358,1.079],[-0.238,1.034],[-0.107,0.989],[0.035,0.953],[0.188,0.929],[0.325,0.916],[0.484,0.896],[0.65,0.854],[0.811,0.77],[0.955,0.631],[1.068,0.422],[1.138,0.126],[1.152,-0.272],[1.121,-0.451],[1.046,-0.602],[0.928,-0.727],[0.767,-0.833],[0.565,-0.925],[0.323,-1.001],[0.042,-1.075],[-0.277,-1.142],[-0.368,-1.147],[-0.475,-1.136],[-0.589,-1.104],[-0.704,-1.05],[-0.815,-0.973],[-0.913,-0.871],[-0.993,-0.746],[-1.048,-0.593],[-1.109,-0.298],[-1.145,-0.018],[-1.154,0.243],[-1.135,0.482],[-1.085,0.695],[-1.003,0.874],[-0.886,1.02]],[[0.001,-1.006],[0.134,-1.001],[0.262,-1.024],[0.382,-1.073],[0.498,-1.107],[0.614,-1.105],[0.733,-1.049],[0.85,-0.932],[0.953,-0.755],[1.03,-0.529],[1.067,-0.265],[1.054,0.018],[0.986,0.3],[0.868,0.557],[0.715,0.766],[0.55,0.909],[0.396,0.984],[0.262,1.005],[0.148,1.003],[0.037,1.008],[-0.083,1.04],[-0.22,1.118],[-0.36,1.192],[-0.5,1.234],[-0.65,1.175],[-0.81,1.045],[-0.99,0.855],[-1.14,0.61],[-1.24,0.33],[-1.27,0.04],[-1.275,-0.25],[-1.225,-0.5],[-1.08,-0.72],[-0.89,-0.92],[-0.734,-1.053],[-0.614,-1.112],[-0.498,-1.127],[-0.381,-1.11],[-0.261,-1.095],[-0.133,-1.058],[0.001,-1.01],[0.134,-1.001]]];\n","// Generated from visualli.ai design-system/components/src/blob.ts — do not edit here.\nimport { BLOB_SHAPES } from './blobShapes';\nexport { BLOB_SHAPES };\n\n/** Which outline ideas take. Every idea, at every depth, has the same shape (`idea`, the shape of the map's centre);\n * depth shows through the depth trail, rings, the layer tint and the step-inside motion. The other authored shapes\n * stay in BLOB_SHAPES for explicit use (`Node shape`); `varyByLevel: true` brings back one shape per level. */\nexport const SHAPE: { idea: number; varyByLevel: boolean } = { idea: 0, varyByLevel: false };\n\n/** Shape for a layer level: SHAPE.idea for every level. With SHAPE.varyByLevel, level 0 is SHAPE.idea and consecutive\n * levels never repeat. */\nexport function shapeForLevel(level: number): number {\n if (!SHAPE.varyByLevel) return SHAPE.idea;\n const n = BLOB_SHAPES.length;\n let cur = SHAPE.idea;\n for (let i = 1; i <= level; i++) {\n const r = Math.floor(Math.abs(Math.sin((i + 1) * 12.9898) * 43758.5453));\n cur = (cur + (r % (n - 1)) + 1) % n;\n }\n return cur;\n}\n\n/** SVG path of a node outline centered on (cx, cy): midpoint-quadratic smoothing through the authored points. */\nexport function blobPath(shape: number, rx: number, ry: number, cx = 0, cy = 0): string {\n const pts = BLOB_SHAPES[((shape % BLOB_SHAPES.length) + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [cx + x * rx, cy + y * ry]);\n const mid = (a: number[], b: number[]) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n const f = (n: number) => n.toFixed(2);\n const start = mid(pts[pts.length - 1], pts[0]);\n let d = `M${f(start[0])} ${f(start[1])}`;\n for (let i = 0; i < pts.length; i++) {\n const p = pts[i], m = mid(p, pts[(i + 1) % pts.length]);\n d += `Q${f(p[0])} ${f(p[1])} ${f(m[0])} ${f(m[1])}`;\n }\n return d + 'Z';\n}\n\n/** The path calls drawBlob makes: satisfied by a CanvasRenderingContext2D and by Konva's Context (sceneFunc / hitFunc). */\nexport type PathContext = { beginPath(): void; moveTo(x: number, y: number): void; quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void; closePath(): void };\n\n/** Canvas twin of blobPath — call fill()/stroke() after (in Konva: ctx.fillStrokeShape(shape)). */\nexport function drawBlob(ctx: PathContext, shape: number, rx: number, ry: number, cx = 0, cy = 0) {\n const pts = BLOB_SHAPES[((shape % BLOB_SHAPES.length) + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [cx + x * rx, cy + y * ry]);\n const mid = (a: number[], b: number[]) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n const s = mid(pts[pts.length - 1], pts[0]);\n ctx.beginPath();\n ctx.moveTo(s[0], s[1]);\n pts.forEach((p, i) => { const m = mid(p, pts[(i + 1) % pts.length]); ctx.quadraticCurveTo(p[0], p[1], m[0], m[1]); });\n ctx.closePath();\n}\n\n/** Distance from the center to the smoothed outline (blobPath) in direction `angle` (radians, SVG y-down).\n * Connectors use it to start and end just outside the outline an idea actually has, not a stand-in ellipse. */\nexport function blobRadius(shape: number, rx: number, ry: number, angle: number): number {\n const pts = BLOB_SHAPES[((shape % BLOB_SHAPES.length) + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [x * rx, y * ry]);\n const mid = (a: number[], b: number[]) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n // Sample the midpoint-quadratic curve into a polygon, then cast a ray from the center.\n const poly: number[][] = [];\n for (let i = 0; i < pts.length; i++) {\n const p0 = mid(pts[(i - 1 + pts.length) % pts.length], pts[i]), c = pts[i], p1 = mid(pts[i], pts[(i + 1) % pts.length]);\n for (let k = 0; k < 8; k++) { const t = k / 8, u = 1 - t; poly.push([u * u * p0[0] + 2 * u * t * c[0] + t * t * p1[0], u * u * p0[1] + 2 * u * t * c[1] + t * t * p1[1]]); }\n }\n const dx = Math.cos(angle), dy = Math.sin(angle);\n let best = 0;\n for (let i = 0; i < poly.length; i++) {\n const [ax, ay] = poly[i], [bx, by] = poly[(i + 1) % poly.length], ex = bx - ax, ey = by - ay;\n const den = dx * ey - dy * ex;\n if (Math.abs(den) < 1e-9) continue;\n const r = (ax * ey - ay * ex) / den, q = (ax * dy - ay * dx) / den;\n if (r > 0 && q >= 0 && q <= 1) best = Math.max(best, r);\n }\n return best || Math.max(rx, ry);\n}\n\n/** Ring recipe: index 0 is the innermost ring. A node shows min(childLayers, 3) rings. */\nexport const RINGS = [\n { scale: 1.1, opacity: 0.5, rotate: -5, width: 2, dash: '' },\n { scale: 1.2, opacity: 0.4, rotate: -10, width: 1, dash: '' },\n { scale: 1.3, opacity: 0.3, rotate: -15, width: 3, dash: '6 4' },\n];\n\n/** Connector geometry: a cubic that bows perpendicular to the chord, flatter as distance grows. */\nexport function edgePath(a: { x: number; y: number }, b: { x: number; y: number }, bow = 1) {\n const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1;\n const arc = Math.min(45, 45 / (1 + Math.max(0, (d - 200) / 400))) * bow;\n const nx = -dy / d, ny = dx / d;\n const mx = (a.x + b.x) / 2 + nx * arc, my = (a.y + b.y) / 2 + ny * arc;\n const c1 = { x: a.x + (mx - a.x) * 0.66 + 0, y: a.y + (my - a.y) * 0.66 };\n const c2 = { x: b.x + (mx - b.x) * 0.66, y: b.y + (my - b.y) * 0.66 };\n const ang = Math.atan2(b.y - c2.y, b.x - c2.x);\n return { d: `M${a.x.toFixed(1)} ${a.y.toFixed(1)}C${c1.x.toFixed(1)} ${c1.y.toFixed(1)} ${c2.x.toFixed(1)} ${c2.y.toFixed(1)} ${b.x.toFixed(1)} ${b.y.toFixed(1)}`, angle: ang, mid: { x: mx, y: my } };\n}\n\n/** Open V arrowhead at point p, pointing along angle. */\nexport function arrowPath(p: { x: number; y: number }, angle: number, len = 11) {\n const s = Math.PI / 5.5;\n const a1 = { x: p.x - len * Math.cos(angle - s), y: p.y - len * Math.sin(angle - s) };\n const a2 = { x: p.x - len * Math.cos(angle + s), y: p.y - len * Math.sin(angle + s) };\n return `M${a1.x.toFixed(1)} ${a1.y.toFixed(1)}L${p.x.toFixed(1)} ${p.y.toFixed(1)}L${a2.x.toFixed(1)} ${a2.y.toFixed(1)}`;\n}\n","// ─── Node & connector geometry ───────────────────────────────────────────────\n//\n// Thin layer over the design system's geometry (generated/geometry/blob.ts):\n// it chooses the radii, shapes, rings and endpoints for a FlatNode and never\n// re-implements outline or curve math. Pure functions, no DOM.\n\nimport { BLOB_SHAPES, RINGS, SHAPE, arrowPath, blobPath, blobRadius, edgePath, shapeForLevel } from '../generated/geometry/blob.js';\nimport { METRICS } from '../generated/designSystem.js';\nimport type { FlatNode } from '../types/mindmap.js';\n\n// The design system's geometry is part of the public API (single source of truth).\nexport { BLOB_SHAPES, RINGS, SHAPE, arrowPath, blobPath, blobRadius, edgePath, shapeForLevel };\nexport { drawBlob } from '../generated/geometry/blob.js';\n\n/** Minimum a node needs to be drawn / connected. */\nexport type NodeLike = Pick<FlatNode, 'x' | 'y' | 'width' | 'height' | 'level' | 'branchCount'>;\n\nexport const MAX_RINGS = RINGS.length;\nconst DEG = Math.PI / 180;\n\n/** Outline radii of an idea: half its size (sized for its label by the design system's idea.ts). */\nexport function nodeRadii(n: Pick<FlatNode, 'width' | 'height'>): { rx: number; ry: number } {\n return { rx: n.width / 2, ry: n.height / 2 };\n}\n\n/** Rings an idea shows: one per layer beneath it, at most 3. */\nexport const ringCount = (branchCount: number | undefined): number => Math.max(0, Math.min(branchCount ?? 0, MAX_RINGS));\n\n/** The design system's ring recipes an idea shows, innermost first (index 0 = scale 1.1). */\nexport const ringsFor = (branchCount: number | undefined): typeof RINGS => RINGS.slice(0, ringCount(branchCount));\n\nconst shapeCache = new Map<number, number>();\n/** Outline shape for a layer level (memoised: shapeForLevel is O(level)). */\nexport function shapeOfLevel(level: number): number {\n let s = shapeCache.get(level);\n if (s === undefined) { s = shapeForLevel(level); shapeCache.set(level, s); }\n return s;\n}\n\n/** Largest distance from a blob's centre to any control point, in units of max(rx, ry). */\nconst BLOB_MAX_NORM = Math.max(...BLOB_SHAPES.flatMap((s) => s.map(([x, y]) => Math.hypot(x!, y!))));\n\n/** Visual margin around an idea beyond its outline: stroke, hover lift and the node shadow. */\nconst VISUAL_MARGIN = METRICS.nodeStroke * 2 + 24;\n\n/**\n * Conservative world-space bounds of everything drawn for an idea (rings and\n * stroke included). Ideas are drawn CENTRED on (x, y), so bounds are centred\n * too. A circle bound is used so ring rotation can never push ink outside it.\n */\nexport function nodeBounds(n: NodeLike): { minX: number; minY: number; maxX: number; maxY: number } {\n const { rx, ry } = nodeRadii(n);\n const rings = ringCount(n.branchCount);\n const scale = rings > 0 ? RINGS[rings - 1]!.scale : 1;\n const r = BLOB_MAX_NORM * Math.max(rx, ry) * scale + VISUAL_MARGIN;\n return { minX: n.x - r, minY: n.y - r, maxX: n.x + r, maxY: n.y + r };\n}\n\n/**\n * Distance from an idea's centre to the outline connectors must clear, in the\n * world direction `angle` (radians, y down). That is the outermost visible\n * ring (rotated like the ring is drawn), or the body when there are no rings.\n */\nexport function outlineRadius(n: NodeLike, angle: number): number {\n const { rx, ry } = nodeRadii(n);\n const shape = shapeOfLevel(n.level);\n const rings = ringCount(n.branchCount);\n if (rings === 0) return blobRadius(shape, rx, ry, angle);\n const ring = RINGS[rings - 1]!;\n // The ring is the same blob scaled and rotated about the centre, so look up\n // the radius in the ring's own frame.\n return blobRadius(shape, rx * ring.scale, ry * ring.scale, angle - ring.rotate * DEG);\n}\n\nexport interface ConnectorGeometry {\n /** Start / end points: `edge-gap` outside the source / target outline. */\n a: { x: number; y: number };\n b: { x: number; y: number };\n /** Cubic path data (SVG syntax) and the arrowhead direction, from the design system's edgePath. */\n d: string;\n angle: number;\n /** Bow apex returned by edgePath, where the connector label sits. */\n mid: { x: number; y: number };\n /** Open arrowhead path data at b, from the design system's arrowPath. */\n arrow: string;\n}\n\n/** Endpoints and curve of a connector between two ideas. */\nexport function connectorGeometry(from: NodeLike, to: NodeLike, gap: number = METRICS.edgeGap): ConnectorGeometry {\n const theta = Math.atan2(to.y - from.y, to.x - from.x);\n const ra = outlineRadius(from, theta) + gap;\n const rb = outlineRadius(to, theta + Math.PI) + gap;\n const a = { x: from.x + Math.cos(theta) * ra, y: from.y + Math.sin(theta) * ra };\n const b = { x: to.x - Math.cos(theta) * rb, y: to.y - Math.sin(theta) * rb };\n const e = edgePath(a, b);\n return { a, b, d: e.d, angle: e.angle, mid: e.mid, arrow: arrowPath(b, e.angle) };\n}\n"],"mappings":"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,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,MAAK,MAAM,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,IAAI,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,KAAK,CAAC,GAAE,CAAC,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,MAAK,KAAK,GAAE,CAAC,OAAM,IAAI,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,IAAI,CAAC,GAAE,CAAC,CAAC,MAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,MAAK,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,MAAK,MAAM,GAAE,CAAC,OAAM,MAAM,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,GAAG,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,MAAK,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,QAAO,IAAI,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,MAAK,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,IAAI,GAAE,CAAC,OAAM,IAAI,GAAE,CAAC,OAAM,IAAI,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,IAAI,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,OAAM,KAAK,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,KAAK,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,QAAO,MAAM,GAAE,CAAC,MAAM,KAAK,GAAE,CAAC,OAAM,MAAM,CAAC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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-RFMUATWB.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-ITDZ72JE.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":[]}
|