@tremolo-ui/react 0.0.8 → 0.0.10

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 (148) hide show
  1. package/dist/cjs/index.js +439 -275
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +9 -4
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/AnimationKnob/index.d.ts +2 -0
  6. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
  7. package/dist/cjs/types/components/DragObserver/index.d.ts +3 -1
  8. package/dist/cjs/types/components/DragObserver/index.d.ts.map +1 -1
  9. package/dist/cjs/types/components/Knob/index.d.ts +2 -0
  10. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  11. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +7 -0
  12. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -0
  13. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +7 -0
  14. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -0
  15. package/dist/cjs/types/components/NumberInput/Stepper.d.ts +8 -0
  16. package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -0
  17. package/dist/cjs/types/components/NumberInput/context.d.ts +27 -0
  18. package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -0
  19. package/dist/cjs/types/components/NumberInput/index.d.ts +31 -16
  20. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  21. package/dist/cjs/types/components/Piano/BlackKey.d.ts +1 -0
  22. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +2 -0
  24. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
  25. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +3 -0
  26. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
  27. package/dist/cjs/types/components/Piano/index.d.ts +6 -4
  28. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  29. package/dist/cjs/types/components/Piano/key.d.ts +8 -2
  30. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  31. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -0
  32. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
  33. package/dist/cjs/types/components/Slider/Scale.d.ts +2 -0
  34. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
  35. package/dist/cjs/types/components/Slider/ScaleOption.d.ts +2 -0
  36. package/dist/cjs/types/components/Slider/ScaleOption.d.ts.map +1 -1
  37. package/dist/cjs/types/components/Slider/Thumb.d.ts +4 -0
  38. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  39. package/dist/cjs/types/components/Slider/Track.d.ts +6 -1
  40. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  41. package/dist/cjs/types/components/Slider/context.d.ts +15 -12
  42. package/dist/cjs/types/components/Slider/context.d.ts.map +1 -1
  43. package/dist/cjs/types/components/Slider/index.d.ts +8 -5
  44. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  45. package/dist/cjs/types/components/WheelObserver/index.d.ts +1 -0
  46. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
  47. package/dist/cjs/types/components/XYPad/Area.d.ts +2 -0
  48. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
  49. package/dist/cjs/types/components/XYPad/Thumb.d.ts +3 -0
  50. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
  51. package/dist/cjs/types/components/XYPad/index.d.ts +4 -0
  52. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  53. package/dist/cjs/types/hooks/useAnimationFrame.d.ts +3 -0
  54. package/dist/cjs/types/hooks/useAnimationFrame.d.ts.map +1 -0
  55. package/dist/cjs/types/hooks/useDrag.d.ts +15 -0
  56. package/dist/cjs/types/hooks/useDrag.d.ts.map +1 -0
  57. package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts +1 -4
  58. package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts.map +1 -1
  59. package/dist/esm/index.js +437 -266
  60. package/dist/esm/index.js.map +1 -1
  61. package/dist/esm/types/components/AnimationCanvas/index.d.ts +9 -4
  62. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  63. package/dist/esm/types/components/AnimationKnob/index.d.ts +2 -0
  64. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
  65. package/dist/esm/types/components/DragObserver/index.d.ts +3 -1
  66. package/dist/esm/types/components/DragObserver/index.d.ts.map +1 -1
  67. package/dist/esm/types/components/Knob/index.d.ts +2 -0
  68. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  69. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +7 -0
  70. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -0
  71. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +7 -0
  72. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -0
  73. package/dist/esm/types/components/NumberInput/Stepper.d.ts +8 -0
  74. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -0
  75. package/dist/esm/types/components/NumberInput/context.d.ts +27 -0
  76. package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -0
  77. package/dist/esm/types/components/NumberInput/index.d.ts +31 -16
  78. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  79. package/dist/esm/types/components/Piano/BlackKey.d.ts +1 -0
  80. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
  81. package/dist/esm/types/components/Piano/KeyLabel.d.ts +2 -0
  82. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
  83. package/dist/esm/types/components/Piano/WhiteKey.d.ts +3 -0
  84. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
  85. package/dist/esm/types/components/Piano/index.d.ts +6 -4
  86. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  87. package/dist/esm/types/components/Piano/key.d.ts +8 -2
  88. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  89. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -0
  90. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
  91. package/dist/esm/types/components/Slider/Scale.d.ts +2 -0
  92. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
  93. package/dist/esm/types/components/Slider/ScaleOption.d.ts +2 -0
  94. package/dist/esm/types/components/Slider/ScaleOption.d.ts.map +1 -1
  95. package/dist/esm/types/components/Slider/Thumb.d.ts +4 -0
  96. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  97. package/dist/esm/types/components/Slider/Track.d.ts +6 -1
  98. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  99. package/dist/esm/types/components/Slider/context.d.ts +15 -12
  100. package/dist/esm/types/components/Slider/context.d.ts.map +1 -1
  101. package/dist/esm/types/components/Slider/index.d.ts +8 -5
  102. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  103. package/dist/esm/types/components/WheelObserver/index.d.ts +1 -0
  104. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
  105. package/dist/esm/types/components/XYPad/Area.d.ts +2 -0
  106. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
  107. package/dist/esm/types/components/XYPad/Thumb.d.ts +3 -0
  108. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
  109. package/dist/esm/types/components/XYPad/index.d.ts +4 -0
  110. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  111. package/dist/esm/types/hooks/useAnimationFrame.d.ts +3 -0
  112. package/dist/esm/types/hooks/useAnimationFrame.d.ts.map +1 -0
  113. package/dist/esm/types/hooks/useDrag.d.ts +15 -0
  114. package/dist/esm/types/hooks/useDrag.d.ts.map +1 -0
  115. package/dist/esm/types/hooks/useRefCallbackEvent.d.ts +1 -4
  116. package/dist/esm/types/hooks/useRefCallbackEvent.d.ts.map +1 -1
  117. package/dist/styles/index.css +110 -13
  118. package/package.json +8 -5
  119. package/src/components/AnimationCanvas/index.tsx +13 -4
  120. package/src/components/AnimationKnob/index.tsx +24 -42
  121. package/src/components/DragObserver/index.tsx +14 -52
  122. package/src/components/Knob/index.tsx +23 -40
  123. package/src/components/NumberInput/DecrementStepper.tsx +52 -0
  124. package/src/components/NumberInput/IncrementStepper.tsx +52 -0
  125. package/src/components/NumberInput/Stepper.tsx +25 -0
  126. package/src/components/NumberInput/context.tsx +150 -0
  127. package/src/components/NumberInput/index.css +102 -13
  128. package/src/components/NumberInput/index.tsx +164 -55
  129. package/src/components/Piano/BlackKey.tsx +15 -11
  130. package/src/components/Piano/KeyLabel.tsx +2 -0
  131. package/src/components/Piano/WhiteKey.tsx +17 -11
  132. package/src/components/Piano/index.tsx +16 -14
  133. package/src/components/Piano/key.ts +8 -2
  134. package/src/components/Piano/keyboardShortcuts.ts +2 -0
  135. package/src/components/Slider/Scale.tsx +8 -6
  136. package/src/components/Slider/ScaleOption.tsx +8 -6
  137. package/src/components/Slider/Thumb.tsx +7 -3
  138. package/src/components/Slider/Track.tsx +27 -15
  139. package/src/components/Slider/context.tsx +65 -51
  140. package/src/components/Slider/index.css +8 -0
  141. package/src/components/Slider/index.tsx +19 -10
  142. package/src/components/WheelObserver/index.tsx +1 -0
  143. package/src/components/XYPad/Area.tsx +2 -0
  144. package/src/components/XYPad/Thumb.tsx +3 -0
  145. package/src/components/XYPad/index.tsx +6 -2
  146. package/src/hooks/useAnimationFrame.ts +18 -0
  147. package/src/hooks/useDrag.ts +92 -0
  148. package/src/hooks/useRefCallbackEvent.ts +4 -7
@@ -1,5 +1,6 @@
1
1
  import React, { ReactElement } from 'react';
2
2
  import { InputEventOption } from '@tremolo-ui/functions';
3
+ /** @category XYPad */
3
4
  export interface ValueOptions {
4
5
  value: number;
5
6
  min: number;
@@ -16,6 +17,7 @@ export interface ValueOptions {
16
17
  */
17
18
  keyboard?: InputEventOption | null;
18
19
  }
20
+ /** @category XYPad */
19
21
  export interface XYPadProps {
20
22
  x: ValueOptions;
21
23
  y: ValueOptions;
@@ -26,12 +28,14 @@ export interface XYPadProps {
26
28
  /** \<XYPadThumb /> | \<XYPadArea /> */
27
29
  children?: ReactElement | ReactElement[];
28
30
  }
31
+ /** @category XYPad */
29
32
  export interface XYPadMethods {
30
33
  focus: () => void;
31
34
  blur: () => void;
32
35
  }
33
36
  /**
34
37
  * Simple XYPad
38
+ * @category XYPad
35
39
  */
36
40
  export declare const XYPad: React.ForwardRefExoticComponent<XYPadProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof XYPadProps> & React.RefAttributes<XYPadMethods>>;
37
41
  export * from './Thumb';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/XYPad/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAGZ,YAAY,EAKb,MAAM,OAAO,CAAA;AAEd,OAAO,EAML,gBAAgB,EAEjB,MAAM,uBAAuB,CAAA;AAS9B,MAAM,WAAW,YAAY;IAE3B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IAGX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;OAEG;IACH,KAAK,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC/B;;OAEG;IACH,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,YAAY,CAAA;IACf,CAAC,EAAE,YAAY,CAAA;IAEf,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACnD,uCAAuC;IACvC,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAA;CACzC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB;AAaD;;GAEG;AACH,eAAO,MAAM,KAAK,sMA0OjB,CAAA;AAED,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/XYPad/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAGZ,YAAY,EAKb,MAAM,OAAO,CAAA;AAEd,OAAO,EAML,gBAAgB,EAEjB,MAAM,uBAAuB,CAAA;AAS9B,sBAAsB;AACtB,MAAM,WAAW,YAAY;IAE3B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IAGX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;OAEG;IACH,KAAK,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC/B;;OAEG;IACH,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACnC;AAED,sBAAsB;AACtB,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,YAAY,CAAA;IACf,CAAC,EAAE,YAAY,CAAA;IAEf,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACnD,uCAAuC;IACvC,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAA;CACzC;AAED,sBAAsB;AACtB,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB;AAaD;;;GAGG;AACH,eAAO,MAAM,KAAK,sMA0OjB,CAAA;AAED,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { DependencyList } from 'react';
2
+ export declare const useAnimationFrame: (callback?: () => void, deps?: DependencyList) => void;
3
+ //# sourceMappingURL=useAnimationFrame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnimationFrame.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useAnimationFrame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkC,MAAM,OAAO,CAAA;AAEtE,eAAO,MAAM,iBAAiB,iCAEtB,cAAc,SAarB,CAAA"}
@@ -0,0 +1,15 @@
1
+ interface Props {
2
+ threshold?: number;
3
+ onDrag: (x: number, y: number, deltaX: number, deltaY: number) => void;
4
+ onDragStart?: () => void;
5
+ onDragEnd?: () => void;
6
+ }
7
+ /**
8
+ * @returns [refCallback, pointerDownHandler]
9
+ */
10
+ export declare function useDrag<T extends Element>({ threshold, onDrag, onDragStart, onDragEnd, }: Props): [
11
+ (div: EventTarget | null) => void,
12
+ (event: React.PointerEvent<T>) => void
13
+ ];
14
+ export {};
15
+ //# sourceMappingURL=useDrag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDrag.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useDrag.ts"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACtE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EAAE,EACzC,SAAa,EACb,MAAM,EACN,WAAW,EACX,SAAS,GACV,EAAE,KAAK,GAAG;IACT,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI;IACjC,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI;CACvC,CAmEA"}
@@ -1,5 +1,2 @@
1
- type Options = boolean | AddEventListenerOptions;
2
- type CallbackFunc = (div: EventTarget | null) => void;
3
- export declare function useRefCallbackEvent<K extends keyof DocumentEventMap>(event: K, handler: (event: DocumentEventMap[K]) => void, options?: Options, deps?: React.DependencyList): CallbackFunc;
4
- export {};
1
+ export declare function useRefCallbackEvent<K extends keyof DocumentEventMap>(event: K, handler: (event: DocumentEventMap[K]) => void, options?: boolean | AddEventListenerOptions, deps?: React.DependencyList): (div: EventTarget | null) => void;
5
2
  //# sourceMappingURL=useRefCallbackEvent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useRefCallbackEvent.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useRefCallbackEvent.ts"],"names":[],"mappings":"AAEA,KAAK,OAAO,GAAG,OAAO,GAAG,uBAAuB,CAAA;AAChD,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;AAErD,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAClE,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,CAAC,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE,KAAK,CAAC,cAAc,GAC1B,YAAY,CAAA"}
1
+ {"version":3,"file":"useRefCallbackEvent.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useRefCallbackEvent.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAClE,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,EAC3C,IAAI,CAAC,EAAE,KAAK,CAAC,cAAc,GAC1B,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA"}
@@ -56,30 +56,119 @@
56
56
  color: #eee;
57
57
  }
58
58
 
59
- .tremolo-number-input {
60
- display: inline-block;
61
- font: inherit;
62
- margin: 0px;
63
- padding-top: 6px;
64
- padding-bottom: 6px;
65
- padding-left: 10px;
66
- padding-right: 10px;
59
+ .tremolo-number-input-wrapper {
60
+ --tremolo-number-input-border-radius: 4px;
61
+ --tremolo-number-input-stepper-width: 20px;
62
+
63
+ display: inline-flex;
64
+ flex-direction: row;
65
+ position: relative;
66
+
67
67
  border-style: solid;
68
68
  border-width: 1px;
69
69
  border-color: #bbb;
70
- border-radius: 4px;
71
- outline: 0px;
72
- transition: all 0.1s linear;
73
- appearance: textfield;
70
+ border-radius: var(--tremolo-number-input-border-radius);
71
+
72
+ width: 140px;
73
+ height: min-content;
74
74
 
75
75
  &:hover {
76
76
  border-color: var(--tremolo-theme-color);
77
77
  }
78
78
 
79
- &:focus {
79
+ &:not(:focus-within):has(> .tremolo-number-input[data-error='true']) {
80
+ border-color: #f34343;
81
+ }
82
+
83
+ &:focus-within {
80
84
  border-color: var(--tremolo-theme-color);
81
85
  box-shadow: 0px 0px 0px 2px rgba(var(--tremolo-theme-color-rgb), 0.1);
82
86
  }
87
+
88
+ &[data-stepper='false'] {
89
+ --tremolo-number-input-stepper-width: 0px;
90
+ }
91
+ }
92
+
93
+ .tremolo-number-input {
94
+ display: block;
95
+ width: calc(100% - 20px - var(--tremolo-number-input-stepper-width));
96
+ background-color: inherit;
97
+ font: inherit;
98
+ padding: 6px 10px;
99
+
100
+ margin: 0;
101
+ border: none;
102
+ outline: none;
103
+ appearance: textfield;
104
+ }
105
+
106
+ .tremolo-number-input-stepper {
107
+ display: flex;
108
+ flex-direction: column;
109
+ height: 100%;
110
+ position: absolute;
111
+ top: 0;
112
+ right: 0;
113
+ width: var(--tremolo-number-input-stepper-width);
114
+ transition: all 0.1s linear;
115
+
116
+ &[data-dynamic='true'] {
117
+ opacity: 0;
118
+ transform: translateX(40%);
119
+ }
120
+ }
121
+
122
+ .tremolo-number-input-wrapper:hover .tremolo-number-input-stepper {
123
+ opacity: 1;
124
+ transform: translateX(0);
125
+ }
126
+
127
+ .tremolo-number-input-increment-stepper {
128
+ border-inline-start-width: 1px;
129
+ border-inline-start-style: solid;
130
+ border-inline-start-color: #ccc;
131
+ font-size: calc(0.75rem);
132
+ display: flex;
133
+ justify-content: center;
134
+ align-items: center;
135
+ flex: 1 1 0%;
136
+ max-height: 50%;
137
+ user-select: none;
138
+ cursor: pointer;
139
+ border-top-right-radius: var(--tremolo-number-input-border-radius);
140
+
141
+ &[aria-disabled='false']:active {
142
+ background-color: #3443560e;
143
+ }
144
+
145
+ &[aria-disabled='true'] {
146
+ cursor: not-allowed;
147
+ }
148
+ }
149
+
150
+ .tremolo-number-input-decrement-stepper {
151
+ border-inline-start-width: 1px;
152
+ border-inline-start-style: solid;
153
+ border-inline-start-color: #ccc;
154
+ border-top: 1px solid #ccc;
155
+ font-size: calc(0.75rem);
156
+ display: flex;
157
+ justify-content: center;
158
+ align-items: center;
159
+ flex: 1 1 0%;
160
+ max-height: 50%;
161
+ user-select: none;
162
+ cursor: pointer;
163
+ border-bottom-right-radius: var(--tremolo-number-input-border-radius);
164
+
165
+ &[aria-disabled='false']:active {
166
+ background-color: #3443560e;
167
+ }
168
+
169
+ &[aria-disabled='true'] {
170
+ cursor: not-allowed;
171
+ }
83
172
  }
84
173
 
85
174
  .tremolo-piano {
@@ -183,6 +272,14 @@
183
272
 
184
273
  position: relative;
185
274
 
275
+ &[data-vertical='false'] {
276
+ min-width: 50px;
277
+ }
278
+
279
+ &[data-vertical='true'] {
280
+ min-height: 50px;
281
+ }
282
+
186
283
  &:hover {
187
284
  --active: #6387e9;
188
285
  --inactive: #e0e0e0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tremolo-ui/react",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "React component library for audio app",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -29,7 +29,7 @@
29
29
  "default": "./dist/esm/index.js"
30
30
  }
31
31
  },
32
- "./styles": "./src/styles"
32
+ "./styles/index.css": "./dist/styles/index.css"
33
33
  },
34
34
  "files": [
35
35
  "dist",
@@ -66,6 +66,7 @@
66
66
  "@storybook/test": "^8.3.3",
67
67
  "@testing-library/react": "^16.0.1",
68
68
  "jest-environment-jsdom": "^29.7.0",
69
+ "jotai": "^2.12.1",
69
70
  "react": "^18.3.1",
70
71
  "react-component-benchmark": "^2.0.0",
71
72
  "react-dom": "^18.3.1",
@@ -73,11 +74,13 @@
73
74
  "tone": "^15.0.4"
74
75
  },
75
76
  "peerDependencies": {
76
- "react": "18.x",
77
- "react-dom": "18.x"
77
+ "@tremolo-ui/functions": "0.0.10",
78
+ "react": "18.x || 19.x",
79
+ "react-dom": "18.x || 19.x"
78
80
  },
79
81
  "dependencies": {
80
82
  "@tremolo-ui/functions": "file:../functions",
81
- "clsx": "^2.1.1"
83
+ "clsx": "^2.1.1",
84
+ "zustand": "^5.0.3"
82
85
  }
83
86
  }
@@ -24,14 +24,18 @@ function setDprConfig(
24
24
  canvas.style.height = `${height}px`
25
25
  }
26
26
 
27
+ /** @category AnimationCanvas */
27
28
  export type DrawFunction = (
29
+ // TODO: saving scale and translate
30
+ // context: Omit<CanvasRenderingContext2D, 'save' | 'restore' | 'scale'>,
28
31
  context: CanvasRenderingContext2D,
29
32
  width: MutableRefObject<number>,
30
33
  height: MutableRefObject<number>,
31
34
  count: number,
32
35
  ) => void
33
36
 
34
- interface CommonProps {
37
+ /** @category AnimationCanvas */
38
+ export interface CommonProps {
35
39
  /**
36
40
  * @see https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/getContext#contextattributes
37
41
  */
@@ -40,22 +44,26 @@ interface CommonProps {
40
44
  draw: DrawFunction
41
45
  }
42
46
 
43
- interface AbsoluteSizingProps {
47
+ /** @category AnimationCanvas */
48
+ export interface AbsoluteSizingProps {
44
49
  width?: number
45
50
  height?: number
46
51
  }
47
52
 
48
- interface RelativeSizingProps {
53
+ /** @category AnimationCanvas */
54
+ export interface RelativeSizingProps {
49
55
  relativeSize?: boolean
50
56
  reduceFlickering?: boolean
51
57
  }
52
58
 
59
+ /** @category AnimationCanvas */
53
60
  export type AnimationCanvasProps = AbsoluteSizingProps &
54
61
  RelativeSizingProps &
55
62
  CommonProps
56
63
 
57
64
  /**
58
65
  * A simple animatable canvas with requestAnimationFrame()
66
+ * @category AnimationCanvas
59
67
  */
60
68
  export function AnimationCanvas(
61
69
  props: AbsoluteSizingProps &
@@ -108,7 +116,7 @@ export function AnimationCanvas({
108
116
  const canvas = canvasRef.current
109
117
  const context = canvas.getContext('2d', options)
110
118
  if (!context) throw new Error('Cannot get canvas context.')
111
- const dpr = window.devicePixelRatio
119
+ const dpr = globalThis.window.devicePixelRatio
112
120
 
113
121
  if (relativeSize) {
114
122
  const parent = canvas.parentElement
@@ -122,6 +130,7 @@ export function AnimationCanvas({
122
130
  // https://zenn.dev/megeton/articles/be1c677e174c84#%E6%A0%B9%E6%9C%AC%E7%9A%84%E3%81%AB-resizeobserver-loop-limit-exceeded-%E3%81%8C%E5%87%BA%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%AB%E5%90%91%E3%81%8D%E5%90%88%E3%81%86
123
131
  for (const entry of entries) {
124
132
  // Prevents loss of some context when the canvas is resized
133
+ // TODO: saving scale and translate
125
134
  const contextMemo = {} as DrawingContext
126
135
  for (const prop of drawingState) {
127
136
  ;(contextMemo[prop] as DrawingStateValue) = context[prop]
@@ -15,11 +15,12 @@ import {
15
15
  InputEventOption,
16
16
  } from '@tremolo-ui/functions'
17
17
 
18
- import { useEventListener } from '../../hooks/useEventListener'
18
+ import { useDrag } from '../../hooks/useDrag'
19
19
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
20
20
  import { addNoSelect, removeNoSelect } from '../_util'
21
21
  import { AnimationCanvas, DrawFunction } from '../AnimationCanvas'
22
22
 
23
+ /** @category AnimationKnob */
23
24
  export type AnimationKnobProps = {
24
25
  // required
25
26
  value: number
@@ -77,6 +78,7 @@ export type AnimationKnobProps = {
77
78
 
78
79
  /**
79
80
  * simple rotary Knob.
81
+ * @category AnimationKnob
80
82
  */
81
83
  export function AnimationKnob({
82
84
  value,
@@ -107,35 +109,21 @@ export function AnimationKnob({
107
109
  }: AnimationKnobProps &
108
110
  Omit<ComponentPropsWithoutRef<'div'>, keyof AnimationKnobProps>) {
109
111
  // -- state and ref ---
110
- // const [privateValue, setPrivateValue] = useState(value);
111
- const dragOffsetY = useRef<number | undefined>(undefined)
112
+ const valueRef = useRef(0)
112
113
 
113
114
  // --- interpret props ---
114
- // const percent = normalizeValue(value, min, max, skew)
115
-
116
115
  const isRelativeSize =
117
116
  typeof (size ?? width) == 'string' || typeof height == 'string'
118
117
 
119
- const handleEvent = (
120
- event:
121
- | MouseEvent
122
- | React.MouseEvent<HTMLDivElement, MouseEvent>
123
- | TouchEvent,
124
- ) => {
125
- const isTouch = event instanceof TouchEvent
126
- if (isTouch && event.cancelable) event.preventDefault()
127
- if (dragOffsetY.current) {
128
- if (bodyNoSelect) addNoSelect()
129
- const screenY = isTouch ? event.touches[0].screenY : event.screenY
130
- const delta = dragOffsetY.current - screenY
131
- if (Math.abs(delta) < 1) return
132
- dragOffsetY.current = screenY
133
- const n = normalizeValue(value, min, max, skew)
134
- const v = rawValue(n + delta / 100, min, max, skew)
118
+ const onDrag = useCallback(
119
+ (_x: number, y: number) => {
120
+ if (!onChange) return
121
+ const v = rawValue(valueRef.current - y / 100, min, max, skew)
135
122
  const v2 = clamp(stepValue(v, step), min, max)
136
- if (onChange) onChange(v2)
137
- }
138
- }
123
+ onChange(v2)
124
+ },
125
+ [max, min, onChange, skew, step],
126
+ )
139
127
 
140
128
  const handleKeyDown = useCallback(
141
129
  (event: React.KeyboardEvent<HTMLDivElement>) => {
@@ -159,6 +147,17 @@ export function AnimationKnob({
159
147
  )
160
148
 
161
149
  // --- hooks ---
150
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag<HTMLDivElement>({
151
+ onDrag: onDrag,
152
+ onDragStart: () => {
153
+ valueRef.current = normalizeValue(value, min, max, skew)
154
+ if (bodyNoSelect) addNoSelect()
155
+ },
156
+ onDragEnd: () => {
157
+ if (bodyNoSelect) removeNoSelect()
158
+ },
159
+ })
160
+
162
161
  const wheelRefCallback = useRefCallbackEvent(
163
162
  'wheel',
164
163
  (event) => {
@@ -178,20 +177,6 @@ export function AnimationKnob({
178
177
  [wheel, value, min, max, skew, step, onChange],
179
178
  )
180
179
 
181
- const touchMoveRefCallback = useRefCallbackEvent(
182
- 'touchmove',
183
- handleEvent,
184
- { passive: false },
185
- [value, min, max, skew, step, onChange, bodyNoSelect],
186
- )
187
-
188
- useEventListener(window, 'mousemove', handleEvent)
189
-
190
- useEventListener(window, 'pointerup', () => {
191
- dragOffsetY.current = undefined
192
- if (bodyNoSelect) removeNoSelect()
193
- })
194
-
195
180
  return (
196
181
  <div
197
182
  className={clsx('tremolo-animation-knob', className)}
@@ -212,10 +197,7 @@ export function AnimationKnob({
212
197
  WebkitTapHighlightColor: 'transparent',
213
198
  ...style,
214
199
  }}
215
- onPointerDown={(event) => {
216
- dragOffsetY.current = event.screenY
217
- handleEvent(event)
218
- }}
200
+ onPointerDown={pointerDownHandler}
219
201
  onDoubleClick={() => {
220
202
  if (enableDoubleClickDefault && onChange) {
221
203
  onChange(defaultValue)
@@ -1,8 +1,8 @@
1
- import { ComponentPropsWithoutRef, ElementType, ReactNode, useRef } from 'react'
1
+ import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
2
2
 
3
- import { useEventListener } from '../../hooks/useEventListener'
4
- import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
3
+ import { useDrag } from '../../hooks/useDrag'
5
4
 
5
+ /** @category DragObserver */
6
6
  export interface DragObserverProps<T extends ElementType> {
7
7
  /**
8
8
  * React.ElementType
@@ -17,75 +17,37 @@ export interface DragObserverProps<T extends ElementType> {
17
17
  threshold?: number
18
18
 
19
19
  children?: ReactNode
20
- onDrag: (x: number, y: number) => void
20
+ onDrag: (x: number, y: number, deltaX: number, deltaY: number) => void
21
21
  onDragStart?: () => void
22
22
  onDragEnd?: () => void
23
23
  }
24
24
 
25
+ /** @category DragObserver */
25
26
  export function DragObserver<T extends ElementType = 'div'>(
26
27
  props: DragObserverProps<T> &
27
28
  Omit<ComponentPropsWithoutRef<T>, keyof DragObserverProps<T>>,
28
29
  ) {
29
30
  const {
31
+ as: Component = 'div',
32
+ threshold = 1,
30
33
  children,
31
34
  onDrag,
32
35
  onDragStart,
33
36
  onDragEnd,
34
- as: Component = 'div',
35
37
  ...attributes
36
38
  } = props
37
39
 
38
- const dragOffsetX = useRef<number | undefined>(undefined)
39
- const dragOffsetY = useRef<number | undefined>(undefined)
40
-
41
- const handleEvent = (
42
- event:
43
- | MouseEvent
44
- | React.MouseEvent<HTMLDivElement, MouseEvent>
45
- | TouchEvent,
46
- ) => {
47
- const isTouch = event instanceof TouchEvent
48
- if (isTouch && event.cancelable) event.preventDefault()
49
- const screenX = isTouch ? event.touches[0].screenX : event.screenX
50
- const screenY = isTouch ? event.touches[0].screenY : event.screenY
51
- let deltaX = 0,
52
- deltaY = 0
53
- if (dragOffsetX.current) {
54
- deltaX = screenX - dragOffsetX.current
55
- dragOffsetX.current = screenX
56
- }
57
- if (dragOffsetY.current) {
58
- deltaY = screenY - dragOffsetY.current
59
- dragOffsetY.current = screenY
60
- }
61
- if (Math.abs(deltaX) < 1 && Math.abs(deltaY) < 1) return
62
- if (onDrag) onDrag(deltaX, deltaY)
63
- }
64
-
65
- const touchMoveRefCallback = useRefCallbackEvent(
66
- 'touchmove',
67
- handleEvent,
68
- { passive: false },
69
- [onDrag],
70
- )
71
-
72
- useEventListener(window, 'mousemove', handleEvent)
73
-
74
- useEventListener(window, 'pointerup', () => {
75
- dragOffsetX.current = undefined
76
- dragOffsetY.current = undefined
77
- onDragEnd?.()
40
+ const [refCallback, pointerDownHandler] = useDrag({
41
+ threshold: threshold,
42
+ onDrag: onDrag,
43
+ onDragStart: onDragStart,
44
+ onDragEnd: onDragEnd,
78
45
  })
79
46
 
80
47
  return (
81
48
  <Component
82
- ref={touchMoveRefCallback}
83
- onPointerDown={(event) => {
84
- dragOffsetX.current = event.screenX
85
- dragOffsetY.current = event.screenY
86
- handleEvent(event)
87
- onDragStart?.()
88
- }}
49
+ ref={refCallback}
50
+ onPointerDown={pointerDownHandler}
89
51
  {...attributes}
90
52
  >
91
53
  {children}
@@ -10,10 +10,11 @@ import {
10
10
  stepValue,
11
11
  } from '@tremolo-ui/functions'
12
12
 
13
- import { useEventListener } from '../../hooks/useEventListener'
13
+ import { useDrag } from '../../hooks/useDrag'
14
14
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
15
15
  import { addNoSelect, removeNoSelect } from '../_util'
16
16
 
17
+ /** @category Knob */
17
18
  export interface KnobProps {
18
19
  // required
19
20
  value: number
@@ -69,6 +70,7 @@ export interface KnobProps {
69
70
  onChange?: (value: number) => void
70
71
  }
71
72
 
73
+ /** @category Knob */
72
74
  export function Knob({
73
75
  value,
74
76
  min,
@@ -93,8 +95,7 @@ export function Knob({
93
95
  classes,
94
96
  ...props
95
97
  }: KnobProps & Omit<ComponentPropsWithoutRef<'svg'>, keyof KnobProps>) {
96
- // -- state and ref ---
97
- const dragOffsetY = useRef<number | undefined>(undefined)
98
+ const valueRef = useRef(0)
98
99
 
99
100
  // --- interpret props ---
100
101
  const padding = 8 // %
@@ -105,27 +106,15 @@ export function Knob({
105
106
  const s = normalizeValue(startValue, min, max, skew)
106
107
 
107
108
  // --- internal functions ---
108
- const handleEvent = (
109
- event:
110
- | MouseEvent
111
- | React.MouseEvent<HTMLOrSVGElement, MouseEvent>
112
- | TouchEvent,
113
- ) => {
114
- if (!onChange || readonly) return
115
- const isTouch = event instanceof TouchEvent
116
- if (isTouch && event.cancelable) event.preventDefault()
117
- if (dragOffsetY.current) {
118
- if (bodyNoSelect) addNoSelect()
119
- const screenY = isTouch ? event.touches[0].screenY : event.screenY
120
- const delta = dragOffsetY.current - screenY
121
- if (Math.abs(delta) < 1) return
122
- dragOffsetY.current = screenY
123
- const n = normalizeValue(value, min, max, skew)
124
- const v = rawValue(n + delta / 100, min, max, skew)
109
+ const onDrag = useCallback(
110
+ (_x: number, y: number) => {
111
+ if (!onChange || readonly) return
112
+ const v = rawValue(valueRef.current - y / 100, min, max, skew)
125
113
  const v2 = clamp(stepValue(v, step), min, max)
126
114
  onChange(v2)
127
- }
128
- }
115
+ },
116
+ [max, min, onChange, readonly, skew, step],
117
+ )
129
118
 
130
119
  const handleKeyDown = useCallback(
131
120
  (event: React.KeyboardEvent<HTMLOrSVGElement>) => {
@@ -149,6 +138,17 @@ export function Knob({
149
138
  )
150
139
 
151
140
  // --- hooks ---
141
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag<SVGElement>({
142
+ onDrag: onDrag,
143
+ onDragStart: () => {
144
+ valueRef.current = normalizeValue(value, min, max, skew)
145
+ if (bodyNoSelect) addNoSelect()
146
+ },
147
+ onDragEnd: () => {
148
+ if (bodyNoSelect) removeNoSelect()
149
+ },
150
+ })
151
+
152
152
  const wheelRefCallback = useRefCallbackEvent(
153
153
  'wheel',
154
154
  (event) => {
@@ -168,20 +168,6 @@ export function Knob({
168
168
  [wheel, value, min, max, skew, step, onChange],
169
169
  )
170
170
 
171
- const touchMoveRefCallback = useRefCallbackEvent(
172
- 'touchmove',
173
- handleEvent,
174
- { passive: false },
175
- [value, min, max, skew, step, onChange, bodyNoSelect],
176
- )
177
-
178
- useEventListener(window, 'mousemove', handleEvent)
179
-
180
- useEventListener(window, 'pointerup', () => {
181
- dragOffsetY.current = undefined
182
- if (bodyNoSelect) removeNoSelect()
183
- })
184
-
185
171
  const center = size / 2
186
172
  const r1 = -135
187
173
  const r2 = -135 + Math.min(p, s) * 270
@@ -211,10 +197,7 @@ export function Knob({
211
197
  aria-valuemax={max}
212
198
  aria-disabled={disabled}
213
199
  aria-readonly={readonly}
214
- onPointerDown={(event) => {
215
- dragOffsetY.current = event.screenY
216
- handleEvent(event)
217
- }}
200
+ onPointerDown={pointerDownHandler}
218
201
  onDoubleClick={() => {
219
202
  if (enableDoubleClickDefault && onChange) {
220
203
  onChange(defaultValue)