@tremolo-ui/react 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/compose-refs.cjs +72 -0
  2. package/dist/compose-refs.cjs.map +1 -0
  3. package/dist/compose-refs.d.cts +43 -0
  4. package/dist/compose-refs.d.cts.map +1 -0
  5. package/dist/compose-refs.d.ts +43 -0
  6. package/dist/compose-refs.d.ts.map +1 -0
  7. package/dist/compose-refs.js +70 -0
  8. package/dist/compose-refs.js.map +1 -0
  9. package/dist/index.cjs +2272 -1650
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1461 -475
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.ts +1461 -475
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2258 -1648
  16. package/dist/index.js.map +1 -1
  17. package/package.json +32 -31
  18. package/src/components/AnimationCanvas/index.tsx +162 -194
  19. package/src/components/Knob/ActiveLine.tsx +9 -8
  20. package/src/components/Knob/InactiveLine.tsx +11 -14
  21. package/src/components/Knob/SVGRoot.tsx +10 -17
  22. package/src/components/Knob/Thumb.tsx +33 -11
  23. package/src/components/Knob/context.tsx +73 -89
  24. package/src/components/Knob/index.tsx +227 -105
  25. package/src/components/NumberInput/DecrementStepper.tsx +21 -50
  26. package/src/components/NumberInput/IncrementStepper.tsx +22 -50
  27. package/src/components/NumberInput/InputField.tsx +206 -0
  28. package/src/components/NumberInput/Stepper.tsx +150 -21
  29. package/src/components/NumberInput/StepperButton.tsx +95 -0
  30. package/src/components/NumberInput/context.tsx +104 -149
  31. package/src/components/NumberInput/index.tsx +461 -110
  32. package/src/components/Piano/index.tsx +413 -263
  33. package/src/components/Piano/keyboard-shortcuts.ts +61 -0
  34. package/src/components/PointsEditor/Background.tsx +19 -4
  35. package/src/components/PointsEditor/Container.tsx +92 -22
  36. package/src/components/PointsEditor/Point.tsx +293 -53
  37. package/src/components/PointsEditor/SelectionBox.tsx +54 -0
  38. package/src/components/PointsEditor/context.tsx +97 -75
  39. package/src/components/PointsEditor/index.tsx +497 -62
  40. package/src/components/Slider/Marks.tsx +84 -0
  41. package/src/components/Slider/MarksOption.tsx +115 -0
  42. package/src/components/Slider/Thumb.tsx +99 -54
  43. package/src/components/Slider/Track.tsx +73 -62
  44. package/src/components/Slider/context.tsx +40 -53
  45. package/src/components/Slider/index.tsx +237 -190
  46. package/src/components/Slider/type.ts +27 -9
  47. package/src/components/XYPad/Area.tsx +52 -33
  48. package/src/components/XYPad/Thumb.tsx +121 -60
  49. package/src/components/XYPad/context.tsx +49 -0
  50. package/src/components/XYPad/index.tsx +338 -246
  51. package/src/components/_util/Placement.tsx +68 -0
  52. package/src/components/_util/VisuallyHiddenRangeInput.tsx +24 -0
  53. package/src/components/_util/css-length.ts +13 -0
  54. package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
  55. package/src/hooks/{useCallbackRef.ts → _internal/useCallbackRef.ts} +3 -1
  56. package/src/hooks/_internal/useCheckSteps.ts +170 -0
  57. package/src/hooks/useAnimationFrame.ts +17 -7
  58. package/src/hooks/useDrag.ts +39 -9
  59. package/src/hooks/useDragValue.ts +194 -0
  60. package/src/hooks/useEventListener.ts +11 -7
  61. package/src/hooks/useLongPress.ts +42 -8
  62. package/src/hooks/useMIDIAccess.ts +82 -11
  63. package/src/hooks/useMIDIInput.ts +45 -13
  64. package/src/hooks/useMIDIMessage.ts +23 -5
  65. package/src/hooks/useWheel.ts +29 -9
  66. package/src/index.ts +50 -27
  67. package/src/input-event.ts +32 -0
  68. package/dist/index.css +0 -536
  69. package/src/Globals.d.ts +0 -3
  70. package/src/components/AnimationCanvas/canvas.ts +0 -46
  71. package/src/components/Knob/index.css +0 -77
  72. package/src/components/NumberInput/InternalInput.tsx +0 -216
  73. package/src/components/NumberInput/index.css +0 -185
  74. package/src/components/NumberInput/type.ts +0 -58
  75. package/src/components/Piano/KeyLabel.tsx +0 -51
  76. package/src/components/Piano/context.tsx +0 -73
  77. package/src/components/Piano/index.css +0 -90
  78. package/src/components/Piano/key.tsx +0 -205
  79. package/src/components/Piano/keyboardShortcuts.ts +0 -31
  80. package/src/components/PointsEditor/index.css +0 -30
  81. package/src/components/Slider/Scale.tsx +0 -52
  82. package/src/components/Slider/ScaleOption.tsx +0 -103
  83. package/src/components/Slider/index.css +0 -112
  84. package/src/components/XYPad/index.css +0 -48
  85. package/src/components/_util/index.ts +0 -28
  86. package/src/components/_util/type.ts +0 -1
  87. package/src/hooks/useDragWithElement.ts +0 -100
  88. package/src/hooks/usePianoDrag.ts +0 -75
  89. package/src/hooks/useRefCallbackEvent.ts +0 -34
  90. package/src/styles/global.css +0 -19
@@ -1,91 +1,113 @@
1
- import {
2
- createContext,
3
- createRef,
4
- RefObject,
5
- useContext,
6
- useEffect,
7
- useRef,
8
- } from 'react'
9
- import { createStore, useStore } from 'zustand'
1
+ import { createContext, CSSProperties, RefObject, useContext } from 'react'
10
2
 
11
- import { Cursor } from '../_util'
3
+ import type { SelectionBoxRect } from '@tremolo-ui/dom'
4
+ import type {
5
+ InputEventOption,
6
+ ModifierState,
7
+ ModifierValue,
8
+ } from '@tremolo-ui/functions'
12
9
 
13
- type State = {
14
- containerElementRef: RefObject<HTMLDivElement | null>
10
+ import type { PointBaseType } from './Point'
11
+
12
+ /**
13
+ * What a `Point` tells the editor about itself, so that a selection can be
14
+ * moved without the editor knowing how the points are stored.
15
+ *
16
+ * Held behind a ref and rewritten on every render: the value changes on every
17
+ * frame of a drag, and a registry keyed on it would be rebuilt just as often.
18
+ */
19
+ export interface PointRegistration {
20
+ value: PointBaseType
21
+ min?: Partial<PointBaseType>
22
+ max?: Partial<PointBaseType>
23
+ readonly: boolean
24
+ onChange?: (value: PointBaseType) => void
25
+ /** The point's own element, to match the focus against. */
26
+ element: HTMLElement | null
27
+ /** The wheel option this point resolved, root inheritance applied. */
28
+ wheel: ModifierValue<InputEventOption> | null
29
+ }
30
+
31
+ export type PointsEditorContextValue = {
15
32
  disabled: boolean
16
33
  readonly: boolean
34
+ /** Inherited by every `Point`; `null` turns the wheel off. */
35
+ wheel: ModifierValue<InputEventOption> | null
36
+ /** Inherited by every `Point`; `null` turns the keyboard off. */
37
+ keyboard: ModifierValue<InputEventOption> | null
38
+ /** Inherited by every `Point`. See `PointsEditorProps.dragSensitivity`. */
39
+ dragSensitivity: ModifierValue<number>
17
40
  externalStyles: {
18
- userSelectNone?: boolean
19
- cursor?: Cursor
41
+ cursor?: CSSProperties['cursor']
20
42
  }
21
- }
22
-
23
- type Action = {
24
- setContainerElementRef: (
25
- containerElementRef: RefObject<HTMLDivElement | null>,
26
- ) => void
27
- }
28
43
 
29
- type PointsEditorStore = ReturnType<typeof createPointsEditorStore>
44
+ /**
45
+ * `Container` registers its element here; `Point` normalizes the pointer
46
+ * against it, so a point is placed by its position within the container.
47
+ */
48
+ containerRef: RefObject<HTMLDivElement | null>
30
49
 
31
- const createPointsEditorStore = (initProps: Partial<State & Action>) => {
32
- // const noteRange = initProps.noteRange || { first: 0, last: 127 }
33
- const DEFAULT_PROPS: State = {
34
- containerElementRef: createRef(),
35
- disabled: false,
36
- readonly: false,
37
- externalStyles: {
38
- userSelectNone: true,
39
- cursor: 'grabbing',
40
- },
41
- }
50
+ /** Whether points can be selected at all. See `PointsEditorProps.selectable`. */
51
+ selectable: boolean
52
+ /** Ids of the points currently selected. Always empty while `selectable` is off. */
53
+ selection: readonly string[]
54
+ /** Register a point so that a selection can move it with the rest. */
55
+ registerPoint: (id: string, entry: RefObject<PointRegistration>) => () => void
56
+ /**
57
+ * Whether the element is a point, or inside one. The registry knows which
58
+ * elements the points are, so nothing has to be recognised by a class name.
59
+ */
60
+ isPointElement: (element: Element | null | undefined) => boolean
61
+ /**
62
+ * A pointer went down on a point: works out the new selection and takes the
63
+ * snapshot the move will be measured against.
64
+ */
65
+ beginPointDrag: (id: string, modifiers: ModifierState) => void
66
+ /** Move everything the drag picked up, by one amount, clamped as one. */
67
+ movePointDrag: (delta: PointBaseType) => void
68
+ /**
69
+ * Move the selection by an amount that did not come from a drag — an arrow
70
+ * key or a wheel notch. The current values are the starting point.
71
+ */
72
+ nudgeSelection: (id: string, delta: PointBaseType) => void
73
+ /**
74
+ * Move the point that currently holds focus by one notch. The wheel listener
75
+ * belongs to `Container` rather than to every `Point`, so the lookup of who
76
+ * has focus happens here, where the registry is. Returns whether a point
77
+ * took it, so the caller knows whether to consume the event.
78
+ */
79
+ nudgeFocusedPoint: (
80
+ axis: 'x' | 'y',
81
+ direction: number,
82
+ modifiers: ModifierState,
83
+ ) => boolean
42
84
 
43
- return createStore<State & Action>()((set) => ({
44
- ...DEFAULT_PROPS,
45
- ...initProps,
46
- setContainerElementRef: (containerElementRef) => {
47
- set(() => ({
48
- containerElementRef,
49
- }))
50
- },
51
- }))
85
+ /** The selection box, while one is being dragged. */
86
+ selectionBox: SelectionBoxRect | null
87
+ beginSelectionBox: (at: PointBaseType, modifiers: ModifierState) => void
88
+ moveSelectionBox: (to: PointBaseType) => void
89
+ endSelectionBox: () => void
52
90
  }
53
91
 
54
- const PointsEditorContext = createContext<PointsEditorStore | null>(null)
55
-
56
- type PointsEditorProviderProps = React.PropsWithChildren<
57
- Partial<State & Action>
58
- >
59
-
60
- export function PointsEditorProvider({
61
- children,
62
- ...props
63
- }: PointsEditorProviderProps) {
64
- const storeRef = useRef<PointsEditorStore>(null)
65
- if (!storeRef.current) {
66
- storeRef.current = createPointsEditorStore(props)
67
- }
92
+ const PointsEditorContext =
93
+ /* @__PURE__ */ createContext<PointsEditorContextValue | null>(null)
68
94
 
69
- useEffect(() => {
70
- if (storeRef.current) {
71
- storeRef.current.setState(props)
72
- } else {
73
- storeRef.current = createPointsEditorStore(props)
74
- }
75
- }, [props])
76
-
77
- return (
78
- <PointsEditorContext.Provider value={storeRef.current}>
79
- {children}
80
- </PointsEditorContext.Provider>
81
- )
82
- }
95
+ export const PointsEditorProvider = PointsEditorContext.Provider
83
96
 
97
+ /**
98
+ * The settings `Root` was given, for the subcomponents to read. A point's
99
+ * value still belongs to the `Point` that draws it; what the root keeps is
100
+ * which points are selected, and a registry of who they are.
101
+ */
102
+ export function usePointsEditorContext(): PointsEditorContextValue
103
+ export function usePointsEditorContext<T>(
104
+ selector: (state: PointsEditorContextValue) => T,
105
+ ): T
84
106
  export function usePointsEditorContext<T>(
85
- selector: (state: State & Action) => T,
86
- ): T {
87
- const store = useContext(PointsEditorContext)
88
- if (!store)
107
+ selector?: (state: PointsEditorContextValue) => T,
108
+ ) {
109
+ const context = useContext(PointsEditorContext)
110
+ if (!context)
89
111
  throw new Error('Missing PointsEditorContext.Provider in the tree')
90
- return useStore(store, selector)
112
+ return selector ? selector(context) : context
91
113
  }