@tremolo-ui/svelte 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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/actions/drag-value.d.ts +22 -0
  4. package/dist/actions/drag-value.d.ts.map +1 -0
  5. package/dist/actions/drag-value.js +30 -0
  6. package/dist/actions/drag.d.ts +14 -0
  7. package/dist/actions/drag.d.ts.map +1 -0
  8. package/dist/actions/drag.js +22 -0
  9. package/dist/actions/drop-zone.d.ts +12 -0
  10. package/dist/actions/drop-zone.d.ts.map +1 -0
  11. package/dist/actions/drop-zone.js +20 -0
  12. package/dist/actions/long-press.d.ts +11 -0
  13. package/dist/actions/long-press.d.ts.map +1 -0
  14. package/dist/actions/long-press.js +24 -0
  15. package/dist/actions/wheel.d.ts +15 -0
  16. package/dist/actions/wheel.d.ts.map +1 -0
  17. package/dist/actions/wheel.js +32 -0
  18. package/dist/components/AnimationCanvas/AnimationCanvas.svelte +69 -0
  19. package/dist/components/AnimationCanvas/AnimationCanvas.svelte.d.ts +7 -0
  20. package/dist/components/AnimationCanvas/AnimationCanvas.svelte.d.ts.map +1 -0
  21. package/dist/components/AnimationCanvas/types.d.ts +46 -0
  22. package/dist/components/AnimationCanvas/types.d.ts.map +1 -0
  23. package/dist/components/AnimationCanvas/types.js +1 -0
  24. package/dist/components/DropZone/Root.svelte +47 -0
  25. package/dist/components/DropZone/Root.svelte.d.ts +7 -0
  26. package/dist/components/DropZone/Root.svelte.d.ts.map +1 -0
  27. package/dist/components/DropZone/index.d.ts +2 -0
  28. package/dist/components/DropZone/index.d.ts.map +1 -0
  29. package/dist/components/DropZone/index.js +1 -0
  30. package/dist/components/DropZone/types.d.ts +26 -0
  31. package/dist/components/DropZone/types.d.ts.map +1 -0
  32. package/dist/components/DropZone/types.js +1 -0
  33. package/dist/components/FileInput/Root.svelte +68 -0
  34. package/dist/components/FileInput/Root.svelte.d.ts +7 -0
  35. package/dist/components/FileInput/Root.svelte.d.ts.map +1 -0
  36. package/dist/components/FileInput/Trigger.svelte +28 -0
  37. package/dist/components/FileInput/Trigger.svelte.d.ts +11 -0
  38. package/dist/components/FileInput/Trigger.svelte.d.ts.map +1 -0
  39. package/dist/components/FileInput/context.d.ts +10 -0
  40. package/dist/components/FileInput/context.d.ts.map +1 -0
  41. package/dist/components/FileInput/context.js +5 -0
  42. package/dist/components/FileInput/index.d.ts +3 -0
  43. package/dist/components/FileInput/index.d.ts.map +1 -0
  44. package/dist/components/FileInput/index.js +2 -0
  45. package/dist/components/FileInput/types.d.ts +28 -0
  46. package/dist/components/FileInput/types.d.ts.map +1 -0
  47. package/dist/components/FileInput/types.js +1 -0
  48. package/dist/components/Knob/ActiveLine.svelte +29 -0
  49. package/dist/components/Knob/ActiveLine.svelte.d.ts +6 -0
  50. package/dist/components/Knob/ActiveLine.svelte.d.ts.map +1 -0
  51. package/dist/components/Knob/InactiveLine.svelte +41 -0
  52. package/dist/components/Knob/InactiveLine.svelte.d.ts +6 -0
  53. package/dist/components/Knob/InactiveLine.svelte.d.ts.map +1 -0
  54. package/dist/components/Knob/Root.svelte +189 -0
  55. package/dist/components/Knob/Root.svelte.d.ts +10 -0
  56. package/dist/components/Knob/Root.svelte.d.ts.map +1 -0
  57. package/dist/components/Knob/SVGRoot.svelte +28 -0
  58. package/dist/components/Knob/SVGRoot.svelte.d.ts +13 -0
  59. package/dist/components/Knob/SVGRoot.svelte.d.ts.map +1 -0
  60. package/dist/components/Knob/Thumb.svelte +43 -0
  61. package/dist/components/Knob/Thumb.svelte.d.ts +7 -0
  62. package/dist/components/Knob/Thumb.svelte.d.ts.map +1 -0
  63. package/dist/components/Knob/context.d.ts +22 -0
  64. package/dist/components/Knob/context.d.ts.map +1 -0
  65. package/dist/components/Knob/context.js +5 -0
  66. package/dist/components/Knob/index.d.ts +6 -0
  67. package/dist/components/Knob/index.d.ts.map +1 -0
  68. package/dist/components/Knob/index.js +5 -0
  69. package/dist/components/Knob/types.d.ts +139 -0
  70. package/dist/components/Knob/types.d.ts.map +1 -0
  71. package/dist/components/Knob/types.js +1 -0
  72. package/dist/components/NumberInput/DecrementStepper.svelte +11 -0
  73. package/dist/components/NumberInput/DecrementStepper.svelte.d.ts +9 -0
  74. package/dist/components/NumberInput/DecrementStepper.svelte.d.ts.map +1 -0
  75. package/dist/components/NumberInput/IncrementStepper.svelte +11 -0
  76. package/dist/components/NumberInput/IncrementStepper.svelte.d.ts +9 -0
  77. package/dist/components/NumberInput/IncrementStepper.svelte.d.ts.map +1 -0
  78. package/dist/components/NumberInput/InputField.svelte +134 -0
  79. package/dist/components/NumberInput/InputField.svelte.d.ts +8 -0
  80. package/dist/components/NumberInput/InputField.svelte.d.ts.map +1 -0
  81. package/dist/components/NumberInput/Root.svelte +209 -0
  82. package/dist/components/NumberInput/Root.svelte.d.ts +10 -0
  83. package/dist/components/NumberInput/Root.svelte.d.ts.map +1 -0
  84. package/dist/components/NumberInput/Stepper.svelte +61 -0
  85. package/dist/components/NumberInput/Stepper.svelte.d.ts +10 -0
  86. package/dist/components/NumberInput/Stepper.svelte.d.ts.map +1 -0
  87. package/dist/components/NumberInput/StepperButton.svelte +63 -0
  88. package/dist/components/NumberInput/StepperButton.svelte.d.ts +10 -0
  89. package/dist/components/NumberInput/StepperButton.svelte.d.ts.map +1 -0
  90. package/dist/components/NumberInput/context.d.ts +48 -0
  91. package/dist/components/NumberInput/context.d.ts.map +1 -0
  92. package/dist/components/NumberInput/context.js +8 -0
  93. package/dist/components/NumberInput/index.d.ts +6 -0
  94. package/dist/components/NumberInput/index.d.ts.map +1 -0
  95. package/dist/components/NumberInput/index.js +5 -0
  96. package/dist/components/NumberInput/types.d.ts +104 -0
  97. package/dist/components/NumberInput/types.d.ts.map +1 -0
  98. package/dist/components/NumberInput/types.js +1 -0
  99. package/dist/components/Piano/Root.svelte +177 -0
  100. package/dist/components/Piano/Root.svelte.d.ts +10 -0
  101. package/dist/components/Piano/Root.svelte.d.ts.map +1 -0
  102. package/dist/components/Piano/index.d.ts +2 -0
  103. package/dist/components/Piano/index.d.ts.map +1 -0
  104. package/dist/components/Piano/index.js +1 -0
  105. package/dist/components/Piano/types.d.ts +90 -0
  106. package/dist/components/Piano/types.d.ts.map +1 -0
  107. package/dist/components/Piano/types.js +1 -0
  108. package/dist/components/PointsEditor/Background.svelte +14 -0
  109. package/dist/components/PointsEditor/Background.svelte.d.ts +9 -0
  110. package/dist/components/PointsEditor/Background.svelte.d.ts.map +1 -0
  111. package/dist/components/PointsEditor/Container.svelte +82 -0
  112. package/dist/components/PointsEditor/Container.svelte.d.ts +10 -0
  113. package/dist/components/PointsEditor/Container.svelte.d.ts.map +1 -0
  114. package/dist/components/PointsEditor/Point.svelte +213 -0
  115. package/dist/components/PointsEditor/Point.svelte.d.ts +7 -0
  116. package/dist/components/PointsEditor/Point.svelte.d.ts.map +1 -0
  117. package/dist/components/PointsEditor/Root.svelte +107 -0
  118. package/dist/components/PointsEditor/Root.svelte.d.ts +7 -0
  119. package/dist/components/PointsEditor/Root.svelte.d.ts.map +1 -0
  120. package/dist/components/PointsEditor/SelectionBox.svelte +32 -0
  121. package/dist/components/PointsEditor/SelectionBox.svelte.d.ts +9 -0
  122. package/dist/components/PointsEditor/SelectionBox.svelte.d.ts.map +1 -0
  123. package/dist/components/PointsEditor/context.d.ts +28 -0
  124. package/dist/components/PointsEditor/context.d.ts.map +1 -0
  125. package/dist/components/PointsEditor/context.js +5 -0
  126. package/dist/components/PointsEditor/index.d.ts +6 -0
  127. package/dist/components/PointsEditor/index.d.ts.map +1 -0
  128. package/dist/components/PointsEditor/index.js +5 -0
  129. package/dist/components/PointsEditor/types.d.ts +84 -0
  130. package/dist/components/PointsEditor/types.d.ts.map +1 -0
  131. package/dist/components/PointsEditor/types.js +1 -0
  132. package/dist/components/Slider/Marks.svelte +42 -0
  133. package/dist/components/Slider/Marks.svelte.d.ts +7 -0
  134. package/dist/components/Slider/Marks.svelte.d.ts.map +1 -0
  135. package/dist/components/Slider/MarksOption.svelte +63 -0
  136. package/dist/components/Slider/MarksOption.svelte.d.ts +7 -0
  137. package/dist/components/Slider/MarksOption.svelte.d.ts.map +1 -0
  138. package/dist/components/Slider/Root.svelte +217 -0
  139. package/dist/components/Slider/Root.svelte.d.ts +10 -0
  140. package/dist/components/Slider/Root.svelte.d.ts.map +1 -0
  141. package/dist/components/Slider/Thumb.svelte +84 -0
  142. package/dist/components/Slider/Thumb.svelte.d.ts +10 -0
  143. package/dist/components/Slider/Thumb.svelte.d.ts.map +1 -0
  144. package/dist/components/Slider/Track.svelte +51 -0
  145. package/dist/components/Slider/Track.svelte.d.ts +7 -0
  146. package/dist/components/Slider/Track.svelte.d.ts.map +1 -0
  147. package/dist/components/Slider/context.d.ts +28 -0
  148. package/dist/components/Slider/context.d.ts.map +1 -0
  149. package/dist/components/Slider/context.js +5 -0
  150. package/dist/components/Slider/index.d.ts +6 -0
  151. package/dist/components/Slider/index.d.ts.map +1 -0
  152. package/dist/components/Slider/index.js +5 -0
  153. package/dist/components/Slider/types.d.ts +138 -0
  154. package/dist/components/Slider/types.d.ts.map +1 -0
  155. package/dist/components/Slider/types.js +1 -0
  156. package/dist/components/XYPad/Area.svelte +29 -0
  157. package/dist/components/XYPad/Area.svelte.d.ts +10 -0
  158. package/dist/components/XYPad/Area.svelte.d.ts.map +1 -0
  159. package/dist/components/XYPad/Root.svelte +238 -0
  160. package/dist/components/XYPad/Root.svelte.d.ts +10 -0
  161. package/dist/components/XYPad/Root.svelte.d.ts.map +1 -0
  162. package/dist/components/XYPad/Thumb.svelte +109 -0
  163. package/dist/components/XYPad/Thumb.svelte.d.ts +10 -0
  164. package/dist/components/XYPad/Thumb.svelte.d.ts.map +1 -0
  165. package/dist/components/XYPad/context.d.ts +25 -0
  166. package/dist/components/XYPad/context.d.ts.map +1 -0
  167. package/dist/components/XYPad/context.js +5 -0
  168. package/dist/components/XYPad/index.d.ts +4 -0
  169. package/dist/components/XYPad/index.d.ts.map +1 -0
  170. package/dist/components/XYPad/index.js +3 -0
  171. package/dist/components/XYPad/types.d.ts +80 -0
  172. package/dist/components/XYPad/types.d.ts.map +1 -0
  173. package/dist/components/XYPad/types.js +1 -0
  174. package/dist/components/_util/VisuallyHiddenRangeInput.svelte +17 -0
  175. package/dist/components/_util/VisuallyHiddenRangeInput.svelte.d.ts +8 -0
  176. package/dist/components/_util/VisuallyHiddenRangeInput.svelte.d.ts.map +1 -0
  177. package/dist/components/_util/check-steps.svelte.d.ts +10 -0
  178. package/dist/components/_util/check-steps.svelte.d.ts.map +1 -0
  179. package/dist/components/_util/check-steps.svelte.js +17 -0
  180. package/dist/components/_util/placement.d.ts +15 -0
  181. package/dist/components/_util/placement.d.ts.map +1 -0
  182. package/dist/components/_util/placement.js +30 -0
  183. package/dist/components/_util/style.d.ts +5 -0
  184. package/dist/components/_util/style.d.ts.map +1 -0
  185. package/dist/components/_util/style.js +9 -0
  186. package/dist/hooks/useMIDIAccess.svelte.d.ts +29 -0
  187. package/dist/hooks/useMIDIAccess.svelte.d.ts.map +1 -0
  188. package/dist/hooks/useMIDIAccess.svelte.js +62 -0
  189. package/dist/hooks/useMIDIInput.svelte.d.ts +18 -0
  190. package/dist/hooks/useMIDIInput.svelte.d.ts.map +1 -0
  191. package/dist/hooks/useMIDIInput.svelte.js +31 -0
  192. package/dist/hooks/useMIDIMessage.svelte.d.ts +9 -0
  193. package/dist/hooks/useMIDIMessage.svelte.d.ts.map +1 -0
  194. package/dist/hooks/useMIDIMessage.svelte.js +23 -0
  195. package/dist/index.d.ts +33 -0
  196. package/dist/index.d.ts.map +1 -0
  197. package/dist/index.js +26 -0
  198. package/package.json +72 -0
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import StepperButton from './StepperButton.svelte'
3
+
4
+ import type { Snippet } from 'svelte'
5
+ import type { HTMLAttributes } from 'svelte/elements'
6
+
7
+ /** Lowers the value by one `step`, repeating while held. */
8
+ let props: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props()
9
+ </script>
10
+
11
+ <StepperButton direction={-1} {...props} />
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ children?: Snippet;
5
+ };
6
+ declare const DecrementStepper: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type DecrementStepper = ReturnType<typeof DecrementStepper>;
8
+ export default DecrementStepper;
9
+ //# sourceMappingURL=DecrementStepper.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DecrementStepper.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/DecrementStepper.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAErD,KAAK,gBAAgB,GAAI,cAAc,CAAC,cAAc,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAelF,QAAA,MAAM,gBAAgB,sDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import StepperButton from './StepperButton.svelte'
3
+
4
+ import type { Snippet } from 'svelte'
5
+ import type { HTMLAttributes } from 'svelte/elements'
6
+
7
+ /** Raises the value by one `step`, repeating while held. */
8
+ let props: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props()
9
+ </script>
10
+
11
+ <StepperButton direction={1} {...props} />
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ children?: Snippet;
5
+ };
6
+ declare const IncrementStepper: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type IncrementStepper = ReturnType<typeof IncrementStepper>;
8
+ export default IncrementStepper;
9
+ //# sourceMappingURL=IncrementStepper.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IncrementStepper.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/IncrementStepper.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAErD,KAAK,gBAAgB,GAAI,cAAc,CAAC,cAAc,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAelF,QAAA,MAAM,gBAAgB,sDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,134 @@
1
+ <script lang="ts">
2
+ import {
3
+ caretAtDecimalOffset,
4
+ caretDecimalOffset,
5
+ numberSpan,
6
+ } from '@tremolo-ui/dom'
7
+ import { tick } from 'svelte'
8
+
9
+ import { useNumberInputContext } from './context.js'
10
+
11
+ import type { HTMLInputAttributes } from 'svelte/elements'
12
+
13
+ // The value belongs to `NumberInput.Root`, and the field is always text.
14
+ type Props = Omit<HTMLInputAttributes, 'type' | 'value'> & {
15
+ ref?: HTMLInputElement | null
16
+ }
17
+
18
+ let {
19
+ ref = $bindable(null),
20
+ onfocus,
21
+ oninput,
22
+ onblur,
23
+ onkeydown,
24
+ ...rest
25
+ }: Props = $props()
26
+
27
+ const field = useNumberInputContext()
28
+
29
+ $effect(() => {
30
+ field.setInput(ref)
31
+ return () => field.setInput(null)
32
+ })
33
+
34
+ let focused = $state(false)
35
+
36
+ // Only until the first keystroke: from then on the draft is the user's own
37
+ // text and stands on its own, formatted or not.
38
+ const shown = $derived(
39
+ field.unformatOnFocus && focused && !field.editing
40
+ ? String(field.value)
41
+ : field.text,
42
+ )
43
+
44
+ async function select() {
45
+ if (field.selectOnFocus === 'none') return
46
+ // Selecting has to wait for `unformatOnFocus` to swap the text in, or it
47
+ // would cover the wrong characters.
48
+ await tick()
49
+ const input = ref
50
+ if (!input || !focused) return
51
+ const span =
52
+ field.selectOnFocus === 'all'
53
+ ? { start: 0, end: input.value.length }
54
+ : numberSpan(input.value)
55
+ if (span) input.setSelectionRange(span.start, span.end)
56
+ }
57
+
58
+ async function step(direction: number, event: KeyboardEvent) {
59
+ const input = event.currentTarget as HTMLInputElement
60
+ const from = input.value
61
+ const offset =
62
+ field.keepCaretOnStep && input.selectionStart !== null
63
+ ? caretDecimalOffset(from, input.selectionStart)
64
+ : null
65
+ field.nudge(direction, field.keyboard!, event)
66
+ if (offset === null) return
67
+ await tick()
68
+ // The step may have been clamped away, leaving the text as it was.
69
+ if (input.value === from) return
70
+ const caret = caretAtDecimalOffset(input.value, offset)
71
+ input.setSelectionRange(caret, caret)
72
+ }
73
+ </script>
74
+
75
+ <!-- Not type="number": that brings native spinners and a value the browser
76
+ parses itself, neither of which survives a unit suffix. The ARIA value text
77
+ is the formatted text even while the plain number is shown: it is the one
78
+ that says what the value means. -->
79
+ <!-- The caller's attributes go first, so that the field's own state —
80
+ disabled, readonly, the ARIA — cannot be overridden by them. -->
81
+ <input
82
+ {...rest}
83
+ bind:this={ref}
84
+ type="text"
85
+ inputmode="decimal"
86
+ role="spinbutton"
87
+ value={shown}
88
+ disabled={field.disabled}
89
+ readonly={field.readonly}
90
+ aria-disabled={field.disabled}
91
+ aria-readonly={field.readonly}
92
+ aria-valuenow={field.value}
93
+ aria-valuemin={field.min}
94
+ aria-valuemax={field.max}
95
+ aria-valuetext={field.text}
96
+ step={field.step}
97
+ data-disabled={field.disabled ? '' : undefined}
98
+ data-readonly={field.readonly ? '' : undefined}
99
+ data-out-of-range={field.outOfRange ? '' : undefined}
100
+ oninput={(event) => {
101
+ field.setDraft(event.currentTarget.value)
102
+ oninput?.(event)
103
+ }}
104
+ onfocus={(event) => {
105
+ focused = true
106
+ select()
107
+ onfocus?.(event)
108
+ }}
109
+ onblur={(event) => {
110
+ focused = false
111
+ // Nothing was typed, so there is no draft and this returns at once —
112
+ // taking focus and leaving again never commits anything.
113
+ field.commitDraft()
114
+ onblur?.(event)
115
+ }}
116
+ onkeydown={(event) => {
117
+ const key = event.key
118
+ if (key === 'Enter') {
119
+ field.commitDraft()
120
+ if (field.blurOnEnter) event.currentTarget.blur()
121
+ } else if (
122
+ field.keyboard &&
123
+ !field.disabled &&
124
+ !field.readonly &&
125
+ (key === 'ArrowUp' || key === 'ArrowDown') &&
126
+ // Arrow keys pick a candidate while an IME is converting.
127
+ !event.isComposing
128
+ ) {
129
+ event.preventDefault()
130
+ step(key === 'ArrowUp' ? 1 : -1, event)
131
+ }
132
+ onkeydown?.(event)
133
+ }}
134
+ />
@@ -0,0 +1,8 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ type Props = Omit<HTMLInputAttributes, 'type' | 'value'> & {
3
+ ref?: HTMLInputElement | null;
4
+ };
5
+ declare const InputField: import("svelte").Component<Props, {}, "ref">;
6
+ type InputField = ReturnType<typeof InputField>;
7
+ export default InputField;
8
+ //# sourceMappingURL=InputField.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputField.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/InputField.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIzD,KAAK,KAAK,GAAG,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG;IACzD,GAAG,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;CAC9B,CAAC;AAuGJ,QAAA,MAAM,UAAU,8CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,209 @@
1
+ <script lang="ts">
2
+ import {
3
+ commitNumberInputText,
4
+ DEFAULT_DRAG_SENSITIVITY,
5
+ DEFAULT_KEYBOARD_OPTIONS,
6
+ DEFAULT_WHEEL_OPTIONS,
7
+ numberInputBounds,
8
+ numberInputRanges,
9
+ nudgeNumberInput,
10
+ parseNumberText,
11
+ wheelDirection,
12
+ } from '@tremolo-ui/dom'
13
+ import { linearScale } from '@tremolo-ui/functions'
14
+
15
+ import { wheel as wheelAction } from '../../actions/wheel.js'
16
+ import { useCheckSteps } from '../_util/check-steps.svelte.js'
17
+
18
+ import { setNumberInputContext } from './context.js'
19
+ import type { NumberInputProps } from './types.js'
20
+
21
+ import type { HTMLAttributes } from 'svelte/elements'
22
+
23
+ type Props = NumberInputProps &
24
+ Omit<HTMLAttributes<HTMLDivElement>, keyof NumberInputProps>
25
+
26
+ let {
27
+ value = $bindable(),
28
+ min,
29
+ max,
30
+ step = 1,
31
+ scale = linearScale,
32
+ format = String,
33
+ parse = parseNumberText,
34
+ clampValue = true,
35
+ wheel = DEFAULT_WHEEL_OPTIONS,
36
+ keyboard = DEFAULT_KEYBOARD_OPTIONS,
37
+ drag = 1,
38
+ dragSensitivity = DEFAULT_DRAG_SENSITIVITY,
39
+ pointerLock = false,
40
+ selectOnFocus = 'none',
41
+ unformatOnFocus = false,
42
+ keepCaretOnStep = false,
43
+ blurOnEnter = true,
44
+ disabled = false,
45
+ readonly = false,
46
+ onChange,
47
+ ref = $bindable(null),
48
+ children,
49
+ ...rest
50
+ }: Props = $props()
51
+
52
+ /**
53
+ * The text being typed. The only state here: it is not a copy of `value`,
54
+ * but the half-finished entry that has no value to be derived from yet.
55
+ */
56
+ let draft: string | null = $state(null)
57
+ let input: HTMLInputElement | null = null
58
+
59
+ const inactive = $derived(disabled || readonly)
60
+ const ranges = $derived(
61
+ numberInputRanges({ min, max, step, scale, clampValue }),
62
+ )
63
+ const bounds = $derived(numberInputBounds(value, { min, max, clampValue }))
64
+ const text = $derived(draft ?? format(value))
65
+
66
+ // An open end has no travel to sample, so the check is skipped.
67
+ useCheckSteps(() => ({
68
+ component: 'NumberInput',
69
+ range:
70
+ min !== undefined && max !== undefined && min < max
71
+ ? { min, max, step, scale }
72
+ : null,
73
+ keyboard,
74
+ wheel,
75
+ format,
76
+ }))
77
+
78
+ function changeValue(next: number) {
79
+ if (inactive) return
80
+ draft = null
81
+ if (next === value) return
82
+ value = next
83
+ onChange?.(next)
84
+ }
85
+
86
+ setNumberInputContext({
87
+ get value() {
88
+ return value
89
+ },
90
+ get min() {
91
+ return min
92
+ },
93
+ get max() {
94
+ return max
95
+ },
96
+ get step() {
97
+ return step
98
+ },
99
+ get disabled() {
100
+ return disabled
101
+ },
102
+ get readonly() {
103
+ return readonly
104
+ },
105
+ get keyboard() {
106
+ return keyboard
107
+ },
108
+ get drag() {
109
+ return drag
110
+ },
111
+ get dragSensitivity() {
112
+ return dragSensitivity
113
+ },
114
+ get pointerLock() {
115
+ return pointerLock
116
+ },
117
+ get selectOnFocus() {
118
+ return selectOnFocus
119
+ },
120
+ get unformatOnFocus() {
121
+ return unformatOnFocus
122
+ },
123
+ get keepCaretOnStep() {
124
+ return keepCaretOnStep
125
+ },
126
+ get blurOnEnter() {
127
+ return blurOnEnter
128
+ },
129
+ get text() {
130
+ return text
131
+ },
132
+ get editing() {
133
+ return draft !== null
134
+ },
135
+ get outOfRange() {
136
+ return draft === null && bounds.outOfRange
137
+ },
138
+ get atMin() {
139
+ return bounds.atMin
140
+ },
141
+ get atMax() {
142
+ return bounds.atMax
143
+ },
144
+ get rawRange() {
145
+ return ranges.raw
146
+ },
147
+ setDraft: (next) => {
148
+ if (inactive) return
149
+ draft = next
150
+ // Deliberately unclamped: clamping here would make "1500" impossible
151
+ // to type into an input whose max is 100.
152
+ const parsed = parse(next)
153
+ if (Number.isFinite(parsed)) {
154
+ value = parsed
155
+ onChange?.(parsed)
156
+ }
157
+ },
158
+ commitDraft: () => {
159
+ if (draft === null || inactive) return
160
+ const committed = commitNumberInputText(draft, parse, {
161
+ min,
162
+ max,
163
+ clampValue,
164
+ })
165
+ // Text with no number is not a value: the field goes back to what it
166
+ // was showing.
167
+ if (committed === null) draft = null
168
+ else changeValue(committed)
169
+ },
170
+ changeValue,
171
+ nudge: (direction, option, modifiers) =>
172
+ changeValue(
173
+ nudgeNumberInput(value, direction, option, ranges, modifiers),
174
+ ),
175
+ setInput: (element) => {
176
+ input = element
177
+ },
178
+ })
179
+
180
+ const wheelOptions = $derived({
181
+ requireFocus: true,
182
+ onWheel: (event: WheelEvent) => {
183
+ const direction = wheelDirection(event)
184
+ if (!wheel || inactive || direction === null) return
185
+ event.preventDefault()
186
+ changeValue(nudgeNumberInput(value, direction, wheel, ranges, event))
187
+ },
188
+ })
189
+
190
+ /** Move the focus to the field. */
191
+ export function focus() {
192
+ if (!disabled) input?.focus()
193
+ }
194
+
195
+ /** Take the focus away from the field. */
196
+ export function blur() {
197
+ input?.blur()
198
+ }
199
+ </script>
200
+
201
+ <div
202
+ bind:this={ref}
203
+ data-disabled={disabled ? '' : undefined}
204
+ data-readonly={readonly ? '' : undefined}
205
+ use:wheelAction={wheelOptions}
206
+ {...rest}
207
+ >
208
+ {@render children()}
209
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { NumberInputProps } from './types.js';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = NumberInputProps & Omit<HTMLAttributes<HTMLDivElement>, keyof NumberInputProps>;
4
+ declare const Root: import("svelte").Component<Props, {
5
+ focus: () => void;
6
+ blur: () => void;
7
+ }, "ref" | "value">;
8
+ type Root = ReturnType<typeof Root>;
9
+ export default Root;
10
+ //# sourceMappingURL=Root.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Root.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/Root.svelte.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,KAAK,GAAG,gBAAgB,GAC3B,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,MAAM,gBAAgB,CAAC,CAAC;AAmMjE,QAAA,MAAM,IAAI;;;mBAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import { createStepperDrag, type StepperDragInstance } from '@tremolo-ui/dom'
3
+ import { untrack } from 'svelte'
4
+
5
+ import { setStepperContext, useNumberInputContext } from './context.js'
6
+
7
+ import type { Snippet } from 'svelte'
8
+ import type { HTMLAttributes } from 'svelte/elements'
9
+
10
+ type Props = HTMLAttributes<HTMLDivElement> & {
11
+ ref?: HTMLDivElement | null
12
+ children?: Snippet
13
+ }
14
+
15
+ let { ref = $bindable(null), children, ...rest }: Props = $props()
16
+
17
+ const field = useNumberInputContext()
18
+
19
+ // Only attached while it can do something: `createDrag` puts
20
+ // `touch-action: none` on the element, and a stepper that cannot be dragged
21
+ // has no reason to stop the page scrolling under a finger.
22
+ const enabled = $derived(
23
+ field.drag !== null && !field.disabled && !field.readonly,
24
+ )
25
+ const options = $derived({
26
+ range: field.rawRange,
27
+ pixels: field.drag ?? 1,
28
+ sensitivity: field.dragSensitivity,
29
+ pointerLock: field.pointerLock,
30
+ })
31
+
32
+ let instance: StepperDragInstance | null = null
33
+
34
+ $effect(() => {
35
+ if (!ref || !enabled) return
36
+ const current = createStepperDrag(ref, {
37
+ // Read once here; the effect below keeps them current without tearing
38
+ // the drag down.
39
+ ...untrack(() => options),
40
+ getValue: () => field.value,
41
+ onChange: (next) => field.changeValue(next),
42
+ })
43
+ instance = current
44
+ return () => {
45
+ current.destroy()
46
+ instance = null
47
+ }
48
+ })
49
+
50
+ $effect(() => {
51
+ instance?.update(options)
52
+ })
53
+
54
+ setStepperContext({ moved: () => instance?.moved() ?? false })
55
+ </script>
56
+
57
+ <!-- The area the steppers sit in, and a drag handle in its own right:
58
+ dragging it up and down moves the value one `step` every `drag` pixels. -->
59
+ <div bind:this={ref} {...rest}>
60
+ {@render children?.()}
61
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = HTMLAttributes<HTMLDivElement> & {
4
+ ref?: HTMLDivElement | null;
5
+ children?: Snippet;
6
+ };
7
+ declare const Stepper: import("svelte").Component<Props, {}, "ref">;
8
+ type Stepper = ReturnType<typeof Stepper>;
9
+ export default Stepper;
10
+ //# sourceMappingURL=Stepper.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stepper.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/Stepper.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,KAAK,GAAG,cAAc,CAAC,cAAc,CAAC,GAAG;IAC5C,GAAG,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC;AA4DJ,QAAA,MAAM,OAAO,8CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import { longPress } from '../../actions/long-press.js'
3
+
4
+ import { useNumberInputContext, useStepperContext } from './context.js'
5
+
6
+ import type { Snippet } from 'svelte'
7
+ import type { HTMLAttributes } from 'svelte/elements'
8
+
9
+ type Props = HTMLAttributes<HTMLDivElement> & {
10
+ direction: 1 | -1
11
+ children?: Snippet
12
+ }
13
+
14
+ let { direction, children, ...rest }: Props = $props()
15
+
16
+ const field = useNumberInputContext()
17
+ const stepper = useStepperContext()
18
+
19
+ const blocked = $derived(
20
+ field.disabled || (direction > 0 ? field.atMax : field.atMin),
21
+ )
22
+
23
+ const pressOptions = {
24
+ onPress: () => {
25
+ if (field.disabled || field.readonly) return
26
+ // Once the pointer has actually travelled, the drag on `Stepper` owns
27
+ // the value; repeating on top of it would move it twice.
28
+ if (stepper?.moved()) return
29
+ field.nudge(direction, ['raw', field.step])
30
+ },
31
+ }
32
+ </script>
33
+
34
+ <!-- A bare arrow has no accessible name of its own. Overridable, since a
35
+ caller may need it in their own language. `role="button"` does not take
36
+ aria-readonly, so that state reaches the styles through the data attribute
37
+ alone. -->
38
+ <div
39
+ role="button"
40
+ tabindex="-1"
41
+ aria-label={direction > 0 ? 'Increment' : 'Decrement'}
42
+ aria-disabled={blocked}
43
+ data-disabled={blocked ? '' : undefined}
44
+ data-readonly={field.readonly ? '' : undefined}
45
+ use:longPress={pressOptions}
46
+ {...rest}
47
+ >
48
+ {#if children}
49
+ {@render children()}
50
+ {:else}
51
+ <svg
52
+ style="width: var(--stepper-icon-size); height: var(--stepper-icon-size)"
53
+ viewBox="0 0 24 24"
54
+ fill="none"
55
+ stroke="currentColor"
56
+ stroke-width="2"
57
+ stroke-linecap="round"
58
+ stroke-linejoin="round"
59
+ >
60
+ <polyline points={direction > 0 ? '18 15 12 9 6 15' : '6 9 12 15 18 9'} />
61
+ </svg>
62
+ {/if}
63
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = HTMLAttributes<HTMLDivElement> & {
4
+ direction: 1 | -1;
5
+ children?: Snippet;
6
+ };
7
+ declare const StepperButton: import("svelte").Component<Props, {}, "">;
8
+ type StepperButton = ReturnType<typeof StepperButton>;
9
+ export default StepperButton;
10
+ //# sourceMappingURL=StepperButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StepperButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/StepperButton.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,KAAK,GAAG,cAAc,CAAC,cAAc,CAAC,GAAG;IAC5C,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC;AA2CJ,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { InputEventOption, ModifierState, ModifierValue } from '@tremolo-ui/dom';
2
+ import type { ValueRange } from '@tremolo-ui/functions';
3
+ /** What `NumberInput.Root` shares with its parts. Every field is a getter. */
4
+ export interface NumberInputContextValue {
5
+ readonly value: number;
6
+ readonly min: number | undefined;
7
+ readonly max: number | undefined;
8
+ readonly step: number;
9
+ readonly disabled: boolean;
10
+ readonly readonly: boolean;
11
+ readonly keyboard: ModifierValue<InputEventOption> | null;
12
+ readonly drag: number | null;
13
+ readonly dragSensitivity: ModifierValue<number>;
14
+ readonly pointerLock: boolean;
15
+ readonly selectOnFocus: 'all' | 'number' | 'none';
16
+ readonly unformatOnFocus: boolean;
17
+ readonly keepCaretOnStep: boolean;
18
+ readonly blurOnEnter: boolean;
19
+ /** The text the field shows: the draft while typing, else `format(value)`. */
20
+ readonly text: string;
21
+ /** Whether the user has typed since the value was last committed. */
22
+ readonly editing: boolean;
23
+ readonly outOfRange: boolean;
24
+ readonly atMin: boolean;
25
+ readonly atMax: boolean;
26
+ /** The range a raw amount moves across, for the stepper drag. */
27
+ readonly rawRange: ValueRange;
28
+ /** Keep typed text as the draft, reporting the number in it. */
29
+ setDraft: (text: string) => void;
30
+ /** Commit the draft, clamped, or drop it when it holds no number. */
31
+ commitDraft: () => void;
32
+ /** Set the value, dropping any draft. */
33
+ changeValue: (value: number) => void;
34
+ /** Move the value by one press of `option`. */
35
+ nudge: (direction: number, option: ModifierValue<InputEventOption>, modifiers?: ModifierState) => void;
36
+ /** @internal `InputField` registers the element focus() moves to. */
37
+ setInput: (input: HTMLInputElement | null) => void;
38
+ }
39
+ /** The context of the enclosing `NumberInput.Root`, for a part of your own. */
40
+ export declare const useNumberInputContext: () => NumberInputContextValue;
41
+ export declare const setNumberInputContext: (context: NumberInputContextValue) => NumberInputContextValue;
42
+ /** @internal Whether the drag on `Stepper` has moved the value. */
43
+ export interface StepperContextValue {
44
+ moved: () => boolean;
45
+ }
46
+ export declare const useStepperContext: () => StepperContextValue | null;
47
+ export declare const setStepperContext: (context: StepperContextValue) => StepperContextValue;
48
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,aAAa,EACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEvD,8EAA8E;AAC9E,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAA;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC/C,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,aAAa,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;IACjD,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAA;IAC7B,gEAAgE;IAChE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,qEAAqE;IACrE,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,yCAAyC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,+CAA+C;IAC/C,KAAK,EAAE,CACL,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,EACvC,SAAS,CAAC,EAAE,aAAa,KACtB,IAAI,CAAA;IACT,qEAAqE;IACrE,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAA;CACnD;AAID,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,+BAAM,CAAA;AACxC,eAAO,MAAM,qBAAqB,+DAAM,CAAA;AAExC,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,OAAO,CAAA;CACrB;AAKD,eAAO,MAAM,iBAAiB,kCAA6C,CAAA;AAC3E,eAAO,MAAM,iBAAiB,uDAAa,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { createContext } from 'svelte';
2
+ const [get, set] = createContext();
3
+ /** The context of the enclosing `NumberInput.Root`, for a part of your own. */
4
+ export const useNumberInputContext = get;
5
+ export const setNumberInputContext = set;
6
+ const [getStepper, setStepper, hasStepper] = createContext();
7
+ export const useStepperContext = () => (hasStepper() ? getStepper() : null);
8
+ export const setStepperContext = setStepper;
@@ -0,0 +1,6 @@
1
+ export { default as DecrementStepper } from './DecrementStepper.svelte';
2
+ export { default as IncrementStepper } from './IncrementStepper.svelte';
3
+ export { default as InputField } from './InputField.svelte';
4
+ export { default as Root } from './Root.svelte';
5
+ export { default as Stepper } from './Stepper.svelte';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { default as DecrementStepper } from './DecrementStepper.svelte';
2
+ export { default as IncrementStepper } from './IncrementStepper.svelte';
3
+ export { default as InputField } from './InputField.svelte';
4
+ export { default as Root } from './Root.svelte';
5
+ export { default as Stepper } from './Stepper.svelte';