@tremolo-ui/dom 0.5.0 → 0.6.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/dist/index.cjs +459 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +234 -39
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +234 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +457 -102
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/canvas/animation.ts +49 -31
- package/src/canvas/context.ts +32 -5
- package/src/index.ts +13 -2
- package/src/midi/access.ts +83 -13
- package/src/midi/input.ts +97 -26
- package/src/midi/message.ts +37 -8
- package/src/piano/{input.ts → index.ts} +23 -3
- package/src/pointer/{dragValue.ts → drag-value.ts} +155 -19
- package/src/pointer/drag.ts +215 -20
- package/src/pointer/wheel.ts +4 -2
- package/src/selection/box.ts +136 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { XY } from '../xy'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A rectangle in the 0..1 space of whatever the box is drawn over, with `y`
|
|
5
|
+
* growing downwards — the same space the items are given in.
|
|
6
|
+
*/
|
|
7
|
+
export interface SelectionBoxRect {
|
|
8
|
+
x: number
|
|
9
|
+
y: number
|
|
10
|
+
width: number
|
|
11
|
+
height: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SelectionBoxOptions<Id> {
|
|
15
|
+
/**
|
|
16
|
+
* Everything the box can pick up, read on every move rather than taken as a
|
|
17
|
+
* snapshot: what is under the box changes while it is dragged, and an item
|
|
18
|
+
* may have moved since the last one.
|
|
19
|
+
*/
|
|
20
|
+
items: () => Iterable<readonly [Id, XY<number>]>
|
|
21
|
+
/** The box as it is dragged, and `null` once it is gone. */
|
|
22
|
+
onBoxChange?: (box: SelectionBoxRect | null) => void
|
|
23
|
+
/** What the box covers, added to whatever it started from. */
|
|
24
|
+
onSelectionChange?: (ids: Id[]) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SelectionBoxBeginOptions<Id> {
|
|
28
|
+
/**
|
|
29
|
+
* Add to `selection` rather than replacing it. Ctrl / meta rather than
|
|
30
|
+
* shift, for the controls where shift is the fine-adjustment key.
|
|
31
|
+
*/
|
|
32
|
+
additive?: boolean
|
|
33
|
+
/** What was selected before the box started. Only read when `additive`. */
|
|
34
|
+
selection?: readonly Id[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SelectionBoxInstance<Id> {
|
|
38
|
+
/** Replace the given options, without interrupting a box in progress. */
|
|
39
|
+
update: (options: Partial<SelectionBoxOptions<Id>>) => void
|
|
40
|
+
|
|
41
|
+
/** Start a box at `at`, which is one of its corners. */
|
|
42
|
+
begin: (at: XY<number>, options?: SelectionBoxBeginOptions<Id>) => void
|
|
43
|
+
/** Drag the opposite corner to `to`. */
|
|
44
|
+
move: (to: XY<number>) => void
|
|
45
|
+
/**
|
|
46
|
+
* Finish the box. The selection stays as it is.
|
|
47
|
+
*
|
|
48
|
+
* @returns whether a box was running, so that a caller can tell a drag from
|
|
49
|
+
* a press that selected nothing.
|
|
50
|
+
*/
|
|
51
|
+
end: () => boolean
|
|
52
|
+
|
|
53
|
+
/** The box being dragged, or `null` when there is none. */
|
|
54
|
+
box: () => SelectionBoxRect | null
|
|
55
|
+
|
|
56
|
+
destroy: () => void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rectOf(from: XY<number>, to: XY<number>): SelectionBoxRect {
|
|
60
|
+
return {
|
|
61
|
+
x: Math.min(from[0], to[0]),
|
|
62
|
+
y: Math.min(from[1], to[1]),
|
|
63
|
+
width: Math.abs(to[0] - from[0]),
|
|
64
|
+
height: Math.abs(to[1] - from[1]),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Whether the box covers the point, edges included. */
|
|
69
|
+
export function selectionBoxCovers(
|
|
70
|
+
box: SelectionBoxRect,
|
|
71
|
+
[x, y]: XY<number>,
|
|
72
|
+
): boolean {
|
|
73
|
+
return (
|
|
74
|
+
x >= box.x &&
|
|
75
|
+
x <= box.x + box.width &&
|
|
76
|
+
y >= box.y &&
|
|
77
|
+
y <= box.y + box.height
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Selecting by dragging a box over a set of items.
|
|
83
|
+
*
|
|
84
|
+
* The drag itself is not here: the caller owns the pointer, and reports where
|
|
85
|
+
* it went in the same 0..1 space the items are given in.
|
|
86
|
+
*/
|
|
87
|
+
export function createSelectionBox<Id>(
|
|
88
|
+
options: SelectionBoxOptions<Id>,
|
|
89
|
+
): SelectionBoxInstance<Id> {
|
|
90
|
+
let opts = options
|
|
91
|
+
let drag: { from: XY<number>; base: readonly Id[] } | null = null
|
|
92
|
+
let box: SelectionBoxRect | null = null
|
|
93
|
+
|
|
94
|
+
function setBox(next: SelectionBoxRect | null) {
|
|
95
|
+
box = next
|
|
96
|
+
opts.onBoxChange?.(next)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function apply(rect: SelectionBoxRect, base: readonly Id[]) {
|
|
100
|
+
const inside: Id[] = []
|
|
101
|
+
for (const [id, at] of opts.items()) {
|
|
102
|
+
if (selectionBoxCovers(rect, at) && !base.includes(id)) inside.push(id)
|
|
103
|
+
}
|
|
104
|
+
opts.onSelectionChange?.([...base, ...inside])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
update: (next) => {
|
|
109
|
+
opts = { ...opts, ...next }
|
|
110
|
+
},
|
|
111
|
+
begin: (at, { additive = false, selection = [] } = {}) => {
|
|
112
|
+
drag = { from: at, base: additive ? [...selection] : [] }
|
|
113
|
+
setBox(rectOf(at, at))
|
|
114
|
+
// A plain press is a deselect of its own, so that a drag that picks
|
|
115
|
+
// nothing up leaves nothing selected.
|
|
116
|
+
if (!additive) opts.onSelectionChange?.([])
|
|
117
|
+
},
|
|
118
|
+
move: (to) => {
|
|
119
|
+
if (!drag) return
|
|
120
|
+
const rect = rectOf(drag.from, to)
|
|
121
|
+
setBox(rect)
|
|
122
|
+
apply(rect, drag.base)
|
|
123
|
+
},
|
|
124
|
+
end: () => {
|
|
125
|
+
const running = drag !== null
|
|
126
|
+
drag = null
|
|
127
|
+
if (box !== null) setBox(null)
|
|
128
|
+
return running
|
|
129
|
+
},
|
|
130
|
+
box: () => box,
|
|
131
|
+
destroy: () => {
|
|
132
|
+
drag = null
|
|
133
|
+
box = null
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
}
|