@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,216 +0,0 @@
1
- import clsx from 'clsx'
2
- import {
3
- ComponentPropsWithoutRef,
4
- forwardRef,
5
- useCallback,
6
- useImperativeHandle,
7
- useMemo,
8
- useRef,
9
- useState,
10
- } from 'react'
11
-
12
- import {
13
- clamp,
14
- InputEventOption,
15
- normalizeValue,
16
- rawValue,
17
- stepValue,
18
- } from '@tremolo-ui/functions'
19
-
20
- import { useWheel } from '../../hooks/useWheel'
21
-
22
- import { safeClamp, useNumberInputContext } from './context'
23
- import { parseValue } from './type'
24
-
25
- import { NumberInputMethods } from '.'
26
-
27
- interface InternalProps {
28
- disabled: boolean
29
- readonly: boolean
30
- wheel: InputEventOption | null
31
- keyboard: InputEventOption | null
32
-
33
- selectWithFocus?: 'all' | 'number' | 'none'
34
- blurOnEnter?: boolean
35
- keepWithinRange?: boolean
36
- clampValueOnBlur?: boolean
37
-
38
- onChange?: (value: number, text: string) => void
39
- onFocus?: (
40
- value: number,
41
- text: string,
42
- event: React.FocusEvent<HTMLInputElement, Element>,
43
- ) => void
44
- onBlur?: (
45
- value: number,
46
- text: string,
47
- event: React.FocusEvent<HTMLInputElement, Element>,
48
- ) => void
49
- }
50
-
51
- type InternalPropsWithAttributes = InternalProps &
52
- Omit<ComponentPropsWithoutRef<'input'>, keyof InternalProps>
53
-
54
- export const InternalInput = forwardRef<
55
- NumberInputMethods,
56
- InternalPropsWithAttributes
57
- >(
58
- (
59
- {
60
- disabled,
61
- readonly,
62
- selectWithFocus,
63
- blurOnEnter,
64
- clampValueOnBlur,
65
- wheel,
66
- keyboard,
67
- className,
68
- onChange,
69
- onFocus,
70
- onBlur,
71
- onKeyDown,
72
- children: _children,
73
- ...props
74
- }: InternalPropsWithAttributes,
75
- ref,
76
- ) => {
77
- const elmRef = useRef<HTMLInputElement>(null)
78
- const [editing, setEditing] = useState(false)
79
- const value = useNumberInputContext((s) => s.value)
80
- const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
81
- const min = useNumberInputContext((s) => s.min)
82
- const max = useNumberInputContext((s) => s.max)
83
- const step = useNumberInputContext((s) => s.step)
84
- const units = useNumberInputContext((s) => s.units)
85
- const digit = useNumberInputContext((s) => s.digit)
86
- const change = useNumberInputContext((s) => s.change)
87
-
88
- const error = useMemo(() => {
89
- if (editing) return false
90
- const v = valueAsNumber
91
- return (min ?? -Infinity) > v || v > (max ?? Infinity)
92
- }, [editing, max, min, valueAsNumber])
93
-
94
- const updateValueByEvent = useCallback(
95
- (eventType: InputEventOption[0], x: number) => {
96
- let newValue
97
- if (eventType == 'normalized') {
98
- if (!min || !max) {
99
- throw new Error(
100
- '[NumberInput] "min" and "max" are required when InputEventOption[0] is set to "normalized".',
101
- )
102
- }
103
- const n = normalizeValue(valueAsNumber, min, max)
104
- newValue = rawValue(n + x, min, max)
105
- return clamp(stepValue(newValue, step), min, max)
106
- } else {
107
- newValue = valueAsNumber + x
108
- return stepValue(newValue, step)
109
- }
110
- },
111
- [max, min, step, valueAsNumber],
112
- )
113
-
114
- const handleKeyDown = useCallback(
115
- (event: React.KeyboardEvent<HTMLDivElement>) => {
116
- if (!keyboard || !onChange || readonly) return
117
- const key = event.key
118
- if (!['ArrowUp', 'ArrowDown'].includes(key)) return
119
- event.preventDefault()
120
- const x = key == 'ArrowUp' ? keyboard[1] : -keyboard[1]
121
- const parsed = parseValue(String(updateValueByEvent(keyboard[0], x)))
122
- // console.log('key control')
123
- onChange?.(parsed.rawValue, parsed.formatValue)
124
- },
125
- [keyboard, readonly, onChange, updateValueByEvent],
126
- )
127
-
128
- const wheelRefCallback = useWheel<HTMLInputElement>((event) => {
129
- if (!wheel || readonly) return
130
- event.preventDefault()
131
- if (!onChange || event.deltaY == 0) return
132
- const x = Math.sign(event.deltaY) * -wheel[1]
133
- const parsed = parseValue(String(updateValueByEvent(wheel[0], x)))
134
- // console.log('wheel control')
135
- onChange?.(parsed.rawValue, parsed.formatValue)
136
- })
137
-
138
- useImperativeHandle(ref, () => {
139
- return {
140
- focus() {
141
- elmRef.current?.focus()
142
- },
143
- blur() {
144
- elmRef.current?.blur()
145
- },
146
- }
147
- }, [])
148
-
149
- return (
150
- <input
151
- ref={(input) => {
152
- elmRef.current = input
153
- wheelRefCallback(input)
154
- }}
155
- className={clsx('tremolo-number-input', className)}
156
- value={value}
157
- min={min}
158
- max={max}
159
- step={step}
160
- type="text"
161
- aria-disabled={disabled}
162
- aria-readonly={readonly}
163
- tabIndex={-1}
164
- data-error={error}
165
- onChange={(event) => {
166
- if (readonly) return
167
- const v = event.currentTarget.value
168
- const cursorPos = event.target.selectionStart
169
- change(v)
170
- const valueAsNumber = parseValue(v, units, digit).rawValue
171
- onChange?.(valueAsNumber, v)
172
- if (cursorPos) {
173
- event.target.selectionStart = cursorPos
174
- event.target.selectionEnd = cursorPos
175
- }
176
- }}
177
- onFocus={(event) => {
178
- setEditing(true)
179
- const v = event.currentTarget.value
180
- const parsed = parseValue(v, units, digit)
181
- if (selectWithFocus == 'all') {
182
- event.currentTarget.setSelectionRange(0, v.length)
183
- } else if (selectWithFocus == 'number') {
184
- event.currentTarget.setSelectionRange(
185
- 0,
186
- parsed.formatValue.length - parsed.unit.length,
187
- )
188
- }
189
- onFocus?.(parsed.rawValue, parsed.formatValue, event)
190
- }}
191
- onBlur={(event) => {
192
- setEditing(false)
193
- // update value
194
- let v = parseValue(event.currentTarget.value, units, digit).rawValue
195
- if (clampValueOnBlur) {
196
- v = safeClamp(v, min, max)
197
- }
198
- const parsed = parseValue(String(v), units, digit)
199
- change(parsed.formatValue)
200
- if (clampValueOnBlur) {
201
- onChange?.(parsed.rawValue, parsed.formatValue)
202
- }
203
- onBlur?.(parsed.rawValue, parsed.formatValue, event)
204
- }}
205
- onKeyDown={(event) => {
206
- if (blurOnEnter && event.key == 'Enter') {
207
- event.currentTarget.blur()
208
- }
209
- handleKeyDown(event)
210
- onKeyDown?.(event)
211
- }}
212
- {...props}
213
- />
214
- )
215
- },
216
- )
@@ -1,185 +0,0 @@
1
- .tremolo-number-input-wrapper {
2
- --border-radius: 4px;
3
- --stepper-width: 20px;
4
- --active-color: oklch(59.5% 0.17548 266.373);
5
- --border-color: oklch(81% 0 0);
6
-
7
- display: inline-flex;
8
- flex-direction: row;
9
- position: relative;
10
- width: 140px;
11
- height: min-content;
12
- border-style: solid;
13
- border-color: var(--border-color);
14
- border-width: 0px;
15
- outline: none;
16
- transition: all 0.1s;
17
-
18
- &:not(:focus-within):has(> .tremolo-number-input[data-error='true']) {
19
- border-color: oklch(64.245% 0.21266 25.499);
20
- }
21
-
22
- &[data-variant='outline'] {
23
- border-width: 1px;
24
- border-radius: var(--border-radius);
25
-
26
- &:hover {
27
- --border-color: oklch(65.335% 0 0);
28
- }
29
-
30
- &:focus-within {
31
- --border-color: var(--active-color);
32
- box-shadow: 0px 0px 0px 2px
33
- color-mix(in srgb, var(--active-color) 10%, transparent);
34
- }
35
- }
36
-
37
- &[data-variant='filled'] {
38
- --bg: oklch(95.88% 0 0);
39
- border-width: 1px;
40
- border-radius: var(--border-radius);
41
- border-color: transparent;
42
- background-color: var(--bg);
43
-
44
- &:hover {
45
- --bg: oklch(93.166% 0 0);
46
- }
47
-
48
- &:focus-within {
49
- background-color: inherit;
50
- --border-color: var(--active-color);
51
- box-shadow: 0px 0px 0px 2px
52
- color-mix(in srgb, var(--active-color) 10%, transparent);
53
- }
54
- }
55
-
56
- &[data-variant='flushed'] {
57
- border-bottom-width: 1px;
58
-
59
- &:hover {
60
- --border-color: oklch(65.335% 0 0);
61
- }
62
-
63
- &:focus-within {
64
- --border-color: var(--active-color);
65
- box-shadow: 0px 2px 0px 0px
66
- color-mix(in srgb, var(--active-color) 10%, transparent);
67
- }
68
- }
69
-
70
- &[data-stepper='false'] {
71
- --stepper-width: 0px;
72
- }
73
- }
74
-
75
- .tremolo-number-input {
76
- display: block;
77
- color: inherit;
78
- width: calc(100% - 20px - var(--stepper-width));
79
- background-color: inherit;
80
- font: inherit;
81
- padding: 6px 10px;
82
-
83
- margin: 0;
84
- border: none;
85
- outline: none;
86
- appearance: none;
87
-
88
- &[aria-readonly='true'] {
89
- cursor: not-allowed;
90
- }
91
- }
92
-
93
- .tremolo-number-input-stepper {
94
- display: flex;
95
- flex-direction: column;
96
- height: 100%;
97
- position: absolute;
98
- top: 0;
99
- right: 0;
100
- width: var(--stepper-width);
101
- transition: all 0.1s linear;
102
-
103
- &[data-dynamic='true'] {
104
- opacity: 0;
105
- transform: translateX(40%);
106
- }
107
- }
108
-
109
- :is(
110
- .tremolo-number-input-wrapper:hover,
111
- .tremolo-number-input-wrapper:focus-within
112
- )
113
- .tremolo-number-input-stepper {
114
- opacity: 1;
115
- transform: translateX(0);
116
- }
117
-
118
- .tremolo-number-input-increment-stepper {
119
- border-inline-start-width: 1px;
120
- border-inline-start-style: solid;
121
- border-inline-start-color: oklch(84.522% 0 0);
122
- font-size: calc(0.75rem);
123
- display: flex;
124
- justify-content: center;
125
- align-items: center;
126
- flex: 1 1 0%;
127
- max-height: 50%;
128
- user-select: none;
129
- cursor: pointer;
130
- border-top-right-radius: var(--border-radius);
131
-
132
- &[aria-disabled='false']:active {
133
- background-color: oklch(37.798% 0.03812 254.612 / 0.055);
134
- }
135
-
136
- &[aria-disabled='true'] {
137
- cursor: not-allowed;
138
- }
139
-
140
- &[aria-readonly='true'] {
141
- cursor: not-allowed;
142
- }
143
- }
144
-
145
- .tremolo-number-input-decrement-stepper {
146
- border-inline-start-width: 1px;
147
- border-inline-start-style: solid;
148
- border-inline-start-color: oklch(84.522% 0 0);
149
- border-top: 1px solid oklch(84.522% 0 0);
150
- font-size: calc(0.75rem);
151
- display: flex;
152
- justify-content: center;
153
- align-items: center;
154
- flex: 1 1 0%;
155
- max-height: 50%;
156
- user-select: none;
157
- cursor: pointer;
158
- border-bottom-right-radius: var(--border-radius);
159
-
160
- &[aria-disabled='false']:active {
161
- background-color: oklch(37.798% 0.03812 254.612 / 0.055);
162
- }
163
-
164
- &[aria-disabled='true'] {
165
- cursor: not-allowed;
166
- }
167
-
168
- &[aria-readonly='true'] {
169
- cursor: not-allowed;
170
- }
171
- }
172
-
173
- :where(.dark, [data-theme='dark']) {
174
- .tremolo-number-input-wrapper {
175
- --border-color: oklch(52% 0 0);
176
-
177
- &[data-variant='filled'] {
178
- --bg: oklch(43.5% 0 0);
179
-
180
- &:hover {
181
- --bg: oklch(46.6% 0 0);
182
- }
183
- }
184
- }
185
- }
@@ -1,58 +0,0 @@
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
- }
@@ -1,51 +0,0 @@
1
- import clsx from 'clsx'
2
- import { ComponentPropsWithoutRef, CSSProperties, ReactNode } from 'react'
3
-
4
- import { usePianoContext } from './context'
5
-
6
- import { getNoteRangeArray } from '.'
7
-
8
- export interface KeyLabelProps {
9
- /**
10
- * override Piano.label
11
- */
12
- label?: (note: number, index: number) => ReactNode
13
-
14
- wrapperClassName?: string
15
- wrapperStyle?: CSSProperties
16
-
17
- /** @internal */
18
- __note?: number
19
- /** @internal */
20
- __label?: (note: number, index: number) => ReactNode
21
- }
22
-
23
- export function KeyLabel({
24
- label,
25
- className,
26
- wrapperClassName,
27
- wrapperStyle,
28
- __note,
29
- __label,
30
- ...props
31
- }: KeyLabelProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyLabelProps>) {
32
- const noteRange = usePianoContext((s) => s.noteRange)
33
- const noteRangeArray = getNoteRangeArray(noteRange)
34
- const index = noteRangeArray.indexOf(__note!)
35
- const content = label
36
- ? label(__note!, index)
37
- : __label && __label(__note!, index)
38
-
39
- return (
40
- (content != undefined || content != null) && (
41
- <div
42
- className={clsx('tremolo-piano-key-label-wrapper', wrapperClassName)}
43
- style={wrapperStyle}
44
- >
45
- <div className={clsx('tremolo-piano-key-label', className)} {...props}>
46
- {content}
47
- </div>
48
- </div>
49
- )
50
- )
51
- }
@@ -1,73 +0,0 @@
1
- import { createContext, ReactNode, useContext, useEffect, useRef } from 'react'
2
- import { createStore, useStore } from 'zustand'
3
-
4
- export type NoteRange = {
5
- first: number
6
- last: number
7
- }
8
-
9
- type State = {
10
- noteRange: NoteRange
11
- glissando: boolean
12
- midiMax: number
13
- fill: boolean
14
- onPlayNote: (noteNumber: number, velocity?: number) => void
15
- onStopNote: (noteNumber: number) => void
16
- label: (note: number, index: number) => ReactNode
17
- }
18
-
19
- type Action = {
20
- notePosition: (note: number) => number
21
- }
22
-
23
- type PianoStore = ReturnType<typeof createPianoStore>
24
-
25
- const createPianoStore = (initProps: Partial<State> & Action) => {
26
- // const noteRange = initProps.noteRange || { first: 0, last: 127 }
27
- const DEFAULT_PROPS: State = {
28
- noteRange: { first: 0, last: 127 },
29
- glissando: true,
30
- midiMax: 127,
31
- fill: false,
32
- label: () => undefined,
33
- onPlayNote: () => {},
34
- onStopNote: () => {},
35
- }
36
-
37
- return createStore<State & Action>()(() => ({
38
- ...DEFAULT_PROPS,
39
- ...initProps,
40
- notePosition: initProps.notePosition,
41
- }))
42
- }
43
-
44
- const PianoContext = createContext<PianoStore | null>(null)
45
-
46
- type PianoProviderProps = React.PropsWithChildren<Partial<State> & Action>
47
-
48
- export function PianoProvider({ children, ...props }: PianoProviderProps) {
49
- const storeRef = useRef<PianoStore>(null)
50
- if (!storeRef.current) {
51
- storeRef.current = createPianoStore(props)
52
- }
53
-
54
- useEffect(() => {
55
- if (storeRef.current) {
56
- storeRef.current.setState(props)
57
- } else {
58
- storeRef.current = createPianoStore(props)
59
- }
60
- }, [props])
61
-
62
- return (
63
- <PianoContext.Provider value={storeRef.current}>
64
- {children}
65
- </PianoContext.Provider>
66
- )
67
- }
68
-
69
- export function usePianoContext<T>(selector: (state: State & Action) => T): T {
70
- const store = useContext(PianoContext)
71
- if (!store) throw new Error('Missing PianoContext.Provider in the tree')
72
- return useStore(store, selector)
73
- }
@@ -1,90 +0,0 @@
1
- .tremolo-piano {
2
- display: inline-block;
3
- box-sizing: border-box;
4
- user-select: none;
5
- touch-action: none;
6
- position: relative;
7
- }
8
-
9
- .tremolo-piano-white-key {
10
- --bg: oklch(100% 0 0);
11
- --color: oklch(0% 0 0);
12
- --active-bg: oklch(92% 0 0);
13
- --active-color: oklch(0% 0 0);
14
-
15
- box-sizing: border-box;
16
- position: absolute;
17
- background-color: var(--bg);
18
- -webkit-tap-highlight-color: transparent;
19
- color: var(--color);
20
- border: 1px solid #555;
21
- border-radius: 0px 0px 8px 8px;
22
- cursor: pointer;
23
- z-index: 1;
24
-
25
- &[data-active='true'] {
26
- background-color: var(--active-bg);
27
- color: var(--active-color);
28
- }
29
-
30
- &[aria-disabled='true'] {
31
- cursor: not-allowed;
32
- }
33
- }
34
-
35
- .tremolo-piano-black-key {
36
- --color: oklch(100% 0 0);
37
- --bg: oklch(32% 0 0);
38
- --active-color: oklch(100% 0 0);
39
- --active-bg: oklch(44% 0 0);
40
-
41
- box-sizing: border-box;
42
- position: absolute;
43
- background-color: var(--bg);
44
- -webkit-tap-highlight-color: transparent;
45
- color: var(--color);
46
- border: 1px solid #555;
47
- border-radius: 0px 0px 8px 8px;
48
- cursor: pointer;
49
- z-index: 2;
50
-
51
- &[data-active='true'] {
52
- background-color: var(--active-bg);
53
- color: var(--active-color);
54
- }
55
-
56
- &[aria-disabled='true'] {
57
- cursor: not-allowed;
58
- }
59
- }
60
-
61
- .tremolo-piano-key-label-wrapper {
62
- display: flex;
63
- justify-content: center;
64
- align-items: end;
65
- height: 100%;
66
- }
67
-
68
- .tremolo-piano-key-label {
69
- box-sizing: content-box;
70
- display: flex;
71
- justify-content: center;
72
- align-items: center;
73
- font-size: 0.6rem;
74
- height: 10px;
75
- margin-bottom: 10px;
76
- padding: 4px;
77
- border-radius: 4px;
78
- border: 1px solid #888;
79
- aspect-ratio: 1;
80
- max-width: calc(100% - 16px);
81
- }
82
-
83
- :where(.dark, [data-theme='dark']) {
84
- .tremolo-piano-white-key {
85
- --bg: oklch(80% 0 0);
86
- --color: oklch(0% 0 0);
87
- --active-bg: oklch(72% 0 0);
88
- --active-color: oklch(0% 0 0);
89
- }
90
- }