@restless-dreams/orbit 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +165 -2
- package/dist/bin/orbit.js +8907 -0
- package/dist/bin/orbit.js.map +48 -0
- package/dist/chunk-0gwh723s.js +408 -0
- package/dist/chunk-0gwh723s.js.map +10 -0
- package/dist/chunk-7n00wx0t.js +82 -0
- package/dist/chunk-7n00wx0t.js.map +10 -0
- package/dist/chunk-mtrnm0yc.js +16993 -0
- package/dist/chunk-mtrnm0yc.js.map +63 -0
- package/dist/core/auto-render.d.ts +85 -0
- package/dist/core/browser.d.ts +23 -0
- package/dist/core/create.d.ts +23 -0
- package/dist/core/diagnostics.d.ts +40 -0
- package/dist/core/dispatch.d.ts +23 -0
- package/dist/core/document-options.d.ts +18 -0
- package/dist/core/document-source.d.ts +128 -0
- package/dist/core/editing.d.ts +63 -0
- package/dist/core/engine/animation-clock.d.ts +40 -0
- package/dist/core/engine/camera.d.ts +70 -0
- package/dist/core/engine/colors.d.ts +51 -0
- package/dist/core/engine/display.d.ts +125 -0
- package/dist/core/engine/export.d.ts +32 -0
- package/dist/core/engine/glide.d.ts +34 -0
- package/dist/core/engine/labels.d.ts +106 -0
- package/dist/core/engine/node-drag.d.ts +49 -0
- package/dist/core/engine/scene.d.ts +159 -0
- package/dist/core/engine/spin.d.ts +62 -0
- package/dist/core/engine/surface-pattern.d.ts +20 -0
- package/dist/core/engine/text-measure.d.ts +116 -0
- package/dist/core/engine/views.d.ts +353 -0
- package/dist/core/extensions.d.ts +155 -0
- package/dist/core/format-upgrades.d.ts +33 -0
- package/dist/core/index.d.ts +36 -0
- package/dist/core/input-controller.d.ts +109 -0
- package/dist/core/languages.d.ts +199 -0
- package/dist/core/layout.d.ts +61 -0
- package/dist/core/map-tools/definitions.d.ts +71 -0
- package/dist/core/map-tools/input.d.ts +62 -0
- package/dist/core/map-tools/reading.d.ts +40 -0
- package/dist/core/map-tools/tools.d.ts +51 -0
- package/dist/core/markdown-writing.d.ts +87 -0
- package/dist/core/material.d.ts +25 -0
- package/dist/core/media.d.ts +42 -0
- package/dist/core/node-attributes.d.ts +31 -0
- package/dist/core/node-links.d.ts +25 -0
- package/dist/core/parser-markdown.d.ts +42 -0
- package/dist/core/parser-yaml.d.ts +8 -0
- package/dist/core/render-to-svg.d.ts +57 -0
- package/dist/core/serializer-markdown.d.ts +18 -0
- package/dist/core/serializer-yaml.d.ts +2 -0
- package/dist/core/source-patch.d.ts +24 -0
- package/dist/core/source.d.ts +27 -0
- package/dist/core/styles.d.ts +6 -0
- package/dist/core/suggest.d.ts +8 -0
- package/dist/core/svg-renderer.d.ts +729 -0
- package/dist/core/text-layout.d.ts +54 -0
- package/dist/core/themes.d.ts +62 -0
- package/dist/core/types.d.ts +463 -0
- package/dist/core/ui/actions.d.ts +75 -0
- package/dist/core/ui/controls.d.ts +61 -0
- package/dist/core/ui/detail-sheet.d.ts +88 -0
- package/dist/core/ui/frames.d.ts +11 -0
- package/dist/core/ui/icons.d.ts +14 -0
- package/dist/core/ui/inline-editor.d.ts +79 -0
- package/dist/core/ui/media-players.d.ts +65 -0
- package/dist/core/ui/media-viewer.d.ts +169 -0
- package/dist/core/ui/media-zoom.d.ts +51 -0
- package/dist/core/ui/note-markdown.d.ts +19 -0
- package/dist/core/ui/outline.d.ts +44 -0
- package/dist/core/url-guard.d.ts +40 -0
- package/dist/core/utils.d.ts +23 -0
- package/dist/core/web-page/page.d.ts +58 -0
- package/dist/core/web-page/runtime.d.ts +13 -0
- package/dist/core/web-page/start.d.ts +1 -0
- package/dist/core/yaml-mini.d.ts +54 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1448 -0
- package/dist/index.js.map +17 -0
- package/dist/marked/index.d.ts +31 -0
- package/dist/marked/index.js +110 -0
- package/dist/marked/index.js.map +10 -0
- package/dist/react/index.d.ts +55 -0
- package/dist/react/index.js +352 -0
- package/dist/react/index.js.map +1 -0
- package/dist/web-page.d.ts +2 -0
- package/dist/web-page.js +130 -0
- package/llms-full.txt +2585 -0
- package/llms.txt +35 -0
- package/package.json +118 -4
- package/skills/orbit/SKILL.md +140 -0
- package/skills/orbit/references/editing.md +194 -0
- package/skills/orbit/references/extending.md +247 -0
- package/skills/orbit/references/format.md +303 -0
- package/skills/orbit/references/guide.md +522 -0
- package/skills/orbit/references/languages.md +159 -0
- package/skills/orbit/references/map-tools.md +188 -0
- package/skills/orbit/references/media.md +160 -0
- package/skills/orbit/references/recipes.md +227 -0
- package/skills/orbit/references/theming.md +184 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Orbit
|
|
2
|
+
|
|
3
|
+
> Mind maps written in markdown and explored like a solar system. The node in focus is the sun, its children orbit it as planets and their children are moons; readers click a planet to fly into it, zoom, and flick the orbit to spin it. Framework-agnostic and dependency-free, with a web component, React and marked integrations, SVG export without a browser, and a command line.
|
|
4
|
+
|
|
5
|
+
- Install with `npm install @restless-dreams/orbit`. The package is ESM-only. React (`@restless-dreams/orbit/react`) and marked (`@restless-dreams/orbit/marked`) are optional peers.
|
|
6
|
+
- A map fills its container: always give the container a height.
|
|
7
|
+
- Write maps in orbit-md, Orbit's markdown (every heading and list item is a node, text under it is its note, `<!-- orbit key: value -->` comments set attributes), or in YAML (`nodes:` with `text` and `children`).
|
|
8
|
+
- Check a map with `npx @restless-dreams/orbit validate --json map.md`: every problem comes with a suggested fix, and its line where it has one. `npx @restless-dreams/orbit example` prints a map with the main features, and `orbit convert --to yaml map.md` (or `--to md`) turns one flavor into the other.
|
|
9
|
+
- Draw one with `createOrbitMap(element, markdown, options)`, the `<orbit-map>` element, `<OrbitMindMap source={markdown} />` from `@restless-dreams/orbit/react`, or `renderToSVGString(markdown, options)` without a browser.
|
|
10
|
+
- Share a map as one web page, which opens in any browser, offline: `npx @restless-dreams/orbit export --html map.md --out map.html`.
|
|
11
|
+
- Let people build the map on the map with `editable: true` (a + in the orbit, text typed inside nodes), and save what they make with `map.toMarkdown()` on the `edit` event.
|
|
12
|
+
- Let a language model read and change a map with `createMapTools(map)`: `tools.for('anthropic' | 'openai' | 'openai-chat' | 'gemini')` lists the tools for its API, and `tools.run(name, input).text` is each call's result.
|
|
13
|
+
- Maps speak the page's language (`lang`): English and Spanish are built in, `registerLanguage` adds more.
|
|
14
|
+
- Themes: `paper` (default), `night`, `glass`, `glass-night`, `cosmos`, `blueprint`, `ink`, `contrast`, `flat`, `flat-night`, `material`, `material-night`, `liquid`, `liquid-night`, `auto`. Pass `theme: 'night'` or `theme: { preset: 'night', tokens: { … } }`, or set `--orbit-*` CSS custom properties.
|
|
15
|
+
|
|
16
|
+
## Docs
|
|
17
|
+
|
|
18
|
+
- [Map format](https://github.com/restless-dreams/orbit/blob/main/docs/format.md): headings, lists, notes, media, every attribute and document option, the YAML flavor, and every diagnostic code
|
|
19
|
+
- [README](https://github.com/restless-dreams/orbit/blob/main/README.md): install and a quick start for each way to draw a map
|
|
20
|
+
- [Guide](https://github.com/restless-dreams/orbit/blob/main/docs/guide.md): every renderer option, event and method, the React component, the element, marked, pictures and videos, SVG export and the command line
|
|
21
|
+
- [Recipes](https://github.com/restless-dreams/orbit/blob/main/docs/recipes.md): plain HTML, React and Next.js, Vue, Svelte, Astro, remark and rehype, static sites
|
|
22
|
+
- [Theming](https://github.com/restless-dreams/orbit/blob/main/docs/theming.md): the fourteen themes, Material themes from any color, and every CSS custom property
|
|
23
|
+
- [Extending](https://github.com/restless-dreams/orbit/blob/main/docs/extending.md): node types, your own actions, status words and named themes, for one map or every map
|
|
24
|
+
- [Editing](https://github.com/restless-dreams/orbit/blob/main/docs/editing.md): context menus on a long press, adding, renaming, moving and removing nodes on the map, and saving the result as orbit-md
|
|
25
|
+
- [Map tools for AI](https://github.com/restless-dreams/orbit/blob/main/docs/map-tools.md): tools for any model to read and change a map, on screen or on a server, for each provider's API and MCP
|
|
26
|
+
- [Languages](https://github.com/restless-dreams/orbit/blob/main/docs/languages.md): maps in the page's language, English and Spanish built in, adding languages and changing single words
|
|
27
|
+
- [Pictures and videos](https://github.com/restless-dreams/orbit/blob/main/docs/media.md): the media viewer, its keys, YouTube and Vimeo, sound and autoplay, and opening, playing and following media from code
|
|
28
|
+
- [Coding agents](https://github.com/restless-dreams/orbit/blob/main/docs/agents.md): the skill, validation as JSON, and messages for setup mistakes
|
|
29
|
+
- [Everything in one file](https://cdn.jsdelivr.net/npm/@restless-dreams/orbit/llms-full.txt): the README and all of the guides above, for the latest release
|
|
30
|
+
- [Website](https://orbit.hyuchia.com): live examples, the themes side by side, setup for each integration, and a playground to write a map and explore it in the browser
|
|
31
|
+
|
|
32
|
+
## Optional
|
|
33
|
+
|
|
34
|
+
- [Accessibility](https://github.com/restless-dreams/orbit/blob/main/docs/accessibility.md): keyboard, screen readers, reduced motion and contrast
|
|
35
|
+
- [API reference](https://github.com/restless-dreams/orbit/blob/main/docs/api/README.md): every export, generated from the types
|
package/package.json
CHANGED
|
@@ -1,6 +1,120 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@restless-dreams/orbit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Markdown-first orbit/solar-system mind map renderer.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"markdown",
|
|
7
|
+
"mind-map",
|
|
8
|
+
"mindmap",
|
|
9
|
+
"svg",
|
|
10
|
+
"react",
|
|
11
|
+
"marked",
|
|
12
|
+
"visualization"
|
|
13
|
+
],
|
|
14
|
+
"author": "Diana Islas Ocampo",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/restless-dreams/orbit.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://orbit.hyuchia.com",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/restless-dreams/orbit/issues"
|
|
23
|
+
},
|
|
24
|
+
"packageManager": "bun@1.4.2",
|
|
25
|
+
"bin": {
|
|
26
|
+
"orbit": "./dist/bin/orbit.js"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"skills",
|
|
33
|
+
"llms.txt",
|
|
34
|
+
"llms-full.txt",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./react": {
|
|
46
|
+
"types": "./dist/react/index.d.ts",
|
|
47
|
+
"default": "./dist/react/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./marked": {
|
|
50
|
+
"types": "./dist/marked/index.d.ts",
|
|
51
|
+
"default": "./dist/marked/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./web-page.js": {
|
|
54
|
+
"types": "./dist/web-page.d.ts",
|
|
55
|
+
"default": "./dist/web-page.js"
|
|
56
|
+
},
|
|
57
|
+
"./package.json": "./package.json"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build:js": "bun run scripts/build.ts",
|
|
61
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
62
|
+
"build": "bun run build:js && bun run build:types",
|
|
63
|
+
"test": "bun test",
|
|
64
|
+
"lint": "eslint .",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"verify": "bun run lint && bun run typecheck && bun run test && bun run build && bun run check:dist && bun run lint:package",
|
|
67
|
+
"check:dist": "bun run scripts/check-dist.ts",
|
|
68
|
+
"clean": "rm -rf dist",
|
|
69
|
+
"pack:lib": "bun run build && bun pm pack",
|
|
70
|
+
"prepack": "bun run build",
|
|
71
|
+
"lint:package": "publint --strict && attw --pack . --profile esm-only",
|
|
72
|
+
"test:browser": "bun run build && docker run --rm --ipc=host -e CI -v \"$PWD\":/work -w /work mcr.microsoft.com/playwright:v1.63.0-noble node node_modules/@playwright/test/cli.js test",
|
|
73
|
+
"bench": "bun run build && docker run --rm --ipc=host -v \"$PWD\":/work -w /work mcr.microsoft.com/playwright:v1.63.0-noble node scripts/bench-animations.mjs",
|
|
74
|
+
"docs:api": "typedoc",
|
|
75
|
+
"docs:llms": "bun run scripts/build-llms.ts"
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=20.19"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
85
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
86
|
+
"marked": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
|
87
|
+
},
|
|
88
|
+
"peerDependenciesMeta": {
|
|
89
|
+
"react": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"react-dom": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"marked": {
|
|
96
|
+
"optional": true
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"devDependencies": {
|
|
100
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
101
|
+
"@eslint/js": "^9.0.0",
|
|
102
|
+
"@happy-dom/global-registrator": "^20.9.0",
|
|
103
|
+
"@playwright/test": "1.63.0",
|
|
104
|
+
"@types/react": "^19.2.5",
|
|
105
|
+
"@types/react-dom": "^19.2.3",
|
|
106
|
+
"bun-types": "^1.3.13",
|
|
107
|
+
"eslint": "^9.0.0",
|
|
108
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
109
|
+
"globals": "^14.0.0",
|
|
110
|
+
"marked": "^18.0.3",
|
|
111
|
+
"publint": "^0.3.25",
|
|
112
|
+
"react": "^19.2.0",
|
|
113
|
+
"react-dom": "^19.2.0",
|
|
114
|
+
"sass": "^1",
|
|
115
|
+
"typedoc": "^0.28",
|
|
116
|
+
"typedoc-plugin-markdown": "^4",
|
|
117
|
+
"typescript": "~5.9.3",
|
|
118
|
+
"typescript-eslint": "^8.0.0"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orbit
|
|
3
|
+
description: Write, check and embed Orbit mind maps (@restless-dreams/orbit), written in markdown (orbit-md) or YAML and drawn as interactive solar systems you fly through, or as SVG pictures. Use when asked to make, draw or visualize a mind map, or to turn an outline, roadmap or other hierarchy into an interactive map or a picture of one, or when code uses @restless-dreams/orbit, createOrbitMap, the orbit-map element, OrbitMindMap, renderToSVGString or the orbit command. Not for flowcharts, sequence or architecture diagrams, or charts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Orbit
|
|
7
|
+
|
|
8
|
+
Orbit draws a tree as a solar system: the node in focus is the sun, its children orbit it as planets, and their children are moons. Readers click a planet to fly into it, zoom, and flick the orbit to spin it. Maps are written in orbit-md, its markdown (headings and lists), or in YAML.
|
|
9
|
+
|
|
10
|
+
## Write a map
|
|
11
|
+
|
|
12
|
+
```markdown
|
|
13
|
+
---
|
|
14
|
+
title: Product roadmap
|
|
15
|
+
theme: night
|
|
16
|
+
---
|
|
17
|
+
# 🚀 Product roadmap
|
|
18
|
+
What ships this year.
|
|
19
|
+
|
|
20
|
+
## Research <!-- orbit status: done -->
|
|
21
|
+
Six weeks of interviews before we commit to scope.
|
|
22
|
+
- [x] User interviews
|
|
23
|
+
- Competitor teardown #market
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
<!-- orbit
|
|
27
|
+
status: active
|
|
28
|
+
color: "#C6B4F2"
|
|
29
|
+
-->
|
|
30
|
+
### Frontend
|
|
31
|
+
- [/] App shell
|
|
32
|
+
- Offline sync <!-- orbit status: blocked -->
|
|
33
|
+
Waiting on the [storage review](#storage-review).
|
|
34
|
+
### Storage review
|
|
35
|
+
|
|
36
|
+
## [Launch](https://example.com/launch-plan)
|
|
37
|
+

|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- Every heading and every list item is a node; everything else belongs to the node above it. One top heading is the root (several share one named after `title`). Every list under a heading is its children; indent list items to nest them.
|
|
41
|
+
- Text under a heading, or indented under a list item, is the node's note (markdown, shown in the detail sheet). Quote a bullet list with `>` to keep it in the note instead of making children.
|
|
42
|
+
- An emoji at the start of a title is the node's icon; a link in the title sets its link (`#id` flies to that node); `[ ]` / `[x]` / `[/]` set status `todo` / `done` / `active`; hashtags at the end are tags. `**bold**`, `*italics*` and `` `code` `` show as plain text; a backslash keeps a character literal.
|
|
43
|
+
- A line with only `` is the node's image. A line with only a markdown link to a YouTube or Vimeo video, or to an `.mp4`, `.webm` or `.mov` file, is its video: `[Trailer](https://youtu.be/…)` (a bare address stays text in the note). Under a list item, indent them.
|
|
44
|
+
- Attributes go in `<!-- orbit key: value -->` (at the end of a line; separate several with commas) or a block `<!-- orbit … -->` (YAML, after a heading or indented under a list item): `color`, `icon`, `status`, `priority` (a number), `tags` (a list, or words separated by commas), `collapsed`, `type`, `link`, `note`, `noteFormat`, `image`, `video`, `embed`, `caption`, `id`. Other keys are kept as your own attributes. Other HTML comments are hidden, as in markdown: `<!-- status: done -->` without `orbit` sets nothing, and `validate` says so.
|
|
45
|
+
- Status words with a colored dot: `active`, `done`, `blocked`, `todo` (and synonyms such as `in progress`, `shipped`, `at risk`, `planned`).
|
|
46
|
+
- Frontmatter: `title` (the map's accessible name too), `theme`, `view` (`focus` or `all`), `palette`, `maxDepth`, `showRings`, `interactive`, `animation`, `speed` (seconds per turn of the drift), `baseUrl`, `className`, and `height`, which sizes `<orbit-map>` and marked's placeholders only (in code and React, size the container).
|
|
47
|
+
- Themes: `paper` (default), `night`, `glass`, `glass-night`, `cosmos`, `blueprint`, `ink`, `contrast`, `flat`, `flat-night`, `material`, `material-night`, `liquid`, `liquid-night`, `auto`. In code, `materialTheme('#0B57D0')` makes a Material theme from any color.
|
|
48
|
+
- For everyone: give pictures alt text, keep titles short with the detail in notes, and don't say things with color alone (statuses have words).
|
|
49
|
+
- `npx @restless-dreams/orbit example` prints a map with the main features; `npx @restless-dreams/orbit example --yaml` prints it as YAML.
|
|
50
|
+
|
|
51
|
+
Every rule, with tables of attributes, options and diagnostics: [references/format.md](references/format.md).
|
|
52
|
+
|
|
53
|
+
## Check it
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx @restless-dreams/orbit validate --json map.md
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
It prints `valid`, the `flavor`, the `root` text, the number of `nodes`, the `depth`, and `diagnostics`, each with a `code`, a `severity`, a message that says how to fix it ("Did you mean "status"?") and its `line` where it has one. It exits with 1 while the map has a problem: fix them and validate again until `valid` is true. Some diagnostics only note something and leave the map valid: an attribute Orbit doesn't know, kept as your own (`unknown-attribute`: fix it when it's a typo), a root made up for several top-level nodes (`synthetic-root`), a caption with no media and an upgraded format. `validate` doesn't know themes registered in code and reports them as unknown; `--loose` accepts that. Add `--tree` to print the map as Orbit reads it, a node a line with its `#id`: it shows what became a node, and the ids for `#id` links and `--focus`. In code, `parseOrbit(source).diagnostics` holds the same list.
|
|
60
|
+
|
|
61
|
+
## Show it
|
|
62
|
+
|
|
63
|
+
The map fills its container: **always give the container a height**, or nothing shows (the console says so).
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// Any page: one call
|
|
67
|
+
import { createOrbitMap } from '@restless-dreams/orbit';
|
|
68
|
+
const map = createOrbitMap(document.querySelector('#map')!, markdown, { theme: 'auto' });
|
|
69
|
+
map.on('focus-change', ({ node }) => console.log(node.text));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<!-- No build step: the web component -->
|
|
74
|
+
<script type="module">
|
|
75
|
+
import { defineOrbitMapElement } from 'https://cdn.jsdelivr.net/npm/@restless-dreams/orbit/+esm';
|
|
76
|
+
defineOrbitMapElement();
|
|
77
|
+
</script>
|
|
78
|
+
<orbit-map height="420" theme="night">
|
|
79
|
+
# Launch
|
|
80
|
+
## Research
|
|
81
|
+
## Build
|
|
82
|
+
</orbit-map>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// React and Next.js ("use client" is built in; it renders a static SVG on the server)
|
|
87
|
+
import { OrbitMindMap } from '@restless-dreams/orbit/react';
|
|
88
|
+
<div style={{ height: 480 }}><OrbitMindMap source={markdown} theme="cosmos" /></div>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// No browser: static SVG for files, emails, server rendering
|
|
93
|
+
import { renderToSVGString } from '@restless-dreams/orbit';
|
|
94
|
+
const svg = renderToSVGString(markdown, { width: 1200, height: 800, theme: 'paper', viewMode: 'all' });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **Afterwards:** `map.render(source)` redraws and keeps the reader's place, `map.setOptions({ … })` changes options, `map.flyTo(id)` and `map.overview()` move, and `map.destroy()` cleans up. Events come through `map.on(name, listener)`, or as `orbit-<name>` DOM events that bubble out of `<orbit-map>` (details in `event.detail`).
|
|
98
|
+
- **Your own controls over the map:** `insets: { bottom: 72 }` fits each view into what a toolbar floating over the map leaves.
|
|
99
|
+
- **From the command line:** `npx @restless-dreams/orbit render map.md --view all --out map.svg --theme night` draws the whole map; without `--view all` it draws it as it opens, where only the root and its children have labels. `orbit convert --to yaml map.md` (or `--to md`) turns one flavor into the other.
|
|
100
|
+
- **To share a map for review** (with someone, or to the user's own phone): `npx @restless-dreams/orbit export --html map.md --out map.html` makes one web page with Orbit, the map and its pictures inside, which opens in any browser, offline, to explore or present.
|
|
101
|
+
- **marked:** `new Marked(orbitMarked())` (from `@restless-dreams/orbit/marked`) turns ```` ```orbit ```` fences into placeholders, and `hydrateOrbitBlocks(container)` makes them maps. Fences take display options: ```` ```orbit height=480 view=all ````.
|
|
102
|
+
|
|
103
|
+
Every option, event and method: [references/guide.md](references/guide.md). Vue, Svelte, Astro and markdown pipelines: [references/recipes.md](references/recipes.md).
|
|
104
|
+
|
|
105
|
+
## Style it
|
|
106
|
+
|
|
107
|
+
`theme` takes a name or `{ preset, tokens }`, where tokens are the CSS custom properties without `--orbit-`: `{ preset: 'night', tokens: { surface: '#0b1020', 'ring-dash': 'none' } }`. In CSS, set `--orbit-*` properties on the container; they win over the theme. All properties: [references/theming.md](references/theming.md).
|
|
108
|
+
|
|
109
|
+
## Pictures, videos and languages
|
|
110
|
+
|
|
111
|
+
- Pictures and videos open in a viewer over the whole window, to zoom into and to play, stepping through the map's media. `mediaViewer: false` shows them in the detail sheet instead, an `onAction` that returns `false` handles them yourself, and `map.openMedia(id)` opens one from code. YouTube's videos don't play in a page opened from a file: serve it. All of it: [references/media.md](references/media.md).
|
|
112
|
+
- Maps speak the language of the closest `lang` around them: English and Spanish are built in, and other languages fall back to English. `language: 'es'` picks one, `strings: { … }` changes single words, `registerLanguage(tag, strings)` adds a language, and `orbit render --language es` draws in one. Every word: [references/languages.md](references/languages.md).
|
|
113
|
+
|
|
114
|
+
## Extend it
|
|
115
|
+
|
|
116
|
+
- **Node types:** a node picks one with `<!-- orbit type: milestone -->`. The `nodeTypes` option, or `registerNodeType(name, …)` for every map, sets the type's `label`, default `icon` and `color`, theme `tokens`, `render(node, element)` to draw inside the node, `details(node, element)` for the detail sheet, and its own `actions`.
|
|
117
|
+
- **Actions:** `actions: [{ id, label, icon, when, run }]` (or `registerAction`) adds buttons under the focused node and in its detail sheet. They fire the `action` event with `action` set to the id.
|
|
118
|
+
- **Status words and themes:** `statuses: { review: 'active', 'on hold': '#A855F7' }` (or `registerStatus`) colors status words. `registerTheme('brand', { preset, tokens })` makes `theme: brand` work in options, frontmatter and `<orbit-map theme>`.
|
|
119
|
+
|
|
120
|
+
Every option, with examples: [references/extending.md](references/extending.md).
|
|
121
|
+
|
|
122
|
+
## Edit it on the map
|
|
123
|
+
|
|
124
|
+
- `editable: true` (or `<orbit-map editable>`): a + in the orbit adds ideas, typed inside their new planet, one after another; a long press, a right click or F2 edits a node's text in place, and a node held still lifts and can be dragged onto another node or along the orbit. Save on `edit` (its `kind` is `add`, `update`, `remove` or `move`) with `map.toMarkdown()`, or `serializeOrbitYAML(map.getDocument())` for a YAML map.
|
|
125
|
+
- `editingKeys: true` as well (`<orbit-map editable editing-keys>`): Tab adds an idea under the node with keyboard focus, Shift+Enter one beside it, Delete removes it; while typing, Tab and Shift+Tab go a level down and up.
|
|
126
|
+
- `onNodeContext: ({ node, x, y }) => …` (or `map.on('node-context', …)`) fires for a long press, a right click or the context menu key on a node: show your own menu at `x`, `y` (CSS pixels from the map's top left corner), and call `map.editNode(id)` or `map.editNewNode(parentId)` from it.
|
|
127
|
+
- `map.addNode(parentId, { text, … }, index?)`, `map.updateNode(id, changes)` (`undefined` removes an attribute), `map.removeNode(id)` and `map.moveNode(id, parentId, index?)` change the map with an animation, fire `edit` (`onEdit`), and return the node, or `null` when there's nothing to do.
|
|
128
|
+
- Ids follow the text: read a node's current id from what the method returns.
|
|
129
|
+
- For an AI assistant in an app, `createMapTools(map)` gives a language model tools to read the map and change it: `tools.for('anthropic' | 'openai' | 'openai-chat' | 'gemini')` for its API (`tools.definitions` for MCP), `tools.instructions` for its system prompt, and `tools.run(call.name, call.input).text` as each call's result. Don't write tools of your own for this: these check every call, animate each change, and say what to fix when a call is wrong.
|
|
130
|
+
|
|
131
|
+
Ids, focus, saving, React and `<orbit-map>`: [references/editing.md](references/editing.md). The map tools, with the loop for each provider: [references/map-tools.md](references/map-tools.md).
|
|
132
|
+
|
|
133
|
+
## Mistakes to avoid
|
|
134
|
+
|
|
135
|
+
- A container without a height: the map is 0 px tall.
|
|
136
|
+
- `view: overview`: the overview is `view: all`. In code it's `viewMode: 'all'`, and `view-mode="all"` on `<orbit-map>`; the frontmatter's `animation`, `speed` and `title` are `orbitAnimation`, `orbitSpeed` and `ariaLabel` in code.
|
|
137
|
+
- A bullet list meant as part of a note, written without `>`: it becomes child nodes.
|
|
138
|
+
- A `#id` link to a node that isn't in the map: `validate` reports it and suggests the id you meant.
|
|
139
|
+
- YAML nodes use `text` and `children` (not `title`, `name` or `items`, which `validate` reports); `nodes:` holds the root (several share one named after `title`).
|
|
140
|
+
- Inventing options: every option and prop is typed in the package's declarations (`RendererOptions`). TypeScript rejects one that doesn't exist; plain JavaScript ignores it without a word. `validate` checks the map file only.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<!-- A copy of docs/editing.md, made by scripts/build-llms.ts, with links to other files pointing at the repository. Edit the original. -->
|
|
2
|
+
|
|
3
|
+
# Editing on the map
|
|
4
|
+
|
|
5
|
+
People can build a map on the map itself. A **+** in the orbit adds an idea, typed right inside its new planet, a node's text is edited where it is, and a node is dragged to wherever it should go. Each edit animates into place, and the map comes back as orbit-md to save.
|
|
6
|
+
|
|
7
|
+
## Turn it on
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { createOrbitMap } from '@restless-dreams/orbit';
|
|
11
|
+
|
|
12
|
+
const map = createOrbitMap(element, markdown, {
|
|
13
|
+
editable: true,
|
|
14
|
+
onEdit: () => save(map.toMarkdown()),
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With `editable`:
|
|
19
|
+
|
|
20
|
+
- **A + in the orbit.** In focus mode, the orbit keeps a free place after the planets, drawn as a dashed planet with a plus.
|
|
21
|
+
- Press it and type: the text goes inside the new planet.
|
|
22
|
+
- Enter adds the idea right where it was typed and opens the next one after it, so ideas go in one after another, the keyboard staying up.
|
|
23
|
+
- Escape, or Enter with nothing typed, stops.
|
|
24
|
+
- **Text edited where it is.** A long press, a right click or F2 on a node turns its label into a field.
|
|
25
|
+
- Enter saves, and Escape leaves the text as it was. Tapping anywhere else saves.
|
|
26
|
+
- A moon flies into its planet's orbit first, so its text has room. In the overview, small nodes get a field floating over them, at a size you can read.
|
|
27
|
+
- **Nodes dragged somewhere else** (see [Dragging nodes](#dragging-nodes)).
|
|
28
|
+
|
|
29
|
+
`<orbit-map editable>` and `<OrbitMindMap editable>` turn it on too.
|
|
30
|
+
|
|
31
|
+
## Your own menu
|
|
32
|
+
|
|
33
|
+
To offer more than editing text, show your own menu when a node is long-pressed, right-clicked, or chosen with the context menu key:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const map = createOrbitMap(element, markdown, {
|
|
37
|
+
editable: true,
|
|
38
|
+
onNodeContext: ({ node, x, y }) => showMenu(x, y, [
|
|
39
|
+
{ label: 'Rename', run: () => map.editNode(node.id) },
|
|
40
|
+
{ label: 'Add an idea under it', run: () => map.editNewNode(node.id) },
|
|
41
|
+
{ label: 'Mark done', run: () => map.updateNode(node.id, { status: 'done' }) },
|
|
42
|
+
{ label: 'Delete', run: () => map.removeNode(node.id) },
|
|
43
|
+
]),
|
|
44
|
+
onEdit: () => save(map.toMarkdown()),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- **What `node-context` reports:** a long press (half a second, held still; `longPressDelay` changes it) with a finger or a pen, a right click, or the context menu key or Shift+F10 on the node with keyboard focus. `x` and `y` are where to put your menu, in CSS pixels from the map's top left corner: the press, or the node's center for the keyboard.
|
|
49
|
+
- **The press is yours** with an `onNodeContext` callback or a `node-context` listener (`map.on('node-context', …)`). It doesn't open the node, it doesn't start editing the text, and the browser's own menu stays closed. A listener for the `orbit-node-context` DOM event takes it by calling `event.preventDefault()`.
|
|
50
|
+
- **Without any of them,** a long press edits the text on an editable map, once it's let go without moving: moving it drags the node instead. On other maps, it is an ordinary tap and a right click opens the browser's menu.
|
|
51
|
+
- **The same long press can drag the node.** Your menu opens as the press is held; if the finger then moves, `node-drag` fires with `phase: 'start'`, and that's the moment to close the menu: the node is on its way somewhere else.
|
|
52
|
+
|
|
53
|
+
## Dragging nodes
|
|
54
|
+
|
|
55
|
+
On an editable map, a node held still lifts: a long press with a finger or a pen, or the mouse held down half a second. Move it, and it follows the press, its moons with it.
|
|
56
|
+
|
|
57
|
+
- **Onto another node:** that node grows and takes a ring. Let go, and the dragged node goes into it, after its last child.
|
|
58
|
+
- **Along the orbit:** a planet keeps its place held, an empty ring where it was, so nothing slides under the finger. Carried along the ring, the place moves as it passes each planet, and the others make room. A planet catches a drop only near its middle; over its rim, the press is on the orbit, placing the node beside it. A moon or a deeper node carried to the orbit takes the + as its place.
|
|
59
|
+
- **Let go anywhere else,** and it lands in the place held for it. One that never left its place goes back down. Escape, or a second finger, puts it back.
|
|
60
|
+
- **What can't happen:** a node can't go inside itself or anything under it, the sun and the root don't lift, and a map that isn't editable never lifts a node.
|
|
61
|
+
|
|
62
|
+
Each drop is a `moveNode`: it fires `edit` with `kind: 'move'`, the node flies from where it was let go to its place, and Orbit announces it. A mouse that's let go without moving still clicks, so a slow click opens the node as usual.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const map = createOrbitMap(element, markdown, {
|
|
66
|
+
editable: true,
|
|
67
|
+
onNodeContext: ({ node, x, y }) => showMenu(x, y, node),
|
|
68
|
+
onNodeDrag: ({ phase }) => {
|
|
69
|
+
if (phase === 'start') {
|
|
70
|
+
closeMenu();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onEdit: () => save(map.toMarkdown()),
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`node-drag` reports each step: `start` as the node leaves its place (with where it was), `over` whenever where it would go changes (`parent` and `index`, or `null` over no place, for haptics or a hint), and `end` when it's let go (where it went, after the `edit`; `null` when it went back).
|
|
78
|
+
|
|
79
|
+
## Keys for editing
|
|
80
|
+
|
|
81
|
+
With `editingKeys` as well as `editable`, the keyboard builds a map the way mind-map apps do:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const map = createOrbitMap(element, markdown, { editable: true, editingKeys: true });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Key | On a node with keyboard focus | While typing |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| Tab | Adds an idea under it (Insert does too) | Keeps what was typed and goes on under it. With nothing typed, moves the field under the idea before. |
|
|
90
|
+
| Shift+Tab | Leaves the map, as usual | Keeps what was typed and goes on a level up, beside the parent |
|
|
91
|
+
| Shift+Enter | Adds an idea beside it, right after it | |
|
|
92
|
+
| Enter | Flies into it, as without the keys | Keeps what was typed and goes on beside it |
|
|
93
|
+
| Delete | Removes it, with everything under it | |
|
|
94
|
+
|
|
95
|
+
- **A whole map without the mouse:** Tab on the sun and type. Enter brings the next idea, Tab goes under the one just typed, Shift+Tab comes back up, and Escape stops.
|
|
96
|
+
- **Tab stays in the map.** Without the keys the map is one tab stop, and Tab leaves it. With them, Tab on a node adds an idea, and Shift+Tab leaves. Turn them on where the map is the page's main tool, such as an editor, rather than for a map among other controls.
|
|
97
|
+
- **After Delete,** keyboard focus moves on to the node after the removed one, or the one before it, or its parent. The root stays. To offer undo, keep the markdown from before each edit (see [Saving](#saving)).
|
|
98
|
+
|
|
99
|
+
## The editing methods
|
|
100
|
+
|
|
101
|
+
| Method | What it does |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `editNode(id)` | Opens the field over a node, to type its text where it is. |
|
|
104
|
+
| `editNewNode(parentId?, index?)` | Opens the field in the orbit of `parentId` (default: the node in focus), at `index` among its children (default: after the last). It flies there first. |
|
|
105
|
+
| `stopEditing(save?)` | Closes the field, keeping what was typed unless `save` is false. |
|
|
106
|
+
| `addNode(parentId, { text, …, children }, index?)` | Adds a node under `parentId`, at `index` among its children (default: last), with the nodes `children` lists under it, as many levels down as you like. Returns the new node. |
|
|
107
|
+
| `updateNode(id, changes)` | Changes text and attributes: `{ text: 'Budget' }`, `{ status: 'done', color: '#E8A33D' }`. `undefined` removes one: `{ color: undefined }`. Returns the node. |
|
|
108
|
+
| `removeNode(id)` | Removes a node and everything under it. The root stays. Returns the removed node. |
|
|
109
|
+
| `moveNode(id, parentId, index?)` | Moves a node, with everything under it, under `parentId` at `index`. Under the same parent, it changes the order. Returns the node. |
|
|
110
|
+
| `getDocument()` | The map as a document: its tree, and the options its frontmatter set. |
|
|
111
|
+
| `toMarkdown()` | The map as orbit-md, frontmatter included. |
|
|
112
|
+
|
|
113
|
+
To let a language model make these changes, see [Map tools for AI](map-tools.md).
|
|
114
|
+
|
|
115
|
+
`editNode` and `editNewNode` return false, and the others `null`, when there's nothing to do: an id that isn't in the map, removing or moving the root, or moving a node inside itself. They work on any interactive map. `editable` adds the ways people edit by themselves: the +, editing on a long press, a right click, the context menu key or F2, and dragging.
|
|
116
|
+
|
|
117
|
+
Besides `text`, `addNode` and `updateNode` take the node's attributes: `icon`, `color`, `status`, `priority`, `tags`, `collapsed`, `type`, `link`, `note`, `noteFormat`, `image`, `video`, `embed`, and `attrs` for your own.
|
|
118
|
+
|
|
119
|
+
- **Ids come from the text,** as when a map is read from markdown: "Night markets" is `night-markets`, and a second "Ideas" is `ideas-2`. New text gives a node a new id, so the saved file needs no `id` attribute. A node keeps its id when a `#id` link points at it, or when the id was set by hand. Read a node's current id from what the method returns.
|
|
120
|
+
- **Titles are one line:** line breaks become spaces, typed or pasted.
|
|
121
|
+
- **The reader keeps their place.** When the node in focus, or a node above it, is removed, the nearest node left takes the focus and `focus-change` fires. A node in focus that gets a new id stays in focus.
|
|
122
|
+
- **Edits change the tree in place:** `getDocument().root`, which is the tree you passed to `render()` if you passed one.
|
|
123
|
+
|
|
124
|
+
## Events
|
|
125
|
+
|
|
126
|
+
| Event | DOM event | Option | Detail |
|
|
127
|
+
| --- | --- | --- | --- |
|
|
128
|
+
| `edit` | `orbit-edit` | `onEdit` | `{ kind, node, tree }`: `kind` is `add`, `update`, `remove` or `move` |
|
|
129
|
+
| `editor-open` | `orbit-editor-open` | `onEditorOpen` | `{ node, parent }`: the node whose text is typed, or `node: null` and the `parent` of a new idea |
|
|
130
|
+
| `editor-close` | `orbit-editor-close` | `onEditorClose` | the same |
|
|
131
|
+
| `node-context` | `orbit-node-context` | `onNodeContext` | `{ node, x, y }` |
|
|
132
|
+
| `node-drag` | `orbit-node-drag` | `onNodeDrag` | `{ phase, node, parent, index }`: `phase` is `start`, `over` or `end` |
|
|
133
|
+
|
|
134
|
+
The field's events help a page around the map: hide a button that would cover the keyboard, say, or bring a phone's keyboard up when an app opens the field itself.
|
|
135
|
+
|
|
136
|
+
## Saving
|
|
137
|
+
|
|
138
|
+
Save the map on every `edit`:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
map.on('edit', () => localStorage.setItem('map', map.toMarkdown()));
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- **`toMarkdown()` reads back as the same map,** ids included, and keeps the file as it was written: only the ideas an edit changed are written again, so a note written by hand or by another app keeps its formatting and everything Orbit doesn't read (see [What saving keeps](format.md#what-saving-keeps)). New ideas are written the way people write maps: headings for the root and its children, lists below them, checkboxes for statuses, hashtags for tags. For YAML, use `serializeOrbitYAML(map.getDocument())`.
|
|
145
|
+
- **A map written for a newer version of the format** has an `unsupported-schema-version` diagnostic: open it without `editable`, so nothing is changed that this version doesn't understand.
|
|
146
|
+
- **Drawing the saved text again is fine.** When `render()` gets the map as it already is, Orbit keeps the view, and an edit's animation carries on.
|
|
147
|
+
- **Undo is yours to keep:** save the markdown before an edit, and `render()` it to go back. Orbit animates to the restored map and keeps the reader's place.
|
|
148
|
+
|
|
149
|
+
## In React
|
|
150
|
+
|
|
151
|
+
`<OrbitMindMap>` takes `editable`, `onEdit`, `onNodeContext`, `onNodeDrag`, `onEditorOpen` and `onEditorClose`, and the ref's `renderer` has the methods:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { useRef, useState } from 'react';
|
|
155
|
+
import { OrbitMindMap, type OrbitMindMapRef } from '@restless-dreams/orbit/react';
|
|
156
|
+
|
|
157
|
+
export const Brainstorm = ({ initial }: { initial: string }) => {
|
|
158
|
+
const map = useRef<OrbitMindMapRef>(null);
|
|
159
|
+
const [source, setSource] = useState(initial);
|
|
160
|
+
return (
|
|
161
|
+
<div style={{ height: 480 }}>
|
|
162
|
+
<OrbitMindMap
|
|
163
|
+
ref={map}
|
|
164
|
+
source={source}
|
|
165
|
+
editable
|
|
166
|
+
onEdit={() => setSource(map.current?.renderer?.toMarkdown() ?? source)}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Setting `source` to what the map just wrote doesn't draw it again.
|
|
174
|
+
|
|
175
|
+
## In `<orbit-map>`
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<orbit-map editable>
|
|
179
|
+
# Weekend in Lisbon
|
|
180
|
+
## Food
|
|
181
|
+
## Places
|
|
182
|
+
</orbit-map>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The element's `renderer` has the same methods, and its events bubble out of it: listen for `orbit-edit` and save `element.renderer.toMarkdown()`. Edits change the map, not the element's text. `<orbit-map editable editing-keys>` turns on the [keys for editing](#keys-for-editing) too.
|
|
186
|
+
|
|
187
|
+
## Keyboard and screen readers
|
|
188
|
+
|
|
189
|
+
- **F2** edits the text of the node with keyboard focus. With `editingKeys`, Tab, Shift+Enter and Delete add and remove ideas too (see [Keys for editing](#keys-for-editing)).
|
|
190
|
+
- **The +** takes its turn among the planets with `←` and `→`; Enter or Space on it adds an idea.
|
|
191
|
+
- **The context menu key** and Shift+F10 open your menu for the node with keyboard focus, so whatever a long press offers works from the keyboard too.
|
|
192
|
+
- **The field** is named after what it's for ("Text of Food", "New idea for Trip"), and when it closes with Enter or Escape, keyboard focus returns to the node, or to the +.
|
|
193
|
+
- **Announcements:** Orbit announces added, removed and moved nodes in its live region ("Added Night markets").
|
|
194
|
+
- **Moving without dragging:** dragging needs a pointer, so offer a way to move nodes in your menu too, with `moveNode` (earlier, later, in, out), as the keyboard can reach it.
|