@octanejs/aria 0.0.23 → 0.0.25

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.
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@octanejs/aria",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
+ "sideEffects": [
7
+ "./src/index.ts",
8
+ "./src/interactions/useFocusVisible.ts",
9
+ "./src/utils/runAfterTransition.ts"
10
+ ],
6
11
  "engines": {
7
12
  "node": ">=22.22.2"
8
13
  },
@@ -46,7 +51,7 @@
46
51
  "clsx": "^2.1.1"
47
52
  },
48
53
  "peerDependencies": {
49
- "octane": "0.1.29"
54
+ "octane": "0.1.31"
50
55
  },
51
56
  "devDependencies": {
52
57
  "@tsrx/react": "^0.2.56",
@@ -57,7 +62,7 @@
57
62
  "react-dom": "^19.2.7",
58
63
  "react-stately": "3.48.0",
59
64
  "vitest": "^4.1.10",
60
- "octane": "0.1.29"
65
+ "octane": "0.1.31"
61
66
  },
62
67
  "scripts": {
63
68
  "test": "vitest run"
package/src/index.ts CHANGED
@@ -2,6 +2,9 @@
2
2
  // upstream monopackage index (.react-spectrum/packages/react-aria/exports/index.ts) and
3
3
  // grow area-by-area with the migration plan (docs/aria-migration-plan.md).
4
4
 
5
+ // Keep global transition tracking reachable when unused behavior hooks are tree-shaken.
6
+ import './utils/runAfterTransition';
7
+
5
8
  // button
6
9
  export { useButton } from './button/useButton';
7
10
  export { useToggleButton } from './button/useToggleButton';
@@ -18,7 +18,7 @@ import type {
18
18
  ValueBase,
19
19
  } from '@react-types/shared';
20
20
  import { NumberFormatter, NumberParser } from '@internationalized/number';
21
- import { useCallback, useMemo, useState } from 'octane';
21
+ import { useCallback, useLinkedState, useMemo, useState } from 'octane';
22
22
 
23
23
  import type { FocusableProps } from '../../interactions/useFocusable';
24
24
 
@@ -111,6 +111,12 @@ export interface NumberFieldStateOptions extends NumberFieldProps {
111
111
  locale: string;
112
112
  }
113
113
 
114
+ interface NumberFieldInputSource {
115
+ value: number;
116
+ locale: string;
117
+ formatOptions: Intl.NumberFormatOptions | undefined;
118
+ }
119
+
114
120
  /**
115
121
  * Provides state management for a number field component. Number fields allow users to enter a
116
122
  * number, and increment or decrement the value using stepper buttons.
@@ -169,17 +175,60 @@ export function useNumberFieldState(...args: any[]): NumberFieldState {
169
175
  subSlot(slot, 'number'),
170
176
  );
171
177
  let [initialValue] = useState(numberValue, subSlot(slot, 'initial'));
172
- let [inputValue, setInputValue] = useState(
173
- () =>
174
- isNaN(numberValue) ? '' : new NumberFormatter(locale, formatOptions).format(numberValue),
175
- subSlot(slot, 'input'),
176
- );
177
-
178
- let numberParser = useMemo(
179
- () => new NumberParser(locale, formatOptions),
178
+ const providedFormatOptions = formatOptions;
179
+ // Normalize parser rounding options before linked-source comparison or formatting.
180
+ let [numberParser, normalizedFormatOptions] = useMemo(
181
+ () => {
182
+ let normalizedOptions = formatOptions;
183
+ if (
184
+ normalizedOptions?.roundingIncrement != null &&
185
+ normalizedOptions.roundingIncrement !== 1 &&
186
+ (normalizedOptions.minimumFractionDigits == null ||
187
+ normalizedOptions.maximumFractionDigits == null)
188
+ ) {
189
+ const fractionDigits =
190
+ normalizedOptions.minimumFractionDigits ?? normalizedOptions.maximumFractionDigits ?? 0;
191
+ normalizedOptions = {
192
+ ...normalizedOptions,
193
+ minimumFractionDigits: normalizedOptions.minimumFractionDigits ?? fractionDigits,
194
+ maximumFractionDigits: normalizedOptions.maximumFractionDigits ?? fractionDigits,
195
+ };
196
+ }
197
+ return [new NumberParser(locale, normalizedOptions), normalizedOptions] as const;
198
+ },
180
199
  [locale, formatOptions],
181
200
  subSlot(slot, 'parser'),
182
201
  );
202
+ formatOptions = normalizedFormatOptions;
203
+ let [inputValue, setInputValue] = useLinkedState<NumberFieldInputSource, string>(
204
+ { value: numberValue, locale, formatOptions },
205
+ (source, previous) => {
206
+ if (isNaN(source.value)) {
207
+ return '';
208
+ }
209
+
210
+ // React formats initial currencies before their parser normalizes rounding digits.
211
+ let options =
212
+ previous === undefined &&
213
+ providedFormatOptions?.style === 'currency' &&
214
+ providedFormatOptions.minimumFractionDigits == null &&
215
+ providedFormatOptions.maximumFractionDigits == null
216
+ ? providedFormatOptions
217
+ : source.formatOptions;
218
+ if (previous !== undefined) {
219
+ const numberingSystem = numberParser.getNumberingSystem(previous.value);
220
+ options = { ...source.formatOptions, numberingSystem };
221
+ }
222
+ return new NumberFormatter(source.locale, options).format(source.value);
223
+ },
224
+ {
225
+ sourceEqual: (previous, next) =>
226
+ Object.is(previous.value, next.value) &&
227
+ previous.locale === next.locale &&
228
+ isEqualFormatOptions(previous.formatOptions, next.formatOptions),
229
+ },
230
+ subSlot(slot, 'input'),
231
+ );
183
232
  let numberingSystem = useMemo(
184
233
  () => numberParser.getNumberingSystem(inputValue),
185
234
  [numberParser, inputValue],
@@ -210,26 +259,6 @@ export function useNumberFieldState(...args: any[]): NumberFieldState {
210
259
  clampStep = 0.01;
211
260
  }
212
261
 
213
- // Update the input value when the number value or format options change. This is done
214
- // in a useEffect so that the controlled behavior is correct and we only update the
215
- // textfield after prop changes.
216
- let [prevValue, setPrevValue] = useState(numberValue, subSlot(slot, 'prevValue'));
217
- let [prevLocale, setPrevLocale] = useState(locale, subSlot(slot, 'prevLocale'));
218
- let [prevFormatOptions, setPrevFormatOptions] = useState(
219
- formatOptions,
220
- subSlot(slot, 'prevFormat'),
221
- );
222
- if (
223
- !Object.is(numberValue, prevValue) ||
224
- locale !== prevLocale ||
225
- !isEqualFormatOptions(formatOptions, prevFormatOptions)
226
- ) {
227
- setInputValue(format(numberValue));
228
- setPrevValue(numberValue);
229
- setPrevLocale(locale);
230
- setPrevFormatOptions(formatOptions);
231
- }
232
-
233
262
  let parsedValue = useMemo(
234
263
  () => numberParser.parse(inputValue),
235
264
  [numberParser, inputValue],