@tremolo-ui/vue 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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/index.cjs +2486 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1678 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.ts +1678 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +2444 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +78 -0
  12. package/src/components/AnimationCanvas/AnimationCanvas.ts +105 -0
  13. package/src/components/AnimationCanvas/index.ts +5 -0
  14. package/src/components/DropZone/DropZone.ts +43 -0
  15. package/src/components/DropZone/index.ts +1 -0
  16. package/src/components/FileInput/FileInput.ts +82 -0
  17. package/src/components/FileInput/FileInputTrigger.ts +24 -0
  18. package/src/components/FileInput/context.ts +18 -0
  19. package/src/components/FileInput/index.ts +3 -0
  20. package/src/components/Knob/Knob.ts +237 -0
  21. package/src/components/Knob/KnobActiveLine.ts +31 -0
  22. package/src/components/Knob/KnobInactiveLine.ts +36 -0
  23. package/src/components/Knob/KnobSVGRoot.ts +25 -0
  24. package/src/components/Knob/KnobThumb.ts +53 -0
  25. package/src/components/Knob/context.ts +30 -0
  26. package/src/components/Knob/index.ts +6 -0
  27. package/src/components/NumberInput/NumberInput.ts +258 -0
  28. package/src/components/NumberInput/NumberInputDecrementStepper.ts +7 -0
  29. package/src/components/NumberInput/NumberInputField.ts +128 -0
  30. package/src/components/NumberInput/NumberInputIncrementStepper.ts +7 -0
  31. package/src/components/NumberInput/NumberInputStepper.ts +54 -0
  32. package/src/components/NumberInput/context.ts +63 -0
  33. package/src/components/NumberInput/index.ts +6 -0
  34. package/src/components/NumberInput/stepper-button.ts +65 -0
  35. package/src/components/Piano/Piano.ts +243 -0
  36. package/src/components/Piano/index.ts +1 -0
  37. package/src/components/PointsEditor/PointsEditor.ts +146 -0
  38. package/src/components/PointsEditor/PointsEditorBackground.ts +17 -0
  39. package/src/components/PointsEditor/PointsEditorContainer.ts +73 -0
  40. package/src/components/PointsEditor/PointsEditorPoint.ts +253 -0
  41. package/src/components/PointsEditor/PointsEditorSelectionBox.ts +36 -0
  42. package/src/components/PointsEditor/context.ts +39 -0
  43. package/src/components/PointsEditor/index.ts +9 -0
  44. package/src/components/Slider/Slider.ts +224 -0
  45. package/src/components/Slider/SliderMarks.ts +57 -0
  46. package/src/components/Slider/SliderMarksOption.ts +85 -0
  47. package/src/components/Slider/SliderThumb.ts +92 -0
  48. package/src/components/Slider/SliderTrack.ts +50 -0
  49. package/src/components/Slider/context.ts +33 -0
  50. package/src/components/Slider/index.ts +6 -0
  51. package/src/components/XYPad/XYPad.ts +250 -0
  52. package/src/components/XYPad/XYPadArea.ts +20 -0
  53. package/src/components/XYPad/XYPadThumb.ts +105 -0
  54. package/src/components/XYPad/context.ts +33 -0
  55. package/src/components/XYPad/index.ts +4 -0
  56. package/src/components/_util/context.ts +8 -0
  57. package/src/components/_util/placement.ts +36 -0
  58. package/src/components/_util/props.ts +32 -0
  59. package/src/components/_util/useCheckSteps.ts +22 -0
  60. package/src/components/_util/visually-hidden-range-input.ts +16 -0
  61. package/src/composables/useDrag.ts +22 -0
  62. package/src/composables/useDragValue.ts +20 -0
  63. package/src/composables/useDropZone.ts +32 -0
  64. package/src/composables/useInstance.ts +52 -0
  65. package/src/composables/useLongPress.ts +20 -0
  66. package/src/composables/useMIDIAccess.ts +58 -0
  67. package/src/composables/useMIDIInput.ts +33 -0
  68. package/src/composables/useMIDIMessage.ts +30 -0
  69. package/src/composables/useWheel.ts +23 -0
  70. package/src/index.ts +67 -0
@@ -0,0 +1,82 @@
1
+ import { defineComponent, h, provide, useId } from 'vue'
2
+
3
+ import { partitionByAccept, visuallyHiddenStyle } from '@tremolo-ui/dom'
4
+
5
+ import { FileInputKey } from './context'
6
+
7
+ /**
8
+ * Picks files. The control is the native file input, out of sight but in the
9
+ * tab order, with `FileInputTrigger` as its label.
10
+ */
11
+ export const FileInput = /* @__PURE__ */ defineComponent({
12
+ name: 'FileInput',
13
+ inheritAttrs: false,
14
+ props: {
15
+ /**
16
+ * Which files to take: extensions, MIME types and type groups. It is
17
+ * checked again when the files arrive, since the picker only takes it as
18
+ * a hint.
19
+ */
20
+ accept: String,
21
+ /** Let several files be picked at once. */
22
+ multiple: Boolean,
23
+ /** Make the input unusable. The parts carry `data-disabled`. */
24
+ disabled: Boolean,
25
+ },
26
+ emits: {
27
+ /** The picked files that satisfy `accept`. */
28
+ change: (files: File[]) => Array.isArray(files),
29
+ /** The picked files that do not satisfy `accept`, after `change` for the same selection. */
30
+ reject: (files: File[]) => Array.isArray(files),
31
+ },
32
+ setup(props, { slots, emit, attrs }) {
33
+ const inputId = useId()
34
+ provide(FileInputKey, {
35
+ inputId,
36
+ get disabled() {
37
+ return props.disabled
38
+ },
39
+ })
40
+
41
+ function onChange(event: Event) {
42
+ const input = event.target as HTMLInputElement
43
+ const { accepted, rejected } = partitionByAccept(
44
+ Array.from(input.files ?? []),
45
+ props.accept,
46
+ )
47
+ // Picking the same file twice fires no second change while the value
48
+ // is still on the input, so it is cleared as soon as it is read.
49
+ input.value = ''
50
+ if (accepted.length > 0) emit('change', accepted)
51
+ if (rejected.length > 0) emit('reject', rejected)
52
+ }
53
+
54
+ return () => {
55
+ const {
56
+ 'aria-label': ariaLabel,
57
+ 'aria-labelledby': ariaLabelledby,
58
+ 'aria-describedby': ariaDescribedby,
59
+ ...rest
60
+ } = attrs
61
+ return h(
62
+ 'div',
63
+ { ...rest, 'data-disabled': props.disabled ? '' : undefined },
64
+ [
65
+ h('input', {
66
+ id: inputId,
67
+ type: 'file',
68
+ accept: props.accept,
69
+ multiple: props.multiple,
70
+ disabled: props.disabled,
71
+ style: visuallyHiddenStyle,
72
+ 'aria-label': ariaLabel,
73
+ 'aria-labelledby': ariaLabelledby,
74
+ 'aria-describedby': ariaDescribedby,
75
+ onChange,
76
+ }),
77
+ slots.default?.(),
78
+ ],
79
+ )
80
+ }
81
+ },
82
+ })
@@ -0,0 +1,24 @@
1
+ import { defineComponent, h } from 'vue'
2
+
3
+ import { useFileInputContext } from './context'
4
+
5
+ /**
6
+ * Opens the file picker and names the input. A `<label>` rather than a
7
+ * button: the browser forwards the click, the input stays the one thing in
8
+ * the tab order, and its name comes from this text.
9
+ */
10
+ export const FileInputTrigger = /* @__PURE__ */ defineComponent({
11
+ name: 'FileInputTrigger',
12
+ setup(_, { slots }) {
13
+ const fileInput = useFileInputContext()
14
+ return () =>
15
+ h(
16
+ 'label',
17
+ {
18
+ for: fileInput.inputId,
19
+ 'data-disabled': fileInput.disabled ? '' : undefined,
20
+ },
21
+ slots.default?.(),
22
+ )
23
+ },
24
+ })
@@ -0,0 +1,18 @@
1
+ import { type InjectionKey } from 'vue'
2
+
3
+ import { injectContext } from '../_util/context'
4
+
5
+ /** What `FileInput` shares with `FileInputTrigger`. */
6
+ export interface FileInputContextValue {
7
+ /** The id of the file input, for the trigger's `for`. */
8
+ readonly inputId: string
9
+ readonly disabled: boolean
10
+ }
11
+
12
+ export const FileInputKey: InjectionKey<FileInputContextValue> =
13
+ Symbol('FileInput')
14
+
15
+ /** The context of the enclosing `FileInput`, for a part of your own. */
16
+ export function useFileInputContext(): FileInputContextValue {
17
+ return injectContext(FileInputKey, 'FileInput')
18
+ }
@@ -0,0 +1,3 @@
1
+ export { useFileInputContext, type FileInputContextValue } from './context'
2
+ export { FileInput } from './FileInput'
3
+ export { FileInputTrigger } from './FileInputTrigger'
@@ -0,0 +1,237 @@
1
+ import { computed, defineComponent, h, provide, ref, type PropType } from 'vue'
2
+
3
+ import {
4
+ applyDelta,
5
+ arrowKeyDirection,
6
+ cssLength,
7
+ DEFAULT_DRAG_SENSITIVITY,
8
+ DEFAULT_KEYBOARD_OPTIONS,
9
+ DEFAULT_WHEEL_OPTIONS,
10
+ knobAngles,
11
+ relativeMapping,
12
+ selectModifier,
13
+ wheelDirection,
14
+ type AxisOptions,
15
+ type InputEventOption,
16
+ type ModifierValue,
17
+ type XY,
18
+ } from '@tremolo-ui/dom'
19
+ import { linearScale, type Scale } from '@tremolo-ui/functions'
20
+
21
+ import { useDragValue } from '../../composables/useDragValue'
22
+ import { useWheel } from '../../composables/useWheel'
23
+ import { useCheckSteps } from '../_util/useCheckSteps'
24
+
25
+ import { KnobKey } from './context'
26
+
27
+ /**
28
+ * Interactive rotary knob, drawn in SVG by `KnobSVGRoot` and the parts inside
29
+ * it. Bind the value with `v-model`.
30
+ */
31
+ export const Knob = /* @__PURE__ */ defineComponent({
32
+ name: 'Knob',
33
+ props: {
34
+ /** The current value. Bind it with `v-model`. */
35
+ modelValue: { type: Number, required: true },
36
+ /** The value with the knob turned all the way down. */
37
+ min: { type: Number, required: true },
38
+ /** The value with the knob turned all the way up. */
39
+ max: { type: Number, required: true },
40
+ /** Granularity of the value. @default 1 */
41
+ step: { type: Number, default: 1 },
42
+ /** How the value is distributed across the travel. @default linearScale */
43
+ scale: { type: Object as PropType<Scale>, default: () => linearScale },
44
+ /** The value a double click restores. @default min */
45
+ defaultValue: Number,
46
+ /** Where the active arc starts. @default min */
47
+ startValue: Number,
48
+ /** Width and height of the knob. Sets `--knob-size`. */
49
+ size: [Number, String],
50
+ /** The cursor to show while dragging. @default { cursor: 'grabbing' } */
51
+ externalStyles: Object as PropType<{ cursor?: string }>,
52
+ /** How much one notch of the wheel moves the value. `null` turns it off. */
53
+ wheel: {
54
+ type: [Array, Object] as PropType<ModifierValue<InputEventOption> | null>,
55
+ default: () => DEFAULT_WHEEL_OPTIONS,
56
+ },
57
+ /** How much one arrow key press moves the value. `null` turns it off. */
58
+ keyboard: {
59
+ type: [Array, Object] as PropType<ModifierValue<InputEventOption> | null>,
60
+ default: () => DEFAULT_KEYBOARD_OPTIONS,
61
+ },
62
+ /** How much a drag moves the value, per modifier key. */
63
+ dragSensitivity: {
64
+ type: [Number, Object] as PropType<ModifierValue<number>>,
65
+ default: () => DEFAULT_DRAG_SENSITIVITY,
66
+ },
67
+ /** Hide the cursor while dragging and read the movement directly. */
68
+ pointerLock: Boolean,
69
+ /** Restore `defaultValue` on a double click. @default true */
70
+ enableDoubleClickDefault: { type: Boolean, default: true },
71
+ /** Make the knob unchangeable and remove it from the tab order. */
72
+ disabled: Boolean,
73
+ /** Make the knob unchangeable while leaving it focusable. */
74
+ readonly: Boolean,
75
+ /** How far the knob turns from `min` to `max`, in degrees. @default 270 */
76
+ angleRange: { type: Number, default: 270 },
77
+ },
78
+ emits: {
79
+ 'update:modelValue': (value: number) => typeof value === 'number',
80
+ },
81
+ setup(props, { slots, emit, expose }) {
82
+ const root = ref<HTMLDivElement | null>(null)
83
+ const dragging = ref(false)
84
+ const inactive = computed(() => props.disabled || props.readonly)
85
+ const range = computed(() => ({
86
+ min: props.min,
87
+ max: props.max,
88
+ step: props.step,
89
+ scale: props.scale,
90
+ }))
91
+ const angles = computed(() =>
92
+ knobAngles({
93
+ value: props.modelValue,
94
+ min: props.min,
95
+ max: props.max,
96
+ scale: props.scale,
97
+ startValue: props.startValue ?? props.min,
98
+ angleRange: props.angleRange,
99
+ }),
100
+ )
101
+
102
+ useCheckSteps(() => ({
103
+ component: 'Knob',
104
+ range: range.value,
105
+ keyboard: props.keyboard,
106
+ wheel: props.wheel,
107
+ }))
108
+
109
+ provide(KnobKey, {
110
+ get value() {
111
+ return props.modelValue
112
+ },
113
+ get min() {
114
+ return props.min
115
+ },
116
+ get max() {
117
+ return props.max
118
+ },
119
+ get step() {
120
+ return props.step
121
+ },
122
+ get scale() {
123
+ return props.scale
124
+ },
125
+ get startValue() {
126
+ return props.startValue ?? props.min
127
+ },
128
+ get angleRange() {
129
+ return props.angleRange
130
+ },
131
+ get angles() {
132
+ return angles.value
133
+ },
134
+ })
135
+
136
+ const change = (next: number) => emit('update:modelValue', next)
137
+
138
+ // The knob has no travel of its own: 100px of movement spans the whole
139
+ // range, and only the vertical axis carries a value, reversed so that
140
+ // dragging up raises it.
141
+ useDragValue(root, () => ({
142
+ axis: [range.value, { ...range.value, reverse: true }] as XY<AxisOptions>,
143
+ mapping: relativeMapping({
144
+ pixelRange: 100,
145
+ sensitivity: (state) =>
146
+ selectModifier(props.dragSensitivity, state.event).value,
147
+ }),
148
+ getValue: (): XY<number> => [props.modelValue, props.modelValue],
149
+ threshold: 1,
150
+ cursor: inactive.value
151
+ ? undefined
152
+ : (props.externalStyles?.cursor ?? 'grabbing'),
153
+ pointerLock: inactive.value ? false : props.pointerLock,
154
+ shouldStart: () => !inactive.value,
155
+ onChange: (v) => {
156
+ if (!inactive.value) change(v[1])
157
+ },
158
+ onDragStart: () => {
159
+ dragging.value = true
160
+ },
161
+ onDragEnd: () => {
162
+ dragging.value = false
163
+ },
164
+ }))
165
+
166
+ useWheel(
167
+ root,
168
+ (event) => {
169
+ if (!props.wheel || inactive.value) return
170
+ // A notch the knob does not read — a sideways scroll — is left to
171
+ // the page rather than swallowed.
172
+ const direction = wheelDirection(event)
173
+ if (direction === null) return
174
+ event.preventDefault()
175
+ change(
176
+ applyDelta(
177
+ props.modelValue,
178
+ direction,
179
+ props.wheel,
180
+ range.value,
181
+ event,
182
+ ),
183
+ )
184
+ },
185
+ { requireFocus: true },
186
+ )
187
+
188
+ expose({
189
+ /** Move the focus to the knob. */
190
+ focus: () => {
191
+ if (!props.disabled) root.value?.focus()
192
+ },
193
+ /** Take the focus away from the knob. */
194
+ blur: () => root.value?.blur(),
195
+ })
196
+
197
+ return () =>
198
+ h(
199
+ 'div',
200
+ {
201
+ ref: root,
202
+ role: 'slider',
203
+ tabindex: props.disabled ? -1 : 0,
204
+ 'aria-valuenow': props.modelValue,
205
+ 'aria-valuemin': props.min,
206
+ 'aria-valuemax': props.max,
207
+ 'aria-disabled': props.disabled,
208
+ 'aria-readonly': props.readonly,
209
+ 'data-disabled': props.disabled ? '' : undefined,
210
+ 'data-readonly': props.readonly ? '' : undefined,
211
+ 'data-dragging': dragging.value ? '' : undefined,
212
+ style: { '--knob-size': cssLength(props.size) },
213
+ onKeydown: (event: KeyboardEvent) => {
214
+ if (!props.keyboard || inactive.value) return
215
+ const direction = arrowKeyDirection(event.key)
216
+ if (direction === null) return
217
+ event.preventDefault()
218
+ change(
219
+ applyDelta(
220
+ props.modelValue,
221
+ direction,
222
+ props.keyboard,
223
+ range.value,
224
+ event,
225
+ ),
226
+ )
227
+ },
228
+ onDblclick: () => {
229
+ if (!inactive.value && props.enableDoubleClickDefault) {
230
+ change(props.defaultValue ?? props.min)
231
+ }
232
+ },
233
+ },
234
+ slots.default?.(),
235
+ )
236
+ },
237
+ })
@@ -0,0 +1,31 @@
1
+ import { defineComponent, h } from 'vue'
2
+
3
+ import { knobArcPath, knobArcRadius } from '@tremolo-ui/dom'
4
+
5
+ import { checkPlacement } from '../_util/placement'
6
+
7
+ import { useKnobContext } from './context'
8
+
9
+ /** The arc between `startValue` and the value, as an SVG `<path>`. */
10
+ export const KnobActiveLine = /* @__PURE__ */ defineComponent({
11
+ name: 'KnobActiveLine',
12
+ props: {
13
+ stroke: { type: String, default: 'currentColor' },
14
+ strokeWidth: { type: [Number, String], default: 6 },
15
+ },
16
+ setup(props) {
17
+ checkPlacement('KnobActiveLine', 'KnobSVGRoot')
18
+ const knob = useKnobContext()
19
+ return () =>
20
+ h('path', {
21
+ d: knobArcPath(
22
+ knob.angles.r2,
23
+ knob.angles.r3,
24
+ knobArcRadius(props.strokeWidth),
25
+ ),
26
+ fill: 'none',
27
+ stroke: props.stroke,
28
+ 'stroke-width': props.strokeWidth,
29
+ })
30
+ },
31
+ })
@@ -0,0 +1,36 @@
1
+ import { defineComponent, h } from 'vue'
2
+
3
+ import { knobArcPath, knobArcRadius } from '@tremolo-ui/dom'
4
+
5
+ import { checkPlacement } from '../_util/placement'
6
+
7
+ import { useKnobContext } from './context'
8
+
9
+ /**
10
+ * The travel outside the value, as SVG `<path>`s: one on each side of the
11
+ * active arc that has any length.
12
+ */
13
+ export const KnobInactiveLine = /* @__PURE__ */ defineComponent({
14
+ name: 'KnobInactiveLine',
15
+ inheritAttrs: false,
16
+ props: {
17
+ stroke: { type: String, default: 'currentColor' },
18
+ strokeWidth: { type: [Number, String], default: 6 },
19
+ },
20
+ setup(props, { attrs }) {
21
+ checkPlacement('KnobInactiveLine', 'KnobSVGRoot')
22
+ const knob = useKnobContext()
23
+ const path = (from: number, to: number) =>
24
+ h('path', {
25
+ ...attrs,
26
+ d: knobArcPath(from, to, knobArcRadius(props.strokeWidth)),
27
+ fill: 'none',
28
+ stroke: props.stroke,
29
+ 'stroke-width': props.strokeWidth,
30
+ })
31
+ return () => [
32
+ knob.startValue > knob.min ? path(knob.angles.r1, knob.angles.r2) : null,
33
+ knob.startValue < knob.max ? path(knob.angles.r3, knob.angles.r4) : null,
34
+ ]
35
+ },
36
+ })
@@ -0,0 +1,25 @@
1
+ import { defineComponent, h } from 'vue'
2
+
3
+ import { KNOB_VIEWBOX_SIZE } from '@tremolo-ui/dom'
4
+
5
+ import { providePlacement } from '../_util/placement'
6
+
7
+ /**
8
+ * The `<svg>` the drawing parts go in, in the order you want them painted.
9
+ * It sets the view box they are drawn against.
10
+ */
11
+ export const KnobSVGRoot = /* @__PURE__ */ defineComponent({
12
+ name: 'KnobSVGRoot',
13
+ setup(_, { slots }) {
14
+ providePlacement('KnobSVGRoot')
15
+ return () =>
16
+ h(
17
+ 'svg',
18
+ {
19
+ viewBox: `0 0 ${KNOB_VIEWBOX_SIZE} ${KNOB_VIEWBOX_SIZE}`,
20
+ style: { display: 'block' },
21
+ },
22
+ slots.default?.(),
23
+ )
24
+ },
25
+ })
@@ -0,0 +1,53 @@
1
+ import { defineComponent, h, type PropType } from 'vue'
2
+
3
+ import { KNOB_VIEWBOX_SIZE } from '@tremolo-ui/dom'
4
+ import { clamp } from '@tremolo-ui/functions'
5
+
6
+ import { checkPlacement } from '../_util/placement'
7
+
8
+ import { useKnobContext } from './context'
9
+
10
+ /** The circle that turns with the value, and the line on it that points at it. */
11
+ export const KnobThumb = /* @__PURE__ */ defineComponent({
12
+ name: 'KnobThumb',
13
+ props: {
14
+ /** Fill colour of the circle. @default 'currentColor' */
15
+ thumb: { type: String, default: 'currentColor' },
16
+ /** Colour of the line that points at the value. @default 'currentColor' */
17
+ thumbLine: { type: String, default: 'currentColor' },
18
+ /** Diameter of the circle, as a percentage of the knob. @default 84 */
19
+ thumbSize: { type: Number, default: 84 },
20
+ /** Thickness of the line, as a percentage of the knob. @default 6 */
21
+ thumbLineWeight: { type: Number, default: 6 },
22
+ /** How far down the line reaches, as a percentage. @default 35 */
23
+ thumbLineLength: { type: Number, default: 35 },
24
+ /** Classes for the line that points at the value. */
25
+ classes: Object as PropType<{ thumbLine?: string }>,
26
+ },
27
+ setup(props) {
28
+ checkPlacement('KnobThumb', 'KnobSVGRoot')
29
+ const knob = useKnobContext()
30
+ return () =>
31
+ h('svg', null, [
32
+ h('circle', {
33
+ cx: '50%',
34
+ cy: '50%',
35
+ r: `${props.thumbSize / 2}%`,
36
+ fill: props.thumb,
37
+ }),
38
+ h('line', {
39
+ class: props.classes?.thumbLine,
40
+ x1: '50%',
41
+ y1: `${(KNOB_VIEWBOX_SIZE - clamp(props.thumbSize, 0, 100)) / 2}%`,
42
+ x2: '50%',
43
+ y2: `${props.thumbLineLength}%`,
44
+ stroke: props.thumbLine,
45
+ 'stroke-width': `${props.thumbLineWeight}%`,
46
+ style: {
47
+ transform: `rotate(${knob.angles.r1 + knob.angles.p * knob.angleRange}deg)`,
48
+ transformOrigin: '50% 50%',
49
+ },
50
+ }),
51
+ ])
52
+ },
53
+ })
@@ -0,0 +1,30 @@
1
+ import { type InjectionKey } from 'vue'
2
+
3
+ import { type KnobAngles } from '@tremolo-ui/dom'
4
+ import { type Scale } from '@tremolo-ui/functions'
5
+
6
+ import { injectContext } from '../_util/context'
7
+
8
+ /**
9
+ * What `Knob` shares with its parts. The fields are getters, so reading one in
10
+ * a render or a `computed` follows the value as it changes.
11
+ */
12
+ export interface KnobContextValue {
13
+ readonly value: number
14
+ readonly min: number
15
+ readonly max: number
16
+ readonly step: number
17
+ readonly scale: Scale
18
+ readonly startValue: number
19
+ /** How far the knob turns from `min` to `max`, in degrees. */
20
+ readonly angleRange: number
21
+ /** The angles the parts are drawn with. See `knobAngles` in `@tremolo-ui/dom`. */
22
+ readonly angles: KnobAngles
23
+ }
24
+
25
+ export const KnobKey: InjectionKey<KnobContextValue> = Symbol('Knob')
26
+
27
+ /** The context of the enclosing `Knob`, for a part of your own. */
28
+ export function useKnobContext(): KnobContextValue {
29
+ return injectContext(KnobKey, 'Knob')
30
+ }
@@ -0,0 +1,6 @@
1
+ export { useKnobContext, type KnobContextValue } from './context'
2
+ export { Knob } from './Knob'
3
+ export { KnobActiveLine } from './KnobActiveLine'
4
+ export { KnobInactiveLine } from './KnobInactiveLine'
5
+ export { KnobSVGRoot } from './KnobSVGRoot'
6
+ export { KnobThumb } from './KnobThumb'