@astryxdesign/core 0.7.9-canary.76298e4 → 0.7.9-canary.7cda408

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file NumberInput.tsx
3
- * @input Uses React, useId, useState, useMemo, useCallback, useEffect, Field, Icon, InputGroupContext
4
- * @output Exports NumberInput component, NumberInputProps; optional steppers are hidden on coarse pointers, and a held one repeats into the draft
3
+ * @input Uses React, useId, useState, useMemo, useCallback, Field, Icon, InputGroupContext
4
+ * @output Exports NumberInput component, NumberInputProps; optional steppers are hidden on coarse pointers
5
5
  * @position Core implementation; consumed by index.ts, tested by NumberInput.test.tsx
6
6
  *
7
7
  * SYNC: When modified, update these files to stay in sync:
@@ -188,8 +188,6 @@ interface NumberInputPropsBase extends Omit<FieldInputBaseProps, 'onChange' | 'd
188
188
  /**
189
189
  * Whether to show increment and decrement buttons at the end of the input.
190
190
  * The buttons are hidden on coarse pointers (such as touch screens).
191
- * Holding a button with a mouse or a pen repeats the step and commits once,
192
- * when the hold ends.
193
191
  * Direct text entry and keyboard stepping remain available.
194
192
  * @default false
195
193
  */
@@ -1 +1 @@
1
- {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../src/NumberInput/NumberInput.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../src/NumberInput/NumberInput.tsx"],"names":[],"mappings":"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"}
@@ -4,8 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file NumberInput.tsx
7
- * @input Uses React, useId, useState, useMemo, useCallback, useEffect, Field, Icon, InputGroupContext
8
- * @output Exports NumberInput component, NumberInputProps; optional steppers are hidden on coarse pointers, and a held one repeats into the draft
7
+ * @input Uses React, useId, useState, useMemo, useCallback, Field, Icon, InputGroupContext
8
+ * @output Exports NumberInput component, NumberInputProps; optional steppers are hidden on coarse pointers
9
9
  * @position Core implementation; consumed by index.ts, tested by NumberInput.test.tsx
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
@@ -17,7 +17,7 @@
17
17
  * - /apps/storybook/stories/NumberInput.stories.tsx (storybook stories)
18
18
  * - /packages/cli/assets/templates/blocks/components/NumberInput/ (showcase blocks)
19
19
  */
20
- import { use, useId, useState, useMemo, useCallback, useEffect, useRef } from 'react';
20
+ import { use, useId, useState, useMemo, useCallback, useRef } from 'react';
21
21
  import { flushSync } from 'react-dom';
22
22
  import * as stylex from '@stylexjs/stylex';
23
23
  import "../theme/tokens.stylex.js";
@@ -31,7 +31,6 @@ import { joinAriaIDs } from "../utils/inputAria.js";
31
31
  import { withholdCallerHandlers } from "../utils/disabledInteraction.js";
32
32
  import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
33
33
  import { usePressFeedback } from "../hooks/usePressFeedback.js";
34
- import { lockSelection, locksSelectionFor } from "../utils/selectionLock.js";
35
34
  import { inputDisabledFocusStyles } from "../Field/inputDisabledFocus.js";
36
35
  import { useSize } from "../SizeContext/SizeContext.js";
37
36
  import { useInputContainer } from "../hooks/useInputContainer.js";
@@ -144,24 +143,6 @@ registerEnglishFallback(english);
144
143
  * With `hasClear`, onChange also receives `null` when the user clears the input.
145
144
  */
146
145
 
147
- /**
148
- * A held stepper's timing: the first repeat comes `delayMs` after the press.
149
- * Each later interval is `acceleration` times the one before, from
150
- * `firstIntervalMs` down to `minIntervalMs`.
151
- */
152
- const STEPPER_HOLD = {
153
- delayMs: 500,
154
- firstIntervalMs: 250,
155
- acceleration: 0.9,
156
- minIntervalMs: 50
157
- };
158
-
159
- /**
160
- * A held stepper press: its pointer, its next repeat, the value its repeats
161
- * reached, whether the field stopped taking them, and the release of its
162
- * selection lock.
163
- */
164
-
165
146
  function getDecimalPlaces(value) {
166
147
  const [coefficient, exponentText] = String(value).toLowerCase().split('e');
167
148
  const fractionLength = coefficient.split('.')[1]?.length ?? 0;
@@ -305,13 +286,6 @@ export function NumberInput({
305
286
 
306
287
  // Pending input while user is typing (null = show formatted value)
307
288
  const [pendingInput, setPendingInput] = useState(null);
308
- // The value a held stepper's repeats reached, shown until the hold ends.
309
- const [holdDraft, setHoldDraft] = useState(null);
310
- // A field that turns disabled or read-only drops the held value during
311
- // render, so no commit shows a value that the release does not commit.
312
- if (holdDraft !== null && (isDisabled || isReadOnly)) {
313
- setHoldDraft(null);
314
- }
315
289
  const [isFocused, setIsFocused] = useState(false);
316
290
 
317
291
  // Disabled-reason tooltip. The tooltip listeners attach to the input container (which already exists) and
@@ -366,9 +340,6 @@ export function NumberInput({
366
340
  // Preserve pending text while editing. Otherwise show the formatted value
367
341
  // at rest and the raw numeric value while focused so it remains editable.
368
342
  const displayValue = useMemo(() => {
369
- if (holdDraft !== null) {
370
- return formatEditableNumber(holdDraft, locale);
371
- }
372
343
  if (pendingInput !== null) {
373
344
  return pendingInput;
374
345
  }
@@ -376,19 +347,15 @@ export function NumberInput({
376
347
  return '';
377
348
  }
378
349
  return isFocused ? formatEditableNumber(value, locale) : formattedValue;
379
- }, [formattedValue, holdDraft, isFocused, locale, pendingInput, value]);
350
+ }, [formattedValue, isFocused, locale, pendingInput, value]);
380
351
 
381
352
  // Check if current pending input is valid (for styling purposes)
382
353
  const isInputValid = useMemo(() => {
383
- // A held value is a step on the grid, so it is always valid.
384
- if (holdDraft !== null) {
385
- return true;
386
- }
387
354
  if (pendingInput === null || !pendingInput.trim()) {
388
355
  return true;
389
356
  }
390
357
  return parseInput(pendingInput) !== null;
391
- }, [holdDraft, pendingInput, parseInput]);
358
+ }, [pendingInput, parseInput]);
392
359
 
393
360
  // Keep the whole text edit as a draft until an explicit commit boundary.
394
361
  const handleInputChange = useCallback(e => {
@@ -490,152 +457,6 @@ export function NumberInput({
490
457
  }
491
458
  }, [getNextValue, isDisabled, isReadOnly, onChange, value]);
492
459
 
493
- // A held stepper repeats into its own draft and commits once, when the hold
494
- // ends.
495
- const stepperHoldRef = useRef(null);
496
- const isClickAfterHoldRef = useRef(false);
497
- // The pointer of a repeated hold that another press cancelled. Its own
498
- // release still swallows the click that follows.
499
- const cancelledHoldPointerRef = useRef(null);
500
-
501
- // Ends a hold with no commit, and drops the draft its repeats wrote.
502
- const cancelStepperHold = useCallback(() => {
503
- const hold = stepperHoldRef.current;
504
- if (hold == null) {
505
- return;
506
- }
507
- stepperHoldRef.current = null;
508
- cancelledHoldPointerRef.current = hold.held != null ? hold.pointerId : null;
509
- if (hold.timer != null) {
510
- clearTimeout(hold.timer);
511
- }
512
- hold.unlockSelection?.();
513
- setHoldDraft(null);
514
- }, []);
515
-
516
- // Syncs the held stepper's repeat timer with the field's editability: a
517
- // field that turns disabled or read-only stops the repeats, and the release
518
- // commits nothing.
519
- useEffect(() => {
520
- const hold = stepperHoldRef.current;
521
- if (hold == null || !(isDisabled || isReadOnly)) {
522
- return;
523
- }
524
- hold.isFrozen = true;
525
- if (hold.timer != null) {
526
- clearTimeout(hold.timer);
527
- hold.timer = null;
528
- }
529
- }, [isDisabled, isReadOnly]);
530
-
531
- // The stepper column's removal or unmount ends the hold with no commit, as
532
- // its buttons can no longer hear the release.
533
- const stepperColumnRef = useCallback(() => () => {
534
- cancelStepperHold();
535
- }, [cancelStepperHold]);
536
- const startStepperHold = (direction, canStep, event) => {
537
- isClickAfterHoldRef.current = false;
538
- if (cancelledHoldPointerRef.current === event.pointerId) {
539
- cancelledHoldPointerRef.current = null;
540
- }
541
- // Any new press ends a running hold, whichever pointer made it.
542
- cancelStepperHold();
543
- // Steppers render only under a non-coarse primary pointer, so a hold is a
544
- // mouse or pen press. A finger keeps the single step of its click.
545
- if (event.button !== 0 || event.pointerType === 'touch' || !canStep || isDisabled || isReadOnly) {
546
- return;
547
- }
548
- const first = getNextValue(direction);
549
- if (first == null) {
550
- return;
551
- }
552
- // The release ends the hold wherever the pointer is by then.
553
- event.currentTarget.setPointerCapture?.(event.pointerId);
554
- const grid = {
555
- direction,
556
- min,
557
- max,
558
- step,
559
- isIntegerOnly
560
- };
561
- const hold = {
562
- pointerId: event.pointerId,
563
- timer: null,
564
- held: null,
565
- intervalMs: STEPPER_HOLD.firstIntervalMs,
566
- isFrozen: false,
567
- // A pen's hold starts no text selection or callout.
568
- unlockSelection: locksSelectionFor(event.pointerType) ? lockSelection([event.currentTarget], event.target) : null
569
- };
570
- const repeat = () => {
571
- const next = hold.held == null ? first : getSteppedValue({
572
- currentValue: hold.held,
573
- ...grid
574
- });
575
- // At a bound the repeats stop, and the release still commits.
576
- if (next == null || next === hold.held) {
577
- hold.timer = null;
578
- return;
579
- }
580
- hold.held = next;
581
- setHoldDraft(next);
582
- hold.timer = setTimeout(repeat, hold.intervalMs);
583
- hold.intervalMs = Math.max(STEPPER_HOLD.minIntervalMs, hold.intervalMs * STEPPER_HOLD.acceleration);
584
- };
585
- hold.timer = setTimeout(repeat, STEPPER_HOLD.delayMs);
586
- stepperHoldRef.current = hold;
587
- };
588
- const handleStepperContextMenu = event => {
589
- const hold = stepperHoldRef.current;
590
- if (hold == null) {
591
- return;
592
- }
593
- // A pen's hold opens no callout menu. A mouse's context request, such as
594
- // a Control-click, ends its hold with no commit and opens the menu.
595
- if (hold.unlockSelection != null) {
596
- event.preventDefault();
597
- return;
598
- }
599
- cancelStepperHold();
600
- // A click that follows the menu steps nothing, as after any ended hold.
601
- cancelledHoldPointerRef.current = hold.pointerId;
602
- isClickAfterHoldRef.current = true;
603
- };
604
- const endStepperHold = (end, pointerId) => {
605
- const hold = stepperHoldRef.current;
606
- // Only the pointer that holds can end the hold.
607
- if (hold == null || hold.pointerId !== pointerId) {
608
- if (cancelledHoldPointerRef.current === pointerId) {
609
- cancelledHoldPointerRef.current = null;
610
- // Only arm the swallow here: a lost capture must not clear it.
611
- if (end === 'release') {
612
- isClickAfterHoldRef.current = true;
613
- }
614
- }
615
- return;
616
- }
617
- stepperHoldRef.current = null;
618
- if (hold.timer != null) {
619
- clearTimeout(hold.timer);
620
- }
621
- hold.unlockSelection?.();
622
- // A press released before its first repeat is a click, and its click
623
- // steps.
624
- if (hold.held == null) {
625
- return;
626
- }
627
- isClickAfterHoldRef.current = end === 'release';
628
- setHoldDraft(null);
629
- // A frozen hold commits nothing, so a typed draft under it stays.
630
- if (hold.isFrozen || isDisabled || isReadOnly) {
631
- return;
632
- }
633
- setPendingInput(null);
634
- if (hold.held !== value) {
635
- onChange(hold.held);
636
- }
637
- };
638
-
639
460
  // Handle keyboard events
640
461
  const handleKeyDown = useCallback(e => {
641
462
  if (isDisabled) {
@@ -693,31 +514,8 @@ export function NumberInput({
693
514
  return () => input.removeEventListener('wheel', handleWheel);
694
515
  }, [isDisabled, isReadOnly, isWheelEnabled, stepValue]);
695
516
  const mergedInputRef = useMemo(() => mergeRefs(ref, inputRef, wheelListenerRef), [ref, wheelListenerRef]);
696
-
697
- // While a hold runs, the steppers follow the value that it shows.
698
- const {
699
- canIncrement,
700
- canDecrement
701
- } = useMemo(() => {
702
- const base = holdDraft ?? valueForStepping;
703
- const grid = {
704
- currentValue: base,
705
- min,
706
- max,
707
- step,
708
- isIntegerOnly
709
- };
710
- return {
711
- canIncrement: getSteppedValue({
712
- ...grid,
713
- direction: 1
714
- }) !== base,
715
- canDecrement: getSteppedValue({
716
- ...grid,
717
- direction: -1
718
- }) !== base
719
- };
720
- }, [holdDraft, isIntegerOnly, max, min, step, valueForStepping]);
517
+ const canIncrement = getNextValue(1) !== valueForStepping;
518
+ const canDecrement = getNextValue(-1) !== valueForStepping;
721
519
 
722
520
  // Handle clear button click
723
521
  const handleClear = useCallback(e => {
@@ -840,7 +638,6 @@ export function NumberInput({
840
638
  }),
841
639
  onClick: handleClear
842
640
  }), statusIcon, hasNumberSteppers && /*#__PURE__*/_jsxs("div", {
843
- ref: stepperColumnRef,
844
641
  ...{
845
642
  className: "xkh2ocl x78zum5 xhj50sb xdt5ytf x2lah0s x12xnipv x5j3pgv x13i4uq0 xgbv0en x1t7ytsu x14ydlgx xb3r6kr x1g2gkjz xoao14b"
846
643
  },
@@ -852,23 +649,8 @@ export function NumberInput({
852
649
  "aria-label": t('@astryx.numberInput.incrementLabel', {
853
650
  label
854
651
  }),
855
- onPointerDown: event => {
856
- event.preventDefault();
857
- startStepperHold(1, canIncrement, event);
858
- },
859
- onContextMenu: handleStepperContextMenu,
860
- onPointerUp: event => endStepperHold('release', event.pointerId),
861
- onPointerCancel: event => endStepperHold('cancel', event.pointerId),
862
- onLostPointerCapture: event => endStepperHold('cancel', event.pointerId),
863
- onClick: event => {
864
- const isAfterHold = isClickAfterHoldRef.current;
865
- isClickAfterHoldRef.current = false;
866
- // A pointer's click after a held press's release steps nothing.
867
- // A click with no pointer, such as a screen reader's, steps.
868
- if (isAfterHold && event.detail > 0) {
869
- inputRef.current?.focus();
870
- return;
871
- }
652
+ onPointerDown: event => event.preventDefault(),
653
+ onClick: () => {
872
654
  // An aria-disabled stepper does nothing, as a natively disabled
873
655
  // one did (spec:AST-085 FR2).
874
656
  if (isDisabled || isReadOnly || !canIncrement) {
@@ -892,23 +674,8 @@ export function NumberInput({
892
674
  "aria-label": t('@astryx.numberInput.decrementLabel', {
893
675
  label
894
676
  }),
895
- onPointerDown: event => {
896
- event.preventDefault();
897
- startStepperHold(-1, canDecrement, event);
898
- },
899
- onContextMenu: handleStepperContextMenu,
900
- onPointerUp: event => endStepperHold('release', event.pointerId),
901
- onPointerCancel: event => endStepperHold('cancel', event.pointerId),
902
- onLostPointerCapture: event => endStepperHold('cancel', event.pointerId),
903
- onClick: event => {
904
- const isAfterHold = isClickAfterHoldRef.current;
905
- isClickAfterHoldRef.current = false;
906
- // A pointer's click after a held press's release steps nothing.
907
- // A click with no pointer, such as a screen reader's, steps.
908
- if (isAfterHold && event.detail > 0) {
909
- inputRef.current?.focus();
910
- return;
911
- }
677
+ onPointerDown: event => event.preventDefault(),
678
+ onClick: () => {
912
679
  // An aria-disabled stepper does nothing, as a natively disabled
913
680
  // one did (spec:AST-085 FR2).
914
681
  if (isDisabled || isReadOnly || !canDecrement) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.9-canary.76298e4",
3
+ "version": "0.7.9-canary.7cda408",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -870,9 +870,9 @@
870
870
  "react-dom": ">=19.0.0"
871
871
  },
872
872
  "devDependencies": {
873
- "@astryxdesign/a11y-spec": "0.7.9-canary.76298e4",
874
- "@astryxdesign/cli": "0.7.9-canary.76298e4",
875
- "@astryxdesign/interaction-harness": "0.7.9-canary.76298e4",
873
+ "@astryxdesign/a11y-spec": "0.7.9-canary.7cda408",
874
+ "@astryxdesign/cli": "0.7.9-canary.7cda408",
875
+ "@astryxdesign/interaction-harness": "0.7.9-canary.7cda408",
876
876
  "@babel/cli": "^7.29.7",
877
877
  "@babel/core": "^7.29.7",
878
878
  "@babel/preset-react": "^7.29.7",
@@ -160,7 +160,7 @@ export const docs = {
160
160
  name: 'hasNumberSteppers',
161
161
  type: 'boolean',
162
162
  description:
163
- 'Shows increment and decrement buttons at the end of the input on fine pointers. Hidden on coarse pointers (such as touch screens); text entry and keyboard stepping remain available. Holding a button with a mouse or a pen repeats the step and commits once, when the hold ends.',
163
+ 'Shows increment and decrement buttons at the end of the input on fine pointers. Hidden on coarse pointers (such as touch screens); text entry and keyboard stepping remain available.',
164
164
  default: 'false',
165
165
  },
166
166
  {
@@ -4,8 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file NumberInput.tsx
7
- * @input Uses React, useId, useState, useMemo, useCallback, useEffect, Field, Icon, InputGroupContext
8
- * @output Exports NumberInput component, NumberInputProps; optional steppers are hidden on coarse pointers, and a held one repeats into the draft
7
+ * @input Uses React, useId, useState, useMemo, useCallback, Field, Icon, InputGroupContext
8
+ * @output Exports NumberInput component, NumberInputProps; optional steppers are hidden on coarse pointers
9
9
  * @position Core implementation; consumed by index.ts, tested by NumberInput.test.tsx
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
@@ -24,12 +24,9 @@ import {
24
24
  useState,
25
25
  useMemo,
26
26
  useCallback,
27
- useEffect,
28
27
  useRef,
29
28
  type FocusEvent,
30
29
  type KeyboardEvent,
31
- type MouseEvent,
32
- type PointerEvent,
33
30
  type ReactNode,
34
31
  } from 'react';
35
32
  import {flushSync} from 'react-dom';
@@ -59,7 +56,6 @@ import {joinAriaIDs} from '../utils/inputAria';
59
56
  import {withholdCallerHandlers} from '../utils/disabledInteraction';
60
57
  import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
61
58
  import {usePressFeedback} from '../hooks/usePressFeedback';
62
- import {lockSelection, locksSelectionFor} from '../utils/selectionLock';
63
59
  import {inputDisabledFocusStyles} from '../Field/inputDisabledFocus';
64
60
  import {useSize} from '../SizeContext/SizeContext';
65
61
  import {useInputContainer} from '../hooks/useInputContainer';
@@ -407,8 +403,6 @@ interface NumberInputPropsBase extends Omit<
407
403
  /**
408
404
  * Whether to show increment and decrement buttons at the end of the input.
409
405
  * The buttons are hidden on coarse pointers (such as touch screens).
410
- * Holding a button with a mouse or a pen repeats the step and commits once,
411
- * when the hold ends.
412
406
  * Direct text entry and keyboard stepping remain available.
413
407
  * @default false
414
408
  */
@@ -466,32 +460,6 @@ export type NumberInputProps =
466
460
 
467
461
  type StepDirection = -1 | 1;
468
462
 
469
- /**
470
- * A held stepper's timing: the first repeat comes `delayMs` after the press.
471
- * Each later interval is `acceleration` times the one before, from
472
- * `firstIntervalMs` down to `minIntervalMs`.
473
- */
474
- const STEPPER_HOLD = {
475
- delayMs: 500,
476
- firstIntervalMs: 250,
477
- acceleration: 0.9,
478
- minIntervalMs: 50,
479
- } as const;
480
-
481
- /**
482
- * A held stepper press: its pointer, its next repeat, the value its repeats
483
- * reached, whether the field stopped taking them, and the release of its
484
- * selection lock.
485
- */
486
- type StepperHold = {
487
- pointerId: number;
488
- timer: ReturnType<typeof setTimeout> | null;
489
- held: number | null;
490
- intervalMs: number;
491
- isFrozen: boolean;
492
- unlockSelection: (() => void) | null;
493
- };
494
-
495
463
  function getDecimalPlaces(value: number): number {
496
464
  const [coefficient, exponentText] = String(value).toLowerCase().split('e');
497
465
  const fractionLength = coefficient.split('.')[1]?.length ?? 0;
@@ -657,13 +625,6 @@ export function NumberInput({
657
625
 
658
626
  // Pending input while user is typing (null = show formatted value)
659
627
  const [pendingInput, setPendingInput] = useState<string | null>(null);
660
- // The value a held stepper's repeats reached, shown until the hold ends.
661
- const [holdDraft, setHoldDraft] = useState<number | null>(null);
662
- // A field that turns disabled or read-only drops the held value during
663
- // render, so no commit shows a value that the release does not commit.
664
- if (holdDraft !== null && (isDisabled || isReadOnly)) {
665
- setHoldDraft(null);
666
- }
667
628
  const [isFocused, setIsFocused] = useState(false);
668
629
 
669
630
  // Disabled-reason tooltip. The tooltip listeners attach to the input container (which already exists) and
@@ -728,9 +689,6 @@ export function NumberInput({
728
689
  // Preserve pending text while editing. Otherwise show the formatted value
729
690
  // at rest and the raw numeric value while focused so it remains editable.
730
691
  const displayValue = useMemo(() => {
731
- if (holdDraft !== null) {
732
- return formatEditableNumber(holdDraft, locale);
733
- }
734
692
  if (pendingInput !== null) {
735
693
  return pendingInput;
736
694
  }
@@ -738,19 +696,15 @@ export function NumberInput({
738
696
  return '';
739
697
  }
740
698
  return isFocused ? formatEditableNumber(value, locale) : formattedValue;
741
- }, [formattedValue, holdDraft, isFocused, locale, pendingInput, value]);
699
+ }, [formattedValue, isFocused, locale, pendingInput, value]);
742
700
 
743
701
  // Check if current pending input is valid (for styling purposes)
744
702
  const isInputValid = useMemo(() => {
745
- // A held value is a step on the grid, so it is always valid.
746
- if (holdDraft !== null) {
747
- return true;
748
- }
749
703
  if (pendingInput === null || !pendingInput.trim()) {
750
704
  return true;
751
705
  }
752
706
  return parseInput(pendingInput) !== null;
753
- }, [holdDraft, pendingInput, parseInput]);
707
+ }, [pendingInput, parseInput]);
754
708
 
755
709
  // Keep the whole text edit as a draft until an explicit commit boundary.
756
710
  const handleInputChange = useCallback(
@@ -896,167 +850,6 @@ export function NumberInput({
896
850
  [getNextValue, isDisabled, isReadOnly, onChange, value],
897
851
  );
898
852
 
899
- // A held stepper repeats into its own draft and commits once, when the hold
900
- // ends.
901
- const stepperHoldRef = useRef<StepperHold | null>(null);
902
- const isClickAfterHoldRef = useRef(false);
903
- // The pointer of a repeated hold that another press cancelled. Its own
904
- // release still swallows the click that follows.
905
- const cancelledHoldPointerRef = useRef<number | null>(null);
906
-
907
- // Ends a hold with no commit, and drops the draft its repeats wrote.
908
- const cancelStepperHold = useCallback(() => {
909
- const hold = stepperHoldRef.current;
910
- if (hold == null) {
911
- return;
912
- }
913
- stepperHoldRef.current = null;
914
- cancelledHoldPointerRef.current = hold.held != null ? hold.pointerId : null;
915
- if (hold.timer != null) {
916
- clearTimeout(hold.timer);
917
- }
918
- hold.unlockSelection?.();
919
- setHoldDraft(null);
920
- }, []);
921
-
922
- // Syncs the held stepper's repeat timer with the field's editability: a
923
- // field that turns disabled or read-only stops the repeats, and the release
924
- // commits nothing.
925
- useEffect(() => {
926
- const hold = stepperHoldRef.current;
927
- if (hold == null || !(isDisabled || isReadOnly)) {
928
- return;
929
- }
930
- hold.isFrozen = true;
931
- if (hold.timer != null) {
932
- clearTimeout(hold.timer);
933
- hold.timer = null;
934
- }
935
- }, [isDisabled, isReadOnly]);
936
-
937
- // The stepper column's removal or unmount ends the hold with no commit, as
938
- // its buttons can no longer hear the release.
939
- const stepperColumnRef = useCallback(
940
- () => () => {
941
- cancelStepperHold();
942
- },
943
- [cancelStepperHold],
944
- );
945
-
946
- const startStepperHold = (
947
- direction: StepDirection,
948
- canStep: boolean,
949
- event: PointerEvent<HTMLButtonElement>,
950
- ) => {
951
- isClickAfterHoldRef.current = false;
952
- if (cancelledHoldPointerRef.current === event.pointerId) {
953
- cancelledHoldPointerRef.current = null;
954
- }
955
- // Any new press ends a running hold, whichever pointer made it.
956
- cancelStepperHold();
957
- // Steppers render only under a non-coarse primary pointer, so a hold is a
958
- // mouse or pen press. A finger keeps the single step of its click.
959
- if (
960
- event.button !== 0 ||
961
- event.pointerType === 'touch' ||
962
- !canStep ||
963
- isDisabled ||
964
- isReadOnly
965
- ) {
966
- return;
967
- }
968
- const first = getNextValue(direction);
969
- if (first == null) {
970
- return;
971
- }
972
- // The release ends the hold wherever the pointer is by then.
973
- event.currentTarget.setPointerCapture?.(event.pointerId);
974
- const grid = {direction, min, max, step, isIntegerOnly};
975
- const hold: StepperHold = {
976
- pointerId: event.pointerId,
977
- timer: null,
978
- held: null,
979
- intervalMs: STEPPER_HOLD.firstIntervalMs,
980
- isFrozen: false,
981
- // A pen's hold starts no text selection or callout.
982
- unlockSelection: locksSelectionFor(event.pointerType)
983
- ? lockSelection([event.currentTarget], event.target)
984
- : null,
985
- };
986
- const repeat = () => {
987
- const next =
988
- hold.held == null
989
- ? first
990
- : getSteppedValue({currentValue: hold.held, ...grid});
991
- // At a bound the repeats stop, and the release still commits.
992
- if (next == null || next === hold.held) {
993
- hold.timer = null;
994
- return;
995
- }
996
- hold.held = next;
997
- setHoldDraft(next);
998
- hold.timer = setTimeout(repeat, hold.intervalMs);
999
- hold.intervalMs = Math.max(
1000
- STEPPER_HOLD.minIntervalMs,
1001
- hold.intervalMs * STEPPER_HOLD.acceleration,
1002
- );
1003
- };
1004
- hold.timer = setTimeout(repeat, STEPPER_HOLD.delayMs);
1005
- stepperHoldRef.current = hold;
1006
- };
1007
-
1008
- const handleStepperContextMenu = (event: MouseEvent<HTMLButtonElement>) => {
1009
- const hold = stepperHoldRef.current;
1010
- if (hold == null) {
1011
- return;
1012
- }
1013
- // A pen's hold opens no callout menu. A mouse's context request, such as
1014
- // a Control-click, ends its hold with no commit and opens the menu.
1015
- if (hold.unlockSelection != null) {
1016
- event.preventDefault();
1017
- return;
1018
- }
1019
- cancelStepperHold();
1020
- // A click that follows the menu steps nothing, as after any ended hold.
1021
- cancelledHoldPointerRef.current = hold.pointerId;
1022
- isClickAfterHoldRef.current = true;
1023
- };
1024
-
1025
- const endStepperHold = (end: 'release' | 'cancel', pointerId: number) => {
1026
- const hold = stepperHoldRef.current;
1027
- // Only the pointer that holds can end the hold.
1028
- if (hold == null || hold.pointerId !== pointerId) {
1029
- if (cancelledHoldPointerRef.current === pointerId) {
1030
- cancelledHoldPointerRef.current = null;
1031
- // Only arm the swallow here: a lost capture must not clear it.
1032
- if (end === 'release') {
1033
- isClickAfterHoldRef.current = true;
1034
- }
1035
- }
1036
- return;
1037
- }
1038
- stepperHoldRef.current = null;
1039
- if (hold.timer != null) {
1040
- clearTimeout(hold.timer);
1041
- }
1042
- hold.unlockSelection?.();
1043
- // A press released before its first repeat is a click, and its click
1044
- // steps.
1045
- if (hold.held == null) {
1046
- return;
1047
- }
1048
- isClickAfterHoldRef.current = end === 'release';
1049
- setHoldDraft(null);
1050
- // A frozen hold commits nothing, so a typed draft under it stays.
1051
- if (hold.isFrozen || isDisabled || isReadOnly) {
1052
- return;
1053
- }
1054
- setPendingInput(null);
1055
- if (hold.held !== value) {
1056
- onChange(hold.held);
1057
- }
1058
- };
1059
-
1060
853
  // Handle keyboard events
1061
854
  const handleKeyDown = useCallback(
1062
855
  (e: KeyboardEvent<HTMLInputElement>) => {
@@ -1134,15 +927,8 @@ export function NumberInput({
1134
927
  [ref, wheelListenerRef],
1135
928
  );
1136
929
 
1137
- // While a hold runs, the steppers follow the value that it shows.
1138
- const {canIncrement, canDecrement} = useMemo(() => {
1139
- const base = holdDraft ?? valueForStepping;
1140
- const grid = {currentValue: base, min, max, step, isIntegerOnly};
1141
- return {
1142
- canIncrement: getSteppedValue({...grid, direction: 1}) !== base,
1143
- canDecrement: getSteppedValue({...grid, direction: -1}) !== base,
1144
- };
1145
- }, [holdDraft, isIntegerOnly, max, min, step, valueForStepping]);
930
+ const canIncrement = getNextValue(1) !== valueForStepping;
931
+ const canDecrement = getNextValue(-1) !== valueForStepping;
1146
932
 
1147
933
  // Handle clear button click
1148
934
  const handleClear = useCallback(
@@ -1283,7 +1069,7 @@ export function NumberInput({
1283
1069
  )}
1284
1070
  {statusIcon}
1285
1071
  {hasNumberSteppers && (
1286
- <div ref={stepperColumnRef} {...stylex.props(styles.numberSteppers)}>
1072
+ <div {...stylex.props(styles.numberSteppers)}>
1287
1073
  <button
1288
1074
  type="button"
1289
1075
  tabIndex={-1}
@@ -1292,25 +1078,8 @@ export function NumberInput({
1292
1078
  isDisabled || isReadOnly || !canIncrement || undefined
1293
1079
  }
1294
1080
  aria-label={t('@astryx.numberInput.incrementLabel', {label})}
1295
- onPointerDown={event => {
1296
- event.preventDefault();
1297
- startStepperHold(1, canIncrement, event);
1298
- }}
1299
- onContextMenu={handleStepperContextMenu}
1300
- onPointerUp={event => endStepperHold('release', event.pointerId)}
1301
- onPointerCancel={event => endStepperHold('cancel', event.pointerId)}
1302
- onLostPointerCapture={event =>
1303
- endStepperHold('cancel', event.pointerId)
1304
- }
1305
- onClick={event => {
1306
- const isAfterHold = isClickAfterHoldRef.current;
1307
- isClickAfterHoldRef.current = false;
1308
- // A pointer's click after a held press's release steps nothing.
1309
- // A click with no pointer, such as a screen reader's, steps.
1310
- if (isAfterHold && event.detail > 0) {
1311
- inputRef.current?.focus();
1312
- return;
1313
- }
1081
+ onPointerDown={event => event.preventDefault()}
1082
+ onClick={() => {
1314
1083
  // An aria-disabled stepper does nothing, as a natively disabled
1315
1084
  // one did (spec:AST-085 FR2).
1316
1085
  if (isDisabled || isReadOnly || !canIncrement) {
@@ -1340,25 +1109,8 @@ export function NumberInput({
1340
1109
  isDisabled || isReadOnly || !canDecrement || undefined
1341
1110
  }
1342
1111
  aria-label={t('@astryx.numberInput.decrementLabel', {label})}
1343
- onPointerDown={event => {
1344
- event.preventDefault();
1345
- startStepperHold(-1, canDecrement, event);
1346
- }}
1347
- onContextMenu={handleStepperContextMenu}
1348
- onPointerUp={event => endStepperHold('release', event.pointerId)}
1349
- onPointerCancel={event => endStepperHold('cancel', event.pointerId)}
1350
- onLostPointerCapture={event =>
1351
- endStepperHold('cancel', event.pointerId)
1352
- }
1353
- onClick={event => {
1354
- const isAfterHold = isClickAfterHoldRef.current;
1355
- isClickAfterHoldRef.current = false;
1356
- // A pointer's click after a held press's release steps nothing.
1357
- // A click with no pointer, such as a screen reader's, steps.
1358
- if (isAfterHold && event.detail > 0) {
1359
- inputRef.current?.focus();
1360
- return;
1361
- }
1112
+ onPointerDown={event => event.preventDefault()}
1113
+ onClick={() => {
1362
1114
  // An aria-disabled stepper does nothing, as a natively disabled
1363
1115
  // one did (spec:AST-085 FR2).
1364
1116
  if (isDisabled || isReadOnly || !canDecrement) {