@restless-dreams/orbit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +168 -2
- package/dist/bin/orbit.js +11666 -0
- package/dist/bin/orbit.js.map +60 -0
- package/dist/chunk-fkdfgtx9.js +17295 -0
- package/dist/chunk-fkdfgtx9.js.map +75 -0
- package/dist/chunk-y748hzcr.js +82 -0
- package/dist/chunk-y748hzcr.js.map +10 -0
- package/dist/chunk-zze4tc9n.js +408 -0
- package/dist/chunk-zze4tc9n.js.map +10 -0
- package/dist/core/auto-render.d.ts +85 -0
- package/dist/core/branch-colors.d.ts +57 -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 +66 -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 +160 -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 +361 -0
- package/dist/core/extensions.d.ts +155 -0
- package/dist/core/format-upgrades.d.ts +33 -0
- package/dist/core/index.d.ts +46 -0
- package/dist/core/input-controller.d.ts +116 -0
- package/dist/core/languages.d.ts +201 -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/other-apps/freemind.d.ts +8 -0
- package/dist/core/other-apps/icons.d.ts +16 -0
- package/dist/core/other-apps/ideas.d.ts +76 -0
- package/dist/core/other-apps/import.d.ts +28 -0
- package/dist/core/other-apps/inflate.d.ts +22 -0
- package/dist/core/other-apps/markup.d.ts +51 -0
- package/dist/core/other-apps/notes.d.ts +80 -0
- package/dist/core/other-apps/opml.d.ts +26 -0
- package/dist/core/other-apps/reading.d.ts +36 -0
- package/dist/core/other-apps/xmind.d.ts +14 -0
- package/dist/core/other-apps/zip.d.ts +37 -0
- package/dist/core/parser-markdown.d.ts +42 -0
- package/dist/core/parser-yaml.d.ts +8 -0
- package/dist/core/print-layout.d.ts +70 -0
- package/dist/core/render-to-svg.d.ts +57 -0
- package/dist/core/renderer/content-painter.d.ts +30 -0
- package/dist/core/renderer/dom.d.ts +15 -0
- package/dist/core/renderer/families.d.ts +35 -0
- package/dist/core/renderer/label-painter.d.ts +93 -0
- package/dist/core/renderer/node-painter.d.ts +113 -0
- package/dist/core/renderer/numbers.d.ts +2 -0
- package/dist/core/renderer/options.d.ts +12 -0
- package/dist/core/renderer/rings-and-links.d.ts +23 -0
- package/dist/core/renderer/scene-helpers.d.ts +33 -0
- package/dist/core/renderer/trees.d.ts +22 -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 +626 -0
- package/dist/core/text-layout.d.ts +54 -0
- package/dist/core/themes.d.ts +62 -0
- package/dist/core/types.d.ts +471 -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 +59 -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 +4357 -0
- package/dist/index.js.map +29 -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 +2785 -0
- package/llms.txt +36 -0
- package/package.json +118 -4
- package/skills/orbit/SKILL.md +140 -0
- package/skills/orbit/references/editing.md +195 -0
- package/skills/orbit/references/extending.md +247 -0
- package/skills/orbit/references/format.md +331 -0
- package/skills/orbit/references/guide.md +546 -0
- package/skills/orbit/references/import-export.md +115 -0
- package/skills/orbit/references/languages.md +160 -0
- package/skills/orbit/references/map-tools.md +188 -0
- package/skills/orbit/references/media.md +160 -0
- package/skills/orbit/references/recipes.md +236 -0
- package/skills/orbit/references/theming.md +199 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map controls: real, themable buttons in an HTML layer over the map.
|
|
3
|
+
* Wide containers show breadcrumbs; narrow ones a back chip with the
|
|
4
|
+
* parent's name (switched by a container query in the stylesheet).
|
|
5
|
+
*/
|
|
6
|
+
import type { OrbitNode, ViewMode } from '../types.js';
|
|
7
|
+
import type { OrbitWords } from '../languages.js';
|
|
8
|
+
export interface ControlsCallbacks {
|
|
9
|
+
onNavigate(nodeId: string): void;
|
|
10
|
+
onViewMode(mode: ViewMode): void;
|
|
11
|
+
onZoom(direction: 1 | -1): void;
|
|
12
|
+
onFit(): void;
|
|
13
|
+
onToggleOutline(): void;
|
|
14
|
+
}
|
|
15
|
+
export interface ControlsState {
|
|
16
|
+
viewMode: ViewMode;
|
|
17
|
+
/** From the root to the focused node. */
|
|
18
|
+
path: OrbitNode[];
|
|
19
|
+
outlineOpen: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** A rectangle on the map, in px from its top left corner. */
|
|
22
|
+
export interface ScreenBox {
|
|
23
|
+
left: number;
|
|
24
|
+
top: number;
|
|
25
|
+
right: number;
|
|
26
|
+
bottom: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class Controls {
|
|
29
|
+
readonly element: HTMLDivElement;
|
|
30
|
+
private readonly callbacks;
|
|
31
|
+
private readonly back;
|
|
32
|
+
private readonly backLabel;
|
|
33
|
+
private readonly breadcrumbs;
|
|
34
|
+
private readonly breadcrumbPanel;
|
|
35
|
+
private readonly viewGroup;
|
|
36
|
+
private readonly focusButton;
|
|
37
|
+
private readonly overviewButton;
|
|
38
|
+
private readonly outlineButton;
|
|
39
|
+
private readonly zoomPanel;
|
|
40
|
+
private readonly zoomInButton;
|
|
41
|
+
private readonly fitButton;
|
|
42
|
+
private readonly zoomOutButton;
|
|
43
|
+
private readonly hint;
|
|
44
|
+
private hintTimer;
|
|
45
|
+
private parentId;
|
|
46
|
+
private words;
|
|
47
|
+
private state;
|
|
48
|
+
constructor(callbacks: ControlsCallbacks, words: OrbitWords);
|
|
49
|
+
/** Writes every label in `words`' language. */
|
|
50
|
+
relabel(words: OrbitWords): void;
|
|
51
|
+
update(state: ControlsState): void;
|
|
52
|
+
/**
|
|
53
|
+
* Where the zoom buttons are on a map `width` by `height` px, known
|
|
54
|
+
* without laying them out, or null while the controls are hidden.
|
|
55
|
+
*/
|
|
56
|
+
zoomBox(width: number, height: number): ScreenBox | null;
|
|
57
|
+
/** Shows a short message at the bottom of the map. */
|
|
58
|
+
showHint(text: string, duration?: number): void;
|
|
59
|
+
destroy(): void;
|
|
60
|
+
private crumbs;
|
|
61
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The detail sheet: a node's media, note, status, tags and link. A native
|
|
3
|
+
* `<dialog>` opened as a modal, so it sits in the top layer, traps focus,
|
|
4
|
+
* closes with Esc and makes the page behind it inert. Phones get a bottom
|
|
5
|
+
* sheet that can be swiped down to close.
|
|
6
|
+
*/
|
|
7
|
+
import type { OrbitNode } from '../types.js';
|
|
8
|
+
import { OrbitWords } from '../languages.js';
|
|
9
|
+
export type DetailSection = 'details' | 'image' | 'video' | 'embed' | 'note';
|
|
10
|
+
/** What the map tells the sheet about the node it shows, beyond its content: its type, actions and status look. */
|
|
11
|
+
export interface SheetExtensions {
|
|
12
|
+
icon(node: OrbitNode): string | undefined;
|
|
13
|
+
typeLabel(node: OrbitNode): string | undefined;
|
|
14
|
+
/** Draws the node type's section, if it has one. */
|
|
15
|
+
details(node: OrbitNode): ((element: HTMLElement) => void) | undefined;
|
|
16
|
+
actions(node: OrbitNode): {
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
icon?: string;
|
|
20
|
+
}[];
|
|
21
|
+
runAction(node: OrbitNode, action: string, event: MouseEvent): void;
|
|
22
|
+
status(word: string): {
|
|
23
|
+
tone: string;
|
|
24
|
+
color?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface DetailRequest {
|
|
28
|
+
node: OrbitNode;
|
|
29
|
+
/** Ancestors from the root, for the path above the title. */
|
|
30
|
+
path: OrbitNode[];
|
|
31
|
+
/** Siblings to step through with the previous / next buttons and arrow keys. */
|
|
32
|
+
siblings: OrbitNode[];
|
|
33
|
+
section?: DetailSection;
|
|
34
|
+
baseUrl?: string;
|
|
35
|
+
noteRenderer?: (note: string, context: {
|
|
36
|
+
node: OrbitNode;
|
|
37
|
+
format: 'markdown' | 'plain';
|
|
38
|
+
}) => string;
|
|
39
|
+
/** Called when the reader steps to a sibling. */
|
|
40
|
+
onNavigate?: (node: OrbitNode) => void;
|
|
41
|
+
/** The node a `#id` link points at, if the map has it. */
|
|
42
|
+
resolveLink?: (href: string) => OrbitNode | null;
|
|
43
|
+
/** Called, after the sheet closes, when the reader follows a link to another node. */
|
|
44
|
+
onFollowLink?: (node: OrbitNode) => void;
|
|
45
|
+
/** The map's node types, actions and status words. */
|
|
46
|
+
extensions?: SheetExtensions;
|
|
47
|
+
/**
|
|
48
|
+
* Opens the media viewer on the node's picture or video: with it, the
|
|
49
|
+
* picture is a button and the video gets one. `startAt` is where the
|
|
50
|
+
* sheet's own player of a video file was.
|
|
51
|
+
*/
|
|
52
|
+
onViewMedia?: (node: OrbitNode, kind: 'image' | 'video', event: MouseEvent, startAt?: number) => void;
|
|
53
|
+
/** The map's words, in its language. Default: English. */
|
|
54
|
+
words?: OrbitWords;
|
|
55
|
+
}
|
|
56
|
+
export declare class DetailSheet {
|
|
57
|
+
private readonly host;
|
|
58
|
+
private dialog;
|
|
59
|
+
private request;
|
|
60
|
+
private returnFocus;
|
|
61
|
+
constructor(host: HTMLElement);
|
|
62
|
+
get isOpen(): boolean;
|
|
63
|
+
/** The words of the map that opened the sheet. */
|
|
64
|
+
private get words();
|
|
65
|
+
/** The node being shown, if the sheet is open. */
|
|
66
|
+
get node(): OrbitNode | null;
|
|
67
|
+
open(request: DetailRequest): void;
|
|
68
|
+
close(): void;
|
|
69
|
+
/** Closes the sheet, then hands the linked node to the map to fly to. */
|
|
70
|
+
private followLink;
|
|
71
|
+
destroy(): void;
|
|
72
|
+
private restoreFocus;
|
|
73
|
+
private ensureDialog;
|
|
74
|
+
private step;
|
|
75
|
+
private enableSwipeToClose;
|
|
76
|
+
private buildContent;
|
|
77
|
+
private stepButton;
|
|
78
|
+
private buildChips;
|
|
79
|
+
private buildMedia;
|
|
80
|
+
/**
|
|
81
|
+
* The button that moves a video to the media viewer. The sheet's own
|
|
82
|
+
* player stops first; a video file goes on from where it was.
|
|
83
|
+
*/
|
|
84
|
+
private expandButton;
|
|
85
|
+
private figure;
|
|
86
|
+
}
|
|
87
|
+
/** Maps a free-text status onto a theme color: active, done, blocked, todo or neutral. */
|
|
88
|
+
export declare const statusTone: (status: string) => "active" | "done" | "blocked" | "todo" | "neutral";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frames for the detail sheet and the media viewer: another site's page
|
|
3
|
+
* (an embed), and YouTube's and Vimeo's players.
|
|
4
|
+
*/
|
|
5
|
+
/** A page shown in a frame, or null when its URL isn't allowed in one. */
|
|
6
|
+
export declare const embedFrame: (url: string, baseUrl: string | undefined, title: string) => HTMLIFrameElement | null;
|
|
7
|
+
/**
|
|
8
|
+
* YouTube's or Vimeo's player in a frame, from an embed URL made by
|
|
9
|
+
* `toVideoEmbedUrl`, or null when its URL isn't allowed in one.
|
|
10
|
+
*/
|
|
11
|
+
export declare const playerFrame: (src: string, title: string) => HTMLIFrameElement | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One icon set for every control: a 24 px grid, 1.75 px strokes with round
|
|
3
|
+
* caps and joins. Each icon is a list of SVG path commands.
|
|
4
|
+
*/
|
|
5
|
+
export type IconName = 'back' | 'home' | 'chevron' | 'focus' | 'overview' | 'zoom-in' | 'zoom-out' | 'fit' | 'image' | 'video' | 'embed' | 'note' | 'link' | 'goto' | 'info' | 'close' | 'previous' | 'next' | 'list' | 'expand';
|
|
6
|
+
/** An inline, decorative icon element for HTML controls. */
|
|
7
|
+
export declare const createIcon: (name: IconName) => SVGSVGElement;
|
|
8
|
+
export declare const iconPaths: (name: IconName) => readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* The icon for one of your actions: one of these icons by name, SVG path
|
|
11
|
+
* data on the same 24-unit grid (`'M4 20h4L20 8l-4-4L4 16z'`), or an emoji
|
|
12
|
+
* or short symbol. Without one, the label's first letter.
|
|
13
|
+
*/
|
|
14
|
+
export declare const createActionIcon: (icon: string | undefined, label: string) => Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The field for typing a node's text on the map: a textarea laid over the
|
|
3
|
+
* node each frame, in the label's font, size and color. Enter saves, Escape
|
|
4
|
+
* cancels, and leaving the field saves.
|
|
5
|
+
*/
|
|
6
|
+
import type { NodeRole } from '../engine/scene.js';
|
|
7
|
+
export interface InlineEditorCallbacks {
|
|
8
|
+
/** Enter, or the field losing focus: keep `text`. */
|
|
9
|
+
onSave(text: string, how: 'enter' | 'blur'): void;
|
|
10
|
+
/** Escape. */
|
|
11
|
+
onCancel(): void;
|
|
12
|
+
/** Tab (`back` for Shift+Tab), with `text` typed. Returns whether it was used; if not, Tab moves keyboard focus as usual. */
|
|
13
|
+
onTab(text: string, back: boolean): boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Where the field goes this frame: centered on a node, in screen pixels. */
|
|
16
|
+
export interface EditorPlacement {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
/** The width text gets inside the node. */
|
|
20
|
+
width: number;
|
|
21
|
+
/** The label's font size, which the text takes on. */
|
|
22
|
+
fontSize: number;
|
|
23
|
+
/** The tallest the field grows before it scrolls. */
|
|
24
|
+
maxHeight: number;
|
|
25
|
+
role: NodeRole;
|
|
26
|
+
/** Light text, on a dark node. */
|
|
27
|
+
light: boolean;
|
|
28
|
+
/** A new idea, in the + rather than a node. */
|
|
29
|
+
slot: boolean;
|
|
30
|
+
/** Over a node too small to hold it, on a card of its own. */
|
|
31
|
+
floating: boolean;
|
|
32
|
+
/** How far below its center (px) the field may reach: the top of the node's "+N", which the field rises to keep clear of as it grows. */
|
|
33
|
+
lowest?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface EditorText {
|
|
36
|
+
/** What the field is for, for assistive technology. */
|
|
37
|
+
label: string;
|
|
38
|
+
placeholder: string;
|
|
39
|
+
/** The label of a phone keyboard's Enter key: `next` while ideas keep coming, `done` otherwise. */
|
|
40
|
+
enterKey: 'next' | 'done';
|
|
41
|
+
}
|
|
42
|
+
export declare class InlineEditor {
|
|
43
|
+
readonly element: HTMLTextAreaElement;
|
|
44
|
+
private readonly callbacks;
|
|
45
|
+
private open;
|
|
46
|
+
/** Closing blurs the field: that blur is no save. */
|
|
47
|
+
private closing;
|
|
48
|
+
/** One Enter can arrive as a key and as a line break: a second one straight after, with nothing typed between, is ignored. */
|
|
49
|
+
private savedAt;
|
|
50
|
+
private typedSinceSave;
|
|
51
|
+
/** The size and style last applied, and the position, so frames that change nothing touch no styles. */
|
|
52
|
+
private look;
|
|
53
|
+
private position;
|
|
54
|
+
/** Where the field was last placed (null while it's out of sight), to move it again when it grows. */
|
|
55
|
+
private placement;
|
|
56
|
+
/** The field's height (px, unscaled) as it last grew to fit its text. */
|
|
57
|
+
private height;
|
|
58
|
+
constructor(callbacks: InlineEditorCallbacks);
|
|
59
|
+
get isOpen(): boolean;
|
|
60
|
+
get value(): string;
|
|
61
|
+
get hasFocus(): boolean;
|
|
62
|
+
/** Shows the field holding `value`, all selected, with the cursor in it. */
|
|
63
|
+
show(value: string, text: EditorText): void;
|
|
64
|
+
/** Empties the field for the next idea. The cursor stays, and with it a phone's keyboard. */
|
|
65
|
+
clear(text: EditorText): void;
|
|
66
|
+
hide(): void;
|
|
67
|
+
/** Lays the field over its node, or keeps it out of sight (and focused) while the node isn't shown. */
|
|
68
|
+
place(placement: EditorPlacement | null): void;
|
|
69
|
+
private describe;
|
|
70
|
+
/** Centers the field over its node, risen as far as it must to keep clear of the node's "+N". */
|
|
71
|
+
private move;
|
|
72
|
+
/** Grows the field with its text. */
|
|
73
|
+
private fit;
|
|
74
|
+
private save;
|
|
75
|
+
private readonly handleKeyDown;
|
|
76
|
+
private readonly handleBeforeInput;
|
|
77
|
+
private readonly handleInput;
|
|
78
|
+
private readonly handleBlur;
|
|
79
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Videos in the media viewer, behind one interface: a file plays in a
|
|
3
|
+
* `<video>` element, and YouTube and Vimeo in their own players, framed and
|
|
4
|
+
* driven through the messages those players take (the same ones their
|
|
5
|
+
* JavaScript APIs send). A video on any other site is framed as the detail
|
|
6
|
+
* sheet frames it, with nothing to drive.
|
|
7
|
+
*/
|
|
8
|
+
import type { OrbitNode } from '../types.js';
|
|
9
|
+
/** A video as it plays. */
|
|
10
|
+
export interface PlaybackState {
|
|
11
|
+
/** Playing, rather than paused, not started yet, or ended. */
|
|
12
|
+
playing: boolean;
|
|
13
|
+
/** Seconds from the start. */
|
|
14
|
+
time: number;
|
|
15
|
+
/** Seconds, once known. */
|
|
16
|
+
duration: number | null;
|
|
17
|
+
/** It played to the end. */
|
|
18
|
+
ended: boolean;
|
|
19
|
+
/** Its sound is off. */
|
|
20
|
+
muted: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface MediaPlayer {
|
|
23
|
+
/** What shows the video: a `<video>` element or a frame. */
|
|
24
|
+
readonly element: HTMLVideoElement | HTMLIFrameElement;
|
|
25
|
+
/** Width over height, as far as it's known yet. */
|
|
26
|
+
readonly ratio: number;
|
|
27
|
+
/** Whether `play`, `pause` and `seek` reach the video, and it reports its state. */
|
|
28
|
+
readonly controllable: boolean;
|
|
29
|
+
readonly state: PlaybackState;
|
|
30
|
+
play(): void;
|
|
31
|
+
pause(): void;
|
|
32
|
+
/** Moves to `seconds` from the start. */
|
|
33
|
+
seek(seconds: number): void;
|
|
34
|
+
/** Turns its sound on. */
|
|
35
|
+
unmute(): void;
|
|
36
|
+
/** Shows or hides the player's own controls, where that can change while it plays. */
|
|
37
|
+
setControls(controls: boolean): void;
|
|
38
|
+
/** Stops the video and lets go of it. */
|
|
39
|
+
destroy(): void;
|
|
40
|
+
}
|
|
41
|
+
export interface PlayerOptions {
|
|
42
|
+
/** Start playing as soon as it can. */
|
|
43
|
+
autoplay: boolean;
|
|
44
|
+
/** Show the player's own controls. */
|
|
45
|
+
controls: boolean;
|
|
46
|
+
/** Seconds into the video to start at. */
|
|
47
|
+
startAt?: number;
|
|
48
|
+
/** The frame's or video's accessible name. */
|
|
49
|
+
title: string;
|
|
50
|
+
/** A picture to show before a video file plays. */
|
|
51
|
+
poster?: string;
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
/** It played, paused, moved on, ended or was muted. */
|
|
54
|
+
onState(state: PlaybackState): void;
|
|
55
|
+
/** It learned its width over height. */
|
|
56
|
+
onRatio(ratio: number): void;
|
|
57
|
+
/** A video file couldn't be played. */
|
|
58
|
+
onError(): void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The player for a node's video, or null when its URL isn't allowed. Files
|
|
62
|
+
* (`.mp4`, `.webm`, `.mov`, `blob:` URLs, or `provider: native`) play in a
|
|
63
|
+
* `<video>` element; YouTube and Vimeo in their players.
|
|
64
|
+
*/
|
|
65
|
+
export declare const createPlayer: (video: NonNullable<OrbitNode["video"]>, options: PlayerOptions) => MediaPlayer | null;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The media viewer: a node's picture or video over the whole window, to see
|
|
3
|
+
* the one up close and to play the other. A native `<dialog>` opened as a
|
|
4
|
+
* modal, so it sits in the top layer above a map of any size, keeps focus
|
|
5
|
+
* inside and makes the page behind it inert.
|
|
6
|
+
*
|
|
7
|
+
* Pictures fit the window. The wheel, a pinch, a double click or tap, the
|
|
8
|
+
* +, − and 0 keys and the zoom buttons zoom them around the point under the
|
|
9
|
+
* pointer, and a zoomed picture pans with a drag. Previous and next step
|
|
10
|
+
* through the map's pictures and videos in reading order; so do the arrow
|
|
11
|
+
* keys and, on touch screens, a swipe, while the picture isn't zoomed. Esc,
|
|
12
|
+
* a click around the media or a swipe down closes it.
|
|
13
|
+
*
|
|
14
|
+
* On a map that isn't interactive, the viewer is the page's to drive: no
|
|
15
|
+
* buttons, no input of its own and no controls on videos.
|
|
16
|
+
*/
|
|
17
|
+
import type { OrbitNode } from '../types.js';
|
|
18
|
+
import type { OrbitWords } from '../languages.js';
|
|
19
|
+
import { type MediaPlayer, type PlaybackState } from './media-players.js';
|
|
20
|
+
export type MediaKind = 'image' | 'video';
|
|
21
|
+
/** A node's picture or its video. */
|
|
22
|
+
export interface MediaItem {
|
|
23
|
+
node: OrbitNode;
|
|
24
|
+
kind: MediaKind;
|
|
25
|
+
}
|
|
26
|
+
/** What the viewer asks the map, and tells it. */
|
|
27
|
+
export interface MediaViewerContext {
|
|
28
|
+
/** The map's pictures and videos, in reading order. */
|
|
29
|
+
items(): MediaItem[];
|
|
30
|
+
/** The map's node with this id, while it has one. */
|
|
31
|
+
node(id: string): OrbitNode | null;
|
|
32
|
+
baseUrl(): string | undefined;
|
|
33
|
+
/** Whether the map takes input; without it, neither does the viewer. */
|
|
34
|
+
interactive(): boolean;
|
|
35
|
+
/** The icon before a node's title: its own, or its type's. */
|
|
36
|
+
icon(node: OrbitNode): string | undefined;
|
|
37
|
+
/** It shows `item`, `index` of `total`: as it opens, and at every step. */
|
|
38
|
+
onShow(item: MediaItem, index: number, total: number): void;
|
|
39
|
+
/** It closed while showing `item`. */
|
|
40
|
+
onClose(item: MediaItem, index: number, total: number): void;
|
|
41
|
+
/** The video it shows started, paused, moved on, ended or was muted. */
|
|
42
|
+
onState(node: OrbitNode, state: PlaybackState): void;
|
|
43
|
+
/** The map's words, in its language. */
|
|
44
|
+
words(): OrbitWords;
|
|
45
|
+
}
|
|
46
|
+
export interface MediaShowOptions {
|
|
47
|
+
/** Start a video playing. Default true. */
|
|
48
|
+
autoplay?: boolean;
|
|
49
|
+
/** Start a video this many seconds in: where the detail sheet's player was, or where the page says. */
|
|
50
|
+
startAt?: number;
|
|
51
|
+
}
|
|
52
|
+
/** Whether a node's picture or video can be shown: it has one, at a URL allowed there. */
|
|
53
|
+
export declare const showsMedia: (node: OrbitNode, kind: MediaKind) => boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The map's pictures and videos in reading order: depth first, as the
|
|
56
|
+
* outline lists the map, a node's picture before its video. Nodes below
|
|
57
|
+
* `maxDepth` are left out, as they are from the map.
|
|
58
|
+
*/
|
|
59
|
+
export declare const galleryItems: (tree: OrbitNode, maxDepth?: number) => MediaItem[];
|
|
60
|
+
export declare class MediaViewer {
|
|
61
|
+
private static count;
|
|
62
|
+
private readonly host;
|
|
63
|
+
private readonly context;
|
|
64
|
+
private readonly id;
|
|
65
|
+
private dialog;
|
|
66
|
+
private parts;
|
|
67
|
+
private shown;
|
|
68
|
+
private items;
|
|
69
|
+
private index;
|
|
70
|
+
private picture;
|
|
71
|
+
private player;
|
|
72
|
+
/** The player's frame or video, in the box fitted to its width over height. */
|
|
73
|
+
private playerBox;
|
|
74
|
+
private returnFocus;
|
|
75
|
+
private resizeObserver;
|
|
76
|
+
private readonly pointers;
|
|
77
|
+
private press;
|
|
78
|
+
/** A pinch, from where it started: the picture's view, and the fingers' distance and middle (from the stage's center). */
|
|
79
|
+
private pinch;
|
|
80
|
+
private suppressClick;
|
|
81
|
+
private lastTap;
|
|
82
|
+
private settleTimer;
|
|
83
|
+
constructor(host: HTMLElement, context: MediaViewerContext);
|
|
84
|
+
get isOpen(): boolean;
|
|
85
|
+
/** What it shows, while it's open. */
|
|
86
|
+
get item(): MediaItem | null;
|
|
87
|
+
/** The video it shows, while it shows one that can be driven. */
|
|
88
|
+
get video(): MediaPlayer | null;
|
|
89
|
+
/**
|
|
90
|
+
* Shows a node's picture or video, opening the viewer if it's closed.
|
|
91
|
+
* Returns false when the node has no such media to show.
|
|
92
|
+
*/
|
|
93
|
+
open(item: MediaItem, options?: MediaShowOptions): boolean;
|
|
94
|
+
close(): void;
|
|
95
|
+
/** Steps to the previous (-1) or next (1) picture or video. Returns false at either end. */
|
|
96
|
+
step(direction: 1 | -1): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The map changed: what the viewer shows takes the node's new title and
|
|
99
|
+
* media, or the viewer closes if the node or its media went away.
|
|
100
|
+
*/
|
|
101
|
+
refresh(): void;
|
|
102
|
+
play(): boolean;
|
|
103
|
+
pause(): boolean;
|
|
104
|
+
seek(seconds: number): boolean;
|
|
105
|
+
unmute(): boolean;
|
|
106
|
+
destroy(): void;
|
|
107
|
+
/** Writes the viewer's buttons in the map's language, and what it shows, if it's open. */
|
|
108
|
+
relabel(): void;
|
|
109
|
+
private go;
|
|
110
|
+
private show;
|
|
111
|
+
private showPicture;
|
|
112
|
+
private pictureLoaded;
|
|
113
|
+
private showVideo;
|
|
114
|
+
/** Lets go of the media shown: a video stops. */
|
|
115
|
+
private clearMedia;
|
|
116
|
+
/** The title, caption, description, counter, link and buttons, for what's shown. */
|
|
117
|
+
private describe;
|
|
118
|
+
/** Where to see the picture on its own, or the video on its site; none for pictures in the map itself (`data:` and `blob:` URLs). */
|
|
119
|
+
private mediaLink;
|
|
120
|
+
/** Shows `text` over the stage, with a spinner while it's `loading`; null shows nothing. */
|
|
121
|
+
private setStatus;
|
|
122
|
+
/** Fits the picture or the player to the stage. */
|
|
123
|
+
private layout;
|
|
124
|
+
/**
|
|
125
|
+
* Where the media fits: the stage, less a margin, and the room the
|
|
126
|
+
* previous and next buttons take beside it on wide stages, or under a
|
|
127
|
+
* video on narrow ones.
|
|
128
|
+
*/
|
|
129
|
+
private mediaRoom;
|
|
130
|
+
/** The stage's place in the window: happy-dom has no layout, so tests replace this. */
|
|
131
|
+
private readStageBox;
|
|
132
|
+
/** A point in the window, from the stage's center. */
|
|
133
|
+
private stagePoint;
|
|
134
|
+
private setView;
|
|
135
|
+
/** Zooms the picture by `factor` around `anchor` (from the stage's center). */
|
|
136
|
+
private zoomAt;
|
|
137
|
+
private fitPicture;
|
|
138
|
+
private panBy;
|
|
139
|
+
private get zoomed();
|
|
140
|
+
private updateZoomButtons;
|
|
141
|
+
/** A button that had focus and can't be pressed any more (the last step, the furthest zoom) hands focus to the viewer, which keeps its keys. */
|
|
142
|
+
private keepFocus;
|
|
143
|
+
private ensureDialog;
|
|
144
|
+
/** After the dialog closed, by `close()` or by the browser. */
|
|
145
|
+
private finish;
|
|
146
|
+
private observeStage;
|
|
147
|
+
private readonly handleKeyDown;
|
|
148
|
+
/** Plays the video if it's paused, and pauses it if it plays. */
|
|
149
|
+
private togglePlayback;
|
|
150
|
+
/** Esc, as the browser reports it: the viewer closes itself, so focus goes back and the map hears of it. */
|
|
151
|
+
private readonly handleCancel;
|
|
152
|
+
private readonly handleClick;
|
|
153
|
+
private readonly handleWheel;
|
|
154
|
+
/** A drag on the viewer never scrolls the page behind it. */
|
|
155
|
+
private readonly handleTouchMove;
|
|
156
|
+
private readonly handleDoubleClick;
|
|
157
|
+
private toggleZoomAt;
|
|
158
|
+
private readonly handlePointerDown;
|
|
159
|
+
private readonly handlePointerMove;
|
|
160
|
+
private readonly handlePointerUp;
|
|
161
|
+
/** What a drag that starts this way does: pans a zoomed picture, or swipes across or down on a touch screen. */
|
|
162
|
+
private dragMode;
|
|
163
|
+
/** Brings media a swipe moved back to its place. */
|
|
164
|
+
private settleLayer;
|
|
165
|
+
private resetGestures;
|
|
166
|
+
private capture;
|
|
167
|
+
private pinchDistance;
|
|
168
|
+
private pinchCenter;
|
|
169
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry for pictures in the media viewer: fitting a picture to the
|
|
3
|
+
* window, zooming around a point that stays where it is, and keeping a
|
|
4
|
+
* zoomed picture over the stage while it is panned.
|
|
5
|
+
*
|
|
6
|
+
* Points are in px from the stage's center. A view is the picture's zoom
|
|
7
|
+
* relative to its fitted size, and where its center is.
|
|
8
|
+
*/
|
|
9
|
+
export interface Size {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Point {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}
|
|
17
|
+
/** A picture shown `scale` times its fitted size, its center `x`, `y` px from the stage's center. */
|
|
18
|
+
export interface PictureView {
|
|
19
|
+
scale: number;
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const FITTED: PictureView;
|
|
24
|
+
/**
|
|
25
|
+
* The size a picture is drawn at to fit `room`: as large as fits, but no
|
|
26
|
+
* more than twice its own size, unless it's `scalable` (a vector picture).
|
|
27
|
+
*/
|
|
28
|
+
export declare const fittedSize: (natural: Size, room: Size, scalable?: boolean) => Size;
|
|
29
|
+
/** The furthest a picture zooms in, relative to its fitted size. */
|
|
30
|
+
export declare const maximumZoom: (natural: Size, fitted: Size) => number;
|
|
31
|
+
/**
|
|
32
|
+
* `view` within its limits: zoomed between the fitted size and `maximum`,
|
|
33
|
+
* and placed so a picture bigger than the stage leaves no gap at its edges,
|
|
34
|
+
* while one smaller than the stage (in either direction) stays centered.
|
|
35
|
+
*/
|
|
36
|
+
export declare const clampView: (view: PictureView, fitted: Size, stage: Size, maximum: number) => PictureView;
|
|
37
|
+
/**
|
|
38
|
+
* Zooms by `factor`, keeping the point of the picture under `anchor` where
|
|
39
|
+
* it is, as far as the limits allow.
|
|
40
|
+
*/
|
|
41
|
+
export declare const zoomAround: (view: PictureView, factor: number, anchor: Point, fitted: Size, stage: Size, maximum: number) => PictureView;
|
|
42
|
+
/** Moves the picture by `deltaX`, `deltaY`, as far as its edges allow. */
|
|
43
|
+
export declare const panView: (view: PictureView, deltaX: number, deltaY: number, fitted: Size, stage: Size, maximum: number) => PictureView;
|
|
44
|
+
/**
|
|
45
|
+
* A double click or tap: back to the fitted view when zoomed in; otherwise
|
|
46
|
+
* in at `anchor`, to the picture's own size, or to twice the fitted size
|
|
47
|
+
* when that is further.
|
|
48
|
+
*/
|
|
49
|
+
export declare const toggleZoom: (view: PictureView, anchor: Point, natural: Size, fitted: Size, stage: Size, maximum: number) => PictureView;
|
|
50
|
+
/** Whether the picture is zoomed in past its fitted size. */
|
|
51
|
+
export declare const isZoomed: (view: PictureView) => boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, safe markdown renderer for notes. It builds DOM nodes directly
|
|
3
|
+
* (never `innerHTML`), so note text can't inject markup, and links and
|
|
4
|
+
* images go through the same URL guard as the rest of the map.
|
|
5
|
+
*
|
|
6
|
+
* Supported: paragraphs, headings, bullet and numbered lists (nested by
|
|
7
|
+
* indentation), blockquotes, fenced code, **bold**, *italic*, ~~strike~~,
|
|
8
|
+
* `code`, [links](https://…), <https://autolinks> and .
|
|
9
|
+
*/
|
|
10
|
+
export interface NoteRenderContext {
|
|
11
|
+
/** Where relative links and pictures resolve from: the map's `baseUrl`. */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/** Whether a `#id` link points at a node of the map; links to missing nodes show as text. */
|
|
14
|
+
resolveLink?: (href: string) => unknown;
|
|
15
|
+
}
|
|
16
|
+
/** A note as DOM nodes, the way the detail sheet shows it: for pages that show notes in their own UI. */
|
|
17
|
+
export declare const renderNoteMarkdown: (markdown: string, context?: NoteRenderContext) => DocumentFragment;
|
|
18
|
+
/** The note as plain text, for tooltips: markdown syntax removed, whitespace collapsed. */
|
|
19
|
+
export declare const noteToPlainText: (markdown: string) => string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The map as a nested list. Screen readers can always browse it (it is
|
|
3
|
+
* visually hidden but present), the list button shows it to everyone, and
|
|
4
|
+
* a polite live region announces where navigation landed.
|
|
5
|
+
*/
|
|
6
|
+
import type { OrbitNode } from '../types.js';
|
|
7
|
+
import type { OrbitWords } from '../languages.js';
|
|
8
|
+
export declare class AccessibleOutline {
|
|
9
|
+
readonly element: HTMLElement;
|
|
10
|
+
readonly live: HTMLDivElement;
|
|
11
|
+
private readonly onSelect;
|
|
12
|
+
private visible;
|
|
13
|
+
private icon;
|
|
14
|
+
/** Each node's entry, so moving between nodes only moves the current marker. */
|
|
15
|
+
private readonly entries;
|
|
16
|
+
private currentId;
|
|
17
|
+
private words;
|
|
18
|
+
constructor(onSelect: (nodeId: string) => void, words: OrbitWords);
|
|
19
|
+
/** Names the list in `words`' language; the next `update` writes its entries in it. */
|
|
20
|
+
relabel(words: OrbitWords): void;
|
|
21
|
+
get isVisible(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Rebuilds the list. Without `interactive`, entries are plain text instead
|
|
24
|
+
* of buttons. `icon` gives a node's icon, its type's when it has none.
|
|
25
|
+
*/
|
|
26
|
+
update(tree: OrbitNode, focusId: string | null, maxDepth: number | undefined, interactive: boolean, icon?: (node: OrbitNode) => string | undefined): void;
|
|
27
|
+
/** Marks the focused node's entry, without rebuilding the list: a big map's outline has thousands. */
|
|
28
|
+
setCurrent(focusId: string | null): void;
|
|
29
|
+
setVisible(visible: boolean): void;
|
|
30
|
+
announce(text: string): void;
|
|
31
|
+
private item;
|
|
32
|
+
}
|
|
33
|
+
/** A hover and keyboard-focus tooltip for nodes. */
|
|
34
|
+
export declare class Tooltip {
|
|
35
|
+
readonly element: HTMLDivElement;
|
|
36
|
+
private readonly title;
|
|
37
|
+
private readonly detail;
|
|
38
|
+
constructor();
|
|
39
|
+
get isVisible(): boolean;
|
|
40
|
+
show(title: string, detail: string, x: number, y: number): void;
|
|
41
|
+
/** Puts the tooltip's tip at `x`, `y`, the tooltip centered above it. */
|
|
42
|
+
place(x: number, y: number): void;
|
|
43
|
+
hide(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL guard for Orbit. Validates the scheme per render surface, lets
|
|
3
|
+
* relative URLs through unchanged, and resolves them against an optional
|
|
4
|
+
* baseUrl.
|
|
5
|
+
*/
|
|
6
|
+
export type UrlSurface = 'image' | 'video' | 'embed' | 'link';
|
|
7
|
+
/**
|
|
8
|
+
* A URL as browsers read it: without the control characters and spaces at
|
|
9
|
+
* its ends, and without tabs and line breaks anywhere. The URL standard drops
|
|
10
|
+
* them before it reads the scheme, so " javascript:" and "java\tscript:"
|
|
11
|
+
* are both "javascript:" to a browser.
|
|
12
|
+
*/
|
|
13
|
+
export declare const normalizeUrl: (url: string) => string;
|
|
14
|
+
/**
|
|
15
|
+
* The URL to use on `surface`, or null when its scheme isn't allowed there.
|
|
16
|
+
* Checks the URL the way a browser will read it, and returns it in that form.
|
|
17
|
+
* A blocked URL is reported on the console, unless `report` is false: for
|
|
18
|
+
* checking one before it goes into a map.
|
|
19
|
+
*/
|
|
20
|
+
export declare const guardUrl: (input: string, surface: UrlSurface, report?: boolean) => string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a baseUrl string to its canonical absolute href.
|
|
23
|
+
*
|
|
24
|
+
* - Absolute baseUrls (`https://example.com/assets/`) are returned as `URL().href`.
|
|
25
|
+
* - Relative baseUrls (`/assets/`, `./foo/`) are resolved against `document.baseURI` in browsers.
|
|
26
|
+
* - Throws if baseUrl is relative and no `document.baseURI` is available (Node/SSR).
|
|
27
|
+
*/
|
|
28
|
+
export declare const resolveBaseUrl: (baseUrl: string) => string;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve `url` against `baseUrl` if relative; pass through if absolute or no baseUrl.
|
|
31
|
+
*
|
|
32
|
+
* - Absolute URLs (https://, http://, etc.) pass through unchanged.
|
|
33
|
+
* - Protocol-relative URLs (//host/path) are treated as already-absolute (resolved by the browser at fetch time).
|
|
34
|
+
* - Relative URLs (foo.jpg, ./x, /assets/y) are resolved against `baseUrl`.
|
|
35
|
+
*
|
|
36
|
+
* For directory-relative resolution, `baseUrl` should end with `/`.
|
|
37
|
+
* Per WHATWG URL spec, `new URL('foo.jpg', 'https://x/dir')` yields `https://x/foo.jpg`
|
|
38
|
+
* (the trailing path segment is consumed); `new URL('foo.jpg', 'https://x/dir/')` yields `https://x/dir/foo.jpg`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const resolveUrl: (url: string, baseUrl: string | undefined) => string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { OrbitNode } from './types.js';
|
|
2
|
+
export declare const slugify: (value: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Slug used for auto-generated node ids in both parsers and for detecting
|
|
5
|
+
* explicit ids in both serializers (`nodesWithExplicitIds`), which is why
|
|
6
|
+
* it lives here.
|
|
7
|
+
*/
|
|
8
|
+
export declare const nodeSlug: (text: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Links a node to its parent. The link is non-enumerable so a parsed tree
|
|
11
|
+
* stays a plain tree for `JSON.stringify`, spreads and structured clones.
|
|
12
|
+
*/
|
|
13
|
+
export declare const setParent: (node: OrbitNode, parent: OrbitNode | undefined) => void;
|
|
14
|
+
export declare const ensureUniqueNodeIds: (root: OrbitNode) => void;
|
|
15
|
+
/**
|
|
16
|
+
* The nodes whose ids a serializer has to write: those the parsers wouldn't
|
|
17
|
+
* give them from their text. A second "Research" reads back as
|
|
18
|
+
* `research-2` on its own, so it needs no id attribute.
|
|
19
|
+
*/
|
|
20
|
+
export declare const nodesWithExplicitIds: (root: OrbitNode) => Set<OrbitNode>;
|
|
21
|
+
export declare const findNodeById: (root: OrbitNode, id: string) => OrbitNode | null;
|
|
22
|
+
export declare const getNodePath: (root: OrbitNode, id: string) => OrbitNode[];
|
|
23
|
+
export declare const clamp: (value: number, min: number, max: number) => number;
|