@plotwork.official/viewer 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 +100 -2
- package/dist/core/canvas-view.d.ts +154 -0
- package/dist/core/canvas-view.js +207 -0
- package/dist/core/fingerprint.d.ts +11 -0
- package/dist/core/fingerprint.js +20 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +8 -0
- package/dist/core/limits.d.ts +19 -0
- package/dist/core/limits.js +19 -0
- package/dist/core/live-watch.d.ts +18 -0
- package/dist/core/live-watch.js +49 -0
- package/dist/core/picture-loader.d.ts +38 -0
- package/dist/core/picture-loader.js +58 -0
- package/dist/core/picture-map.d.ts +86 -0
- package/dist/core/picture-map.js +288 -0
- package/dist/core/picture-scheme.d.ts +18 -0
- package/dist/core/picture-scheme.js +31 -0
- package/dist/core/picture-source.d.ts +23 -0
- package/dist/core/picture-source.js +57 -0
- package/dist/element/copy.d.ts +22 -0
- package/dist/element/copy.js +28 -0
- package/dist/element/define.d.ts +7 -0
- package/dist/element/define.js +12 -0
- package/dist/element/dom.d.ts +12 -0
- package/dist/element/dom.js +25 -0
- package/dist/element/events.d.ts +39 -0
- package/dist/element/events.js +4 -0
- package/dist/element/gestures.d.ts +28 -0
- package/dist/element/gestures.js +142 -0
- package/dist/element/plotwork-diagram.d.ts +21 -0
- package/dist/element/plotwork-diagram.js +397 -0
- package/dist/element/shell.d.ts +22 -0
- package/dist/element/shell.js +30 -0
- package/dist/element/styles.d.ts +6 -0
- package/dist/element/styles.js +137 -0
- package/dist/element/trace-layer.d.ts +9 -0
- package/dist/element/trace-layer.js +45 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/react/index.d.ts +39 -0
- package/dist/react/index.js +52 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +6 -0
- package/package.json +74 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The element's tag. */
|
|
2
|
+
export declare const TAG_NAME = "plotwork-diagram";
|
|
3
|
+
/**
|
|
4
|
+
* Registers `<plotwork-diagram>` once. Safe to call many times, from several copies of the package on one page, and where there is
|
|
5
|
+
* no `customElements` (a server render), where it does nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function definePlotworkDiagram(tag?: string): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createPlotworkDiagramClass } from './plotwork-diagram.js';
|
|
2
|
+
/** The element's tag. */
|
|
3
|
+
export const TAG_NAME = 'plotwork-diagram';
|
|
4
|
+
/**
|
|
5
|
+
* Registers `<plotwork-diagram>` once. Safe to call many times, from several copies of the package on one page, and where there is
|
|
6
|
+
* no `customElements` (a server render), where it does nothing.
|
|
7
|
+
*/
|
|
8
|
+
export function definePlotworkDiagram(tag = TAG_NAME) {
|
|
9
|
+
if (typeof customElements === 'undefined' || customElements.get(tag) !== undefined)
|
|
10
|
+
return;
|
|
11
|
+
customElements.define(tag, createPlotworkDiagramClass());
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building the element's own nodes with `createElement` and `createElementNS`, never from a string of HTML, so nothing a picture or
|
|
3
|
+
* a page supplies can become markup. Attribute values are set with `setAttribute`, and text with `textContent`.
|
|
4
|
+
*/
|
|
5
|
+
type Attributes = Readonly<Record<string, string | number | undefined>>;
|
|
6
|
+
/** An HTML element of the element's own. */
|
|
7
|
+
export declare function html<K extends keyof HTMLElementTagNameMap>(tag: K, attributes?: Attributes, ...children: (Node | string)[]): HTMLElementTagNameMap[K];
|
|
8
|
+
/** An SVG element of the element's own. */
|
|
9
|
+
export declare function svg<K extends keyof SVGElementTagNameMap>(tag: K, attributes?: Attributes, ...children: (Node | string)[]): SVGElementTagNameMap[K];
|
|
10
|
+
/** An icon: a 20 by 20 SVG of one or more paths, hidden from assistive technology because its button has a name. */
|
|
11
|
+
export declare function icon(paths: readonly string[]): SVGSVGElement;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building the element's own nodes with `createElement` and `createElementNS`, never from a string of HTML, so nothing a picture or
|
|
3
|
+
* a page supplies can become markup. Attribute values are set with `setAttribute`, and text with `textContent`.
|
|
4
|
+
*/
|
|
5
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
6
|
+
function apply(element, attributes, children) {
|
|
7
|
+
for (const [name, value] of Object.entries(attributes))
|
|
8
|
+
if (value !== undefined)
|
|
9
|
+
element.setAttribute(name, String(value));
|
|
10
|
+
for (const child of children)
|
|
11
|
+
element.append(child);
|
|
12
|
+
return element;
|
|
13
|
+
}
|
|
14
|
+
/** An HTML element of the element's own. */
|
|
15
|
+
export function html(tag, attributes = {}, ...children) {
|
|
16
|
+
return apply(document.createElement(tag), attributes, children);
|
|
17
|
+
}
|
|
18
|
+
/** An SVG element of the element's own. */
|
|
19
|
+
export function svg(tag, attributes = {}, ...children) {
|
|
20
|
+
return apply(document.createElementNS(SVG_NS, tag), attributes, children);
|
|
21
|
+
}
|
|
22
|
+
/** An icon: a 20 by 20 SVG of one or more paths, hidden from assistive technology because its button has a name. */
|
|
23
|
+
export function icon(paths) {
|
|
24
|
+
return svg('svg', { viewBox: '0 0 20 20', width: 20, height: 20, 'aria-hidden': 'true', focusable: 'false' }, ...paths.map((d) => svg('path', { d })));
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { LoadFailure } from '../core/picture-loader.js';
|
|
2
|
+
/**
|
|
3
|
+
* The events `<plotwork-diagram>` sends. Each is a `CustomEvent` that bubbles and crosses the shadow boundary (`composed`), so a
|
|
4
|
+
* page listens on the element or on anything that holds it.
|
|
5
|
+
*/
|
|
6
|
+
/** A picture is shown for the first time, or again after `src`, `svg` or `theme` changed. */
|
|
7
|
+
export interface LoadDetail {
|
|
8
|
+
fingerprint: string;
|
|
9
|
+
/** True when the picture carries its source, which `source()` returns. */
|
|
10
|
+
hasSource: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** A live picture changed on the server, and the new version is shown. */
|
|
13
|
+
export interface ChangeDetail {
|
|
14
|
+
fingerprint: string;
|
|
15
|
+
previous: string;
|
|
16
|
+
}
|
|
17
|
+
/** The picture cannot be shown, and the notice says why. `empty` is an element with neither `src` nor `svg`. */
|
|
18
|
+
export interface ErrorDetail {
|
|
19
|
+
reason: LoadFailure | 'empty';
|
|
20
|
+
}
|
|
21
|
+
/** A box that names a part of its series (`part=`) was pressed. The page decides what opens. */
|
|
22
|
+
export interface DrillDetail {
|
|
23
|
+
id: string;
|
|
24
|
+
part: number;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
/** A box is traced (its id), or nothing is (null). */
|
|
28
|
+
export interface TraceDetail {
|
|
29
|
+
id: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface PlotworkEventMap {
|
|
32
|
+
'plotwork-load': CustomEvent<LoadDetail>;
|
|
33
|
+
'plotwork-change': CustomEvent<ChangeDetail>;
|
|
34
|
+
'plotwork-error': CustomEvent<ErrorDetail>;
|
|
35
|
+
'plotwork-drill': CustomEvent<DrillDetail>;
|
|
36
|
+
'plotwork-trace': CustomEvent<TraceDetail>;
|
|
37
|
+
}
|
|
38
|
+
/** Sends one of the element's events from it. */
|
|
39
|
+
export declare function emit<K extends keyof PlotworkEventMap>(target: EventTarget, type: K, detail: PlotworkEventMap[K]['detail']): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Point, Size, View, WheelMode } from '../core/canvas-view.js';
|
|
2
|
+
/**
|
|
3
|
+
* What moves the picture: the wheel, a drag, a two-finger pinch, a double press, and the keys. Every handler reads the latest view
|
|
4
|
+
* from the controller, because a pointer reports several times a frame, and hands back a view the arithmetic already held inside
|
|
5
|
+
* the rules. A press that did not move counts as a press, so the element can trace or drill.
|
|
6
|
+
*/
|
|
7
|
+
/** What the gestures need from the element. */
|
|
8
|
+
export interface CanvasController {
|
|
9
|
+
view(): View;
|
|
10
|
+
setView(view: View, glide?: boolean): void;
|
|
11
|
+
/** The canvas and the picture, or null before the picture has a size. */
|
|
12
|
+
sizes(): {
|
|
13
|
+
viewport: Size;
|
|
14
|
+
picture: Size;
|
|
15
|
+
} | null;
|
|
16
|
+
wheelMode(): WheelMode;
|
|
17
|
+
/** The pointer is over this canvas point, or has left (null). */
|
|
18
|
+
hover(point: Point | null): void;
|
|
19
|
+
/** A press that did not move, at this canvas point. */
|
|
20
|
+
press(point: Point): void;
|
|
21
|
+
/** A plain wheel was left to the page: tell the reader how to zoom. */
|
|
22
|
+
hintWheel(): void;
|
|
23
|
+
fit(): void;
|
|
24
|
+
/** Escape: let go of a trace. */
|
|
25
|
+
release(): void;
|
|
26
|
+
}
|
|
27
|
+
/** Wires the gestures to a canvas; the function it returns removes them. */
|
|
28
|
+
export declare function attachGestures(canvas: HTMLElement, control: CanvasController): () => void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { middleOf, mouseWheelZoomFactor, panBy, pinchView, toggledScale, wheelIntent, wheelPixels, wheelZoomFactor, zoomAt, isMouseWheel } from '../core/canvas-view.js';
|
|
2
|
+
import { PRESS_SLOP_PX, VIEWER_PAN_STEP, VIEWER_ZOOM_STEP } from '../core/limits.js';
|
|
3
|
+
/** Wires the gestures to a canvas; the function it returns removes them. */
|
|
4
|
+
export function attachGestures(canvas, control) {
|
|
5
|
+
const pointers = new Map();
|
|
6
|
+
let drag = null;
|
|
7
|
+
let pinch = null;
|
|
8
|
+
const local = (event) => {
|
|
9
|
+
const rect = canvas.getBoundingClientRect();
|
|
10
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
11
|
+
};
|
|
12
|
+
const pair = () => {
|
|
13
|
+
const [a, b] = [...pointers.values()];
|
|
14
|
+
if (a === undefined || b === undefined)
|
|
15
|
+
return null;
|
|
16
|
+
return { mid: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }, distance: Math.hypot(a.x - b.x, a.y - b.y) };
|
|
17
|
+
};
|
|
18
|
+
const onDown = (event) => {
|
|
19
|
+
if (event.button !== 0)
|
|
20
|
+
return;
|
|
21
|
+
canvas.setPointerCapture?.(event.pointerId);
|
|
22
|
+
const point = local(event);
|
|
23
|
+
pointers.set(event.pointerId, point);
|
|
24
|
+
const both = pair();
|
|
25
|
+
if (both !== null) {
|
|
26
|
+
pinch = { view: control.view(), ...both };
|
|
27
|
+
drag = null;
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
drag = { start: point, last: point, moved: false };
|
|
31
|
+
};
|
|
32
|
+
const onMove = (event) => {
|
|
33
|
+
const point = local(event);
|
|
34
|
+
if (!pointers.has(event.pointerId)) {
|
|
35
|
+
if (event.pointerType !== 'touch')
|
|
36
|
+
control.hover(point);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
pointers.set(event.pointerId, point);
|
|
40
|
+
const sizes = control.sizes();
|
|
41
|
+
if (sizes === null)
|
|
42
|
+
return;
|
|
43
|
+
const both = pair();
|
|
44
|
+
if (pinch !== null && both !== null) {
|
|
45
|
+
control.setView(pinchView(pinch, both, sizes.viewport, sizes.picture));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (drag === null)
|
|
49
|
+
return;
|
|
50
|
+
if (!drag.moved && Math.hypot(point.x - drag.start.x, point.y - drag.start.y) < PRESS_SLOP_PX)
|
|
51
|
+
return;
|
|
52
|
+
if (!drag.moved)
|
|
53
|
+
canvas.setAttribute('data-dragging', '');
|
|
54
|
+
drag.moved = true;
|
|
55
|
+
control.setView(panBy(control.view(), point.x - drag.last.x, point.y - drag.last.y, sizes.viewport, sizes.picture));
|
|
56
|
+
drag.last = point;
|
|
57
|
+
};
|
|
58
|
+
const onUp = (event) => {
|
|
59
|
+
if (!pointers.delete(event.pointerId))
|
|
60
|
+
return;
|
|
61
|
+
if (drag !== null && !drag.moved && pinch === null)
|
|
62
|
+
control.press(local(event));
|
|
63
|
+
if (pointers.size < 2)
|
|
64
|
+
pinch = null;
|
|
65
|
+
drag = null;
|
|
66
|
+
canvas.removeAttribute('data-dragging');
|
|
67
|
+
};
|
|
68
|
+
const onLeave = () => {
|
|
69
|
+
if (pointers.size === 0)
|
|
70
|
+
control.hover(null);
|
|
71
|
+
};
|
|
72
|
+
const onWheel = (event) => {
|
|
73
|
+
const sizes = control.sizes();
|
|
74
|
+
if (sizes === null)
|
|
75
|
+
return;
|
|
76
|
+
const intent = wheelIntent(event, control.wheelMode());
|
|
77
|
+
if (intent === 'page') {
|
|
78
|
+
control.hintWheel();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
const view = control.view();
|
|
83
|
+
if (intent === 'pan') {
|
|
84
|
+
control.setView(panBy(view, -wheelPixels(event.deltaX, event.deltaMode, sizes.viewport.width), -wheelPixels(event.deltaY, event.deltaMode, sizes.viewport.height), sizes.viewport, sizes.picture));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const delta = wheelPixels(event.deltaY, event.deltaMode, sizes.viewport.height);
|
|
88
|
+
const factor = isMouseWheel(event) && !event.ctrlKey ? mouseWheelZoomFactor(delta) : wheelZoomFactor(delta);
|
|
89
|
+
control.setView(zoomAt(view, local(event), view.scale * factor, sizes.viewport, sizes.picture));
|
|
90
|
+
};
|
|
91
|
+
const onDouble = (event) => {
|
|
92
|
+
const sizes = control.sizes();
|
|
93
|
+
if (sizes === null)
|
|
94
|
+
return;
|
|
95
|
+
const view = control.view();
|
|
96
|
+
control.setView(zoomAt(view, local(event), toggledScale(view, sizes.viewport, sizes.picture), sizes.viewport, sizes.picture), true);
|
|
97
|
+
};
|
|
98
|
+
const onKey = (event) => {
|
|
99
|
+
if (event.altKey || event.ctrlKey || event.metaKey)
|
|
100
|
+
return;
|
|
101
|
+
const sizes = control.sizes();
|
|
102
|
+
if (sizes === null)
|
|
103
|
+
return;
|
|
104
|
+
const view = control.view();
|
|
105
|
+
const zoom = (factor) => control.setView(zoomAt(view, middleOf(sizes.viewport), view.scale * factor, sizes.viewport, sizes.picture), true);
|
|
106
|
+
const pan = (dx, dy) => control.setView(panBy(view, dx, dy, sizes.viewport, sizes.picture), true);
|
|
107
|
+
const keys = {
|
|
108
|
+
'+': () => zoom(VIEWER_ZOOM_STEP),
|
|
109
|
+
'=': () => zoom(VIEWER_ZOOM_STEP),
|
|
110
|
+
'-': () => zoom(1 / VIEWER_ZOOM_STEP),
|
|
111
|
+
'0': () => control.fit(),
|
|
112
|
+
ArrowLeft: () => pan(VIEWER_PAN_STEP, 0),
|
|
113
|
+
ArrowRight: () => pan(-VIEWER_PAN_STEP, 0),
|
|
114
|
+
ArrowUp: () => pan(0, VIEWER_PAN_STEP),
|
|
115
|
+
ArrowDown: () => pan(0, -VIEWER_PAN_STEP),
|
|
116
|
+
Escape: () => control.release(),
|
|
117
|
+
};
|
|
118
|
+
const action = keys[event.key];
|
|
119
|
+
if (action === undefined)
|
|
120
|
+
return;
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
action();
|
|
123
|
+
};
|
|
124
|
+
canvas.addEventListener('pointerdown', onDown);
|
|
125
|
+
canvas.addEventListener('pointermove', onMove);
|
|
126
|
+
canvas.addEventListener('pointerup', onUp);
|
|
127
|
+
canvas.addEventListener('pointercancel', onUp);
|
|
128
|
+
canvas.addEventListener('pointerleave', onLeave);
|
|
129
|
+
canvas.addEventListener('wheel', onWheel, { passive: false });
|
|
130
|
+
canvas.addEventListener('dblclick', onDouble);
|
|
131
|
+
canvas.addEventListener('keydown', onKey);
|
|
132
|
+
return () => {
|
|
133
|
+
canvas.removeEventListener('pointerdown', onDown);
|
|
134
|
+
canvas.removeEventListener('pointermove', onMove);
|
|
135
|
+
canvas.removeEventListener('pointerup', onUp);
|
|
136
|
+
canvas.removeEventListener('pointercancel', onUp);
|
|
137
|
+
canvas.removeEventListener('pointerleave', onLeave);
|
|
138
|
+
canvas.removeEventListener('wheel', onWheel);
|
|
139
|
+
canvas.removeEventListener('dblclick', onDouble);
|
|
140
|
+
canvas.removeEventListener('keydown', onKey);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ViewerLabels } from './copy.js';
|
|
2
|
+
/** The attributes the element reads. */
|
|
3
|
+
export declare const OBSERVED_ATTRIBUTES: readonly ['src', 'theme', 'live', 'interval', 'fingerprint', 'wheel', 'label'];
|
|
4
|
+
/** What a page can call and set on `<plotwork-diagram>`, besides its attributes. */
|
|
5
|
+
export interface PlotworkDiagramElement extends HTMLElement {
|
|
6
|
+
/** The picture's text, for a page that already holds it (a bundler import). It wins over `src`. */
|
|
7
|
+
svg: string | null;
|
|
8
|
+
/** Words in the page's language, over the English defaults. */
|
|
9
|
+
labels: Partial<ViewerLabels> | undefined;
|
|
10
|
+
/** The fingerprint of the picture shown, or null. */
|
|
11
|
+
readonly fingerprint: string | null;
|
|
12
|
+
/** The source the picture carries, or null when it carries none. */
|
|
13
|
+
source(): string | null;
|
|
14
|
+
fit(): void;
|
|
15
|
+
zoomIn(): void;
|
|
16
|
+
zoomOut(): void;
|
|
17
|
+
/** Loads the picture again, checking the server's copy. */
|
|
18
|
+
reload(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** The element's class, made on first use so that importing the package where there is no `HTMLElement` (a server render) is safe. */
|
|
21
|
+
export declare function createPlotworkDiagramClass(): CustomElementConstructor;
|