@tremolo-ui/react 0.7.0 → 0.9.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.
Files changed (55) hide show
  1. package/dist/index.cjs +666 -1171
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +316 -315
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +316 -315
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +643 -1123
  8. package/dist/index.js.map +1 -1
  9. package/package.json +9 -9
  10. package/src/components/AnimationCanvas/index.tsx +4 -18
  11. package/src/components/DropZone/index.tsx +2 -1
  12. package/src/components/FileInput/index.tsx +11 -13
  13. package/src/components/Knob/ActiveLine.tsx +5 -3
  14. package/src/components/Knob/InactiveLine.tsx +6 -4
  15. package/src/components/Knob/SVGRoot.tsx +3 -3
  16. package/src/components/Knob/Thumb.tsx +21 -20
  17. package/src/components/Knob/context.tsx +3 -74
  18. package/src/components/Knob/index.tsx +99 -52
  19. package/src/components/NumberInput/InputField.tsx +20 -33
  20. package/src/components/NumberInput/Stepper.tsx +76 -100
  21. package/src/components/NumberInput/StepperButton.tsx +15 -8
  22. package/src/components/NumberInput/context.tsx +26 -3
  23. package/src/components/NumberInput/index.tsx +175 -91
  24. package/src/components/Piano/index.tsx +54 -144
  25. package/src/components/PointsEditor/Container.tsx +15 -26
  26. package/src/components/PointsEditor/Point.tsx +94 -110
  27. package/src/components/PointsEditor/context.tsx +8 -60
  28. package/src/components/PointsEditor/index.tsx +41 -324
  29. package/src/components/Slider/Marks.tsx +5 -6
  30. package/src/components/Slider/MarksOption.tsx +9 -19
  31. package/src/components/Slider/Thumb.tsx +20 -11
  32. package/src/components/Slider/Track.tsx +16 -13
  33. package/src/components/Slider/context.tsx +3 -3
  34. package/src/components/Slider/index.tsx +110 -72
  35. package/src/components/XYPad/Area.tsx +27 -49
  36. package/src/components/XYPad/Thumb.tsx +20 -8
  37. package/src/components/XYPad/context.tsx +2 -4
  38. package/src/components/XYPad/index.tsx +102 -83
  39. package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
  40. package/src/hooks/_internal/useChangeGesture.ts +93 -0
  41. package/src/hooks/_internal/useCheckSteps.ts +9 -146
  42. package/src/hooks/useAnimationFrame.ts +37 -15
  43. package/src/hooks/useEventListener.ts +93 -22
  44. package/src/hooks/useLongPress.ts +35 -51
  45. package/src/hooks/useMIDIAccess.ts +0 -8
  46. package/src/hooks/useMIDIInput.ts +0 -3
  47. package/src/hooks/useWheel.ts +4 -4
  48. package/src/index.ts +11 -32
  49. package/src/components/Piano/keyboard-shortcuts.ts +0 -61
  50. package/src/components/Slider/type.ts +0 -46
  51. package/src/components/_util/css-length.ts +0 -13
  52. package/src/components/_util/decimal-digits.ts +0 -23
  53. package/src/components/_util/visually-hidden.ts +0 -25
  54. package/src/hooks/useInterval.ts +0 -27
  55. package/src/input-event.ts +0 -32
@@ -2,52 +2,123 @@ import { useCallback, useEffect, useRef } from 'react'
2
2
 
3
3
  import { useCallbackRef } from './_internal/useCallbackRef'
4
4
 
5
- type Target = EventTarget | null | (() => EventTarget | null)
6
- type Options = boolean | AddEventListenerOptions
5
+ /**
6
+ * What {@link useEventListener} listens on. A function is called after every
7
+ * render, so it can return an element out of a ref; `null` listens on nothing.
8
+ */
9
+ export type UseEventListenerTarget =
10
+ | EventTarget
11
+ | null
12
+ | (() => EventTarget | null)
7
13
 
14
+ /**
15
+ * The options of `addEventListener`, as a boolean for `capture` or as an
16
+ * object. `signal` is left out: the hook already owns when the listener goes.
17
+ */
18
+ export type UseEventListenerOptions =
19
+ | boolean
20
+ | Pick<AddEventListenerOptions, 'capture' | 'once' | 'passive'>
21
+
22
+ interface Registration {
23
+ node: EventTarget
24
+ event: string
25
+ capture: boolean
26
+ once: boolean | undefined
27
+ passive: boolean | undefined
28
+ remove: VoidFunction
29
+ }
30
+
31
+ /**
32
+ * Listen to an event on a target for as long as the component is mounted.
33
+ *
34
+ * The handler is read on every event, and the target and the options are
35
+ * compared by what they resolve to, so all three can be written inline. The
36
+ * listener is re-attached only when the element, the event or one of the
37
+ * options actually changes.
38
+ *
39
+ * @returns a function that removes the listener. It stays removed until the
40
+ * target, the event or the options change.
41
+ */
8
42
  export function useEventListener<K extends keyof DocumentEventMap>(
9
- target: Target,
43
+ target: UseEventListenerTarget,
10
44
  event: K,
11
45
  handler: (event: DocumentEventMap[K]) => void,
12
- options?: Options,
46
+ options?: UseEventListenerOptions,
13
47
  ): VoidFunction
14
48
  export function useEventListener<K extends keyof WindowEventMap>(
15
- target: Target,
49
+ target: UseEventListenerTarget,
16
50
  event: K,
17
51
  handler: (event: WindowEventMap[K]) => void,
18
- options?: Options,
52
+ options?: UseEventListenerOptions,
19
53
  ): VoidFunction
20
54
  export function useEventListener<K extends keyof GlobalEventHandlersEventMap>(
21
- target: Target,
55
+ target: UseEventListenerTarget,
22
56
  event: K,
23
57
  handler: (event: GlobalEventHandlersEventMap[K]) => void,
24
- options?: Options,
58
+ options?: UseEventListenerOptions,
25
59
  ): VoidFunction
26
60
  export function useEventListener(
27
- target: Target,
61
+ target: UseEventListenerTarget,
28
62
  event: string,
29
63
  handler: (event: Event) => void,
30
- options?: Options,
64
+ options?: UseEventListenerOptions,
31
65
  ) {
32
66
  const listener = useCallbackRef(handler)
33
- const cleanupRef = useRef<VoidFunction>(() => {})
67
+ const registrationRef = useRef<Registration | null>(null)
68
+
69
+ const {
70
+ capture = false,
71
+ once,
72
+ passive,
73
+ } = typeof options === 'boolean' ? { capture: options } : (options ?? {})
34
74
 
75
+ // No dependency list: an inline target or options object is a new value on
76
+ // every render, so what decides a re-attach is the comparison below, not
77
+ // their identity.
35
78
  useEffect(() => {
36
- const node = typeof target === 'function' ? target() : (target ?? document)
79
+ const node = typeof target === 'function' ? target() : target
80
+ const current = registrationRef.current
37
81
 
82
+ if (
83
+ current &&
84
+ current.node === node &&
85
+ current.event === event &&
86
+ current.capture === capture &&
87
+ current.once === once &&
88
+ current.passive === passive
89
+ ) {
90
+ return
91
+ }
92
+
93
+ current?.remove()
94
+ registrationRef.current = null
38
95
  if (!node) return
39
96
 
40
- node.addEventListener(event, listener, options)
41
- const cleanup = () => {
42
- node.removeEventListener(event, listener, options)
97
+ const listenerOptions = { capture, once, passive }
98
+ node.addEventListener(event, listener, listenerOptions)
99
+ registrationRef.current = {
100
+ node,
101
+ event,
102
+ capture,
103
+ once,
104
+ passive,
105
+ remove: () => node.removeEventListener(event, listener, listenerOptions),
43
106
  }
44
- cleanupRef.current = cleanup
107
+ })
45
108
 
46
- return () => {
47
- cleanup()
48
- if (cleanupRef.current === cleanup) cleanupRef.current = () => {}
49
- }
50
- }, [event, target, options, listener])
109
+ useEffect(
110
+ () => () => {
111
+ registrationRef.current?.remove()
112
+ registrationRef.current = null
113
+ },
114
+ [],
115
+ )
51
116
 
52
- return useCallback(() => cleanupRef.current(), [])
117
+ // Kept registered, so a render with the same target does not attach again.
118
+ return useCallback(() => {
119
+ const current = registrationRef.current
120
+ if (!current) return
121
+ current.remove()
122
+ current.remove = () => {}
123
+ }, [])
53
124
  }
@@ -1,65 +1,49 @@
1
- import { useCallback, useRef, useState } from 'react'
1
+ import { useCallback, useEffect, useRef } from 'react'
2
2
 
3
- import { useEventListener } from './useEventListener'
4
- import { useInterval } from './useInterval'
3
+ import { createLongPress, type LongPressInstance } from '@tremolo-ui/dom'
5
4
 
5
+ import { useCallbackRef } from './_internal/useCallbackRef'
6
+
7
+ /**
8
+ * Repeat `callback` while a pointer is held down: once on the press, then
9
+ * every `interval` after `initialDelay`. The repeat itself is
10
+ * `createLongPress` in `@tremolo-ui/dom`.
11
+ *
12
+ * Returns the function that starts a press, for an `onPointerDown`.
13
+ */
6
14
  export function useLongPress(
7
15
  callback: () => void,
8
16
  initialDelay = 500,
9
17
  interval = 40,
10
18
  ) {
11
- const [pressed, setPressed] = useState(false)
12
- const [delay, setDelay] = useState(initialDelay)
13
- const activePointerId = useRef<number | null>(null)
14
- const isPressed = useRef(false)
15
-
16
- useInterval(
17
- () => {
18
- callback()
19
- setDelay(interval)
20
- },
21
- pressed ? delay : null,
22
- )
23
-
24
- const stop = useCallback(
25
- (pointerId?: number) => {
26
- if (!isPressed.current) return
27
- if (
28
- pointerId !== undefined &&
29
- activePointerId.current !== null &&
30
- activePointerId.current !== pointerId
31
- )
32
- return
33
-
34
- isPressed.current = false
35
- activePointerId.current = null
36
- setPressed(false)
37
- setDelay(initialDelay)
38
- },
39
- [initialDelay],
40
- )
19
+ // Updated before layout effects run, so that a press started from one
20
+ // right after a re-render already calls the new callback.
21
+ const onPress = useCallbackRef(callback)
22
+ const instanceRef = useRef<LongPressInstance | null>(null)
41
23
 
42
- useEventListener(globalThis.window, 'pointerup', (event) => {
43
- stop(event.pointerId)
24
+ useEffect(() => {
25
+ instanceRef.current?.update({ delay: initialDelay, interval })
44
26
  })
45
27
 
46
- useEventListener(globalThis.window, 'pointercancel', (event) => {
47
- stop(event.pointerId)
48
- })
49
-
50
- useEventListener(globalThis.window, 'blur', () => {
51
- stop()
52
- })
28
+ useEffect(() => {
29
+ const instance = createLongPress({
30
+ onPress: () => onPress(),
31
+ delay: initialDelay,
32
+ interval,
33
+ })
34
+ instanceRef.current = instance
35
+ return () => {
36
+ instanceRef.current = null
37
+ instance.destroy()
38
+ }
39
+ // Created once: the options are pushed with update() above, so that a new
40
+ // delay does not end a press in progress.
41
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [])
53
43
 
54
44
  return useCallback(
55
- (event?: Pick<PointerEvent, 'button' | 'pointerId'>) => {
56
- if (isPressed.current || (event && event.button !== 0)) return
57
-
58
- isPressed.current = true
59
- activePointerId.current = event?.pointerId ?? null
60
- callback()
61
- setPressed(true)
62
- },
63
- [callback],
45
+ (event?: Pick<PointerEvent, 'button' | 'pointerId'>) =>
46
+ instanceRef.current?.start(event),
47
+ [],
64
48
  )
65
49
  }
@@ -7,14 +7,6 @@ import {
7
7
  type MIDIAccessState,
8
8
  } from '@tremolo-ui/dom'
9
9
 
10
- export {
11
- NOT_SUPPORTED,
12
- PERMISSION_DENIED,
13
- UNAVAILABLE,
14
- type MIDIAccessError,
15
- type MIDIAccessOptions,
16
- } from '@tremolo-ui/dom'
17
-
18
10
  const INITIAL_STATE: MIDIAccessState = {
19
11
  midiAccess: null,
20
12
  error: null,
@@ -2,13 +2,10 @@ import { useEffect, useRef } from 'react'
2
2
 
3
3
  import {
4
4
  createMIDIInput,
5
- PITCH_BEND_CENTER,
6
5
  type MIDIInputHandlers,
7
6
  type MIDIInputInstance,
8
7
  } from '@tremolo-ui/dom'
9
8
 
10
- export { PITCH_BEND_CENTER, type MIDIInputHandlers }
11
-
12
9
  /**
13
10
  * Handle MIDI input events. To be used with {@link useMIDIAccess}.
14
11
  *
@@ -4,9 +4,8 @@ import { createWheel, type WheelOptions } from '@tremolo-ui/dom'
4
4
 
5
5
  import { useCallbackRef } from './_internal/useCallbackRef'
6
6
 
7
- // `onWheel` is left out: `createWheel` takes it so that `update()` can replace
8
- // the callback, but this hook takes the handler as its first argument and the
9
- // option would be silently ignored.
7
+ // `onWheel` is left out: this hook takes the handler as its first argument, so
8
+ // the option would be a second way to say the same thing.
10
9
  export interface UseWheelOptions extends Omit<WheelOptions, 'onWheel'> {
11
10
  /**
12
11
  * Listen on this element rather than on the one the returned ref callback is
@@ -43,7 +42,8 @@ export function useWheel<T extends Element>(
43
42
  const element = target ? target.current : node
44
43
  if (!element) return
45
44
 
46
- const instance = createWheel(element, (event) => wheelHandler(event), {
45
+ const instance = createWheel(element, {
46
+ onWheel: (event) => wheelHandler(event),
47
47
  requireFocus,
48
48
  })
49
49
 
package/src/index.ts CHANGED
@@ -4,8 +4,6 @@ export {
4
4
  type AnimationCanvasFixedProps,
5
5
  type AnimationCanvasProps,
6
6
  type AnimationCanvasResizableProps,
7
- type DrawFunction,
8
- type InitFunction,
9
7
  } from './components/AnimationCanvas'
10
8
  export { DropZone, type DropZoneProps } from './components/DropZone'
11
9
  export {
@@ -33,28 +31,23 @@ export {
33
31
  } from './components/NumberInput'
34
32
  export {
35
33
  Piano,
36
- SHORTCUTS,
37
34
  type PianoProps,
38
35
  type PianoMethods,
39
36
  type KeyState,
40
37
  type KeyAttributes,
41
- type KeyboardShortcuts,
42
38
  } from './components/Piano'
43
39
  export {
44
40
  PointsEditor,
45
41
  usePointsEditorContext,
46
- clampPoint,
47
42
  type PointsEditorProps,
48
43
  type PointsEditorContextValue,
49
44
  type PointsEditorPointProps,
50
- type PointBaseType,
51
45
  } from './components/PointsEditor'
52
46
  export {
53
47
  Slider,
54
48
  useSliderContext,
55
49
  type SliderContextValue,
56
50
  type SliderMarksOptionProps,
57
- type MarksOptions,
58
51
  type SliderMarksProps,
59
52
  type SliderMethods,
60
53
  type SliderProps,
@@ -68,41 +61,27 @@ export {
68
61
  type XYPadContextValue,
69
62
  type XYPadProps,
70
63
  type XYPadMethods,
71
- type XYPadAreaProps,
72
64
  type XYPadThumbProps,
73
65
  type XYPadThumbMethods,
74
- type XY,
75
- type XYInput,
76
66
  } from './components/XYPad'
77
67
 
78
68
  export { type CSSVariables } from './css-variables'
79
69
 
80
70
  // hooks
81
- export { useAnimationFrame } from './hooks/useAnimationFrame'
71
+ export {
72
+ useAnimationFrame,
73
+ type UseAnimationFrameOptions,
74
+ } from './hooks/useAnimationFrame'
82
75
  export { useDrag, type UseDragOptions } from './hooks/useDrag'
83
76
  export { useDragValue, type UseDragValueOptions } from './hooks/useDragValue'
84
77
  export { useDropZone, type UseDropZoneOptions } from './hooks/useDropZone'
85
- export { useEventListener } from './hooks/useEventListener'
86
- export { useInterval } from './hooks/useInterval'
87
- export { useLongPress } from './hooks/useLongPress'
88
- export {
89
- useMIDIAccess,
90
- NOT_SUPPORTED,
91
- PERMISSION_DENIED,
92
- UNAVAILABLE,
93
- type MIDIAccessError,
94
- type MIDIAccessOptions,
95
- } from './hooks/useMIDIAccess'
96
78
  export {
97
- useMIDIInput,
98
- PITCH_BEND_CENTER,
99
- type MIDIInputHandlers,
100
- } from './hooks/useMIDIInput'
79
+ useEventListener,
80
+ type UseEventListenerOptions,
81
+ type UseEventListenerTarget,
82
+ } from './hooks/useEventListener'
83
+ export { useLongPress } from './hooks/useLongPress'
84
+ export { useMIDIAccess } from './hooks/useMIDIAccess'
85
+ export { useMIDIInput } from './hooks/useMIDIInput'
101
86
  export { useMIDIMessage } from './hooks/useMIDIMessage'
102
87
  export { useWheel, type UseWheelOptions } from './hooks/useWheel'
103
-
104
- export {
105
- DEFAULT_DRAG_SENSITIVITY,
106
- DEFAULT_KEYBOARD_OPTIONS,
107
- DEFAULT_WHEEL_OPTIONS,
108
- } from './input-event'
@@ -1,61 +0,0 @@
1
- export type KeyboardShortcuts = {
2
- /**
3
- * Keys laid out from `noteRange.first`, one entry per semitone.
4
- *
5
- * An empty string leaves that note without a shortcut: `KeyboardEvent.key` is
6
- * never empty, so the entry can never match. Use it to skip the black keys
7
- * (see {@link SHORTCUTS.HOME_ROW_NATURAL}) and keep the remaining entries
8
- * lined up with the notes.
9
- */
10
- keys: string[]
11
- }
12
-
13
- /**
14
- * Ready-made keyboard layouts. Both assume `noteRange.first` is a C.
15
- */
16
- export const SHORTCUTS = {
17
- /** Every semitone from C, over the two rows of a QWERTY keyboard. */
18
- HOME_ROW: {
19
- keys: [
20
- 'a',
21
- 'w',
22
- 's',
23
- 'e',
24
- 'd',
25
- 'f',
26
- 't',
27
- 'g',
28
- 'y',
29
- 'h',
30
- 'u',
31
- 'j',
32
- 'k',
33
- 'o',
34
- 'l',
35
- 'p',
36
- ';',
37
- ],
38
- },
39
- /** The white keys only, on the home row. Black keys have no shortcut. */
40
- HOME_ROW_NATURAL: {
41
- keys: [
42
- 'a',
43
- '',
44
- 's',
45
- '',
46
- 'd',
47
- 'f',
48
- '',
49
- 'g',
50
- '',
51
- 'h',
52
- '',
53
- 'j',
54
- 'k',
55
- '',
56
- 'l',
57
- '',
58
- ';',
59
- ],
60
- },
61
- }
@@ -1,46 +0,0 @@
1
- import { toFixed } from '@tremolo-ui/functions'
2
-
3
- import { decimalDigits } from '../_util/decimal-digits'
4
-
5
- /**
6
- * How `Slider.Marks` fills itself in when it is given no children: one option
7
- * every `per`, or every `step` of the slider. The object form turns off the
8
- * mark or the label for the whole set; a single option is customized by
9
- * writing `Slider.MarksOption` out instead.
10
- */
11
- export type MarksOptions =
12
- | 'step'
13
- | number
14
- | {
15
- per: 'step' | number
16
- mark?: boolean
17
- label?: boolean
18
- }
19
-
20
- export function generateOptionsList(
21
- options: MarksOptions,
22
- min: number,
23
- max: number,
24
- step: number,
25
- ) {
26
- const {
27
- per,
28
- mark = true,
29
- label = true,
30
- } = typeof options === 'object' ? options : { per: options }
31
- const optionsList: {
32
- value: number
33
- mark: boolean
34
- label: boolean
35
- }[] = []
36
- const interval = per === 'step' ? step : per
37
- const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1
38
- for (let i = 0; i < count; i++) {
39
- const value = toFixed(
40
- interval * (Math.ceil(min / interval) + i),
41
- decimalDigits(interval),
42
- )
43
- optionsList.push({ value: value, mark: mark, label: label })
44
- }
45
- return optionsList
46
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * A number as a CSS length, for a custom property.
3
- *
4
- * React appends `px` to a bare number for the properties it knows take a
5
- * length, and a custom property is never one of them: `--size: 50` would come
6
- * out as the invalid `50`. Anything already a string is passed through, so
7
- * `'3rem'` and `'100%'` still work.
8
- */
9
- export function cssLength(
10
- value: number | string | undefined,
11
- ): string | undefined {
12
- return typeof value === 'number' ? `${value}px` : value
13
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * How many digits a number carries after the decimal point.
3
- *
4
- * `Slider.Marks` builds its labels by multiplying an interval, which leaves
5
- * binary debris in the last digits (`0.1 * 3` is `0.30000000000000004`). The
6
- * count is what tells `toFixed` how far to round that back.
7
- *
8
- * The exponent form has to be handled separately: `String(1e-7)` is `'1e-7'`,
9
- * which has no decimal point at all, so reading the text after the point
10
- * reports no digits and the interval rounds away to whole numbers.
11
- */
12
- export function decimalDigits(x: number): number {
13
- if (!Number.isFinite(x)) return 0
14
-
15
- const text = String(x)
16
- const e = text.indexOf('e')
17
- if (e === -1) return text.split('.')[1]?.length ?? 0
18
-
19
- const fraction = text.slice(0, e).split('.')[1]?.length ?? 0
20
- // A negative exponent pushes the point further right, a positive one pulls
21
- // it back past the digits that are there.
22
- return Math.max(0, fraction - Number(text.slice(e + 1)))
23
- }
@@ -1,25 +0,0 @@
1
- import type { CSSProperties } from 'react'
2
-
3
- /**
4
- * Take an element out of sight while leaving it in the accessibility tree and
5
- * in the tab order.
6
- *
7
- * `display: none` and `visibility: hidden` would remove it from both, and the
8
- * native control underneath a headless component is what carries the ARIA and
9
- * the keyboard behaviour. `pointer-events: none` is safe because nothing is
10
- * ever clicked here directly: a `<label>` forwards its click, and a drag is
11
- * handled by the part that is visible.
12
- */
13
- export const visuallyHiddenStyle: CSSProperties = {
14
- position: 'absolute',
15
- width: 1,
16
- height: 1,
17
- padding: 0,
18
- margin: -1,
19
- overflow: 'hidden',
20
- clip: 'rect(0, 0, 0, 0)',
21
- clipPath: 'inset(50%)',
22
- whiteSpace: 'nowrap',
23
- border: 0,
24
- pointerEvents: 'none',
25
- }
@@ -1,27 +0,0 @@
1
- import { useEffect, useRef } from 'react'
2
-
3
- export function useInterval(callback: () => void, delay: number | null) {
4
- const savedCallback = useRef(callback)
5
-
6
- // Remember the latest callback if it changes.
7
- useEffect(() => {
8
- savedCallback.current = callback
9
- }, [callback])
10
-
11
- // Set up the interval.
12
- useEffect(() => {
13
- // Don't schedule if no delay is specified.
14
- // Note: 0 is a valid value for delay.
15
- if (delay === null) {
16
- return
17
- }
18
-
19
- const id = setInterval(() => {
20
- savedCallback.current()
21
- }, delay)
22
-
23
- return () => {
24
- clearInterval(id)
25
- }
26
- }, [delay])
27
- }
@@ -1,32 +0,0 @@
1
- import { type InputEventOption, type ModifierValue } from '@tremolo-ui/dom'
2
-
3
- /**
4
- * The keyboard amount used by Knob, NumberInput, Slider, and XYPad by default:
5
- * 1 per press in the units of the value, and 0.1 with shift held. That is one
6
- * `step` only while `step` is 1; a coarser `step` rounds 1 straight back, which
7
- * `useCheckSteps` warns about.
8
- *
9
- * A modifier entry is not snapped to `step`, which is what lets the finer
10
- * amount move at all — see `applyDelta` in `@tremolo-ui/dom`.
11
- */
12
- export const DEFAULT_KEYBOARD_OPTIONS: ModifierValue<InputEventOption> = {
13
- default: ['raw', 1],
14
- shift: ['raw', 0.1],
15
- }
16
-
17
- /**
18
- * The wheel amount used by Knob, NumberInput, Slider, and XYPad by default.
19
- *
20
- * Browsers turn shift+wheel into horizontal scrolling, which empties `deltaY`
21
- * and fills `deltaX`, so no modifier is bound here.
22
- */
23
- export const DEFAULT_WHEEL_OPTIONS: ModifierValue<InputEventOption> = ['raw', 1]
24
-
25
- /**
26
- * The drag sensitivity used by value controls by default. Shift makes the
27
- * same movement cover a tenth of the range, matching the arrow keys.
28
- */
29
- export const DEFAULT_DRAG_SENSITIVITY: ModifierValue<number> = {
30
- default: 1,
31
- shift: 0.1,
32
- }