@mindlogic-ai/logician-ui 4.0.0-alpha.28 → 4.0.0-alpha.30

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 (55) hide show
  1. package/dist/components/Pulse/Pulse.d.ts +9 -4
  2. package/dist/components/Pulse/Pulse.d.ts.map +1 -1
  3. package/dist/components/Pulse/Pulse.js +12 -13
  4. package/dist/components/Pulse/Pulse.js.map +1 -1
  5. package/dist/components/Pulse/Pulse.mjs +13 -14
  6. package/dist/components/Pulse/Pulse.mjs.map +1 -1
  7. package/dist/components/Pulse/Pulse.styles.d.ts +14 -1
  8. package/dist/components/Pulse/Pulse.styles.d.ts.map +1 -1
  9. package/dist/components/Pulse/Pulse.styles.js +16 -3
  10. package/dist/components/Pulse/Pulse.styles.js.map +1 -1
  11. package/dist/components/Pulse/Pulse.styles.mjs +16 -3
  12. package/dist/components/Pulse/Pulse.styles.mjs.map +1 -1
  13. package/dist/components/Shake/Shake.d.ts +5 -2
  14. package/dist/components/Shake/Shake.d.ts.map +1 -1
  15. package/dist/components/Shake/Shake.js +9 -5
  16. package/dist/components/Shake/Shake.js.map +1 -1
  17. package/dist/components/Shake/Shake.mjs +10 -6
  18. package/dist/components/Shake/Shake.mjs.map +1 -1
  19. package/dist/components/Shake/Shake.styles.d.ts +6 -1
  20. package/dist/components/Shake/Shake.styles.d.ts.map +1 -1
  21. package/dist/components/Shake/Shake.styles.js +8 -3
  22. package/dist/components/Shake/Shake.styles.js.map +1 -1
  23. package/dist/components/Shake/Shake.styles.mjs +8 -3
  24. package/dist/components/Shake/Shake.styles.mjs.map +1 -1
  25. package/dist/components/SwapTransition/SwapTransition.d.ts +22 -15
  26. package/dist/components/SwapTransition/SwapTransition.d.ts.map +1 -1
  27. package/dist/components/SwapTransition/SwapTransition.js +45 -38
  28. package/dist/components/SwapTransition/SwapTransition.js.map +1 -1
  29. package/dist/components/SwapTransition/SwapTransition.mjs +46 -39
  30. package/dist/components/SwapTransition/SwapTransition.mjs.map +1 -1
  31. package/dist/components/Tabs/Tab/Tab.styles.d.ts +1 -1
  32. package/dist/components/Tabs/Tab/Tab.styles.d.ts.map +1 -1
  33. package/dist/components/Tabs/Tab/Tab.styles.js +7 -1
  34. package/dist/components/Tabs/Tab/Tab.styles.js.map +1 -1
  35. package/dist/components/Tabs/Tab/Tab.styles.mjs +7 -1
  36. package/dist/components/Tabs/Tab/Tab.styles.mjs.map +1 -1
  37. package/dist/theme/motion.d.ts +38 -0
  38. package/dist/theme/motion.d.ts.map +1 -1
  39. package/dist/theme/motion.js +22 -0
  40. package/dist/theme/motion.js.map +1 -1
  41. package/dist/theme/motion.mjs +22 -0
  42. package/dist/theme/motion.mjs.map +1 -1
  43. package/package.json +1 -1
  44. package/src/components/Pulse/Pulse.remount.test.tsx +179 -0
  45. package/src/components/Pulse/Pulse.styles.ts +16 -3
  46. package/src/components/Pulse/Pulse.tsx +11 -12
  47. package/src/components/Reveal/Reveal.test.tsx +80 -0
  48. package/src/components/Shake/Shake.styles.ts +8 -3
  49. package/src/components/Shake/Shake.tsx +6 -4
  50. package/src/components/SwapTransition/SwapTransition.stories.tsx +95 -0
  51. package/src/components/SwapTransition/SwapTransition.test.tsx +127 -0
  52. package/src/components/SwapTransition/SwapTransition.tsx +62 -59
  53. package/src/components/Tabs/Tab/Tab.styles.ts +7 -1
  54. package/src/theme/MotionCases.stories.tsx +4 -5
  55. package/src/theme/motion.ts +22 -0
@@ -5,10 +5,15 @@ import { PulseProps } from './Pulse.types';
5
5
  *
6
6
  * Pure CSS. The obvious implementation reaches for an animation library, since
7
7
  * "replay this on demand" is what their imperative controls are for, but CSS
8
- * already has the mechanism: an element whose `key` changes is a *new* element,
9
- * and a new element runs its animation from the top. That is the same trick the
10
- * `stagger` preset uses to replay on reopen, and it is why this needs no
11
- * dependency, no ref plumbing and no effect.
8
+ * already has the mechanism: an animation restarts when its **name** changes, so
9
+ * this alternates between two byte-identical keyframes. No dependency, no ref
10
+ * plumbing, no effect.
11
+ *
12
+ * Not a changed React `key`, which also restarts it and was how this shipped
13
+ * first. A new key is a new element, so the whole subtree is thrown away and
14
+ * rebuilt — measured cost: keyboard focus lands on `<body>`, uncontrolled inputs
15
+ * reset, child animations restart. A pop that draws the eye to a value must not
16
+ * also move the keyboard out from under someone.
12
17
  *
13
18
  * The first render never pops. A counter that mounts already at 12 has not just
14
19
  * become 12, and an interface that celebrates its own initial paint is noise.
@@ -1 +1 @@
1
- {"version":3,"file":"Pulse.d.ts","sourceRoot":"","sources":["../../../src/components/Pulse/Pulse.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,KAAK,uGAwCjB,CAAC"}
1
+ {"version":3,"file":"Pulse.d.ts","sourceRoot":"","sources":["../../../src/components/Pulse/Pulse.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,KAAK,uGAkCjB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
+ var jsxRuntime = require('react/jsx-runtime');
4
5
  var React = require('react');
5
6
  var react = require('@chakra-ui/react');
6
7
  var mergeCss = require('../../utils/mergeCss.js');
@@ -12,10 +13,15 @@ var Pulse_styles = require('./Pulse.styles.js');
12
13
  *
13
14
  * Pure CSS. The obvious implementation reaches for an animation library, since
14
15
  * "replay this on demand" is what their imperative controls are for, but CSS
15
- * already has the mechanism: an element whose `key` changes is a *new* element,
16
- * and a new element runs its animation from the top. That is the same trick the
17
- * `stagger` preset uses to replay on reopen, and it is why this needs no
18
- * dependency, no ref plumbing and no effect.
16
+ * already has the mechanism: an animation restarts when its **name** changes, so
17
+ * this alternates between two byte-identical keyframes. No dependency, no ref
18
+ * plumbing, no effect.
19
+ *
20
+ * Not a changed React `key`, which also restarts it and was how this shipped
21
+ * first. A new key is a new element, so the whole subtree is thrown away and
22
+ * rebuilt — measured cost: keyboard focus lands on `<body>`, uncontrolled inputs
23
+ * reset, child animations restart. A pop that draws the eye to a value must not
24
+ * also move the keyboard out from under someone.
19
25
  *
20
26
  * The first render never pops. A counter that mounts already at 12 has not just
21
27
  * become 12, and an interface that celebrates its own initial paint is noise.
@@ -48,16 +54,9 @@ const Pulse = React.forwardRef(({ trigger, peak, children, css, ...rest }, ref)
48
54
  previous.current = trigger;
49
55
  plays.current += 1;
50
56
  }
51
- return (React.createElement(react.Box, { ref: ref, display: "inline-flex", ...rest,
52
- // Remounts on every change, which restarts the animation. `plays` is 0
53
- // until the first change, so the initial mount renders the branch that
54
- // has no animation on it at all.
55
- key: plays.current, css: plays.current === 0
57
+ return (jsxRuntime.jsx(react.Box, { ref: ref, display: "inline-flex", ...rest, css: plays.current === 0
56
58
  ? css
57
- : mergeCss.mergeCss({
58
- ...Pulse_styles.pulsePop,
59
- ...(peak === undefined ? {} : { '--pulse-peak': peak }),
60
- }, css) }, children));
59
+ : mergeCss.mergeCss(Pulse_styles.pulsePop(plays.current), peak === undefined ? undefined : { '--pulse-peak': peak }, css), children: children }));
61
60
  });
62
61
  Pulse.displayName = 'Pulse';
63
62
 
@@ -1 +1 @@
1
- {"version":3,"file":"Pulse.js","sources":["../../../src/components/Pulse/Pulse.tsx"],"sourcesContent":[null],"names":["forwardRef","useRef","_createElement","Box","mergeCss","pulsePop"],"mappings":";;;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;MACU,KAAK,GAAGA,gBAAU,CAC7B,CACE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACrD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAGC,YAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAGA,YAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;AAEA,IAAA,QACEC,mBAAA,CAACC,SAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI;;;;QAIR,GAAG,EAAE,KAAK,CAAC,OAAO,EAClB,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;cACAC,iBAAQ,CACN;AACE,gBAAA,GAAGC,qBAAQ;AACX,gBAAA,IAAI,IAAI,KAAK,SAAS,GAAG,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AACxD,aAAA,EACD,GAAG,CACJ,EAAA,EAGN,QAAQ,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
1
+ {"version":3,"file":"Pulse.js","sources":["../../../src/components/Pulse/Pulse.tsx"],"sourcesContent":[null],"names":["forwardRef","useRef","_jsx","Box","mergeCss","pulsePop"],"mappings":";;;;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCG;MACU,KAAK,GAAGA,gBAAU,CAC7B,CACE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACrD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAGC,YAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAGA,YAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;IAEA,QACEC,eAACC,SAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI,EACR,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;AACF,cAAEC,iBAAQ,CACNC,qBAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EACvB,IAAI,KAAK,SAAS,GAAG,SAAS,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,EACzD,GAAG,CACJ,EAAA,QAAA,EAGN,QAAQ,EAAA,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { forwardRef, useRef, createElement } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { forwardRef, useRef } from 'react';
3
4
  import { Box } from '@chakra-ui/react';
4
5
  import { mergeCss } from '../../utils/mergeCss.mjs';
5
6
  import { pulsePop } from './Pulse.styles.mjs';
@@ -10,10 +11,15 @@ import { pulsePop } from './Pulse.styles.mjs';
10
11
  *
11
12
  * Pure CSS. The obvious implementation reaches for an animation library, since
12
13
  * "replay this on demand" is what their imperative controls are for, but CSS
13
- * already has the mechanism: an element whose `key` changes is a *new* element,
14
- * and a new element runs its animation from the top. That is the same trick the
15
- * `stagger` preset uses to replay on reopen, and it is why this needs no
16
- * dependency, no ref plumbing and no effect.
14
+ * already has the mechanism: an animation restarts when its **name** changes, so
15
+ * this alternates between two byte-identical keyframes. No dependency, no ref
16
+ * plumbing, no effect.
17
+ *
18
+ * Not a changed React `key`, which also restarts it and was how this shipped
19
+ * first. A new key is a new element, so the whole subtree is thrown away and
20
+ * rebuilt — measured cost: keyboard focus lands on `<body>`, uncontrolled inputs
21
+ * reset, child animations restart. A pop that draws the eye to a value must not
22
+ * also move the keyboard out from under someone.
17
23
  *
18
24
  * The first render never pops. A counter that mounts already at 12 has not just
19
25
  * become 12, and an interface that celebrates its own initial paint is noise.
@@ -46,16 +52,9 @@ const Pulse = forwardRef(({ trigger, peak, children, css, ...rest }, ref) => {
46
52
  previous.current = trigger;
47
53
  plays.current += 1;
48
54
  }
49
- return (createElement(Box, { ref: ref, display: "inline-flex", ...rest,
50
- // Remounts on every change, which restarts the animation. `plays` is 0
51
- // until the first change, so the initial mount renders the branch that
52
- // has no animation on it at all.
53
- key: plays.current, css: plays.current === 0
55
+ return (jsx(Box, { ref: ref, display: "inline-flex", ...rest, css: plays.current === 0
54
56
  ? css
55
- : mergeCss({
56
- ...pulsePop,
57
- ...(peak === undefined ? {} : { '--pulse-peak': peak }),
58
- }, css) }, children));
57
+ : mergeCss(pulsePop(plays.current), peak === undefined ? undefined : { '--pulse-peak': peak }, css), children: children }));
59
58
  });
60
59
  Pulse.displayName = 'Pulse';
61
60
 
@@ -1 +1 @@
1
- {"version":3,"file":"Pulse.mjs","sources":["../../../src/components/Pulse/Pulse.tsx"],"sourcesContent":[null],"names":["_createElement"],"mappings":";;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;MACU,KAAK,GAAG,UAAU,CAC7B,CACE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACrD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;AAEA,IAAA,QACEA,aAAA,CAAC,GAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI;;;;QAIR,GAAG,EAAE,KAAK,CAAC,OAAO,EAClB,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;cACA,QAAQ,CACN;AACE,gBAAA,GAAG,QAAQ;AACX,gBAAA,IAAI,IAAI,KAAK,SAAS,GAAG,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AACxD,aAAA,EACD,GAAG,CACJ,EAAA,EAGN,QAAQ,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
1
+ {"version":3,"file":"Pulse.mjs","sources":["../../../src/components/Pulse/Pulse.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":";;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCG;MACU,KAAK,GAAG,UAAU,CAC7B,CACE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACrD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;IAEA,QACEA,IAAC,GAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI,EACR,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;AACF,cAAE,QAAQ,CACN,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EACvB,IAAI,KAAK,SAAS,GAAG,SAAS,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,EACzD,GAAG,CACJ,EAAA,QAAA,EAGN,QAAQ,EAAA,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
@@ -18,5 +18,18 @@ import type { SystemStyleObject } from '@chakra-ui/react';
18
18
  * the eye to has already changed by the time this plays, so nothing is lost but
19
19
  * the emphasis.
20
20
  */
21
- export declare const pulsePop: SystemStyleObject;
21
+ /**
22
+ * A function of the play count, because the *name* is what restarts it.
23
+ *
24
+ * A CSS animation replays when `animation-name` changes, so alternating between
25
+ * two byte-identical keyframes restarts the pop on every trigger without
26
+ * touching the DOM. The earlier version restarted it by changing the React
27
+ * `key`, which remounts the subtree — and a remount throws away whatever was
28
+ * inside it: the user's focus (measured: it lands on `<body>`), any uncontrolled
29
+ * input state, and any child animation mid-flight. A pop that draws the eye to a
30
+ * changed value must not also move the keyboard out from under someone, and
31
+ * `Shake` is worse on this axis than `Pulse` is: it fires on a rejected answer,
32
+ * which is exactly when a keyboard user is mid-interaction.
33
+ */
34
+ export declare const pulsePop: (play: number) => SystemStyleObject;
22
35
  //# sourceMappingURL=Pulse.styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Pulse.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Pulse/Pulse.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ,EAAE,iBAOtB,CAAC"}
1
+ {"version":3,"file":"Pulse.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Pulse/Pulse.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,iBAOtC,CAAC"}
@@ -20,14 +20,27 @@
20
20
  * the eye to has already changed by the time this plays, so nothing is lost but
21
21
  * the emphasis.
22
22
  */
23
- const pulsePop = {
24
- animationName: 'pulse-pop',
23
+ /**
24
+ * A function of the play count, because the *name* is what restarts it.
25
+ *
26
+ * A CSS animation replays when `animation-name` changes, so alternating between
27
+ * two byte-identical keyframes restarts the pop on every trigger without
28
+ * touching the DOM. The earlier version restarted it by changing the React
29
+ * `key`, which remounts the subtree — and a remount throws away whatever was
30
+ * inside it: the user's focus (measured: it lands on `<body>`), any uncontrolled
31
+ * input state, and any child animation mid-flight. A pop that draws the eye to a
32
+ * changed value must not also move the keyboard out from under someone, and
33
+ * `Shake` is worse on this axis than `Pulse` is: it fires on a rejected answer,
34
+ * which is exactly when a keyboard user is mid-interaction.
35
+ */
36
+ const pulsePop = (play) => ({
37
+ animationName: play % 2 === 0 ? 'pulse-pop-alt' : 'pulse-pop',
25
38
  animationDuration: 'motion.slow',
26
39
  animationTimingFunction: 'overshoot',
27
40
  animationFillMode: 'both',
28
41
  transformOrigin: 'center',
29
42
  _motionReduce: { animationName: 'none' },
30
- };
43
+ });
31
44
 
32
45
  exports.pulsePop = pulsePop;
33
46
  //# sourceMappingURL=Pulse.styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Pulse.styles.js","sources":["../../../src/components/Pulse/Pulse.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;AAkBG;AACI,MAAM,QAAQ,GAAsB;AACzC,IAAA,aAAa,EAAE,WAAW;AAC1B,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,WAAW;AACpC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;;;;;"}
1
+ {"version":3,"file":"Pulse.styles.js","sources":["../../../src/components/Pulse/Pulse.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;AAkBG;AACH;;;;;;;;;;;;AAYG;MACU,QAAQ,GAAG,CAAC,IAAY,MAAyB;AAC5D,IAAA,aAAa,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,eAAe,GAAG,WAAW;AAC7D,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,WAAW;AACpC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;AACzC,CAAA;;;;"}
@@ -18,14 +18,27 @@
18
18
  * the eye to has already changed by the time this plays, so nothing is lost but
19
19
  * the emphasis.
20
20
  */
21
- const pulsePop = {
22
- animationName: 'pulse-pop',
21
+ /**
22
+ * A function of the play count, because the *name* is what restarts it.
23
+ *
24
+ * A CSS animation replays when `animation-name` changes, so alternating between
25
+ * two byte-identical keyframes restarts the pop on every trigger without
26
+ * touching the DOM. The earlier version restarted it by changing the React
27
+ * `key`, which remounts the subtree — and a remount throws away whatever was
28
+ * inside it: the user's focus (measured: it lands on `<body>`), any uncontrolled
29
+ * input state, and any child animation mid-flight. A pop that draws the eye to a
30
+ * changed value must not also move the keyboard out from under someone, and
31
+ * `Shake` is worse on this axis than `Pulse` is: it fires on a rejected answer,
32
+ * which is exactly when a keyboard user is mid-interaction.
33
+ */
34
+ const pulsePop = (play) => ({
35
+ animationName: play % 2 === 0 ? 'pulse-pop-alt' : 'pulse-pop',
23
36
  animationDuration: 'motion.slow',
24
37
  animationTimingFunction: 'overshoot',
25
38
  animationFillMode: 'both',
26
39
  transformOrigin: 'center',
27
40
  _motionReduce: { animationName: 'none' },
28
- };
41
+ });
29
42
 
30
43
  export { pulsePop };
31
44
  //# sourceMappingURL=Pulse.styles.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Pulse.styles.mjs","sources":["../../../src/components/Pulse/Pulse.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;AAkBG;AACI,MAAM,QAAQ,GAAsB;AACzC,IAAA,aAAa,EAAE,WAAW;AAC1B,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,WAAW;AACpC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;;;;;"}
1
+ {"version":3,"file":"Pulse.styles.mjs","sources":["../../../src/components/Pulse/Pulse.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;AAkBG;AACH;;;;;;;;;;;;AAYG;MACU,QAAQ,GAAG,CAAC,IAAY,MAAyB;AAC5D,IAAA,aAAa,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,eAAe,GAAG,WAAW;AAC7D,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,WAAW;AACpC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;AACzC,CAAA;;;;"}
@@ -8,8 +8,11 @@ import { ShakeProps } from './Shake.types';
8
8
  * shake refuses. A shake used as emphasis reads as an error the reader then goes
9
9
  * looking for.
10
10
  *
11
- * Replays the same way `Pulse` does — a changed `key` is a new element, and a
12
- * new element runs its animation from the top. The first render never shakes.
11
+ * Replays the same way `Pulse` does — two byte-identical keyframes alternating
12
+ * by play count, because a CSS animation restarts when its name changes. The
13
+ * DOM is left alone, which matters more here than anywhere: a shake fires on a
14
+ * refusal, so the earlier `key`-driven replay took the keyboard focus of the
15
+ * person who had just been told no. The first render never shakes.
13
16
  *
14
17
  * **Never let this be the only signal.** It is removed entirely under reduced
15
18
  * motion, so for some readers nothing happens at all; pair it with colour, an
@@ -1 +1 @@
1
- {"version":3,"file":"Shake.d.ts","sourceRoot":"","sources":["../../../src/components/Shake/Shake.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,KAAK,uGAqCjB,CAAC"}
1
+ {"version":3,"file":"Shake.d.ts","sourceRoot":"","sources":["../../../src/components/Shake/Shake.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,KAAK,uGAoCjB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
+ var jsxRuntime = require('react/jsx-runtime');
4
5
  var React = require('react');
5
6
  var react = require('@chakra-ui/react');
6
7
  var mergeCss = require('../../utils/mergeCss.js');
@@ -15,8 +16,11 @@ var Shake_styles = require('./Shake.styles.js');
15
16
  * shake refuses. A shake used as emphasis reads as an error the reader then goes
16
17
  * looking for.
17
18
  *
18
- * Replays the same way `Pulse` does — a changed `key` is a new element, and a
19
- * new element runs its animation from the top. The first render never shakes.
19
+ * Replays the same way `Pulse` does — two byte-identical keyframes alternating
20
+ * by play count, because a CSS animation restarts when its name changes. The
21
+ * DOM is left alone, which matters more here than anywhere: a shake fires on a
22
+ * refusal, so the earlier `key`-driven replay took the keyboard focus of the
23
+ * person who had just been told no. The first render never shakes.
20
24
  *
21
25
  * **Never let this be the only signal.** It is removed entirely under reduced
22
26
  * motion, so for some readers nothing happens at all; pair it with colour, an
@@ -45,11 +49,11 @@ const Shake = React.forwardRef(({ trigger, distance, children, css, ...rest }, r
45
49
  previous.current = trigger;
46
50
  plays.current += 1;
47
51
  }
48
- return (React.createElement(react.Box, { ref: ref, display: "inline-flex", ...rest, key: plays.current, css: plays.current === 0
52
+ return (jsxRuntime.jsx(react.Box, { ref: ref, display: "inline-flex", ...rest, css: plays.current === 0
49
53
  ? css
50
- : mergeCss.mergeCss(Shake_styles.shakeX, distance === undefined
54
+ : mergeCss.mergeCss(Shake_styles.shakeX(plays.current), distance === undefined
51
55
  ? undefined
52
- : { '--shake-distance': `${distance}px` }, css) }, children));
56
+ : { '--shake-distance': `${distance}px` }, css), children: children }));
53
57
  });
54
58
  Shake.displayName = 'Shake';
55
59
 
@@ -1 +1 @@
1
- {"version":3,"file":"Shake.js","sources":["../../../src/components/Shake/Shake.tsx"],"sourcesContent":[null],"names":["forwardRef","useRef","_createElement","Box","mergeCss","shakeX"],"mappings":";;;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;MACU,KAAK,GAAGA,gBAAU,CAC7B,CACE,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACzD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAGC,YAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAGA,YAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;IAEA,QACEC,mBAAA,CAACC,SAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI,EACR,GAAG,EAAE,KAAK,CAAC,OAAO,EAClB,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;AACF,cAAEC,iBAAQ,CACNC,mBAAM,EACN,QAAQ,KAAK;AACX,kBAAE;AACF,kBAAE,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAAA,EAAA,CAAI,EAAE,EAC3C,GAAG,CACJ,EAAA,EAGN,QAAQ,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
1
+ {"version":3,"file":"Shake.js","sources":["../../../src/components/Shake/Shake.tsx"],"sourcesContent":[null],"names":["forwardRef","useRef","_jsx","Box","mergeCss","shakeX"],"mappings":";;;;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;MACU,KAAK,GAAGA,gBAAU,CAC7B,CACE,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACzD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAGC,YAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAGA,YAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;IAEA,QACEC,eAACC,SAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI,EACR,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;AACF,cAAEC,iBAAQ,CACNC,mBAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EACrB,QAAQ,KAAK;AACX,kBAAE;AACF,kBAAE,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAAA,EAAA,CAAI,EAAE,EAC3C,GAAG,CACJ,EAAA,QAAA,EAGN,QAAQ,EAAA,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { forwardRef, useRef, createElement } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { forwardRef, useRef } from 'react';
3
4
  import { Box } from '@chakra-ui/react';
4
5
  import { mergeCss } from '../../utils/mergeCss.mjs';
5
6
  import { shakeX } from './Shake.styles.mjs';
@@ -13,8 +14,11 @@ import { shakeX } from './Shake.styles.mjs';
13
14
  * shake refuses. A shake used as emphasis reads as an error the reader then goes
14
15
  * looking for.
15
16
  *
16
- * Replays the same way `Pulse` does — a changed `key` is a new element, and a
17
- * new element runs its animation from the top. The first render never shakes.
17
+ * Replays the same way `Pulse` does — two byte-identical keyframes alternating
18
+ * by play count, because a CSS animation restarts when its name changes. The
19
+ * DOM is left alone, which matters more here than anywhere: a shake fires on a
20
+ * refusal, so the earlier `key`-driven replay took the keyboard focus of the
21
+ * person who had just been told no. The first render never shakes.
18
22
  *
19
23
  * **Never let this be the only signal.** It is removed entirely under reduced
20
24
  * motion, so for some readers nothing happens at all; pair it with colour, an
@@ -43,11 +47,11 @@ const Shake = forwardRef(({ trigger, distance, children, css, ...rest }, ref) =>
43
47
  previous.current = trigger;
44
48
  plays.current += 1;
45
49
  }
46
- return (createElement(Box, { ref: ref, display: "inline-flex", ...rest, key: plays.current, css: plays.current === 0
50
+ return (jsx(Box, { ref: ref, display: "inline-flex", ...rest, css: plays.current === 0
47
51
  ? css
48
- : mergeCss(shakeX, distance === undefined
52
+ : mergeCss(shakeX(plays.current), distance === undefined
49
53
  ? undefined
50
- : { '--shake-distance': `${distance}px` }, css) }, children));
54
+ : { '--shake-distance': `${distance}px` }, css), children: children }));
51
55
  });
52
56
  Shake.displayName = 'Shake';
53
57
 
@@ -1 +1 @@
1
- {"version":3,"file":"Shake.mjs","sources":["../../../src/components/Shake/Shake.tsx"],"sourcesContent":[null],"names":["_createElement"],"mappings":";;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;MACU,KAAK,GAAG,UAAU,CAC7B,CACE,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACzD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;IAEA,QACEA,aAAA,CAAC,GAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI,EACR,GAAG,EAAE,KAAK,CAAC,OAAO,EAClB,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;AACF,cAAE,QAAQ,CACN,MAAM,EACN,QAAQ,KAAK;AACX,kBAAE;AACF,kBAAE,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAAA,EAAA,CAAI,EAAE,EAC3C,GAAG,CACJ,EAAA,EAGN,QAAQ,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
1
+ {"version":3,"file":"Shake.mjs","sources":["../../../src/components/Shake/Shake.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":";;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;MACU,KAAK,GAAG,UAAU,CAC7B,CACE,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,EAAc,EACzD,GAAkC,KAChC;;;;AAIF,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;AACvB,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;AAChC,IAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,EAAE;AAChC,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,KAAK,CAAC,OAAO,IAAI,CAAC;IACpB;IAEA,QACEA,IAAC,GAAG,EAAA,EACF,GAAG,EAAE,GAAG,EACR,OAAO,EAAC,aAAa,EAAA,GACjB,IAAI,EACR,GAAG,EACD,KAAK,CAAC,OAAO,KAAK;AAChB,cAAE;AACF,cAAE,QAAQ,CACN,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EACrB,QAAQ,KAAK;AACX,kBAAE;AACF,kBAAE,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAAA,EAAA,CAAI,EAAE,EAC3C,GAAG,CACJ,EAAA,QAAA,EAGN,QAAQ,EAAA,CACL;AAEV,CAAC;AAGH,KAAK,CAAC,WAAW,GAAG,OAAO;;;;"}
@@ -15,5 +15,10 @@ import type { SystemStyleObject } from '@chakra-ui/react';
15
15
  * the only signal. Pair it with colour, an icon, or copy that says the same
16
16
  * thing, because for some readers this simply will not happen.
17
17
  */
18
- export declare const shakeX: SystemStyleObject;
18
+ /**
19
+ * A function of the play count — see `pulsePop` for why. The stake is higher
20
+ * here: a shake fires on a refusal, so the remount the earlier version used to
21
+ * replay it would drop the keyboard focus of the person who just got told no.
22
+ */
23
+ export declare const shakeX: (play: number) => SystemStyleObject;
19
24
  //# sourceMappingURL=Shake.styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Shake.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Shake/Shake.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM,EAAE,iBAOpB,CAAC"}
1
+ {"version":3,"file":"Shake.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Shake/Shake.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,iBAOpC,CAAC"}
@@ -17,14 +17,19 @@
17
17
  * the only signal. Pair it with colour, an icon, or copy that says the same
18
18
  * thing, because for some readers this simply will not happen.
19
19
  */
20
- const shakeX = {
21
- animationName: 'shake-x',
20
+ /**
21
+ * A function of the play count — see `pulsePop` for why. The stake is higher
22
+ * here: a shake fires on a refusal, so the remount the earlier version used to
23
+ * replay it would drop the keyboard focus of the person who just got told no.
24
+ */
25
+ const shakeX = (play) => ({
26
+ animationName: play % 2 === 0 ? 'shake-x-alt' : 'shake-x',
22
27
  animationDuration: 'motion.base',
23
28
  animationTimingFunction: 'standard',
24
29
  animationFillMode: 'both',
25
30
  transformOrigin: 'center',
26
31
  _motionReduce: { animationName: 'none' },
27
- };
32
+ });
28
33
 
29
34
  exports.shakeX = shakeX;
30
35
  //# sourceMappingURL=Shake.styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Shake.styles.js","sources":["../../../src/components/Shake/Shake.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;AAeG;AACI,MAAM,MAAM,GAAsB;AACvC,IAAA,aAAa,EAAE,SAAS;AACxB,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,UAAU;AACnC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;;;;;"}
1
+ {"version":3,"file":"Shake.styles.js","sources":["../../../src/components/Shake/Shake.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;AAeG;AACH;;;;AAIG;MACU,MAAM,GAAG,CAAC,IAAY,MAAyB;AAC1D,IAAA,aAAa,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,aAAa,GAAG,SAAS;AACzD,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,UAAU;AACnC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;AACzC,CAAA;;;;"}
@@ -15,14 +15,19 @@
15
15
  * the only signal. Pair it with colour, an icon, or copy that says the same
16
16
  * thing, because for some readers this simply will not happen.
17
17
  */
18
- const shakeX = {
19
- animationName: 'shake-x',
18
+ /**
19
+ * A function of the play count — see `pulsePop` for why. The stake is higher
20
+ * here: a shake fires on a refusal, so the remount the earlier version used to
21
+ * replay it would drop the keyboard focus of the person who just got told no.
22
+ */
23
+ const shakeX = (play) => ({
24
+ animationName: play % 2 === 0 ? 'shake-x-alt' : 'shake-x',
20
25
  animationDuration: 'motion.base',
21
26
  animationTimingFunction: 'standard',
22
27
  animationFillMode: 'both',
23
28
  transformOrigin: 'center',
24
29
  _motionReduce: { animationName: 'none' },
25
- };
30
+ });
26
31
 
27
32
  export { shakeX };
28
33
  //# sourceMappingURL=Shake.styles.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Shake.styles.mjs","sources":["../../../src/components/Shake/Shake.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;AAeG;AACI,MAAM,MAAM,GAAsB;AACvC,IAAA,aAAa,EAAE,SAAS;AACxB,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,UAAU;AACnC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;;;;;"}
1
+ {"version":3,"file":"Shake.styles.mjs","sources":["../../../src/components/Shake/Shake.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;AAeG;AACH;;;;AAIG;MACU,MAAM,GAAG,CAAC,IAAY,MAAyB;AAC1D,IAAA,aAAa,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,aAAa,GAAG,SAAS;AACzD,IAAA,iBAAiB,EAAE,aAAa;AAChC,IAAA,uBAAuB,EAAE,UAAU;AACnC,IAAA,iBAAiB,EAAE,MAAM;AACzB,IAAA,eAAe,EAAE,QAAQ;AACzB,IAAA,aAAa,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;AACzC,CAAA;;;;"}
@@ -7,24 +7,31 @@ import { SwapTransitionProps } from './SwapTransition.types';
7
7
  * The outgoing content leaves *against* the travel direction, so the pair reads
8
8
  * as one movement rather than as two things passing each other.
9
9
  *
10
- * ## The one primitive here that needs React to hold state
10
+ * ## One element, and the children are yours
11
11
  *
12
- * The others are CSS: a keyframe plays on mount, or a changed `key` replays one.
13
- * This cannot be, because the outgoing content has to stay on screen long enough
14
- * to leave, and React has already been told to render the new content. So this
15
- * keeps the previous children in state for exactly one exit, then swaps.
12
+ * Two rules this has to keep, both learned by breaking them:
16
13
  *
17
- * That is a smaller thing than an animation library, and it is the *whole*
18
- * reason `framer-motion` looked necessary for this file — `AnimatePresence` is a
19
- * general answer to the same problem. Here the problem is one element and one
20
- * exit, so the general answer costs more than it returns.
14
+ * **It renders a single box.** Everything the caller passes lands on the element
15
+ * that actually animates, so a wrapper told to fill — `display="flex" flex={1}
16
+ * minH={0}`, the shape a full-height pane needs still fills. An earlier
17
+ * version stacked the two states in a grid cell and put the children one level
18
+ * further in; the sizing props stopped reaching them, and a pane that should
19
+ * have filled its column collapsed to content height with the action bar riding
20
+ * up under it. The two states are mutually exclusive anyway — only ever one of
21
+ * them is on screen — so the cell they were sharing bought nothing.
21
22
  *
22
- * **Adds two elements** the grid that stacks both states in one cell, and the
23
- * `div` that actually slides. Layout props land on the outer one, and `children`
24
- * is no longer a *direct* child of what surrounds it, so animate the inner
25
- * element instead of wrapping when the parent needs its children directly: a
26
- * `<tr>` needs `<td>`s, and Ark parts (`Menu.List`, `Tree`) walk their own
27
- * children for focus and typeahead.
23
+ * **It renders `children` directly, never a copy.** The same earlier version
24
+ * held the subtree in state and rendered that instead, refreshing it from an
25
+ * effect keyed on `transitionKey` so content that changed *without* a new key
26
+ * never reached the screen. A radio that was picked stayed unpicked, and the
27
+ * interface read as dead. A component that stands between a caller and its own
28
+ * children has to be transparent to every render that is not a swap.
29
+ *
30
+ * What it does keep is one snapshot of the outgoing subtree, for as long as the
31
+ * exit takes. That is unavoidable without an animation library: React has
32
+ * already been told to render the new content, and the old content still has to
33
+ * leave. It is fifteen lines and one `setTimeout`, and it is the only reason
34
+ * `framer-motion` looked necessary for this file.
28
35
  *
29
36
  * ```tsx
30
37
  * <SwapTransition transitionKey={step}>
@@ -1 +1 @@
1
- {"version":3,"file":"SwapTransition.d.ts","sourceRoot":"","sources":["../../../src/components/SwapTransition/SwapTransition.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,cAAc,gHA4E1B,CAAC"}
1
+ {"version":3,"file":"SwapTransition.d.ts","sourceRoot":"","sources":["../../../src/components/SwapTransition/SwapTransition.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,cAAc,gHAwE1B,CAAC"}
@@ -15,24 +15,31 @@ var SwapTransition_styles = require('./SwapTransition.styles.js');
15
15
  * The outgoing content leaves *against* the travel direction, so the pair reads
16
16
  * as one movement rather than as two things passing each other.
17
17
  *
18
- * ## The one primitive here that needs React to hold state
18
+ * ## One element, and the children are yours
19
19
  *
20
- * The others are CSS: a keyframe plays on mount, or a changed `key` replays one.
21
- * This cannot be, because the outgoing content has to stay on screen long enough
22
- * to leave, and React has already been told to render the new content. So this
23
- * keeps the previous children in state for exactly one exit, then swaps.
20
+ * Two rules this has to keep, both learned by breaking them:
24
21
  *
25
- * That is a smaller thing than an animation library, and it is the *whole*
26
- * reason `framer-motion` looked necessary for this file — `AnimatePresence` is a
27
- * general answer to the same problem. Here the problem is one element and one
28
- * exit, so the general answer costs more than it returns.
22
+ * **It renders a single box.** Everything the caller passes lands on the element
23
+ * that actually animates, so a wrapper told to fill — `display="flex" flex={1}
24
+ * minH={0}`, the shape a full-height pane needs still fills. An earlier
25
+ * version stacked the two states in a grid cell and put the children one level
26
+ * further in; the sizing props stopped reaching them, and a pane that should
27
+ * have filled its column collapsed to content height with the action bar riding
28
+ * up under it. The two states are mutually exclusive anyway — only ever one of
29
+ * them is on screen — so the cell they were sharing bought nothing.
29
30
  *
30
- * **Adds two elements** the grid that stacks both states in one cell, and the
31
- * `div` that actually slides. Layout props land on the outer one, and `children`
32
- * is no longer a *direct* child of what surrounds it, so animate the inner
33
- * element instead of wrapping when the parent needs its children directly: a
34
- * `<tr>` needs `<td>`s, and Ark parts (`Menu.List`, `Tree`) walk their own
35
- * children for focus and typeahead.
31
+ * **It renders `children` directly, never a copy.** The same earlier version
32
+ * held the subtree in state and rendered that instead, refreshing it from an
33
+ * effect keyed on `transitionKey` so content that changed *without* a new key
34
+ * never reached the screen. A radio that was picked stayed unpicked, and the
35
+ * interface read as dead. A component that stands between a caller and its own
36
+ * children has to be transparent to every render that is not a swap.
37
+ *
38
+ * What it does keep is one snapshot of the outgoing subtree, for as long as the
39
+ * exit takes. That is unavoidable without an animation library: React has
40
+ * already been told to render the new content, and the old content still has to
41
+ * leave. It is fifteen lines and one `setTimeout`, and it is the only reason
42
+ * `framer-motion` looked necessary for this file.
36
43
  *
37
44
  * ```tsx
38
45
  * <SwapTransition transitionKey={step}>
@@ -41,33 +48,33 @@ var SwapTransition_styles = require('./SwapTransition.styles.js');
41
48
  * ```
42
49
  */
43
50
  const SwapTransition = React.forwardRef(({ transitionKey, distance = 24, animateInitial = false, children, css, ...rest }, ref) => {
44
- const [shown, setShown] = React.useState({ key: transitionKey, node: children, play: animateInitial ? 1 : 0 });
45
- const [leaving, setLeaving] = React.useState(null);
46
- // Read inside the effect rather than depended upon: `children` is a new
47
- // object every render, and listing it would restart the swap continuously.
48
- const latest = React.useRef(children);
49
- latest.current = children;
51
+ const [phase, setPhase] = React.useState({ key: transitionKey, leaving: null, play: animateInitial ? 1 : 0 });
52
+ // The subtree as last committed, held only to hand to the exit. It is never
53
+ // rendered in place of `children` see the note above.
54
+ const committed = React.useRef(children);
55
+ // Derived during render rather than in an effect, deliberately: the exit has
56
+ // to begin in the same commit the key changed, or the incoming content
57
+ // paints for a frame before the outgoing has started to leave.
58
+ if (phase.key !== transitionKey) {
59
+ setPhase({
60
+ key: transitionKey,
61
+ leaving: committed.current,
62
+ play: phase.play + 1,
63
+ });
64
+ }
65
+ // No dependency array: this has to see every commit, because the snapshot it
66
+ // keeps is "whatever was on screen last", not "whatever was there when the
67
+ // key last changed".
50
68
  React.useEffect(() => {
51
- if (transitionKey === shown.key) {
52
- // Same content, re-rendered — update in place without replaying.
53
- setShown((s) => ({ ...s, node: latest.current }));
69
+ if (phase.leaving === null) {
70
+ committed.current = children;
54
71
  return;
55
72
  }
56
- setLeaving(shown.node);
57
- const timer = setTimeout(() => {
58
- setLeaving(null);
59
- setShown((s) => ({
60
- key: transitionKey,
61
- node: latest.current,
62
- play: s.play + 1,
63
- }));
64
- }, SwapTransition_styles.SWAP_OUT_MS);
73
+ const timer = setTimeout(() => setPhase((p) => ({ ...p, leaving: null })), SwapTransition_styles.SWAP_OUT_MS);
65
74
  return () => clearTimeout(timer);
66
- // `shown` is deliberately absent from the deps: the effect *sets* it, and
67
- // depending on it would start a second swap out of the first one's own
68
- // result.
69
- }, [transitionKey]);
70
- return (jsxRuntime.jsxs(react.Box, { ref: ref, display: "grid", ...rest, css: mergeCss.mergeCss({ '--swap-distance': `${distance}px` }, css), children: [leaving !== null && (jsxRuntime.jsx(react.Box, { gridArea: "1 / 1", css: SwapTransition_styles.swapOut, "aria-hidden": true, pointerEvents: "none", children: leaving })), leaving === null && (jsxRuntime.jsx(react.Box, { gridArea: "1 / 1", css: shown.play === 0 ? undefined : SwapTransition_styles.swapIn, children: shown.node }, shown.play))] }));
75
+ });
76
+ const exiting = phase.leaving !== null;
77
+ return (jsxRuntime.jsx(react.Box, { ref: ref, ...rest, "aria-hidden": exiting || undefined, pointerEvents: exiting ? 'none' : undefined, css: mergeCss.mergeCss({ '--swap-distance': `${distance}px` }, exiting ? SwapTransition_styles.swapOut : phase.play === 0 ? undefined : SwapTransition_styles.swapIn, css), children: exiting ? phase.leaving : children }));
71
78
  });
72
79
  SwapTransition.displayName = 'SwapTransition';
73
80