@tremolo-ui/react 0.1.6 → 0.2.1

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.
Files changed (68) hide show
  1. package/dist/index.cjs +1329 -1152
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +238 -183
  4. package/dist/index.d.cts +367 -366
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.ts +367 -366
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1313 -1125
  9. package/dist/index.js.map +1 -1
  10. package/package.json +17 -19
  11. package/src/components/AnimationCanvas/index.tsx +0 -7
  12. package/src/components/DragObserver/index.tsx +9 -7
  13. package/src/components/Knob/ActiveLine.tsx +29 -0
  14. package/src/components/Knob/InactiveLine.tsx +53 -0
  15. package/src/components/Knob/SVGRoot.tsx +39 -0
  16. package/src/components/Knob/Thumb.tsx +64 -0
  17. package/src/components/Knob/context.tsx +120 -0
  18. package/src/components/Knob/index.css +25 -27
  19. package/src/components/Knob/index.tsx +110 -164
  20. package/src/components/NumberInput/DecrementStepper.tsx +0 -2
  21. package/src/components/NumberInput/IncrementStepper.tsx +0 -2
  22. package/src/components/NumberInput/InternalInput.tsx +3 -1
  23. package/src/components/NumberInput/Stepper.tsx +0 -2
  24. package/src/components/NumberInput/context.tsx +0 -1
  25. package/src/components/NumberInput/index.css +31 -15
  26. package/src/components/NumberInput/index.tsx +21 -14
  27. package/src/components/Piano/KeyLabel.tsx +0 -2
  28. package/src/components/Piano/context.tsx +0 -2
  29. package/src/components/Piano/index.css +7 -14
  30. package/src/components/Piano/index.tsx +14 -15
  31. package/src/components/Piano/key.tsx +0 -10
  32. package/src/components/Piano/keyboardShortcuts.ts +0 -2
  33. package/src/components/PointsEditor/Background.tsx +17 -0
  34. package/src/components/PointsEditor/Container.tsx +26 -0
  35. package/src/components/PointsEditor/Point.tsx +137 -0
  36. package/src/components/PointsEditor/context.tsx +91 -0
  37. package/src/components/PointsEditor/index.css +30 -0
  38. package/src/components/PointsEditor/index.tsx +129 -0
  39. package/src/components/Slider/Scale.tsx +0 -2
  40. package/src/components/Slider/ScaleOption.tsx +0 -2
  41. package/src/components/Slider/Thumb.tsx +1 -5
  42. package/src/components/Slider/Track.tsx +1 -3
  43. package/src/components/Slider/context.tsx +0 -1
  44. package/src/components/Slider/index.css +33 -8
  45. package/src/components/Slider/index.tsx +61 -33
  46. package/src/components/WheelObserver/index.tsx +17 -19
  47. package/src/components/XYPad/Area.tsx +4 -5
  48. package/src/components/XYPad/Thumb.tsx +8 -7
  49. package/src/components/XYPad/index.css +18 -4
  50. package/src/components/XYPad/index.tsx +71 -38
  51. package/src/components/_util/composeRefs.tsx +63 -0
  52. package/src/components/_util/index.ts +23 -6
  53. package/src/components/_util/type.ts +1 -0
  54. package/src/hooks/useAnimationFrame.ts +0 -3
  55. package/src/hooks/useCallbackRef.ts +2 -2
  56. package/src/hooks/useDrag.ts +12 -11
  57. package/src/hooks/useDragWithElement.ts +12 -17
  58. package/src/hooks/useEventListener.ts +6 -9
  59. package/src/hooks/useInterval.ts +0 -3
  60. package/src/hooks/useLongPress.ts +0 -3
  61. package/src/hooks/useMIDIAccess.ts +0 -1
  62. package/src/hooks/useMIDIInput.ts +0 -1
  63. package/src/hooks/useMIDIMessage.ts +0 -1
  64. package/src/hooks/usePianoDrag.ts +75 -0
  65. package/src/index.ts +21 -30
  66. package/src/styles/global.css +16 -4
  67. package/dist/index.css.map +0 -1
  68. package/src/components/AnimationKnob/index.tsx +0 -314
@@ -0,0 +1,63 @@
1
+ // ref:
2
+ // https://github.com/radix-ui/primitives/blob/2bab24a811e45c7a83198659272097f6cfa5a165/packages/react/compose-refs/src/compose-refs.tsx
3
+
4
+ import { useCallback } from 'react'
5
+
6
+ type PossibleRef<T> = React.Ref<T> | undefined
7
+
8
+ /**
9
+ * Set a given ref to a given value
10
+ * This utility takes care of different types of refs: callback refs and RefObject(s)
11
+ */
12
+ function setRef<T>(ref: PossibleRef<T>, value: T) {
13
+ if (typeof ref === 'function') {
14
+ return ref(value)
15
+ } else if (ref !== null && ref !== undefined) {
16
+ ref.current = value
17
+ }
18
+ }
19
+
20
+ /**
21
+ * A utility to compose multiple refs together
22
+ * Accepts callback refs and RefObject(s)
23
+ */
24
+ function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
25
+ return (node) => {
26
+ let hasCleanup = false
27
+ const cleanups = refs.map((ref) => {
28
+ const cleanup = setRef(ref, node)
29
+ if (!hasCleanup && typeof cleanup == 'function') {
30
+ hasCleanup = true
31
+ }
32
+ return cleanup
33
+ })
34
+
35
+ // React <19 will log an error to the console if a callback ref returns a
36
+ // value. We don't use ref cleanups internally so this will only happen if a
37
+ // user's ref callback returns a value, which we only expect if they are
38
+ // using the cleanup functionality added in React 19.
39
+ if (hasCleanup) {
40
+ return () => {
41
+ for (let i = 0; i < cleanups.length; i++) {
42
+ const cleanup = cleanups[i]
43
+ if (typeof cleanup == 'function') {
44
+ cleanup()
45
+ } else {
46
+ setRef(refs[i], null)
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * A custom hook that composes multiple refs
56
+ * Accepts callback refs and RefObject(s)
57
+ */
58
+ function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ return useCallback(composeRefs(...refs), refs)
61
+ }
62
+
63
+ export { composeRefs, useComposedRefs }
@@ -1,11 +1,28 @@
1
- export function addNoSelect() {
2
- document.body.classList.add('tremolo-no-select')
1
+ export function addUserSelectNone() {
2
+ window.document.body.classList.add('tremolo-user-select-none')
3
3
  }
4
4
 
5
- export function removeNoSelect() {
6
- document.body.classList.remove('tremolo-no-select')
5
+ export function removeUserSelectNone() {
6
+ window.document.body.classList.remove('tremolo-user-select-none')
7
7
  }
8
8
 
9
- export function toggleNoSelect() {
10
- document.body.classList.toggle('tremolo-no-select')
9
+ const cursorStyles = {
10
+ grabbing: 'tremolo-cursor-grabbing',
11
+ grab: 'tremolo-cursor-grab',
12
+ pointer: 'tremolo-cursor-pointer',
13
+ move: 'tremolo-cursor-move',
14
+ none: 'tremolo-cursor-none',
15
+ }
16
+
17
+ export type Cursor = keyof typeof cursorStyles
18
+
19
+ export function setCursorStyle(type: Cursor) {
20
+ resetCursorStyle()
21
+ window.document.body.classList.add(cursorStyles[type])
22
+ }
23
+
24
+ export function resetCursorStyle() {
25
+ Object.values(cursorStyles).forEach((s) => {
26
+ window.document.body.classList.remove(s)
27
+ })
11
28
  }
@@ -0,0 +1 @@
1
+ export type Override<T, U> = T & Omit<U, keyof T>
@@ -1,8 +1,5 @@
1
1
  import { DependencyList, useCallback, useEffect, useRef } from 'react'
2
2
 
3
- /**
4
- * @category hooks
5
- */
6
3
  export function useAnimationFrame(
7
4
  callback = () => {},
8
5
  deps: DependencyList = [],
@@ -1,4 +1,4 @@
1
- import { useCallback, useInsertionEffect, useRef } from 'react'
1
+ import { DependencyList, useCallback, useInsertionEffect, useRef } from 'react'
2
2
 
3
3
  /**
4
4
  * Internal
@@ -6,7 +6,7 @@ import { useCallback, useInsertionEffect, useRef } from 'react'
6
6
  */
7
7
  export function useCallbackRef<Args extends unknown[], Return>(
8
8
  callback: ((...args: Args) => Return) | undefined,
9
- deps: React.DependencyList = [],
9
+ deps: DependencyList = [],
10
10
  ) {
11
11
  const callbackRef = useRef<typeof callback>(() => {
12
12
  throw new Error('Cannot call an event handler while rendering.')
@@ -6,17 +6,16 @@ import { useRefCallbackEvent } from './useRefCallbackEvent'
6
6
  interface UseDragProps {
7
7
  threshold?: number
8
8
 
9
- onDrag: (x: number, y: number, deltaX: number, deltaY: number) => void
9
+ onDrag?: (x: number, y: number, deltaX: number, deltaY: number) => void
10
10
  onDragStart?: () => void
11
11
  onDragEnd?: () => void
12
12
  }
13
13
 
14
14
  /**
15
- * @category hooks
16
15
  * @returns [refCallback, pointerDownHandler]
17
16
  */
18
17
  export function useDrag<T extends Element>({
19
- threshold = 1,
18
+ threshold: _threshold = 1,
20
19
  onDrag,
21
20
  onDragStart,
22
21
  onDragEnd,
@@ -29,6 +28,8 @@ export function useDrag<T extends Element>({
29
28
  const dragStartX = useRef(0)
30
29
  const dragStartY = useRef(0)
31
30
 
31
+ const threshold = Math.max(_threshold, 1)
32
+
32
33
  const handleDrag = useCallback(
33
34
  (
34
35
  event: MouseEvent | React.MouseEvent<T, MouseEvent> | TouchEvent,
@@ -53,7 +54,7 @@ export function useDrag<T extends Element>({
53
54
  dragOffsetY.current = screenY
54
55
  }
55
56
  if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return
56
- onDrag(
57
+ onDrag?.(
57
58
  screenX - dragStartX.current,
58
59
  screenY - dragStartY.current,
59
60
  deltaX,
@@ -63,13 +64,6 @@ export function useDrag<T extends Element>({
63
64
  [threshold, onDrag],
64
65
  )
65
66
 
66
- const refHandler = useRefCallbackEvent(
67
- 'touchmove',
68
- handleDrag,
69
- { passive: false },
70
- [onDrag],
71
- )
72
-
73
67
  const pointerDownHandler = useCallback(
74
68
  (event: React.PointerEvent<T>) => {
75
69
  dragOffsetX.current = event.screenX
@@ -80,6 +74,13 @@ export function useDrag<T extends Element>({
80
74
  [handleDrag, onDragStart],
81
75
  )
82
76
 
77
+ const refHandler = useRefCallbackEvent(
78
+ 'touchmove',
79
+ handleDrag,
80
+ { passive: false },
81
+ [onDrag],
82
+ )
83
+
83
84
  useEventListener(globalThis.window, 'mousemove', handleDrag)
84
85
 
85
86
  useEventListener(globalThis.window, 'pointerup', () => {
@@ -1,37 +1,30 @@
1
- import { useRef, useCallback, RefObject } from 'react'
1
+ import { useRef, useCallback, RefObject, useState } from 'react'
2
2
 
3
3
  import { normalizeValue } from '@tremolo-ui/functions'
4
4
 
5
5
  import { useEventListener } from './useEventListener'
6
6
  import { useRefCallbackEvent } from './useRefCallbackEvent'
7
7
 
8
- interface UseDragWithElementProps<T extends Element> {
8
+ interface UseDragWithElement<T extends Element> {
9
9
  baseElementRef: RefObject<T | null>
10
10
  onDrag: (normalizedX: number, normalizedY: number) => void
11
11
  onDragStart?: (normalizedX: number, normalizedY: number) => void
12
12
  onDragEnd?: (normalizedX: number, normalizedY: number) => void
13
13
  }
14
14
 
15
- /**
16
- * @category hooks
17
- * @returns [refCallback, pointerDownHandler]
18
- */
19
15
  export function useDragWithElement<T extends Element>({
20
16
  baseElementRef,
21
17
  onDrag,
22
18
  onDragStart,
23
19
  onDragEnd,
24
- }: UseDragWithElementProps<T>): [
25
- (div: EventTarget | null) => void,
26
- (event: React.PointerEvent<T>) => void,
27
- ] {
28
- const dragged = useRef(false)
20
+ }: UseDragWithElement<T>) {
21
+ const [dragging, setDragging] = useState(false)
29
22
  const normalizedX = useRef(0)
30
23
  const normalizedY = useRef(0)
31
24
 
32
25
  const handleDrag = useCallback(
33
26
  (event: MouseEvent | React.PointerEvent<T> | TouchEvent) => {
34
- if (!baseElementRef.current || !dragged.current) {
27
+ if (!baseElementRef.current || !dragging) {
35
28
  return
36
29
  }
37
30
  const isTouch = event instanceof TouchEvent
@@ -46,7 +39,7 @@ export function useDragWithElement<T extends Element>({
46
39
  normalizedY.current = ny
47
40
  onDrag(nx, ny)
48
41
  },
49
- [onDrag, baseElementRef],
42
+ [onDrag, baseElementRef, dragging],
50
43
  )
51
44
 
52
45
  const refHandler = useRefCallbackEvent(
@@ -58,7 +51,8 @@ export function useDragWithElement<T extends Element>({
58
51
 
59
52
  const pointerDownHandler = useCallback(
60
53
  (event: React.PointerEvent<T>) => {
61
- dragged.current = true
54
+ // dragging.current = true
55
+ setDragging(true)
62
56
  handleDrag(event)
63
57
  onDragStart?.(normalizedX.current, normalizedY.current)
64
58
  },
@@ -68,10 +62,11 @@ export function useDragWithElement<T extends Element>({
68
62
  useEventListener(globalThis.window, 'pointermove', handleDrag)
69
63
 
70
64
  useEventListener(globalThis.window, 'pointerup', () => {
71
- if (!dragged.current) return
72
- dragged.current = false
65
+ if (!dragging) return
66
+ // dragging.current = false
67
+ setDragging(false)
73
68
  onDragEnd?.(normalizedX.current, normalizedY.current)
74
69
  })
75
70
 
76
- return [refHandler, pointerDownHandler]
71
+ return { refHandler, pointerDownHandler, dragging }
77
72
  }
@@ -5,31 +5,28 @@ import { useCallbackRef } from './useCallbackRef'
5
5
  type Target = EventTarget | null | (() => EventTarget | null)
6
6
  type Options = boolean | AddEventListenerOptions
7
7
 
8
- /**
9
- * @category hooks
10
- */
11
8
  export function useEventListener<K extends keyof DocumentEventMap>(
12
9
  target: Target,
13
10
  event: K,
14
- handler?: (event: DocumentEventMap[K]) => void,
11
+ handler: (event: DocumentEventMap[K]) => void,
15
12
  options?: Options,
16
13
  ): VoidFunction
17
14
  export function useEventListener<K extends keyof WindowEventMap>(
18
15
  target: Target,
19
16
  event: K,
20
- handler?: (event: WindowEventMap[K]) => void,
17
+ handler: (event: WindowEventMap[K]) => void,
21
18
  options?: Options,
22
19
  ): VoidFunction
23
20
  export function useEventListener<K extends keyof GlobalEventHandlersEventMap>(
24
21
  target: Target,
25
22
  event: K,
26
- handler?: (event: GlobalEventHandlersEventMap[K]) => void,
23
+ handler: (event: GlobalEventHandlersEventMap[K]) => void,
27
24
  options?: Options,
28
25
  ): VoidFunction
29
26
  export function useEventListener(
30
27
  target: Target,
31
28
  event: string,
32
- handler: ((event: Event) => void) | undefined,
29
+ handler: (event: Event) => void,
33
30
  options?: Options,
34
31
  ) {
35
32
  const listener = useCallbackRef(handler)
@@ -37,13 +34,13 @@ export function useEventListener(
37
34
  useEffect(() => {
38
35
  const node = typeof target === 'function' ? target() : (target ?? document)
39
36
 
40
- if (!handler || !node) return
37
+ if (!node) return
41
38
 
42
39
  node.addEventListener(event, listener, options)
43
40
  return () => {
44
41
  node.removeEventListener(event, listener, options)
45
42
  }
46
- }, [event, target, options, listener, handler])
43
+ }, [event, target, options, listener])
47
44
 
48
45
  return () => {
49
46
  const node = typeof target === 'function' ? target() : (target ?? document)
@@ -1,8 +1,5 @@
1
1
  import { useEffect, useRef } from 'react'
2
2
 
3
- /**
4
- * @category hooks
5
- */
6
3
  export function useInterval(callback: () => void, delay: number | null) {
7
4
  const savedCallback = useRef(callback)
8
5
 
@@ -3,9 +3,6 @@ import { useCallback, useState } from 'react'
3
3
  import { useEventListener } from './useEventListener'
4
4
  import { useInterval } from './useInterval'
5
5
 
6
- /**
7
- * @category hooks
8
- */
9
6
  export function useLongPress(
10
7
  callback: () => void,
11
8
  initialDelay = 500,
@@ -10,7 +10,6 @@ export type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED
10
10
 
11
11
  /**
12
12
  * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
13
- * @category hooks
14
13
  */
15
14
  export function useMIDIAccess(requestOnMount = true) {
16
15
  const [midiAccess, setMidiAccess] = useState<MIDIAccess | null>(null)
@@ -16,7 +16,6 @@ const MIDI_EVENT_TO_NUMBER = {
16
16
 
17
17
  /**
18
18
  * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
19
- * @category hooks
20
19
  */
21
20
  export function useMIDIInput(
22
21
  midiAccess: MIDIAccess | null,
@@ -2,7 +2,6 @@ import { useEffect } from 'react'
2
2
 
3
3
  /**
4
4
  * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
5
- * @category hooks
6
5
  */
7
6
  export function useMIDIMessage(
8
7
  midiAccess: MIDIAccess | null,
@@ -0,0 +1,75 @@
1
+ import { useRef, useCallback, RefObject } from 'react'
2
+
3
+ import { normalizeValue } from '@tremolo-ui/functions'
4
+
5
+ import { useEventListener } from './useEventListener'
6
+ import { useRefCallbackEvent } from './useRefCallbackEvent'
7
+
8
+ interface UsePianoDrag<T extends Element> {
9
+ baseElementRef: RefObject<T | null>
10
+ onDrag: (normalizedX: number, normalizedY: number) => void
11
+ onDragStart?: (normalizedX: number, normalizedY: number) => void
12
+ onDragEnd?: (normalizedX: number, normalizedY: number) => void
13
+ }
14
+
15
+ /**
16
+ * Internal
17
+ * @returns [refCallback, pointerDownHandler]
18
+ * @private
19
+ */
20
+ export function usePianoDrag<T extends Element>({
21
+ baseElementRef,
22
+ onDrag,
23
+ onDragStart,
24
+ onDragEnd,
25
+ }: UsePianoDrag<T>) {
26
+ const dragged = useRef(false)
27
+ const normalizedX = useRef(0)
28
+ const normalizedY = useRef(0)
29
+
30
+ const handleDrag = useCallback(
31
+ (event: MouseEvent | React.PointerEvent<T> | TouchEvent) => {
32
+ if (!baseElementRef.current || !dragged.current) {
33
+ return
34
+ }
35
+ const isTouch = event instanceof TouchEvent
36
+ if (isTouch && event.cancelable) event.preventDefault()
37
+ const { left, top, right, bottom } =
38
+ baseElementRef.current.getBoundingClientRect()
39
+ const mouseX = isTouch ? event.touches[0].clientX : event.clientX
40
+ const mouseY = isTouch ? event.touches[0].clientY : event.clientY
41
+ const nx = normalizeValue(mouseX, left, right)
42
+ const ny = normalizeValue(mouseY, top, bottom)
43
+ normalizedX.current = nx
44
+ normalizedY.current = ny
45
+ onDrag(nx, ny)
46
+ },
47
+ [onDrag, baseElementRef],
48
+ )
49
+
50
+ const refHandler = useRefCallbackEvent(
51
+ 'touchmove',
52
+ handleDrag,
53
+ { passive: false },
54
+ [onDrag],
55
+ )
56
+
57
+ const pointerDownHandler = useCallback(
58
+ (event: React.PointerEvent<T>) => {
59
+ dragged.current = true
60
+ handleDrag(event)
61
+ onDragStart?.(normalizedX.current, normalizedY.current)
62
+ },
63
+ [handleDrag, onDragStart],
64
+ )
65
+
66
+ useEventListener(globalThis.window, 'pointermove', handleDrag)
67
+
68
+ useEventListener(globalThis.window, 'pointerup', () => {
69
+ if (!dragged.current) return
70
+ dragged.current = false
71
+ onDragEnd?.(normalizedX.current, normalizedY.current)
72
+ })
73
+
74
+ return { refHandler, pointerDownHandler }
75
+ }
package/src/index.ts CHANGED
@@ -3,79 +3,70 @@ import './styles/global.css'
3
3
  import './components/Knob/index.css'
4
4
  import './components/NumberInput/index.css'
5
5
  import './components/Piano/index.css'
6
+ import './components/PointsEditor/index.css'
6
7
  import './components/Slider/index.css'
7
8
  import './components/XYPad/index.css'
8
9
 
9
10
  export {
10
- type AbsoluteSizingProps,
11
11
  AnimationCanvas,
12
+ type AbsoluteSizingProps,
12
13
  type AnimationCanvasProps,
13
14
  type CommonProps,
14
15
  type DrawFunction,
15
16
  type InitFunction,
16
17
  type RelativeSizingProps,
17
18
  } from './components/AnimationCanvas'
18
- export {
19
- AnimationKnob,
20
- type AnimationKnobMethods,
21
- type AnimationKnobProps,
22
- } from './components/AnimationKnob'
23
19
  export { DragObserver, type DragObserverProps } from './components/DragObserver'
24
- export { Knob, type KnobMethods, type KnobProps } from './components/Knob'
20
+ export { Knob, type KnobProps, type KnobMethods } from './components/Knob'
25
21
  export {
26
- DecrementStepper,
27
- type DecrementStepperProps,
28
- IncrementStepper,
29
- type IncrementStepperProps,
30
22
  NumberInput,
31
- type NumberInputMethods,
32
23
  type NumberInputProps,
33
- Stepper,
24
+ type NumberInputMethods,
34
25
  type StepperProps,
26
+ type IncrementStepperProps,
27
+ type DecrementStepperProps,
35
28
  } from './components/NumberInput'
36
29
  export {
37
30
  Piano,
31
+ getNoteRangeArray,
32
+ SHORTCUTS,
38
33
  type PianoProps,
39
34
  type PianoMethods,
40
- getNoteRangeArray,
41
- WhiteKey,
42
- BlackKey,
43
35
  type KeyProps,
44
36
  type KeyMethods,
45
- KeyLabel,
46
37
  type KeyLabelProps,
47
- SHORTCUTS,
48
38
  type KeyboardShortcuts,
49
39
  } from './components/Piano'
50
40
  export {
51
- Scale,
52
- ScaleOption,
41
+ PointsEditor,
42
+ clampPoint,
43
+ type PointsEditorProps,
44
+ type PointProps,
45
+ type PointBaseType,
46
+ } from './components/PointsEditor'
47
+ export {
48
+ Slider,
49
+ useSliderContext,
53
50
  type ScaleOptionProps,
54
51
  type ScaleProps,
55
- Slider,
56
52
  type SliderMethods,
57
53
  type SliderProps,
58
- SliderThumb,
59
54
  type SliderThumbMethods,
60
55
  type SliderThumbProps,
61
- SliderTrack,
62
56
  type SliderTrackProps,
63
- useSliderContext,
64
57
  } from './components/Slider'
65
58
  export {
66
59
  WheelObserver,
67
60
  type WheelObserverProps,
68
61
  } from './components/WheelObserver'
69
62
  export {
70
- type AreaProps,
71
- type ThumbProps,
72
- type ValueOptions,
73
63
  XYPad,
74
- XYPadArea,
75
- type XYPadMethods,
76
64
  type XYPadProps,
77
- XYPadThumb,
65
+ type XYPadMethods,
66
+ type XYPadAreaProps,
67
+ type XYPadThumbProps,
78
68
  type XYPadThumbMethods,
69
+ type ValueOptions,
79
70
  } from './components/XYPad'
80
71
 
81
72
  // hooks
@@ -1,7 +1,19 @@
1
- :root {
2
- --tremolo-theme-color: oklch(59.5% 0.17548 266.373);
1
+ .tremolo-user-select-none {
2
+ user-select: none;
3
3
  }
4
4
 
5
- .tremolo-no-select {
6
- user-select: none;
5
+ .tremolo-cursor-pointer {
6
+ cursor: pointer;
7
+ }
8
+
9
+ .tremolo-cursor-move {
10
+ cursor: move;
11
+ }
12
+
13
+ .tremolo-cursor-grabbing {
14
+ cursor: grabbing;
15
+ }
16
+
17
+ .tremolo-cursor-none {
18
+ cursor: none;
7
19
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.css","names":[],"sources":["../src/styles/global.css","../src/components/Knob/index.css","../src/components/NumberInput/index.css","../src/components/Piano/index.css","../src/components/Slider/index.css","../src/components/XYPad/index.css"],"sourcesContent":[":root {\n --tremolo-theme-color: oklch(59.5% 0.17548 266.373);\n}\n\n.tremolo-no-select {\n user-select: none;\n}\n",".tremolo-knob {\n display: inline-block;\n cursor: pointer;\n overflow: visible;\n outline: 0;\n\n &[aria-readonly='true'] {\n cursor: not-allowed;\n }\n}\n\n.tremolo-knob-active-line {\n overflow: visible;\n color: oklch(69.2% 0.17548 266.373);\n\n :where(.tremolo-knob:focus, .tremolo-knob:hover) & {\n color: var(--tremolo-theme-color);\n }\n\n :where(.tremolo-knob[aria-disabled='true']) & {\n color: oklch(50.5% 0.03307 270.021);\n }\n}\n\n:where(.dark, [data-theme='dark']) {\n .tremolo-knob-active-line {\n color: var(--tremolo-theme-color);\n\n :where(.tremolo-knob:focus, .tremolo-knob:hover) & {\n color: oklch(65.2% 0.17548 266.373);\n }\n }\n}\n\n.tremolo-knob-inactive-line {\n color: oklch(95% 0 0);\n\n :where(.tremolo-knob:focus, .tremolo-knob:hover) & {\n color: oklch(90% 0 0);\n }\n}\n\n:where(.dark, [data-theme='dark']) {\n .tremolo-knob-inactive-line {\n color: oklch(41.7% 0 0);\n\n :where(.tremolo-knob:focus, .tremolo-knob:hover) & {\n color: oklch(44.59% 0 0);\n }\n }\n}\n\n.tremolo-knob-thumb {\n color: oklch(85.7% 0 0);\n\n :where(.tremolo-knob:focus, .tremolo-knob:hover) & {\n color: oklch(84.5% 0 0);\n }\n}\n\n:where(.dark, [data-theme='dark']) {\n .tremolo-knob-thumb {\n color: oklch(48.6% 0 0);\n\n :where(.tremolo-knob:focus, .tremolo-knob:hover) & {\n color: oklch(51.7% 0 0);\n }\n }\n}\n\n.tremolo-knob-thumb-line {\n color: oklch(94.2% 0 0);\n}\n\n:where(.dark, [data-theme='dark']) {\n .tremolo-knob-thumb-line {\n color: oklch(84.2% 0 0);\n }\n}\n",".tremolo-number-input-wrapper {\n --border-radius: 4px;\n --stepper-width: 20px;\n --active-color: var(--tremolo-theme-color);\n\n display: inline-flex;\n flex-direction: row;\n position: relative;\n width: 140px;\n height: min-content;\n border-style: solid;\n border-color: #c1c1c1;\n border-width: 0px;\n outline: none;\n transition: all 0.1s;\n\n &:not(:focus-within):has(> .tremolo-number-input[data-error='true']) {\n border-color: #f34343;\n }\n\n &[data-variant='outline'] {\n border-width: 1px;\n border-radius: var(--border-radius);\n\n &:hover {\n border-color: #909090;\n }\n\n &:focus-within {\n border-color: var(--active-color);\n box-shadow: 0px 0px 0px 2px\n color-mix(in srgb, var(--active-color) 10%, transparent);\n }\n }\n\n &[data-variant='filled'] {\n border-width: 1px;\n border-radius: var(--border-radius);\n border-color: transparent;\n background-color: #edf2f7;\n\n &:hover {\n background-color: #e4e9ee;\n }\n\n &:focus-within {\n background-color: inherit;\n border-color: var(--active-color);\n box-shadow: 0px 0px 0px 2px\n color-mix(in srgb, var(--active-color) 10%, transparent);\n }\n }\n\n &[data-variant='flushed'] {\n border-bottom-width: 1px;\n\n &:hover {\n border-color: #909090;\n }\n\n &:focus-within {\n border-color: var(--active-color);\n box-shadow: 0px 2px 0px 0px\n color-mix(in srgb, var(--active-color) 10%, transparent);\n }\n }\n\n &[data-stepper='false'] {\n --stepper-width: 0px;\n }\n}\n\n.tremolo-number-input {\n display: block;\n color: inherit;\n width: calc(100% - 20px - var(--stepper-width));\n background-color: inherit;\n font: inherit;\n padding: 6px 10px;\n\n margin: 0;\n border: none;\n outline: none;\n appearance: none;\n\n &[aria-readonly='true'] {\n cursor: not-allowed;\n }\n}\n\n.tremolo-number-input-stepper {\n display: flex;\n flex-direction: column;\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n width: var(--stepper-width);\n transition: all 0.1s linear;\n\n &[data-dynamic='true'] {\n opacity: 0;\n transform: translateX(40%);\n }\n}\n\n:is(\n .tremolo-number-input-wrapper:hover,\n .tremolo-number-input-wrapper:focus-within\n )\n .tremolo-number-input-stepper {\n opacity: 1;\n transform: translateX(0);\n}\n\n.tremolo-number-input-increment-stepper {\n border-inline-start-width: 1px;\n border-inline-start-style: solid;\n border-inline-start-color: #ccc;\n font-size: calc(0.75rem);\n display: flex;\n justify-content: center;\n align-items: center;\n flex: 1 1 0%;\n max-height: 50%;\n user-select: none;\n cursor: pointer;\n border-top-right-radius: var(--border-radius);\n\n &[aria-disabled='false']:active {\n background-color: #3443560e;\n }\n\n &[aria-disabled='true'] {\n cursor: not-allowed;\n }\n\n &[aria-readonly='true'] {\n cursor: not-allowed;\n }\n}\n\n.tremolo-number-input-decrement-stepper {\n border-inline-start-width: 1px;\n border-inline-start-style: solid;\n border-inline-start-color: #ccc;\n border-top: 1px solid #ccc;\n font-size: calc(0.75rem);\n display: flex;\n justify-content: center;\n align-items: center;\n flex: 1 1 0%;\n max-height: 50%;\n user-select: none;\n cursor: pointer;\n border-bottom-right-radius: var(--border-radius);\n\n &[aria-disabled='false']:active {\n background-color: #3443560e;\n }\n\n &[aria-disabled='true'] {\n cursor: not-allowed;\n }\n\n &[aria-readonly='true'] {\n cursor: not-allowed;\n }\n}\n",".tremolo-piano {\n display: inline-block;\n box-sizing: border-box;\n user-select: none;\n touch-action: none;\n position: relative;\n}\n\n.tremolo-piano-white-key {\n --bg: oklch(100% 0 0);\n --color: oklch(0% 0 0);\n --active-bg: oklch(90% 0 0);\n --active-color: oklch(0% 0 0);\n\n box-sizing: border-box;\n position: absolute;\n background-color: var(--bg);\n -webkit-tap-highlight-color: transparent;\n color: var(--color);\n border: 1px solid #555;\n border-radius: 0px 0px 8px 8px;\n cursor: pointer;\n z-index: 1;\n\n &[data-active='true'] {\n background-color: var(--active-bg);\n color: var(--active-color);\n }\n\n &[aria-disabled='true'] {\n cursor: not-allowed;\n }\n}\n\n.tremolo-piano-black-key {\n --color: oklch(100% 0 0);\n --bg: oklch(32.109% 0.00004 271.152);\n --active-color: oklch(100% 0 0);\n --active-bg: oklch(47.836% 0.00005 271.152);\n\n box-sizing: border-box;\n position: absolute;\n background-color: var(--bg);\n -webkit-tap-highlight-color: transparent;\n color: var(--color);\n border: 1px solid #555;\n border-radius: 0px 0px 8px 8px;\n cursor: pointer;\n z-index: 2;\n\n &[data-active='true'] {\n background-color: var(--active-bg);\n color: var(--active-color);\n }\n\n &[aria-disabled='true'] {\n cursor: not-allowed;\n }\n}\n\n.tremolo-piano-key-label-wrapper {\n display: flex;\n justify-content: center;\n align-items: end;\n height: 100%;\n}\n\n.tremolo-piano-key-label {\n box-sizing: content-box;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 0.6rem;\n height: 10px;\n margin-bottom: 10px;\n padding: 4px;\n border-radius: 4px;\n border: 1px solid #888;\n aspect-ratio: 1;\n max-width: calc(100% - 16px);\n}\n\n:where(.dark, [data-theme='dark']) {\n .tremolo-piano-white-key {\n --bg: oklch(48.193% 0.00005 271.152);\n --color: oklch(100% 0 0);\n --active-bg: oklch(52.78% 0.00006 271.152);\n --active-color: oklch(100% 0 0);\n }\n\n .tremolo-piano-black-key {\n --color: oklch(100% 0 0);\n --bg: oklch(32.109% 0 0);\n --active-color: oklch(100% 0 0);\n --active-bg: oklch(38.666% 0.00004 271.152);\n }\n}\n",".tremolo-slider {\n display: inline-block;\n cursor: pointer;\n outline: 0;\n -webkit-tap-highlight-color: transparent;\n\n &[aria-readonly='true'] {\n cursor: not-allowed;\n }\n}\n\n.tremolo-slider-thumb-wrapper {\n position: absolute;\n translate: -50% -50%;\n z-index: 100;\n}\n\n.tremolo-slider-thumb {\n --color: var(--tremolo-theme-color);\n\n background: var(--color);\n border-radius: 50%;\n outline: none;\n\n &[aria-disabled='true'] {\n background: #5d6478;\n }\n\n &[aria-readonly='false']:focus {\n box-shadow: 0px 0px 0px 3px\n color-mix(in srgb, var(--color) 20%, transparent);\n }\n}\n\n.tremolo-slider-track {\n --active: #7998ec;\n --inactive: #eee;\n\n position: relative;\n\n &[data-vertical='false'] {\n min-width: 50px;\n }\n\n &[data-vertical='true'] {\n min-height: 50px;\n }\n\n &:hover {\n --active: #6387e9;\n --inactive: #e0e0e0;\n }\n\n &[aria-disabled='true'] {\n --active: #858890;\n --inactive: #ddd;\n }\n}\n\n.tremolo-slider-scale {\n display: block;\n position: relative;\n}\n\n.tremolo-slider-scale-option {\n display: flex;\n color: #222;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n position: absolute;\n translate: -50% 0;\n z-index: 10;\n\n &[data-vertical='true'] {\n flex-direction: row;\n translate: 0 -50%;\n }\n}\n\n.tremolo-slider-scale-option-mark {\n background-color: currentColor;\n}\n\n.tremolo-slider-scale-option-label {\n text-align: right;\n}\n",".tremolo-xy-pad {\n display: inline-block;\n cursor: pointer;\n}\n\n.tremolo-xy-pad-area {\n --color: #eee;\n\n position: relative;\n background-color: var(--color);\n}\n\n.tremolo-xy-pad-thumb-wrapper {\n position: absolute;\n translate: -50% -50%;\n z-index: 100;\n}\n\n.tremolo-xy-pad-thumb {\n --color: var(--tremolo-theme-color);\n\n border-radius: 50%;\n outline: none;\n background-color: var(--color);\n\n &[aria-disabled='true'] {\n background-color: #5d6478;\n }\n\n &:focus {\n box-shadow: 0px 0px 0px 3px\n color-mix(in srgb, var(--color) 20%, transparent);\n }\n}\n"],"mappings":"AAAA;AACA;AACA;;AAEA;AACA;AACA;;ACNA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AC9EA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;ACxKA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AChGA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;ACtFA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA"}