@carbon/react 1.90.0 → 1.91.0-rc.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 (118) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +921 -921
  2. package/es/components/CodeSnippet/CodeSnippet.d.ts +1 -1
  3. package/es/components/CodeSnippet/CodeSnippet.js +1 -1
  4. package/es/components/ComboBox/ComboBox.js +1 -12
  5. package/es/components/ComboButton/index.js +1 -1
  6. package/es/components/ComposedModal/ComposedModal.js +1 -1
  7. package/es/components/Copy/Copy.d.ts +1 -1
  8. package/es/components/Copy/Copy.js +1 -1
  9. package/es/components/CopyButton/CopyButton.d.ts +1 -1
  10. package/es/components/CopyButton/CopyButton.js +1 -1
  11. package/es/components/DataTable/DataTable.d.ts +60 -15
  12. package/es/components/DataTable/DataTable.js +106 -179
  13. package/es/components/DataTable/Table.d.ts +2 -2
  14. package/es/components/DataTable/Table.js +1 -1
  15. package/es/components/DataTable/TableExpandHeader.d.ts +1 -1
  16. package/es/components/DataTable/TableExpandHeader.js +1 -1
  17. package/es/components/DatePicker/DatePicker.d.ts +0 -12
  18. package/es/components/DatePicker/DatePicker.js +3 -3
  19. package/es/components/DatePicker/plugins/rangePlugin.d.ts +19 -2
  20. package/es/components/DatePicker/plugins/rangePlugin.js +18 -14
  21. package/es/components/Dropdown/Dropdown.js +1 -12
  22. package/es/components/FeatureFlags/index.js +1 -0
  23. package/es/components/IconButton/index.js +1 -1
  24. package/es/components/Menu/MenuItem.d.ts +1 -1
  25. package/es/components/Menu/MenuItem.js +5 -5
  26. package/es/components/Modal/Modal.js +1 -1
  27. package/es/components/MultiSelect/FilterableMultiSelect.js +1 -1
  28. package/es/components/MultiSelect/MultiSelect.js +1 -12
  29. package/es/components/Notification/Notification.d.ts +6 -6
  30. package/es/components/Notification/Notification.js +6 -6
  31. package/es/components/OverflowMenu/OverflowMenu.js +1 -1
  32. package/es/components/OverflowMenu/next/index.js +1 -1
  33. package/es/components/Popover/index.js +1 -1
  34. package/es/components/Search/Search.d.ts +4 -2
  35. package/es/components/Search/Search.js +5 -4
  36. package/es/components/Slider/Slider.d.ts +144 -188
  37. package/es/components/Slider/Slider.js +787 -710
  38. package/es/components/Slider/index.d.ts +2 -2
  39. package/es/components/Tabs/Tabs.d.ts +4 -0
  40. package/es/components/TextArea/TextArea.js +13 -6
  41. package/es/components/TextInput/ControlledPasswordInput.js +2 -2
  42. package/es/components/TextInput/PasswordInput.js +2 -2
  43. package/es/components/TextInput/TextInput.js +2 -2
  44. package/es/components/TextInput/util.d.ts +17 -5
  45. package/es/components/TextInput/util.js +2 -7
  46. package/es/components/UIShell/HeaderPanel.d.ts +1 -1
  47. package/es/index.d.ts +12 -13
  48. package/es/index.js +25 -24
  49. package/es/internal/defaultItemToString.d.ts +7 -0
  50. package/es/internal/defaultItemToString.js +17 -0
  51. package/es/internal/index.d.ts +1 -0
  52. package/es/prop-types/deprecateValuesWithin.d.ts +8 -1
  53. package/es/prop-types/deprecateValuesWithin.js +6 -6
  54. package/es/prop-types/requiredIfGivenPropIsTruthy.d.ts +8 -7
  55. package/es/prop-types/requiredIfGivenPropIsTruthy.js +10 -10
  56. package/lib/components/CodeSnippet/CodeSnippet.d.ts +1 -1
  57. package/lib/components/CodeSnippet/CodeSnippet.js +1 -1
  58. package/lib/components/ComboBox/ComboBox.js +3 -14
  59. package/lib/components/ComboButton/index.js +1 -1
  60. package/lib/components/ComposedModal/ComposedModal.js +1 -1
  61. package/lib/components/Copy/Copy.d.ts +1 -1
  62. package/lib/components/Copy/Copy.js +1 -1
  63. package/lib/components/CopyButton/CopyButton.d.ts +1 -1
  64. package/lib/components/CopyButton/CopyButton.js +1 -1
  65. package/lib/components/DataTable/DataTable.d.ts +60 -15
  66. package/lib/components/DataTable/DataTable.js +106 -179
  67. package/lib/components/DataTable/Table.d.ts +2 -2
  68. package/lib/components/DataTable/Table.js +1 -1
  69. package/lib/components/DataTable/TableExpandHeader.d.ts +1 -1
  70. package/lib/components/DataTable/TableExpandHeader.js +3 -3
  71. package/lib/components/DatePicker/DatePicker.d.ts +0 -12
  72. package/lib/components/DatePicker/DatePicker.js +2 -2
  73. package/lib/components/DatePicker/plugins/rangePlugin.d.ts +19 -2
  74. package/lib/components/DatePicker/plugins/rangePlugin.js +18 -16
  75. package/lib/components/Dropdown/Dropdown.js +3 -14
  76. package/lib/components/FeatureFlags/index.js +1 -0
  77. package/lib/components/IconButton/index.js +1 -1
  78. package/lib/components/Menu/MenuItem.d.ts +1 -1
  79. package/lib/components/Menu/MenuItem.js +6 -6
  80. package/lib/components/Modal/Modal.js +1 -1
  81. package/lib/components/MultiSelect/FilterableMultiSelect.js +8 -8
  82. package/lib/components/MultiSelect/MultiSelect.js +2 -13
  83. package/lib/components/Notification/Notification.d.ts +6 -6
  84. package/lib/components/Notification/Notification.js +6 -6
  85. package/lib/components/OverflowMenu/OverflowMenu.js +1 -1
  86. package/lib/components/OverflowMenu/next/index.js +1 -1
  87. package/lib/components/Popover/index.js +1 -1
  88. package/lib/components/Search/Search.d.ts +4 -2
  89. package/lib/components/Search/Search.js +5 -4
  90. package/lib/components/Slider/Slider.d.ts +144 -188
  91. package/lib/components/Slider/Slider.js +784 -709
  92. package/lib/components/Slider/index.d.ts +2 -2
  93. package/lib/components/Tabs/Tabs.d.ts +4 -0
  94. package/lib/components/TextArea/TextArea.js +13 -6
  95. package/lib/components/TextInput/ControlledPasswordInput.js +1 -1
  96. package/lib/components/TextInput/PasswordInput.js +1 -1
  97. package/lib/components/TextInput/TextInput.js +1 -1
  98. package/lib/components/TextInput/util.d.ts +17 -5
  99. package/lib/components/TextInput/util.js +2 -7
  100. package/lib/components/UIShell/HeaderPanel.d.ts +1 -1
  101. package/lib/index.d.ts +12 -13
  102. package/lib/index.js +51 -28
  103. package/lib/internal/defaultItemToString.d.ts +7 -0
  104. package/lib/internal/defaultItemToString.js +19 -0
  105. package/lib/internal/index.d.ts +1 -0
  106. package/lib/prop-types/deprecateValuesWithin.d.ts +8 -1
  107. package/lib/prop-types/deprecateValuesWithin.js +6 -8
  108. package/lib/prop-types/requiredIfGivenPropIsTruthy.d.ts +8 -7
  109. package/lib/prop-types/requiredIfGivenPropIsTruthy.js +10 -12
  110. package/package.json +8 -7
  111. package/es/components/MultiSelect/tools/itemToString.d.ts +0 -1
  112. package/es/components/MultiSelect/tools/itemToString.js +0 -21
  113. package/es/components/Slider/index.js +0 -14
  114. package/es/internal/createClassWrapper.js +0 -23
  115. package/lib/components/MultiSelect/tools/itemToString.d.ts +0 -1
  116. package/lib/components/MultiSelect/tools/itemToString.js +0 -23
  117. package/lib/components/Slider/index.js +0 -20
  118. package/lib/internal/createClassWrapper.js +0 -25
@@ -7,8 +7,6 @@
7
7
 
8
8
  'use strict';
9
9
 
10
- Object.defineProperty(exports, '__esModule', { value: true });
11
-
12
10
  var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
13
11
  var React = require('react');
14
12
  var PropTypes = require('prop-types');
@@ -17,7 +15,6 @@ var keys = require('../../internal/keyboard/keys.js');
17
15
  var match = require('../../internal/keyboard/match.js');
18
16
  var usePrefix = require('../../internal/usePrefix.js');
19
17
  var deprecate = require('../../prop-types/deprecate.js');
20
- var index = require('../FeatureFlags/index.js');
21
18
  var iconsReact = require('@carbon/icons-react');
22
19
  require('../Text/index.js');
23
20
  require('../Tooltip/DefinitionTooltip.js');
@@ -80,752 +77,834 @@ var HandlePosition = /*#__PURE__*/function (HandlePosition) {
80
77
  HandlePosition["LOWER"] = "lower";
81
78
  HandlePosition["UPPER"] = "upper";
82
79
  return HandlePosition;
83
- }(HandlePosition || {});
84
- class Slider extends React.PureComponent {
85
- constructor(props) {
86
- super(props);
87
- _rollupPluginBabelHelpers.defineProperty(this, "state", {
88
- value: this.props.value,
89
- valueUpper: this.props.unstable_valueUpper,
90
- left: 0,
91
- leftUpper: 0,
92
- needsOnRelease: false,
93
- isValid: true,
94
- isValidUpper: true,
95
- activeHandle: undefined,
96
- correctedValue: null,
97
- correctedPosition: null,
98
- isRtl: false
99
- });
100
- _rollupPluginBabelHelpers.defineProperty(this, "thumbRef", void 0);
101
- _rollupPluginBabelHelpers.defineProperty(this, "thumbRefUpper", void 0);
102
- _rollupPluginBabelHelpers.defineProperty(this, "filledTrackRef", void 0);
103
- _rollupPluginBabelHelpers.defineProperty(this, "element", null);
104
- _rollupPluginBabelHelpers.defineProperty(this, "inputId", '');
105
- _rollupPluginBabelHelpers.defineProperty(this, "track", void 0);
106
- _rollupPluginBabelHelpers.defineProperty(this, "handleDrag", event => {
107
- if (event instanceof globalThis.MouseEvent || event instanceof globalThis.TouchEvent) {
108
- this.onDrag(event);
109
- }
110
- });
111
- /**
112
- * Sets up "drag" event handlers and calls `this.onDrag` in case dragging
113
- * started on somewhere other than the thumb without a corresponding "move"
114
- * event.
115
- */
116
- _rollupPluginBabelHelpers.defineProperty(this, "onDragStart", evt => {
117
- // Do nothing if component is disabled
118
- if (this.props.disabled || this.props.readOnly) {
119
- return;
120
- }
121
-
122
- // We're going to force focus on one of the handles later on here, b/c we're
123
- // firing on a mousedown event, we need to call event.preventDefault() to
124
- // keep the focus from leaving the HTMLElement.
125
- // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#notes
126
- evt.preventDefault();
127
-
128
- // Add drag stop handlers
129
- DRAG_STOP_EVENT_TYPES.forEach(element => {
130
- this.element?.ownerDocument.addEventListener(element, this.onDragStop);
131
- });
132
-
133
- // Add drag handlers
134
- DRAG_EVENT_TYPES.forEach(element => {
135
- this.element?.ownerDocument.addEventListener(element, this.handleDrag);
136
- });
137
- const clientX = this.getClientXFromEvent(evt.nativeEvent);
138
- let activeHandle;
139
- if (this.hasTwoHandles()) {
140
- if (evt.target == this.thumbRef.current) {
141
- activeHandle = HandlePosition.LOWER;
142
- } else if (evt.target == this.thumbRefUpper.current) {
143
- activeHandle = HandlePosition.UPPER;
144
- } else if (clientX) {
145
- const distanceToLower = this.calcDistanceToHandle(HandlePosition.LOWER, clientX);
146
- const distanceToUpper = this.calcDistanceToHandle(HandlePosition.UPPER, clientX);
147
- if (distanceToLower <= distanceToUpper) {
148
- activeHandle = HandlePosition.LOWER;
149
- } else {
150
- activeHandle = HandlePosition.UPPER;
151
- }
152
- }
153
- }
80
+ }(HandlePosition || {}); // TODO: Delete this type and directory type the properties in the function.
81
+ const Slider = props => {
82
+ // TODO: Move destructured `props` from the IIFE to here.
154
83
 
155
- // Force focus to the appropriate handle.
156
- const focusOptions = {
157
- preventScroll: true
158
- };
159
- if (this.hasTwoHandles()) {
160
- if (this.thumbRef.current && activeHandle === HandlePosition.LOWER) {
161
- this.thumbRef.current.focus(focusOptions);
162
- } else if (this.thumbRefUpper.current && activeHandle === HandlePosition.UPPER) {
163
- this.thumbRefUpper.current.focus(focusOptions);
164
- }
165
- } else if (this.thumbRef.current) {
166
- this.thumbRef.current.focus(focusOptions);
167
- }
168
- this.setState({
169
- activeHandle
170
- });
84
+ const initialState = {
85
+ value: props.value,
86
+ valueUpper: props.unstable_valueUpper,
87
+ left: 0,
88
+ leftUpper: 0,
89
+ needsOnRelease: false,
90
+ isValid: true,
91
+ isValidUpper: true,
92
+ activeHandle: undefined,
93
+ correctedValue: null,
94
+ correctedPosition: null,
95
+ isRtl: false
96
+ };
171
97
 
172
- // Perform first recalculation since we probably didn't click exactly in the
173
- // middle of the thumb.
174
- this.onDrag(evt.nativeEvent, activeHandle);
175
- });
176
- /**
177
- * Removes "drag" and "drag stop" event handlers and calls sets the flag
178
- * indicating that the `onRelease` callback should be called.
179
- */
180
- _rollupPluginBabelHelpers.defineProperty(this, "onDragStop", () => {
181
- // Do nothing if component is disabled
182
- if (this.props.disabled || this.props.readOnly) {
183
- return;
184
- }
98
+ // TODO: Investigate using generics on the hook.
99
+ const [state, setState] = React.useReducer((prev, args) => ({
100
+ ...prev,
101
+ ...args
102
+ }), initialState);
185
103
 
186
- // Remove drag stop handlers
187
- DRAG_STOP_EVENT_TYPES.forEach(element => {
188
- this.element?.ownerDocument.removeEventListener(element, this.onDragStop);
189
- });
104
+ // TODO: Investigate getting rid of these references.
105
+ const stateRef = React.useRef(state);
106
+ React.useEffect(() => {
107
+ stateRef.current = state;
108
+ }, [state]);
109
+ const propsRef = React.useRef(props);
110
+ React.useEffect(() => {
111
+ propsRef.current = props;
112
+ }, [props]);
113
+ const thumbRef = React.useRef(null);
114
+ const thumbRefUpper = React.useRef(null);
115
+ const filledTrackRef = React.useRef(null);
116
+ const elementRef = React.useRef(null);
117
+ const trackRef = React.useRef(null);
118
+ const inputIdRef = React.useRef('');
190
119
 
191
- // Remove drag handlers
192
- DRAG_EVENT_TYPES.forEach(element => {
193
- this.element?.ownerDocument.removeEventListener(element, this.handleDrag);
194
- });
120
+ // TODO: Delete this function and set its return value as the value of
121
+ // `twoHandles`.
122
+ const hasTwoHandles = () => {
123
+ return typeof state.valueUpper !== 'undefined';
124
+ };
125
+ const twoHandles = hasTwoHandles();
195
126
 
196
- // Set needsOnRelease flag so event fires on next update.
197
- this.setState({
198
- needsOnRelease: true,
199
- isValid: true,
200
- isValidUpper: true
201
- });
202
- });
203
- /**
204
- * Handles a "drag" event by recalculating the value/thumb and setting state
205
- * accordingly.
206
- *
207
- * @param evt The event.
208
- * @param activeHandle The first drag event call, we may have an explicit
209
- * activeHandle value, which is to be used before state is used.
210
- */
211
- _rollupPluginBabelHelpers.defineProperty(this, "_onDrag", (evt, activeHandle) => {
212
- activeHandle = activeHandle ?? this.state.activeHandle;
213
- // Do nothing if component is disabled, or we have no event.
214
- if (this.props.disabled || this.props.readOnly || !evt) {
215
- return;
216
- }
217
- const clientX = this.getClientXFromEvent(evt);
218
- const {
219
- value,
220
- left
221
- } = this.calcValue({
222
- clientX,
223
- value: this.state.value
224
- });
225
- // If we're set to two handles, negotiate which drag handle is closest to
226
- // the users' interaction.
227
- if (this.hasTwoHandles() && activeHandle) {
228
- this.setValueLeftForHandle(activeHandle, {
229
- value: this.nearestStepValue(value),
127
+ /**
128
+ * Sets up initial slider position and value in response to component mount.
129
+ */
130
+ React.useEffect(() => {
131
+ if (elementRef.current) {
132
+ const isRtl = document?.dir === 'rtl';
133
+ if (hasTwoHandles()) {
134
+ const {
135
+ value,
230
136
  left
137
+ } = calcValue({
138
+ value: stateRef.current.value,
139
+ useRawValue: true
231
140
  });
232
- } else {
233
- this.setState({
234
- value: this.nearestStepValue(value),
141
+ const {
142
+ value: valueUpper,
143
+ left: leftUpper
144
+ } = calcValue({
145
+ value: stateRef.current.valueUpper,
146
+ useRawValue: true
147
+ });
148
+ setState({
149
+ isRtl,
150
+ value,
235
151
  left,
236
- isValid: true
152
+ valueUpper,
153
+ leftUpper
237
154
  });
238
- }
239
- this.setState({
240
- correctedValue: null,
241
- correctedPosition: null
242
- });
243
- });
244
- /**
245
- * Throttles calls to `this._onDrag` by limiting events to being processed at
246
- * most once every `EVENT_THROTTLE` milliseconds.
247
- */
248
- _rollupPluginBabelHelpers.defineProperty(this, "onDrag", throttle.throttle(this._onDrag, EVENT_THROTTLE, {
249
- leading: true,
250
- trailing: false
251
- }));
252
- /**
253
- * Handles a `keydown` event by recalculating the value/thumb and setting
254
- * state accordingly.
255
- */
256
- _rollupPluginBabelHelpers.defineProperty(this, "onKeyDown", evt => {
257
- // Do nothing if component is disabled, or we don't have a valid event
258
- if (this.props.disabled || this.props.readOnly) {
259
- return;
260
- }
261
- const {
262
- step = 1,
263
- stepMultiplier = 4
264
- } = this.props;
265
- let delta = 0;
266
- if (match.matches(evt, [keys.ArrowDown, keys.ArrowLeft])) {
267
- delta = -step;
268
- } else if (match.matches(evt, [keys.ArrowUp, keys.ArrowRight])) {
269
- delta = step;
270
155
  } else {
271
- // Ignore keys we don't want to handle
272
- return;
273
- }
274
-
275
- // If shift was held, account for the stepMultiplier
276
- if (evt.shiftKey) {
277
- delta *= stepMultiplier;
278
- }
279
- if (this.hasTwoHandles() && this.state.activeHandle) {
280
- const currentValue = this.state.activeHandle === HandlePosition.LOWER ? this.state.value : this.state.valueUpper;
281
156
  const {
282
157
  value,
283
158
  left
284
- } = this.calcValue({
285
- value: this.calcValueForDelta(currentValue ?? this.props.min, delta, this.props.step)
286
- });
287
- this.setValueLeftForHandle(this.state.activeHandle, {
288
- value: this.nearestStepValue(value),
289
- left
159
+ } = calcValue({
160
+ value: stateRef.current.value,
161
+ useRawValue: true
290
162
  });
291
- } else {
292
- const {
163
+ setState({
164
+ isRtl,
293
165
  value,
294
166
  left
295
- } = this.calcValue({
296
- // Ensures custom value from `<input>` won't cause skipping next stepping
297
- // point with right arrow key, e.g. Typing 51 in `<input>`, moving focus
298
- // onto the thumb and the hitting right arrow key should yield 52 instead
299
- // of 54.
300
- value: this.calcValueForDelta(this.state.value, delta, this.props.step)
301
- });
302
- this.setState({
303
- value: this.nearestStepValue(value),
304
- left,
305
- isValid: true
306
167
  });
307
168
  }
308
- this.setState({
309
- correctedValue: null,
310
- correctedPosition: null
311
- });
312
- });
313
- /**
314
- * Provides the two-way binding for the input field of the Slider. It also
315
- * Handles a change to the input field by recalculating the value/thumb and
316
- * setting state accordingly.
317
- */
318
- _rollupPluginBabelHelpers.defineProperty(this, "onChange", evt => {
319
- // Do nothing if component is disabled
320
- if (this.props.disabled || this.props.readOnly) {
321
- return;
322
- }
323
-
324
- // Do nothing if we have no valid event, target, or value
325
- if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') {
326
- return;
327
- }
169
+ }
170
+ return () => {
171
+ DRAG_STOP_EVENT_TYPES.forEach(element => elementRef.current?.ownerDocument.removeEventListener(element, onDragStop));
172
+ DRAG_EVENT_TYPES.forEach(element => elementRef.current?.ownerDocument.removeEventListener(element, handleDrag));
173
+ };
174
+ // eslint-disable-next-line react-hooks/exhaustive-deps
175
+ }, []);
176
+ React.useEffect(() => {
177
+ // TODO: Uncomment this code and delete all of the `filledTrackRef.current`
178
+ // checks.
179
+ // const el = filledTrackRef.current;
180
+ //
181
+ // if (!el) return;
328
182
 
329
- // Avoid calling calcValue for invalid numbers, but still update the state.
330
- const activeHandle = evt.target.dataset.handlePosition ?? HandlePosition.LOWER;
331
- const targetValue = Number.parseFloat(evt.target.value);
332
- if (this.hasTwoHandles()) {
333
- if (isNaN(targetValue)) {
334
- this.setValueForHandle(activeHandle, evt.target.value);
335
- } else if (this.isValidValueForPosition({
336
- handle: activeHandle,
337
- value: targetValue,
338
- min: this.props.min,
339
- max: this.props.max
340
- })) {
341
- this.processNewInputValue(evt.target);
342
- } else {
343
- this.setValueForHandle(activeHandle, targetValue);
344
- }
345
- } else {
346
- if (isNaN(targetValue)) {
347
- this.setState({
348
- value: evt.target.value
349
- });
350
- } else if (this.isValidValue({
351
- value: targetValue,
352
- min: this.props.min,
353
- max: this.props.max
354
- })) {
355
- this.processNewInputValue(evt.target);
356
- } else {
357
- this.setState({
358
- value: targetValue
359
- });
360
- }
361
- }
362
- });
363
- /**
364
- * Checks for validity of input value after clicking out of the input. It also
365
- * Handles state change to isValid state.
366
- */
367
- _rollupPluginBabelHelpers.defineProperty(this, "onBlur", evt => {
368
- // Do nothing if we have no valid event, target, or value
369
- if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') {
370
- return;
183
+ // Fire onChange event handler if present, if there's a usable value, and
184
+ // if the value is different from the last one
185
+ if (hasTwoHandles()) {
186
+ if (filledTrackRef.current) {
187
+ filledTrackRef.current.style.transform = state.isRtl ? `translate(${100 - state.leftUpper}%, -50%) scaleX(${(state.leftUpper - state.left) / 100})` : `translate(${state.left}%, -50%) scaleX(${(state.leftUpper - state.left) / 100})`;
371
188
  }
372
- const {
373
- value: targetValue
374
- } = evt.target;
375
- this.processNewInputValue(evt.target);
376
- this.props.onBlur?.({
377
- value: targetValue,
378
- handlePosition: evt.target.dataset.handlePosition
379
- });
380
- });
381
- _rollupPluginBabelHelpers.defineProperty(this, "onInputKeyDown", evt => {
382
- // Do nothing if component is disabled, or we don't have a valid event.
383
- if (this.props.disabled || this.props.readOnly || !(evt.target instanceof HTMLInputElement)) {
384
- return;
189
+ } else {
190
+ if (filledTrackRef.current) {
191
+ filledTrackRef.current.style.transform = state.isRtl ? `translate(100%, -50%) scaleX(-${state.left / 100})` : `translate(0%, -50%) scaleX(${state.left / 100})`;
385
192
  }
193
+ }
194
+ // TODO: Investigate whether the missing dependency should be added.
195
+ //
196
+ // eslint-disable-next-line react-hooks/exhaustive-deps
197
+ }, [state.left, state.leftUpper, state.isRtl]);
386
198
 
387
- // Do nothing if we have no valid event, target, or value.
388
- if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') {
389
- return;
390
- }
391
- if (match.matches(evt, [keys.Enter])) {
392
- this.processNewInputValue(evt.target);
393
- }
394
- });
395
- _rollupPluginBabelHelpers.defineProperty(this, "processNewInputValue", input => {
396
- this.setState({
397
- correctedValue: null,
398
- correctedPosition: null
199
+ // Fire onChange when value(s) change
200
+ const prevValsRef = React.useRef(null);
201
+ React.useEffect(() => {
202
+ const prev = prevValsRef.current;
203
+ if (prev && (prev.value !== state.value || prev.valueUpper !== state.valueUpper) && typeof props.onChange === 'function') {
204
+ props.onChange({
205
+ value: state.value,
206
+ valueUpper: state.valueUpper
399
207
  });
400
- const targetValue = Number.parseFloat(input.value);
401
- const validity = !isNaN(targetValue);
402
-
403
- // When there are two handles, we'll also have the data-handle-position
404
- // attribute to consider the other value before settling on the validity to
405
- // set.
406
- const handlePosition = input.dataset.handlePosition;
407
- if (handlePosition === HandlePosition.LOWER) {
408
- this.setState({
409
- isValid: validity
410
- });
411
- } else if (handlePosition === HandlePosition.UPPER) {
412
- this.setState({
413
- isValidUpper: validity
414
- });
415
- }
416
- this.setState({
417
- isValid: validity
208
+ }
209
+ prevValsRef.current = {
210
+ value: state.value,
211
+ valueUpper: state.valueUpper
212
+ };
213
+ // TODO: Investigate whether the missing dependency should be added.
214
+ //
215
+ // eslint-disable-next-line react-hooks/exhaustive-deps
216
+ }, [state.value, state.valueUpper, props.onChange]);
217
+ React.useEffect(() => {
218
+ // Fire onRelease event handler if present and if needed
219
+ if (state.needsOnRelease && typeof props.onRelease === 'function') {
220
+ props.onRelease({
221
+ value: state.value,
222
+ valueUpper: state.valueUpper
418
223
  });
419
- if (validity) {
420
- const adjustedValue = handlePosition ? this.getAdjustedValueForPosition({
421
- handle: handlePosition,
422
- value: targetValue,
423
- min: this.props.min,
424
- max: this.props.max
425
- }) : this.getAdjustedValue({
426
- value: targetValue,
427
- min: this.props.min,
428
- max: this.props.max
429
- });
430
- if (adjustedValue !== targetValue) {
431
- this.setState({
432
- correctedValue: targetValue.toString(),
433
- correctedPosition: handlePosition
434
- });
435
- } else {
436
- this.setState({
437
- correctedValue: null,
438
- correctedPosition: null
439
- });
440
- }
224
+ // Reset the flag
225
+ setState({
226
+ needsOnRelease: false
227
+ });
228
+ }
229
+ // TODO: Investigate whether the missing dependency should be added.
230
+ //
231
+ // eslint-disable-next-line react-hooks/exhaustive-deps
232
+ }, [state.needsOnRelease, state.value, state.valueUpper, props.onRelease]);
233
+ const prevSyncKeysRef = React.useRef(null);
234
+ React.useEffect(() => {
235
+ const prev = prevSyncKeysRef.current;
236
+ const next = [props.value, props.unstable_valueUpper, props.max, props.min];
237
+
238
+ // If value from props does not change, do nothing here.
239
+ // Otherwise, do prop -> state sync without "value capping".
240
+ if (!prev || prev[0] !== next[0] || prev[1] !== next[1] || prev[2] !== next[2] || prev[3] !== next[3]) {
241
+ setState(calcValue({
242
+ value: props.value,
243
+ useRawValue: true
244
+ }));
245
+ if (typeof props.unstable_valueUpper !== 'undefined') {
441
246
  const {
442
- value,
443
- left
444
- } = this.calcValue({
445
- value: adjustedValue,
247
+ value: valueUpper,
248
+ left: leftUpper
249
+ } = calcValue({
250
+ value: props.unstable_valueUpper,
446
251
  useRawValue: true
447
252
  });
448
- if (handlePosition) {
449
- this.setValueLeftForHandle(handlePosition, {
450
- value: this.nearestStepValue(value),
451
- left
452
- });
453
- } else {
454
- this.setState({
455
- value,
456
- left
457
- });
458
- }
253
+ setState({
254
+ valueUpper,
255
+ leftUpper
256
+ });
257
+ } else {
258
+ setState({
259
+ valueUpper: undefined,
260
+ leftUpper: undefined
261
+ });
459
262
  }
263
+ prevSyncKeysRef.current = next;
264
+ }
265
+ // TODO: Investigate whether the missing dependency should be added.
266
+ //
267
+ // eslint-disable-next-line react-hooks/exhaustive-deps
268
+ }, [props.value, props.unstable_valueUpper, props.max, props.min]);
269
+
270
+ /**
271
+ * Rounds a given value to the nearest step defined by the slider's `step`
272
+ * prop.
273
+ *
274
+ * @param value - The value to adjust to the nearest step. Defaults to `0`.
275
+ * @returns The value rounded to the precision determined by the step.
276
+ */
277
+ const nearestStepValue = (value = 0) => {
278
+ // TODO: Use a nullish coalescing operator.
279
+ const decimals = (props.step?.toString().split('.')[1] || '').length;
280
+ return Number(value.toFixed(decimals));
281
+ };
282
+ const handleDrag = event => {
283
+ if (event instanceof globalThis.MouseEvent || event instanceof globalThis.TouchEvent) {
284
+ onDrag(event);
285
+ }
286
+ };
287
+
288
+ /**
289
+ * Sets up "drag" event handlers and calls `onDrag` in case dragging
290
+ * started on somewhere other than the thumb without a corresponding "move"
291
+ * event.
292
+ */
293
+ const onDragStart = evt => {
294
+ // Do nothing if component is disabled
295
+ if (props.disabled || props.readOnly) {
296
+ return;
297
+ }
298
+
299
+ // We're going to force focus on one of the handles later on here, b/c we're
300
+ // firing on a mousedown event, we need to call event.preventDefault() to
301
+ // keep the focus from leaving the HTMLElement.
302
+ // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#notes
303
+ evt.preventDefault();
304
+
305
+ // TODO: Abstract `elementRef.current?.ownerDocument` to a variable that can
306
+ // be used here and everywhere else in this file.
307
+
308
+ // Add drag stop handlers
309
+ DRAG_STOP_EVENT_TYPES.forEach(element => {
310
+ elementRef.current?.ownerDocument.addEventListener(element, onDragStop);
460
311
  });
461
- _rollupPluginBabelHelpers.defineProperty(this, "calcLeftPercent", ({
462
- clientX,
463
- value,
464
- range
465
- }) => {
466
- const boundingRect = this.element?.getBoundingClientRect?.();
467
- let width = boundingRect ? boundingRect.right - boundingRect.left : 0;
468
312
 
469
- // Enforce a minimum width of at least 1 for calculations
470
- if (width <= 0) {
471
- width = 1;
313
+ // Add drag handlers
314
+ DRAG_EVENT_TYPES.forEach(element => {
315
+ elementRef.current?.ownerDocument.addEventListener(element, handleDrag);
316
+ });
317
+ const clientX = getClientXFromEvent(evt.nativeEvent);
318
+ let activeHandle;
319
+ if (hasTwoHandles()) {
320
+ if (evt.target == thumbRef.current) {
321
+ activeHandle = HandlePosition.LOWER;
322
+ } else if (evt.target == thumbRefUpper.current) {
323
+ activeHandle = HandlePosition.UPPER;
324
+ } else if (clientX) {
325
+ const distanceToLower = calcDistanceToHandle(HandlePosition.LOWER, clientX);
326
+ const distanceToUpper = calcDistanceToHandle(HandlePosition.UPPER, clientX);
327
+ if (distanceToLower <= distanceToUpper) {
328
+ activeHandle = HandlePosition.LOWER;
329
+ } else {
330
+ activeHandle = HandlePosition.UPPER;
331
+ }
472
332
  }
333
+ }
473
334
 
474
- // If a clientX is specified, use it to calculate the leftPercent. If not,
475
- // use the provided value to calculate it instead.
476
- if (clientX) {
477
- const leftOffset = this.state.isRtl ? (boundingRect?.right ?? 0) - clientX : clientX - (boundingRect?.left ?? 0);
478
- return leftOffset / width;
479
- } else if (value !== null && typeof value !== 'undefined' && range) {
480
- // Prevent NaN calculation if the range is 0.
481
- return range === 0 ? 0 : (value - this.props.min) / range;
335
+ // Force focus to the appropriate handle.
336
+ const focusOptions = {
337
+ preventScroll: true
338
+ };
339
+ if (hasTwoHandles()) {
340
+ if (thumbRef.current && activeHandle === HandlePosition.LOWER) {
341
+ thumbRef.current.focus(focusOptions);
342
+ } else if (thumbRefUpper.current && activeHandle === HandlePosition.UPPER) {
343
+ thumbRefUpper.current.focus(focusOptions);
482
344
  }
483
- // We should never end up in this scenario, but in case we do, and to
484
- // re-assure Typescript, return 0.
485
- return 0;
345
+ } else if (thumbRef.current) {
346
+ thumbRef.current.focus(focusOptions);
347
+ }
348
+ setState({
349
+ activeHandle
486
350
  });
487
- /**
488
- * Calculates the discrete value (snapped to the nearest step) along
489
- * with the corresponding handle position percentage.
490
- */
491
- _rollupPluginBabelHelpers.defineProperty(this, "calcDiscreteValueAndPercent", ({
492
- leftPercent
493
- }) => {
494
- const {
495
- step = 1,
496
- min,
497
- max
498
- } = this.props;
499
- const numSteps = Math.floor((max - min) / step) + ((max - min) % step === 0 ? 1 : 2);
500
- /** Index of the step that corresponds to `leftPercent`. */
501
- const stepIndex = Math.round(leftPercent * (numSteps - 1));
502
- const discreteValue = stepIndex === numSteps - 1 ? max : min + step * stepIndex;
503
- /** Percentage corresponding to the step index. */
504
- const discretePercent = stepIndex / (numSteps - 1);
505
- return {
506
- discreteValue,
507
- discretePercent
508
- };
351
+
352
+ // Perform first recalculation since we probably didn't click exactly in the
353
+ // middle of the thumb.
354
+ onDrag(evt.nativeEvent, activeHandle);
355
+ };
356
+
357
+ /**
358
+ * Removes "drag" and "drag stop" event handlers and calls sets the flag
359
+ * indicating that the `onRelease` callback should be called.
360
+ */
361
+ const onDragStop = () => {
362
+ // Do nothing if component is disabled
363
+ if (props.disabled || props.readOnly) {
364
+ return;
365
+ }
366
+
367
+ // TODO: Rename parameters in `DRAG_*` loops to `type`.
368
+ // Remove drag stop handlers
369
+ DRAG_STOP_EVENT_TYPES.forEach(element => {
370
+ elementRef.current?.ownerDocument.removeEventListener(element, onDragStop);
509
371
  });
510
- /**
511
- * Calculates the slider's value and handle position based on either a
512
- * mouse/touch event or an explicit value.
513
- */
514
- _rollupPluginBabelHelpers.defineProperty(this, "calcValue", ({
515
- clientX,
372
+
373
+ // Remove drag handlers
374
+ DRAG_EVENT_TYPES.forEach(element => {
375
+ elementRef.current?.ownerDocument.removeEventListener(element, handleDrag);
376
+ });
377
+
378
+ // Set needsOnRelease flag so event fires on next update.
379
+ setState({
380
+ needsOnRelease: true,
381
+ isValid: true,
382
+ isValidUpper: true
383
+ });
384
+ };
385
+
386
+ // TODO: Rename this reference.
387
+ /**
388
+ * Handles a "drag" event by recalculating the value/thumb and setting state
389
+ * accordingly.
390
+ *
391
+ * @param evt The event.
392
+ * @param activeHandle The first drag event call, we may have an explicit
393
+ * activeHandle value, which is to be used before state is used.
394
+ */
395
+ const _onDragRef = React.useRef(null);
396
+ _onDragRef.current = (evt, activeHandle) => {
397
+ activeHandle = activeHandle ?? stateRef.current.activeHandle;
398
+ // Do nothing if component is disabled, or we have no event.
399
+ if (propsRef.current.disabled || propsRef.current.readOnly || !evt) {
400
+ return;
401
+ }
402
+ const clientX = getClientXFromEvent(evt);
403
+ const {
516
404
  value,
517
- useRawValue
518
- }) => {
519
- const range = this.props.max - this.props.min;
520
- const leftPercentRaw = this.calcLeftPercent({
521
- clientX,
522
- value,
523
- range
405
+ left
406
+ } = calcValue({
407
+ clientX,
408
+ value: stateRef.current.value
409
+ });
410
+ // If we're set to two handles, negotiate which drag handle is closest to
411
+ // the users' interaction.
412
+ if (hasTwoHandles() && activeHandle) {
413
+ setValueLeftForHandle(activeHandle, {
414
+ value: nearestStepValue(value),
415
+ left
524
416
  });
525
- /** `leftPercentRaw` clamped between 0 and 1. */
526
- const leftPercent = clamp.clamp(leftPercentRaw, 0, 1);
527
- if (useRawValue) {
528
- return {
529
- value,
530
- left: leftPercent * 100
531
- };
532
- }
417
+ } else {
418
+ setState({
419
+ value: nearestStepValue(value),
420
+ left,
421
+ isValid: true
422
+ });
423
+ }
424
+ // TODO: Investigate if it would be better to not call `setState`
425
+ // back-to-back here and in other places.
426
+ setState({
427
+ correctedValue: null,
428
+ correctedPosition: null
429
+ });
430
+ };
431
+
432
+ /**
433
+ * Throttles calls to `_onDrag` by limiting events to being processed at
434
+ * most once every `EVENT_THROTTLE` milliseconds.
435
+ */
436
+ const onDrag = React.useMemo(() => throttle.throttle((evt, activeHandle) => {
437
+ _onDragRef.current?.(evt, activeHandle);
438
+ }, EVENT_THROTTLE, {
439
+ leading: true,
440
+ trailing: false
441
+ }), []);
533
442
 
534
- // Use the discrete value and percentage for snapping.
443
+ /**
444
+ * Handles a `keydown` event by recalculating the value/thumb and setting
445
+ * state accordingly.
446
+ */
447
+ const onKeyDown = evt => {
448
+ // Do nothing if component is disabled, or we don't have a valid event
449
+ if (props.disabled || props.readOnly) {
450
+ return;
451
+ }
452
+ const {
453
+ step = 1,
454
+ stepMultiplier = 4
455
+ } = props;
456
+ let delta = 0;
457
+ if (match.matches(evt, [keys.ArrowDown, keys.ArrowLeft])) {
458
+ delta = -step;
459
+ } else if (match.matches(evt, [keys.ArrowUp, keys.ArrowRight])) {
460
+ delta = step;
461
+ } else {
462
+ // Ignore keys we don't want to handle
463
+ return;
464
+ }
465
+
466
+ // If shift was held, account for the stepMultiplier
467
+ if (evt.shiftKey) {
468
+ delta *= stepMultiplier;
469
+ }
470
+ if (hasTwoHandles() && state.activeHandle) {
471
+ const currentValue = state.activeHandle === HandlePosition.LOWER ? state.value : state.valueUpper;
535
472
  const {
536
- discreteValue,
537
- discretePercent
538
- } = this.calcDiscreteValueAndPercent({
539
- leftPercent
473
+ value,
474
+ left
475
+ } = calcValue({
476
+ value: calcValueForDelta(currentValue ?? props.min, delta, props.step)
540
477
  });
541
- return {
542
- value: discreteValue,
543
- left: discretePercent * 100
544
- };
545
- });
546
- _rollupPluginBabelHelpers.defineProperty(this, "calcDistanceToHandle", (handle, clientX) => {
547
- const handleBoundingRect = this.getHandleBoundingRect(handle);
548
- // x co-ordinate of the midpoint.
549
- const handleX = handleBoundingRect.left + handleBoundingRect.width / 2;
550
- return Math.abs(handleX - clientX);
551
- });
552
- /**
553
- * Calculates a new slider value based on the current value, a change delta,
554
- * and a step.
555
- *
556
- * @param currentValue - The starting value from which the slider is moving.
557
- * @param delta - The amount to adjust the current value by.
558
- * @param step - The step. Defaults to `1`.
559
- * @returns The new slider value, rounded to the same number of decimal places
560
- * as the step.
561
- */
562
- _rollupPluginBabelHelpers.defineProperty(this, "calcValueForDelta", (currentValue, delta, step = 1) => {
563
- const base = delta > 0 ? Math.floor(currentValue / step) * step : currentValue;
564
- const newValue = base + delta;
565
- const decimals = (step.toString().split('.')[1] || '').length;
566
- return Number(newValue.toFixed(decimals));
567
- });
568
- /**
569
- * Sets state relevant to the given handle position.
570
- *
571
- * Guards against setting either lower or upper values beyond its counterpart.
572
- */
573
- _rollupPluginBabelHelpers.defineProperty(this, "setValueLeftForHandle", (handle, {
574
- value: newValue,
575
- left: newLeft
576
- }) => {
478
+ setValueLeftForHandle(state.activeHandle, {
479
+ value: nearestStepValue(value),
480
+ left
481
+ });
482
+ } else {
577
483
  const {
578
484
  value,
579
- valueUpper,
485
+ left
486
+ } = calcValue({
487
+ // Ensures custom value from `<input>` won't cause skipping next stepping
488
+ // point with right arrow key, e.g. Typing 51 in `<input>`, moving focus
489
+ // onto the thumb and the hitting right arrow key should yield 52 instead
490
+ // of 54.
491
+ value: calcValueForDelta(state.value, delta, props.step)
492
+ });
493
+ setState({
494
+ value: nearestStepValue(value),
580
495
  left,
581
- leftUpper
582
- } = this.state;
583
- if (handle === HandlePosition.LOWER) {
584
- // Don't allow higher than the upper handle.
585
- this.setState({
586
- value: valueUpper && newValue > valueUpper ? valueUpper : newValue,
587
- left: valueUpper && newValue > valueUpper ? leftUpper : newLeft,
588
- isValid: true
496
+ isValid: true
497
+ });
498
+ }
499
+ setState({
500
+ correctedValue: null,
501
+ correctedPosition: null
502
+ });
503
+ };
504
+
505
+ /**
506
+ * Provides the two-way binding for the input field of the Slider. It also
507
+ * Handles a change to the input field by recalculating the value/thumb and
508
+ * setting state accordingly.
509
+ */
510
+ const onChangeInput = evt => {
511
+ // Do nothing if component is disabled
512
+ if (props.disabled || props.readOnly) {
513
+ return;
514
+ }
515
+
516
+ // Do nothing if we have no valid event, target, or value
517
+ if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') {
518
+ return;
519
+ }
520
+
521
+ // Avoid calling calcValue for invalid numbers, but still update the state.
522
+ const activeHandle = evt.target.dataset.handlePosition ?? HandlePosition.LOWER;
523
+ const targetValue = Number.parseFloat(evt.target.value);
524
+ if (hasTwoHandles()) {
525
+ if (isNaN(targetValue)) {
526
+ setValueForHandle(activeHandle, evt.target.value);
527
+ } else if (isValidValueForPosition({
528
+ handle: activeHandle,
529
+ value: targetValue,
530
+ min: props.min,
531
+ max: props.max
532
+ })) {
533
+ processNewInputValue(evt.target);
534
+ } else {
535
+ setValueForHandle(activeHandle, targetValue);
536
+ }
537
+ } else {
538
+ if (isNaN(targetValue)) {
539
+ // TODO: Address this error
540
+ //
541
+ // @ts-expect-error - Passing a string to something that expects a
542
+ // number.
543
+ setState({
544
+ value: evt.target.value
589
545
  });
546
+ } else if (isValidValue({
547
+ value: targetValue,
548
+ min: props.min,
549
+ max: props.max
550
+ })) {
551
+ processNewInputValue(evt.target);
590
552
  } else {
591
- this.setState({
592
- valueUpper: value && newValue < value ? value : newValue,
593
- leftUpper: value && newValue < value ? left : newLeft,
594
- isValidUpper: true
553
+ setState({
554
+ value: targetValue
595
555
  });
596
556
  }
557
+ }
558
+ };
559
+
560
+ /**
561
+ * Checks for validity of input value after clicking out of the input. It also
562
+ * Handles state change to isValid state.
563
+ */
564
+ const onBlurInput = evt => {
565
+ // Do nothing if we have no valid event, target, or value
566
+ if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') {
567
+ return;
568
+ }
569
+ const {
570
+ value: targetValue
571
+ } = evt.target;
572
+ processNewInputValue(evt.target);
573
+ props.onBlur?.({
574
+ value: targetValue,
575
+ handlePosition: evt.target.dataset.handlePosition
597
576
  });
598
- _rollupPluginBabelHelpers.defineProperty(this, "setValueForHandle", (handle, value) => {
599
- if (handle === HandlePosition.LOWER) {
600
- this.setState({
601
- value,
602
- isValid: true
577
+ };
578
+ const onInputKeyDown = evt => {
579
+ // Do nothing if component is disabled, or we don't have a valid event.
580
+ if (props.disabled || props.readOnly || !(evt.target instanceof HTMLInputElement)) {
581
+ return;
582
+ }
583
+
584
+ // Do nothing if we have no valid event, target, or value.
585
+ if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') {
586
+ return;
587
+ }
588
+ if (match.matches(evt, [keys.Enter])) {
589
+ processNewInputValue(evt.target);
590
+ }
591
+ };
592
+ const processNewInputValue = input => {
593
+ setState({
594
+ correctedValue: null,
595
+ correctedPosition: null
596
+ });
597
+ const targetValue = Number.parseFloat(input.value);
598
+ const validity = !isNaN(targetValue);
599
+
600
+ // When there are two handles, we'll also have the data-handle-position
601
+ // attribute to consider the other value before settling on the validity to
602
+ // set.
603
+ const handlePosition = input.dataset.handlePosition;
604
+ if (handlePosition === HandlePosition.LOWER) {
605
+ setState({
606
+ isValid: validity
607
+ });
608
+ } else if (handlePosition === HandlePosition.UPPER) {
609
+ setState({
610
+ isValidUpper: validity
611
+ });
612
+ }
613
+ setState({
614
+ isValid: validity
615
+ });
616
+ if (validity) {
617
+ const adjustedValue = handlePosition ? getAdjustedValueForPosition({
618
+ handle: handlePosition,
619
+ value: targetValue,
620
+ min: props.min,
621
+ max: props.max
622
+ }) : getAdjustedValue({
623
+ value: targetValue,
624
+ min: props.min,
625
+ max: props.max
626
+ });
627
+ if (adjustedValue !== targetValue) {
628
+ setState({
629
+ correctedValue: targetValue.toString(),
630
+ correctedPosition: handlePosition ?? null
603
631
  });
604
632
  } else {
605
- this.setState({
606
- valueUpper: value,
607
- isValidUpper: true
633
+ setState({
634
+ correctedValue: null,
635
+ correctedPosition: null
608
636
  });
609
637
  }
610
- });
611
- _rollupPluginBabelHelpers.defineProperty(this, "isValidValueForPosition", ({
612
- handle,
613
- value: newValue,
614
- min,
615
- max
616
- }) => {
617
638
  const {
618
639
  value,
619
- valueUpper
620
- } = this.state;
621
- if (!this.isValidValue({
622
- value: newValue,
623
- min,
624
- max
625
- })) {
626
- return false;
627
- }
628
- if (handle === HandlePosition.LOWER) {
629
- return !valueUpper || newValue <= valueUpper;
630
- } else if (handle === HandlePosition.UPPER) {
631
- return !value || newValue >= value;
640
+ left
641
+ } = calcValue({
642
+ value: adjustedValue,
643
+ useRawValue: true
644
+ });
645
+ if (handlePosition) {
646
+ setValueLeftForHandle(handlePosition, {
647
+ value: nearestStepValue(value),
648
+ left
649
+ });
650
+ } else {
651
+ setState({
652
+ value,
653
+ left
654
+ });
632
655
  }
633
- return false;
634
- });
635
- _rollupPluginBabelHelpers.defineProperty(this, "isValidValue", ({
636
- value,
637
- min,
638
- max
639
- }) => {
640
- return !(value < min || value > max);
641
- });
642
- _rollupPluginBabelHelpers.defineProperty(this, "getAdjustedValueForPosition", ({
643
- handle,
644
- value: newValue,
656
+ }
657
+ };
658
+ const calcLeftPercent = ({
659
+ clientX,
660
+ value,
661
+ range
662
+ }) => {
663
+ // TODO: Delete the optional chaining operator after `getBoundingClientRect`.
664
+ const boundingRect = elementRef.current?.getBoundingClientRect?.();
665
+ let width = boundingRect ? boundingRect.right - boundingRect.left : 0;
666
+
667
+ // Enforce a minimum width of at least 1 for calculations
668
+ if (width <= 0) {
669
+ width = 1;
670
+ }
671
+
672
+ // If a clientX is specified, use it to calculate the leftPercent. If not,
673
+ // use the provided value to calculate it instead.
674
+ if (clientX) {
675
+ const leftOffset = state.isRtl ? (boundingRect?.right ?? 0) - clientX : clientX - (boundingRect?.left ?? 0);
676
+ return leftOffset / width;
677
+ } else if (value !== null && typeof value !== 'undefined' && range) {
678
+ // Prevent NaN calculation if the range is 0.
679
+ return range === 0 ? 0 : (value - props.min) / range;
680
+ }
681
+ // We should never end up in this scenario, but in case we do, and to
682
+ // re-assure Typescript, return 0.
683
+ return 0;
684
+ };
685
+
686
+ /**
687
+ * Calculates the discrete value (snapped to the nearest step) along
688
+ * with the corresponding handle position percentage.
689
+ */
690
+ const calcDiscreteValueAndPercent = ({
691
+ leftPercent
692
+ }) => {
693
+ const {
694
+ step = 1,
645
695
  min,
646
696
  max
647
- }) => {
648
- const {
649
- value,
650
- valueUpper
651
- } = this.state;
652
- newValue = this.getAdjustedValue({
653
- value: newValue,
654
- min,
655
- max
656
- });
697
+ } = props;
698
+ const numSteps = Math.floor((max - min) / step) + ((max - min) % step === 0 ? 1 : 2);
699
+ /** Index of the step that corresponds to `leftPercent`. */
700
+ const stepIndex = Math.round(leftPercent * (numSteps - 1));
701
+ const discreteValue = stepIndex === numSteps - 1 ? max : min + step * stepIndex;
702
+ /** Percentage corresponding to the step index. */
703
+ const discretePercent = stepIndex / (numSteps - 1);
704
+ return {
705
+ discreteValue,
706
+ discretePercent
707
+ };
708
+ };
657
709
 
658
- // Next adjust to the opposite handle.
659
- if (handle === HandlePosition.LOWER && valueUpper) {
660
- newValue = newValue > valueUpper ? valueUpper : newValue;
661
- } else if (handle === HandlePosition.UPPER && value) {
662
- newValue = newValue < value ? value : newValue;
663
- }
664
- return newValue;
665
- });
666
- _rollupPluginBabelHelpers.defineProperty(this, "getAdjustedValue", ({
710
+ /**
711
+ * Calculates the slider's value and handle position based on either a
712
+ * mouse/touch event or an explicit value.
713
+ */
714
+ const calcValue = ({
715
+ clientX,
716
+ value,
717
+ useRawValue
718
+ }) => {
719
+ const range = props.max - props.min;
720
+ const leftPercentRaw = calcLeftPercent({
721
+ clientX,
667
722
  value,
668
- min,
669
- max
670
- }) => {
671
- if (value < min) {
672
- value = min;
673
- }
674
- if (value > max) {
675
- value = max;
676
- }
677
- return value;
723
+ range
678
724
  });
679
- /**
680
- * Get the bounding rect for the requested handles' DOM element.
681
- *
682
- * If the bounding rect is not available, a new, empty DOMRect is returned.
683
- */
684
- _rollupPluginBabelHelpers.defineProperty(this, "getHandleBoundingRect", handle => {
685
- let boundingRect;
686
- if (handle === HandlePosition.LOWER) {
687
- boundingRect = this.thumbRef.current?.getBoundingClientRect();
688
- } else {
689
- boundingRect = this.thumbRefUpper.current?.getBoundingClientRect();
690
- }
691
- return boundingRect ?? new DOMRect();
725
+ /** `leftPercentRaw` clamped between 0 and 1. */
726
+ const leftPercent = clamp.clamp(leftPercentRaw, 0, 1);
727
+ if (useRawValue) {
728
+ return {
729
+ value,
730
+ left: leftPercent * 100
731
+ };
732
+ }
733
+
734
+ // Use the discrete value and percentage for snapping.
735
+ const {
736
+ discreteValue,
737
+ discretePercent
738
+ } = calcDiscreteValueAndPercent({
739
+ leftPercent
692
740
  });
693
- this.thumbRef = /*#__PURE__*/React.createRef();
694
- this.thumbRefUpper = /*#__PURE__*/React.createRef();
695
- this.filledTrackRef = /*#__PURE__*/React.createRef();
696
- }
741
+ return {
742
+ value: discreteValue,
743
+ left: discretePercent * 100
744
+ };
745
+ };
746
+ const calcDistanceToHandle = (handle, clientX) => {
747
+ const handleBoundingRect = getHandleBoundingRect(handle);
748
+ /** x-coordinate of the midpoint. */
749
+ const handleX = handleBoundingRect.left + handleBoundingRect.width / 2;
750
+ return Math.abs(handleX - clientX);
751
+ };
697
752
 
698
753
  /**
699
- * Sets up initial slider position and value in response to component mount.
754
+ * Calculates a new slider value based on the current value, a change delta,
755
+ * and a step.
756
+ *
757
+ * @param currentValue - The starting value from which the slider is moving.
758
+ * @param delta - The amount to adjust the current value by.
759
+ * @param step - The step. Defaults to `1`.
760
+ * @returns The new slider value, rounded to the same number of decimal places
761
+ * as the step.
700
762
  */
701
- componentDidMount() {
702
- if (this.element) {
703
- const isRtl = document?.dir === 'rtl';
704
- if (this.hasTwoHandles()) {
705
- const {
706
- value,
707
- left
708
- } = this.calcValue({
709
- value: this.state.value,
710
- useRawValue: true
711
- });
712
- const {
713
- value: valueUpper,
714
- left: leftUpper
715
- } = this.calcValue({
716
- value: this.state.valueUpper,
717
- useRawValue: true
718
- });
719
- this.setState({
720
- isRtl,
721
- value,
722
- left,
723
- valueUpper,
724
- leftUpper
725
- });
726
- if (this.filledTrackRef.current) {
727
- this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(${100 - this.state.leftUpper}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})` : `translate(${this.state.left}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})`;
728
- }
729
- } else {
730
- const {
731
- value,
732
- left
733
- } = this.calcValue({
734
- value: this.state.value,
735
- useRawValue: true
736
- });
737
- this.setState({
738
- isRtl,
739
- value,
740
- left
741
- });
742
- if (this.filledTrackRef.current) {
743
- this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(100%, -50%) scaleX(-${this.state.left / 100})` : `translate(0%, -50%) scaleX(${this.state.left / 100})`;
744
- }
745
- }
746
- }
747
- }
763
+ const calcValueForDelta = (currentValue, delta, step = 1) => {
764
+ const base = delta > 0 ? Math.floor(currentValue / step) * step : currentValue;
765
+ const newValue = base + delta;
766
+ // TODO: Why is the logical OR needed here?
767
+ const decimals = (step.toString().split('.')[1] || '').length;
768
+ return Number(newValue.toFixed(decimals));
769
+ };
748
770
 
749
771
  /**
750
- * Handles firing of `onChange` and `onRelease` callbacks to parent in
751
- * response to state changes.
772
+ * Sets state relevant to the given handle position.
752
773
  *
753
- * @param {*} prevProps prevProps
754
- * @param {*} prevState The previous Slider state, used to see if callbacks
755
- * should be called.
774
+ * Guards against setting either lower or upper values beyond its counterpart.
756
775
  */
757
- componentDidUpdate(prevProps, prevState) {
758
- // Fire onChange event handler if present, if there's a usable value, and
759
- // if the value is different from the last one
760
- if (this.hasTwoHandles()) {
761
- if (this.filledTrackRef.current) {
762
- this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(${100 - this.state.leftUpper}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})` : `translate(${this.state.left}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})`;
763
- }
776
+ const setValueLeftForHandle = (handle, {
777
+ value: newValue,
778
+ left: newLeft
779
+ }) => {
780
+ const {
781
+ value,
782
+ valueUpper,
783
+ left,
784
+ leftUpper
785
+ } = state;
786
+ if (handle === HandlePosition.LOWER) {
787
+ // Don't allow higher than the upper handle.
788
+ setState({
789
+ value: valueUpper && newValue > valueUpper ? valueUpper : newValue,
790
+ left: valueUpper && newValue > valueUpper ? leftUpper : newLeft,
791
+ isValid: true
792
+ });
764
793
  } else {
765
- if (this.filledTrackRef.current) {
766
- this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(100%, -50%) scaleX(-${this.state.left / 100})` : `translate(0%, -50%) scaleX(${this.state.left / 100})`;
767
- }
768
- }
769
- if ((prevState.value !== this.state.value || prevState.valueUpper !== this.state.valueUpper) && typeof this.props.onChange === 'function') {
770
- this.props.onChange({
771
- value: this.state.value,
772
- valueUpper: this.state.valueUpper
794
+ setState({
795
+ valueUpper: value && newValue < value ? value : newValue,
796
+ leftUpper: value && newValue < value ? left : newLeft,
797
+ isValidUpper: true
773
798
  });
774
799
  }
775
-
776
- // Fire onRelease event handler if present and if needed
777
- if (this.state.needsOnRelease && typeof this.props.onRelease === 'function') {
778
- this.props.onRelease({
779
- value: this.state.value,
780
- valueUpper: this.state.valueUpper
800
+ };
801
+ const setValueForHandle = (handle, value) => {
802
+ if (handle === HandlePosition.LOWER) {
803
+ setState({
804
+ // TODO: Address this error
805
+ //
806
+ // @ts-expect-error - Passing a string to something that expects a
807
+ // number.
808
+ value,
809
+ isValid: true
781
810
  });
782
- // Reset the flag
783
- this.setState({
784
- needsOnRelease: false
811
+ } else {
812
+ setState({
813
+ // TODO: Address this error
814
+ //
815
+ // @ts-expect-error - Passing a string to something that expects a
816
+ // number.
817
+ valueUpper: value,
818
+ isValidUpper: true
785
819
  });
786
820
  }
821
+ };
822
+ const isValidValueForPosition = ({
823
+ handle,
824
+ value: newValue,
825
+ min,
826
+ max
827
+ }) => {
828
+ const {
829
+ value,
830
+ valueUpper
831
+ } = state;
832
+ if (!isValidValue({
833
+ value: newValue,
834
+ min,
835
+ max
836
+ })) {
837
+ return false;
838
+ }
839
+ if (handle === HandlePosition.LOWER) {
840
+ return !valueUpper || newValue <= valueUpper;
841
+ } else if (handle === HandlePosition.UPPER) {
842
+ return !value || newValue >= value;
843
+ }
844
+ return false;
845
+ };
846
+ const isValidValue = ({
847
+ value,
848
+ min,
849
+ max
850
+ }) => {
851
+ return !(value < min || value > max);
852
+ };
853
+ const getAdjustedValueForPosition = ({
854
+ handle,
855
+ value: newValueInput,
856
+ min,
857
+ max
858
+ }) => {
859
+ const {
860
+ value,
861
+ valueUpper
862
+ } = state;
863
+ let newValue = getAdjustedValue({
864
+ value: newValueInput,
865
+ min,
866
+ max
867
+ });
787
868
 
788
- // If value from props does not change, do nothing here.
789
- // Otherwise, do prop -> state sync without "value capping".
790
- if (prevProps.value === this.props.value && prevProps.unstable_valueUpper === this.props.unstable_valueUpper && prevProps.max === this.props.max && prevProps.min === this.props.min) {
791
- return;
869
+ // TODO: Just return the value.
870
+ // Next adjust to the opposite handle.
871
+ if (handle === HandlePosition.LOWER && valueUpper) {
872
+ newValue = newValue > valueUpper ? valueUpper : newValue;
873
+ } else if (handle === HandlePosition.UPPER && value) {
874
+ newValue = newValue < value ? value : newValue;
792
875
  }
793
- this.setState(this.calcValue({
794
- value: this.props.value,
795
- useRawValue: true
796
- }));
797
- if (typeof this.props.unstable_valueUpper !== 'undefined') {
798
- const {
799
- value: valueUpper,
800
- left: leftUpper
801
- } = this.calcValue({
802
- value: this.props.unstable_valueUpper,
803
- useRawValue: true
804
- });
805
- this.setState({
806
- valueUpper,
807
- leftUpper
808
- });
809
- } else {
810
- this.setState({
811
- valueUpper: undefined,
812
- leftUpper: undefined
813
- });
876
+ return newValue;
877
+ };
878
+ const getAdjustedValue = ({
879
+ value,
880
+ min,
881
+ max
882
+ }) => {
883
+ // TODO: Just return the value.
884
+ if (value < min) {
885
+ value = min;
814
886
  }
815
- }
887
+ if (value > max) {
888
+ value = max;
889
+ }
890
+ return value;
891
+ };
816
892
 
817
893
  /**
818
- * Rounds a given value to the nearest step defined by the slider's `step`
819
- * prop.
894
+ * Get the bounding rect for the requested handles' DOM element.
820
895
  *
821
- * @param value - The value to adjust to the nearest step. Defaults to `0`.
822
- * @returns The value rounded to the precision determined by the step.
896
+ * If the bounding rect is not available, a new, empty DOMRect is returned.
823
897
  */
824
- nearestStepValue(value = 0) {
825
- const decimals = (this.props.step?.toString().split('.')[1] || '').length;
826
- return Number(value.toFixed(decimals));
827
- }
828
- getClientXFromEvent(event) {
898
+ const getHandleBoundingRect = handle => {
899
+ let boundingRect;
900
+ if (handle === HandlePosition.LOWER) {
901
+ boundingRect = thumbRef.current?.getBoundingClientRect();
902
+ } else {
903
+ boundingRect = thumbRefUpper.current?.getBoundingClientRect();
904
+ }
905
+ return boundingRect ?? new DOMRect();
906
+ };
907
+ const getClientXFromEvent = event => {
829
908
  let clientX;
830
909
  if ('clientX' in event) {
831
910
  clientX = event.clientX;
@@ -833,17 +912,12 @@ class Slider extends React.PureComponent {
833
912
  clientX = event.touches[0].clientX;
834
913
  }
835
914
  return clientX;
836
- }
837
- hasTwoHandles() {
838
- return typeof this.state.valueUpper !== 'undefined';
839
- }
840
-
841
- // syncs invalid state and prop
842
- static getDerivedStateFromProps(props, state) {
915
+ };
916
+ React.useEffect(() => {
843
917
  const {
844
918
  isValid,
845
919
  isValidUpper
846
- } = state;
920
+ } = stateRef.current;
847
921
  const derivedState = {};
848
922
 
849
923
  // Will override state in favor of invalid prop
@@ -854,16 +928,20 @@ class Slider extends React.PureComponent {
854
928
  if (isValid === false) derivedState.isValid = true;
855
929
  if (isValidUpper === false) derivedState.isValidUpper = true;
856
930
  }
857
- return Object.keys(derivedState).length ? derivedState : null;
858
- }
859
- render() {
860
- var _Fragment, _Fragment2, _Fragment3, _Fragment4;
931
+ if (Object.keys(derivedState).length) {
932
+ setState(derivedState);
933
+ }
934
+ }, [props.invalid]);
935
+
936
+ // TODO: Delete this IIFE. It was added to maintain whitespace and to make it clear
937
+ // what exactly has changed.
938
+ return ((_Fragment, _Fragment2, _Fragment3, _Fragment4) => {
861
939
  const {
862
940
  ariaLabelInput,
863
941
  unstable_ariaLabelInputUpper: ariaLabelInputUpper,
864
942
  className,
865
943
  hideTextInput = false,
866
- id = this.inputId = this.inputId ||
944
+ id = inputIdRef.current = inputIdRef.current ||
867
945
  // TODO:
868
946
  // 1. Why isn't `inputId` just set to this value instead of an empty
869
947
  // string?
@@ -893,8 +971,7 @@ class Slider extends React.PureComponent {
893
971
  warnText,
894
972
  translateWithId: t = translateWithId,
895
973
  ...other
896
- } = this.props;
897
- const twoHandles = this.hasTwoHandles();
974
+ } = props;
898
975
  delete other.onRelease;
899
976
  delete other.invalid;
900
977
  delete other.unstable_valueUpper;
@@ -906,7 +983,7 @@ class Slider extends React.PureComponent {
906
983
  correctedValue,
907
984
  correctedPosition,
908
985
  isRtl
909
- } = this.state;
986
+ } = state;
910
987
  const showWarning = !readOnly && warn ||
911
988
  // TODO: https://github.com/carbon-design-system/carbon/issues/18991#issuecomment-2795709637
912
989
  // eslint-disable-next-line valid-typeof , no-constant-binary-expression -- https://github.com/carbon-design-system/carbon/issues/20071
@@ -965,12 +1042,12 @@ class Slider extends React.PureComponent {
965
1042
  }]);
966
1043
  const lowerThumbWrapperProps = {
967
1044
  style: {
968
- insetInlineStart: `${this.state.left}%`
1045
+ insetInlineStart: `${state.left}%`
969
1046
  }
970
1047
  };
971
1048
  const upperThumbWrapperProps = {
972
1049
  style: {
973
- insetInlineStart: `${this.state.leftUpper}%`
1050
+ insetInlineStart: `${state.leftUpper}%`
974
1051
  }
975
1052
  };
976
1053
  return /*#__PURE__*/React.createElement("div", {
@@ -996,10 +1073,10 @@ class Slider extends React.PureComponent {
996
1073
  min: min,
997
1074
  max: max,
998
1075
  step: step,
999
- onChange: this.onChange,
1000
- onBlur: this.onBlur,
1001
- onKeyUp: this.props.onInputKeyUp,
1002
- onKeyDown: this.onInputKeyDown,
1076
+ onChange: onChangeInput,
1077
+ onBlur: onBlurInput,
1078
+ onKeyUp: props.onInputKeyUp,
1079
+ onKeyDown: onInputKeyDown,
1003
1080
  "data-invalid": !isValid && !readOnly ? true : null,
1004
1081
  "data-handle-position": HandlePosition.LOWER,
1005
1082
  "aria-invalid": !isValid && !readOnly ? true : undefined,
@@ -1013,11 +1090,11 @@ class Slider extends React.PureComponent {
1013
1090
  }, formatLabel(min, minLabel)), /*#__PURE__*/React.createElement("div", _rollupPluginBabelHelpers.extends({
1014
1091
  className: sliderClasses,
1015
1092
  ref: node => {
1016
- this.element = node;
1093
+ elementRef.current = node;
1017
1094
  },
1018
- onMouseDown: this.onDragStart,
1019
- onTouchStart: this.onDragStart,
1020
- onKeyDown: this.onKeyDown,
1095
+ onMouseDown: onDragStart,
1096
+ onTouchStart: onDragStart,
1097
+ onKeyDown: onKeyDown,
1021
1098
  role: "presentation",
1022
1099
  tabIndex: -1,
1023
1100
  "data-invalid": (twoHandles ? !isValid || !isValidUpper : !isValid) && !readOnly ? true : null
@@ -1037,8 +1114,8 @@ class Slider extends React.PureComponent {
1037
1114
  "aria-valuenow": value,
1038
1115
  "aria-labelledby": twoHandles ? undefined : labelId,
1039
1116
  "aria-label": twoHandles ? ariaLabelInput : undefined,
1040
- ref: this.thumbRef,
1041
- onFocus: () => this.setState({
1117
+ ref: thumbRef,
1118
+ onFocus: () => setState({
1042
1119
  activeHandle: HandlePosition.LOWER
1043
1120
  })
1044
1121
  }, twoHandles && !isRtl ? _Fragment || (_Fragment = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SliderHandles.LowerHandle, {
@@ -1062,8 +1139,8 @@ class Slider extends React.PureComponent {
1062
1139
  "aria-valuemin": value,
1063
1140
  "aria-valuenow": valueUpper,
1064
1141
  "aria-label": ariaLabelInputUpper,
1065
- ref: this.thumbRefUpper,
1066
- onFocus: () => this.setState({
1142
+ ref: thumbRefUpper,
1143
+ onFocus: () => setState({
1067
1144
  activeHandle: HandlePosition.UPPER
1068
1145
  })
1069
1146
  }, twoHandles && !isRtl ? _Fragment3 || (_Fragment3 = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SliderHandles.UpperHandle, {
@@ -1077,11 +1154,11 @@ class Slider extends React.PureComponent {
1077
1154
  }))) : undefined)) : null, /*#__PURE__*/React.createElement("div", {
1078
1155
  className: `${prefix}--slider__track`,
1079
1156
  ref: node => {
1080
- this.track = node;
1157
+ trackRef.current = node;
1081
1158
  }
1082
1159
  }), /*#__PURE__*/React.createElement("div", {
1083
1160
  className: `${prefix}--slider__filled-track`,
1084
- ref: this.filledTrackRef
1161
+ ref: filledTrackRef
1085
1162
  })), /*#__PURE__*/React.createElement(Text.Text, {
1086
1163
  className: `${prefix}--slider__range-label`
1087
1164
  }, formatLabel(max, maxLabel)), /*#__PURE__*/React.createElement("div", {
@@ -1099,10 +1176,10 @@ class Slider extends React.PureComponent {
1099
1176
  min: min,
1100
1177
  max: max,
1101
1178
  step: step,
1102
- onChange: this.onChange,
1103
- onBlur: this.onBlur,
1104
- onKeyDown: this.onInputKeyDown,
1105
- onKeyUp: this.props.onInputKeyUp,
1179
+ onChange: onChangeInput,
1180
+ onBlur: onBlurInput,
1181
+ onKeyDown: onInputKeyDown,
1182
+ onKeyUp: props.onInputKeyUp,
1106
1183
  "data-invalid": (twoHandles ? !isValidUpper : !isValid) && !readOnly ? true : null,
1107
1184
  "data-handle-position": twoHandles ? HandlePosition.UPPER : null,
1108
1185
  "aria-invalid": (twoHandles ? !isValidUpper : !isValid) && !readOnly ? true : undefined,
@@ -1125,10 +1202,8 @@ class Slider extends React.PureComponent {
1125
1202
  correctedValue
1126
1203
  })));
1127
1204
  });
1128
- }
1129
- }
1130
- _rollupPluginBabelHelpers.defineProperty(Slider, "contextType", index.FeatureFlagContext);
1131
- _rollupPluginBabelHelpers.defineProperty(Slider, "translationIds", Object.values(translationIds));
1205
+ })();
1206
+ };
1132
1207
  Slider.propTypes = {
1133
1208
  /**
1134
1209
  * The `ariaLabel` for the `<input>`.
@@ -1269,4 +1344,4 @@ Slider.propTypes = {
1269
1344
  warnText: PropTypes.node
1270
1345
  };
1271
1346
 
1272
- exports.default = Slider;
1347
+ exports.Slider = Slider;