@tremolo-ui/react 0.1.4 → 0.1.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tremolo-ui/react",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "React component library for audio app",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -16,7 +16,7 @@
16
16
  "serve": "http-server ./storybook-static",
17
17
  "lint": "eslint --fix --ext .ts,.tsx .",
18
18
  "format": "prettier --write .",
19
- "test": "echo 'No test yet.'",
19
+ "test": "jest",
20
20
  "build:package": "tsc && tsdown",
21
21
  "clean": "npm run clean:build && npm run clean:sb",
22
22
  "clean:build": "rimraf ./dist",
@@ -39,14 +39,14 @@
39
39
  "react": "^19",
40
40
  "react-dom": "^19",
41
41
  "storybook": "^8.6.7",
42
- "tone": "^15.0.4"
42
+ "tone": "^15"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": "^18 ||^19",
46
46
  "react-dom": "^18 || ^19"
47
47
  },
48
48
  "dependencies": {
49
- "@tremolo-ui/functions": "^0.1.4",
49
+ "@tremolo-ui/functions": "^0.1.5",
50
50
  "clsx": "^2.1.1",
51
51
  "zustand": "^5.0.3"
52
52
  },
@@ -78,14 +78,19 @@
78
78
  "exports": {
79
79
  ".": {
80
80
  "require": {
81
- "default": "./dist/index.cjs",
82
- "types": "./dist/index.d.cts"
81
+ "types": "./dist/index.d.cts",
82
+ "default": "./dist/index.cjs"
83
83
  },
84
84
  "import": {
85
- "default": "./dist/index.js",
86
- "types": "./dist/index.d.ts"
85
+ "types": "./dist/index.d.ts",
86
+ "default": "./dist/index.js"
87
87
  }
88
88
  },
89
- "./index.css": "./dist/index.css"
89
+ "./styles/index.css": "./dist/index.css",
90
+ "./styles/global.css": "./src/styles/global.css",
91
+ "./styles/Knob.css": "./src/components/Knob/index.css",
92
+ "./styles/NumberInput.css": "./src/components/NumberInput/index.css",
93
+ "./styles/Piano.css": "./src/components/Piano/index.css",
94
+ "./styles/XYPad.css": "./src/components/XYPad/index.css"
90
95
  }
91
96
  }
@@ -28,3 +28,19 @@ export function isDrawingState(obj: unknown): obj is DrawingState {
28
28
  const d: string[] = [...drawingState]
29
29
  return typeof obj == 'string' && d.includes(obj)
30
30
  }
31
+
32
+ export function setDprConfig(
33
+ canvas: HTMLCanvasElement,
34
+ context: CanvasRenderingContext2D,
35
+ width: number,
36
+ height: number,
37
+ dpr: number,
38
+ ) {
39
+ canvas.width = width * dpr
40
+ canvas.height = height * dpr
41
+ // Reset current transformation matrix to the identity matrix
42
+ context.setTransform(1, 0, 0, 1, 0, 0)
43
+ context.scale(dpr, dpr)
44
+ canvas.style.width = `${width}px`
45
+ canvas.style.height = `${height}px`
46
+ }
@@ -8,30 +8,12 @@ import {
8
8
  useRef,
9
9
  } from 'react'
10
10
 
11
- import { DrawingContext, drawingState, DrawingStateValue } from './canvas'
12
-
13
- /*
14
- TODO
15
- AbsoluteSizingProps, RelativeSizingPropsの型付けを強くする
16
- AbsoluteSizingPropsの場合、width, heightは必須
17
- RelativeSizingPropsのの場合、relativeSizeは必須、reduceFlickeringはオプショナル
18
- */
19
-
20
- function setDprConfig(
21
- canvas: HTMLCanvasElement,
22
- context: CanvasRenderingContext2D,
23
- width: number,
24
- height: number,
25
- dpr: number,
26
- ) {
27
- canvas.width = width * dpr
28
- canvas.height = height * dpr
29
- // Reset current transformation matrix to the identity matrix
30
- context.setTransform(1, 0, 0, 1, 0, 0)
31
- context.scale(dpr, dpr)
32
- canvas.style.width = `${width}px`
33
- canvas.style.height = `${height}px`
34
- }
11
+ import {
12
+ DrawingContext,
13
+ drawingState,
14
+ DrawingStateValue,
15
+ setDprConfig,
16
+ } from './canvas'
35
17
 
36
18
  /** @category AnimationCanvas */
37
19
  export type InitFunction = (
@@ -46,8 +28,6 @@ export type InitFunction = (
46
28
 
47
29
  /** @category AnimationCanvas */
48
30
  export type DrawFunction = (
49
- // TODO: saving scale and translate
50
- // context: Omit<CanvasRenderingContext2D, 'save' | 'restore' | 'scale'>,
51
31
  context: CanvasRenderingContext2D,
52
32
  option: {
53
33
  /** current canvas width */
@@ -67,12 +47,13 @@ export type DrawFunction = (
67
47
 
68
48
  /** @category AnimationCanvas */
69
49
  export interface CommonProps {
50
+ draw: DrawFunction
51
+ init?: InitFunction
52
+ animate?: boolean
70
53
  /**
71
54
  * @see https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/getContext#contextattributes
72
55
  */
73
56
  options?: CanvasRenderingContext2DSettings
74
- init?: InitFunction
75
- draw: DrawFunction
76
57
  }
77
58
 
78
59
  /** @category AnimationCanvas */
@@ -108,12 +89,13 @@ export function AnimationCanvas(
108
89
  ): ReactElement
109
90
  export function AnimationCanvas({
110
91
  // common
111
- options,
112
- init,
113
92
  draw,
93
+ init,
94
+ animate = true,
95
+ options,
114
96
  // absolute
115
- width = 100,
116
- height = 100,
97
+ width: _width = 100,
98
+ height: _height = 100,
117
99
  // relative
118
100
  relativeSize,
119
101
  reduceFlickering = true,
@@ -145,9 +127,11 @@ export function AnimationCanvas({
145
127
  const deltaTime = now - deltaMemoRef.current
146
128
  const elapsedTime = now - startTimeRef.current
147
129
  deltaMemoRef.current = now
148
- reqIdRef.current = requestAnimationFrame(() =>
149
- loop(context, width, height, count + 1),
150
- )
130
+ if (animate) {
131
+ reqIdRef.current = requestAnimationFrame(() =>
132
+ loop(context, width, height, count + 1),
133
+ )
134
+ }
151
135
  draw(context, {
152
136
  width: width.current,
153
137
  height: height.current,
@@ -157,7 +141,7 @@ export function AnimationCanvas({
157
141
  fps: 1000 / deltaTime,
158
142
  })
159
143
  },
160
- [draw],
144
+ [draw, animate],
161
145
  )
162
146
 
163
147
  useEffect(() => {
@@ -167,6 +151,18 @@ export function AnimationCanvas({
167
151
  if (!context) throw new Error('Cannot get canvas context.')
168
152
  const dpr = globalThis.devicePixelRatio
169
153
 
154
+ const firstRendering = (width: number, height: number) => {
155
+ setDprConfig(canvas, context, width, height, dpr)
156
+ widthRef.current = width
157
+ heightRef.current = height
158
+
159
+ if (init) init(context, { width, height })
160
+ const now = performance.now()
161
+ deltaMemoRef.current = now
162
+ startTimeRef.current = now
163
+ loop(context, widthRef, heightRef, -1)
164
+ }
165
+
170
166
  if (relativeSize) {
171
167
  const parent = canvas.parentElement
172
168
  if (!parent) throw new Error("Canvas doesn't have a parent element.")
@@ -175,16 +171,14 @@ export function AnimationCanvas({
175
171
 
176
172
  const ro = new ResizeObserver((entries) => {
177
173
  for (const entry of entries) {
178
- // Prevents loss of some context when the canvas is resized
179
- const contextMemo = {} as DrawingContext
180
- for (const prop of drawingState) {
181
- ;(contextMemo[prop] as DrawingStateValue) = context[prop]
182
- }
183
-
184
174
  const w = entry.contentRect.width
185
175
  const h = entry.contentRect.height
186
-
176
+ const contextMemo = {} as DrawingContext
187
177
  if (reduceFlickering && memoCanvas && memoContext) {
178
+ // Prevents loss of some context when the canvas is resized
179
+ for (const prop of drawingState) {
180
+ ;(contextMemo[prop] as DrawingStateValue) = context[prop]
181
+ }
188
182
  memoCanvas.width = w * dpr
189
183
  memoCanvas.height = h * dpr
190
184
  memoContext.scale(1 / dpr, 1 / dpr)
@@ -197,10 +191,6 @@ export function AnimationCanvas({
197
191
  widthRef.current = w
198
192
  heightRef.current = h
199
193
 
200
- for (const prop of drawingState) {
201
- ;(context[prop] as DrawingStateValue) = contextMemo[prop]
202
- }
203
-
204
194
  if (
205
195
  reduceFlickering &&
206
196
  memoContext &&
@@ -208,52 +198,45 @@ export function AnimationCanvas({
208
198
  memoCanvas.width > 0 &&
209
199
  memoCanvas.height > 0
210
200
  ) {
201
+ for (const prop of drawingState) {
202
+ ;(context[prop] as DrawingStateValue) = contextMemo[prop]
203
+ }
204
+
211
205
  context.drawImage(memoContext.canvas, 0, 0)
212
206
  }
207
+
208
+ if (!animate) {
209
+ // re rendering
210
+ loop(context, widthRef, heightRef, -1)
211
+ }
213
212
  }
214
213
  })
215
214
  ro.observe(parent)
216
215
 
217
- const w = parent.clientWidth
218
- const h = parent.clientHeight
219
- setDprConfig(canvas, context, w, h, dpr)
220
-
221
- if (init)
222
- init(context, { width: widthRef.current, height: heightRef.current })
223
- const now = performance.now()
224
- deltaMemoRef.current = now
225
- startTimeRef.current = now
226
- loop(context, widthRef, heightRef, -1)
216
+ const width = parent.clientWidth
217
+ const height = parent.clientHeight
218
+ firstRendering(width, height)
227
219
 
228
220
  return () => {
229
221
  if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current)
230
222
  ro.disconnect()
231
223
  }
232
224
  } else {
233
- const rect = canvas.getBoundingClientRect()
234
- setDprConfig(canvas, context, rect.width, rect.height, dpr)
235
- widthRef.current = rect.width
236
- heightRef.current = rect.height
237
-
238
- if (init)
239
- init(context, { width: widthRef.current, height: heightRef.current })
240
- const now = performance.now()
241
- deltaMemoRef.current = now
242
- startTimeRef.current = now
243
- loop(context, widthRef, heightRef, -1)
225
+ const { width, height } = canvas.getBoundingClientRect()
226
+ firstRendering(width, height)
244
227
 
245
228
  return () => {
246
229
  if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current)
247
230
  }
248
231
  }
249
- }, [loop, init, options, reduceFlickering, relativeSize])
232
+ }, [loop, init, options, reduceFlickering, relativeSize, animate])
250
233
 
251
234
  return (
252
235
  <>
253
236
  <canvas
254
237
  className={clsx('tremolo-animation-canvas', className)}
255
- width={relativeSize ? 0 : width}
256
- height={relativeSize ? 0 : height}
238
+ width={relativeSize ? 0 : _width}
239
+ height={relativeSize ? 0 : _height}
257
240
  ref={canvasRef}
258
241
  onContextMenu={onContextMenu}
259
242
  {...props}
@@ -16,11 +16,11 @@ import {
16
16
  rawValue,
17
17
  stepValue,
18
18
  } from '@tremolo-ui/functions'
19
- import { parseValue } from '@tremolo-ui/functions/NumberInput'
20
19
 
21
20
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
22
21
 
23
22
  import { safeClamp, useNumberInputContext } from './context'
23
+ import { parseValue } from './type'
24
24
 
25
25
  import { NumberInputMethods } from '.'
26
26
 
@@ -2,7 +2,8 @@ import { createContext, useContext, useEffect, useRef } from 'react'
2
2
  import { createStore, useStore } from 'zustand'
3
3
 
4
4
  import { clamp } from '@tremolo-ui/functions'
5
- import { parseValue, Units } from '@tremolo-ui/functions/NumberInput'
5
+
6
+ import { parseValue, Units } from './type'
6
7
 
7
8
  type State = {
8
9
  value: string
@@ -72,6 +72,7 @@
72
72
 
73
73
  .tremolo-number-input {
74
74
  display: block;
75
+ color: inherit;
75
76
  width: calc(100% - 20px - var(--stepper-width));
76
77
  background-color: inherit;
77
78
  font: inherit;
@@ -2,10 +2,10 @@ import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, forwardRef, ReactNode } from 'react'
3
3
 
4
4
  import { InputEventOption } from '@tremolo-ui/functions'
5
- import { Units } from '@tremolo-ui/functions/NumberInput'
6
5
 
7
6
  import { NumberInputProvider } from './context'
8
7
  import { InternalInput } from './InternalInput'
8
+ import { Units } from './type'
9
9
 
10
10
  /*
11
11
  TODO
@@ -178,3 +178,4 @@ export const NumberInput = forwardRef<NumberInputMethods, Props>(
178
178
  export * from './Stepper'
179
179
  export * from './IncrementStepper'
180
180
  export * from './DecrementStepper'
181
+ export { type Units } from './type'
@@ -0,0 +1,58 @@
1
+ /**
2
+ * [unit, scale][]
3
+ * @example
4
+ * [['Hz', 1], ['kHz', 1000]]
5
+ */
6
+ export type Units = [string, number][]
7
+
8
+ export function selectUnit(units: Units, value: number): [string, number] {
9
+ let i = 0
10
+ for (; i < units.length; i++) {
11
+ if (Math.abs(units[i][1]) > Math.abs(value)) break
12
+ }
13
+ return units[Math.max(0, i - 1)]
14
+ }
15
+
16
+ export function parseValue(
17
+ inputString: string,
18
+ units?: string | Units,
19
+ digit?: number,
20
+ ): {
21
+ rawValue: number
22
+ formatValue: string
23
+ unit: string
24
+ } {
25
+ const str = inputString.trim()
26
+ if (!units || typeof units == 'string') {
27
+ const m = str.match(/-?\d+(\.\d+)?/)
28
+ let v = Number(m?.[0] ?? '0')
29
+ v = isNaN(v) ? 0 : v
30
+ return {
31
+ rawValue: v,
32
+ formatValue: (digit != undefined ? v.toFixed(digit) : v) + (units ?? ''),
33
+ unit: units ?? '',
34
+ }
35
+ }
36
+
37
+ const unitList = units.map(([u, _s]) => u)
38
+ const scaleList = units.map(([_u, s]) => s)
39
+ let rawValue = 0
40
+ let unit = unitList[0]
41
+ let formatValue =
42
+ (digit != undefined ? rawValue.toFixed(digit) : rawValue) + unit
43
+ const m = str.match(/^(-?\d+(\.\d+)?)\s*(\w*)$/)
44
+ if (m) {
45
+ rawValue = Number(m[1]) || 0
46
+ const uIndex = unitList.indexOf(m[3])
47
+ rawValue *= uIndex != -1 ? scaleList[uIndex] : 1
48
+ const [u, scale] = selectUnit(units, rawValue)
49
+ const v = rawValue / scale
50
+ formatValue = (digit != undefined ? v.toFixed(digit) : v) + u
51
+ unit = u
52
+ }
53
+ return {
54
+ rawValue: rawValue,
55
+ formatValue: formatValue,
56
+ unit: unit,
57
+ }
58
+ }
@@ -309,7 +309,6 @@ export function Piano({
309
309
  )
310
310
  }
311
311
 
312
- export * from './keyboardShortcuts'
313
-
312
+ export { type KeyboardShortcuts, SHORTCUTS } from './keyboardShortcuts'
314
313
  export { WhiteKey, BlackKey, type KeyProps, type KeyMethods } from './key'
315
314
  export { KeyLabel, type KeyLabelProps } from './KeyLabel'
@@ -2,10 +2,10 @@ import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react'
3
3
 
4
4
  import { xor } from '@tremolo-ui/functions'
5
- import { generateOptionsList, ScaleOptions } from '@tremolo-ui/functions/Slider'
6
5
 
7
6
  import { useSliderContext } from './context'
8
7
  import { ScaleOption } from './ScaleOption'
8
+ import { generateOptionsList, ScaleOptions } from './type'
9
9
 
10
10
  /** @category Slider */
11
11
  export interface ScaleProps {
@@ -2,9 +2,9 @@ import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, CSSProperties, useCallback } from 'react'
3
3
 
4
4
  import { toFixed, xor, normalizeValue } from '@tremolo-ui/functions'
5
- import { ScaleType } from '@tremolo-ui/functions/Slider'
6
5
 
7
6
  import { useSliderContext } from './context'
7
+ import { ScaleType } from './type'
8
8
 
9
9
  /** @category Slider */
10
10
  export interface ScaleOptionProps {
@@ -341,3 +341,4 @@ export {
341
341
  export { SliderTrack, type SliderTrackProps } from './Track'
342
342
  export { Scale, type ScaleProps } from './Scale'
343
343
  export { ScaleOption, type ScaleOptionProps } from './ScaleOption'
344
+ export { type ScaleOptions, type ScaleType } from './type'
@@ -0,0 +1,26 @@
1
+ import { decimalPart, toFixed } from '@tremolo-ui/functions'
2
+
3
+ export type ScaleType = 'mark' | 'mark-number' | 'number'
4
+ export type ScaleOptions = ['step', ScaleType] | [number, ScaleType]
5
+
6
+ export function generateOptionsList(
7
+ options: ScaleOptions,
8
+ min: number,
9
+ max: number,
10
+ step: number,
11
+ ) {
12
+ const optionsList: {
13
+ value: number
14
+ type: ScaleType
15
+ }[] = []
16
+ const per = options[0] == 'step' ? step : options[0]
17
+ const count = Math.floor(max / per) - Math.ceil(min / per) + 1
18
+ for (let i = 0; i < count; i++) {
19
+ const value = toFixed(
20
+ per * (Math.ceil(min / per) + i),
21
+ decimalPart(per)?.length,
22
+ )
23
+ optionsList.push({ value: value, type: options[1] })
24
+ }
25
+ return optionsList
26
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- import './styles/index.css'
1
+ // For bundled CSS (@tremolo-ui/react/styles/index.css)
2
+ import './styles/global.css'
2
3
  import './components/Knob/index.css'
3
4
  import './components/NumberInput/index.css'
4
5
  import './components/Piano/index.css'
File without changes