@astryxdesign/core 0.7.7-canary.d111827 → 0.7.7-canary.eadd175

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 +1 @@
1
- {"version":3,"file":"MonthScroller.d.ts","sourceRoot":"","sources":["../../src/DateInput/MonthScroller.tsx"],"names":[],"mappings":"AAqDA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EAOf,MAAM,UAAU,CAAC;AAgNlB,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACxD,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,+BAA+B;IAC/B,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,4CAA4C;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,2DAA2D;IAC3D,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC;IAC7C,gCAAgC;IAChC,YAAY,EAAE,SAAS,CAAC;IACxB,kCAAkC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,cAAc,EACd,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,+BA0fpB;yBArgBe,aAAa"}
1
+ {"version":3,"file":"MonthScroller.d.ts","sourceRoot":"","sources":["../../src/DateInput/MonthScroller.tsx"],"names":[],"mappings":"AAqDA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EAOf,MAAM,UAAU,CAAC;AAiNlB,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACxD,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,+BAA+B;IAC/B,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,4CAA4C;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,2DAA2D;IAC3D,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC;IAC7C,gCAAgC;IAChC,YAAY,EAAE,SAAS,CAAC;IACxB,kCAAkC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,cAAc,EACd,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,+BAmjBpB;yBA9jBe,aAAa"}
@@ -38,6 +38,7 @@ import { colorVars, radiusVars, fontWeightVars, typeScaleVars, durationVars, bor
38
38
  import { focusOutlineStyles } from "../utils/index.js";
39
39
  import { plainDateFormatStandaloneMonthYear } from "../utils/plainDate.js";
40
40
  import { plainDateAddDays, plainDateDayOfWeek, plainDateFormat, plainDateIsEqual, plainDateToISO, DATE_FORMAT_WITH_WEEKDAY } from "../utils/index.js";
41
+ import { reducedMotionFade } from "../motion/reducedMotion.js";
41
42
  import "./tokens.stylex.js";
42
43
  import { dateInputTouchSizes, dateInputTouchGeometry } from "./tokens.stylex.js";
43
44
  import { useOwnScrollGesture } from "./useOwnScrollGesture.js";
@@ -251,7 +252,25 @@ export function MonthScroller({
251
252
  hasPositionedRef.current = true;
252
253
  scroller.scrollLeft = scrollOffsetForRow(initialMonthIndex - minMonthIndex, paneSize, isRTL);
253
254
  }, [paneSize, initialMonthIndex, minMonthIndex, isRTL]);
255
+
256
+ // The Reduced Motion dip of a step on a touch screen, owned here so a
257
+ // second step or the unmount clears it.
258
+ const dipRef = useRef(null);
259
+ const stopDip = useCallback(() => {
260
+ const dip = dipRef.current;
261
+ if (dip != null) {
262
+ window.clearTimeout(dip.timer);
263
+ dip.animation.cancel();
264
+ dipRef.current = null;
265
+ }
266
+ }, []);
267
+ // Synchronizes with the dip's timer and animation: neither outlives the
268
+ // scroller.
269
+ useEffect(() => stopDip, [stopDip]);
254
270
  const scrollToMonth = useCallback((monthIndex, behavior = 'smooth') => {
271
+ // Every step replaces a running dip, whichever way it moves. Otherwise
272
+ // the dip's landing would undo a jump or an immediate step made after it.
273
+ stopDip();
255
274
  const scroller = scrollerRef.current;
256
275
  if (scroller == null || paneSize === 0) {
257
276
  return;
@@ -274,12 +293,63 @@ export function MonthScroller({
274
293
  recenter(scroller, row);
275
294
  return;
276
295
  }
296
+ const left = scrollOffsetForRow(row, paneSize, isRTL);
297
+ // Under reduced motion the month changes without travel: at once on a fine
298
+ // primary pointer, under an opacity dip on a coarse one (reducedMotionFade).
299
+ const fade = behavior === 'smooth' ? reducedMotionFade(scroller) : null;
300
+ if (fade != null) {
301
+ // The window mounts around the target first, and the scroll lands in
302
+ // the layout effect below, as a far jump does. So nothing moves the
303
+ // window after the jump. If the window followed the jump, WebKit would
304
+ // re-snap it one pane further on every frame. A scroll that lands on
305
+ // the centre row reports nothing.
306
+ const land = () => {
307
+ // The landing is the newest steer. An earlier steer whose scroll
308
+ // never arrived would otherwise hide every scroll after it, a
309
+ // trackpad's included. Closing the wheels makes such a steer: it
310
+ // re-asserts the month already in view.
311
+ steeredRowRef.current = null;
312
+ if (row === centerRowRef.current) {
313
+ scroller.scrollTo({
314
+ left,
315
+ behavior: 'auto'
316
+ });
317
+ return;
318
+ }
319
+ pendingScrollRef.current = row;
320
+ centerRowRef.current = row;
321
+ setCenterRow(row);
322
+ };
323
+ if (fade.duration === 0) {
324
+ land();
325
+ return;
326
+ }
327
+ // A second step within the dip has replaced the first (above): only
328
+ // its scroll lands, at its own dip's middle.
329
+ dipRef.current = {
330
+ animation: scroller.animate([{
331
+ opacity: 1
332
+ }, {
333
+ opacity: 0
334
+ }, {
335
+ opacity: 1
336
+ }], {
337
+ duration: fade.duration,
338
+ easing: fade.easing
339
+ }),
340
+ timer: window.setTimeout(() => {
341
+ dipRef.current = null;
342
+ land();
343
+ }, fade.duration / 2)
344
+ };
345
+ return;
346
+ }
277
347
  steeredRowRef.current = row;
278
348
  scroller.scrollTo({
279
- left: scrollOffsetForRow(row, paneSize, isRTL),
349
+ left,
280
350
  behavior
281
351
  });
282
- }, [paneSize, minMonthIndex, rowCount, isRTL, recenter]);
352
+ }, [paneSize, minMonthIndex, rowCount, isRTL, recenter, stopDip]);
283
353
 
284
354
  // Deliberately un-keyed: it runs after every render, costs a null check, and
285
355
  // must fire on whichever render finally mounts the pending row's pane.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.7-canary.d111827",
3
+ "version": "0.7.7-canary.eadd175",
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",
@@ -860,9 +860,9 @@
860
860
  "react-dom": ">=19.0.0"
861
861
  },
862
862
  "devDependencies": {
863
- "@astryxdesign/a11y-spec": "0.7.7-canary.d111827",
864
- "@astryxdesign/cli": "0.7.7-canary.d111827",
865
- "@astryxdesign/interaction-harness": "0.7.7-canary.d111827",
863
+ "@astryxdesign/a11y-spec": "0.7.7-canary.eadd175",
864
+ "@astryxdesign/cli": "0.7.7-canary.eadd175",
865
+ "@astryxdesign/interaction-harness": "0.7.7-canary.eadd175",
866
866
  "@babel/cli": "^7.29.7",
867
867
  "@babel/core": "^7.29.7",
868
868
  "@babel/preset-react": "^7.29.7",
@@ -62,6 +62,7 @@ import {
62
62
  plainDateToISO,
63
63
  DATE_FORMAT_WITH_WEEKDAY,
64
64
  } from '../utils';
65
+ import {reducedMotionFade} from '../motion/reducedMotion';
65
66
  import {dateInputTouchSizes, dateInputTouchGeometry} from './tokens.stylex';
66
67
  import {useOwnScrollGesture} from './useOwnScrollGesture';
67
68
  import {useScrollSettle} from './useScrollSettle';
@@ -431,8 +432,26 @@ export function MonthScroller({
431
432
  );
432
433
  }, [paneSize, initialMonthIndex, minMonthIndex, isRTL]);
433
434
 
435
+ // The Reduced Motion dip of a step on a touch screen, owned here so a
436
+ // second step or the unmount clears it.
437
+ const dipRef = useRef<{animation: Animation; timer: number} | null>(null);
438
+ const stopDip = useCallback(() => {
439
+ const dip = dipRef.current;
440
+ if (dip != null) {
441
+ window.clearTimeout(dip.timer);
442
+ dip.animation.cancel();
443
+ dipRef.current = null;
444
+ }
445
+ }, []);
446
+ // Synchronizes with the dip's timer and animation: neither outlives the
447
+ // scroller.
448
+ useEffect(() => stopDip, [stopDip]);
449
+
434
450
  const scrollToMonth = useCallback(
435
451
  (monthIndex: number, behavior: ScrollBehavior = 'smooth') => {
452
+ // Every step replaces a running dip, whichever way it moves. Otherwise
453
+ // the dip's landing would undo a jump or an immediate step made after it.
454
+ stopDip();
436
455
  const scroller = scrollerRef.current;
437
456
  if (scroller == null || paneSize === 0) {
438
457
  return;
@@ -458,13 +477,52 @@ export function MonthScroller({
458
477
  recenter(scroller, row);
459
478
  return;
460
479
  }
480
+ const left = scrollOffsetForRow(row, paneSize, isRTL);
481
+ // Under reduced motion the month changes without travel: at once on a fine
482
+ // primary pointer, under an opacity dip on a coarse one (reducedMotionFade).
483
+ const fade = behavior === 'smooth' ? reducedMotionFade(scroller) : null;
484
+ if (fade != null) {
485
+ // The window mounts around the target first, and the scroll lands in
486
+ // the layout effect below, as a far jump does. So nothing moves the
487
+ // window after the jump. If the window followed the jump, WebKit would
488
+ // re-snap it one pane further on every frame. A scroll that lands on
489
+ // the centre row reports nothing.
490
+ const land = () => {
491
+ // The landing is the newest steer. An earlier steer whose scroll
492
+ // never arrived would otherwise hide every scroll after it, a
493
+ // trackpad's included. Closing the wheels makes such a steer: it
494
+ // re-asserts the month already in view.
495
+ steeredRowRef.current = null;
496
+ if (row === centerRowRef.current) {
497
+ scroller.scrollTo({left, behavior: 'auto'});
498
+ return;
499
+ }
500
+ pendingScrollRef.current = row;
501
+ centerRowRef.current = row;
502
+ setCenterRow(row);
503
+ };
504
+ if (fade.duration === 0) {
505
+ land();
506
+ return;
507
+ }
508
+ // A second step within the dip has replaced the first (above): only
509
+ // its scroll lands, at its own dip's middle.
510
+ dipRef.current = {
511
+ animation: scroller.animate(
512
+ [{opacity: 1}, {opacity: 0}, {opacity: 1}],
513
+ {duration: fade.duration, easing: fade.easing},
514
+ ),
515
+ timer: window.setTimeout(() => {
516
+ dipRef.current = null;
517
+ land();
518
+ }, fade.duration / 2),
519
+ };
520
+ return;
521
+ }
461
522
  steeredRowRef.current = row;
462
- scroller.scrollTo({
463
- left: scrollOffsetForRow(row, paneSize, isRTL),
464
- behavior,
465
- });
523
+ scroller.scrollTo({left, behavior});
466
524
  },
467
- [paneSize, minMonthIndex, rowCount, isRTL, recenter],
525
+ [paneSize, minMonthIndex, rowCount, isRTL, recenter, stopDip],
468
526
  );
469
527
 
470
528
  // Deliberately un-keyed: it runs after every render, costs a null check, and