@astryxdesign/lab 0.7.9-canary.d0cf29a → 0.7.9-canary.f8a309d

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 (51) hide show
  1. package/dist/CircularProgress/CircularProgress.d.ts +4 -2
  2. package/dist/CircularProgress/CircularProgress.d.ts.map +1 -1
  3. package/dist/CircularProgress/CircularProgress.js +19 -23
  4. package/dist/RefreshControl/RefreshPullRecognizer.d.ts +40 -0
  5. package/dist/RefreshControl/RefreshPullRecognizer.d.ts.map +1 -0
  6. package/dist/RefreshControl/RefreshPullRecognizer.js +90 -0
  7. package/dist/RefreshControl/index.d.ts +11 -0
  8. package/dist/RefreshControl/index.d.ts.map +1 -0
  9. package/dist/RefreshControl/index.js +13 -0
  10. package/dist/RefreshControl/refreshIndicator.d.ts +10 -0
  11. package/dist/RefreshControl/refreshIndicator.d.ts.map +1 -0
  12. package/dist/RefreshControl/refreshIndicator.js +12 -0
  13. package/dist/RefreshControl/refreshModel.d.ts +61 -0
  14. package/dist/RefreshControl/refreshModel.d.ts.map +1 -0
  15. package/dist/RefreshControl/refreshModel.js +116 -0
  16. package/dist/RefreshControl/refreshPull.d.ts +69 -0
  17. package/dist/RefreshControl/refreshPull.d.ts.map +1 -0
  18. package/dist/RefreshControl/refreshPull.js +159 -0
  19. package/dist/RefreshControl/refreshTouchController.d.ts +45 -0
  20. package/dist/RefreshControl/refreshTouchController.d.ts.map +1 -0
  21. package/dist/RefreshControl/refreshTouchController.js +701 -0
  22. package/dist/RefreshControl/refreshValues.d.ts +76 -0
  23. package/dist/RefreshControl/refreshValues.d.ts.map +1 -0
  24. package/dist/RefreshControl/refreshValues.js +80 -0
  25. package/dist/RefreshControl/useMotionAnimator.d.ts +8 -0
  26. package/dist/RefreshControl/useMotionAnimator.d.ts.map +1 -0
  27. package/dist/RefreshControl/useMotionAnimator.js +25 -0
  28. package/dist/RefreshControl/useRefreshControl.d.ts +84 -0
  29. package/dist/RefreshControl/useRefreshControl.d.ts.map +1 -0
  30. package/dist/RefreshControl/useRefreshControl.js +207 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -1
  34. package/dist/lab.css +0 -8
  35. package/package.json +5 -5
  36. package/src/CircularProgress/CircularProgress.test.tsx +23 -0
  37. package/src/CircularProgress/CircularProgress.tsx +15 -42
  38. package/src/RefreshControl/RefreshPullRecognizer.ts +104 -0
  39. package/src/RefreshControl/__tests__/RefreshControl.interaction.browser.spec.ts +1454 -0
  40. package/src/RefreshControl/index.ts +18 -0
  41. package/src/RefreshControl/refreshIndicator.ts +12 -0
  42. package/src/RefreshControl/refreshModel.test.ts +174 -0
  43. package/src/RefreshControl/refreshModel.ts +117 -0
  44. package/src/RefreshControl/refreshPull.test.ts +270 -0
  45. package/src/RefreshControl/refreshPull.ts +186 -0
  46. package/src/RefreshControl/refreshTouchController.ts +838 -0
  47. package/src/RefreshControl/refreshValues.ts +80 -0
  48. package/src/RefreshControl/useMotionAnimator.ts +35 -0
  49. package/src/RefreshControl/useRefreshControl.test.tsx +1315 -0
  50. package/src/RefreshControl/useRefreshControl.ts +255 -0
  51. package/src/index.ts +9 -0
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @file CircularProgress.tsx
3
3
  * @input Uses React (useId, refs, layout effects), stylex, SVG, Web
4
- * Animations, color/spacing/duration/ease tokens
4
+ * Animations, color/spacing/duration/ease tokens, and Spinner's rotor
5
+ * (core internal/spinner-rotor) for the indeterminate ring
5
6
  * @output Exports CircularProgress component, CircularProgressProps, CircularProgressSize, CircularProgressVariant types
6
7
  * @position Core implementation; consumed by index.ts
7
8
  *
@@ -89,7 +90,8 @@ export interface CircularProgressProps extends BaseProps<HTMLDivElement> {
89
90
  */
90
91
  variant?: CircularProgressVariant;
91
92
  /**
92
- * When true, renders an animated indeterminate progress indicator.
93
+ * When true, renders an animated indeterminate progress indicator: an arc
94
+ * that turns as Spinner's does.
93
95
  * Use when the progress amount is unknown (e.g. loading, processing).
94
96
  * The `value` and `hasValueLabel` props are ignored in this mode.
95
97
  * Respects `prefers-reduced-motion` by slowing the animation.
@@ -1 +1 @@
1
- {"version":3,"file":"CircularProgress.d.ts","sourceRoot":"","sources":["../../src/CircularProgress/CircularProgress.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAYrE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,0BAA0B,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAWtD,MAAM,WAAW,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACtE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmWD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAS,EACT,GAAS,EACT,KAAK,EACL,aAAoB,EACpB,aAAqB,EACrB,gBAA0C,EAC1C,QAAQ,EACR,IAAW,EACX,OAAkB,EAClB,eAAuB,EACvB,UAAkB,EAClB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAU,EACzB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,qBAAqB,+BA+QvB;yBAjSe,gBAAgB"}
1
+ {"version":3,"file":"CircularProgress.d.ts","sourceRoot":"","sources":["../../src/CircularProgress/CircularProgress.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAiBrE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,0BAA0B,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAWtD,MAAM,WAAW,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACtE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA2TD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAS,EACT,GAAS,EACT,KAAK,EACL,aAAoB,EACpB,aAAqB,EACrB,gBAA0C,EAC1C,QAAQ,EACR,IAAW,EACX,OAAkB,EAClB,eAAuB,EACvB,UAAkB,EAClB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAU,EACzB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,qBAAqB,+BAqRvB;yBAvSe,gBAAgB"}
@@ -5,7 +5,8 @@
5
5
  /**
6
6
  * @file CircularProgress.tsx
7
7
  * @input Uses React (useId, refs, layout effects), stylex, SVG, Web
8
- * Animations, color/spacing/duration/ease tokens
8
+ * Animations, color/spacing/duration/ease tokens, and Spinner's rotor
9
+ * (core internal/spinner-rotor) for the indeterminate ring
9
10
  * @output Exports CircularProgress component, CircularProgressProps, CircularProgressSize, CircularProgressVariant types
10
11
  * @position Core implementation; consumed by index.ts
11
12
  *
@@ -19,6 +20,7 @@ import { useId, useLayoutEffect, useRef } from 'react';
19
20
  import * as stylex from '@stylexjs/stylex';
20
21
  import '@astryxdesign/core/theme/tokens.stylex';
21
22
  import { colorVars, durationVars, easeVars, fontWeightVars, spacingVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
23
+ import { SPINNER_ARC_FRACTION, spinnerRotorStyles, syncRotationPhase } from '@astryxdesign/core/internal/spinner-rotor';
22
24
  import { mergeProps } from '@astryxdesign/core/utils';
23
25
  import { themeProps } from '@astryxdesign/core/utils';
24
26
 
@@ -52,16 +54,6 @@ const SIZE_CONFIG = {
52
54
  strokeWidth: 5
53
55
  }
54
56
  };
55
- // =============================================================================
56
- // Indeterminate animation
57
- // =============================================================================
58
-
59
- const indeterminateRotation = "xqng64z-B";
60
-
61
- // SVG dash percentages scale with the square viewport. A 300% gap exceeds every
62
- // ring circumference; shifting the 2% + 300% pattern by 302% closes the loop.
63
- const indeterminateDash = "x9ppb3p-B";
64
-
65
57
  // =============================================================================
66
58
  // Styles
67
59
  // =============================================================================
@@ -80,6 +72,15 @@ const styles = {
80
72
  kOIVth: "xzye2dw",
81
73
  $$css: true
82
74
  },
75
+ svg: {
76
+ k1xSpc: "x1lliihq",
77
+ k3aq6I: "x9tu13d",
78
+ $$css: true
79
+ },
80
+ svgIndeterminate: {
81
+ k1xSpc: "x1lliihq",
82
+ $$css: true
83
+ },
83
84
  track: {
84
85
  kDwRjp: "xbh8q5q",
85
86
  kjVXCG: "xpi25hw",
@@ -98,10 +99,6 @@ const styles = {
98
99
  fillIndeterminate: {
99
100
  kDwRjp: "xbh8q5q",
100
101
  kU5bRw: "x1owpc8m",
101
- kKVMdj: "x16zax3f",
102
- k44tkh: "xmg6eyc xnh0sag",
103
- kyAemX: "x4hg4is",
104
- ko0y90: "xa4qsjk",
105
102
  $$css: true
106
103
  }
107
104
  };
@@ -462,14 +459,8 @@ export function CircularProgress({
462
459
  width: diameter,
463
460
  height: diameter,
464
461
  viewBox: `0 0 ${diameter} ${diameter}`,
465
- ...{
466
- 0: {
467
- className: "x1lliihq x9tu13d"
468
- },
469
- 1: {
470
- className: "x1lliihq x1ka1v4i x1c74tu6 xy02sl2 x1esw782 xa4qsjk"
471
- }
472
- }[!!isIndeterminate << 0],
462
+ ref: isIndeterminate ? syncRotationPhase : undefined,
463
+ ...stylex.props(isIndeterminate ? styles.svgIndeterminate : styles.svg, isIndeterminate && spinnerRotorStyles.turn),
473
464
  children: [/*#__PURE__*/_jsx("circle", {
474
465
  ...mergeProps(themeProps('circular-progress-track'), stylex.props(styles.track, trackVariantStyles[trackVariant])),
475
466
  cx: center,
@@ -484,6 +475,11 @@ export function CircularProgress({
484
475
  cy: center,
485
476
  r: radius,
486
477
  strokeWidth: strokeWidth
478
+ // Spinner's arc: the same fraction of the ring, starting at 12
479
+ // o'clock.
480
+ ,
481
+ strokeDasharray: `${circumference * SPINNER_ARC_FRACTION} ${circumference * (1 - SPINNER_ARC_FRACTION)}`,
482
+ transform: `rotate(-90 ${center} ${center})`
487
483
  }) : null]
488
484
  }), isIndeterminate ? null :
489
485
  /*#__PURE__*/
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @file RefreshPullRecognizer.ts
3
+ * @input Touches from the gesture arena (Core's internal touch entry)
4
+ * @output Exports RefreshPullRecognizer, the vertical pan that reads a pull
5
+ * to refresh without taking the touch, and RefreshScrollPullRecognizer
6
+ * @position The gesture of useRefreshControl, on the foundation's
7
+ * PanGestureRecognizer
8
+ *
9
+ * SYNC: When modified, update:
10
+ * - /packages/lab/src/RefreshControl/useRefreshControl.ts
11
+ */
12
+ import { PanGestureRecognizer, type PanGestureRecognizerOptions } from '@astryxdesign/core/internal/touch';
13
+ import type { GestureEventContext, GestureTouch, PointerKind } from '@astryxdesign/core/internal/touch';
14
+ export declare class RefreshPullRecognizer extends PanGestureRecognizer {
15
+ /** Called when the first contact of a gesture lands, with its timestamp. */
16
+ onTouchDown: ((time: number) => void) | null;
17
+ /** The pointer of the gesture's first contact. */
18
+ pointerType: PointerKind;
19
+ /** Set when the gesture is a trackpad's scroll rather than a contact. */
20
+ scrollType: GestureTouch['scrollType'] | null;
21
+ constructor(options?: PanGestureRecognizerOptions);
22
+ /** Native scrolling keeps the finger: the pull only reads it. */
23
+ get claimsTouches(): boolean;
24
+ protected touchesBegan(touches: ReadonlyArray<GestureTouch>, event: GestureEventContext): void;
25
+ }
26
+ /**
27
+ * The scroll pull: a trackpad's two-finger scroll at the top. It is a
28
+ * scroll-edge pan that takes a trackpad's scroll and no pointer. So it takes a
29
+ * scroll only where nothing under the pointer could scroll: the owner at its
30
+ * top and the content moving down. The host cancels each wheel event of a
31
+ * gesture that it takes, so its element gets a blocking `wheel` listener. Only
32
+ * a recognizer that claims may register one. It takes no pointer, so it adds
33
+ * no `touchmove` listener.
34
+ */
35
+ export declare class RefreshScrollPullRecognizer extends RefreshPullRecognizer {
36
+ constructor(options?: PanGestureRecognizerOptions);
37
+ /** The scroll it takes is its own: the host cancels its wheel events. */
38
+ get claimsTouches(): boolean;
39
+ }
40
+ //# sourceMappingURL=RefreshPullRecognizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RefreshPullRecognizer.d.ts","sourceRoot":"","sources":["../../src/RefreshControl/RefreshPullRecognizer.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAoBH,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACZ,MAAM,mCAAmC,CAAC;AAE3C,qBAAa,qBAAsB,SAAQ,oBAAoB;IAC7D,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IACpD,kDAAkD;IAClD,WAAW,EAAE,WAAW,CAAW;IACnC,yEAAyE;IACzE,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,GAAG,IAAI,CAAQ;gBAEzC,OAAO,GAAE,2BAAgC;IAUrD,iEAAiE;IACjE,IAAa,aAAa,IAAI,OAAO,CAEpC;cAEkB,YAAY,CAC7B,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,EACpC,KAAK,EAAE,mBAAmB,GACzB,IAAI;CASR;AAED;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,qBAAqB;gBACxD,OAAO,GAAE,2BAAgC;IAUrD,yEAAyE;IACzE,IAAa,aAAa,IAAI,OAAO,CAEpC;CACF"}
@@ -0,0 +1,90 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file RefreshPullRecognizer.ts
5
+ * @input Touches from the gesture arena (Core's internal touch entry)
6
+ * @output Exports RefreshPullRecognizer, the vertical pan that reads a pull
7
+ * to refresh without taking the touch, and RefreshScrollPullRecognizer
8
+ * @position The gesture of useRefreshControl, on the foundation's
9
+ * PanGestureRecognizer
10
+ *
11
+ * SYNC: When modified, update:
12
+ * - /packages/lab/src/RefreshControl/useRefreshControl.ts
13
+ */
14
+
15
+ // UIKit's refresh pull is the scroll view's own drag, not a second gesture.
16
+ // One touch can scroll, pull past the top, and scroll the same content again.
17
+ // So this pan never claims its touch, and only watches it. The browser keeps
18
+ // the finger for native scrolling, so nothing changes hands midway. The pan
19
+ // reads the finger to draw the overscroll, because the scroll owner's
20
+ // `overscroll-behavior-y` is `none` near the top.
21
+ //
22
+ // The pan begins on vertical travel past its hysteresis. The touch layer
23
+ // starts the pull where the content is at its top and the finger moves down.
24
+ // When the browser takes the touch to scroll, the pan keeps reading it to the
25
+ // lift. A second finger is the browser's, for pinch-zoom.
26
+ //
27
+ // A trackpad's scroll pulls through RefreshScrollPullRecognizer. A pen's or
28
+ // a mouse's drag stays the browser's, for text selection: the pan takes only
29
+ // a finger. `pointerType` and `scrollType` record the first contact's input.
30
+ // `onTouchDown` reports that contact landing, so a settle in flight can stop
31
+ // where it is, as UIScrollView brakes on touch-down.
32
+
33
+ import { PanGestureRecognizer } from '@astryxdesign/core/internal/touch';
34
+ export class RefreshPullRecognizer extends PanGestureRecognizer {
35
+ /** Called when the first contact of a gesture lands, with its timestamp. */
36
+ onTouchDown = null;
37
+ /** The pointer of the gesture's first contact. */
38
+ pointerType = 'touch';
39
+ /** Set when the gesture is a trackpad's scroll rather than a contact. */
40
+ scrollType = null;
41
+ constructor(options = {}) {
42
+ super({
43
+ name: 'RefreshPull',
44
+ direction: 'vertical',
45
+ maximumNumberOfTouches: 1,
46
+ allowedPointerTypes: ['touch'],
47
+ ...options
48
+ });
49
+ }
50
+
51
+ /** Native scrolling keeps the finger: the pull only reads it. */
52
+ get claimsTouches() {
53
+ return false;
54
+ }
55
+ touchesBegan(touches, event) {
56
+ if (this.numberOfTouches === touches.length) {
57
+ const touch = touches[0];
58
+ this.pointerType = touch?.pointerType ?? 'touch';
59
+ this.scrollType = touch?.scrollType ?? null;
60
+ this.onTouchDown?.(event.time);
61
+ }
62
+ super.touchesBegan(touches, event);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * The scroll pull: a trackpad's two-finger scroll at the top. It is a
68
+ * scroll-edge pan that takes a trackpad's scroll and no pointer. So it takes a
69
+ * scroll only where nothing under the pointer could scroll: the owner at its
70
+ * top and the content moving down. The host cancels each wheel event of a
71
+ * gesture that it takes, so its element gets a blocking `wheel` listener. Only
72
+ * a recognizer that claims may register one. It takes no pointer, so it adds
73
+ * no `touchmove` listener.
74
+ */
75
+ export class RefreshScrollPullRecognizer extends RefreshPullRecognizer {
76
+ constructor(options = {}) {
77
+ super({
78
+ name: 'RefreshScrollPull',
79
+ scrollEdge: true,
80
+ allowedPointerTypes: [],
81
+ allowedScrollTypes: ['continuous'],
82
+ ...options
83
+ });
84
+ }
85
+
86
+ /** The scroll it takes is its own: the host cancels its wheel events. */
87
+ get claimsTouches() {
88
+ return true;
89
+ }
90
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @file index.ts
3
+ * @input Imports from useRefreshControl
4
+ * @output Exports useRefreshControl, its types and the reveal's custom
5
+ * property
6
+ * @position Entry point for Lab's pull to refresh; re-exported by
7
+ * /packages/lab/src/index.ts
8
+ */
9
+ export { REFRESH_REVEAL_VAR, useRefreshControl } from './useRefreshControl.js';
10
+ export type { UseRefreshControlOptions, UseRefreshControlResult, } from './useRefreshControl.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/RefreshControl/index.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,OAAO,EAAC,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAC1E,YAAY,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,13 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file index.ts
7
+ * @input Imports from useRefreshControl
8
+ * @output Exports useRefreshControl, its types and the reveal's custom
9
+ * property
10
+ * @position Entry point for Lab's pull to refresh; re-exported by
11
+ * /packages/lab/src/index.ts
12
+ */
13
+ export { REFRESH_REVEAL_VAR, useRefreshControl } from "./useRefreshControl.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file refreshIndicator.ts
3
+ * @output The name of the custom property the touch layer writes and the
4
+ * app's indicator styles may read
5
+ * @position Internal to useRefreshControl; re-exported for the app's
6
+ * indicator styles
7
+ */
8
+ /** CSS custom property the indicator reads: how much of it is revealed (0–1). */
9
+ export declare const REFRESH_REVEAL_VAR = "--astryx-refresh-reveal";
10
+ //# sourceMappingURL=refreshIndicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refreshIndicator.d.ts","sourceRoot":"","sources":["../../src/RefreshControl/refreshIndicator.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,4BAA4B,CAAC"}
@@ -0,0 +1,12 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file refreshIndicator.ts
5
+ * @output The name of the custom property the touch layer writes and the
6
+ * app's indicator styles may read
7
+ * @position Internal to useRefreshControl; re-exported for the app's
8
+ * indicator styles
9
+ */
10
+
11
+ /** CSS custom property the indicator reads: how much of it is revealed (0–1). */
12
+ export const REFRESH_REVEAL_VAR = '--astryx-refresh-reveal';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @file refreshModel.ts
3
+ * @input Pull events from the refresh pull recognizer, and programmatic begin
4
+ * and end requests
5
+ * @output Exports reduceRefresh (UIRefreshControl's state machine as a pure
6
+ * reducer that returns the effects to run), the initial state and types
7
+ * @position Pure logic for useRefreshControl; no DOM, no React
8
+ *
9
+ * SYNC: When modified, update:
10
+ * - /packages/lab/src/RefreshControl/refreshModel.test.ts
11
+ */
12
+ export interface RefreshState {
13
+ /** A refresh is in progress (`UIRefreshControl.isRefreshing`). */
14
+ readonly isRefreshing: boolean;
15
+ /** A finger is pulling the content. */
16
+ readonly isPulling: boolean;
17
+ /** The current pull has triggered (its mapping turns one to one). */
18
+ readonly hasTriggered: boolean;
19
+ }
20
+ export type RefreshEvent = {
21
+ readonly type: 'pullStart';
22
+ } | {
23
+ readonly type: 'pullMove';
24
+ readonly overscroll: number;
25
+ /** The trigger distance for this scroll owner. */
26
+ readonly trigger: number;
27
+ } | {
28
+ readonly type: 'pullEnd';
29
+ } | {
30
+ readonly type: 'pullCancel';
31
+ } | {
32
+ readonly type: 'begin';
33
+ readonly source: 'user' | 'program';
34
+ } | {
35
+ readonly type: 'end';
36
+ };
37
+ export type RefreshEffect =
38
+ /** The pull reached the trigger: spin, announce, call the handler. */
39
+ {
40
+ readonly type: 'trigger';
41
+ }
42
+ /** A refresh started without a pull; `callHandler` for the user's request. */
43
+ | {
44
+ readonly type: 'begin';
45
+ readonly callHandler: boolean;
46
+ }
47
+ /** The finger let go: settle onto the inset or back to the top. */
48
+ | {
49
+ readonly type: 'settle';
50
+ readonly to: 'inset' | 'top';
51
+ }
52
+ /** The refresh ended: remove the inset and hide the indicator. */
53
+ | {
54
+ readonly type: 'end';
55
+ };
56
+ export declare const INITIAL_REFRESH_STATE: RefreshState;
57
+ export declare function reduceRefresh(state: RefreshState, event: RefreshEvent): {
58
+ state: RefreshState;
59
+ effects: ReadonlyArray<RefreshEffect>;
60
+ };
61
+ //# sourceMappingURL=refreshModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refreshModel.d.ts","sourceRoot":"","sources":["../../src/RefreshControl/refreshModel.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAeH,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,MAAM,YAAY,GACpB;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAC,GAC5B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,GACD;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GAC1B;IAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAC,GAC7B;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAAC,GAC7D;IAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAC,CAAC;AAE3B,MAAM,MAAM,aAAa;AACvB,sEAAsE;AACpE;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAC;AAC5B,8EAA8E;GAC5E;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAAC;AACzD,mEAAmE;GACjE;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,KAAK,CAAA;CAAC;AACzD,kEAAkE;GAChE;IAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAC,CAAC;AAE3B,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAC;AAEF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,YAAY,GAClB;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAAC,CAgD9D"}
@@ -0,0 +1,116 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file refreshModel.ts
5
+ * @input Pull events from the refresh pull recognizer, and programmatic begin
6
+ * and end requests
7
+ * @output Exports reduceRefresh (UIRefreshControl's state machine as a pure
8
+ * reducer that returns the effects to run), the initial state and types
9
+ * @position Pure logic for useRefreshControl; no DOM, no React
10
+ *
11
+ * SYNC: When modified, update:
12
+ * - /packages/lab/src/RefreshControl/refreshModel.test.ts
13
+ */
14
+
15
+ // The rules, as UIKit runs them:
16
+ // - A pull triggers once, with the finger down, when its overscroll reaches
17
+ // the trigger distance. Only overscroll that a finger drives triggers, never
18
+ // a momentum overshoot.
19
+ // - A release settles onto the refreshing inset while refreshing, and back to
20
+ // the top otherwise. A cancelled pull settles the same way.
21
+ // - A programmatic begin puts the control in the refreshing state. It calls
22
+ // the refresh handler only when it stands for the user's request, not when
23
+ // the app reports a refresh that it started.
24
+ // - Ending a refresh that is not running does nothing.
25
+ //
26
+ // One refresh runs at a time: a pull while refreshing only stretches.
27
+
28
+ export const INITIAL_REFRESH_STATE = {
29
+ isRefreshing: false,
30
+ isPulling: false,
31
+ hasTriggered: false
32
+ };
33
+ export function reduceRefresh(state, event) {
34
+ switch (event.type) {
35
+ case 'pullStart':
36
+ return {
37
+ state: {
38
+ ...state,
39
+ isPulling: true,
40
+ hasTriggered: false
41
+ },
42
+ effects: []
43
+ };
44
+ case 'pullMove':
45
+ {
46
+ // One refresh at a time: a pull while refreshing only stretches.
47
+ if (!state.isPulling || state.hasTriggered || state.isRefreshing || event.overscroll < event.trigger) {
48
+ return {
49
+ state,
50
+ effects: []
51
+ };
52
+ }
53
+ return {
54
+ state: {
55
+ ...state,
56
+ isRefreshing: true,
57
+ hasTriggered: true
58
+ },
59
+ effects: [{
60
+ type: 'trigger'
61
+ }]
62
+ };
63
+ }
64
+ case 'pullEnd':
65
+ case 'pullCancel':
66
+ if (!state.isPulling) {
67
+ return {
68
+ state,
69
+ effects: []
70
+ };
71
+ }
72
+ return {
73
+ state: {
74
+ ...state,
75
+ isPulling: false
76
+ },
77
+ effects: [{
78
+ type: 'settle',
79
+ to: state.isRefreshing ? 'inset' : 'top'
80
+ }]
81
+ };
82
+ case 'begin':
83
+ if (state.isRefreshing) {
84
+ return {
85
+ state,
86
+ effects: []
87
+ };
88
+ }
89
+ return {
90
+ state: {
91
+ ...state,
92
+ isRefreshing: true
93
+ },
94
+ effects: [{
95
+ type: 'begin',
96
+ callHandler: event.source === 'user'
97
+ }]
98
+ };
99
+ case 'end':
100
+ if (!state.isRefreshing) {
101
+ return {
102
+ state,
103
+ effects: []
104
+ };
105
+ }
106
+ return {
107
+ state: {
108
+ ...state,
109
+ isRefreshing: false
110
+ },
111
+ effects: [{
112
+ type: 'end'
113
+ }]
114
+ };
115
+ }
116
+ }
@@ -0,0 +1,69 @@
1
+ /** The refreshing inset and one-to-one stretch, pt. */
2
+ export declare const REFRESH_HEIGHT: number;
3
+ /**
4
+ * Which mapping a pull follows.
5
+ * - `reveal`: before the trigger. One to one up to `height`, then the rubber
6
+ * band on the excess.
7
+ * - `band`: after a trigger within twice the height, and for a pull from the
8
+ * refreshing inset. One to one up to twice the height, then the rubber band.
9
+ */
10
+ export type PullMode = 'reveal' | 'band';
11
+ /**
12
+ * Overscroll shown for `travel` px of pull (the pan's translation, which
13
+ * already leaves out its hysteresis). `dimension` is the scroll owner's
14
+ * height, the rubber band's dimension.
15
+ */
16
+ export declare function pullOverscroll(travel: number, dimension: number, mode: PullMode): number;
17
+ /**
18
+ * How fast the overscroll moves per px of finger travel at `travel`: 1 where
19
+ * it tracks the finger, the rubber band's slope past it. A release's speed
20
+ * times this slope is the content's speed, which its settle starts at.
21
+ */
22
+ export declare function pullSlope(travel: number, dimension: number, mode: PullMode): number;
23
+ /**
24
+ * The travel that shows `overscroll`: where a finger landing on content
25
+ * already pulled (a re-grab mid-settle) resumes without a jump.
26
+ */
27
+ export declare function pullTravel(overscroll: number, dimension: number, mode: PullMode): number;
28
+ /**
29
+ * The mapping after a trigger at `travel`. It is the one-to-one band when the
30
+ * trigger falls within it, and the next frame steps onto the finger. Otherwise
31
+ * it is the reveal mapping, as when a tall scroll view triggers past the band.
32
+ */
33
+ export declare function modeAfterTrigger(travel: number): PullMode;
34
+ /**
35
+ * The overscroll at which a pull triggers, for a scroll owner `dimension`
36
+ * tall: a fraction of its height, never below the minimum.
37
+ */
38
+ export declare function triggerDistance(dimension: number): number;
39
+ /** How much of the indicator a pull reveals, 0 to 1 (linear in the pull). */
40
+ export declare function revealFraction(overscroll: number, dimension: number): number;
41
+ /** The reveal fraction on UIKit's reveal curve: the indicator's opacity. */
42
+ export declare function revealProgress(overscroll: number, dimension: number): number;
43
+ /**
44
+ * Where the content comes to rest when the finger lifts: on the refreshing
45
+ * inset while refreshing, back at the top otherwise.
46
+ */
47
+ export declare function releaseTarget(isRefreshing: boolean): number;
48
+ /**
49
+ * How the end of a refresh moves the content. `scrolled` (≥ 0) is how far the
50
+ * scroll owner is scrolled with the inset still in place. While the gap
51
+ * shows, the content slides up until the gap closes: the inset animates from
52
+ * `height` to `scrolled`. Then the inset and the scroll offset drop together
53
+ * with no visible change. Scrolled past the gap, nothing moves, and the inset
54
+ * and the offset drop at once.
55
+ */
56
+ export declare function endMotion(scrolled: number): {
57
+ animateTo: number | null;
58
+ scrollBy: number;
59
+ };
60
+ /**
61
+ * The end's duration and curve: the theme tokens that `refreshControl` names,
62
+ * as the end's element computes them. Null when either token is missing or
63
+ * in a form it cannot read, such as `calc()`, `var()` or `steps()`.
64
+ */
65
+ export declare function endTiming(style: Pick<CSSStyleDeclaration, 'getPropertyValue'>): {
66
+ ms: number;
67
+ points: readonly [number, number, number, number];
68
+ } | null;
69
+ //# sourceMappingURL=refreshPull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refreshPull.d.ts","sourceRoot":"","sources":["../../src/RefreshControl/refreshPull.ts"],"names":[],"mappings":"AAmCA,uDAAuD;AACvD,eAAO,MAAM,cAAc,EAAE,MAA8B,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAQzC;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,GACb,MAAM,CASR;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,GACb,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,GACb,MAAM,CASR;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAEzD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5E;AAID,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAE3D;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG;IAC3C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAOA;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,GACnD;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAC,GAAG,IAAI,CASxE"}