solid-pianoroll 0.0.2 → 0.0.4

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/cjs/index.js CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  var web = require('solid-js/web');
4
4
  var solidJs = require('solid-js');
5
+ var store = require('solid-js/store');
6
+
7
+ function styleInject(css, ref) {
8
+ if (ref === void 0) ref = {};
9
+ var insertAt = ref.insertAt;
10
+ if (!css || typeof document === 'undefined') {
11
+ return;
12
+ }
13
+ var head = document.head || document.getElementsByTagName('head')[0];
14
+ var style = document.createElement('style');
15
+ style.type = 'text/css';
16
+ if (insertAt === 'top') {
17
+ if (head.firstChild) {
18
+ head.insertBefore(style, head.firstChild);
19
+ } else {
20
+ head.appendChild(style);
21
+ }
22
+ } else {
23
+ head.appendChild(style);
24
+ }
25
+ if (style.styleSheet) {
26
+ style.styleSheet.cssText = css;
27
+ } else {
28
+ style.appendChild(document.createTextNode(css));
29
+ }
30
+ }
31
+
32
+ var css_248z$1 = ".PianoRoll-module_PianoRoll__rkRYx {\n box-sizing: border-box;\n display: flex;\n overflow: hidden;\n flex-direction: column;\n padding: 16px;\n}\n\n.PianoRoll-module_PianoRollContainer__Oyxr7 {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n\n.PianoRoll-module_PianoRollInnerContainer__9wdIQ {\n position: relative;\n display: flex;\n overflow: hidden;\n flex: 1;\n}\n";
33
+ var styles$1 = {"PianoRoll":"PianoRoll-module_PianoRoll__rkRYx","PianoRollContainer":"PianoRoll-module_PianoRollContainer__Oyxr7","PianoRollInnerContainer":"PianoRoll-module_PianoRollInnerContainer__9wdIQ"};
34
+ styleInject(css_248z$1);
35
+
36
+ const defaultRect = {
37
+ left: 0,
38
+ width: 0,
39
+ top: 0,
40
+ height: 0,
41
+ bottom: 0,
42
+ right: 0,
43
+ x: 0,
44
+ y: 0
45
+ };
46
+ function useBoundingClientRect(containerRef) {
47
+ const [boundingClientRect, setBoundingClientRect] = solidJs.createSignal(defaultRect);
48
+ solidJs.createEffect(() => {
49
+ const container = containerRef();
50
+ if (!container) return;
51
+ const updateBoundingClientRect = () => {
52
+ setBoundingClientRect(container.getBoundingClientRect() ?? defaultRect);
53
+ };
54
+ const resizeObserver = new ResizeObserver(updateBoundingClientRect);
55
+ resizeObserver.observe(container);
56
+ updateBoundingClientRect();
57
+ return () => {
58
+ resizeObserver.disconnect();
59
+ };
60
+ });
61
+ return boundingClientRect;
62
+ }
5
63
 
6
64
  const clamp = (value, min, max) => {
7
65
  return Math.max(min, Math.min(max, value));
@@ -40,308 +98,355 @@ function useViewPortScaler(getState) {
40
98
  };
41
99
  }
42
100
 
43
- const _tmpl$ = /*#__PURE__*/web.template(`<div><div><div class="PianoRoll-Keys"></div><div><div class="PianoRoll-Notes-Container"><div class="PianoRoll-Notes"></div></div><div class="PianoRoll-Vertical-Scroller"><div></div></div></div><input type="range" min="1" max="11" step="0.01"></div><input type="range" max="500" min="1" step="0.01"></div>`, 18),
44
- _tmpl$2 = /*#__PURE__*/web.template(`<div class="PianoRoll-Key"></div>`, 2),
45
- _tmpl$3 = /*#__PURE__*/web.template(`<div class="PianoRoll-Note"></div>`, 2);
46
- const PianoRoll = allProps => {
47
- let scrollContainerRef;
48
- let scrollContentRef;
49
- let notesContainerRef;
50
- const [props, divProps] = solidJs.splitProps(allProps, ["ppq", "notes", "position", "duration", "zoom", "verticalPosition", "verticalZoom", "onVerticalZoomChange", "onVerticalPositionChange", "onZoomChange", "onPositionChange", "onNoteChange"]);
51
- const [dimensions, setDimensions] = solidJs.createSignal({
52
- left: 0,
53
- width: 0,
54
- top: 0,
55
- height: 0
56
- });
57
- const resizeObserver = new ResizeObserver(event => {
58
- const {
59
- left,
60
- top,
61
- width,
62
- height
63
- } = scrollContainerRef?.getBoundingClientRect() ?? {
64
- left: 0,
65
- width: 0,
66
- top: 0,
67
- height: 0
68
- };
69
- setDimensions({
70
- left,
71
- top,
72
- width,
73
- height
74
- });
75
- });
76
- solidJs.createEffect(() => {
77
- console.log(props.notes);
78
- });
79
- solidJs.onMount(() => {
80
- if (!scrollContainerRef) return;
81
- resizeObserver.observe(scrollContainerRef);
82
- });
83
- solidJs.onCleanup(() => {
84
- resizeObserver.disconnect();
85
- });
101
+ const PianoRollContext = solidJs.createContext(undefined);
102
+ const PianoRollContextProvider = props => {
103
+ const [noteDragMode, setNoteDragMode] = solidJs.createSignal();
104
+ const [isDragging, setIsDragging] = solidJs.createSignal(false);
105
+ const [_ownProps, contextProps] = solidJs.splitProps(props, ["children", "scrollContainer"]);
106
+ const clientRect = useBoundingClientRect(() => props.scrollContainer);
86
107
  const horizontalViewPort = useViewPortScaler(() => ({
87
- viewPortOffset: dimensions().left,
88
- viewPortSize: dimensions().width,
108
+ viewPortOffset: clientRect().left,
109
+ viewPortSize: clientRect().width,
89
110
  virtualPosition: props.position,
90
111
  virtualRange: props.duration,
91
112
  zoom: props.zoom
92
113
  }));
93
114
  const verticalViewPort = useViewPortScaler(() => ({
94
- viewPortOffset: dimensions().top,
95
- viewPortSize: dimensions().height,
115
+ viewPortOffset: clientRect().top,
116
+ viewPortSize: clientRect().height,
96
117
  virtualPosition: props.verticalPosition,
97
- virtualRange: 128,
98
- zoom: props.verticalZoom
118
+ zoom: props.verticalZoom,
119
+ virtualRange: 128
99
120
  }));
100
- const handleScroll = event => {
101
- event.preventDefault();
102
- if (didUpdateScroll) {
103
- didUpdateScroll = false;
104
- return;
105
- }
106
- const maxVerticalPosition = 128 - 128 / props.verticalZoom;
107
- const maxPosition = props.duration - props.duration / props.zoom;
108
- const {
109
- width,
110
- height
111
- } = dimensions();
112
- const {
113
- scrollTop,
114
- scrollLeft,
115
- scrollWidth,
116
- scrollHeight
117
- } = event.currentTarget;
118
- const scrollTopAmount = scrollTop / (scrollHeight - height);
119
- const scrollLeftAmount = scrollLeft / (scrollWidth - width);
120
- props.onVerticalPositionChange?.(maxVerticalPosition * scrollTopAmount);
121
- props.onPositionChange?.(maxPosition * scrollLeftAmount);
121
+ const getContextValue = () => {
122
+ return {
123
+ ...contextProps,
124
+ horizontalViewPort,
125
+ verticalViewPort,
126
+ clientRect: clientRect(),
127
+ isDragging: isDragging(),
128
+ onIsDraggingChange: setIsDragging,
129
+ noteDragMode: noteDragMode(),
130
+ onNoteDragModeChange: setNoteDragMode
131
+ };
122
132
  };
123
- let didUpdateScroll = false;
133
+ const [contextValue, setContextValue] = store.createStore(getContextValue());
124
134
  solidJs.createEffect(() => {
125
- const maxVerticalPosition = 128 - 128 / props.verticalZoom;
126
- const maxPosition = props.duration - props.duration / props.zoom;
127
- const scrollTopAmount = maxVerticalPosition > 0 ? props.verticalPosition / maxVerticalPosition : 0;
128
- const scrollLeftAmount = maxPosition > 0 ? props.position / maxPosition : 0;
129
- const {
130
- height,
131
- width
132
- } = dimensions();
133
- if (!scrollContentRef?.parentElement) return;
134
- const scrollDivHeight = clamp(props.verticalZoom * height, height, 10000);
135
- const scrollTop = scrollTopAmount * (scrollDivHeight - height);
136
- const scrollDivWidth = clamp(props.zoom * width, width, 10000);
137
- const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
138
- didUpdateScroll = true;
139
- scrollContentRef.style.height = `${scrollDivHeight}px`;
140
- scrollContentRef.style.width = `${scrollDivWidth}px`;
141
- scrollContentRef.parentElement.scrollTo({
142
- left: scrollLeft,
143
- top: scrollTop
144
- });
135
+ setContextValue(getContextValue());
145
136
  });
146
- const forwardEventToNote = event => {
147
- const x = "clientX" in event ? event.clientX : event.touches[0]?.clientX;
148
- const y = "clientY" in event ? event.clientY : event.touches[0]?.clientY;
149
- const elementUnderMouse = [...(notesContainerRef?.querySelectorAll?.("div") ?? [])].find(element => {
150
- const rect = element.getBoundingClientRect();
151
- return x >= rect.left && rect.left + rect.width > x && y >= rect.top && rect.top + rect.height > y;
152
- });
153
- if (elementUnderMouse) {
154
- return elementUnderMouse.dispatchEvent(new MouseEvent(event.type, event));
137
+ return web.createComponent(PianoRollContext.Provider, {
138
+ value: contextValue,
139
+ get children() {
140
+ return props.children;
155
141
  }
156
- };
157
- const [noteDragMode, setNoteDragMode] = solidJs.createSignal();
158
- const [isDragging, setIsDragging] = solidJs.createSignal(false);
142
+ });
143
+ };
144
+ const usePianoRollContext = () => {
145
+ const context = solidJs.useContext(PianoRollContext);
146
+ if (!context) throw new Error("No PianoRollContext found");
147
+ return context;
148
+ };
149
+
150
+ const _tmpl$$6 = /*#__PURE__*/web.template(`<input type="range" max="500" min="1" step="0.01">`, 1);
151
+ const HorizontalZoomControl = () => {
152
+ const context = usePianoRollContext();
159
153
  return (() => {
160
- const _el$ = _tmpl$.cloneNode(true),
161
- _el$2 = _el$.firstChild,
162
- _el$3 = _el$2.firstChild,
163
- _el$4 = _el$3.nextSibling,
164
- _el$5 = _el$4.firstChild,
165
- _el$6 = _el$5.firstChild,
166
- _el$7 = _el$5.nextSibling,
167
- _el$8 = _el$7.firstChild,
168
- _el$9 = _el$4.nextSibling,
169
- _el$10 = _el$2.nextSibling;
170
- web.spread(_el$, web.mergeProps(divProps, {
171
- "class": "PianoRoll",
172
- get style() {
173
- return {
174
- "box-sizing": "border-box",
175
- display: "flex",
176
- overflow: "hidden",
177
- "flex-direction": "column",
178
- padding: "16px",
179
- ...(typeof divProps.style === "object" && divProps.style)
180
- };
181
- }
182
- }), false, true);
183
- _el$2.style.setProperty("overflow", "hidden");
184
- _el$2.style.setProperty("height", "100%");
185
- _el$2.style.setProperty("display", "flex");
186
- _el$2.style.setProperty("flex-direction", "row");
187
- _el$3.style.setProperty("position", "relative");
188
- _el$3.style.setProperty("height", "100%");
189
- _el$3.style.setProperty("width", "50px");
190
- web.insert(_el$3, web.createComponent(solidJs.Index, {
154
+ const _el$ = _tmpl$$6.cloneNode(true);
155
+ _el$.$$input = event => context.onZoomChange?.(event.currentTarget.valueAsNumber);
156
+ _el$.style.setProperty("margin-left", "50px");
157
+ _el$.style.setProperty("margin-right", "16px");
158
+ web.effect(() => _el$.value = context.zoom);
159
+ return _el$;
160
+ })();
161
+ };
162
+ web.delegateEvents(["input"]);
163
+
164
+ var css_248z = ".PianoRollKeys-module_PianoRollKeys__5vnQ0 {\n position: relative;\n height: 100%;\n width: 50px;\n}\n\n.PianoRollKeys-module_PianoRollKeys-Key__xunQt {\n position: absolute;\n box-sizing: border-box;\n left: 0;\n width: 100%;\n border-color: #000;\n border-style: solid;\n}\n";
165
+ var styles = {"PianoRollKeys":"PianoRollKeys-module_PianoRollKeys__5vnQ0","PianoRollKeys-Key":"PianoRollKeys-module_PianoRollKeys-Key__xunQt"};
166
+ styleInject(css_248z);
167
+
168
+ const _tmpl$$5 = /*#__PURE__*/web.template(`<div></div>`, 2);
169
+ const PianoRollKeys = () => {
170
+ const context = usePianoRollContext();
171
+ return (() => {
172
+ const _el$ = _tmpl$$5.cloneNode(true);
173
+ web.insert(_el$, web.createComponent(solidJs.Index, {
191
174
  each: keys,
192
175
  children: key => {
193
- const virtualDimensions = solidJs.createMemo(() => verticalViewPort.getVirtualDimensions(127 - key().number, 1));
176
+ const virtualDimensions = solidJs.createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - key().number, 1));
194
177
  return web.createComponent(solidJs.Show, {
195
178
  get when() {
196
179
  return virtualDimensions().size > 0;
197
180
  },
198
181
  get children() {
199
- const _el$11 = _tmpl$2.cloneNode(true);
200
- _el$11.style.setProperty("position", "absolute");
201
- _el$11.style.setProperty("box-sizing", "border-box");
202
- _el$11.style.setProperty("left", "0");
203
- _el$11.style.setProperty("width", "100%");
204
- _el$11.style.setProperty("border-color", "#000");
205
- _el$11.style.setProperty("border-style", "solid");
182
+ const _el$2 = _tmpl$$5.cloneNode(true);
206
183
  web.effect(_p$ => {
207
- const _v$5 = key().name,
208
- _v$6 = key().number % 12,
209
- _v$7 = `${virtualDimensions().offset}px`,
210
- _v$8 = `${virtualDimensions().size}px`,
211
- _v$9 = key().isBlack ? "#000" : "#fff",
212
- _v$10 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
213
- _v$5 !== _p$._v$5 && web.setAttribute(_el$11, "title", _p$._v$5 = _v$5);
214
- _v$6 !== _p$._v$6 && web.setAttribute(_el$11, "data-index", _p$._v$6 = _v$6);
215
- _v$7 !== _p$._v$7 && _el$11.style.setProperty("top", _p$._v$7 = _v$7);
216
- _v$8 !== _p$._v$8 && _el$11.style.setProperty("height", _p$._v$8 = _v$8);
217
- _v$9 !== _p$._v$9 && _el$11.style.setProperty("background-color", _p$._v$9 = _v$9);
218
- _v$10 !== _p$._v$10 && _el$11.style.setProperty("border-width", _p$._v$10 = _v$10);
184
+ const _v$ = styles["PianoRollKeys-Key"],
185
+ _v$2 = key().name,
186
+ _v$3 = key().number % 12,
187
+ _v$4 = `${virtualDimensions().offset}px`,
188
+ _v$5 = `${virtualDimensions().size}px`,
189
+ _v$6 = key().isBlack ? "#000" : "#fff",
190
+ _v$7 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
191
+ _v$ !== _p$._v$ && web.className(_el$2, _p$._v$ = _v$);
192
+ _v$2 !== _p$._v$2 && web.setAttribute(_el$2, "title", _p$._v$2 = _v$2);
193
+ _v$3 !== _p$._v$3 && web.setAttribute(_el$2, "data-index", _p$._v$3 = _v$3);
194
+ _v$4 !== _p$._v$4 && _el$2.style.setProperty("top", _p$._v$4 = _v$4);
195
+ _v$5 !== _p$._v$5 && _el$2.style.setProperty("height", _p$._v$5 = _v$5);
196
+ _v$6 !== _p$._v$6 && _el$2.style.setProperty("background-color", _p$._v$6 = _v$6);
197
+ _v$7 !== _p$._v$7 && _el$2.style.setProperty("border-width", _p$._v$7 = _v$7);
219
198
  return _p$;
220
199
  }, {
200
+ _v$: undefined,
201
+ _v$2: undefined,
202
+ _v$3: undefined,
203
+ _v$4: undefined,
221
204
  _v$5: undefined,
222
205
  _v$6: undefined,
223
- _v$7: undefined,
224
- _v$8: undefined,
225
- _v$9: undefined,
226
- _v$10: undefined
206
+ _v$7: undefined
227
207
  });
228
- return _el$11;
208
+ return _el$2;
229
209
  }
230
210
  });
231
211
  }
232
212
  }));
233
- _el$4.style.setProperty("position", "relative");
234
- _el$4.style.setProperty("display", "flex");
235
- _el$4.style.setProperty("overflow", "hidden");
236
- _el$4.style.setProperty("flex", "1");
237
- _el$5.style.setProperty("position", "absolute");
238
- const _ref$ = notesContainerRef;
239
- typeof _ref$ === "function" ? web.use(_ref$, _el$6) : notesContainerRef = _el$6;
240
- _el$6.style.setProperty("position", "relative");
241
- _el$6.style.setProperty("height", "100%");
242
- web.insert(_el$6, web.createComponent(solidJs.Index, {
213
+ web.effect(() => web.className(_el$, styles.PianoRollKeys));
214
+ return _el$;
215
+ })();
216
+ };
217
+ const blackKeys = [1, 3, 6, 8, 10];
218
+ const keyNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
219
+ const keys = Array.from({
220
+ length: 128
221
+ }).map((_, index) => ({
222
+ number: index,
223
+ name: `${keyNames[index % 12]} ${Math.floor(index / 12) - 2}`,
224
+ isBlack: blackKeys.includes(index % 12)
225
+ }));
226
+
227
+ const _tmpl$$4 = /*#__PURE__*/web.template(`<div class="PianoRoll-Notes-Container"><div class="PianoRoll-Notes"></div></div>`, 4),
228
+ _tmpl$2$1 = /*#__PURE__*/web.template(`<div class="PianoRoll-Note"></div>`, 2);
229
+ const PianoRollNotes = props => {
230
+ const context = usePianoRollContext();
231
+ const gridDivisionTicks = solidJs.createMemo(() => context.ppq * 4 / context.gridDivision);
232
+ const snapValueToGridIfEnabled = (value, altKey) => context.snapToGrid && !altKey ? Math.round(value / gridDivisionTicks()) * gridDivisionTicks() : value;
233
+ return (() => {
234
+ const _el$ = _tmpl$$4.cloneNode(true),
235
+ _el$2 = _el$.firstChild;
236
+ _el$.style.setProperty("position", "absolute");
237
+ const _ref$ = props.ref;
238
+ typeof _ref$ === "function" ? web.use(_ref$, _el$2) : props.ref = _el$2;
239
+ _el$2.style.setProperty("position", "relative");
240
+ _el$2.style.setProperty("height", "100%");
241
+ web.insert(_el$2, web.createComponent(solidJs.Index, {
243
242
  get each() {
244
- return props.notes;
243
+ return context.notes;
245
244
  },
246
245
  children: (note, index) => {
247
- const horizontalVirtualDimensions = solidJs.createMemo(() => verticalViewPort.getVirtualDimensions(127 - note().midi, 1));
248
- const verticalVirtualDimensions = solidJs.createMemo(() => horizontalViewPort.getVirtualDimensions(note().ticks, note().durationTicks));
246
+ const horizontalVirtualDimensions = solidJs.createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - note().midi, 1));
247
+ const verticalVirtualDimensions = solidJs.createMemo(() => context.horizontalViewPort.getVirtualDimensions(note().ticks, note().durationTicks));
249
248
  return web.createComponent(solidJs.Show, {
250
249
  get when() {
251
250
  return web.memo(() => !!!!horizontalVirtualDimensions().size)() && !!verticalVirtualDimensions().size;
252
251
  },
253
252
  get children() {
254
- const _el$12 = _tmpl$3.cloneNode(true);
255
- _el$12.$$mousedown = event => {
256
- setIsDragging(true);
257
- const initialPosition = horizontalViewPort.getPosition(event.clientX);
253
+ const _el$3 = _tmpl$2$1.cloneNode(true);
254
+ _el$3.$$mousedown = event => {
255
+ context.onIsDraggingChange(true);
256
+ const initialPosition = context.horizontalViewPort.getPosition(event.clientX);
258
257
  const diffPosition = initialPosition - note().ticks;
259
258
  const handleMouseMove = mouseMoveEvent => {
260
- const ticks = Math.max(horizontalViewPort.getPosition(mouseMoveEvent.clientX) - diffPosition, 0);
261
- props.onNoteChange?.(index, {
259
+ const ticks = snapValueToGridIfEnabled(Math.max(context.horizontalViewPort.getPosition(mouseMoveEvent.clientX) - diffPosition, 0), mouseMoveEvent.altKey);
260
+ context.onNoteChange?.(index, {
262
261
  ...note(),
263
- midi: Math.round(127 - verticalViewPort.getPosition(mouseMoveEvent.clientY)),
264
- ...((noteDragMode() === "move" || noteDragMode() === "trimStart") && {
262
+ ...(context.noteDragMode === "move" && {
263
+ midi: Math.round(127 - context.verticalViewPort.getPosition(mouseMoveEvent.clientY))
264
+ }),
265
+ ...((context.noteDragMode === "move" || context.noteDragMode === "trimStart") && {
265
266
  ticks
266
267
  }),
267
- ...(noteDragMode() === "trimStart" && {
268
+ ...(context.noteDragMode === "trimStart" && {
268
269
  durationTicks: note().durationTicks + note().ticks - ticks
269
270
  }),
270
- ...(noteDragMode() === "trimEnd" && {
271
- durationTicks: horizontalViewPort.getPosition(mouseMoveEvent.clientX) - note().ticks
271
+ ...(context.noteDragMode === "trimEnd" && {
272
+ durationTicks: snapValueToGridIfEnabled(context.horizontalViewPort.getPosition(mouseMoveEvent.clientX) - note().ticks, mouseMoveEvent.altKey)
272
273
  })
273
274
  });
274
275
  };
275
276
  const handleMouseUp = () => {
276
- setIsDragging(false);
277
+ context.onIsDraggingChange(false);
277
278
  window.removeEventListener("mousemove", handleMouseMove);
278
279
  window.removeEventListener("mouseup", handleMouseUp);
279
280
  };
280
281
  window.addEventListener("mousemove", handleMouseMove);
281
282
  window.addEventListener("mouseup", handleMouseUp);
282
283
  };
283
- _el$12.$$mousemove = event => {
284
- if (isDragging()) return;
285
- const relativeX = horizontalViewPort.getScaledValue(horizontalViewPort.getPosition(event.clientX));
286
- const noteStartX = horizontalViewPort.getScaledValue(note().ticks);
287
- const noteEndX = horizontalViewPort.getScaledValue(note().ticks + note().durationTicks);
288
- setNoteDragMode(relativeX - noteStartX < 3 ? "trimStart" : noteEndX - relativeX < 3 ? "trimEnd" : "move");
284
+ _el$3.$$mousemove = event => {
285
+ if (context.isDragging) return;
286
+ const relativeX = context.horizontalViewPort.getScaledValue(context.horizontalViewPort.getPosition(event.clientX));
287
+ const noteStartX = context.horizontalViewPort.getScaledValue(note().ticks);
288
+ const noteEndX = context.horizontalViewPort.getScaledValue(note().ticks + note().durationTicks);
289
+ context.onNoteDragModeChange(relativeX - noteStartX < 3 ? "trimStart" : noteEndX - relativeX < 3 ? "trimEnd" : "move");
289
290
  };
290
- _el$12.style.setProperty("z-index", "1");
291
- _el$12.style.setProperty("position", "absolute");
292
- _el$12.style.setProperty("box-sizing", "border-box");
293
- _el$12.style.setProperty("border-width", "0.5px");
294
- _el$12.style.setProperty("border-style", "solid");
295
- _el$12.style.setProperty("background-color", "#f00");
296
- _el$12.style.setProperty("border-color", "#a00");
291
+ _el$3.style.setProperty("z-index", "2");
292
+ _el$3.style.setProperty("position", "absolute");
293
+ _el$3.style.setProperty("box-sizing", "border-box");
294
+ _el$3.style.setProperty("border-width", "0.5px");
295
+ _el$3.style.setProperty("border-style", "solid");
296
+ _el$3.style.setProperty("border-color", "#a00");
297
297
  web.effect(_p$ => {
298
- const _v$11 = `${horizontalVirtualDimensions().offset}px`,
299
- _v$12 = `${horizontalVirtualDimensions().size}px`,
300
- _v$13 = `${verticalVirtualDimensions().offset}px`,
301
- _v$14 = `${verticalVirtualDimensions().size}px`;
302
- _v$11 !== _p$._v$11 && _el$12.style.setProperty("top", _p$._v$11 = _v$11);
303
- _v$12 !== _p$._v$12 && _el$12.style.setProperty("height", _p$._v$12 = _v$12);
304
- _v$13 !== _p$._v$13 && _el$12.style.setProperty("left", _p$._v$13 = _v$13);
305
- _v$14 !== _p$._v$14 && _el$12.style.setProperty("width", _p$._v$14 = _v$14);
298
+ const _v$4 = `${horizontalVirtualDimensions().offset}px`,
299
+ _v$5 = `${horizontalVirtualDimensions().size}px`,
300
+ _v$6 = `${verticalVirtualDimensions().offset}px`,
301
+ _v$7 = `${verticalVirtualDimensions().size}px`,
302
+ _v$8 = `rgba(255,0,0, ${(128 + note().velocity) / 256})`;
303
+ _v$4 !== _p$._v$4 && _el$3.style.setProperty("top", _p$._v$4 = _v$4);
304
+ _v$5 !== _p$._v$5 && _el$3.style.setProperty("height", _p$._v$5 = _v$5);
305
+ _v$6 !== _p$._v$6 && _el$3.style.setProperty("left", _p$._v$6 = _v$6);
306
+ _v$7 !== _p$._v$7 && _el$3.style.setProperty("width", _p$._v$7 = _v$7);
307
+ _v$8 !== _p$._v$8 && _el$3.style.setProperty("background-color", _p$._v$8 = _v$8);
306
308
  return _p$;
307
309
  }, {
308
- _v$11: undefined,
309
- _v$12: undefined,
310
- _v$13: undefined,
311
- _v$14: undefined
310
+ _v$4: undefined,
311
+ _v$5: undefined,
312
+ _v$6: undefined,
313
+ _v$7: undefined,
314
+ _v$8: undefined
312
315
  });
313
- return _el$12;
316
+ return _el$3;
314
317
  }
315
318
  });
316
319
  }
317
320
  }));
318
- _el$7.addEventListener("dragend", forwardEventToNote);
319
- _el$7.addEventListener("drag", forwardEventToNote);
320
- _el$7.addEventListener("dragstart", forwardEventToNote);
321
- _el$7.addEventListener("touchcancel", forwardEventToNote);
322
- _el$7.$$touchend = forwardEventToNote;
323
- _el$7.$$touchmove = forwardEventToNote;
324
- _el$7.$$touchstart = forwardEventToNote;
325
- _el$7.$$mouseup = forwardEventToNote;
326
- _el$7.$$dblclick = event => {
321
+ web.effect(_p$ => {
322
+ const _v$ = `${context.clientRect.width}px`,
323
+ _v$2 = `${context.clientRect.height}px`,
324
+ _v$3 = `${context.clientRect.width}px`;
325
+ _v$ !== _p$._v$ && _el$.style.setProperty("width", _p$._v$ = _v$);
326
+ _v$2 !== _p$._v$2 && _el$.style.setProperty("height", _p$._v$2 = _v$2);
327
+ _v$3 !== _p$._v$3 && _el$2.style.setProperty("width", _p$._v$3 = _v$3);
328
+ return _p$;
329
+ }, {
330
+ _v$: undefined,
331
+ _v$2: undefined,
332
+ _v$3: undefined
333
+ });
334
+ return _el$;
335
+ })();
336
+ };
337
+ web.delegateEvents(["mousemove", "mousedown"]);
338
+
339
+ const _tmpl$$3 = /*#__PURE__*/web.template(`<div class="PianoRoll-Vertical-Scroller"><div></div></div>`, 4);
340
+ const ScrollContainer = props => {
341
+ let scrollContentRef;
342
+ const context = usePianoRollContext();
343
+ const forwardEventToNote = event => {
344
+ const x = "clientX" in event ? event.clientX : event.touches[0]?.clientX;
345
+ const y = "clientY" in event ? event.clientY : event.touches[0]?.clientY;
346
+ const elementUnderMouse = [...(context.notesContainer?.querySelectorAll?.("div") ?? [])].find(element => {
347
+ const rect = element.getBoundingClientRect();
348
+ return x >= rect.left && rect.left + rect.width > x && y >= rect.top && rect.top + rect.height > y;
349
+ });
350
+ if (elementUnderMouse) {
351
+ return elementUnderMouse.dispatchEvent(new MouseEvent(event.type, event));
352
+ }
353
+ };
354
+ const handleScroll = event => {
355
+ event.preventDefault();
356
+ if (didUpdateScroll) {
357
+ didUpdateScroll = false;
358
+ return;
359
+ }
360
+ const maxVerticalPosition = 128 - 128 / context.verticalZoom;
361
+ const maxPosition = context.duration - context.duration / context.zoom;
362
+ const {
363
+ width,
364
+ height
365
+ } = context.clientRect;
366
+ const {
367
+ scrollTop,
368
+ scrollLeft,
369
+ scrollWidth,
370
+ scrollHeight
371
+ } = event.currentTarget;
372
+ const scrollTopAmount = scrollTop / (scrollHeight - height);
373
+ const scrollLeftAmount = scrollLeft / (scrollWidth - width);
374
+ context.onVerticalPositionChange?.(maxVerticalPosition * scrollTopAmount);
375
+ context.onPositionChange?.(maxPosition * scrollLeftAmount);
376
+ };
377
+ let didUpdateScroll = false;
378
+ solidJs.createEffect(() => {
379
+ const maxVerticalPosition = 128 - 128 / context.verticalZoom;
380
+ const maxPosition = context.duration - context.duration / context.zoom;
381
+ const scrollTopAmount = maxVerticalPosition > 0 ? context.verticalPosition / maxVerticalPosition : 0;
382
+ const scrollLeftAmount = maxPosition > 0 ? context.position / maxPosition : 0;
383
+ const {
384
+ height,
385
+ width
386
+ } = context.clientRect;
387
+ if (!scrollContentRef?.parentElement) return;
388
+ const scrollDivHeight = clamp(context.verticalZoom * height, height, 10000);
389
+ const scrollTop = scrollTopAmount * (scrollDivHeight - height);
390
+ const scrollDivWidth = clamp(context.zoom * width, width, 10000);
391
+ const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
392
+ didUpdateScroll = true;
393
+ scrollContentRef.style.height = `${scrollDivHeight}px`;
394
+ scrollContentRef.style.width = `${scrollDivWidth}px`;
395
+ scrollContentRef.parentElement.scrollTo({
396
+ left: scrollLeft,
397
+ top: scrollTop
398
+ });
399
+ });
400
+ return (() => {
401
+ const _el$ = _tmpl$$3.cloneNode(true),
402
+ _el$2 = _el$.firstChild;
403
+ _el$.addEventListener("dragend", forwardEventToNote);
404
+ _el$.addEventListener("drag", forwardEventToNote);
405
+ _el$.addEventListener("dragstart", forwardEventToNote);
406
+ _el$.addEventListener("touchcancel", forwardEventToNote);
407
+ _el$.$$touchend = forwardEventToNote;
408
+ _el$.$$touchmove = forwardEventToNote;
409
+ _el$.$$touchstart = forwardEventToNote;
410
+ _el$.$$mouseup = forwardEventToNote;
411
+ _el$.$$dblclick = event => {
327
412
  if (!forwardEventToNote(event)) ;
328
413
  };
329
- _el$7.$$click = forwardEventToNote;
330
- _el$7.$$mousemove = event => {
414
+ _el$.$$click = forwardEventToNote;
415
+ _el$.$$mousemove = event => {
331
416
  if (forwardEventToNote(event)) return;
332
- if (isDragging()) return;
333
- setNoteDragMode(undefined);
417
+ if (context.isDragging) return;
418
+ context.onNoteDragModeChange(undefined);
334
419
  };
335
- _el$7.$$mousedown = forwardEventToNote;
336
- _el$7.addEventListener("scroll", handleScroll);
337
- const _ref$2 = scrollContainerRef;
338
- typeof _ref$2 === "function" ? web.use(_ref$2, _el$7) : scrollContainerRef = _el$7;
339
- const _ref$3 = scrollContentRef;
340
- typeof _ref$3 === "function" ? web.use(_ref$3, _el$8) : scrollContentRef = _el$8;
341
- _el$9.$$input = event => props.onVerticalZoomChange?.(event.currentTarget.valueAsNumber);
342
- web.spread(_el$9, web.mergeProps({
420
+ _el$.$$mousedown = forwardEventToNote;
421
+ _el$.addEventListener("scroll", handleScroll);
422
+ const _ref$ = props.ref;
423
+ typeof _ref$ === "function" ? web.use(_ref$, _el$) : props.ref = _el$;
424
+ const _ref$2 = scrollContentRef;
425
+ typeof _ref$2 === "function" ? web.use(_ref$2, _el$2) : scrollContentRef = _el$2;
426
+ web.effect(_$p => web.style(_el$, {
427
+ flex: 1,
428
+ ...(context.noteDragMode && {
429
+ cursor: context.noteDragMode === "trimStart" ? "w-resize" : context.noteDragMode === "trimEnd" ? "e-resize" : "pointer"
430
+ }),
431
+ //"box-sizing": "border-box",
432
+ "z-index": 4,
433
+ overflow: "scroll",
434
+ "pointer-events": "auto"
435
+ }, _$p));
436
+ return _el$;
437
+ })();
438
+ };
439
+ web.delegateEvents(["mousedown", "mousemove", "click", "dblclick", "mouseup", "touchstart", "touchmove", "touchend"]);
440
+
441
+ const _tmpl$$2 = /*#__PURE__*/web.template(`<input type="range" min="1" max="11" step="0.01">`, 1);
442
+ const VerticalZoomControl = () => {
443
+ const context = usePianoRollContext();
444
+ return (() => {
445
+ const _el$ = _tmpl$$2.cloneNode(true);
446
+ _el$.$$input = event => context.onVerticalZoomChange?.(event.currentTarget.valueAsNumber);
447
+ web.spread(_el$, web.mergeProps({
343
448
  get value() {
344
- return props.verticalZoom;
449
+ return context.verticalZoom;
345
450
  }
346
451
  }, {
347
452
  orient: "vertical"
@@ -356,48 +461,161 @@ const PianoRoll = allProps => {
356
461
  };
357
462
  }
358
463
  }), false, false);
359
- _el$10.$$input = event => props.onZoomChange?.(event.currentTarget.valueAsNumber);
360
- _el$10.style.setProperty("margin-left", "50px");
361
- _el$10.style.setProperty("margin-right", "16px");
464
+ return _el$;
465
+ })();
466
+ };
467
+ web.delegateEvents(["input"]);
468
+
469
+ const _tmpl$$1 = /*#__PURE__*/web.template(`<div class="PianoRoll-Grid-Container"><div class="PianoRoll-Grid"></div></div>`, 4),
470
+ _tmpl$2 = /*#__PURE__*/web.template(`<div class="PianoRoll-Grid-Time"></div>`, 2),
471
+ _tmpl$3 = /*#__PURE__*/web.template(`<div class="PianoRoll-Grid-Key"></div>`, 2);
472
+ const PianoRollGrid = () => {
473
+ const context = usePianoRollContext();
474
+ const gridDivisorTicks = solidJs.createMemo(() => context.ppq * 4 / context.gridDivision);
475
+ const gridArray = solidJs.createMemo(() => {
476
+ const numberOfLines = context.duration / gridDivisorTicks();
477
+ return Array.from({
478
+ length: numberOfLines
479
+ }).map((_, index) => index * gridDivisorTicks());
480
+ });
481
+ return (() => {
482
+ const _el$ = _tmpl$$1.cloneNode(true),
483
+ _el$2 = _el$.firstChild;
484
+ _el$.style.setProperty("position", "absolute");
485
+ _el$2.style.setProperty("position", "relative");
486
+ _el$2.style.setProperty("height", "100%");
487
+ web.insert(_el$2, web.createComponent(solidJs.Index, {
488
+ get each() {
489
+ return gridArray();
490
+ },
491
+ children: (tick, index) => {
492
+ const virtualDimensions = solidJs.createMemo(() => context.horizontalViewPort.getVirtualDimensions(tick(), gridDivisorTicks()));
493
+ return web.createComponent(solidJs.Show, {
494
+ get when() {
495
+ return virtualDimensions().size > 0;
496
+ },
497
+ get children() {
498
+ const _el$3 = _tmpl$2.cloneNode(true);
499
+ _el$3.style.setProperty("z-index", "1");
500
+ _el$3.style.setProperty("position", "absolute");
501
+ _el$3.style.setProperty("box-sizing", "border-box");
502
+ _el$3.style.setProperty("background", index % 4 === 0 ? "#ccc" : "#ddd");
503
+ _el$3.style.setProperty("top", "0px");
504
+ _el$3.style.setProperty("height", "100%");
505
+ _el$3.style.setProperty("border-left", "1px gray solid");
506
+ web.effect(_p$ => {
507
+ const _v$4 = `${virtualDimensions().offset}px`,
508
+ _v$5 = `${virtualDimensions().size}px`;
509
+ _v$4 !== _p$._v$4 && _el$3.style.setProperty("left", _p$._v$4 = _v$4);
510
+ _v$5 !== _p$._v$5 && _el$3.style.setProperty("width", _p$._v$5 = _v$5);
511
+ return _p$;
512
+ }, {
513
+ _v$4: undefined,
514
+ _v$5: undefined
515
+ });
516
+ return _el$3;
517
+ }
518
+ });
519
+ }
520
+ }), null);
521
+ web.insert(_el$2, web.createComponent(solidJs.Index, {
522
+ each: keys,
523
+ children: key => {
524
+ const virtualDimensions = solidJs.createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - key().number, 1));
525
+ return web.createComponent(solidJs.Show, {
526
+ get when() {
527
+ return virtualDimensions().size > 0;
528
+ },
529
+ get children() {
530
+ const _el$4 = _tmpl$3.cloneNode(true);
531
+ _el$4.style.setProperty("position", "absolute");
532
+ _el$4.style.setProperty("width", "100%");
533
+ _el$4.style.setProperty("border-style", "solid");
534
+ _el$4.style.setProperty("border-color", "gray");
535
+ _el$4.style.setProperty("z-index", "1");
536
+ web.effect(_p$ => {
537
+ const _v$6 = `${virtualDimensions().offset}px`,
538
+ _v$7 = `${virtualDimensions().size}px`,
539
+ _v$8 = key().isBlack ? "rgba(0,0,0,0.2)" : "none",
540
+ _v$9 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
541
+ _v$6 !== _p$._v$6 && _el$4.style.setProperty("top", _p$._v$6 = _v$6);
542
+ _v$7 !== _p$._v$7 && _el$4.style.setProperty("height", _p$._v$7 = _v$7);
543
+ _v$8 !== _p$._v$8 && _el$4.style.setProperty("background-color", _p$._v$8 = _v$8);
544
+ _v$9 !== _p$._v$9 && _el$4.style.setProperty("border-width", _p$._v$9 = _v$9);
545
+ return _p$;
546
+ }, {
547
+ _v$6: undefined,
548
+ _v$7: undefined,
549
+ _v$8: undefined,
550
+ _v$9: undefined
551
+ });
552
+ return _el$4;
553
+ }
554
+ });
555
+ }
556
+ }), null);
362
557
  web.effect(_p$ => {
363
- const _v$ = `${dimensions().width}px`,
364
- _v$2 = `${dimensions().height}px`,
365
- _v$3 = `${dimensions().width}px`,
366
- _v$4 = {
367
- flex: 1,
368
- ...(noteDragMode() && {
369
- cursor: noteDragMode() === "trimStart" ? "w-resize" : noteDragMode() === "trimEnd" ? "e-resize" : "pointer"
370
- }),
371
- //"box-sizing": "border-box",
372
- "z-index": 1,
373
- overflow: "scroll",
374
- "pointer-events": "auto"
375
- };
376
- _v$ !== _p$._v$ && _el$5.style.setProperty("width", _p$._v$ = _v$);
377
- _v$2 !== _p$._v$2 && _el$5.style.setProperty("height", _p$._v$2 = _v$2);
378
- _v$3 !== _p$._v$3 && _el$6.style.setProperty("width", _p$._v$3 = _v$3);
379
- _p$._v$4 = web.style(_el$7, _v$4, _p$._v$4);
558
+ const _v$ = `${context.clientRect.width}px`,
559
+ _v$2 = `${context.clientRect.height}px`,
560
+ _v$3 = `${context.clientRect.width}px`;
561
+ _v$ !== _p$._v$ && _el$.style.setProperty("width", _p$._v$ = _v$);
562
+ _v$2 !== _p$._v$2 && _el$.style.setProperty("height", _p$._v$2 = _v$2);
563
+ _v$3 !== _p$._v$3 && _el$2.style.setProperty("width", _p$._v$3 = _v$3);
380
564
  return _p$;
381
565
  }, {
382
566
  _v$: undefined,
383
567
  _v$2: undefined,
384
- _v$3: undefined,
385
- _v$4: undefined
568
+ _v$3: undefined
386
569
  });
387
- web.effect(() => _el$10.value = props.zoom);
388
570
  return _el$;
389
571
  })();
390
572
  };
391
- const blackKeys = [1, 3, 6, 8, 10];
392
- const keyNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
393
- const keys = Array.from({
394
- length: 128
395
- }).map((_, index) => ({
396
- number: index,
397
- name: `${keyNames[index % 12]} ${Math.floor(index / 12) - 2}`,
398
- isBlack: blackKeys.includes(index % 12)
399
- }));
400
- web.delegateEvents(["mousedown", "mousemove", "click", "dblclick", "mouseup", "touchstart", "touchmove", "touchend", "input"]);
573
+
574
+ const _tmpl$ = /*#__PURE__*/web.template(`<div><div><div></div></div></div>`, 6);
575
+ const PianoRoll = allProps => {
576
+ const [contextProps, divProps] = solidJs.splitProps(allProps, ["ppq", "notes", "position", "duration", "zoom", "verticalPosition", "verticalZoom", "onVerticalZoomChange", "onVerticalPositionChange", "onZoomChange", "onPositionChange", "onNoteChange", "gridDivision", "snapToGrid"]);
577
+ const [scrollContainer, setScrollContainer] = solidJs.createSignal();
578
+ const [notesContainer, setNotesContainer] = solidJs.createSignal();
579
+ return web.createComponent(PianoRollContextProvider, web.mergeProps(contextProps, {
580
+ get scrollContainer() {
581
+ return scrollContainer();
582
+ },
583
+ get notesContainer() {
584
+ return notesContainer();
585
+ },
586
+ get children() {
587
+ const _el$ = _tmpl$.cloneNode(true),
588
+ _el$2 = _el$.firstChild,
589
+ _el$3 = _el$2.firstChild;
590
+ web.spread(_el$, web.mergeProps(divProps, {
591
+ get ["class"]() {
592
+ return styles$1.PianoRoll;
593
+ }
594
+ }), false, true);
595
+ web.insert(_el$2, web.createComponent(PianoRollKeys, {}), _el$3);
596
+ web.insert(_el$3, web.createComponent(PianoRollNotes, {
597
+ ref: setNotesContainer
598
+ }), null);
599
+ web.insert(_el$3, web.createComponent(PianoRollGrid, {}), null);
600
+ web.insert(_el$3, web.createComponent(ScrollContainer, {
601
+ ref: setScrollContainer
602
+ }), null);
603
+ web.insert(_el$2, web.createComponent(VerticalZoomControl, {}), null);
604
+ web.insert(_el$, web.createComponent(HorizontalZoomControl, {}), null);
605
+ web.effect(_p$ => {
606
+ const _v$ = styles$1.PianoRollContainer,
607
+ _v$2 = styles$1.PianoRollInnerContainer;
608
+ _v$ !== _p$._v$ && web.className(_el$2, _p$._v$ = _v$);
609
+ _v$2 !== _p$._v$2 && web.className(_el$3, _p$._v$2 = _v$2);
610
+ return _p$;
611
+ }, {
612
+ _v$: undefined,
613
+ _v$2: undefined
614
+ });
615
+ return _el$;
616
+ }
617
+ }));
618
+ };
401
619
 
402
620
  exports.PianoRoll = PianoRoll;
403
621
  //# sourceMappingURL=index.js.map