@tremolo-ui/react 0.6.0 → 0.7.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 (48) hide show
  1. package/dist/index.cjs +252 -58
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +550 -133
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +550 -133
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +218 -39
  8. package/dist/index.js.map +1 -1
  9. package/package.json +4 -4
  10. package/src/components/DropZone/index.tsx +100 -0
  11. package/src/components/FileInput/Trigger.tsx +39 -0
  12. package/src/components/FileInput/context.tsx +24 -0
  13. package/src/components/FileInput/index.tsx +144 -0
  14. package/src/components/Knob/index.tsx +8 -5
  15. package/src/components/NumberInput/DecrementStepper.tsx +2 -13
  16. package/src/components/NumberInput/IncrementStepper.tsx +2 -14
  17. package/src/components/NumberInput/InputField.tsx +5 -11
  18. package/src/components/NumberInput/Stepper.tsx +4 -19
  19. package/src/components/NumberInput/StepperButton.tsx +13 -8
  20. package/src/components/NumberInput/context.tsx +6 -7
  21. package/src/components/NumberInput/index.tsx +4 -13
  22. package/src/components/Piano/index.tsx +10 -13
  23. package/src/components/PointsEditor/Background.tsx +2 -8
  24. package/src/components/PointsEditor/Container.tsx +2 -8
  25. package/src/components/PointsEditor/Point.tsx +7 -5
  26. package/src/components/PointsEditor/SelectionBox.tsx +2 -8
  27. package/src/components/PointsEditor/context.tsx +6 -6
  28. package/src/components/PointsEditor/index.tsx +9 -11
  29. package/src/components/Slider/Marks.tsx +6 -9
  30. package/src/components/Slider/MarksOption.tsx +7 -2
  31. package/src/components/Slider/Thumb.tsx +9 -15
  32. package/src/components/Slider/Track.tsx +6 -13
  33. package/src/components/Slider/index.tsx +5 -12
  34. package/src/components/Slider/type.ts +4 -2
  35. package/src/components/XYPad/Area.tsx +5 -10
  36. package/src/components/XYPad/Thumb.tsx +5 -11
  37. package/src/components/XYPad/index.tsx +3 -5
  38. package/src/components/_util/VisuallyHiddenRangeInput.tsx +3 -15
  39. package/src/components/_util/decimal-digits.ts +23 -0
  40. package/src/components/_util/visually-hidden.ts +25 -0
  41. package/src/css-variables.ts +23 -0
  42. package/src/hooks/_internal/useCheckSteps.ts +2 -3
  43. package/src/hooks/useDrag.ts +13 -4
  44. package/src/hooks/useDragValue.ts +17 -2
  45. package/src/hooks/useDropZone.ts +78 -0
  46. package/src/hooks/useWheel.ts +4 -1
  47. package/src/index.ts +12 -9
  48. package/src/input-event.ts +2 -2
@@ -51,9 +51,19 @@ export interface UseDragValueOptions {
51
51
  */
52
52
  sensitivity?: (state: DragState) => number
53
53
 
54
- /** @see DragValueOptions.updateOnPointerDown */
54
+ /**
55
+ * Move the value on pointerdown, before the pointer has moved at all. With
56
+ * `baseElementRef` that makes a press jump the value to where it landed.
57
+ *
58
+ * @see DragValueOptions.updateOnPointerDown
59
+ */
55
60
  updateOnPointerDown?: boolean
56
- /** @default 0 */
61
+ /**
62
+ * Pixels the pointer has to travel before the drag counts as one. The
63
+ * default is 0, so the value follows the very first move.
64
+ *
65
+ * @default 0
66
+ */
57
67
  threshold?: number
58
68
  /** CSS cursor to show while dragging. Applied to the element itself. */
59
69
  cursor?: string
@@ -75,11 +85,16 @@ export interface UseDragValueOptions {
75
85
  shouldStart?: (event: PointerEvent) => boolean
76
86
 
77
87
  /**
88
+ * Called with the new value whenever the drag moves it. The hook holds no
89
+ * value of its own, so this is where it is stored.
90
+ *
78
91
  * @param state the whole drag, for anything the value leaves out — the
79
92
  * pointer event and its modifier keys, most of all.
80
93
  */
81
94
  onChange?: (value: XY<number>, state: DragState) => void
95
+ /** Called once the drag starts, with the value it starts from. */
82
96
  onDragStart?: (value: XY<number>, state: DragState) => void
97
+ /** Called when the pointer is released, with the value it ends on. */
83
98
  onDragEnd?: (value: XY<number>, state: DragState) => void
84
99
  }
85
100
 
@@ -0,0 +1,78 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ import {
4
+ createDropZone,
5
+ type DropZoneInstance,
6
+ type DropZoneOptions,
7
+ } from '@tremolo-ui/dom'
8
+
9
+ import { useCallbackRef } from './_internal/useCallbackRef'
10
+
11
+ // `onStateChange` is left out: what it reports comes back from the hook as
12
+ // `over` and `invalid`, which is what a component renders from.
13
+ export interface UseDropZoneOptions extends Omit<
14
+ DropZoneOptions,
15
+ 'onStateChange'
16
+ > {}
17
+
18
+ /**
19
+ * Take files dropped onto an element.
20
+ *
21
+ * Any element can become a drop target this way — a waveform display, a track
22
+ * lane, a canvas — without wrapping it in anything.
23
+ *
24
+ * @returns a ref callback for the element, and whether a drag is over it
25
+ */
26
+ export function useDropZone<T extends Element>({
27
+ accept,
28
+ multiple,
29
+ disabled,
30
+ onDrop,
31
+ onReject,
32
+ }: UseDropZoneOptions = {}): {
33
+ refCallback: (node: T | null) => void
34
+ over: boolean
35
+ invalid: boolean
36
+ } {
37
+ const dropHandler = useCallbackRef(onDrop)
38
+ const rejectHandler = useCallbackRef(onReject)
39
+
40
+ const [state, setState] = useState({ over: false, invalid: false })
41
+
42
+ // See useDrag for why the node is held in state.
43
+ const [node, setNode] = useState<T | null>(null)
44
+
45
+ const instanceRef = useRef<DropZoneInstance | null>(null)
46
+
47
+ // Read when the instance is created. The effect below keeps it current, and
48
+ // runs right after, so a stale setting is replaced within the same commit.
49
+ const latest = useRef({ accept, multiple, disabled })
50
+
51
+ useEffect(() => {
52
+ if (!node) return
53
+
54
+ const instance = createDropZone(node, {
55
+ ...latest.current,
56
+ onDrop: (files, event) => dropHandler(files, event),
57
+ onReject: (files, event) => rejectHandler(files, event),
58
+ onStateChange: (next) => setState(next),
59
+ })
60
+ instanceRef.current = instance
61
+
62
+ return () => {
63
+ instanceRef.current = null
64
+ instance.destroy()
65
+ // The listeners are gone, so no `dragleave` is coming to clear this.
66
+ setState({ over: false, invalid: false })
67
+ }
68
+ }, [node, dropHandler, rejectHandler])
69
+
70
+ // Runs after every render: the settings come from props and are cheap to
71
+ // push, and updating in place leaves a drag in progress untouched.
72
+ useEffect(() => {
73
+ latest.current = { accept, multiple, disabled }
74
+ instanceRef.current?.update({ accept, multiple, disabled })
75
+ })
76
+
77
+ return { refCallback: setNode, over: state.over, invalid: state.invalid }
78
+ }
@@ -4,7 +4,10 @@ import { createWheel, type WheelOptions } from '@tremolo-ui/dom'
4
4
 
5
5
  import { useCallbackRef } from './_internal/useCallbackRef'
6
6
 
7
- export interface UseWheelOptions extends WheelOptions {
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.
10
+ export interface UseWheelOptions extends Omit<WheelOptions, 'onWheel'> {
8
11
  /**
9
12
  * Listen on this element rather than on the one the returned ref callback is
10
13
  * attached to, and ignore that callback.
package/src/index.ts CHANGED
@@ -7,6 +7,14 @@ export {
7
7
  type DrawFunction,
8
8
  type InitFunction,
9
9
  } from './components/AnimationCanvas'
10
+ export { DropZone, type DropZoneProps } from './components/DropZone'
11
+ export {
12
+ FileInput,
13
+ useFileInputContext,
14
+ type FileInputContextValue,
15
+ type FileInputProps,
16
+ type FileInputTriggerProps,
17
+ } from './components/FileInput'
10
18
  export {
11
19
  Knob,
12
20
  useKnobContext,
@@ -22,10 +30,6 @@ export {
22
30
  type NumberInputContextValue,
23
31
  type NumberInputProps,
24
32
  type NumberInputMethods,
25
- type NumberInputInputFieldProps,
26
- type NumberInputStepperProps,
27
- type NumberInputIncrementStepperProps,
28
- type NumberInputDecrementStepperProps,
29
33
  } from './components/NumberInput'
30
34
  export {
31
35
  Piano,
@@ -34,7 +38,6 @@ export {
34
38
  type PianoMethods,
35
39
  type KeyState,
36
40
  type KeyAttributes,
37
- type CSSVariables,
38
41
  type KeyboardShortcuts,
39
42
  } from './components/Piano'
40
43
  export {
@@ -43,9 +46,6 @@ export {
43
46
  clampPoint,
44
47
  type PointsEditorProps,
45
48
  type PointsEditorContextValue,
46
- type PointsEditorBackgroundProps,
47
- type PointsEditorContainerProps,
48
- type PointsEditorSelectionBoxProps,
49
49
  type PointsEditorPointProps,
50
50
  type PointBaseType,
51
51
  } from './components/PointsEditor'
@@ -75,10 +75,13 @@ export {
75
75
  type XYInput,
76
76
  } from './components/XYPad'
77
77
 
78
+ export { type CSSVariables } from './css-variables'
79
+
78
80
  // hooks
79
81
  export { useAnimationFrame } from './hooks/useAnimationFrame'
80
- export { useDrag } from './hooks/useDrag'
82
+ export { useDrag, type UseDragOptions } from './hooks/useDrag'
81
83
  export { useDragValue, type UseDragValueOptions } from './hooks/useDragValue'
84
+ export { useDropZone, type UseDropZoneOptions } from './hooks/useDropZone'
82
85
  export { useEventListener } from './hooks/useEventListener'
83
86
  export { useInterval } from './hooks/useInterval'
84
87
  export { useLongPress } from './hooks/useLongPress'
@@ -1,4 +1,4 @@
1
- import type { InputEventOption, ModifierValue } from '@tremolo-ui/functions'
1
+ import { type InputEventOption, type ModifierValue } from '@tremolo-ui/dom'
2
2
 
3
3
  /**
4
4
  * The keyboard amount used by Knob, NumberInput, Slider, and XYPad by default:
@@ -7,7 +7,7 @@ import type { InputEventOption, ModifierValue } from '@tremolo-ui/functions'
7
7
  * `useCheckSteps` warns about.
8
8
  *
9
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/functions`.
10
+ * amount move at all — see `applyDelta` in `@tremolo-ui/dom`.
11
11
  */
12
12
  export const DEFAULT_KEYBOARD_OPTIONS: ModifierValue<InputEventOption> = {
13
13
  default: ['raw', 1],