tgui-core 1.0.2 → 1.0.4

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 (158) hide show
  1. package/{src/components → components}/AnimatedNumber.tsx +185 -185
  2. package/{src/components → components}/BlockQuote.tsx +15 -15
  3. package/{src/components → components}/BodyZoneSelector.tsx +149 -149
  4. package/{src/components → components}/Box.tsx +255 -255
  5. package/{src/components → components}/Button.tsx +415 -415
  6. package/{src/components → components}/ByondUi.jsx +121 -121
  7. package/{src/components → components}/Chart.tsx +160 -160
  8. package/{src/components → components}/ColorBox.tsx +30 -30
  9. package/{src/components → components}/Dimmer.tsx +19 -19
  10. package/{src/components → components}/Divider.tsx +26 -26
  11. package/{src/components → components}/DmIcon.tsx +72 -72
  12. package/{src/components → components}/DraggableControl.jsx +282 -282
  13. package/{src/components → components}/Dropdown.tsx +246 -246
  14. package/{src/components → components}/Flex.tsx +105 -105
  15. package/{src/components → components}/Icon.tsx +91 -91
  16. package/{src/components → components}/Input.tsx +181 -181
  17. package/{src/components → components}/KeyListener.tsx +40 -40
  18. package/{src/components → components}/Knob.tsx +185 -185
  19. package/{src/components → components}/LabeledList.tsx +130 -130
  20. package/{src/components → components}/MenuBar.tsx +233 -238
  21. package/{src/components → components}/Modal.tsx +25 -25
  22. package/{src/components → components}/NoticeBox.tsx +48 -48
  23. package/{src/components → components}/NumberInput.tsx +328 -328
  24. package/{src/components → components}/ProgressBar.tsx +79 -79
  25. package/{src/components → components}/RestrictedInput.jsx +301 -301
  26. package/{src/components → components}/RoundGauge.tsx +189 -189
  27. package/{src/components → components}/Section.tsx +125 -125
  28. package/{src/components → components}/Slider.tsx +173 -173
  29. package/{src/components → components}/Stack.tsx +101 -101
  30. package/{src/components → components}/Table.tsx +90 -90
  31. package/{src/components → components}/Tabs.tsx +90 -90
  32. package/{src/components → components}/TextArea.tsx +198 -198
  33. package/{src/components → components}/TimeDisplay.jsx +64 -64
  34. package/components/index.ts +51 -0
  35. package/{src/debug/KitchenSink.jsx → debug/KitchenSink.tsx} +56 -56
  36. package/{src/debug/actions.js → debug/actions.ts} +11 -11
  37. package/{src/debug/hooks.js → debug/hooks.ts} +10 -10
  38. package/{src/debug/middleware.js → debug/middleware.ts} +67 -86
  39. package/{src/debug/reducer.js → debug/reducer.ts} +27 -22
  40. package/{src/debug/selectors.js → debug/selectors.ts} +7 -7
  41. package/{src/layouts → layouts}/Layout.tsx +75 -75
  42. package/{src/layouts → layouts}/NtosWindow.tsx +162 -162
  43. package/{src/layouts → layouts}/Pane.tsx +56 -56
  44. package/{src/layouts → layouts}/Window.tsx +227 -227
  45. package/layouts/index.ts +10 -0
  46. package/package.json +3 -2
  47. package/src/assets.ts +43 -43
  48. package/src/backend.ts +368 -369
  49. package/src/drag.ts +280 -280
  50. package/src/events.ts +237 -237
  51. package/src/hotkeys.ts +212 -212
  52. package/src/renderer.ts +50 -50
  53. package/stories/Blink.stories.tsx +20 -0
  54. package/stories/BlockQuote.stories.tsx +23 -0
  55. package/stories/Box.stories.tsx +27 -0
  56. package/stories/Button.stories.tsx +68 -0
  57. package/stories/ByondUi.stories.tsx +45 -0
  58. package/stories/Collapsible.stories.tsx +23 -0
  59. package/stories/Flex.stories.tsx +68 -0
  60. package/stories/Input.stories.tsx +124 -0
  61. package/stories/LabeledList.stories.tsx +73 -0
  62. package/stories/Popper.stories.tsx +58 -0
  63. package/stories/ProgressBar.stories.tsx +58 -0
  64. package/stories/Stack.stories.tsx +55 -0
  65. package/stories/Storage.stories.tsx +46 -0
  66. package/stories/Themes.stories.tsx +30 -0
  67. package/stories/Tooltip.stories.tsx +48 -0
  68. package/stories/common.tsx +19 -0
  69. package/tsconfig.json +0 -21
  70. package/src/components/Grid.tsx +0 -44
  71. /package/{src/common → common}/collections.ts +0 -0
  72. /package/{src/common → common}/color.ts +0 -0
  73. /package/{src/common → common}/events.ts +0 -0
  74. /package/{src/common → common}/exhaustive.ts +0 -0
  75. /package/{src/common → common}/fp.ts +0 -0
  76. /package/{src/common → common}/keycodes.ts +0 -0
  77. /package/{src/common → common}/keys.ts +0 -0
  78. /package/{src/common → common}/math.ts +0 -0
  79. /package/{src/common → common}/perf.ts +0 -0
  80. /package/{src/common → common}/random.ts +0 -0
  81. /package/{src/common → common}/react.ts +0 -0
  82. /package/{src/common → common}/redux.ts +0 -0
  83. /package/{src/common → common}/storage.js +0 -0
  84. /package/{src/common → common}/string.ts +0 -0
  85. /package/{src/common → common}/timer.ts +0 -0
  86. /package/{src/common → common}/type-utils.ts +0 -0
  87. /package/{src/common → common}/types.ts +0 -0
  88. /package/{src/common → common}/uuid.ts +0 -0
  89. /package/{src/common → common}/vector.ts +0 -0
  90. /package/{src/components → components}/Autofocus.tsx +0 -0
  91. /package/{src/components → components}/Blink.jsx +0 -0
  92. /package/{src/components → components}/Collapsible.tsx +0 -0
  93. /package/{src/components → components}/Dialog.tsx +0 -0
  94. /package/{src/components → components}/FakeTerminal.jsx +0 -0
  95. /package/{src/components → components}/FitText.tsx +0 -0
  96. /package/{src/components → components}/Image.tsx +0 -0
  97. /package/{src/components → components}/InfinitePlane.jsx +0 -0
  98. /package/{src/components → components}/LabeledControls.tsx +0 -0
  99. /package/{src/components → components}/Popper.tsx +0 -0
  100. /package/{src/components → components}/StyleableSection.tsx +0 -0
  101. /package/{src/components → components}/Tooltip.tsx +0 -0
  102. /package/{src/components → components}/TrackOutsideClicks.tsx +0 -0
  103. /package/{src/components → components}/VirtualList.tsx +0 -0
  104. /package/{src/debug → debug}/index.ts +0 -0
  105. /package/{src/styles → styles}/base.scss +0 -0
  106. /package/{src/styles → styles}/colors.scss +0 -0
  107. /package/{src/styles → styles}/components/BlockQuote.scss +0 -0
  108. /package/{src/styles → styles}/components/Button.scss +0 -0
  109. /package/{src/styles → styles}/components/ColorBox.scss +0 -0
  110. /package/{src/styles → styles}/components/Dialog.scss +0 -0
  111. /package/{src/styles → styles}/components/Dimmer.scss +0 -0
  112. /package/{src/styles → styles}/components/Divider.scss +0 -0
  113. /package/{src/styles → styles}/components/Dropdown.scss +0 -0
  114. /package/{src/styles → styles}/components/Flex.scss +0 -0
  115. /package/{src/styles → styles}/components/Icon.scss +0 -0
  116. /package/{src/styles → styles}/components/Input.scss +0 -0
  117. /package/{src/styles → styles}/components/Knob.scss +0 -0
  118. /package/{src/styles → styles}/components/LabeledList.scss +0 -0
  119. /package/{src/styles → styles}/components/MenuBar.scss +0 -0
  120. /package/{src/styles → styles}/components/Modal.scss +0 -0
  121. /package/{src/styles → styles}/components/NoticeBox.scss +0 -0
  122. /package/{src/styles → styles}/components/NumberInput.scss +0 -0
  123. /package/{src/styles → styles}/components/ProgressBar.scss +0 -0
  124. /package/{src/styles → styles}/components/RoundGauge.scss +0 -0
  125. /package/{src/styles → styles}/components/Section.scss +0 -0
  126. /package/{src/styles → styles}/components/Slider.scss +0 -0
  127. /package/{src/styles → styles}/components/Stack.scss +0 -0
  128. /package/{src/styles → styles}/components/Table.scss +0 -0
  129. /package/{src/styles → styles}/components/Tabs.scss +0 -0
  130. /package/{src/styles → styles}/components/TextArea.scss +0 -0
  131. /package/{src/styles → styles}/components/Tooltip.scss +0 -0
  132. /package/{src/styles → styles}/functions.scss +0 -0
  133. /package/{src/styles → styles}/layouts/Layout.scss +0 -0
  134. /package/{src/styles → styles}/layouts/NtosHeader.scss +0 -0
  135. /package/{src/styles → styles}/layouts/NtosWindow.scss +0 -0
  136. /package/{src/styles → styles}/layouts/TitleBar.scss +0 -0
  137. /package/{src/styles → styles}/layouts/Window.scss +0 -0
  138. /package/{src/styles → styles}/main.scss +0 -0
  139. /package/{src/styles → styles}/reset.scss +0 -0
  140. /package/{src/styles → styles}/themes/abductor.scss +0 -0
  141. /package/{src/styles → styles}/themes/admin.scss +0 -0
  142. /package/{src/styles → styles}/themes/cardtable.scss +0 -0
  143. /package/{src/styles → styles}/themes/hackerman.scss +0 -0
  144. /package/{src/styles → styles}/themes/malfunction.scss +0 -0
  145. /package/{src/styles → styles}/themes/neutral.scss +0 -0
  146. /package/{src/styles → styles}/themes/ntOS95.scss +0 -0
  147. /package/{src/styles → styles}/themes/ntos.scss +0 -0
  148. /package/{src/styles → styles}/themes/ntos_cat.scss +0 -0
  149. /package/{src/styles → styles}/themes/ntos_darkmode.scss +0 -0
  150. /package/{src/styles → styles}/themes/ntos_lightmode.scss +0 -0
  151. /package/{src/styles → styles}/themes/ntos_spooky.scss +0 -0
  152. /package/{src/styles → styles}/themes/ntos_synth.scss +0 -0
  153. /package/{src/styles → styles}/themes/ntos_terminal.scss +0 -0
  154. /package/{src/styles → styles}/themes/paper.scss +0 -0
  155. /package/{src/styles → styles}/themes/retro.scss +0 -0
  156. /package/{src/styles → styles}/themes/spookyconsole.scss +0 -0
  157. /package/{src/styles → styles}/themes/syndicate.scss +0 -0
  158. /package/{src/styles → styles}/themes/wizard.scss +0 -0
@@ -1,328 +1,328 @@
1
- import { KEY } from '../common/keys';
2
- import { clamp } from '../common/math';
3
- import { BooleanLike, classes } from '../common/react';
4
- import {
5
- Component,
6
- createRef,
7
- FocusEventHandler,
8
- KeyboardEventHandler,
9
- MouseEventHandler,
10
- RefObject,
11
- } from 'react';
12
-
13
- import { AnimatedNumber } from './AnimatedNumber';
14
- import { Box } from './Box';
15
-
16
- type Props = Required<{
17
- value: number | string;
18
- minValue: number;
19
- maxValue: number;
20
- step: number;
21
- }> &
22
- Partial<{
23
- stepPixelSize: number;
24
- disabled: BooleanLike;
25
-
26
- className: string;
27
- fluid: BooleanLike;
28
- animated: BooleanLike;
29
- unit: string;
30
- height: string;
31
- width: string;
32
- lineHeight: string;
33
- fontSize: string;
34
- format: (value: number) => string;
35
- onChange: (value: number) => void;
36
- onDrag: (value: number) => void;
37
- }>;
38
-
39
- type State = {
40
- editing: BooleanLike;
41
- dragging: BooleanLike;
42
- currentValue: number;
43
- previousValue: number;
44
- origin: number;
45
- };
46
-
47
- export class NumberInput extends Component<Props, State> {
48
- // Ref to the input field to set focus & highlight
49
- inputRef: RefObject<HTMLInputElement> = createRef();
50
-
51
- // After this time has elapsed we are in drag mode so no editing when dragging ends
52
- dragTimeout: NodeJS.Timeout;
53
-
54
- // Call onDrag at this interval
55
- dragInterval: NodeJS.Timeout;
56
-
57
- // default values for the number input state
58
- state: State = {
59
- editing: false,
60
- dragging: false,
61
- currentValue: 0,
62
- previousValue: 0,
63
- origin: 0,
64
- };
65
-
66
- constructor(props: Props) {
67
- super(props);
68
- }
69
-
70
- componentDidMount(): void {
71
- let displayValue = parseFloat(this.props.value.toString());
72
-
73
- this.setState({
74
- currentValue: displayValue,
75
- previousValue: displayValue,
76
- });
77
- }
78
-
79
- handleDragStart: MouseEventHandler<HTMLDivElement> = (event) => {
80
- const { value, disabled } = this.props;
81
- const { editing } = this.state;
82
- if (disabled || editing) {
83
- return;
84
- }
85
- document.body.style['pointer-events'] = 'none';
86
-
87
- const parsedValue = parseFloat(value.toString());
88
- this.setState({
89
- dragging: false,
90
- origin: event.screenY,
91
- currentValue: parsedValue,
92
- previousValue: parsedValue,
93
- });
94
-
95
- this.dragTimeout = setTimeout(() => {
96
- this.setState({
97
- dragging: true,
98
- });
99
- }, 250);
100
- this.dragInterval = setInterval(() => {
101
- const { dragging, currentValue, previousValue } = this.state;
102
- const { onDrag } = this.props;
103
- if (dragging && currentValue !== previousValue) {
104
- this.setState({
105
- previousValue: currentValue,
106
- });
107
- onDrag?.(currentValue);
108
- }
109
- }, 400);
110
-
111
- document.addEventListener('mousemove', this.handleDragMove);
112
- document.addEventListener('mouseup', this.handleDragEnd);
113
- };
114
-
115
- handleDragMove = (event: MouseEvent) => {
116
- const { minValue, maxValue, step, stepPixelSize, disabled } = this.props;
117
- if (disabled) {
118
- return;
119
- }
120
-
121
- this.setState((prevState) => {
122
- const state = { ...prevState };
123
-
124
- const offset = state.origin - event.screenY;
125
- if (prevState.dragging) {
126
- const stepOffset = isFinite(minValue) ? minValue % step : 0;
127
- // Translate mouse movement to value
128
- // Give it some headroom (by increasing clamp range by 1 step)
129
- state.currentValue = clamp(
130
- state.currentValue + (offset * step) / (stepPixelSize || 1),
131
- minValue - step,
132
- maxValue + step
133
- );
134
- // Clamp the final value
135
- state.currentValue = clamp(
136
- state.currentValue - (state.currentValue % step) + stepOffset,
137
- minValue,
138
- maxValue
139
- );
140
- // Set the new origin
141
- state.origin = event.screenY;
142
- } else if (Math.abs(offset) > 4) {
143
- state.dragging = true;
144
- }
145
- return state;
146
- });
147
- };
148
-
149
- handleDragEnd = (event: MouseEvent) => {
150
- const { dragging, currentValue } = this.state;
151
- const { onDrag, onChange, disabled } = this.props;
152
- if (disabled) {
153
- return;
154
- }
155
- document.body.style['pointer-events'] = 'auto';
156
-
157
- clearInterval(this.dragInterval);
158
- clearTimeout(this.dragTimeout);
159
-
160
- this.setState({
161
- dragging: false,
162
- editing: !dragging,
163
- previousValue: currentValue,
164
- });
165
- if (dragging) {
166
- onChange?.(currentValue);
167
- onDrag?.(currentValue);
168
- } else if (this.inputRef) {
169
- const input = this.inputRef.current;
170
- if (input) {
171
- input.value = `${currentValue}`;
172
- setTimeout(() => {
173
- input.focus();
174
- input.select();
175
- }, 1);
176
- }
177
- }
178
-
179
- document.removeEventListener('mousemove', this.handleDragMove);
180
- document.removeEventListener('mouseup', this.handleDragEnd);
181
- };
182
-
183
- handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {
184
- const { editing, previousValue } = this.state;
185
- const { minValue, maxValue, onChange, onDrag, disabled } = this.props;
186
- if (disabled || !editing) {
187
- return;
188
- }
189
-
190
- const targetValue = clamp(
191
- parseFloat(event.target.value),
192
- minValue,
193
- maxValue
194
- );
195
- if (isNaN(targetValue)) {
196
- this.setState({
197
- editing: false,
198
- });
199
- return;
200
- }
201
-
202
- this.setState({
203
- editing: false,
204
- currentValue: targetValue,
205
- previousValue: targetValue,
206
- });
207
- if (previousValue !== targetValue) {
208
- onChange?.(targetValue);
209
- onDrag?.(targetValue);
210
- }
211
- };
212
-
213
- handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
214
- const { minValue, maxValue, onChange, onDrag, disabled } = this.props;
215
- if (disabled) {
216
- return;
217
- }
218
- const { previousValue } = this.state;
219
-
220
- if (event.key === KEY.Enter) {
221
- const targetValue = clamp(
222
- parseFloat(event.currentTarget.value),
223
- minValue,
224
- maxValue
225
- );
226
- if (isNaN(targetValue)) {
227
- this.setState({
228
- editing: false,
229
- });
230
- return;
231
- }
232
-
233
- this.setState({
234
- editing: false,
235
- currentValue: targetValue,
236
- previousValue: targetValue,
237
- });
238
- if (previousValue !== targetValue) {
239
- onChange?.(targetValue);
240
- onDrag?.(targetValue);
241
- }
242
- } else if (event.key === KEY.Escape) {
243
- this.setState({
244
- editing: false,
245
- });
246
- }
247
- };
248
-
249
- render() {
250
- const { dragging, editing, currentValue } = this.state;
251
-
252
- const {
253
- className,
254
- fluid,
255
- animated,
256
- unit,
257
- value,
258
- minValue,
259
- maxValue,
260
- height,
261
- width,
262
- lineHeight,
263
- fontSize,
264
- format,
265
- } = this.props;
266
-
267
- let displayValue = parseFloat(value.toString());
268
- if (dragging) {
269
- displayValue = currentValue;
270
- }
271
-
272
- const contentElement = (
273
- <div className="NumberInput__content">
274
- {animated && !dragging ? (
275
- <AnimatedNumber value={displayValue} format={format} />
276
- ) : format ? (
277
- format(displayValue)
278
- ) : (
279
- displayValue
280
- )}
281
-
282
- {unit ? ' ' + unit : ''}
283
- </div>
284
- );
285
-
286
- return (
287
- <Box
288
- className={classes([
289
- 'NumberInput',
290
- fluid && 'NumberInput--fluid',
291
- className,
292
- ])}
293
- minWidth={width}
294
- minHeight={height}
295
- lineHeight={lineHeight}
296
- fontSize={fontSize}
297
- onMouseDown={this.handleDragStart}
298
- >
299
- <div className="NumberInput__barContainer">
300
- <div
301
- className="NumberInput__bar"
302
- style={{
303
- height:
304
- clamp(
305
- ((displayValue - minValue) / (maxValue - minValue)) * 100,
306
- 0,
307
- 100
308
- ) + '%',
309
- }}
310
- />
311
- </div>
312
- {contentElement}
313
- <input
314
- ref={this.inputRef}
315
- className="NumberInput__input"
316
- style={{
317
- display: !editing ? 'none' : 'inline',
318
- height: height,
319
- lineHeight: lineHeight,
320
- fontSize: fontSize,
321
- }}
322
- onBlur={this.handleBlur}
323
- onKeyDown={this.handleKeyDown}
324
- />
325
- </Box>
326
- );
327
- }
328
- }
1
+ import { KEY } from '../common/keys';
2
+ import { clamp } from '../common/math';
3
+ import { BooleanLike, classes } from '../common/react';
4
+ import {
5
+ Component,
6
+ createRef,
7
+ FocusEventHandler,
8
+ KeyboardEventHandler,
9
+ MouseEventHandler,
10
+ RefObject,
11
+ } from 'react';
12
+
13
+ import { AnimatedNumber } from './AnimatedNumber';
14
+ import { Box } from './Box';
15
+
16
+ type Props = Required<{
17
+ value: number | string;
18
+ minValue: number;
19
+ maxValue: number;
20
+ step: number;
21
+ }> &
22
+ Partial<{
23
+ stepPixelSize: number;
24
+ disabled: BooleanLike;
25
+
26
+ className: string;
27
+ fluid: BooleanLike;
28
+ animated: BooleanLike;
29
+ unit: string;
30
+ height: string;
31
+ width: string;
32
+ lineHeight: string;
33
+ fontSize: string;
34
+ format: (value: number) => string;
35
+ onChange: (value: number) => void;
36
+ onDrag: (value: number) => void;
37
+ }>;
38
+
39
+ type State = {
40
+ editing: BooleanLike;
41
+ dragging: BooleanLike;
42
+ currentValue: number;
43
+ previousValue: number;
44
+ origin: number;
45
+ };
46
+
47
+ export class NumberInput extends Component<Props, State> {
48
+ // Ref to the input field to set focus & highlight
49
+ inputRef: RefObject<HTMLInputElement> = createRef();
50
+
51
+ // After this time has elapsed we are in drag mode so no editing when dragging ends
52
+ dragTimeout: NodeJS.Timeout;
53
+
54
+ // Call onDrag at this interval
55
+ dragInterval: NodeJS.Timeout;
56
+
57
+ // default values for the number input state
58
+ state: State = {
59
+ editing: false,
60
+ dragging: false,
61
+ currentValue: 0,
62
+ previousValue: 0,
63
+ origin: 0,
64
+ };
65
+
66
+ constructor(props: Props) {
67
+ super(props);
68
+ }
69
+
70
+ componentDidMount(): void {
71
+ let displayValue = parseFloat(this.props.value.toString());
72
+
73
+ this.setState({
74
+ currentValue: displayValue,
75
+ previousValue: displayValue,
76
+ });
77
+ }
78
+
79
+ handleDragStart: MouseEventHandler<HTMLDivElement> = (event) => {
80
+ const { value, disabled } = this.props;
81
+ const { editing } = this.state;
82
+ if (disabled || editing) {
83
+ return;
84
+ }
85
+ document.body.style['pointer-events'] = 'none';
86
+
87
+ const parsedValue = parseFloat(value.toString());
88
+ this.setState({
89
+ dragging: false,
90
+ origin: event.screenY,
91
+ currentValue: parsedValue,
92
+ previousValue: parsedValue,
93
+ });
94
+
95
+ this.dragTimeout = setTimeout(() => {
96
+ this.setState({
97
+ dragging: true,
98
+ });
99
+ }, 250);
100
+ this.dragInterval = setInterval(() => {
101
+ const { dragging, currentValue, previousValue } = this.state;
102
+ const { onDrag } = this.props;
103
+ if (dragging && currentValue !== previousValue) {
104
+ this.setState({
105
+ previousValue: currentValue,
106
+ });
107
+ onDrag?.(currentValue);
108
+ }
109
+ }, 400);
110
+
111
+ document.addEventListener('mousemove', this.handleDragMove);
112
+ document.addEventListener('mouseup', this.handleDragEnd);
113
+ };
114
+
115
+ handleDragMove = (event: MouseEvent) => {
116
+ const { minValue, maxValue, step, stepPixelSize, disabled } = this.props;
117
+ if (disabled) {
118
+ return;
119
+ }
120
+
121
+ this.setState((prevState) => {
122
+ const state = { ...prevState };
123
+
124
+ const offset = state.origin - event.screenY;
125
+ if (prevState.dragging) {
126
+ const stepOffset = isFinite(minValue) ? minValue % step : 0;
127
+ // Translate mouse movement to value
128
+ // Give it some headroom (by increasing clamp range by 1 step)
129
+ state.currentValue = clamp(
130
+ state.currentValue + (offset * step) / (stepPixelSize || 1),
131
+ minValue - step,
132
+ maxValue + step
133
+ );
134
+ // Clamp the final value
135
+ state.currentValue = clamp(
136
+ state.currentValue - (state.currentValue % step) + stepOffset,
137
+ minValue,
138
+ maxValue
139
+ );
140
+ // Set the new origin
141
+ state.origin = event.screenY;
142
+ } else if (Math.abs(offset) > 4) {
143
+ state.dragging = true;
144
+ }
145
+ return state;
146
+ });
147
+ };
148
+
149
+ handleDragEnd = (event: MouseEvent) => {
150
+ const { dragging, currentValue } = this.state;
151
+ const { onDrag, onChange, disabled } = this.props;
152
+ if (disabled) {
153
+ return;
154
+ }
155
+ document.body.style['pointer-events'] = 'auto';
156
+
157
+ clearInterval(this.dragInterval);
158
+ clearTimeout(this.dragTimeout);
159
+
160
+ this.setState({
161
+ dragging: false,
162
+ editing: !dragging,
163
+ previousValue: currentValue,
164
+ });
165
+ if (dragging) {
166
+ onChange?.(currentValue);
167
+ onDrag?.(currentValue);
168
+ } else if (this.inputRef) {
169
+ const input = this.inputRef.current;
170
+ if (input) {
171
+ input.value = `${currentValue}`;
172
+ setTimeout(() => {
173
+ input.focus();
174
+ input.select();
175
+ }, 1);
176
+ }
177
+ }
178
+
179
+ document.removeEventListener('mousemove', this.handleDragMove);
180
+ document.removeEventListener('mouseup', this.handleDragEnd);
181
+ };
182
+
183
+ handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {
184
+ const { editing, previousValue } = this.state;
185
+ const { minValue, maxValue, onChange, onDrag, disabled } = this.props;
186
+ if (disabled || !editing) {
187
+ return;
188
+ }
189
+
190
+ const targetValue = clamp(
191
+ parseFloat(event.target.value),
192
+ minValue,
193
+ maxValue
194
+ );
195
+ if (isNaN(targetValue)) {
196
+ this.setState({
197
+ editing: false,
198
+ });
199
+ return;
200
+ }
201
+
202
+ this.setState({
203
+ editing: false,
204
+ currentValue: targetValue,
205
+ previousValue: targetValue,
206
+ });
207
+ if (previousValue !== targetValue) {
208
+ onChange?.(targetValue);
209
+ onDrag?.(targetValue);
210
+ }
211
+ };
212
+
213
+ handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
214
+ const { minValue, maxValue, onChange, onDrag, disabled } = this.props;
215
+ if (disabled) {
216
+ return;
217
+ }
218
+ const { previousValue } = this.state;
219
+
220
+ if (event.key === KEY.Enter) {
221
+ const targetValue = clamp(
222
+ parseFloat(event.currentTarget.value),
223
+ minValue,
224
+ maxValue
225
+ );
226
+ if (isNaN(targetValue)) {
227
+ this.setState({
228
+ editing: false,
229
+ });
230
+ return;
231
+ }
232
+
233
+ this.setState({
234
+ editing: false,
235
+ currentValue: targetValue,
236
+ previousValue: targetValue,
237
+ });
238
+ if (previousValue !== targetValue) {
239
+ onChange?.(targetValue);
240
+ onDrag?.(targetValue);
241
+ }
242
+ } else if (event.key === KEY.Escape) {
243
+ this.setState({
244
+ editing: false,
245
+ });
246
+ }
247
+ };
248
+
249
+ render() {
250
+ const { dragging, editing, currentValue } = this.state;
251
+
252
+ const {
253
+ className,
254
+ fluid,
255
+ animated,
256
+ unit,
257
+ value,
258
+ minValue,
259
+ maxValue,
260
+ height,
261
+ width,
262
+ lineHeight,
263
+ fontSize,
264
+ format,
265
+ } = this.props;
266
+
267
+ let displayValue = parseFloat(value.toString());
268
+ if (dragging) {
269
+ displayValue = currentValue;
270
+ }
271
+
272
+ const contentElement = (
273
+ <div className="NumberInput__content">
274
+ {animated && !dragging ? (
275
+ <AnimatedNumber value={displayValue} format={format} />
276
+ ) : format ? (
277
+ format(displayValue)
278
+ ) : (
279
+ displayValue
280
+ )}
281
+
282
+ {unit ? ' ' + unit : ''}
283
+ </div>
284
+ );
285
+
286
+ return (
287
+ <Box
288
+ className={classes([
289
+ 'NumberInput',
290
+ fluid && 'NumberInput--fluid',
291
+ className,
292
+ ])}
293
+ minWidth={width}
294
+ minHeight={height}
295
+ lineHeight={lineHeight}
296
+ fontSize={fontSize}
297
+ onMouseDown={this.handleDragStart}
298
+ >
299
+ <div className="NumberInput__barContainer">
300
+ <div
301
+ className="NumberInput__bar"
302
+ style={{
303
+ height:
304
+ clamp(
305
+ ((displayValue - minValue) / (maxValue - minValue)) * 100,
306
+ 0,
307
+ 100
308
+ ) + '%',
309
+ }}
310
+ />
311
+ </div>
312
+ {contentElement}
313
+ <input
314
+ ref={this.inputRef}
315
+ className="NumberInput__input"
316
+ style={{
317
+ display: !editing ? 'none' : 'inline',
318
+ height: height,
319
+ lineHeight: lineHeight,
320
+ fontSize: fontSize,
321
+ }}
322
+ onBlur={this.handleBlur}
323
+ onKeyDown={this.handleKeyDown}
324
+ />
325
+ </Box>
326
+ );
327
+ }
328
+ }