@fusion.dev/helix-docs 0.176.1 → 0.177.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/content/canvases.mdx +141 -0
- package/content/graphs.mdx +8 -0
- package/content/maps.mdx +160 -19
- package/content/metrics.mdx +221 -221
- package/content/motion.mdx +5 -1
- package/dist/pages.generated.js +6 -0
- package/dist/sources.generated.js +536 -241
- package/package.json +9 -9
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Add a board of cards and connections that people can move, connect, and read with any input, and save it as JSON Canvas.
|
|
3
|
+
section: Essentials
|
|
4
|
+
title: Canvases
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Canvases
|
|
8
|
+
|
|
9
|
+
Helix Canvas draws a board of cards and the connections between them. People
|
|
10
|
+
pan and zoom the board, move and resize cards, draw connections, and walk the
|
|
11
|
+
board in reading order from the keyboard. The board reads and writes
|
|
12
|
+
[JSON Canvas](https://jsoncanvas.org), an open format.
|
|
13
|
+
|
|
14
|
+
## Add a board
|
|
15
|
+
|
|
16
|
+
Write each card as a `helix-canvas-node` and each connection as a
|
|
17
|
+
`helix-canvas-edge`. A card's `x`, `y`, `width`, and `height` are in the
|
|
18
|
+
document's own units, and a connection names its two cards by `name`:
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<helix-canvas label="Launch plan">
|
|
22
|
+
<helix-canvas-node name="draft" x="0" y="0" width="160" height="64">Draft the email</helix-canvas-node>
|
|
23
|
+
<helix-canvas-node name="ship" tone="success" x="256" y="0" width="160" height="64">Publish the release</helix-canvas-node>
|
|
24
|
+
<helix-canvas-edge from="draft" to="ship"></helix-canvas-edge>
|
|
25
|
+
</helix-canvas>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import '@fusion.dev/helix/canvas'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Give the board a `label` that says what it holds. A screen reader announces it
|
|
33
|
+
as "Launch plan, canvas".
|
|
34
|
+
|
|
35
|
+
## Load and save a document
|
|
36
|
+
|
|
37
|
+
`fromJsonCanvas` turns a JSON Canvas document into the props of the elements.
|
|
38
|
+
What a card shows is yours to render, so this example writes each text card's
|
|
39
|
+
text:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
import { fromJsonCanvas } from '@fusion.dev/helix/canvas'
|
|
43
|
+
|
|
44
|
+
const board = document.querySelector('helix-canvas')
|
|
45
|
+
const { nodes, edges } = fromJsonCanvas(saved)
|
|
46
|
+
for (const node of nodes) {
|
|
47
|
+
const card = document.createElement('helix-canvas-node')
|
|
48
|
+
const { group, height, label, name, tone, width, x, y } = node
|
|
49
|
+
Object.assign(card, { group, height, label, name, tone, width, x, y })
|
|
50
|
+
card.textContent = node.text ?? ''
|
|
51
|
+
board.append(card)
|
|
52
|
+
}
|
|
53
|
+
for (const edge of edges) {
|
|
54
|
+
const line = document.createElement('helix-canvas-edge')
|
|
55
|
+
const { from, fromSide, label, name, to, toSide, tone } = edge
|
|
56
|
+
Object.assign(line, { from, fromSide, label, name, to, toSide, tone })
|
|
57
|
+
board.append(line)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`board.toJsonCanvas()` writes the board back out as a JSON Canvas document.
|
|
62
|
+
|
|
63
|
+
## Keep the board in your state
|
|
64
|
+
|
|
65
|
+
The board moves a card itself and sends `helix-move` when the move is finished.
|
|
66
|
+
To keep positions in your own state, cancel `helix-before-move` and write the
|
|
67
|
+
positions from its detail:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
board.addEventListener('helix-before-move', (event) => {
|
|
71
|
+
event.preventDefault()
|
|
72
|
+
for (const { name, x, y } of event.detail.nodes) {
|
|
73
|
+
const card = board.querySelector(`[name="${name}"]`)
|
|
74
|
+
card.x = x
|
|
75
|
+
card.y = y
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Connecting and removing are requests that your page applies, because a new
|
|
81
|
+
connection is a new element:
|
|
82
|
+
|
|
83
|
+
- `helix-connect` names two cards and their sides. Add a `helix-canvas-edge`
|
|
84
|
+
that joins them.
|
|
85
|
+
- `helix-remove` names the cards and connections to remove, including every
|
|
86
|
+
connection that removing a card would leave without an end. Remove those
|
|
87
|
+
elements.
|
|
88
|
+
- `helix-canvas-edit` asks you to edit a card. Open your own editor in it.
|
|
89
|
+
|
|
90
|
+
Set `readonly` on the board to keep every card in place, or on one card or
|
|
91
|
+
connection to keep only that one.
|
|
92
|
+
|
|
93
|
+
## Keyboard and screen readers
|
|
94
|
+
|
|
95
|
+
The board is one tab stop. A card holds it, and Tab leaves the board.
|
|
96
|
+
|
|
97
|
+
| Keys | On | What they do |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Arrow keys | A card or a connection | Move through the cards in reading order, each card's connections straight after it, and select what they land on |
|
|
100
|
+
| Home, End | A card or a connection | Move to the first or the last stop |
|
|
101
|
+
| Control or Command with an arrow key, Home, or End | A card or a connection | Move without changing the selection |
|
|
102
|
+
| Space | A card or a connection | Add it to the selection or take it out |
|
|
103
|
+
| Control or Command with A | A card or a connection | Select everything |
|
|
104
|
+
| Shift with an arrow key | A card | Move the card by 16 units, or to the next multiple of `snap` |
|
|
105
|
+
| Alt with an arrow key | A card | Resize the card from its bottom right corner |
|
|
106
|
+
| Enter or F2 | A card | Ask to edit it, with `helix-canvas-edit`, or edit a group's name |
|
|
107
|
+
| Enter | A connection | Move to the card it arrives at |
|
|
108
|
+
| F2 | A connection | Edit its label |
|
|
109
|
+
| C, then Enter on another card | A card | Ask to connect the two, with `helix-connect` |
|
|
110
|
+
| C | A connection | Pick up its end, to move it to another card with Enter |
|
|
111
|
+
| Delete or Backspace | A card or a connection | Ask to remove what is selected, with `helix-remove` |
|
|
112
|
+
| Control or Command with C, V | The board | Copy the selected cards as JSON Canvas, and paste a document |
|
|
113
|
+
| Control or Command with Z | The board | Undo the last move or resize. With Shift, or Control with Y, make it again |
|
|
114
|
+
| Plus, minus | The board, a card, or a connection | Zoom in or out |
|
|
115
|
+
| Escape | A card or a connection | Stop connecting, or go out to the board |
|
|
116
|
+
| Arrow keys | The board, after Escape | Pan the board |
|
|
117
|
+
|
|
118
|
+
Every drag also works with clicks. Click a selected card's move handle, a
|
|
119
|
+
corner, a card's connection point, or the end of a selected connection to pick
|
|
120
|
+
it up, then click where it goes. Escape puts it back.
|
|
121
|
+
|
|
122
|
+
Screen readers meet these roles and names:
|
|
123
|
+
|
|
124
|
+
| Part | Role | Named by |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| The board | `group`, described as "canvas" | `label` |
|
|
127
|
+
| A card | `article` | Its content, or its `label` |
|
|
128
|
+
| A group card | `group` | Its `label` |
|
|
129
|
+
| A connection | `button` | "From Draft to Ship", from `connection-label` |
|
|
130
|
+
| The controls | `group` | "Board controls", from `controls-label` |
|
|
131
|
+
| What changed | `status` | The board's own words |
|
|
132
|
+
|
|
133
|
+
A card is also described by its connections, such as "leads to Ship", and by
|
|
134
|
+
whether it is selected and what its tone means, from `tone-labels`. After a key
|
|
135
|
+
moves, resizes, selects, copies, pastes, undoes, or zooms, screen readers hear
|
|
136
|
+
what changed, such as "Ship moved to 16, 200". Each sentence is a `-label`
|
|
137
|
+
property, so you can write it in your page's language, or register the words
|
|
138
|
+
for a language with `registerWords` (see [Localization](./localization)).
|
|
139
|
+
|
|
140
|
+
When your page removes the card that has focus, focus moves to the next stop,
|
|
141
|
+
or to the one before when it was the last.
|
package/content/graphs.mdx
CHANGED
|
@@ -430,9 +430,16 @@ leaves removing it to your framework.
|
|
|
430
430
|
whose name starts with it, and Enter or Space selects a note.
|
|
431
431
|
- In the search field, Down moves into the list, and Escape empties the search.
|
|
432
432
|
With `no-panel`, Escape on an empty search closes the list.
|
|
433
|
+
- The legend is a toolbar and one tab stop. Arrow keys, Home, and End move
|
|
434
|
+
between its groups, typing a letter moves to the next group whose name
|
|
435
|
+
starts with it, and Space or Enter shows or hides a group.
|
|
433
436
|
- Screen readers read the selected note first, then how each other note links
|
|
434
437
|
to it, such as "Linked from Roadmap". They hear the count when a search or
|
|
435
438
|
the legend changes it.
|
|
439
|
+
- When you select a note, in the list or in the drawing, screen readers hear
|
|
440
|
+
its name and how many notes the list holds, such as "Roadmap, selected. 4
|
|
441
|
+
notes", from `selection-label`. A selection your page writes to `active` is
|
|
442
|
+
not said.
|
|
436
443
|
- After the arrow keys, plus, minus, or the buttons move the drawing, screen
|
|
437
444
|
readers hear which way it went, such as "Moved right" or "Zoomed in".
|
|
438
445
|
`connectGraph` tells the frame once the drawing has moved. If you answer
|
|
@@ -441,6 +448,7 @@ leaves removing it to your framework.
|
|
|
441
448
|
- When you change `nodes` or `links`, the list keeps the note the keys are on.
|
|
442
449
|
When that note is removed, the keys move to the note that took its row.
|
|
443
450
|
- Change the words with `search-label`, `count-label`, `selected-label`,
|
|
451
|
+
`selection-label`,
|
|
444
452
|
`links-to-label`, `linked-from-label`, `linked-both-label`,
|
|
445
453
|
`moved-up-label`, `moved-down-label`, `moved-left-label`,
|
|
446
454
|
`moved-right-label`, `zoomed-in-label`, and `zoomed-out-label`, or register
|
package/content/maps.mdx
CHANGED
|
@@ -642,25 +642,166 @@ compass sets `--map-needle-turn` to the angle that points north, so an icon in
|
|
|
642
642
|
|
|
643
643
|
## Write a driver
|
|
644
644
|
|
|
645
|
-
To use another renderer, write a driver: an object
|
|
645
|
+
To use another renderer, write a driver: an object that moves the renderer's
|
|
646
|
+
view when the frame asks, and tells the frame when the view moved. `connectMap`
|
|
647
|
+
calls it. `mapLibreDriver` and `leafletDriver` are drivers, and the `MapDriver`
|
|
648
|
+
type in `@fusion.dev/helix-map` describes every method.
|
|
646
649
|
|
|
647
|
-
|
|
648
|
-
- `zoomBy(direction, motion)` zooms in for `1` and out for `-1`.
|
|
650
|
+
### Units
|
|
649
651
|
|
|
650
|
-
|
|
651
|
-
animation. Each optional method turns on a feature:
|
|
652
|
-
|
|
653
|
-
| Method | Turns on |
|
|
652
|
+
| Quantity | Unit |
|
|
654
653
|
|---|---|
|
|
655
|
-
|
|
|
656
|
-
|
|
|
657
|
-
|
|
|
658
|
-
|
|
|
659
|
-
|
|
|
660
|
-
| `
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
654
|
+
| A place | `{ lat, lng }` in degrees |
|
|
655
|
+
| A position on screen | CSS pixels from the top left of the renderer's box, right and down positive |
|
|
656
|
+
| Bearing | Degrees clockwise from north |
|
|
657
|
+
| Tilt | Degrees from looking straight down, `0` being flat |
|
|
658
|
+
| Zoom | The renderer's own levels |
|
|
659
|
+
| Motion | `{ duration, easing }`, where `duration` is in milliseconds and `easing(t)` maps 0 to 1 onto 0 to 1, or `null` |
|
|
660
|
+
|
|
661
|
+
`motion` comes from your theme's motion tokens. It is `null` when the user has
|
|
662
|
+
asked for reduced motion, and the driver then moves at once.
|
|
663
|
+
|
|
664
|
+
### Required methods
|
|
665
|
+
|
|
666
|
+
- `panBy(x, y, motion)` moves the view by `x` and `y` CSS pixels. The frame
|
|
667
|
+
asks for a fraction of its own box, so an arrow key moves the map by the same
|
|
668
|
+
share of the screen at every zoom. On a right-to-left page, `connectMap`
|
|
669
|
+
turns the frame's start and end into left and right before it calls the
|
|
670
|
+
driver.
|
|
671
|
+
- `zoomBy(direction, motion)` zooms one step in, for `1`, or out, for `-1`. A
|
|
672
|
+
step is whatever the renderer calls one, usually one level.
|
|
673
|
+
|
|
674
|
+
### Optional methods
|
|
675
|
+
|
|
676
|
+
Each optional method turns on a feature. Without it, the feature is off and
|
|
677
|
+
nothing fails.
|
|
678
|
+
|
|
679
|
+
| Method | When `connectMap` calls it | What it must do | Turns on |
|
|
680
|
+
|---|---|---|---|
|
|
681
|
+
| `onView(moved)` | Once, at connect | Call `moved` on every frame the view moves, and return a function that stops | Markers that follow the map, and the announcements after a move |
|
|
682
|
+
| `project(place)` | On every view change, for each marker | Return where `place` falls, in CSS pixels | Placing markers and positions. Without it, markers stay hidden |
|
|
683
|
+
| `zoom()` | After each view change | Return the zoom | Saying "Zoom level 14" after a zoom |
|
|
684
|
+
| `bearing()`, `pitch()` | After each view change | Return the bearing and the tilt | The compass needle, the 3D button's pressed state, and arrows that stay correct on a turned map |
|
|
685
|
+
| `rotate({ by, to }, motion)` | On the rotate buttons, Face north, and Shift with Left or Right | Turn by `by` degrees, or to the bearing `to` | Rotating |
|
|
686
|
+
| `tilt({ by, to }, motion)` | On the 3D button, and Shift with Up or Down | Tilt by `by` degrees, or to `to` | Tilting |
|
|
687
|
+
| `show(place, motion)` | When the keyboard reaches a marker that is off screen | Bring `place` to the middle of the view, at `place.zoom` where it has one | Walking markers off screen |
|
|
688
|
+
| `fit(bounds, padding, motion)` | On `connection.fit()`, and on a press on a cluster | Frame the `northEast` and `southWest` corners, inset by `padding` pixels | `connection.fit()` and zooming in to clusters |
|
|
689
|
+
| `silenceKeyboard()` | Once, at connect | Turn off the renderer's own keys, and return a function that turns them back on | One set of keys |
|
|
690
|
+
| `focusTarget()` | Once, at connect | Return the element the renderer made focusable | One tab stop. `connectMap` sets its `tabindex` to `-1` and puts it back on disconnect |
|
|
691
|
+
| `replaceZoom()` | Once the frame has drawn its zoom buttons | Remove the renderer's zoom buttons, and return a function that puts them back | One set of zoom buttons |
|
|
692
|
+
| `credits(changed)` | Once, at connect | Return `read()`, the credits as markup, and `stop()`; call `changed` when they change | The map credit in the frame's footer |
|
|
693
|
+
| `ready(done)` | Once, at connect | Call `done` when the view is drawn whole, and return a function that stops waiting | `helix-map-ready` and `:state(ready)` |
|
|
694
|
+
| `gestures(mode)` | When the frame's `gestures` changes | Take the wheel and touches as `free`, `cooperative`, or `off` says | Sharing the scroll with the page |
|
|
695
|
+
| `layers(read, events)` | Once, at connect | Draw `read()`, and draw it again on `update()` | Routes, areas, points, and tile layers |
|
|
696
|
+
| `labels(shown)` | When the map type changes | Show or hide the renderer's own roads and names | The Satellite map type |
|
|
697
|
+
| `previews(ask)` | When the layers menu opens after the view moved | Resolve to an image URL for each map type | Pictures in the layers menu |
|
|
698
|
+
| `behind(place)` | With `project` | Return `true` for a place on the far side of a globe | Hiding markers behind a globe |
|
|
699
|
+
|
|
700
|
+
`connection.disconnect()` calls every function a method returned, gives the
|
|
701
|
+
renderer its gestures back with `gestures('free')`, takes the markers off their
|
|
702
|
+
places, puts the focus target's `tabindex` back as it was, and stops listening
|
|
703
|
+
to the frame. Call it before you remove the renderer.
|
|
704
|
+
|
|
705
|
+
### How the frame says a move
|
|
706
|
+
|
|
707
|
+
The frame's keys and buttons only ask. The frame says "Moved left" or "Zoom
|
|
708
|
+
level 14" after the driver calls the `moved` it gave to `onView`, and once the
|
|
709
|
+
view has been still for a moment. A driver without `onView` never makes the
|
|
710
|
+
frame speak, so the frame never describes a move that did not happen.
|
|
711
|
+
|
|
712
|
+
### Example
|
|
713
|
+
|
|
714
|
+
This driver shows a plan drawn as one image, such as a floor plan, whose edges
|
|
715
|
+
are at known latitudes and longitudes. It moves the image with a CSS transform,
|
|
716
|
+
places markers, and animates over the page's motion. The same driver runs in
|
|
717
|
+
our specs, so this example stays correct.
|
|
718
|
+
|
|
719
|
+
```js
|
|
720
|
+
import { connectMap } from '@fusion.dev/helix-map'
|
|
721
|
+
|
|
722
|
+
const planDriver = (image, plan) => {
|
|
723
|
+
const view = { scale: 1, x: 0, y: 0 }
|
|
724
|
+
const listeners = new Set()
|
|
725
|
+
let moving = 0
|
|
726
|
+
|
|
727
|
+
const draw = () => {
|
|
728
|
+
image.style.transformOrigin = '0 0'
|
|
729
|
+
image.style.transform = `translate(${view.x}px, ${view.y}px) scale(${view.scale})`
|
|
730
|
+
for (const moved of listeners) {
|
|
731
|
+
moved()
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Moves the view to `next` over the page's motion, or at once where it is null.
|
|
736
|
+
const go = (next, motion) => {
|
|
737
|
+
const run = ++moving
|
|
738
|
+
if (!motion || motion.duration <= 0) {
|
|
739
|
+
Object.assign(view, next)
|
|
740
|
+
draw()
|
|
741
|
+
return
|
|
742
|
+
}
|
|
743
|
+
const from = { ...view }
|
|
744
|
+
const start = performance.now()
|
|
745
|
+
const step = (now) => {
|
|
746
|
+
if (run !== moving) {
|
|
747
|
+
return
|
|
748
|
+
}
|
|
749
|
+
const t = Math.min(1, (now - start) / motion.duration)
|
|
750
|
+
const k = motion.easing(t)
|
|
751
|
+
view.x = from.x + (next.x - from.x) * k
|
|
752
|
+
view.y = from.y + (next.y - from.y) * k
|
|
753
|
+
view.scale = from.scale + (next.scale - from.scale) * k
|
|
754
|
+
draw()
|
|
755
|
+
if (t < 1) {
|
|
756
|
+
requestAnimationFrame(step)
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
requestAnimationFrame(step)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
onView: (moved) => {
|
|
764
|
+
listeners.add(moved)
|
|
765
|
+
return () => listeners.delete(moved)
|
|
766
|
+
},
|
|
767
|
+
panBy: (x, y, motion) => go({ ...view, x: view.x - x, y: view.y - y }, motion),
|
|
768
|
+
project: ({ lat, lng }) => ({
|
|
769
|
+
x: view.x + ((lng - plan.west) / (plan.east - plan.west)) * plan.width * view.scale,
|
|
770
|
+
y: view.y + ((plan.north - lat) / (plan.north - plan.south)) * plan.height * view.scale,
|
|
771
|
+
}),
|
|
772
|
+
zoom: () => Math.log2(view.scale),
|
|
773
|
+
zoomBy: (direction, motion) => {
|
|
774
|
+
const box = image.parentElement.getBoundingClientRect()
|
|
775
|
+
const k = direction > 0 ? 2 : 0.5
|
|
776
|
+
const [cx, cy] = [box.width / 2, box.height / 2]
|
|
777
|
+
go({ scale: view.scale * k, x: cx - (cx - view.x) * k, y: cy - (cy - view.y) * k }, motion)
|
|
778
|
+
},
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const frame = document.querySelector('helix-map')
|
|
783
|
+
const connection = connectMap(
|
|
784
|
+
frame,
|
|
785
|
+
planDriver(frame.querySelector('img'), {
|
|
786
|
+
east: 0.004,
|
|
787
|
+
height: 600,
|
|
788
|
+
north: 51.502,
|
|
789
|
+
south: 51.5,
|
|
790
|
+
west: 0,
|
|
791
|
+
width: 800,
|
|
792
|
+
}),
|
|
793
|
+
)
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
`width` and `height` are the image's size in CSS pixels at zoom `0`. To animate
|
|
797
|
+
a move your page makes itself, read the same motion with
|
|
798
|
+
`motionOf(element, pace)`.
|
|
799
|
+
|
|
800
|
+
### Test a driver
|
|
801
|
+
|
|
802
|
+
Our specs test each driver twice. A stand-in renderer records what the driver
|
|
803
|
+
asks it, which runs anywhere. Then the browser specs connect a frame to the real
|
|
804
|
+
MapLibre and Leaflet with a style that loads nothing, press the frame's keys and
|
|
805
|
+
buttons, and check that the view moved and that the frame said so. Test your
|
|
806
|
+
driver the same way: connect it to a frame, press a key on the frame's `region`
|
|
807
|
+
part, and wait for the `live` part to say the move.
|