From 87d9d73cf03608ee4267b59a2977d4f0e818ca7b Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 20 Mar 2024 15:17:31 +0100 Subject: [PATCH 01/45] added basic sandbox environment to GH docs (minimal copy of Reanimated docs) --- .../{states-events.md => states-events.mdx} | 19 +-- docs/src/components/AnimableIcon/index.tsx | 40 +++++ .../components/AnimableIcon/styles.module.css | 28 ++++ docs/src/components/CollapseButton/index.tsx | 33 +++++ .../CollapseButton/styles.module.css | 29 ++++ docs/src/components/CollapsibleCode/index.tsx | 36 +++++ .../CollapsibleCode/styles.module.css | 15 ++ .../InteractiveExampleComponent/index.tsx | 28 ++++ .../styles.module.css | 15 ++ .../components/InteractiveExample/index.tsx | 121 +++++++++++++++ .../InteractiveExample/styles.module.css | 140 ++++++++++++++++++ .../components/ReducedMotionWarning/index.tsx | 27 ++++ .../ReducedMotionWarning/styles.module.css | 22 +++ docs/src/examples/PanGesture.jsx | 70 +++++++++ docs/src/theme/MDXComponents.js | 13 ++ docs/static/img/copy-dark.svg | 4 + docs/static/img/copy.svg | 4 + docs/static/img/reset-dark.svg | 6 + docs/static/img/reset.svg | 6 + 19 files changed, 647 insertions(+), 9 deletions(-) rename docs/docs/fundamentals/{states-events.md => states-events.mdx} (85%) create mode 100644 docs/src/components/AnimableIcon/index.tsx create mode 100644 docs/src/components/AnimableIcon/styles.module.css create mode 100644 docs/src/components/CollapseButton/index.tsx create mode 100644 docs/src/components/CollapseButton/styles.module.css create mode 100644 docs/src/components/CollapsibleCode/index.tsx create mode 100644 docs/src/components/CollapsibleCode/styles.module.css create mode 100644 docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx create mode 100644 docs/src/components/InteractiveExample/InteractiveExampleComponent/styles.module.css create mode 100644 docs/src/components/InteractiveExample/index.tsx create mode 100644 docs/src/components/InteractiveExample/styles.module.css create mode 100644 docs/src/components/ReducedMotionWarning/index.tsx create mode 100644 docs/src/components/ReducedMotionWarning/styles.module.css create mode 100644 docs/src/examples/PanGesture.jsx create mode 100644 docs/src/theme/MDXComponents.js create mode 100644 docs/static/img/copy-dark.svg create mode 100644 docs/static/img/copy.svg create mode 100644 docs/static/img/reset-dark.svg create mode 100644 docs/static/img/reset.svg diff --git a/docs/docs/fundamentals/states-events.md b/docs/docs/fundamentals/states-events.mdx similarity index 85% rename from docs/docs/fundamentals/states-events.md rename to docs/docs/fundamentals/states-events.mdx index 39347ebe5b..f85b7324dc 100644 --- a/docs/docs/fundamentals/states-events.md +++ b/docs/docs/fundamentals/states-events.mdx @@ -36,19 +36,20 @@ A gesture can be in one of the six possible states: ## State flows -The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. - -The flow looks as follows (longer arrows represent that there are possibly more touch events received before the state changes): +import PanGesture from '@site/src/examples/PanGesture'; +import PanGestureSrc from '!!raw-loader!@site/src/examples/PanGesture'; -[`UNDETERMINED`](#undetermined) -> [`BEGAN`](#began) ------> [`ACTIVE`](#active) ------> [`END`](#end) -> [`UNDETERMINED`](#undetermined) - -Another possible flow is when a handler receives touches that cause a recognition failure: +The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. -[`UNDETERMINED`](#undetermined) -> [`BEGAN`](#began) ------> [`FAILED`](#failed) -> [`UNDETERMINED`](#undetermined) +The flow looks as follows: -At last, when a handler does properly recognize the gesture but then is interrupted by the touch system the gesture recognition is canceled and the flow looks as follows: + -[`UNDETERMINED`](#undetermined) -> [`BEGAN`](#began) ------> [`ACTIVE`](#active) ------> [`CANCELLED`](#cancelled) -> [`UNDETERMINED`](#undetermined) +} + label="Grab and drag the circle" +/> ## Events diff --git a/docs/src/components/AnimableIcon/index.tsx b/docs/src/components/AnimableIcon/index.tsx new file mode 100644 index 0000000000..e9cd764db5 --- /dev/null +++ b/docs/src/components/AnimableIcon/index.tsx @@ -0,0 +1,40 @@ +import React, { useEffect } from 'react'; +import styles from './styles.module.css'; +import clsx from 'clsx'; +import { useColorMode } from '@docusaurus/theme-common'; + +export const Animation = { + FADE_IN_OUT: styles.iconClicked, +}; + +interface Props { + icon: JSX.Element; + iconDark?: JSX.Element; + animation: string; + onClick: (actionPerformed, setActionPerformed) => void; +} + +const AnimableIcon = ({ + icon, + iconDark, + animation = Animation.FADE_IN_OUT, + onClick, +}: Props): JSX.Element => { + const { colorMode } = useColorMode(); + const [actionPerformed, setActionPerformed] = React.useState(false); + + useEffect(() => { + const timeout = setTimeout(() => setActionPerformed(() => false), 1000); + return () => clearTimeout(timeout); + }, [actionPerformed]); + + return ( +
onClick(actionPerformed, setActionPerformed)} + className={clsx(styles.actionIcon, actionPerformed && animation)}> + {colorMode === 'light' ? icon : iconDark || icon} +
+ ); +}; + +export default AnimableIcon; diff --git a/docs/src/components/AnimableIcon/styles.module.css b/docs/src/components/AnimableIcon/styles.module.css new file mode 100644 index 0000000000..0806c84be6 --- /dev/null +++ b/docs/src/components/AnimableIcon/styles.module.css @@ -0,0 +1,28 @@ +.actionIcon { + display: flex; + align-items: center; + justify-content: center; + + padding: 0.25em; + cursor: pointer; + + /* Border applied to omit enlarging icon during the animation. */ + border: 1px solid transparent; + border-radius: 3px; +} + +.iconClicked { + animation: 1s iconClick; +} + +@keyframes iconClick { + 0% { + border: 1px solid var(--swm-interactive-copy-button-off); + } + 50% { + border: 1px solid var(--swm-interactive-copy-button-on); + } + 100% { + border: 1px solid var(--swm-interactive-copy-button-off); + } +} diff --git a/docs/src/components/CollapseButton/index.tsx b/docs/src/components/CollapseButton/index.tsx new file mode 100644 index 0000000000..8f035ba526 --- /dev/null +++ b/docs/src/components/CollapseButton/index.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import styles from './styles.module.css'; +import Arrow from '@site/static/img/Arrow.svg'; +import ArrowDark from '@site/static/img/Arrow-dark.svg'; +import { useColorMode } from '@docusaurus/theme-common'; +import clsx from 'clsx'; + +const CollapseButton: React.FC<{ + label: string; + labelCollapsed: string; + collapsed: boolean; + onCollapse: () => void; + className?: string; +}> = ({ label, labelCollapsed, collapsed, onCollapse, className }) => { + const { colorMode } = useColorMode(); + + return ( +
onCollapse()}> + {colorMode === 'light' ? ( + + ) : ( + + )} + + +
+ ); +}; + +export default CollapseButton; diff --git a/docs/src/components/CollapseButton/styles.module.css b/docs/src/components/CollapseButton/styles.module.css new file mode 100644 index 0000000000..908df5fa09 --- /dev/null +++ b/docs/src/components/CollapseButton/styles.module.css @@ -0,0 +1,29 @@ +.collapseButton { + display: flex; + align-items: center; + cursor: pointer; +} + +.collapseButton button { + background-color: transparent; + border: none; + padding: 0; + + font-family: var(--swm-body-font); + font-size: 16px; + color: var(--ifm-font-color-base); + cursor: pointer; +} + +.arrow { + height: 12px; + width: 12px; + margin-right: 1rem; + margin-top: 2px; + + transition: var(--swm-expandable-transition); +} + +.collapseButton[data-collapsed='false'] .arrow { + transform: rotate(180deg); +} diff --git a/docs/src/components/CollapsibleCode/index.tsx b/docs/src/components/CollapsibleCode/index.tsx new file mode 100644 index 0000000000..a66c24d1c9 --- /dev/null +++ b/docs/src/components/CollapsibleCode/index.tsx @@ -0,0 +1,36 @@ +import React, { useState } from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import styles from './styles.module.css'; + +import CollapseButton from '@site/src/components/CollapseButton'; + +interface Props { + src: string; + showLines: number[]; +} + +export default function CollapsibleCode({ src, showLines }: Props) { + const [collapsed, setCollapsed] = useState(true); + + if (!showLines) { + return {src}; + } + + const [start, end] = showLines; + + const codeLines = src.split('\n'); + const linesToShow = codeLines.slice(start, end + 1).join('\n'); + + return ( +
+ setCollapsed(!collapsed)} + className={styles.collapseButton} + /> + {collapsed ? linesToShow : src} +
+ ); +} diff --git a/docs/src/components/CollapsibleCode/styles.module.css b/docs/src/components/CollapsibleCode/styles.module.css new file mode 100644 index 0000000000..1502e735e7 --- /dev/null +++ b/docs/src/components/CollapsibleCode/styles.module.css @@ -0,0 +1,15 @@ +.container { + background-color: var(--swm-off-background); + border-radius: 0; + border: 1px solid var(--swm-border); + margin-bottom: 1em; +} + +.container pre, +.container code { + border: none; +} + +.collapseButton { + padding: 1em 0 0 1em; +} diff --git a/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx b/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx new file mode 100644 index 0000000000..15a4d3f8b7 --- /dev/null +++ b/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import clsx from 'clsx'; + +import BrowserOnly from '@docusaurus/BrowserOnly'; +import styles from './styles.module.css'; + +interface Props { + component: React.ReactNode; + label?: string; + idx?: number; +} + +export default function InteractiveExampleComponent({ + component, + label, + idx, +}: Props) { + return ( + Loading...}> + {() => ( +
+ {component} + {label &&
{label}
} +
+ )} +
+ ); +} diff --git a/docs/src/components/InteractiveExample/InteractiveExampleComponent/styles.module.css b/docs/src/components/InteractiveExample/InteractiveExampleComponent/styles.module.css new file mode 100644 index 0000000000..eec1a5a103 --- /dev/null +++ b/docs/src/components/InteractiveExample/InteractiveExampleComponent/styles.module.css @@ -0,0 +1,15 @@ +.container { + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + contain: content; + align-items: center; + background-color: none; + margin-bottom: var(--ifm-leading); +} + +.label { + text-align: center; + font-size: 1rem; +} diff --git a/docs/src/components/InteractiveExample/index.tsx b/docs/src/components/InteractiveExample/index.tsx new file mode 100644 index 0000000000..595eab2e42 --- /dev/null +++ b/docs/src/components/InteractiveExample/index.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useCopyToClipboard } from 'usehooks-ts'; + +import BrowserOnly from '@docusaurus/BrowserOnly'; +import CodeBlock from '@theme/CodeBlock'; +import AnimableIcon, { Animation } from '@site/src/components/AnimableIcon'; +import { useReducedMotion } from 'react-native-reanimated'; +import ReducedMotionWarning from '../ReducedMotionWarning'; + +import Copy from '@site/static/img/copy.svg'; +import CopyDark from '@site/static/img/copy-dark.svg'; +import Reset from '@site/static/img/reset.svg'; +import ResetDark from '@site/static/img/reset-dark.svg'; + +import styles from './styles.module.css'; + +interface Props { + src: string; + component: React.ReactNode; + label?: string; + showCode?: boolean; // whether to show code by default + larger?: boolean; // should the view be enlarged? +} + +export default function InteractiveExample({ + src, + component, + label, + showCode = false, + larger = false, +}: Props) { + const [_, copy] = useCopyToClipboard(); + const [key, setKey] = React.useState(0); + const [showPreview, setShowPreview] = React.useState(!showCode); + + const resetExample = () => { + setKey(key + 1); + }; + + const prefersReducedMotion = useReducedMotion(); + + return ( + Loading...}> + {() => ( +
+ {showPreview && prefersReducedMotion && } +
+
+ + +
+ } + iconDark={} + animation={Animation.FADE_IN_OUT} + onClick={(actionPerformed, setActionPerformed) => { + if (!actionPerformed) { + copy(src); + setActionPerformed(true); + } + }} + /> +
+
+ {showPreview ? ( + <> + {component} + +
+
+ {label &&
{label}
} + } + iconDark={} + animation={Animation.FADE_IN_OUT} + onClick={(actionPerformed, setActionPerformed) => { + if (!actionPerformed) { + resetExample(); + setActionPerformed(true); + } + }} + /> +
+ + ) : ( +
+ {src} +
+ )} +
+
+ )} + + ); +} diff --git a/docs/src/components/InteractiveExample/styles.module.css b/docs/src/components/InteractiveExample/styles.module.css new file mode 100644 index 0000000000..c0b783e99f --- /dev/null +++ b/docs/src/components/InteractiveExample/styles.module.css @@ -0,0 +1,140 @@ +.container { + display: flex; + flex-direction: column; + + contain: content; + + background-color: var(--swm-off-background); + border: 1px solid var(--swm-border); + margin-bottom: var(--ifm-leading); +} + +.largerContainer { + min-height: 400px; +} + +/* Preferred height in code section of container. */ +.container[data-ispreview='false'] { + height: 400px; +} + +/* Classes used to omit default docusaurus styling. */ +[class*='codeBlockContainer'] { + box-shadow: none; +} + +.interactiveCodeBlock [class*='codeBlockContent'] pre { + border: none; +} + +.interactiveCodeBlock [class*='codeBlockContent'] code { + background-color: var(--swm-off-background); + width: 100%; + padding: 0; + border: none; +} + +/* Hide default action buttons, displayed by Docusaurus */ +.interactiveCodeBlock [class*='buttonGroup'] { + display: none; +} + +.code { + flex: 1; +} + +.buttonsContainer { + display: flex; + justify-content: flex-end; + align-items: center; + + padding: 0.25em 0.75em 0.25em 1.25em; + margin: 1.5em 2em 1.5em 1.5em; +} + +.upperButtonsContainer { + margin-bottom: 10px; +} + +@media (max-width: 996px) { + .upperButtonsContainer { + justify-content: center; + + margin: 1.5em 0; + } +} + +.lowerButtonsContainer { + justify-content: space-between; + margin-top: 10px; +} + +.iconStub { + width: 30px; + height: 30px; +} + +.container[data-ispreview='false'] .buttonsContainer { + position: absolute; + right: 0; + top: 0; + + border-radius: 25px; + background-color: var(--swm-code-lines-buttons-background); + + z-index: 1; +} + +@media (max-width: 996px) { + .container[data-ispreview='false'] .buttonsContainer { + position: relative; + width: fit-content; + margin: 1.5em auto; + } +} + +.previewContainer { + flex: 1 1 auto; +} + +/* Style preview only when user is in the 'code' section. */ +.container[data-ispreview='false'] .previewContainer { + flex: 1 1 auto; + overflow-y: auto; + + padding: 0 24px; + margin: 16px 8px 8px 0; +} + +@media (max-width: 996px) { + .container[data-ispreview='false'] .previewContainer { + margin-top: 0; + } +} + +.actionButton { + margin-right: 0.5em; + padding: 0 0 2px 0; + + border: none; + background-color: inherit; + color: var(--swm-interactive-button-color); + cursor: pointer; + + font-family: var(--swm-body-font); + font-weight: 500; + font-size: 16px; +} + +.actionButton:last-of-type { + margin-right: 2em; +} + +.actionButtonActive { + color: var(--swm-interactive-button-active); + border-bottom: 1px solid var(--swm-interactive-button-active); +} + +.label { + /* position: absolute; */ +} diff --git a/docs/src/components/ReducedMotionWarning/index.tsx b/docs/src/components/ReducedMotionWarning/index.tsx new file mode 100644 index 0000000000..a958d0b2aa --- /dev/null +++ b/docs/src/components/ReducedMotionWarning/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import styles from './styles.module.css'; +import Link from '@docusaurus/Link'; + +import { useColorMode } from '@docusaurus/theme-common'; +import Danger from '/static/img/danger.svg'; +import DangerDark from '/static/img/danger-dark.svg'; + +export default function ReducedMotionWarning() { + const { colorMode } = useColorMode(); + return ( +
+
+ {colorMode === 'light' ? : } +
+

+ It looks like reduced motion is turned on in your system preferences. + Some of the animations may be skipped.{' '} + + Learn more + +

+
+ ); +} diff --git a/docs/src/components/ReducedMotionWarning/styles.module.css b/docs/src/components/ReducedMotionWarning/styles.module.css new file mode 100644 index 0000000000..fe557ddc40 --- /dev/null +++ b/docs/src/components/ReducedMotionWarning/styles.module.css @@ -0,0 +1,22 @@ +.dangerMark { + display: flex; + padding: 0.125rem 0.75rem; +} + +.link { + white-space: nowrap; + font-size: 14px; + font-weight: 500; + padding: 0.125rem 0.375rem; +} + +.warningText { + margin: 0.125rem 0; +} + +.container { + display: flex; + align-items: center; + justify-content: center; + background-color: var(--swm-admonition-color-caution); +} diff --git a/docs/src/examples/PanGesture.jsx b/docs/src/examples/PanGesture.jsx new file mode 100644 index 0000000000..bcd09d463d --- /dev/null +++ b/docs/src/examples/PanGesture.jsx @@ -0,0 +1,70 @@ +import 'react-native-gesture-handler'; +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from 'react-native-reanimated'; +import { + Gesture, + GestureDetector, + GestureHandlerRootView, +} from 'react-native-gesture-handler'; + +export default function App() { + const pressed = useSharedValue(false); + // highlight-next-line + const offset = useSharedValue(0); + + const pan = Gesture.Pan() + .onBegin(() => { + pressed.value = true; + }) + // highlight-start + .onChange((event) => { + offset.value = event.translationX; + }) + // highlight-end + .onFinalize(() => { + // highlight-next-line + offset.value = withSpring(0); + pressed.value = false; + }); + + const animatedStyles = useAnimatedStyle(() => ({ + transform: [ + // highlight-next-line + { translateX: offset.value }, + { scale: withTiming(pressed.value ? 1.2 : 1) }, + ], + backgroundColor: pressed.value ? '#FFE04B' : '#b58df1', + })); + + return ( + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + height: '100%', + }, + circle: { + height: 120, + width: 120, + backgroundColor: '#b58df1', + borderRadius: 500, + cursor: 'grab', + }, +}); diff --git a/docs/src/theme/MDXComponents.js b/docs/src/theme/MDXComponents.js new file mode 100644 index 0000000000..ffd713462a --- /dev/null +++ b/docs/src/theme/MDXComponents.js @@ -0,0 +1,13 @@ +// Import the original mapper +import MDXComponents from '@theme-original/MDXComponents'; +import InteractiveExample from '@site/src/components/InteractiveExample'; +import CollapsibleCode from '@site/src/components/CollapsibleCode'; + +export default { + // Re-use the default mapping + ...MDXComponents, + // Map the "" tag to our Highlight component + // `Highlight` will receive all props that were passed to `` in MDX + InteractiveExample, + CollapsibleCode, +}; diff --git a/docs/static/img/copy-dark.svg b/docs/static/img/copy-dark.svg new file mode 100644 index 0000000000..44ebb8a593 --- /dev/null +++ b/docs/static/img/copy-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/static/img/copy.svg b/docs/static/img/copy.svg new file mode 100644 index 0000000000..0598022092 --- /dev/null +++ b/docs/static/img/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/static/img/reset-dark.svg b/docs/static/img/reset-dark.svg new file mode 100644 index 0000000000..f792dab564 --- /dev/null +++ b/docs/static/img/reset-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/static/img/reset.svg b/docs/static/img/reset.svg new file mode 100644 index 0000000000..5995887149 --- /dev/null +++ b/docs/static/img/reset.svg @@ -0,0 +1,6 @@ + + + + + + From ce70c6de915639aa03d599c76a100d28a8940cf4 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 20 Mar 2024 17:01:34 +0100 Subject: [PATCH 02/45] use composed tap + pan component for flow chart --- docs/docs/fundamentals/states-events.mdx | 19 +++++--- .../{PanGesture.jsx => ComposedGesture.jsx} | 44 +++++++++++++------ 2 files changed, 44 insertions(+), 19 deletions(-) rename docs/src/examples/{PanGesture.jsx => ComposedGesture.jsx} (54%) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index f85b7324dc..28d644cd2a 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -36,21 +36,28 @@ A gesture can be in one of the six possible states: ## State flows -import PanGesture from '@site/src/examples/PanGesture'; -import PanGestureSrc from '!!raw-loader!@site/src/examples/PanGesture'; +import ComposedGesture from '@site/src/examples/ComposedGesture'; +import ComposedGestureSrc from '!!raw-loader!@site/src/examples/ComposedGesture'; The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. The flow looks as follows: - + } - label="Grab and drag the circle" + src={ComposedGestureSrc} + component={} + label="Drag or tap the circle" /> + + ## Events There are three types of events in RNGH2: `StateChangeEvent`, `GestureEvent` and `PointerEvent`. The `StateChangeEvent` is send every time a gesture moves to a different state, while `GestureEvent` is send every time a gesture is updated. The first two carry a gesture-specific data and a `state` property, indicating the current state of the gesture. `StateChangeEvent` also carries a `oldState` property indicating the previous state of the gesture. `PointerEvent` carries information about raw touch events, like touching the screen or moving the finger. These events are handled internally before they are passed along to the correct callbacks: diff --git a/docs/src/examples/PanGesture.jsx b/docs/src/examples/ComposedGesture.jsx similarity index 54% rename from docs/src/examples/PanGesture.jsx rename to docs/src/examples/ComposedGesture.jsx index bcd09d463d..ca3608590a 100644 --- a/docs/src/examples/PanGesture.jsx +++ b/docs/src/examples/ComposedGesture.jsx @@ -1,11 +1,12 @@ import 'react-native-gesture-handler'; import React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { Easing, StyleSheet, View } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming, + withSequence, } from 'react-native-reanimated'; import { Gesture, @@ -15,38 +16,55 @@ import { export default function App() { const pressed = useSharedValue(false); - // highlight-next-line + const tapped = useSharedValue(false); + const offset = useSharedValue(0); + const scale = useSharedValue(1); + // highlight-start const pan = Gesture.Pan() .onBegin(() => { pressed.value = true; }) - // highlight-start + .onStart(() => { + scale.value = withSpring(0.6, { duration: 200 }); + }) .onChange((event) => { offset.value = event.translationX; }) - // highlight-end .onFinalize(() => { - // highlight-next-line offset.value = withSpring(0); + scale.value = withTiming(1); pressed.value = false; }); + const tap = Gesture.Tap() + .onBegin(() => { + tapped.value = true; + }) + .onStart(() => { + scale.value = withSequence( + withSpring(1.8, { duration: 80, easing: Easing.ease }), + withSpring(1, { duration: 160, easing: Easing.ease }) + ); + }) + .onFinalize(() => { + tapped.value = false; + }); + // highlight-end + const animatedStyles = useAnimatedStyle(() => ({ - transform: [ - // highlight-next-line - { translateX: offset.value }, - { scale: withTiming(pressed.value ? 1.2 : 1) }, - ], - backgroundColor: pressed.value ? '#FFE04B' : '#b58df1', + transform: [{ translateX: offset.value }, { scale: scale.value }], + backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', })); return ( - - + + + + From 23c0859a3c1c7ff343acb9d468a2108b9174226f Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 10:41:58 +0100 Subject: [PATCH 03/45] first working version --- docs/docs/fundamentals/states-events.mdx | 10 +- docs/src/examples/ChartElement.tsx | 52 ++++++++ docs/src/examples/ChartManager.ts | 72 +++++++++++ docs/src/examples/ComposedGesture.jsx | 88 ------------- docs/src/examples/ComposedGesture.tsx | 150 +++++++++++++++++++++++ docs/src/examples/FlowChart.tsx | 64 ++++++++++ 6 files changed, 339 insertions(+), 97 deletions(-) create mode 100644 docs/src/examples/ChartElement.tsx create mode 100644 docs/src/examples/ChartManager.ts delete mode 100644 docs/src/examples/ComposedGesture.jsx create mode 100644 docs/src/examples/ComposedGesture.tsx create mode 100644 docs/src/examples/FlowChart.tsx diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index 28d644cd2a..5a5856f310 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -43,21 +43,13 @@ The most typical flow of state is when a gesture picks up on an initial touch ev The flow looks as follows: - - } label="Drag or tap the circle" + larger={true} /> - - ## Events There are three types of events in RNGH2: `StateChangeEvent`, `GestureEvent` and `PointerEvent`. The `StateChangeEvent` is send every time a gesture moves to a different state, while `GestureEvent` is send every time a gesture is updated. The first two carry a gesture-specific data and a `state` property, indicating the current state of the gesture. `StateChangeEvent` also carries a `oldState` property indicating the previous state of the gesture. `PointerEvent` carries information about raw touch events, like touching the screen or moving the finger. These events are handled internally before they are passed along to the correct callbacks: diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx new file mode 100644 index 0000000000..b83d3a3c97 --- /dev/null +++ b/docs/src/examples/ChartElement.tsx @@ -0,0 +1,52 @@ +import { Grid } from '@mui/material'; +import React, { LegacyRef, Ref } from 'react'; +import { StyleProp, StyleSheet, View, ViewStyle, Text } from 'react-native'; +import { State } from './ChartManager'; + +type ChartElementProps = { + id: number; + state: State; + label?: string; // optional subtext + position?: null; // todo + innerRef?: LegacyRef; + style?: StyleProp; +}; + +export default function App({ + id, + state, + label, // optional subtext + position, // todo + innerRef, + style, +}: ChartElementProps) { + return ( + + + {state} + + {label} + + ); +} + +const styles = StyleSheet.create({ + box: { + flex: 1, + flexDirection: 'column', + textAlign: 'center', + maxWidth: 270, + }, + element: { + padding: 30, + backgroundColor: '#b58df1', + }, + header: { + fontWeight: '500', + fontSize: 22, + }, + subtext: { + fontWeight: '300', + fontSize: 14, + }, +}); diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts new file mode 100644 index 0000000000..afc7a92298 --- /dev/null +++ b/docs/src/examples/ChartManager.ts @@ -0,0 +1,72 @@ +//import ChartElement from './ChartElement'; + +import { useMemo } from 'react'; + +export enum State { + UNDETERMINED = 'UNDETERMINED', + FAILED = 'FAILED', + BEGAN = 'BEGAN', + CANCELLED = 'CANCELLED', + ACTIVE = 'ACTIVE', + END = 'END', +} + +type ChartElement = { + id: number; + state: State; + label?: string; // optional subtext + position?: null; // todo +}; + +class ChartConnection { + id: number; + from: number; + to: number; +} + +export default class ChartManager { + private _elements: ChartElement[] = []; + private _connections: ChartConnection[] = []; + private _listeners: Map void)[]> = useMemo( + () => new Map(), + [] + ); + + get elements(): ChartElement[] { + return this._elements; + } + + get connections(): ChartConnection[] { + return this._connections; + } + + public addListener(id: number, listener: (isActive: boolean) => void): void { + if (this._listeners.has(id)) this._listeners.get(id).push(listener); + else this._listeners.set(id, [listener]); + } + + public addElement( + state: State, + label: string | null = null + ): [(isActive: boolean) => void, number] { + const newId = this._elements.length; + const newChartElement = { + id: newId, + label: label, + state: state, + position: null, + }; + + this._elements.push(newChartElement); + + // this callback will be used by a .onX hook to broadcast this event to all listeners + return [ + (isActive: boolean) => { + this._listeners.get(newId)?.forEach((listener) => listener(isActive)); + }, + newId, + ]; + } + + public addConnection(fromId: number, toId: number) {} +} diff --git a/docs/src/examples/ComposedGesture.jsx b/docs/src/examples/ComposedGesture.jsx deleted file mode 100644 index ca3608590a..0000000000 --- a/docs/src/examples/ComposedGesture.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import 'react-native-gesture-handler'; -import React from 'react'; -import { Easing, StyleSheet, View } from 'react-native'; -import Animated, { - useAnimatedStyle, - useSharedValue, - withSpring, - withTiming, - withSequence, -} from 'react-native-reanimated'; -import { - Gesture, - GestureDetector, - GestureHandlerRootView, -} from 'react-native-gesture-handler'; - -export default function App() { - const pressed = useSharedValue(false); - const tapped = useSharedValue(false); - - const offset = useSharedValue(0); - const scale = useSharedValue(1); - - // highlight-start - const pan = Gesture.Pan() - .onBegin(() => { - pressed.value = true; - }) - .onStart(() => { - scale.value = withSpring(0.6, { duration: 200 }); - }) - .onChange((event) => { - offset.value = event.translationX; - }) - .onFinalize(() => { - offset.value = withSpring(0); - scale.value = withTiming(1); - pressed.value = false; - }); - - const tap = Gesture.Tap() - .onBegin(() => { - tapped.value = true; - }) - .onStart(() => { - scale.value = withSequence( - withSpring(1.8, { duration: 80, easing: Easing.ease }), - withSpring(1, { duration: 160, easing: Easing.ease }) - ); - }) - .onFinalize(() => { - tapped.value = false; - }); - // highlight-end - - const animatedStyles = useAnimatedStyle(() => ({ - transform: [{ translateX: offset.value }, { scale: scale.value }], - backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', - })); - - return ( - - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - height: '100%', - }, - circle: { - height: 120, - width: 120, - backgroundColor: '#b58df1', - borderRadius: 500, - cursor: 'grab', - }, -}); diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx new file mode 100644 index 0000000000..0c87da4275 --- /dev/null +++ b/docs/src/examples/ComposedGesture.tsx @@ -0,0 +1,150 @@ +import 'react-native-gesture-handler'; +import React, { useEffect, useMemo, useRef } from 'react'; +import { Easing, StyleSheet, View } from 'react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, + withSequence, + withDelay, +} from 'react-native-reanimated'; +import { + Gesture, + GestureDetector, + GestureHandlerRootView, +} from 'react-native-gesture-handler'; +import ChartManager, { State } from './ChartManager'; +import FlowChart from './FlowChart'; + +export default function App() { + const chartManager = useRef(new ChartManager()); + const [undeterminedCallback, undeterminedId] = useMemo( + () => + chartManager.current.addElement( + State.UNDETERMINED, + 'This is the default state' + ), + [chartManager] + ); + + const [beganCallback, beganId] = useMemo( + () => chartManager.current.addElement(State.BEGAN), + [chartManager] + ); + + const [activeCallback, activeId] = useMemo( + () => chartManager.current.addElement(State.ACTIVE), + [chartManager] + ); + + const [endCallback, endId] = useMemo( + () => chartManager.current.addElement(State.END), + [chartManager] + ); + + const [failedCallback, failedId] = useMemo( + () => chartManager.current.addElement(State.FAILED), + [chartManager] + ); + + const [cancelledCallback, cancelledId] = useMemo( + () => + chartManager.current.addElement( + State.CANCELLED, + 'This is some sample text' + ), + [chartManager] + ); + + const pressed = useSharedValue(false); + + const offset = useSharedValue(0); + const scale = useSharedValue(1); + + undeterminedCallback(true); + + // highlight-start + const pan = Gesture.Pan() + .onBegin(() => { + pressed.value = true; + beganCallback(true); + undeterminedCallback(false); + }) + .onStart(() => { + scale.value = withSpring(0.6, { duration: 200 }); + beganCallback(false); + activeCallback(true); + }) + .onEnd(() => { + endCallback(true); + }) + .onFinalize((_, success) => { + beganCallback(false); + activeCallback(false); + if (!success) { + failedCallback(true); + cancelledCallback(true); + } + setTimeout(() => { + endCallback(false); + failedCallback(false); + cancelledCallback(false); + undeterminedCallback(true); + }, 200); + offset.value = withSpring(0); + scale.value = withTiming(1); + pressed.value = false; + }) + .onUpdate((event) => { + offset.value = event.translationX; + }); + const tap = Gesture.Tap().onStart(() => { + scale.value = withSequence( + withSpring(1.8, { duration: 70 }), + withSpring(1, { duration: 140, dampingRatio: 0.4 }) + ); + }); + // highlight-end + + const composed = Gesture.Race(pan, tap); + + const animatedStyles = useAnimatedStyle(() => ({ + transform: [{ translateX: offset.value }, { scale: scale.value }], + backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', + })); + + return ( + <> + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + chartContainer: { + marginBottom: 60, + }, + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + height: '100%', + }, + circle: { + height: 120, + width: 120, + backgroundColor: '#b58df1', + borderRadius: 500, + cursor: 'grab', + }, +}); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx new file mode 100644 index 0000000000..98471318fc --- /dev/null +++ b/docs/src/examples/FlowChart.tsx @@ -0,0 +1,64 @@ +import 'react-native-gesture-handler'; +import React, { useEffect, useRef } from 'react'; +import { StyleSheet, View } from 'react-native'; +import ChartManager from './ChartManager'; +import { Grid } from '@mui/material'; +import ChartElement from './ChartElement'; + +export default function App(props: { chartManager: ChartManager }) { + const currentChartManager = props.chartManager; + const elementsRef = useRef([]); + + useEffect(() => { + currentChartManager.elements.forEach((element) => { + currentChartManager.addListener(element.id, (isActive) => { + elementsRef.current[element.id].style.backgroundColor = isActive + ? '#ffe04b' + : '#b58df1'; + }); + }); + }, [currentChartManager]); + + // get each listener, pass them to the Element, they will change their color on input + return ( + + + {currentChartManager.elements.map((element) => ( + (elementsRef.current[element.id] = el)} + id={element.id} + state={element.state} + label={element.label} + /> + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + height: '100%', + padding: 40, + paddingTop: 0, + }, + box: { + flex: 1, + flexDirection: 'column', + textAlign: 'center', + }, + element: { + padding: 30, + fontWeight: '500', + fontSize: 24, + backgroundColor: '#b58df1', + }, + subtext: { + fontWeight: '300', + fontSize: 14, + }, +}); From e5004df86ffbfde77b8e99c994828e7a3b00c6b3 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 11:31:52 +0100 Subject: [PATCH 04/45] add distinction for cancelled and failed, added arrow library --- docs/src/examples/Arrow.tsx | 58 +++++++++++++++++++++++++++ docs/src/examples/ComposedGesture.tsx | 9 ++++- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 docs/src/examples/Arrow.tsx diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx new file mode 100644 index 0000000000..6580f1e44f --- /dev/null +++ b/docs/src/examples/Arrow.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +// source: https://gist.github.com/jvaclavik/fbf0a951864c98ca34d8f95be01b561d#file-dependency-arrows-1-tsx + +type Point = { + x: number; + y: number; +}; + +type ArrowProps = { + startPoint: Point; + endPoint: Point; +}; + +const Arrow = ({ startPoint, endPoint }: ArrowProps) => { + // Getting info about SVG canvas + const canvasStartPoint = { + x: Math.min(startPoint.x, endPoint.x), + y: Math.min(startPoint.y, endPoint.y), + }; + const canvasWidth = Math.abs(endPoint.x - startPoint.x); + const canvasHeight = Math.abs(endPoint.y - startPoint.y); + + return ( + + + + ); +}; + +function App() { + const featureAPosition = { + x: 300, + y: 0, + }; + + const featureBPosition = { + x: 400, + y: 200, + }; + + return ; +} + +export default App; diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 0c87da4275..b9d91f848a 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -17,6 +17,9 @@ import { import ChartManager, { State } from './ChartManager'; import FlowChart from './FlowChart'; +const STATE_FAILED = 1; +const STATE_CANCELLED = 3; + export default function App() { const chartManager = useRef(new ChartManager()); const [undeterminedCallback, undeterminedId] = useMemo( @@ -79,11 +82,13 @@ export default function App() { .onEnd(() => { endCallback(true); }) - .onFinalize((_, success) => { + .onFinalize((event, success) => { beganCallback(false); activeCallback(false); - if (!success) { + if (event.state == STATE_FAILED) { failedCallback(true); + } + if (event.state == STATE_CANCELLED) { cancelledCallback(true); } setTimeout(() => { From 5847b2e3bcad052230a3e0a1e2721939f4f721d6 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 13:11:15 +0100 Subject: [PATCH 05/45] added connections system, fixed references in docs --- docs/docs/gestures/state-manager.md | 6 ++--- docs/src/examples/Arrow.tsx | 38 ++++++++++++--------------- docs/src/examples/ChartElement.tsx | 1 + docs/src/examples/ChartManager.ts | 8 +++++- docs/src/examples/ComposedGesture.tsx | 36 ++++++++++++++++++++----- docs/src/examples/FlowChart.tsx | 35 +++++++++++++++++++++++- 6 files changed, 92 insertions(+), 32 deletions(-) diff --git a/docs/docs/gestures/state-manager.md b/docs/docs/gestures/state-manager.md index de1f3404ad..2191213cff 100644 --- a/docs/docs/gestures/state-manager.md +++ b/docs/docs/gestures/state-manager.md @@ -11,7 +11,7 @@ sidebar_position: 15 ### `begin()` -Transition the gesture to the [`BEGAN`](/docs/fundamentals/states-events.md#began) state. This method will have no effect if the gesture has already activated or finished. +Transition the gesture to the [`BEGAN`](/docs/fundamentals/states-events#began) state. This method will have no effect if the gesture has already activated or finished. ### `activate()` @@ -20,8 +20,8 @@ If the gesture is [`exclusive`](/docs/fundamentals/gesture-composition) with ano ### `end()` -Transition the gesture to the [`END`](/docs/fundamentals/states-events.md#end) state. This method will have no effect if the handler has already finished. +Transition the gesture to the [`END`](/docs/fundamentals/states-events#end) state. This method will have no effect if the handler has already finished. ### `fail()` -Transition the gesture to the [`FAILED`](/docs/fundamentals/states-events.md#failed) state. This method will have no effect if the handler has already finished. +Transition the gesture to the [`FAILED`](/docs/fundamentals/states-events#failed) state. This method will have no effect if the handler has already finished. diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index 6580f1e44f..de720d9027 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -12,26 +12,38 @@ type ArrowProps = { endPoint: Point; }; -const Arrow = ({ startPoint, endPoint }: ArrowProps) => { +export default function App({ startPoint, endPoint }: ArrowProps) { // Getting info about SVG canvas const canvasStartPoint = { x: Math.min(startPoint.x, endPoint.x), y: Math.min(startPoint.y, endPoint.y), }; - const canvasWidth = Math.abs(endPoint.x - startPoint.x); - const canvasHeight = Math.abs(endPoint.y - startPoint.y); + const strokeWidth = 3; + const halfStrokeWidth = 1.5; + + const canvasWidth = Math.abs(endPoint.x - startPoint.x + strokeWidth); + const canvasHeight = Math.abs(endPoint.y - startPoint.y + strokeWidth); + + // with perfectly straight lines, canvas height/width is set to 0 + // when that is fixed, stoke gets drawn on the border, getting halved + canvasStartPoint.x -= halfStrokeWidth; + canvasStartPoint.y -= halfStrokeWidth; return ( { /> ); -}; - -function App() { - const featureAPosition = { - x: 300, - y: 0, - }; - - const featureBPosition = { - x: 400, - y: 200, - }; - - return ; } - -export default App; diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index b83d3a3c97..b1c4e460c3 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -44,6 +44,7 @@ const styles = StyleSheet.create({ header: { fontWeight: '500', fontSize: 22, + minWidth: 110, }, subtext: { fontWeight: '300', diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index afc7a92298..acc47d043e 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -68,5 +68,11 @@ export default class ChartManager { ]; } - public addConnection(fromId: number, toId: number) {} + public addConnection(fromId: number, toId: number) { + this._connections.push({ + id: 1, + from: fromId, + to: toId, + }); + } } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index b9d91f848a..b19a03c99b 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -60,6 +60,23 @@ export default function App() { [chartManager] ); + const [tapActiveCallback, tapActiveId] = useMemo( + () => + chartManager.current.addElement( + State.ACTIVE, + 'This one activates on tap' + ), + [chartManager] + ); + + useEffect(() => { + chartManager.current.addConnection(undeterminedId, beganId); + chartManager.current.addConnection(beganId, activeId); + chartManager.current.addConnection(beganId, failedId); + chartManager.current.addConnection(activeId, endId); + chartManager.current.addConnection(activeId, cancelledId); + }, [chartManager]); + const pressed = useSharedValue(false); const offset = useSharedValue(0); @@ -104,12 +121,19 @@ export default function App() { .onUpdate((event) => { offset.value = event.translationX; }); - const tap = Gesture.Tap().onStart(() => { - scale.value = withSequence( - withSpring(1.8, { duration: 70 }), - withSpring(1, { duration: 140, dampingRatio: 0.4 }) - ); - }); + const tap = Gesture.Tap() + .onStart(() => { + tapActiveCallback(true); + scale.value = withSequence( + withSpring(1.8, { duration: 70 }), + withSpring(1, { duration: 140, dampingRatio: 0.4 }) + ); + }) + .onFinalize(() => { + setTimeout(() => { + tapActiveCallback(false); + }, 200); + }); // highlight-end const composed = Gesture.Race(pan, tap); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 98471318fc..8faeda0b05 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -4,10 +4,20 @@ import { StyleSheet, View } from 'react-native'; import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; import ChartElement from './ChartElement'; +import Arrow from './Arrow'; + +type Coordinate = { + x: number; + y: number; +}; export default function App(props: { chartManager: ChartManager }) { const currentChartManager = props.chartManager; const elementsRef = useRef([]); + const elementsCoordsRef = useRef([]); + const rootRef = useRef(null); + + const getCenter = (side: number, size: number) => side + size / 2; useEffect(() => { currentChartManager.elements.forEach((element) => { @@ -17,11 +27,22 @@ export default function App(props: { chartManager: ChartManager }) { : '#b58df1'; }); }); + + elementsCoordsRef.current = elementsRef.current.map((element) => { + const box = element.getBoundingClientRect(); + const root = rootRef.current.getBoundingClientRect(); + return { + x: getCenter(box.left, box.width) - root.left, + y: getCenter(box.top, box.height) - root.top, + } as Coordinate; + }); + + console.log(elementsRef.current); }, [currentChartManager]); // get each listener, pass them to the Element, they will change their color on input return ( - + {currentChartManager.elements.map((element) => ( ))} + {currentChartManager.connections.map((connection) => ( + + ))} ); } From 853730b5d93db5ac671f785272a6b6fcf0d33d6e Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 16:03:03 +0100 Subject: [PATCH 06/45] fix arrow --- docs/src/examples/Arrow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index de720d9027..34505f482f 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -21,8 +21,8 @@ export default function App({ startPoint, endPoint }: ArrowProps) { const strokeWidth = 3; const halfStrokeWidth = 1.5; - const canvasWidth = Math.abs(endPoint.x - startPoint.x + strokeWidth); - const canvasHeight = Math.abs(endPoint.y - startPoint.y + strokeWidth); + const canvasWidth = Math.abs(endPoint.x - startPoint.x) + strokeWidth; + const canvasHeight = Math.abs(endPoint.y - startPoint.y) + strokeWidth; // with perfectly straight lines, canvas height/width is set to 0 // when that is fixed, stoke gets drawn on the border, getting halved From 0cc3ab15c20195704c4fb439d0fec2aa6e134ee5 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 16:03:38 +0100 Subject: [PATCH 07/45] add layout modification --- docs/src/examples/ChartElement.tsx | 16 ++++--- docs/src/examples/ChartManager.ts | 31 +++++++++++--- docs/src/examples/ComposedGesture.tsx | 60 ++++++++++++++++++--------- docs/src/examples/FlowChart.tsx | 25 +++++++---- 4 files changed, 92 insertions(+), 40 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index b1c4e460c3..92aa63c8e9 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -10,19 +10,21 @@ type ChartElementProps = { position?: null; // todo innerRef?: LegacyRef; style?: StyleProp; + visible?: boolean; }; export default function App({ - id, state, label, // optional subtext - position, // todo innerRef, style, + visible, }: ChartElementProps) { return ( - - + + {state} {label} @@ -38,16 +40,18 @@ const styles = StyleSheet.create({ maxWidth: 270, }, element: { - padding: 30, + padding: 16, backgroundColor: '#b58df1', }, header: { fontWeight: '500', fontSize: 22, - minWidth: 110, }, subtext: { fontWeight: '300', fontSize: 14, }, + hidden: { + opacity: 0, + }, }); diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index acc47d043e..97c74f0ffc 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -15,7 +15,7 @@ type ChartElement = { id: number; state: State; label?: string; // optional subtext - position?: null; // todo + visible: boolean; }; class ChartConnection { @@ -25,14 +25,21 @@ class ChartConnection { } export default class ChartManager { - private _elements: ChartElement[] = []; - private _connections: ChartConnection[] = []; + private _elements: ChartElement[] = []; // debug: best structure here is array, because this is just a pool of elements, and thier id's are derived from here anyways + private _connections: ChartConnection[] = []; // debug: this is a separate pool, fine as well + private _layout: number[][]; private _listeners: Map void)[]> = useMemo( () => new Map(), [] ); - get elements(): ChartElement[] { + public static EMPTY_SPACE = 0; + + constructor() { + this.addElement(null, null, false); + } + + get elements(): typeof this._elements { return this._elements; } @@ -40,6 +47,14 @@ export default class ChartManager { return this._connections; } + get layout(): number[][] { + return this._layout; + } + + set layout(layoutGrid: number[][]) { + this._layout = layoutGrid; + } + public addListener(id: number, listener: (isActive: boolean) => void): void { if (this._listeners.has(id)) this._listeners.get(id).push(listener); else this._listeners.set(id, [listener]); @@ -47,7 +62,8 @@ export default class ChartManager { public addElement( state: State, - label: string | null = null + label: string | null = null, + visible: boolean = true ): [(isActive: boolean) => void, number] { const newId = this._elements.length; const newChartElement = { @@ -55,6 +71,7 @@ export default class ChartManager { label: label, state: state, position: null, + visible: visible, }; this._elements.push(newChartElement); @@ -70,9 +87,11 @@ export default class ChartManager { public addConnection(fromId: number, toId: number) { this._connections.push({ - id: 1, + id: this._connections.length, from: fromId, to: toId, }); } + + public setGridLayout() {} } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index b19a03c99b..9777c598dd 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -12,14 +12,24 @@ import Animated, { import { Gesture, GestureDetector, + GestureEventPayload, GestureHandlerRootView, + GestureStateChangeEvent, + GestureUpdateEvent, + PanGestureChangeEventPayload, + PanGestureHandlerEventPayload, } from 'react-native-gesture-handler'; import ChartManager, { State } from './ChartManager'; import FlowChart from './FlowChart'; -const STATE_FAILED = 1; -const STATE_CANCELLED = 3; - +enum States { + UNDETERMINED = 0, + FAILED = 1, + BEGAN = 2, + CANCELLED = 3, + ACTIVE = 4, + END = 5, +} export default function App() { const chartManager = useRef(new ChartManager()); const [undeterminedCallback, undeterminedId] = useMemo( @@ -69,6 +79,11 @@ export default function App() { [chartManager] ); + chartManager.current.layout = [ + [undeterminedId, beganId, activeId, endId], + [ChartManager.EMPTY_SPACE, failedId, cancelledId, tapActiveId], + ]; + useEffect(() => { chartManager.current.addConnection(undeterminedId, beganId); chartManager.current.addConnection(beganId, activeId); @@ -84,12 +99,31 @@ export default function App() { undeterminedCallback(true); + const resetAllStates = ( + event: GestureStateChangeEvent + ) => { + beganCallback(false); + activeCallback(false); + undeterminedCallback(true); + if (event.state == States.FAILED) { + failedCallback(true); + } + if (event.state == States.CANCELLED) { + cancelledCallback(true); + } + setTimeout(() => { + endCallback(false); + failedCallback(false); + cancelledCallback(false); + }, 200); + }; + // highlight-start const pan = Gesture.Pan() .onBegin(() => { - pressed.value = true; beganCallback(true); undeterminedCallback(false); + pressed.value = true; }) .onStart(() => { scale.value = withSpring(0.6, { duration: 200 }); @@ -99,21 +133,8 @@ export default function App() { .onEnd(() => { endCallback(true); }) - .onFinalize((event, success) => { - beganCallback(false); - activeCallback(false); - if (event.state == STATE_FAILED) { - failedCallback(true); - } - if (event.state == STATE_CANCELLED) { - cancelledCallback(true); - } - setTimeout(() => { - endCallback(false); - failedCallback(false); - cancelledCallback(false); - undeterminedCallback(true); - }, 200); + .onFinalize((event) => { + resetAllStates(event); offset.value = withSpring(0); scale.value = withTiming(1); pressed.value = false; @@ -121,6 +142,7 @@ export default function App() { .onUpdate((event) => { offset.value = event.translationX; }); + const tap = Gesture.Tap() .onStart(() => { tapActiveCallback(true); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 8faeda0b05..c3bdd25df1 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -43,15 +43,22 @@ export default function App(props: { chartManager: ChartManager }) { // get each listener, pass them to the Element, they will change their color on input return ( - - {currentChartManager.elements.map((element) => ( - (elementsRef.current[element.id] = el)} - id={element.id} - state={element.state} - label={element.label} - /> + + {currentChartManager.layout.map((row) => ( + + {row + .map((elementId) => currentChartManager.elements[elementId]) + .map((element, index) => ( + (elementsRef.current[element.id] = el)} + id={element.id} + state={element.state} + label={element.label} + visible={element.visible} + /> + ))} + ))} {currentChartManager.connections.map((connection) => ( From a943adfd96c86785ce7cc3bfdee7d2b70db8dec6 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 16:17:39 +0100 Subject: [PATCH 08/45] added vertical layout option --- docs/src/examples/ChartElement.tsx | 1 + docs/src/examples/ComposedGesture.tsx | 34 +++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 92aa63c8e9..d19f7d746f 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -50,6 +50,7 @@ const styles = StyleSheet.create({ subtext: { fontWeight: '300', fontSize: 14, + backgroundColor: 'var(--swm-off-background)', }, hidden: { opacity: 0, diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 9777c598dd..1b1b132057 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -33,11 +33,7 @@ enum States { export default function App() { const chartManager = useRef(new ChartManager()); const [undeterminedCallback, undeterminedId] = useMemo( - () => - chartManager.current.addElement( - State.UNDETERMINED, - 'This is the default state' - ), + () => chartManager.current.addElement(State.UNDETERMINED, 'Gesture.pan()'), [chartManager] ); @@ -70,6 +66,11 @@ export default function App() { [chartManager] ); + const [tapUndeterminedCallback, tapUndeterminedId] = useMemo( + () => chartManager.current.addElement(State.UNDETERMINED, 'Gesture.tap()'), + [chartManager] + ); + const [tapActiveCallback, tapActiveId] = useMemo( () => chartManager.current.addElement( @@ -79,17 +80,38 @@ export default function App() { [chartManager] ); + // horizontal layout chartManager.current.layout = [ [undeterminedId, beganId, activeId, endId], [ChartManager.EMPTY_SPACE, failedId, cancelledId, tapActiveId], ]; + // vertical layout + chartManager.current.layout = [ + [ + undeterminedId, + ChartManager.EMPTY_SPACE, + ChartManager.EMPTY_SPACE, + tapUndeterminedId, + ], + [beganId, failedId, ChartManager.EMPTY_SPACE, tapActiveId], + [activeId, cancelledId, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE], + [ + endId, + ChartManager.EMPTY_SPACE, + ChartManager.EMPTY_SPACE, + ChartManager.EMPTY_SPACE, + ], + ]; + useEffect(() => { chartManager.current.addConnection(undeterminedId, beganId); chartManager.current.addConnection(beganId, activeId); chartManager.current.addConnection(beganId, failedId); chartManager.current.addConnection(activeId, endId); chartManager.current.addConnection(activeId, cancelledId); + + chartManager.current.addConnection(tapUndeterminedId, tapActiveId); }, [chartManager]); const pressed = useSharedValue(false); @@ -146,6 +168,7 @@ export default function App() { const tap = Gesture.Tap() .onStart(() => { tapActiveCallback(true); + tapUndeterminedCallback(false); scale.value = withSequence( withSpring(1.8, { duration: 70 }), withSpring(1, { duration: 140, dampingRatio: 0.4 }) @@ -154,6 +177,7 @@ export default function App() { .onFinalize(() => { setTimeout(() => { tapActiveCallback(false); + tapUndeterminedCallback(true); }, 200); }); // highlight-end From 67889c9d94b8ca3194792ee309ac6d5d05a664f4 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 22 Mar 2024 18:32:07 +0100 Subject: [PATCH 09/45] Add arrow heads, fixed console errors --- docs/src/examples/Arrow.tsx | 80 +++++++++++++++++++++++++-- docs/src/examples/ComposedGesture.tsx | 21 ++++--- docs/src/examples/FlowChart.tsx | 5 +- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index 34505f482f..f71748ccf3 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -18,16 +18,68 @@ export default function App({ startPoint, endPoint }: ArrowProps) { x: Math.min(startPoint.x, endPoint.x), y: Math.min(startPoint.y, endPoint.y), }; + const strokeWidth = 3; - const halfStrokeWidth = 1.5; + const spaceForArrows = 100; + const totalPadding = strokeWidth + spaceForArrows; + const halfPadding = totalPadding / 2; - const canvasWidth = Math.abs(endPoint.x - startPoint.x) + strokeWidth; - const canvasHeight = Math.abs(endPoint.y - startPoint.y) + strokeWidth; + const canvasWidth = Math.abs(endPoint.x - startPoint.x) + totalPadding; + const canvasHeight = Math.abs(endPoint.y - startPoint.y) + totalPadding; // with perfectly straight lines, canvas height/width is set to 0 // when that is fixed, stoke gets drawn on the border, getting halved - canvasStartPoint.x -= halfStrokeWidth; - canvasStartPoint.y -= halfStrokeWidth; + canvasStartPoint.x -= halfPadding; + canvasStartPoint.y -= halfPadding; + + const avg = (a: number, b: number) => (a + b) / 2; + + // we will be drawing two deflections from midpoint to origin + const arrowDeflection = 45; + const arrowLength = 10; + const midPoint = { + x: avg(startPoint.x - canvasStartPoint.x, endPoint.x - canvasStartPoint.x), + y: avg(startPoint.y - canvasStartPoint.y, endPoint.y - canvasStartPoint.y), + }; + + const midToOriginVector = { + x: midPoint.x - endPoint.x + canvasStartPoint.x, + y: midPoint.y - endPoint.y + canvasStartPoint.y, + }; + + type Coords = { x: number; y: number }; + + const truncate = ({ x, y }: Coords, length: number): Coords => { + const magnitude = Math.hypot(x, y); + const modifier = length / magnitude; + + return { + x: x * modifier, + y: y * modifier, + }; + }; + + const rotate = ( + { x, y }: { x: number; y: number }, + rotation: number + ): Coords => { + const rotationRadians = (Math.PI * rotation) / 180; + const cosResult = Math.cos(rotationRadians); + const sinResult = Math.sin(rotationRadians); + return { + x: x * cosResult - y * sinResult, + y: x * sinResult + y * cosResult, + }; + }; + + const deflectionVectorLeft = rotate( + truncate(midToOriginVector, arrowLength), + arrowDeflection + ); + const deflectionVectorRight = rotate( + truncate(midToOriginVector, arrowLength), + -arrowDeflection + ); return ( + + ); } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 1b1b132057..35dfd7adf2 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,22 +1,18 @@ import 'react-native-gesture-handler'; import React, { useEffect, useMemo, useRef } from 'react'; -import { Easing, StyleSheet, View } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming, withSequence, - withDelay, } from 'react-native-reanimated'; import { Gesture, GestureDetector, - GestureEventPayload, GestureHandlerRootView, GestureStateChangeEvent, - GestureUpdateEvent, - PanGestureChangeEventPayload, PanGestureHandlerEventPayload, } from 'react-native-gesture-handler'; import ChartManager, { State } from './ChartManager'; @@ -61,7 +57,7 @@ export default function App() { () => chartManager.current.addElement( State.CANCELLED, - 'This is some sample text' + 'Called when realeased out of bounds' ), [chartManager] ); @@ -148,7 +144,7 @@ export default function App() { pressed.value = true; }) .onStart(() => { - scale.value = withSpring(0.6, { duration: 200 }); + scale.value = withSpring(0.6, { duration: 150 }); beganCallback(false); activeCallback(true); }) @@ -157,7 +153,7 @@ export default function App() { }) .onFinalize((event) => { resetAllStates(event); - offset.value = withSpring(0); + offset.value = withSpring(0, { duration: 200 }); scale.value = withTiming(1); pressed.value = false; }) @@ -170,8 +166,8 @@ export default function App() { tapActiveCallback(true); tapUndeterminedCallback(false); scale.value = withSequence( - withSpring(1.8, { duration: 70 }), - withSpring(1, { duration: 140, dampingRatio: 0.4 }) + withSpring(1.8, { duration: 90 }), + withSpring(1, { duration: 180, dampingRatio: 0.4 }) ); }) .onFinalize(() => { @@ -185,7 +181,10 @@ export default function App() { const composed = Gesture.Race(pan, tap); const animatedStyles = useAnimatedStyle(() => ({ - transform: [{ translateX: offset.value }, { scale: scale.value }], + transform: [ + { translateX: withSpring(offset.value, {}) }, + { scale: scale.value }, + ], backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', })); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index c3bdd25df1..4fac406de2 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -5,6 +5,7 @@ import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; import ChartElement from './ChartElement'; import Arrow from './Arrow'; +import { withSpring } from 'react-native-reanimated'; type Coordinate = { x: number; @@ -44,8 +45,8 @@ export default function App(props: { chartManager: ChartManager }) { return ( - {currentChartManager.layout.map((row) => ( - + {currentChartManager.layout.map((row, index) => ( + {row .map((elementId) => currentChartManager.elements[elementId]) .map((element, index) => ( From 72b4b5ae8408d9231c2baa917b537db7b4175be6 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 26 Mar 2024 10:59:23 +0100 Subject: [PATCH 10/45] moved connections and flow logic into a separate library --- docs/src/examples/ChartManager.ts | 143 ++++++++++++++++++++++++- docs/src/examples/ComposedGesture.tsx | 147 +++++++------------------- docs/src/examples/FlowChart.tsx | 68 ++++++------ 3 files changed, 219 insertions(+), 139 deletions(-) diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 97c74f0ffc..0d8dcd810c 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -1,6 +1,20 @@ //import ChartElement from './ChartElement'; import { useMemo } from 'react'; +import { + TapGesture, + PanGesture, + PinchGesture, + RotationGesture, + FlingGesture, + LongPressGesture, + ForceTouchGesture, + NativeGesture, + ManualGesture, + HoverGesture, + GestureStateChangeEvent, +} from 'react-native-gesture-handler'; +import { States } from './ComposedGesture'; export enum State { UNDETERMINED = 'UNDETERMINED', @@ -24,9 +38,41 @@ class ChartConnection { to: number; } +type GesturesUnion = + | TapGesture + | PanGesture + | PinchGesture + | RotationGesture + | FlingGesture + | LongPressGesture + | ForceTouchGesture + | NativeGesture + | ManualGesture + | HoverGesture; + +// FROM, TO +const stateConnectionsMap = [ + [State.UNDETERMINED, State.BEGAN], + [State.BEGAN, State.ACTIVE], + [State.BEGAN, State.FAILED], + [State.ACTIVE, State.END], + [State.ACTIVE, State.CANCELLED], +]; + +export class GestureHandle { + // within gesture, States can be used as unique IDs pointing to the ChartElement pool + elementIds: Map; + elementCbs: Map void>; + constructor() { + this.elementIds = new Map(); + this.elementCbs = new Map(); + } +} + export default class ChartManager { private _elements: ChartElement[] = []; // debug: best structure here is array, because this is just a pool of elements, and thier id's are derived from here anyways private _connections: ChartConnection[] = []; // debug: this is a separate pool, fine as well + private _headers: ChartElement[] = []; private _layout: number[][]; private _listeners: Map void)[]> = useMemo( () => new Map(), @@ -85,6 +131,11 @@ export default class ChartManager { ]; } + public addHeader(text: string): number { + // todo: add elements which can display text, and fill in all of the assigned space + return 0; + } + public addConnection(fromId: number, toId: number) { this._connections.push({ id: this._connections.length, @@ -93,5 +144,95 @@ export default class ChartManager { }); } - public setGridLayout() {} + public connectAll(handle: GestureHandle) { + stateConnectionsMap.forEach(([fromState, toState]) => { + console.log('adding connection'); + const fromId = handle.elementIds.get(fromState); + const toId = handle.elementIds.get(toState); + if (fromId && toId) { + this.addConnection(fromId, toId); + console.log('connected'); + } + }); + } + + public newGesture(gesture: GesturesUnion): [GestureHandle, GesturesUnion] { + // HANDLE IS ONLY STORED ON USERSIDE, THIS AVOIDS MEMLEAKS + // OTHERWISE NEW HANDLE WOULD BE CREATED & STORED EVERY RENDER + + // WARNING + // ELEMENTS STILL GET LEAKED, RERENDER ADDS ELEMENTS TO THE POOL + // we should either cleanup old elements, but that's a bad idea, + // rather, it's better to memoize the creation of ids and cbs, + // but then have a getter for them every render so that we can set them manually + const [beganCallback, beganId] = this.addElement(State.BEGAN); + + const [activeCallback, activeId] = this.addElement(State.ACTIVE); + + const [endCallback, endId] = this.addElement(State.END); + + const [failedCallback, failedId] = this.addElement(State.FAILED); + + const [cancelledCallback, cancelledId] = this.addElement(State.CANCELLED); + + const [undeterminedCallback, undeterminedId] = this.addElement( + State.UNDETERMINED + ); + + const handle = new GestureHandle(); + + handle.elementIds.set(State.BEGAN, beganId); + handle.elementIds.set(State.ACTIVE, activeId); + handle.elementIds.set(State.END, endId); + handle.elementIds.set(State.FAILED, failedId); + handle.elementIds.set(State.CANCELLED, cancelledId); + handle.elementIds.set(State.UNDETERMINED, undeterminedId); + + // for now, all of these values have to be hardcoded somewhere either way, and here seems like the most appropriate area. + // in future, we could theoritically only listen to 'onChange' and play around with the states. + // this could take up a little less space, but we would still need to hardcode the flow itself, just in a different format. + undeterminedCallback(true); + + const resetAllStates = (event: GestureStateChangeEvent) => { + beganCallback(false); + activeCallback(false); + undeterminedCallback(true); + if (event.state == States.FAILED) { + failedCallback(true); + } + if (event.state == States.CANCELLED) { + cancelledCallback(true); + } + setTimeout(() => { + endCallback(false); + failedCallback(false); + cancelledCallback(false); + }, 200); + }; + + // highlight-start + gesture + .onBegin(() => { + beganCallback(true); + undeterminedCallback(false); + }) + .onStart(() => { + beganCallback(false); + activeCallback(true); + }) + .onEnd(() => { + endCallback(true); + }) + .onFinalize((event) => { + resetAllStates(event); + }); + + this.addConnection(undeterminedId, beganId); + this.addConnection(beganId, activeId); + this.addConnection(beganId, failedId); + this.addConnection(activeId, endId); + this.addConnection(activeId, cancelledId); + + return [handle, gesture]; + } } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 35dfd7adf2..304434e4cc 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -13,12 +13,13 @@ import { GestureDetector, GestureHandlerRootView, GestureStateChangeEvent, + PanGesture, PanGestureHandlerEventPayload, } from 'react-native-gesture-handler'; import ChartManager, { State } from './ChartManager'; import FlowChart from './FlowChart'; -enum States { +export enum States { UNDETERMINED = 0, FAILED = 1, BEGAN = 2, @@ -27,60 +28,38 @@ enum States { END = 5, } export default function App() { - const chartManager = useRef(new ChartManager()); - const [undeterminedCallback, undeterminedId] = useMemo( - () => chartManager.current.addElement(State.UNDETERMINED, 'Gesture.pan()'), - [chartManager] - ); - - const [beganCallback, beganId] = useMemo( - () => chartManager.current.addElement(State.BEGAN), - [chartManager] - ); - - const [activeCallback, activeId] = useMemo( - () => chartManager.current.addElement(State.ACTIVE), - [chartManager] - ); + // if chartman would take care of the connections and also gesture capturing, + // we would only need to segregate between pan and tap + // auto connections + // const panHandler = useMemo(() => chartManager.capture(pan)) + // panHandler.capture(State.BEGAN) // only adds this one to the list + // panHandler.captureAll() // auto connects all that need to be connected + // panHandler.connectAll() // this one is a part of the capAll, will connect based on a lookup list + // layout = [panHandler.beganId, ..., ...] - const [endCallback, endId] = useMemo( - () => chartManager.current.addElement(State.END), - [chartManager] - ); + const chartManager = useRef(new ChartManager()); + const pan = Gesture.Pan(); - const [failedCallback, failedId] = useMemo( - () => chartManager.current.addElement(State.FAILED), - [chartManager] + const [panHandle, capturedPan] = useMemo( + () => chartManager.current.newGesture(Gesture.Pan()), + [] ); - const [cancelledCallback, cancelledId] = useMemo( - () => - chartManager.current.addElement( - State.CANCELLED, - 'Called when realeased out of bounds' - ), - [chartManager] + const [tapHandle, capturedTap] = useMemo( + () => chartManager.current.newGesture(Gesture.Tap()), + [] ); - const [tapUndeterminedCallback, tapUndeterminedId] = useMemo( - () => chartManager.current.addElement(State.UNDETERMINED, 'Gesture.tap()'), - [chartManager] - ); + const beganId = panHandle.elementIds.get(State.BEGAN); + const activeId = panHandle.elementIds.get(State.ACTIVE); + const endId = panHandle.elementIds.get(State.END); + const failedId = panHandle.elementIds.get(State.FAILED); + const cancelledId = panHandle.elementIds.get(State.CANCELLED); + const undeterminedId = panHandle.elementIds.get(State.UNDETERMINED); - const [tapActiveCallback, tapActiveId] = useMemo( - () => - chartManager.current.addElement( - State.ACTIVE, - 'This one activates on tap' - ), - [chartManager] - ); - - // horizontal layout - chartManager.current.layout = [ - [undeterminedId, beganId, activeId, endId], - [ChartManager.EMPTY_SPACE, failedId, cancelledId, tapActiveId], - ]; + const tapBeganId = tapHandle.elementIds.get(State.BEGAN); + const tapActiveId = tapHandle.elementIds.get(State.ACTIVE); + const tapUndeterminedId = tapHandle.elementIds.get(State.UNDETERMINED); // vertical layout chartManager.current.layout = [ @@ -90,8 +69,8 @@ export default function App() { ChartManager.EMPTY_SPACE, tapUndeterminedId, ], - [beganId, failedId, ChartManager.EMPTY_SPACE, tapActiveId], - [activeId, cancelledId, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE], + [beganId, failedId, ChartManager.EMPTY_SPACE, tapBeganId], + [activeId, cancelledId, ChartManager.EMPTY_SPACE, tapActiveId], [ endId, ChartManager.EMPTY_SPACE, @@ -100,59 +79,20 @@ export default function App() { ], ]; - useEffect(() => { - chartManager.current.addConnection(undeterminedId, beganId); - chartManager.current.addConnection(beganId, activeId); - chartManager.current.addConnection(beganId, failedId); - chartManager.current.addConnection(activeId, endId); - chartManager.current.addConnection(activeId, cancelledId); - - chartManager.current.addConnection(tapUndeterminedId, tapActiveId); - }, [chartManager]); - const pressed = useSharedValue(false); const offset = useSharedValue(0); const scale = useSharedValue(1); - undeterminedCallback(true); - - const resetAllStates = ( - event: GestureStateChangeEvent - ) => { - beganCallback(false); - activeCallback(false); - undeterminedCallback(true); - if (event.state == States.FAILED) { - failedCallback(true); - } - if (event.state == States.CANCELLED) { - cancelledCallback(true); - } - setTimeout(() => { - endCallback(false); - failedCallback(false); - cancelledCallback(false); - }, 200); - }; - // highlight-start - const pan = Gesture.Pan() + (pan as PanGesture) .onBegin(() => { - beganCallback(true); - undeterminedCallback(false); pressed.value = true; }) .onStart(() => { scale.value = withSpring(0.6, { duration: 150 }); - beganCallback(false); - activeCallback(true); - }) - .onEnd(() => { - endCallback(true); }) - .onFinalize((event) => { - resetAllStates(event); + .onFinalize(() => { offset.value = withSpring(0, { duration: 200 }); scale.value = withTiming(1); pressed.value = false; @@ -161,24 +101,17 @@ export default function App() { offset.value = event.translationX; }); - const tap = Gesture.Tap() - .onStart(() => { - tapActiveCallback(true); - tapUndeterminedCallback(false); - scale.value = withSequence( - withSpring(1.8, { duration: 90 }), - withSpring(1, { duration: 180, dampingRatio: 0.4 }) - ); - }) - .onFinalize(() => { - setTimeout(() => { - tapActiveCallback(false); - tapUndeterminedCallback(true); - }, 200); - }); + const tap = Gesture.Tap().onStart(() => { + scale.value = withSequence( + withSpring(1.8, { duration: 90 }), + withSpring(1, { duration: 180, dampingRatio: 0.4 }) + ); + }); // highlight-end - const composed = Gesture.Race(pan, tap); + const composedPan = Gesture.Simultaneous(pan, capturedPan); + const composedTap = Gesture.Simultaneous(tap, capturedTap); + const composed = Gesture.Race(composedPan, composedTap); const animatedStyles = useAnimatedStyle(() => ({ transform: [ diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 4fac406de2..a9a5d19aee 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -5,7 +5,6 @@ import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; import ChartElement from './ChartElement'; import Arrow from './Arrow'; -import { withSpring } from 'react-native-reanimated'; type Coordinate = { x: number; @@ -20,26 +19,22 @@ export default function App(props: { chartManager: ChartManager }) { const getCenter = (side: number, size: number) => side + size / 2; - useEffect(() => { - currentChartManager.elements.forEach((element) => { - currentChartManager.addListener(element.id, (isActive) => { - elementsRef.current[element.id].style.backgroundColor = isActive - ? '#ffe04b' - : '#b58df1'; - }); + currentChartManager.elements.forEach((element) => { + currentChartManager.addListener(element.id, (isActive) => { + elementsRef.current[element.id].style.backgroundColor = isActive + ? '#ffe04b' + : '#b58df1'; }); + }); - elementsCoordsRef.current = elementsRef.current.map((element) => { - const box = element.getBoundingClientRect(); - const root = rootRef.current.getBoundingClientRect(); - return { - x: getCenter(box.left, box.width) - root.left, - y: getCenter(box.top, box.height) - root.top, - } as Coordinate; - }); - - console.log(elementsRef.current); - }, [currentChartManager]); + elementsCoordsRef.current = elementsRef.current.map((element) => { + const box = element.getBoundingClientRect(); + const root = rootRef.current.getBoundingClientRect(); + return { + x: getCenter(box.left, box.width) - root.left, + y: getCenter(box.top, box.height) - root.top, + } as Coordinate; + }); // get each listener, pass them to the Element, they will change their color on input return ( @@ -62,18 +57,29 @@ export default function App(props: { chartManager: ChartManager }) { ))} - {currentChartManager.connections.map((connection) => ( - - ))} + {elementsCoordsRef.current.length > 0 && + currentChartManager.connections.map((connection, idx) => { + // we have all the connections layed out, + // but the user may choose not to use some of the available elements, + if ( + !elementsCoordsRef.current[connection.from] || + !elementsCoordsRef.current[connection.to] + ) { + return ; + } + return ( + + ); + })} ); } From 252643dace1eab714cfb8833a49e07925a536c99 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 26 Mar 2024 12:32:23 +0100 Subject: [PATCH 11/45] removed hardcoded id gets, added headers, added tap to the layout --- docs/src/examples/ChartElement.tsx | 38 ++++++++++++++++++------- docs/src/examples/ChartManager.ts | 41 +++++++++++++++++++-------- docs/src/examples/ComposedGesture.tsx | 39 +++++++++---------------- docs/src/examples/FlowChart.tsx | 12 ++++---- 4 files changed, 77 insertions(+), 53 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index d19f7d746f..67180443fc 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -5,29 +5,35 @@ import { State } from './ChartManager'; type ChartElementProps = { id: number; - state: State; - label?: string; // optional subtext + label: string; + subtext?: string; // optional subtext position?: null; // todo innerRef?: LegacyRef; style?: StyleProp; - visible?: boolean; + isVisible?: boolean; + isHeader?: boolean; }; export default function App({ - state, - label, // optional subtext + label, + subtext, // optional subtext innerRef, style, - visible, + isVisible, + isHeader, }: ChartElementProps) { return ( - + - {state} + {label} - {label} + {subtext} ); } @@ -39,11 +45,23 @@ const styles = StyleSheet.create({ textAlign: 'center', maxWidth: 270, }, + headerBox: { + flex: 1, + flexDirection: 'column', + textAlign: 'center', + maxWidth: 800, + }, element: { padding: 16, backgroundColor: '#b58df1', }, header: { + fontSize: 28, + fontWeight: '600', + fontFamily: 'var(--ifm-heading-font-family)', + margin: 12, + }, + label: { fontWeight: '500', fontSize: 22, }, diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 0d8dcd810c..4e0bffb0da 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -27,9 +27,10 @@ export enum State { type ChartElement = { id: number; - state: State; - label?: string; // optional subtext - visible: boolean; + label?: string; + subtext?: string; + isVisible: boolean; + isHeader: boolean; }; class ChartConnection { @@ -67,6 +68,16 @@ export class GestureHandle { this.elementIds = new Map(); this.elementCbs = new Map(); } + getIdObject() { + return { + began: this.elementIds.get(State.BEGAN), + active: this.elementIds.get(State.ACTIVE), + end: this.elementIds.get(State.END), + failed: this.elementIds.get(State.FAILED), + cancelled: this.elementIds.get(State.CANCELLED), + undetermined: this.elementIds.get(State.UNDETERMINED), + }; + } } export default class ChartManager { @@ -107,17 +118,19 @@ export default class ChartManager { } public addElement( - state: State, - label: string | null = null, - visible: boolean = true + label: State | string = null, + subtext: string | null = null, + isVisible: boolean = true, + isHeader: boolean = false ): [(isActive: boolean) => void, number] { const newId = this._elements.length; const newChartElement = { id: newId, label: label, - state: state, + subtext: subtext, position: null, - visible: visible, + isVisible: isVisible, + isHeader: isHeader, }; this._elements.push(newChartElement); @@ -133,7 +146,7 @@ export default class ChartManager { public addHeader(text: string): number { // todo: add elements which can display text, and fill in all of the assigned space - return 0; + return this.addElement(text, null, true, true)[1]; } public addConnection(fromId: number, toId: number) { @@ -193,9 +206,9 @@ export default class ChartManager { // this could take up a little less space, but we would still need to hardcode the flow itself, just in a different format. undeterminedCallback(true); + const WAVE_DELAY_MS = 100; + const resetAllStates = (event: GestureStateChangeEvent) => { - beganCallback(false); - activeCallback(false); undeterminedCallback(true); if (event.state == States.FAILED) { failedCallback(true); @@ -203,11 +216,15 @@ export default class ChartManager { if (event.state == States.CANCELLED) { cancelledCallback(true); } + setTimeout(() => { + beganCallback(false); + activeCallback(false); + }, WAVE_DELAY_MS); setTimeout(() => { endCallback(false); failedCallback(false); cancelledCallback(false); - }, 200); + }, 2 * WAVE_DELAY_MS); }; // highlight-start diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 304434e4cc..057c4f957f 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useMemo, useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import Animated, { useAnimatedStyle, @@ -12,9 +12,7 @@ import { Gesture, GestureDetector, GestureHandlerRootView, - GestureStateChangeEvent, PanGesture, - PanGestureHandlerEventPayload, } from 'react-native-gesture-handler'; import ChartManager, { State } from './ChartManager'; import FlowChart from './FlowChart'; @@ -50,33 +48,22 @@ export default function App() { [] ); - const beganId = panHandle.elementIds.get(State.BEGAN); - const activeId = panHandle.elementIds.get(State.ACTIVE); - const endId = panHandle.elementIds.get(State.END); - const failedId = panHandle.elementIds.get(State.FAILED); - const cancelledId = panHandle.elementIds.get(State.CANCELLED); - const undeterminedId = panHandle.elementIds.get(State.UNDETERMINED); + const panIds = panHandle.getIdObject(); + const tapIds = tapHandle.getIdObject(); - const tapBeganId = tapHandle.elementIds.get(State.BEGAN); - const tapActiveId = tapHandle.elementIds.get(State.ACTIVE); - const tapUndeterminedId = tapHandle.elementIds.get(State.UNDETERMINED); + const panHeaderId = chartManager.current.addHeader('Pan Gesture'); + const tapHeaderId = chartManager.current.addHeader('Tap Gesture'); + + // FIXME: tap seems to be broken, and does not follow the typical flow, thus it's quite a bad flow example :P // vertical layout + // prettier-ignore chartManager.current.layout = [ - [ - undeterminedId, - ChartManager.EMPTY_SPACE, - ChartManager.EMPTY_SPACE, - tapUndeterminedId, - ], - [beganId, failedId, ChartManager.EMPTY_SPACE, tapBeganId], - [activeId, cancelledId, ChartManager.EMPTY_SPACE, tapActiveId], - [ - endId, - ChartManager.EMPTY_SPACE, - ChartManager.EMPTY_SPACE, - ChartManager.EMPTY_SPACE, - ], + [panHeaderId, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE, tapHeaderId], + [panIds.undetermined, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE, tapIds.undetermined], + [panIds.began, panIds.failed, tapIds.failed, tapIds.began], + [panIds.active, panIds.cancelled, tapIds.cancelled, tapIds.active], + [panIds.end, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE, tapIds.end], ]; const pressed = useSharedValue(false); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index a9a5d19aee..55333551c2 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -21,9 +21,10 @@ export default function App(props: { chartManager: ChartManager }) { currentChartManager.elements.forEach((element) => { currentChartManager.addListener(element.id, (isActive) => { - elementsRef.current[element.id].style.backgroundColor = isActive - ? '#ffe04b' - : '#b58df1'; + if (elementsRef.current[element.id]) + elementsRef.current[element.id].style.backgroundColor = isActive + ? '#ffe04b' + : '#b58df1'; }); }); @@ -49,9 +50,10 @@ export default function App(props: { chartManager: ChartManager }) { key={index} innerRef={(el) => (elementsRef.current[element.id] = el)} id={element.id} - state={element.state} label={element.label} - visible={element.visible} + subtext={element.subtext} + isVisible={element.isVisible} + isHeader={element.isHeader} /> ))} From b5d4111f99c82f2e6de0924d9309e2cac1a66099 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 26 Mar 2024 13:14:58 +0100 Subject: [PATCH 12/45] cleanup --- docs/src/examples/ChartElement.tsx | 9 ++++----- docs/src/examples/ComposedGesture.tsx | 20 ++++++++------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 67180443fc..04ba17b1a8 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -1,7 +1,6 @@ import { Grid } from '@mui/material'; -import React, { LegacyRef, Ref } from 'react'; +import React, { LegacyRef } from 'react'; import { StyleProp, StyleSheet, View, ViewStyle, Text } from 'react-native'; -import { State } from './ChartManager'; type ChartElementProps = { id: number; @@ -27,11 +26,11 @@ export default function App({ - {label} + {label} {subtext} @@ -55,7 +54,7 @@ const styles = StyleSheet.create({ padding: 16, backgroundColor: '#b58df1', }, - header: { + headerText: { fontSize: 28, fontWeight: '600', fontFamily: 'var(--ifm-heading-font-family)', diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 057c4f957f..3abf17fe42 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useMemo, useRef } from 'react'; +import React, { useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import Animated, { useAnimatedStyle, @@ -12,9 +12,8 @@ import { Gesture, GestureDetector, GestureHandlerRootView, - PanGesture, } from 'react-native-gesture-handler'; -import ChartManager, { State } from './ChartManager'; +import ChartManager from './ChartManager'; import FlowChart from './FlowChart'; export enum States { @@ -36,16 +35,13 @@ export default function App() { // layout = [panHandler.beganId, ..., ...] const chartManager = useRef(new ChartManager()); - const pan = Gesture.Pan(); - const [panHandle, capturedPan] = useMemo( - () => chartManager.current.newGesture(Gesture.Pan()), - [] + const [panHandle, capturedPan] = chartManager.current.newGesture( + Gesture.Pan() ); - const [tapHandle, capturedTap] = useMemo( - () => chartManager.current.newGesture(Gesture.Tap()), - [] + const [tapHandle, capturedTap] = chartManager.current.newGesture( + Gesture.Tap() ); const panIds = panHandle.getIdObject(); @@ -72,7 +68,7 @@ export default function App() { const scale = useSharedValue(1); // highlight-start - (pan as PanGesture) + const pan = Gesture.Pan() .onBegin(() => { pressed.value = true; }) @@ -102,7 +98,7 @@ export default function App() { const animatedStyles = useAnimatedStyle(() => ({ transform: [ - { translateX: withSpring(offset.value, {}) }, + { translateX: withSpring(offset.value, { duration: 1000 }) }, { scale: scale.value }, ], backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', From 7d2955434a4c96b3dec4fcb38af2a9f9049a6f8e Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 26 Mar 2024 13:28:59 +0100 Subject: [PATCH 13/45] fix undetermined state & cleanup --- docs/src/examples/ChartManager.ts | 26 ++++++++------------------ docs/src/examples/ComposedGesture.tsx | 22 ++++++++++++++-------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 4e0bffb0da..e844d27bf6 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -169,25 +169,14 @@ export default class ChartManager { }); } - public newGesture(gesture: GesturesUnion): [GestureHandle, GesturesUnion] { - // HANDLE IS ONLY STORED ON USERSIDE, THIS AVOIDS MEMLEAKS - // OTHERWISE NEW HANDLE WOULD BE CREATED & STORED EVERY RENDER - - // WARNING - // ELEMENTS STILL GET LEAKED, RERENDER ADDS ELEMENTS TO THE POOL - // we should either cleanup old elements, but that's a bad idea, - // rather, it's better to memoize the creation of ids and cbs, - // but then have a getter for them every render so that we can set them manually + public newGesture( + gesture: GesturesUnion + ): [GestureHandle, GesturesUnion, any] { const [beganCallback, beganId] = this.addElement(State.BEGAN); - const [activeCallback, activeId] = this.addElement(State.ACTIVE); - const [endCallback, endId] = this.addElement(State.END); - const [failedCallback, failedId] = this.addElement(State.FAILED); - const [cancelledCallback, cancelledId] = this.addElement(State.CANCELLED); - const [undeterminedCallback, undeterminedId] = this.addElement( State.UNDETERMINED ); @@ -201,9 +190,6 @@ export default class ChartManager { handle.elementIds.set(State.CANCELLED, cancelledId); handle.elementIds.set(State.UNDETERMINED, undeterminedId); - // for now, all of these values have to be hardcoded somewhere either way, and here seems like the most appropriate area. - // in future, we could theoritically only listen to 'onChange' and play around with the states. - // this could take up a little less space, but we would still need to hardcode the flow itself, just in a different format. undeterminedCallback(true); const WAVE_DELAY_MS = 100; @@ -250,6 +236,10 @@ export default class ChartManager { this.addConnection(activeId, endId); this.addConnection(activeId, cancelledId); - return [handle, gesture]; + const resetCb = () => { + undeterminedCallback(true); + }; + + return [handle, gesture, resetCb]; } } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 3abf17fe42..a8081897ee 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import Animated, { useAnimatedStyle, @@ -36,11 +36,11 @@ export default function App() { const chartManager = useRef(new ChartManager()); - const [panHandle, capturedPan] = chartManager.current.newGesture( + const [panHandle, capturedPan, panReset] = chartManager.current.newGesture( Gesture.Pan() ); - const [tapHandle, capturedTap] = chartManager.current.newGesture( + const [tapHandle, capturedTap, tapReset] = chartManager.current.newGesture( Gesture.Tap() ); @@ -55,11 +55,11 @@ export default function App() { // vertical layout // prettier-ignore chartManager.current.layout = [ - [panHeaderId, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE, tapHeaderId], - [panIds.undetermined, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE, tapIds.undetermined], - [panIds.began, panIds.failed, tapIds.failed, tapIds.began], - [panIds.active, panIds.cancelled, tapIds.cancelled, tapIds.active], - [panIds.end, ChartManager.EMPTY_SPACE, ChartManager.EMPTY_SPACE, tapIds.end], + [panHeaderId, ChartManager.EMPTY_SPACE, tapHeaderId, ChartManager.EMPTY_SPACE], + [panIds.undetermined, ChartManager.EMPTY_SPACE, tapIds.undetermined, ChartManager.EMPTY_SPACE], + [panIds.began, panIds.failed, tapIds.began, tapIds.failed], + [panIds.active, panIds.cancelled, tapIds.active, tapIds.cancelled], + [panIds.end, ChartManager.EMPTY_SPACE, tapIds.end, ChartManager.EMPTY_SPACE], ]; const pressed = useSharedValue(false); @@ -104,6 +104,12 @@ export default function App() { backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', })); + useEffect(() => { + // reset on load + panReset(); + tapReset(); + }); + return ( <> From cea1b973090f5b62b051676420f3a29578e3fa23 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 26 Mar 2024 14:51:20 +0100 Subject: [PATCH 14/45] fix crash --- docs/src/examples/FlowChart.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 55333551c2..327f802d05 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -29,6 +29,14 @@ export default function App(props: { chartManager: ChartManager }) { }); elementsCoordsRef.current = elementsRef.current.map((element) => { + // during unloading or overresizing, element may reload itself, causing it to be undefineds + if (!element) { + return { + x: 0, + y: 0, + } as Coordinate; + } + const box = element.getBoundingClientRect(); const root = rootRef.current.getBoundingClientRect(); return { From 2a49d72e56cc526a76b4102c1fb26b3143566ec4 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 26 Mar 2024 15:02:59 +0100 Subject: [PATCH 15/45] replaced tap with longpress --- docs/docs/fundamentals/states-events.mdx | 2 +- docs/src/examples/ChartElement.tsx | 2 +- docs/src/examples/ComposedGesture.tsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index 5a5856f310..67b08f4c7f 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -46,7 +46,7 @@ The flow looks as follows: } - label="Drag or tap the circle" + label="Drag or long press the circle" larger={true} /> diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 04ba17b1a8..d83d8f3b06 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -55,7 +55,7 @@ const styles = StyleSheet.create({ backgroundColor: '#b58df1', }, headerText: { - fontSize: 28, + fontSize: 30, fontWeight: '600', fontFamily: 'var(--ifm-heading-font-family)', margin: 12, diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index a8081897ee..7cafad934c 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -41,14 +41,14 @@ export default function App() { ); const [tapHandle, capturedTap, tapReset] = chartManager.current.newGesture( - Gesture.Tap() + Gesture.LongPress() ); const panIds = panHandle.getIdObject(); const tapIds = tapHandle.getIdObject(); const panHeaderId = chartManager.current.addHeader('Pan Gesture'); - const tapHeaderId = chartManager.current.addHeader('Tap Gesture'); + const tapHeaderId = chartManager.current.addHeader('LongPress Gesture'); // FIXME: tap seems to be broken, and does not follow the typical flow, thus it's quite a bad flow example :P @@ -84,7 +84,7 @@ export default function App() { offset.value = event.translationX; }); - const tap = Gesture.Tap().onStart(() => { + const tap = Gesture.LongPress().onStart(() => { scale.value = withSequence( withSpring(1.8, { duration: 90 }), withSpring(1, { duration: 180, dampingRatio: 0.4 }) From 78a239c1fcbcf3a6fa10baddb05446818ca71071 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 27 Mar 2024 10:50:38 +0100 Subject: [PATCH 16/45] remove hard-coded colors, add transitions to colors, cleanup --- docs/docs/fundamentals/states-events.mdx | 4 +- docs/src/examples/ChartElement.tsx | 70 ++++++++++++++++------ docs/src/examples/ChartManager.ts | 74 +++++++++++++++--------- docs/src/examples/ComposedGesture.tsx | 49 ++++++++-------- docs/src/examples/FlowChart.tsx | 41 ++++++------- 5 files changed, 147 insertions(+), 91 deletions(-) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index 67b08f4c7f..70d0e78581 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -45,7 +45,9 @@ The flow looks as follows: } + component={ + + } label="Drag or long press the circle" larger={true} /> diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index d83d8f3b06..f5572d1aab 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -1,38 +1,72 @@ import { Grid } from '@mui/material'; -import React, { LegacyRef } from 'react'; +import React, { LegacyRef, useEffect } from 'react'; import { StyleProp, StyleSheet, View, ViewStyle, Text } from 'react-native'; +import ChartManager, { ElementData } from './ChartManager'; +import Animated, { + interpolateColor, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from 'react-native-reanimated'; type ChartElementProps = { - id: number; - label: string; - subtext?: string; // optional subtext - position?: null; // todo + data: ElementData; + chartManager: ChartManager; + primaryColor: string; + highlightColor: string; innerRef?: LegacyRef; style?: StyleProp; - isVisible?: boolean; - isHeader?: boolean; }; export default function App({ - label, - subtext, // optional subtext + data, + chartManager, + primaryColor, + highlightColor, innerRef, style, - isVisible, - isHeader, }: ChartElementProps) { + const progress = useSharedValue(0); + + useEffect(() => { + // this listener is cleared through FlowChart + if (data.id != ChartManager.EMPTY_SPACE && !data.isHeader) { + const listenerId = chartManager.addListener(data.id, (isActive) => { + progress.value = withSpring(isActive ? 1 : 0, { duration: 200 }); + }); + + return () => { + chartManager.removeListener(data.id, listenerId); + }; + } + }, [chartManager]); + + const animatedStyle = useAnimatedStyle(() => { + return { + backgroundColor: interpolateColor( + progress.value, + [0, 1], + [primaryColor, highlightColor], + 'RGB' + ), + }; + }); + return ( - - + - {label} - - {subtext} + + {data.label} + + + {data.subtext} ); } diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index e844d27bf6..71fc9943eb 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -1,4 +1,4 @@ -//import ChartElement from './ChartElement'; +//import ElementData from './ElementData'; import { useMemo } from 'react'; import { @@ -13,19 +13,10 @@ import { ManualGesture, HoverGesture, GestureStateChangeEvent, + State, } from 'react-native-gesture-handler'; -import { States } from './ComposedGesture'; - -export enum State { - UNDETERMINED = 'UNDETERMINED', - FAILED = 'FAILED', - BEGAN = 'BEGAN', - CANCELLED = 'CANCELLED', - ACTIVE = 'ACTIVE', - END = 'END', -} -type ChartElement = { +export type ElementData = { id: number; label?: string; subtext?: string; @@ -33,6 +24,15 @@ type ChartElement = { isHeader: boolean; }; +const stateToName = new Map([ + [State.UNDETERMINED, 'UNDETERMINED'], + [State.FAILED, 'FAILED'], + [State.BEGAN, 'BEGAN'], + [State.CANCELLED, 'CANCELLED'], + [State.ACTIVE, 'ACTIVE'], + [State.END, 'END'], +]); + class ChartConnection { id: number; from: number; @@ -61,7 +61,7 @@ const stateConnectionsMap = [ ]; export class GestureHandle { - // within gesture, States can be used as unique IDs pointing to the ChartElement pool + // within gesture, States can be used as unique IDs pointing to the ElementData pool elementIds: Map; elementCbs: Map void>; constructor() { @@ -81,14 +81,12 @@ export class GestureHandle { } export default class ChartManager { - private _elements: ChartElement[] = []; // debug: best structure here is array, because this is just a pool of elements, and thier id's are derived from here anyways + private _elements: ElementData[] = []; // debug: best structure here is array, because this is just a pool of elements, and thier id's are derived from here anyways private _connections: ChartConnection[] = []; // debug: this is a separate pool, fine as well - private _headers: ChartElement[] = []; + private _headers: ElementData[] = []; private _layout: number[][]; - private _listeners: Map void)[]> = useMemo( - () => new Map(), - [] - ); + private _listeners: Map void>> = + useMemo(() => new Map(), []); public static EMPTY_SPACE = 0; @@ -112,9 +110,28 @@ export default class ChartManager { this._layout = layoutGrid; } - public addListener(id: number, listener: (isActive: boolean) => void): void { - if (this._listeners.has(id)) this._listeners.get(id).push(listener); - else this._listeners.set(id, [listener]); + public addListener( + elementId: number, + listener: (isActive: boolean) => void + ): number { + const listenerId = this._listeners.get(elementId)?.size - 1 ?? 0; + + // another map is used inside of _listeners to seamlessly remove listening functions from _listeners + if (this._listeners.has(elementId)) { + this._listeners.get(elementId).set(listenerId, listener); + } else { + this._listeners.set(elementId, new Map([[0, listener]])); + } + + return listenerId; + } + + public removeListener(elementId: number, listenerId: number): void { + this._listeners.get(elementId).delete(listenerId); + } + + public clearListeners(): void { + this._listeners.clear(); } public addElement( @@ -124,7 +141,12 @@ export default class ChartManager { isHeader: boolean = false ): [(isActive: boolean) => void, number] { const newId = this._elements.length; - const newChartElement = { + + if (typeof label == 'number') { + label = stateToName.get(label); + } + + const newElementData = { id: newId, label: label, subtext: subtext, @@ -133,7 +155,7 @@ export default class ChartManager { isHeader: isHeader, }; - this._elements.push(newChartElement); + this._elements.push(newElementData); // this callback will be used by a .onX hook to broadcast this event to all listeners return [ @@ -196,10 +218,10 @@ export default class ChartManager { const resetAllStates = (event: GestureStateChangeEvent) => { undeterminedCallback(true); - if (event.state == States.FAILED) { + if (event.state == 1) { failedCallback(true); } - if (event.state == States.CANCELLED) { + if (event.state == 3) { cancelledCallback(true); } setTimeout(() => { diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 7cafad934c..7328a94956 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import Animated, { useAnimatedStyle, @@ -16,32 +16,20 @@ import { import ChartManager from './ChartManager'; import FlowChart from './FlowChart'; -export enum States { - UNDETERMINED = 0, - FAILED = 1, - BEGAN = 2, - CANCELLED = 3, - ACTIVE = 4, - END = 5, -} -export default function App() { - // if chartman would take care of the connections and also gesture capturing, - // we would only need to segregate between pan and tap - // auto connections - // const panHandler = useMemo(() => chartManager.capture(pan)) - // panHandler.capture(State.BEGAN) // only adds this one to the list - // panHandler.captureAll() // auto connects all that need to be connected - // panHandler.connectAll() // this one is a part of the capAll, will connect based on a lookup list - // layout = [panHandler.beganId, ..., ...] - +export default function App(props: { + primaryColor: string; + highlightColor: string; +}) { const chartManager = useRef(new ChartManager()); - const [panHandle, capturedPan, panReset] = chartManager.current.newGesture( - Gesture.Pan() + const [panHandle, capturedPan, panReset] = useMemo( + () => chartManager.current.newGesture(Gesture.Pan()), + [] ); - const [tapHandle, capturedTap, tapReset] = chartManager.current.newGesture( - Gesture.LongPress() + const [tapHandle, capturedTap, tapReset] = useMemo( + () => chartManager.current.newGesture(Gesture.LongPress()), + [] ); const panIds = panHandle.getIdObject(); @@ -67,6 +55,12 @@ export default function App() { const offset = useSharedValue(0); const scale = useSharedValue(1); + // IMPORTANT + // until the issue with GestureHandlers flow isn't resolved, + // we're adding these temporary arrows connecting BEGAN -> CANCELLED + chartManager.current.addConnection(panIds.began, panIds.cancelled); + chartManager.current.addConnection(tapIds.began, tapIds.cancelled); + // highlight-start const pan = Gesture.Pan() .onBegin(() => { @@ -101,7 +95,7 @@ export default function App() { { translateX: withSpring(offset.value, { duration: 1000 }) }, { scale: scale.value }, ], - backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', + backgroundColor: pressed.value ? props.highlightColor : props.primaryColor, })); useEffect(() => { @@ -113,7 +107,11 @@ export default function App() { return ( <> - + @@ -139,7 +137,6 @@ const styles = StyleSheet.create({ circle: { height: 120, width: 120, - backgroundColor: '#b58df1', borderRadius: 500, cursor: 'grab', }, diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 327f802d05..9c01881359 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -6,28 +6,31 @@ import { Grid } from '@mui/material'; import ChartElement from './ChartElement'; import Arrow from './Arrow'; +const COLOR_DORMANT = '#b58df1'; +const COLOR_ACTIVE = '#ffe04b'; + type Coordinate = { x: number; y: number; }; -export default function App(props: { chartManager: ChartManager }) { - const currentChartManager = props.chartManager; +type FlowChartProps = { + chartManager: ChartManager; + primaryColor: string; + highlightColor: string; +}; + +export default function App({ + chartManager, + primaryColor = COLOR_DORMANT, + highlightColor = COLOR_ACTIVE, +}: FlowChartProps) { const elementsRef = useRef([]); const elementsCoordsRef = useRef([]); const rootRef = useRef(null); const getCenter = (side: number, size: number) => side + size / 2; - currentChartManager.elements.forEach((element) => { - currentChartManager.addListener(element.id, (isActive) => { - if (elementsRef.current[element.id]) - elementsRef.current[element.id].style.backgroundColor = isActive - ? '#ffe04b' - : '#b58df1'; - }); - }); - elementsCoordsRef.current = elementsRef.current.map((element) => { // during unloading or overresizing, element may reload itself, causing it to be undefineds if (!element) { @@ -49,26 +52,25 @@ export default function App(props: { chartManager: ChartManager }) { return ( - {currentChartManager.layout.map((row, index) => ( + {chartManager.layout.map((row, index) => ( {row - .map((elementId) => currentChartManager.elements[elementId]) + .map((elementId) => chartManager.elements[elementId]) .map((element, index) => ( (elementsRef.current[element.id] = el)} - id={element.id} - label={element.label} - subtext={element.subtext} - isVisible={element.isVisible} - isHeader={element.isHeader} + data={element} + primaryColor={primaryColor} + highlightColor={highlightColor} + chartManager={chartManager} /> ))} ))} {elementsCoordsRef.current.length > 0 && - currentChartManager.connections.map((connection, idx) => { + chartManager.connections.map((connection, idx) => { // we have all the connections layed out, // but the user may choose not to use some of the available elements, if ( @@ -112,7 +114,6 @@ const styles = StyleSheet.create({ padding: 30, fontWeight: '500', fontSize: 24, - backgroundColor: '#b58df1', }, subtext: { fontWeight: '300', From 33dcefa2a1e6a0239cf8c24e31093aa63e08f84e Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 27 Mar 2024 12:12:04 +0100 Subject: [PATCH 17/45] added a layout for mobile devices --- docs/src/examples/ChartElement.tsx | 9 +++---- docs/src/examples/ChartManager.ts | 2 -- docs/src/examples/ComposedGesture.tsx | 35 ++++++++++++++++++--------- docs/src/examples/FlowChart.tsx | 18 ++++++++------ 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index f5572d1aab..68aa422990 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -7,7 +7,6 @@ import Animated, { useAnimatedStyle, useSharedValue, withSpring, - withTiming, } from 'react-native-reanimated'; type ChartElementProps = { @@ -16,7 +15,7 @@ type ChartElementProps = { primaryColor: string; highlightColor: string; innerRef?: LegacyRef; - style?: StyleProp; + style?: StyleProp; }; export default function App({ @@ -62,7 +61,7 @@ export default function App({ style, ]} ref={innerRef}> - + {data.label} @@ -76,13 +75,13 @@ const styles = StyleSheet.create({ flex: 1, flexDirection: 'column', textAlign: 'center', - maxWidth: 270, + maxWidth: 900, }, headerBox: { flex: 1, flexDirection: 'column', textAlign: 'center', - maxWidth: 800, + maxWidth: 900, }, element: { padding: 16, diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 71fc9943eb..e800d8cfdf 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -181,12 +181,10 @@ export default class ChartManager { public connectAll(handle: GestureHandle) { stateConnectionsMap.forEach(([fromState, toState]) => { - console.log('adding connection'); const fromId = handle.elementIds.get(fromState); const toId = handle.elementIds.get(toState); if (fromId && toId) { this.addConnection(fromId, toId); - console.log('connected'); } }); } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 7328a94956..fc49c7e2d5 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,6 +1,6 @@ import 'react-native-gesture-handler'; import React, { useEffect, useMemo, useRef } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, View, useWindowDimensions } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, @@ -27,29 +27,41 @@ export default function App(props: { [] ); - const [tapHandle, capturedTap, tapReset] = useMemo( + const [pressHandle, capturedTap, tapReset] = useMemo( () => chartManager.current.newGesture(Gesture.LongPress()), [] ); const panIds = panHandle.getIdObject(); - const tapIds = tapHandle.getIdObject(); + const pressIds = pressHandle.getIdObject(); const panHeaderId = chartManager.current.addHeader('Pan Gesture'); const tapHeaderId = chartManager.current.addHeader('LongPress Gesture'); - // FIXME: tap seems to be broken, and does not follow the typical flow, thus it's quite a bad flow example :P + const dimensions = useWindowDimensions(); + const MAX_PHONE_WIDTH = 768; + const isPhoneMode = dimensions.width < MAX_PHONE_WIDTH; - // vertical layout // prettier-ignore - chartManager.current.layout = [ + const desktopLayout = [ [panHeaderId, ChartManager.EMPTY_SPACE, tapHeaderId, ChartManager.EMPTY_SPACE], - [panIds.undetermined, ChartManager.EMPTY_SPACE, tapIds.undetermined, ChartManager.EMPTY_SPACE], - [panIds.began, panIds.failed, tapIds.began, tapIds.failed], - [panIds.active, panIds.cancelled, tapIds.active, tapIds.cancelled], - [panIds.end, ChartManager.EMPTY_SPACE, tapIds.end, ChartManager.EMPTY_SPACE], + [panIds.undetermined, ChartManager.EMPTY_SPACE, pressIds.undetermined, ChartManager.EMPTY_SPACE], + [panIds.began, panIds.failed, pressIds.began, pressIds.failed], + [panIds.active, panIds.cancelled, pressIds.active, pressIds.cancelled], + [panIds.end, ChartManager.EMPTY_SPACE, pressIds.end, ChartManager.EMPTY_SPACE], ]; + // prettier-ignore + const phoneLayout = [ + [panHeaderId], + [panIds.undetermined], + [panIds.began, panIds.failed, ], + [panIds.active, panIds.cancelled, ], + [panIds.end, ChartManager.EMPTY_SPACE], + ]; + + chartManager.current.layout = isPhoneMode ? phoneLayout : desktopLayout; + const pressed = useSharedValue(false); const offset = useSharedValue(0); @@ -59,7 +71,7 @@ export default function App(props: { // until the issue with GestureHandlers flow isn't resolved, // we're adding these temporary arrows connecting BEGAN -> CANCELLED chartManager.current.addConnection(panIds.began, panIds.cancelled); - chartManager.current.addConnection(tapIds.began, tapIds.cancelled); + chartManager.current.addConnection(pressIds.began, pressIds.cancelled); // highlight-start const pan = Gesture.Pan() @@ -111,6 +123,7 @@ export default function App(props: { chartManager={chartManager.current} primaryColor={props.primaryColor} highlightColor={props.highlightColor} + isPhoneMode={isPhoneMode} /> diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 9c01881359..386818b5bb 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -1,14 +1,11 @@ import 'react-native-gesture-handler'; import React, { useEffect, useRef } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; import ChartElement from './ChartElement'; import Arrow from './Arrow'; -const COLOR_DORMANT = '#b58df1'; -const COLOR_ACTIVE = '#ffe04b'; - type Coordinate = { x: number; y: number; @@ -18,12 +15,14 @@ type FlowChartProps = { chartManager: ChartManager; primaryColor: string; highlightColor: string; + isPhoneMode: boolean; }; export default function App({ chartManager, - primaryColor = COLOR_DORMANT, - highlightColor = COLOR_ACTIVE, + primaryColor, + highlightColor, + isPhoneMode, }: FlowChartProps) { const elementsRef = useRef([]); const elementsCoordsRef = useRef([]); @@ -48,6 +47,10 @@ export default function App({ } as Coordinate; }); + const phoneStyle = { + fontSize: 16, + } as StyleProp; + // get each listener, pass them to the Element, they will change their color on input return ( @@ -64,6 +67,7 @@ export default function App({ primaryColor={primaryColor} highlightColor={highlightColor} chartManager={chartManager} + style={isPhoneMode ? phoneStyle : null} /> ))} @@ -83,7 +87,7 @@ export default function App({ Date: Wed, 27 Mar 2024 13:15:59 +0100 Subject: [PATCH 18/45] fix broken svg line antialiasing --- docs/src/examples/Arrow.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index f71748ccf3..42b33feb4d 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -94,12 +94,15 @@ export default function App({ startPoint, endPoint }: ArrowProps) { transform: `translate(${canvasStartPoint.x}px, ${canvasStartPoint.y}px)`, }}> Date: Wed, 27 Mar 2024 13:35:00 +0100 Subject: [PATCH 19/45] fix antialiasing issues --- docs/src/examples/Arrow.tsx | 3 --- docs/src/examples/ChartElement.tsx | 2 +- docs/src/examples/ChartManager.ts | 3 +++ docs/src/examples/ComposedGesture.tsx | 6 ------ docs/src/examples/FlowChart.tsx | 2 +- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index 42b33feb4d..f71748ccf3 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -94,15 +94,12 @@ export default function App({ startPoint, endPoint }: ArrowProps) { transform: `translate(${canvasStartPoint.x}px, ${canvasStartPoint.y}px)`, }}> { undeterminedCallback(true); }; diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index fc49c7e2d5..aca886616f 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -67,12 +67,6 @@ export default function App(props: { const offset = useSharedValue(0); const scale = useSharedValue(1); - // IMPORTANT - // until the issue with GestureHandlers flow isn't resolved, - // we're adding these temporary arrows connecting BEGAN -> CANCELLED - chartManager.current.addConnection(panIds.began, panIds.cancelled); - chartManager.current.addConnection(pressIds.began, pressIds.cancelled); - // highlight-start const pan = Gesture.Pan() .onBegin(() => { diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 386818b5bb..6c02c6877a 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useEffect, useRef } from 'react'; +import React, { useRef } from 'react'; import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; From 4e9398e9af0a030eb515374c00b022fdde45cebc Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 27 Mar 2024 15:35:39 +0100 Subject: [PATCH 20/45] fix console errors and reactive styling --- docs/src/examples/Arrow.tsx | 6 +++++- docs/src/examples/ChartElement.tsx | 2 +- docs/src/examples/ComposedGesture.tsx | 13 +++++++++---- docs/src/examples/FlowChart.tsx | 8 ++++---- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index f71748ccf3..3791087b29 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -51,7 +51,11 @@ export default function App({ startPoint, endPoint }: ArrowProps) { const truncate = ({ x, y }: Coords, length: number): Coords => { const magnitude = Math.hypot(x, y); - const modifier = length / magnitude; + + let modifier = length / magnitude; + if (!Number.isFinite(modifier)) { + modifier = 0; + } return { x: x * modifier, diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 25bd80c069..cdecedfede 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -84,7 +84,7 @@ const styles = StyleSheet.create({ maxWidth: 900, }, element: { - padding: 16, + paddingVertical: 16, backgroundColor: '#b58df1', }, headerText: { diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index aca886616f..c8a29ad101 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -39,8 +39,13 @@ export default function App(props: { const tapHeaderId = chartManager.current.addHeader('LongPress Gesture'); const dimensions = useWindowDimensions(); - const MAX_PHONE_WIDTH = 768; - const isPhoneMode = dimensions.width < MAX_PHONE_WIDTH; + + // widths pulled from CSS + const MIN_DESKTOP_WIDTH = 1298; + const MAX_PHONE_WIDTH = 996; + + const isPhoneMode = dimensions.width < MIN_DESKTOP_WIDTH; + const isFontReduced = dimensions.width < MAX_PHONE_WIDTH; // prettier-ignore const desktopLayout = [ @@ -108,7 +113,7 @@ export default function App(props: { // reset on load panReset(); tapReset(); - }); + }, []); return ( <> @@ -117,7 +122,7 @@ export default function App(props: { chartManager={chartManager.current} primaryColor={props.primaryColor} highlightColor={props.highlightColor} - isPhoneMode={isPhoneMode} + isFontReduced={isFontReduced} /> diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 6c02c6877a..7a08112a1a 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -15,14 +15,14 @@ type FlowChartProps = { chartManager: ChartManager; primaryColor: string; highlightColor: string; - isPhoneMode: boolean; + isFontReduced: boolean; }; export default function App({ chartManager, primaryColor, highlightColor, - isPhoneMode, + isFontReduced, }: FlowChartProps) { const elementsRef = useRef([]); const elementsCoordsRef = useRef([]); @@ -47,7 +47,7 @@ export default function App({ } as Coordinate; }); - const phoneStyle = { + const tinyFontStyle = { fontSize: 16, } as StyleProp; @@ -67,7 +67,7 @@ export default function App({ primaryColor={primaryColor} highlightColor={highlightColor} chartManager={chartManager} - style={isPhoneMode ? phoneStyle : null} + style={isFontReduced ? tinyFontStyle : null} /> ))} From 476f11a9df8d77898b03f98dcfac227436ab75bc Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 27 Mar 2024 15:58:52 +0100 Subject: [PATCH 21/45] apply review suggestions --- docs/src/examples/ChartElement.tsx | 1 - docs/src/examples/ChartManager.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index cdecedfede..834c71a352 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -29,7 +29,6 @@ export default function App({ const progress = useSharedValue(0); useEffect(() => { - // this listener is cleared through FlowChart if (data.id != ChartManager.EMPTY_SPACE && !data.isHeader) { const listenerId = chartManager.addListener(data.id, (isActive) => { progress.value = withSpring(isActive ? 1 : 0, { duration: 200 }); diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 6d1d7b17dd..2a05dc2ebf 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -216,10 +216,10 @@ export default class ChartManager { const resetAllStates = (event: GestureStateChangeEvent) => { undeterminedCallback(true); - if (event.state == 1) { + if (event.state == State.FAILED) { failedCallback(true); } - if (event.state == 3) { + if (event.state == State.CANCELLED) { cancelledCallback(true); } setTimeout(() => { From f1b6921f012207b8bba6b992447239d41bb12d36 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Thu, 28 Mar 2024 09:53:04 +0100 Subject: [PATCH 22/45] cleanup --- docs/src/examples/ChartElement.tsx | 2 +- docs/src/examples/ChartManager.ts | 35 +++++---------------------- docs/src/examples/ComposedGesture.tsx | 18 +++++++------- docs/src/examples/FlowChart.tsx | 5 ++-- 4 files changed, 18 insertions(+), 42 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 834c71a352..7565bc39fe 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -29,7 +29,7 @@ export default function App({ const progress = useSharedValue(0); useEffect(() => { - if (data.id != ChartManager.EMPTY_SPACE && !data.isHeader) { + if (data.id != ChartManager.EMPTY_SPACE_ID && !data.isHeader) { const listenerId = chartManager.addListener(data.id, (isActive) => { progress.value = withSpring(isActive ? 1 : 0, { duration: 200 }); }); diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 2a05dc2ebf..45e67e62af 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -51,15 +51,6 @@ type GesturesUnion = | ManualGesture | HoverGesture; -// FROM, TO -const stateConnectionsMap = [ - [State.UNDETERMINED, State.BEGAN], - [State.BEGAN, State.ACTIVE], - [State.BEGAN, State.FAILED], - [State.ACTIVE, State.END], - [State.ACTIVE, State.CANCELLED], -]; - export class GestureHandle { // within gesture, States can be used as unique IDs pointing to the ElementData pool elementIds: Map; @@ -81,14 +72,13 @@ export class GestureHandle { } export default class ChartManager { - private _elements: ElementData[] = []; // debug: best structure here is array, because this is just a pool of elements, and thier id's are derived from here anyways - private _connections: ChartConnection[] = []; // debug: this is a separate pool, fine as well - private _headers: ElementData[] = []; + private _elements: ElementData[] = []; + private _connections: ChartConnection[] = []; private _layout: number[][]; private _listeners: Map void>> = useMemo(() => new Map(), []); - public static EMPTY_SPACE = 0; + public static EMPTY_SPACE_ID = 0; constructor() { this.addElement(null, null, false); @@ -167,8 +157,8 @@ export default class ChartManager { } public addHeader(text: string): number { - // todo: add elements which can display text, and fill in all of the assigned space - return this.addElement(text, null, true, true)[1]; + const [_, headerId] = this.addElement(text, null, true, true); + return headerId; } public addConnection(fromId: number, toId: number) { @@ -179,16 +169,6 @@ export default class ChartManager { }); } - public connectAll(handle: GestureHandle) { - stateConnectionsMap.forEach(([fromState, toState]) => { - const fromId = handle.elementIds.get(fromState); - const toId = handle.elementIds.get(toState); - if (fromId && toId) { - this.addConnection(fromId, toId); - } - }); - } - public newGesture( gesture: GesturesUnion ): [GestureHandle, GesturesUnion, any] { @@ -233,7 +213,6 @@ export default class ChartManager { }, 2 * WAVE_DELAY_MS); }; - // highlight-start gesture .onBegin(() => { beganCallback(true); @@ -246,7 +225,7 @@ export default class ChartManager { .onEnd(() => { endCallback(true); }) - .onFinalize((event) => { + .onFinalize((event: GestureStateChangeEvent) => { resetAllStates(event); }); @@ -255,8 +234,6 @@ export default class ChartManager { this.addConnection(beganId, failedId); this.addConnection(activeId, endId); this.addConnection(activeId, cancelledId); - - // todo: once faulty GH flow is fixed, this connection should be removed this.addConnection(beganId, cancelledId); const resetCb = () => { diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index c8a29ad101..4b57726ec3 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -27,7 +27,7 @@ export default function App(props: { [] ); - const [pressHandle, capturedTap, tapReset] = useMemo( + const [pressHandle, capturedPress, pressReset] = useMemo( () => chartManager.current.newGesture(Gesture.LongPress()), [] ); @@ -49,11 +49,11 @@ export default function App(props: { // prettier-ignore const desktopLayout = [ - [panHeaderId, ChartManager.EMPTY_SPACE, tapHeaderId, ChartManager.EMPTY_SPACE], - [panIds.undetermined, ChartManager.EMPTY_SPACE, pressIds.undetermined, ChartManager.EMPTY_SPACE], + [panHeaderId, ChartManager.EMPTY_SPACE_ID, tapHeaderId, ChartManager.EMPTY_SPACE_ID], + [panIds.undetermined, ChartManager.EMPTY_SPACE_ID, pressIds.undetermined, ChartManager.EMPTY_SPACE_ID], [panIds.began, panIds.failed, pressIds.began, pressIds.failed], [panIds.active, panIds.cancelled, pressIds.active, pressIds.cancelled], - [panIds.end, ChartManager.EMPTY_SPACE, pressIds.end, ChartManager.EMPTY_SPACE], + [panIds.end, ChartManager.EMPTY_SPACE_ID, pressIds.end, ChartManager.EMPTY_SPACE_ID], ]; // prettier-ignore @@ -62,7 +62,7 @@ export default function App(props: { [panIds.undetermined], [panIds.began, panIds.failed, ], [panIds.active, panIds.cancelled, ], - [panIds.end, ChartManager.EMPTY_SPACE], + [panIds.end, ChartManager.EMPTY_SPACE_ID], ]; chartManager.current.layout = isPhoneMode ? phoneLayout : desktopLayout; @@ -89,7 +89,7 @@ export default function App(props: { offset.value = event.translationX; }); - const tap = Gesture.LongPress().onStart(() => { + const press = Gesture.LongPress().onStart(() => { scale.value = withSequence( withSpring(1.8, { duration: 90 }), withSpring(1, { duration: 180, dampingRatio: 0.4 }) @@ -98,8 +98,8 @@ export default function App(props: { // highlight-end const composedPan = Gesture.Simultaneous(pan, capturedPan); - const composedTap = Gesture.Simultaneous(tap, capturedTap); - const composed = Gesture.Race(composedPan, composedTap); + const composedPress = Gesture.Simultaneous(press, capturedPress); + const composed = Gesture.Race(composedPan, composedPress); const animatedStyles = useAnimatedStyle(() => ({ transform: [ @@ -112,7 +112,7 @@ export default function App(props: { useEffect(() => { // reset on load panReset(); - tapReset(); + pressReset(); }, []); return ( diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 7a08112a1a..d9c0012839 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -31,7 +31,7 @@ export default function App({ const getCenter = (side: number, size: number) => side + size / 2; elementsCoordsRef.current = elementsRef.current.map((element) => { - // during unloading or overresizing, element may reload itself, causing it to be undefineds + // during unloading or overresizing, element may reload itself, causing it to be undefined if (!element) { return { x: 0, @@ -51,7 +51,6 @@ export default function App({ fontSize: 16, } as StyleProp; - // get each listener, pass them to the Element, they will change their color on input return ( @@ -81,7 +80,7 @@ export default function App({ !elementsCoordsRef.current[connection.from] || !elementsCoordsRef.current[connection.to] ) { - return ; + return ; } return ( Date: Thu, 28 Mar 2024 10:46:46 +0100 Subject: [PATCH 23/45] cleanup --- docs/src/examples/Arrow.tsx | 22 +++++++++------ docs/src/examples/ChartElement.tsx | 39 ++++++++++++++++++--------- docs/src/examples/ComposedGesture.tsx | 12 ++++----- docs/src/examples/FlowChart.tsx | 8 +++--- 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index 3791087b29..88f03045a7 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -32,19 +32,25 @@ export default function App({ startPoint, endPoint }: ArrowProps) { canvasStartPoint.x -= halfPadding; canvasStartPoint.y -= halfPadding; + // adjust coordinates by canvas global offset + startPoint.x = startPoint.x - canvasStartPoint.x; + startPoint.y = startPoint.y - canvasStartPoint.y; + endPoint.x = endPoint.x - canvasStartPoint.x; + endPoint.y = endPoint.y - canvasStartPoint.y; + const avg = (a: number, b: number) => (a + b) / 2; // we will be drawing two deflections from midpoint to origin const arrowDeflection = 45; const arrowLength = 10; const midPoint = { - x: avg(startPoint.x - canvasStartPoint.x, endPoint.x - canvasStartPoint.x), - y: avg(startPoint.y - canvasStartPoint.y, endPoint.y - canvasStartPoint.y), + x: avg(startPoint.x, endPoint.x), + y: avg(startPoint.y, endPoint.y), }; const midToOriginVector = { - x: midPoint.x - endPoint.x + canvasStartPoint.x, - y: midPoint.y - endPoint.y + canvasStartPoint.y, + x: midPoint.x - endPoint.x, + y: midPoint.y - endPoint.y, }; type Coords = { x: number; y: number }; @@ -100,10 +106,10 @@ export default function App({ startPoint, endPoint }: ArrowProps) { { - if (data.id != ChartManager.EMPTY_SPACE_ID && !data.isHeader) { - const listenerId = chartManager.addListener(data.id, (isActive) => { - progress.value = withSpring(isActive ? 1 : 0, { duration: 200 }); - }); + if ( + elementData.id != ChartManager.EMPTY_SPACE_ID && + !elementData.isHeader + ) { + const listenerId = chartManager.addListener( + elementData.id, + (isActive) => { + progress.value = withSpring(isActive ? 1 : 0, { duration: 200 }); + } + ); return () => { - chartManager.removeListener(data.id, listenerId); + chartManager.removeListener(elementData.id, listenerId); }; } }, [chartManager]); @@ -52,19 +58,26 @@ export default function App({ }); return ( - + - - {data.label} + + {elementData.label} - {data.subtext} + {elementData.subtext} ); } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 4b57726ec3..3b3ec29ccb 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -36,7 +36,7 @@ export default function App(props: { const pressIds = pressHandle.getIdObject(); const panHeaderId = chartManager.current.addHeader('Pan Gesture'); - const tapHeaderId = chartManager.current.addHeader('LongPress Gesture'); + const pressHeaderId = chartManager.current.addHeader('LongPress Gesture'); const dimensions = useWindowDimensions(); @@ -49,10 +49,10 @@ export default function App(props: { // prettier-ignore const desktopLayout = [ - [panHeaderId, ChartManager.EMPTY_SPACE_ID, tapHeaderId, ChartManager.EMPTY_SPACE_ID], + [panHeaderId, ChartManager.EMPTY_SPACE_ID, pressHeaderId, ChartManager.EMPTY_SPACE_ID], [panIds.undetermined, ChartManager.EMPTY_SPACE_ID, pressIds.undetermined, ChartManager.EMPTY_SPACE_ID], - [panIds.began, panIds.failed, pressIds.began, pressIds.failed], - [panIds.active, panIds.cancelled, pressIds.active, pressIds.cancelled], + [panIds.began, panIds.failed, pressIds.began, pressIds.failed], + [panIds.active, panIds.cancelled, pressIds.active, pressIds.cancelled], [panIds.end, ChartManager.EMPTY_SPACE_ID, pressIds.end, ChartManager.EMPTY_SPACE_ID], ]; @@ -60,8 +60,8 @@ export default function App(props: { const phoneLayout = [ [panHeaderId], [panIds.undetermined], - [panIds.began, panIds.failed, ], - [panIds.active, panIds.cancelled, ], + [panIds.began, panIds.failed], + [panIds.active, panIds.cancelled], [panIds.end, ChartManager.EMPTY_SPACE_ID], ]; diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index d9c0012839..6da5ae73c8 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -1,6 +1,6 @@ import 'react-native-gesture-handler'; import React, { useRef } from 'react'; -import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import { StyleProp, StyleSheet, View } from 'react-native'; import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; import ChartElement from './ChartElement'; @@ -49,7 +49,7 @@ export default function App({ const tinyFontStyle = { fontSize: 16, - } as StyleProp; + } as StyleProp; return ( @@ -62,7 +62,7 @@ export default function App({ (elementsRef.current[element.id] = el)} - data={element} + elementData={element} primaryColor={primaryColor} highlightColor={highlightColor} chartManager={chartManager} @@ -73,7 +73,7 @@ export default function App({ ))} {elementsCoordsRef.current.length > 0 && - chartManager.connections.map((connection, idx) => { + chartManager.connections.map((connection) => { // we have all the connections layed out, // but the user may choose not to use some of the available elements, if ( From 72673839727e41b35a5868f5e2c6d62d81d5725b Mon Sep 17 00:00:00 2001 From: LatekVo Date: Thu, 28 Mar 2024 13:26:47 +0100 Subject: [PATCH 24/45] UI overhaul --- docs/docs/fundamentals/states-events.mdx | 4 +-- docs/src/examples/Arrow.tsx | 45 +++++++++++------------- docs/src/examples/ChartElement.tsx | 43 +++++++++++++--------- docs/src/examples/ChartManager.ts | 25 +++++++++++-- docs/src/examples/ComposedGesture.tsx | 9 ++--- docs/src/examples/FlowChart.tsx | 11 +----- 6 files changed, 74 insertions(+), 63 deletions(-) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index 70d0e78581..67b08f4c7f 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -45,9 +45,7 @@ The flow looks as follows: - } + component={} label="Drag or long press the circle" larger={true} /> diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index 88f03045a7..456d8dde99 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -41,8 +41,6 @@ export default function App({ startPoint, endPoint }: ArrowProps) { const avg = (a: number, b: number) => (a + b) / 2; // we will be drawing two deflections from midpoint to origin - const arrowDeflection = 45; - const arrowLength = 10; const midPoint = { x: avg(startPoint.x, endPoint.x), y: avg(startPoint.y, endPoint.y), @@ -69,10 +67,7 @@ export default function App({ startPoint, endPoint }: ArrowProps) { }; }; - const rotate = ( - { x, y }: { x: number; y: number }, - rotation: number - ): Coords => { + const rotate = ({ x, y }: Coords, rotation: number): Coords => { const rotationRadians = (Math.PI * rotation) / 180; const cosResult = Math.cos(rotationRadians); const sinResult = Math.sin(rotationRadians); @@ -82,13 +77,21 @@ export default function App({ startPoint, endPoint }: ArrowProps) { }; }; - const deflectionVectorLeft = rotate( - truncate(midToOriginVector, arrowLength), - arrowDeflection - ); - const deflectionVectorRight = rotate( - truncate(midToOriginVector, arrowLength), - -arrowDeflection + const reverse = ({ x, y }: Coords): Coords => { + return { + x: -x, + y: -y, + }; + }; + + const arrowLength = 9; + + const truncatedVector = truncate(midToOriginVector, arrowLength); + + const deflectionVectorLeft = rotate(truncatedVector, 50); + const deflectionVectorRight = rotate(truncatedVector, -50); + const deflectionVectorExtender = reverse( + truncate(midToOriginVector, arrowLength / 1.8) ); return ( @@ -103,20 +106,12 @@ export default function App({ startPoint, endPoint }: ArrowProps) { backgroundColor: 'transparent', transform: `translate(${canvasStartPoint.x}px, ${canvasStartPoint.y}px)`, }}> - @@ -124,8 +119,8 @@ export default function App({ startPoint, endPoint }: ArrowProps) { stroke="#aaa" strokeLinecap="round" strokeWidth={strokeWidth} - x1={midPoint.x} - y1={midPoint.y} + x1={midPoint.x + deflectionVectorExtender.x} + y1={midPoint.y + deflectionVectorExtender.y} x2={midPoint.x + deflectionVectorLeft.x} y2={midPoint.y + deflectionVectorLeft.y} /> diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 7b24905993..691e74d94d 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -1,9 +1,8 @@ import { Grid } from '@mui/material'; import React, { LegacyRef, useEffect } from 'react'; import { StyleProp, StyleSheet, View, Text } from 'react-native'; -import ChartManager, { ElementData } from './ChartManager'; +import ChartManager, { ElementData, WAVE_DELAY_MS } from './ChartManager'; import Animated, { - interpolateColor, useAnimatedStyle, useSharedValue, withSpring, @@ -12,8 +11,6 @@ import Animated, { type ChartElementProps = { elementData: ElementData; chartManager: ChartManager; - primaryColor: string; - highlightColor: string; innerRef?: LegacyRef; style?: StyleProp; }; @@ -21,8 +18,6 @@ type ChartElementProps = { export default function App({ elementData, chartManager, - primaryColor, - highlightColor, innerRef, style, }: ChartElementProps) { @@ -36,7 +31,9 @@ export default function App({ const listenerId = chartManager.addListener( elementData.id, (isActive) => { - progress.value = withSpring(isActive ? 1 : 0, { duration: 200 }); + progress.value = withSpring(isActive ? 1 : 0, { + duration: 2 * WAVE_DELAY_MS, + }); } ); @@ -48,12 +45,19 @@ export default function App({ const animatedStyle = useAnimatedStyle(() => { return { - backgroundColor: interpolateColor( - progress.value, - [0, 1], - [primaryColor, highlightColor], - 'RGB' - ), + backgroundColor: + progress.value > 0.5 + ? elementData.highlightColor + : 'var(--ifm-background-color)', + }; + }); + + const animatedTextStyle = useAnimatedStyle(() => { + return { + color: + progress.value > 0.5 + ? 'var(--swm-navy-light-100)' + : 'var(--swm-border)', }; }); @@ -69,13 +73,14 @@ export default function App({ style, ]} ref={innerRef}> - {elementData.label} - + {elementData.subtext} @@ -97,15 +102,21 @@ const styles = StyleSheet.create({ }, element: { paddingVertical: 16, - backgroundColor: '#b58df1', + backgroundColor: 'var(--ifm-background-color)', + borderWidth: 1, + borderColor: 'var(--swm-border)', + transition: 'background-color 200ms ease-in-out', }, headerText: { fontSize: 30, fontWeight: '600', fontFamily: 'var(--ifm-heading-font-family)', + color: 'var(--ifm-font-color-base)', margin: 12, }, label: { + color: 'var(--swm-border)', + transition: 'color 200ms ease-in-out', fontWeight: '500', fontSize: 22, }, diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 45e67e62af..4379ad7416 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -16,12 +16,15 @@ import { State, } from 'react-native-gesture-handler'; +export const WAVE_DELAY_MS = 150; + export type ElementData = { id: number; label?: string; subtext?: string; isVisible: boolean; isHeader: boolean; + highlightColor: string; }; const stateToName = new Map([ @@ -124,6 +127,23 @@ export default class ChartManager { this._listeners.clear(); } + private static getStateHighlightColor(label: string): string { + switch (label) { + case stateToName.get(State.BEGAN): + return 'var(--swm-blue-light-80)'; + case stateToName.get(State.ACTIVE): + return 'var(--swm-green-light-80)'; + case stateToName.get(State.END): + return 'var(--swm-blue-light-80)'; + case stateToName.get(State.FAILED): + return 'var(--swm-red-light-80)'; + case stateToName.get(State.CANCELLED): + return 'var(--swm-red-light-80)'; + default: + return 'var(--swm-yellow-light-80)'; + } + } + public addElement( label: State | string = null, subtext: string | null = null, @@ -136,6 +156,8 @@ export default class ChartManager { label = stateToName.get(label); } + let highlightColor = ChartManager.getStateHighlightColor(label); + const newElementData = { id: newId, label: label, @@ -143,6 +165,7 @@ export default class ChartManager { position: null, isVisible: isVisible, isHeader: isHeader, + highlightColor: highlightColor, }; this._elements.push(newElementData); @@ -192,8 +215,6 @@ export default class ChartManager { undeterminedCallback(true); - const WAVE_DELAY_MS = 100; - const resetAllStates = (event: GestureStateChangeEvent) => { undeterminedCallback(true); if (event.state == State.FAILED) { diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 3b3ec29ccb..3dfd983f19 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -16,10 +16,7 @@ import { import ChartManager from './ChartManager'; import FlowChart from './FlowChart'; -export default function App(props: { - primaryColor: string; - highlightColor: string; -}) { +export default function App() { const chartManager = useRef(new ChartManager()); const [panHandle, capturedPan, panReset] = useMemo( @@ -106,7 +103,7 @@ export default function App(props: { { translateX: withSpring(offset.value, { duration: 1000 }) }, { scale: scale.value }, ], - backgroundColor: pressed.value ? props.highlightColor : props.primaryColor, + backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', })); useEffect(() => { @@ -120,8 +117,6 @@ export default function App(props: { diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 6da5ae73c8..0cd264af7d 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -13,17 +13,10 @@ type Coordinate = { type FlowChartProps = { chartManager: ChartManager; - primaryColor: string; - highlightColor: string; isFontReduced: boolean; }; -export default function App({ - chartManager, - primaryColor, - highlightColor, - isFontReduced, -}: FlowChartProps) { +export default function App({ chartManager, isFontReduced }: FlowChartProps) { const elementsRef = useRef([]); const elementsCoordsRef = useRef([]); const rootRef = useRef(null); @@ -63,8 +56,6 @@ export default function App({ key={index} innerRef={(el) => (elementsRef.current[element.id] = el)} elementData={element} - primaryColor={primaryColor} - highlightColor={highlightColor} chartManager={chartManager} style={isFontReduced ? tinyFontStyle : null} /> From 80411511d7542b403904ee217451a5fc49b974f1 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Thu, 28 Mar 2024 13:50:43 +0100 Subject: [PATCH 25/45] remove borders from active elements --- docs/docs/fundamentals/states-events.mdx | 2 +- docs/src/examples/ChartElement.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index 67b08f4c7f..c713721dbf 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -46,7 +46,7 @@ The flow looks as follows: } - label="Drag or long press the circle" + label="Drag or long-press the circle" larger={true} /> diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 691e74d94d..38f0acb6e4 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -49,6 +49,7 @@ export default function App({ progress.value > 0.5 ? elementData.highlightColor : 'var(--ifm-background-color)', + borderColor: progress.value > 0.5 ? 'transparent' : 'var(--swm-border)', }; }); @@ -105,7 +106,7 @@ const styles = StyleSheet.create({ backgroundColor: 'var(--ifm-background-color)', borderWidth: 1, borderColor: 'var(--swm-border)', - transition: 'background-color 200ms ease-in-out', + transition: 'all 200ms ease-in-out', }, headerText: { fontSize: 30, From 054148816f925058570e3038c9271285046599e9 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Thu, 28 Mar 2024 14:16:40 +0100 Subject: [PATCH 26/45] adjust arrows colors and add custom easing --- docs/src/examples/Arrow.tsx | 6 ++++-- docs/src/examples/ChartElement.tsx | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/Arrow.tsx index 456d8dde99..c5f84ad540 100644 --- a/docs/src/examples/Arrow.tsx +++ b/docs/src/examples/Arrow.tsx @@ -94,6 +94,8 @@ export default function App({ startPoint, endPoint }: ArrowProps) { truncate(midToOriginVector, arrowLength / 1.8) ); + const arrowColor = 'var(--swm-border)'; + return ( Date: Fri, 29 Mar 2024 10:31:36 +0100 Subject: [PATCH 27/45] simplify code --- docs/src/examples/ChartManager.ts | 94 ++++++++++++++------------- docs/src/examples/ComposedGesture.tsx | 4 +- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 4379ad7416..ae50cd9611 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -17,6 +17,12 @@ import { } from 'react-native-gesture-handler'; export const WAVE_DELAY_MS = 150; +const Colors = { + BLUE: 'var(--swm-blue-light-80)', + GREEN: 'var(--swm-green-light-80)', + YELLOW: 'var(--swm-yellow-light-80)', + RED: 'var(--swm-red-light-80)', +}; export type ElementData = { id: number; @@ -36,6 +42,15 @@ const stateToName = new Map([ [State.END, 'END'], ]); +const labelColorMap = new Map([ + [stateToName.get(State.BEGAN), Colors.BLUE], + [stateToName.get(State.ACTIVE), Colors.GREEN], + [stateToName.get(State.END), Colors.BLUE], + [stateToName.get(State.FAILED), Colors.RED], + [stateToName.get(State.CANCELLED), Colors.RED], + [stateToName.get(State.UNDETERMINED), Colors.YELLOW], +]); + class ChartConnection { id: number; from: number; @@ -54,23 +69,24 @@ type GesturesUnion = | ManualGesture | HoverGesture; +type IdObject = { + began: number; + active: number; + end: number; + failed: number; + cancelled: number; + undetermined: number; +}; + export class GestureHandle { // within gesture, States can be used as unique IDs pointing to the ElementData pool - elementIds: Map; - elementCbs: Map void>; - constructor() { - this.elementIds = new Map(); - this.elementCbs = new Map(); + _elementIds: IdObject; + + get idObject() { + return this._elementIds; } - getIdObject() { - return { - began: this.elementIds.get(State.BEGAN), - active: this.elementIds.get(State.ACTIVE), - end: this.elementIds.get(State.END), - failed: this.elementIds.get(State.FAILED), - cancelled: this.elementIds.get(State.CANCELLED), - undetermined: this.elementIds.get(State.UNDETERMINED), - }; + set idObject(newObject: IdObject) { + this._elementIds = newObject; } } @@ -127,23 +143,6 @@ export default class ChartManager { this._listeners.clear(); } - private static getStateHighlightColor(label: string): string { - switch (label) { - case stateToName.get(State.BEGAN): - return 'var(--swm-blue-light-80)'; - case stateToName.get(State.ACTIVE): - return 'var(--swm-green-light-80)'; - case stateToName.get(State.END): - return 'var(--swm-blue-light-80)'; - case stateToName.get(State.FAILED): - return 'var(--swm-red-light-80)'; - case stateToName.get(State.CANCELLED): - return 'var(--swm-red-light-80)'; - default: - return 'var(--swm-yellow-light-80)'; - } - } - public addElement( label: State | string = null, subtext: string | null = null, @@ -156,7 +155,7 @@ export default class ChartManager { label = stateToName.get(label); } - let highlightColor = ChartManager.getStateHighlightColor(label); + let highlightColor = labelColorMap.get(label) ?? Colors.YELLOW; const newElementData = { id: newId, @@ -205,13 +204,14 @@ export default class ChartManager { ); const handle = new GestureHandle(); - - handle.elementIds.set(State.BEGAN, beganId); - handle.elementIds.set(State.ACTIVE, activeId); - handle.elementIds.set(State.END, endId); - handle.elementIds.set(State.FAILED, failedId); - handle.elementIds.set(State.CANCELLED, cancelledId); - handle.elementIds.set(State.UNDETERMINED, undeterminedId); + handle.idObject = { + began: beganId, + active: activeId, + end: endId, + failed: failedId, + cancelled: cancelledId, + undetermined: undeterminedId, + } as IdObject; undeterminedCallback(true); @@ -250,12 +250,16 @@ export default class ChartManager { resetAllStates(event); }); - this.addConnection(undeterminedId, beganId); - this.addConnection(beganId, activeId); - this.addConnection(beganId, failedId); - this.addConnection(activeId, endId); - this.addConnection(activeId, cancelledId); - this.addConnection(beganId, cancelledId); + [ + [undeterminedId, beganId], + [beganId, activeId], + [beganId, failedId], + [activeId, endId], + [activeId, cancelledId], + [beganId, cancelledId], + ].forEach(([from, to]) => { + this.addConnection(from, to); + }); const resetCb = () => { undeterminedCallback(true); diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 3dfd983f19..294c3b74f4 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -29,8 +29,8 @@ export default function App() { [] ); - const panIds = panHandle.getIdObject(); - const pressIds = pressHandle.getIdObject(); + const panIds = panHandle.idObject; + const pressIds = pressHandle.idObject; const panHeaderId = chartManager.current.addHeader('Pan Gesture'); const pressHeaderId = chartManager.current.addHeader('LongPress Gesture'); From 3e876ec88e3f8634adcf47a5699347fcdadcf371 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 11:28:24 +0200 Subject: [PATCH 28/45] Remove redundant comment --- docs/src/components/AnimableIcon/styles.module.css | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/components/AnimableIcon/styles.module.css b/docs/src/components/AnimableIcon/styles.module.css index 0806c84be6..f972f1294d 100644 --- a/docs/src/components/AnimableIcon/styles.module.css +++ b/docs/src/components/AnimableIcon/styles.module.css @@ -6,7 +6,6 @@ padding: 0.25em; cursor: pointer; - /* Border applied to omit enlarging icon during the animation. */ border: 1px solid transparent; border-radius: 3px; } From 78d85329b71495984a116967657ad06d1c149183 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 11:59:02 +0200 Subject: [PATCH 29/45] Simplify some code --- docs/src/components/AnimableIcon/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/components/AnimableIcon/index.tsx b/docs/src/components/AnimableIcon/index.tsx index e9cd764db5..c582c6925f 100644 --- a/docs/src/components/AnimableIcon/index.tsx +++ b/docs/src/components/AnimableIcon/index.tsx @@ -24,7 +24,7 @@ const AnimableIcon = ({ const [actionPerformed, setActionPerformed] = React.useState(false); useEffect(() => { - const timeout = setTimeout(() => setActionPerformed(() => false), 1000); + const timeout = setTimeout(() => setActionPerformed(false), 1000); return () => clearTimeout(timeout); }, [actionPerformed]); From 6a24f07e853d4050aab89b7df541fedf31af3294 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 13:55:47 +0200 Subject: [PATCH 30/45] Change spring config in example --- docs/src/examples/ComposedGesture.tsx | 19 +++++++++---------- docs/src/examples/FlowChart.tsx | 5 +++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 294c3b74f4..ee716a4de2 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -69,16 +69,15 @@ export default function App() { const offset = useSharedValue(0); const scale = useSharedValue(1); - // highlight-start const pan = Gesture.Pan() .onBegin(() => { pressed.value = true; }) .onStart(() => { - scale.value = withSpring(0.6, { duration: 150 }); + scale.value = withSpring(0.7); }) .onFinalize(() => { - offset.value = withSpring(0, { duration: 200 }); + offset.value = withSpring(0, { damping: 20, stiffness: 150 }); scale.value = withTiming(1); pressed.value = false; }) @@ -86,13 +85,13 @@ export default function App() { offset.value = event.translationX; }); - const press = Gesture.LongPress().onStart(() => { - scale.value = withSequence( - withSpring(1.8, { duration: 90 }), - withSpring(1, { duration: 180, dampingRatio: 0.4 }) - ); - }); - // highlight-end + const press = Gesture.LongPress() + .onStart(() => { + scale.value = withSpring(1.3, { stiffness: 175 }); + }) + .onFinalize(() => { + scale.value = withTiming(1); + }); const composedPan = Gesture.Simultaneous(pan, capturedPan); const composedPress = Gesture.Simultaneous(press, capturedPress); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 0cd264af7d..ca00227e64 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -71,7 +71,7 @@ export default function App({ chartManager, isFontReduced }: FlowChartProps) { !elementsCoordsRef.current[connection.from] || !elementsCoordsRef.current[connection.to] ) { - return ; + return ; } return ( + }} + /> ); })} From fc7f281f65d60978f4759b03b8e2bebcb77fb016 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 14:19:47 +0200 Subject: [PATCH 31/45] Hide src buttons when src not provided --- docs/docs/fundamentals/states-events.mdx | 2 - .../components/InteractiveExample/index.tsx | 70 ++++++++++--------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index c713721dbf..de3ceb82e5 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -37,14 +37,12 @@ A gesture can be in one of the six possible states: ## State flows import ComposedGesture from '@site/src/examples/ComposedGesture'; -import ComposedGestureSrc from '!!raw-loader!@site/src/examples/ComposedGesture'; The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. The flow looks as follows: } label="Drag or long-press the circle" larger={true} diff --git a/docs/src/components/InteractiveExample/index.tsx b/docs/src/components/InteractiveExample/index.tsx index 595eab2e42..720c66ce9c 100644 --- a/docs/src/components/InteractiveExample/index.tsx +++ b/docs/src/components/InteractiveExample/index.tsx @@ -48,41 +48,43 @@ export default function InteractiveExample({ ${!showPreview ? styles.code : ''}`} data-ispreview={showPreview}> {showPreview && prefersReducedMotion && } -
-
- - + {src && ( +
+
+ + +
+ } + iconDark={} + animation={Animation.FADE_IN_OUT} + onClick={(actionPerformed, setActionPerformed) => { + if (!actionPerformed) { + copy(src); + setActionPerformed(true); + } + }} + />
- } - iconDark={} - animation={Animation.FADE_IN_OUT} - onClick={(actionPerformed, setActionPerformed) => { - if (!actionPerformed) { - copy(src); - setActionPerformed(true); - } - }} - /> -
+ )}
{showPreview ? ( <> From d3c01159e605c3e09429ab6f21ce5c825415a6d1 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 15:03:14 +0200 Subject: [PATCH 32/45] Simplify --- docs/src/examples/ChartElement.tsx | 9 ++--- docs/src/examples/ChartManager.ts | 10 ++---- docs/src/examples/ComposedGesture.tsx | 52 +++++++++++++++------------ docs/src/examples/FlowChart.tsx | 19 ---------- 4 files changed, 34 insertions(+), 56 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index ffdd8804ea..6eba861cea 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -8,9 +8,6 @@ import Animated, { withSpring, } from 'react-native-reanimated'; -const TRANSITION_FUNCTION = ' cubic-bezier(.8,0,.8,1)'; -const TRANSITION_DURATION = ' 200ms'; - type ChartElementProps = { elementData: ElementData; chartManager: ChartManager; @@ -18,7 +15,7 @@ type ChartElementProps = { style?: StyleProp; }; -export default function App({ +export default function ChartElement({ elementData, chartManager, innerRef, @@ -109,7 +106,7 @@ const styles = StyleSheet.create({ backgroundColor: 'var(--ifm-background-color)', borderWidth: 1, borderColor: 'var(--swm-border)', - transition: 'all' + TRANSITION_DURATION + TRANSITION_FUNCTION, + transition: 'all 350ms ease-in-out', }, headerText: { fontSize: 30, @@ -120,7 +117,7 @@ const styles = StyleSheet.create({ }, label: { color: 'var(--swm-border)', - transition: 'color' + TRANSITION_DURATION + TRANSITION_FUNCTION, + transition: 'color 350ms ease-in-out', fontWeight: '500', fontSize: 22, }, diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index ae50cd9611..b9af4c9df2 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -191,9 +191,7 @@ export default class ChartManager { }); } - public newGesture( - gesture: GesturesUnion - ): [GestureHandle, GesturesUnion, any] { + public newGesture(gesture: GesturesUnion): [GestureHandle, GesturesUnion] { const [beganCallback, beganId] = this.addElement(State.BEGAN); const [activeCallback, activeId] = this.addElement(State.ACTIVE); const [endCallback, endId] = this.addElement(State.END); @@ -261,10 +259,6 @@ export default class ChartManager { this.addConnection(from, to); }); - const resetCb = () => { - undeterminedCallback(true); - }; - - return [handle, gesture, resetCb]; + return [handle, gesture]; } } diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index ee716a4de2..014f2bb0bb 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -1,12 +1,11 @@ import 'react-native-gesture-handler'; -import React, { useEffect, useMemo, useRef } from 'react'; -import { StyleSheet, View, useWindowDimensions } from 'react-native'; +import React, { useMemo, useRef } from 'react'; +import { StyleSheet, View, useWindowDimensions, Text } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming, - withSequence, } from 'react-native-reanimated'; import { Gesture, @@ -16,15 +15,19 @@ import { import ChartManager from './ChartManager'; import FlowChart from './FlowChart'; +// widths pulled from CSS +const MIN_DESKTOP_WIDTH = 1298; +const MAX_PHONE_WIDTH = 996; + export default function App() { const chartManager = useRef(new ChartManager()); - const [panHandle, capturedPan, panReset] = useMemo( + const [panHandle, capturedPan] = useMemo( () => chartManager.current.newGesture(Gesture.Pan()), [] ); - const [pressHandle, capturedPress, pressReset] = useMemo( + const [pressHandle, capturedPress] = useMemo( () => chartManager.current.newGesture(Gesture.LongPress()), [] ); @@ -32,21 +35,12 @@ export default function App() { const panIds = panHandle.idObject; const pressIds = pressHandle.idObject; - const panHeaderId = chartManager.current.addHeader('Pan Gesture'); - const pressHeaderId = chartManager.current.addHeader('LongPress Gesture'); - const dimensions = useWindowDimensions(); - - // widths pulled from CSS - const MIN_DESKTOP_WIDTH = 1298; - const MAX_PHONE_WIDTH = 996; - - const isPhoneMode = dimensions.width < MIN_DESKTOP_WIDTH; + const isDesktopMode = dimensions.width > MIN_DESKTOP_WIDTH; const isFontReduced = dimensions.width < MAX_PHONE_WIDTH; // prettier-ignore const desktopLayout = [ - [panHeaderId, ChartManager.EMPTY_SPACE_ID, pressHeaderId, ChartManager.EMPTY_SPACE_ID], [panIds.undetermined, ChartManager.EMPTY_SPACE_ID, pressIds.undetermined, ChartManager.EMPTY_SPACE_ID], [panIds.began, panIds.failed, pressIds.began, pressIds.failed], [panIds.active, panIds.cancelled, pressIds.active, pressIds.cancelled], @@ -55,14 +49,13 @@ export default function App() { // prettier-ignore const phoneLayout = [ - [panHeaderId], [panIds.undetermined], [panIds.began, panIds.failed], [panIds.active, panIds.cancelled], [panIds.end, ChartManager.EMPTY_SPACE_ID], ]; - chartManager.current.layout = isPhoneMode ? phoneLayout : desktopLayout; + chartManager.current.layout = isDesktopMode ? desktopLayout : phoneLayout; const pressed = useSharedValue(false); @@ -105,15 +98,15 @@ export default function App() { backgroundColor: pressed.value ? '#ffe04b' : '#b58df1', })); - useEffect(() => { - // reset on load - panReset(); - pressReset(); - }, []); - return ( <> + + Gesture.Pan() + {isDesktopMode && ( + Gesture.LongPress() + )} + ; - return ( @@ -57,7 +53,6 @@ export default function App({ chartManager, isFontReduced }: FlowChartProps) { innerRef={(el) => (elementsRef.current[element.id] = el)} elementData={element} chartManager={chartManager} - style={isFontReduced ? tinyFontStyle : null} /> ))} @@ -100,18 +95,4 @@ const styles = StyleSheet.create({ padding: 40, paddingTop: 0, }, - box: { - flex: 1, - flexDirection: 'column', - textAlign: 'center', - }, - element: { - padding: 30, - fontWeight: '500', - fontSize: 24, - }, - subtext: { - fontWeight: '300', - fontSize: 14, - }, }); From 66e60b2250a6af87a46401d89d9e930699cf29c3 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 15:11:56 +0200 Subject: [PATCH 33/45] Remove redundant code --- docs/src/examples/ChartElement.tsx | 32 ++++-------------------------- docs/src/examples/ChartManager.ts | 10 +--------- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartElement.tsx index 6eba861cea..754b130bb2 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartElement.tsx @@ -24,10 +24,7 @@ export default function ChartElement({ const progress = useSharedValue(0); useEffect(() => { - if ( - elementData.id != ChartManager.EMPTY_SPACE_ID && - !elementData.isHeader - ) { + if (elementData.id != ChartManager.EMPTY_SPACE_ID) { const listenerId = chartManager.addListener( elementData.id, (isActive) => { @@ -63,23 +60,15 @@ export default function ChartElement({ }); return ( - + - + {elementData.label} @@ -95,12 +84,6 @@ const styles = StyleSheet.create({ textAlign: 'center', maxWidth: 900, }, - headerBox: { - flex: 1, - flexDirection: 'column', - textAlign: 'center', - maxWidth: 900, - }, element: { paddingVertical: 16, backgroundColor: 'var(--ifm-background-color)', @@ -108,13 +91,6 @@ const styles = StyleSheet.create({ borderColor: 'var(--swm-border)', transition: 'all 350ms ease-in-out', }, - headerText: { - fontSize: 30, - fontWeight: '600', - fontFamily: 'var(--ifm-heading-font-family)', - color: 'var(--ifm-font-color-base)', - margin: 12, - }, label: { color: 'var(--swm-border)', transition: 'color 350ms ease-in-out', diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index b9af4c9df2..45d9f6e9fb 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -29,7 +29,6 @@ export type ElementData = { label?: string; subtext?: string; isVisible: boolean; - isHeader: boolean; highlightColor: string; }; @@ -146,8 +145,7 @@ export default class ChartManager { public addElement( label: State | string = null, subtext: string | null = null, - isVisible: boolean = true, - isHeader: boolean = false + isVisible: boolean = true ): [(isActive: boolean) => void, number] { const newId = this._elements.length; @@ -163,7 +161,6 @@ export default class ChartManager { subtext: subtext, position: null, isVisible: isVisible, - isHeader: isHeader, highlightColor: highlightColor, }; @@ -178,11 +175,6 @@ export default class ChartManager { ]; } - public addHeader(text: string): number { - const [_, headerId] = this.addElement(text, null, true, true); - return headerId; - } - public addConnection(fromId: number, toId: number) { this._connections.push({ id: this._connections.length, From 93eab576f0fc518503dfbef5c62899e032c2964f Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 15:12:14 +0200 Subject: [PATCH 34/45] Adjust styles --- docs/src/examples/ComposedGesture.tsx | 6 +++--- docs/src/examples/FlowChart.tsx | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index 014f2bb0bb..c1598033fc 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -146,10 +146,10 @@ const styles = StyleSheet.create({ marginBottom: 20, }, label: { - fontSize: 20, + fontSize: 22, fontWeight: 'bold', - marginTop: 20, - marginBottom: 12, + marginTop: 24, + marginBottom: 14, color: 'var(--ifm-font-color-base)', }, }); diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 992b6f6829..41308c25b4 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -92,7 +92,6 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', height: '100%', - padding: 40, - paddingTop: 0, + paddingHorizontal: 40, }, }); From 3ed330f0b66fd08c9e590d76b53c8a817770715c Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Thu, 4 Apr 2024 16:03:03 +0200 Subject: [PATCH 35/45] Rename element -> item --- .../{ChartElement.tsx => ChartItem.tsx} | 43 +++++++-------- docs/src/examples/ChartManager.ts | 54 +++++++++---------- docs/src/examples/ComposedGesture.tsx | 5 +- docs/src/examples/FlowChart.tsx | 41 +++++++------- 4 files changed, 67 insertions(+), 76 deletions(-) rename docs/src/examples/{ChartElement.tsx => ChartItem.tsx} (69%) diff --git a/docs/src/examples/ChartElement.tsx b/docs/src/examples/ChartItem.tsx similarity index 69% rename from docs/src/examples/ChartElement.tsx rename to docs/src/examples/ChartItem.tsx index 754b130bb2..eb0464ffec 100644 --- a/docs/src/examples/ChartElement.tsx +++ b/docs/src/examples/ChartItem.tsx @@ -1,41 +1,38 @@ import { Grid } from '@mui/material'; import React, { LegacyRef, useEffect } from 'react'; import { StyleProp, StyleSheet, View, Text } from 'react-native'; -import ChartManager, { ElementData, WAVE_DELAY_MS } from './ChartManager'; +import ChartManager, { Item, WAVE_DELAY_MS } from './ChartManager'; import Animated, { useAnimatedStyle, useSharedValue, withSpring, } from 'react-native-reanimated'; -type ChartElementProps = { - elementData: ElementData; +interface ChartItemProps { + item: Item; chartManager: ChartManager; innerRef?: LegacyRef; style?: StyleProp; -}; +} -export default function ChartElement({ - elementData, +export default function ChartItem({ + item, chartManager, innerRef, style, -}: ChartElementProps) { +}: ChartItemProps) { const progress = useSharedValue(0); useEffect(() => { - if (elementData.id != ChartManager.EMPTY_SPACE_ID) { - const listenerId = chartManager.addListener( - elementData.id, - (isActive) => { - progress.value = withSpring(isActive ? 1 : 0, { - duration: 2 * WAVE_DELAY_MS, - }); - } - ); + if (item.id != ChartManager.EMPTY_SPACE_ID) { + const listenerId = chartManager.addListener(item.id, (isActive) => { + progress.value = withSpring(isActive ? 1 : 0, { + duration: 2 * WAVE_DELAY_MS, + }); + }); return () => { - chartManager.removeListener(elementData.id, listenerId); + chartManager.removeListener(item.id, listenerId); }; } }, [chartManager]); @@ -44,7 +41,7 @@ export default function ChartElement({ return { backgroundColor: progress.value > 0.5 - ? elementData.highlightColor + ? item.highlightColor : 'var(--ifm-background-color)', borderColor: progress.value > 0.5 ? 'transparent' : 'var(--swm-border)', }; @@ -63,16 +60,16 @@ export default function ChartElement({ - {elementData.label} + {item.label} - {elementData.subtext} + {item.subtext} ); } @@ -84,7 +81,7 @@ const styles = StyleSheet.create({ textAlign: 'center', maxWidth: 900, }, - element: { + item: { paddingVertical: 16, backgroundColor: 'var(--ifm-background-color)', borderWidth: 1, diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/ChartManager.ts index 45d9f6e9fb..ec59d9b4b4 100644 --- a/docs/src/examples/ChartManager.ts +++ b/docs/src/examples/ChartManager.ts @@ -1,5 +1,3 @@ -//import ElementData from './ElementData'; - import { useMemo } from 'react'; import { TapGesture, @@ -24,7 +22,7 @@ const Colors = { RED: 'var(--swm-red-light-80)', }; -export type ElementData = { +export type Item = { id: number; label?: string; subtext?: string; @@ -78,19 +76,19 @@ type IdObject = { }; export class GestureHandle { - // within gesture, States can be used as unique IDs pointing to the ElementData pool - _elementIds: IdObject; + // within gesture, States can be used as unique IDs pointing to the item pool + _itemIds: IdObject; get idObject() { - return this._elementIds; + return this._itemIds; } set idObject(newObject: IdObject) { - this._elementIds = newObject; + this._itemIds = newObject; } } export default class ChartManager { - private _elements: ElementData[] = []; + private _items: Item[] = []; private _connections: ChartConnection[] = []; private _layout: number[][]; private _listeners: Map void>> = @@ -99,11 +97,11 @@ export default class ChartManager { public static EMPTY_SPACE_ID = 0; constructor() { - this.addElement(null, null, false); + this.addItem(null, null, false); } - get elements(): typeof this._elements { - return this._elements; + get items(): Item[] { + return this._items; } get connections(): ChartConnection[] { @@ -119,35 +117,35 @@ export default class ChartManager { } public addListener( - elementId: number, + itemId: number, listener: (isActive: boolean) => void ): number { - const listenerId = this._listeners.get(elementId)?.size - 1 ?? 0; + const listenerId = this._listeners.get(itemId)?.size - 1 ?? 0; // another map is used inside of _listeners to seamlessly remove listening functions from _listeners - if (this._listeners.has(elementId)) { - this._listeners.get(elementId).set(listenerId, listener); + if (this._listeners.has(itemId)) { + this._listeners.get(itemId).set(listenerId, listener); } else { - this._listeners.set(elementId, new Map([[0, listener]])); + this._listeners.set(itemId, new Map([[0, listener]])); } return listenerId; } - public removeListener(elementId: number, listenerId: number): void { - this._listeners.get(elementId).delete(listenerId); + public removeListener(itemId: number, listenerId: number): void { + this._listeners.get(itemId).delete(listenerId); } public clearListeners(): void { this._listeners.clear(); } - public addElement( + public addItem( label: State | string = null, subtext: string | null = null, isVisible: boolean = true ): [(isActive: boolean) => void, number] { - const newId = this._elements.length; + const newId = this._items.length; if (typeof label == 'number') { label = stateToName.get(label); @@ -155,7 +153,7 @@ export default class ChartManager { let highlightColor = labelColorMap.get(label) ?? Colors.YELLOW; - const newElementData = { + const newItem = { id: newId, label: label, subtext: subtext, @@ -164,7 +162,7 @@ export default class ChartManager { highlightColor: highlightColor, }; - this._elements.push(newElementData); + this._items.push(newItem); // this callback will be used by a .onX hook to broadcast this event to all listeners return [ @@ -184,12 +182,12 @@ export default class ChartManager { } public newGesture(gesture: GesturesUnion): [GestureHandle, GesturesUnion] { - const [beganCallback, beganId] = this.addElement(State.BEGAN); - const [activeCallback, activeId] = this.addElement(State.ACTIVE); - const [endCallback, endId] = this.addElement(State.END); - const [failedCallback, failedId] = this.addElement(State.FAILED); - const [cancelledCallback, cancelledId] = this.addElement(State.CANCELLED); - const [undeterminedCallback, undeterminedId] = this.addElement( + const [beganCallback, beganId] = this.addItem(State.BEGAN); + const [activeCallback, activeId] = this.addItem(State.ACTIVE); + const [endCallback, endId] = this.addItem(State.END); + const [failedCallback, failedId] = this.addItem(State.FAILED); + const [cancelledCallback, cancelledId] = this.addItem(State.CANCELLED); + const [undeterminedCallback, undeterminedId] = this.addItem( State.UNDETERMINED ); diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/ComposedGesture.tsx index c1598033fc..595e88aaf2 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/ComposedGesture.tsx @@ -107,10 +107,7 @@ export default function App() { Gesture.LongPress() )} - + diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/FlowChart.tsx index 41308c25b4..4f6e8b88af 100644 --- a/docs/src/examples/FlowChart.tsx +++ b/docs/src/examples/FlowChart.tsx @@ -1,9 +1,9 @@ import 'react-native-gesture-handler'; import React, { useRef } from 'react'; -import { StyleProp, StyleSheet, View } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; -import ChartElement from './ChartElement'; +import ChartItem from './ChartItem'; import Arrow from './Arrow'; type Coordinate = { @@ -13,18 +13,17 @@ type Coordinate = { type FlowChartProps = { chartManager: ChartManager; - isFontReduced: boolean; }; -export default function App({ chartManager, isFontReduced }: FlowChartProps) { - const elementsRef = useRef([]); - const elementsCoordsRef = useRef([]); +export default function FlowChart({ chartManager }: FlowChartProps) { + const itemsRef = useRef([]); + const itemsCoordsRef = useRef([]); const rootRef = useRef(null); const getCenter = (side: number, size: number) => side + size / 2; - elementsCoordsRef.current = elementsRef.current.map((element) => { - // during unloading or overresizing, element may reload itself, causing it to be undefined + itemsCoordsRef.current = itemsRef.current.map((element) => { + // during unloading or overresizing, item may reload itself, causing it to be undefined if (!element) { return { x: 0, @@ -46,25 +45,25 @@ export default function App({ chartManager, isFontReduced }: FlowChartProps) { {chartManager.layout.map((row, index) => ( {row - .map((elementId) => chartManager.elements[elementId]) - .map((element, index) => ( - chartManager.items[itemId]) + .map((item, index) => ( + (elementsRef.current[element.id] = el)} - elementData={element} + innerRef={(el) => (itemsRef.current[item.id] = el)} + item={item} chartManager={chartManager} /> ))} ))} - {elementsCoordsRef.current.length > 0 && + {itemsCoordsRef.current.length > 0 && chartManager.connections.map((connection) => { // we have all the connections layed out, - // but the user may choose not to use some of the available elements, + // but the user may choose not to use some of the available items, if ( - !elementsCoordsRef.current[connection.from] || - !elementsCoordsRef.current[connection.to] + !itemsCoordsRef.current[connection.from] || + !itemsCoordsRef.current[connection.to] ) { return ; } @@ -72,12 +71,12 @@ export default function App({ chartManager, isFontReduced }: FlowChartProps) { ); From 7b3dbfe65ed13675b3e12d962037d9ca7010c8d2 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 11:30:21 +0200 Subject: [PATCH 36/45] Rename stuff & cleanup --- docs/docs/fundamentals/states-events.mdx | 7 ++++--- docs/src/examples/{ => GestureStateFlowExample}/Arrow.tsx | 0 .../examples/{ => GestureStateFlowExample}/ChartItem.tsx | 3 ++- .../examples/{ => GestureStateFlowExample}/ChartManager.ts | 0 .../examples/{ => GestureStateFlowExample}/FlowChart.tsx | 0 .../index.tsx} | 4 +--- 6 files changed, 7 insertions(+), 7 deletions(-) rename docs/src/examples/{ => GestureStateFlowExample}/Arrow.tsx (100%) rename docs/src/examples/{ => GestureStateFlowExample}/ChartItem.tsx (98%) rename docs/src/examples/{ => GestureStateFlowExample}/ChartManager.ts (100%) rename docs/src/examples/{ => GestureStateFlowExample}/FlowChart.tsx (100%) rename docs/src/examples/{ComposedGesture.tsx => GestureStateFlowExample/index.tsx} (97%) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index de3ceb82e5..3af3824111 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -36,14 +36,14 @@ A gesture can be in one of the six possible states: ## State flows -import ComposedGesture from '@site/src/examples/ComposedGesture'; - The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. The flow looks as follows: +import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExample'; + } + component={} label="Drag or long-press the circle" larger={true} /> @@ -91,3 +91,4 @@ Is called when there will be no more information about this pointer. It may be c ### `onPointerChange` Is called before `onPointerDown`, `onPointerMove`, `onPointerUp` and `onPointerCancelled` with the same event, which may be useful in case you share logic between them. It may carry information about more than one pointer because the events are batched. +@site/src/examples/GestureStateFlowExample@site/src/examples diff --git a/docs/src/examples/Arrow.tsx b/docs/src/examples/GestureStateFlowExample/Arrow.tsx similarity index 100% rename from docs/src/examples/Arrow.tsx rename to docs/src/examples/GestureStateFlowExample/Arrow.tsx diff --git a/docs/src/examples/ChartItem.tsx b/docs/src/examples/GestureStateFlowExample/ChartItem.tsx similarity index 98% rename from docs/src/examples/ChartItem.tsx rename to docs/src/examples/GestureStateFlowExample/ChartItem.tsx index eb0464ffec..9a47906a61 100644 --- a/docs/src/examples/ChartItem.tsx +++ b/docs/src/examples/GestureStateFlowExample/ChartItem.tsx @@ -60,8 +60,9 @@ export default function ChartItem({ diff --git a/docs/src/examples/ChartManager.ts b/docs/src/examples/GestureStateFlowExample/ChartManager.ts similarity index 100% rename from docs/src/examples/ChartManager.ts rename to docs/src/examples/GestureStateFlowExample/ChartManager.ts diff --git a/docs/src/examples/FlowChart.tsx b/docs/src/examples/GestureStateFlowExample/FlowChart.tsx similarity index 100% rename from docs/src/examples/FlowChart.tsx rename to docs/src/examples/GestureStateFlowExample/FlowChart.tsx diff --git a/docs/src/examples/ComposedGesture.tsx b/docs/src/examples/GestureStateFlowExample/index.tsx similarity index 97% rename from docs/src/examples/ComposedGesture.tsx rename to docs/src/examples/GestureStateFlowExample/index.tsx index 595e88aaf2..f166e18e76 100644 --- a/docs/src/examples/ComposedGesture.tsx +++ b/docs/src/examples/GestureStateFlowExample/index.tsx @@ -17,7 +17,6 @@ import FlowChart from './FlowChart'; // widths pulled from CSS const MIN_DESKTOP_WIDTH = 1298; -const MAX_PHONE_WIDTH = 996; export default function App() { const chartManager = useRef(new ChartManager()); @@ -37,7 +36,6 @@ export default function App() { const dimensions = useWindowDimensions(); const isDesktopMode = dimensions.width > MIN_DESKTOP_WIDTH; - const isFontReduced = dimensions.width < MAX_PHONE_WIDTH; // prettier-ignore const desktopLayout = [ @@ -143,7 +141,7 @@ const styles = StyleSheet.create({ marginBottom: 20, }, label: { - fontSize: 22, + fontSize: 24, fontWeight: 'bold', marginTop: 24, marginBottom: 14, From 6211b0c4dbb34ad4db895fbe8f4d6784af08f20f Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 11:50:32 +0200 Subject: [PATCH 37/45] Bring back some mysterious code to fix a bug --- .../examples/GestureStateFlowExample/ChartManager.ts | 10 ++++++++-- docs/src/examples/GestureStateFlowExample/index.tsx | 11 ++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/src/examples/GestureStateFlowExample/ChartManager.ts b/docs/src/examples/GestureStateFlowExample/ChartManager.ts index ec59d9b4b4..5c9e791b4e 100644 --- a/docs/src/examples/GestureStateFlowExample/ChartManager.ts +++ b/docs/src/examples/GestureStateFlowExample/ChartManager.ts @@ -181,7 +181,9 @@ export default class ChartManager { }); } - public newGesture(gesture: GesturesUnion): [GestureHandle, GesturesUnion] { + public newGesture( + gesture: GesturesUnion + ): [GestureHandle, GesturesUnion, () => void] { const [beganCallback, beganId] = this.addItem(State.BEGAN); const [activeCallback, activeId] = this.addItem(State.ACTIVE); const [endCallback, endId] = this.addItem(State.END); @@ -249,6 +251,10 @@ export default class ChartManager { this.addConnection(from, to); }); - return [handle, gesture]; + const resetCb = () => { + undeterminedCallback(true); + }; + + return [handle, gesture, resetCb]; } } diff --git a/docs/src/examples/GestureStateFlowExample/index.tsx b/docs/src/examples/GestureStateFlowExample/index.tsx index f166e18e76..902e59117e 100644 --- a/docs/src/examples/GestureStateFlowExample/index.tsx +++ b/docs/src/examples/GestureStateFlowExample/index.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useMemo, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { StyleSheet, View, useWindowDimensions, Text } from 'react-native'; import Animated, { useAnimatedStyle, @@ -21,16 +21,21 @@ const MIN_DESKTOP_WIDTH = 1298; export default function App() { const chartManager = useRef(new ChartManager()); - const [panHandle, capturedPan] = useMemo( + const [panHandle, capturedPan, resetPan] = useMemo( () => chartManager.current.newGesture(Gesture.Pan()), [] ); - const [pressHandle, capturedPress] = useMemo( + const [pressHandle, capturedPress, resetLongPress] = useMemo( () => chartManager.current.newGesture(Gesture.LongPress()), [] ); + useEffect(() => { + resetPan(); + resetLongPress(); + }, []); + const panIds = panHandle.idObject; const pressIds = pressHandle.idObject; From d5649d4456f2b94b075652714b0ab9b1014131a8 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 15:23:09 +0200 Subject: [PATCH 38/45] a man's gotta do what a man's gotta do --- .../GestureStateFlowExample/FlowChart.tsx | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/src/examples/GestureStateFlowExample/FlowChart.tsx b/docs/src/examples/GestureStateFlowExample/FlowChart.tsx index 4f6e8b88af..45b0c7ec27 100644 --- a/docs/src/examples/GestureStateFlowExample/FlowChart.tsx +++ b/docs/src/examples/GestureStateFlowExample/FlowChart.tsx @@ -1,5 +1,5 @@ import 'react-native-gesture-handler'; -import React, { useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import ChartManager from './ChartManager'; import { Grid } from '@mui/material'; @@ -20,6 +20,17 @@ export default function FlowChart({ chartManager }: FlowChartProps) { const itemsCoordsRef = useRef([]); const rootRef = useRef(null); + // there's a bug where arrows are not shown on the first render on production build + // i hate this but it forces a re-render after the component is mounted + // a man's gotta do what a man's gotta do + const [counter, setCounter] = useState(0); + useEffect(() => { + const timeout = setTimeout(() => { + setCounter(counter + 1); + }, 0); + return () => clearTimeout(timeout); + }, []); + const getCenter = (side: number, size: number) => side + size / 2; itemsCoordsRef.current = itemsRef.current.map((element) => { @@ -57,30 +68,29 @@ export default function FlowChart({ chartManager }: FlowChartProps) { ))} - {itemsCoordsRef.current.length > 0 && - chartManager.connections.map((connection) => { - // we have all the connections layed out, - // but the user may choose not to use some of the available items, - if ( - !itemsCoordsRef.current[connection.from] || - !itemsCoordsRef.current[connection.to] - ) { - return ; - } - return ( - - ); - })} + {chartManager.connections.map((connection) => { + // we have all the connections layed out, + // but the user may choose not to use some of the available items, + if ( + !itemsCoordsRef.current[connection.from] || + !itemsCoordsRef.current[connection.to] + ) { + return ; + } + return ( + + ); + })} ); } From 78e4f2c218c7484520448e65fcdf6b7368e5c068 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 15:24:35 +0200 Subject: [PATCH 39/45] Remove a mistake copy-paste --- docs/docs/fundamentals/states-events.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docs/fundamentals/states-events.mdx b/docs/docs/fundamentals/states-events.mdx index 3af3824111..6d2cf5b351 100644 --- a/docs/docs/fundamentals/states-events.mdx +++ b/docs/docs/fundamentals/states-events.mdx @@ -91,4 +91,3 @@ Is called when there will be no more information about this pointer. It may be c ### `onPointerChange` Is called before `onPointerDown`, `onPointerMove`, `onPointerUp` and `onPointerCancelled` with the same event, which may be useful in case you share logic between them. It may carry information about more than one pointer because the events are batched. -@site/src/examples/GestureStateFlowExample@site/src/examples From 0bcc7c6c1315f32857ba493c8662791e2e6854bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Kapu=C5=9Bciak?= <39658211+kacperkapusciak@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:32:32 +0200 Subject: [PATCH 40/45] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrycja KaliƄska <59940332+patrycjakalinska@users.noreply.github.com> --- docs/src/components/InteractiveExample/index.tsx | 7 +++++-- docs/src/components/InteractiveExample/styles.module.css | 3 --- docs/src/components/ReducedMotionWarning/styles.module.css | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/components/InteractiveExample/index.tsx b/docs/src/components/InteractiveExample/index.tsx index 720c66ce9c..6a1dc02a6c 100644 --- a/docs/src/components/InteractiveExample/index.tsx +++ b/docs/src/components/InteractiveExample/index.tsx @@ -44,8 +44,11 @@ export default function InteractiveExample({ Loading...
}> {() => (
{showPreview && prefersReducedMotion && } {src && ( diff --git a/docs/src/components/InteractiveExample/styles.module.css b/docs/src/components/InteractiveExample/styles.module.css index c0b783e99f..e1f1ea9047 100644 --- a/docs/src/components/InteractiveExample/styles.module.css +++ b/docs/src/components/InteractiveExample/styles.module.css @@ -135,6 +135,3 @@ border-bottom: 1px solid var(--swm-interactive-button-active); } -.label { - /* position: absolute; */ -} diff --git a/docs/src/components/ReducedMotionWarning/styles.module.css b/docs/src/components/ReducedMotionWarning/styles.module.css index fe557ddc40..d689c710a3 100644 --- a/docs/src/components/ReducedMotionWarning/styles.module.css +++ b/docs/src/components/ReducedMotionWarning/styles.module.css @@ -7,11 +7,11 @@ white-space: nowrap; font-size: 14px; font-weight: 500; - padding: 0.125rem 0.375rem; + padding: 0.125rem 0; } .warningText { - margin: 0.125rem 0; + margin: 0.25rem 0 0.75rem; } .container { From 4fe0baa92d0d7d93a9851d4e2dc1968c98073767 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 15:32:52 +0200 Subject: [PATCH 41/45] Apply suggestions from code review --- docs/src/components/AnimableIcon/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/components/AnimableIcon/index.tsx b/docs/src/components/AnimableIcon/index.tsx index c582c6925f..78971f3a25 100644 --- a/docs/src/components/AnimableIcon/index.tsx +++ b/docs/src/components/AnimableIcon/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import styles from './styles.module.css'; import clsx from 'clsx'; import { useColorMode } from '@docusaurus/theme-common'; @@ -21,7 +21,7 @@ const AnimableIcon = ({ onClick, }: Props): JSX.Element => { const { colorMode } = useColorMode(); - const [actionPerformed, setActionPerformed] = React.useState(false); + const [actionPerformed, setActionPerformed] = useState(false); useEffect(() => { const timeout = setTimeout(() => setActionPerformed(false), 1000); From 413bb3411e31bc9dd9e35eec0f099d54df4905e4 Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 15:54:42 +0200 Subject: [PATCH 42/45] Rename landing example component --- .../components/GestureExamples/GestureExampleItem/index.tsx | 4 ++-- .../index.tsx | 2 +- .../styles.module.css | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename docs/src/components/{InteractiveExampleComponent => LandingExampleComponent}/index.tsx (84%) rename docs/src/components/{InteractiveExampleComponent => LandingExampleComponent}/styles.module.css (100%) diff --git a/docs/src/components/GestureExamples/GestureExampleItem/index.tsx b/docs/src/components/GestureExamples/GestureExampleItem/index.tsx index a8820822e6..7a298a92ac 100644 --- a/docs/src/components/GestureExamples/GestureExampleItem/index.tsx +++ b/docs/src/components/GestureExamples/GestureExampleItem/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styles from './styles.module.css'; -import InteractiveExampleComponent from '@site/src/components/InteractiveExampleComponent'; +import LandingExampleComponent from '@site/src/components/LandingExampleComponent'; interface Props { title: string; @@ -17,7 +17,7 @@ const GestureExampleItem = ({ title, component, idx, href }: Props) => { {title}
- +
diff --git a/docs/src/components/InteractiveExampleComponent/index.tsx b/docs/src/components/LandingExampleComponent/index.tsx similarity index 84% rename from docs/src/components/InteractiveExampleComponent/index.tsx rename to docs/src/components/LandingExampleComponent/index.tsx index 69def9ab51..65bd0f73e4 100644 --- a/docs/src/components/InteractiveExampleComponent/index.tsx +++ b/docs/src/components/LandingExampleComponent/index.tsx @@ -8,7 +8,7 @@ interface Props { idx?: number; } -export default function InteractiveExampleComponent({ component, idx }: Props) { +export default function LandingExample({ component, idx }: Props) { return ( Loading...
}> {() => ( diff --git a/docs/src/components/InteractiveExampleComponent/styles.module.css b/docs/src/components/LandingExampleComponent/styles.module.css similarity index 100% rename from docs/src/components/InteractiveExampleComponent/styles.module.css rename to docs/src/components/LandingExampleComponent/styles.module.css From c110627926e417a686c40d0bce51362ba489348d Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 15:55:11 +0200 Subject: [PATCH 43/45] Rename showLines -> lineBounds in CollapsibleCode comp --- docs/src/components/CollapsibleCode/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/components/CollapsibleCode/index.tsx b/docs/src/components/CollapsibleCode/index.tsx index a66c24d1c9..ae0ce28a48 100644 --- a/docs/src/components/CollapsibleCode/index.tsx +++ b/docs/src/components/CollapsibleCode/index.tsx @@ -6,17 +6,17 @@ import CollapseButton from '@site/src/components/CollapseButton'; interface Props { src: string; - showLines: number[]; + lineBounds: number[]; } -export default function CollapsibleCode({ src, showLines }: Props) { +export default function CollapsibleCode({ src, lineBounds }: Props) { const [collapsed, setCollapsed] = useState(true); - if (!showLines) { + if (!lineBounds) { return {src}; } - const [start, end] = showLines; + const [start, end] = lineBounds; const codeLines = src.split('\n'); const linesToShow = codeLines.slice(start, end + 1).join('\n'); From b81ed47fe7669ed626c60e7693b3df92112abe3f Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 15:56:59 +0200 Subject: [PATCH 44/45] Remove unused import --- .../InteractiveExample/InteractiveExampleComponent/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx b/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx index 15a4d3f8b7..11c39951a4 100644 --- a/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx +++ b/docs/src/components/InteractiveExample/InteractiveExampleComponent/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import clsx from 'clsx'; import BrowserOnly from '@docusaurus/BrowserOnly'; import styles from './styles.module.css'; From 357ab4bee0259614ae2a662325850e950ff835dc Mon Sep 17 00:00:00 2001 From: kacperkapusciak Date: Fri, 5 Apr 2024 16:05:01 +0200 Subject: [PATCH 45/45] Code reformat --- docs/src/components/InteractiveExample/index.tsx | 2 +- docs/src/components/InteractiveExample/styles.module.css | 1 - docs/src/css/index.css | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/components/InteractiveExample/index.tsx b/docs/src/components/InteractiveExample/index.tsx index 6a1dc02a6c..8a89da64ed 100644 --- a/docs/src/components/InteractiveExample/index.tsx +++ b/docs/src/components/InteractiveExample/index.tsx @@ -44,7 +44,7 @@ export default function InteractiveExample({ Loading...
}> {() => (