From ad9b40e82f94a51a72be88bd2c349bcfb4f7cb3c Mon Sep 17 00:00:00 2001
From: "Sinan Sonmez (Chaush)" <sinansonmez@outlook.com>
Date: Fri, 22 Nov 2024 20:34:42 +0000
Subject: [PATCH 1/4] initial migration for index.js

---
 package.json                |  3 +-
 src/{index.js => index.tsx} | 92 ++++++++++++++++++++++++++-----------
 tsconfig.json               | 35 ++++++++++++++
 3 files changed, 101 insertions(+), 29 deletions(-)
 rename src/{index.js => index.tsx} (82%)
 create mode 100644 tsconfig.json

diff --git a/package.json b/package.json
index c716dbb..25c8b38 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
     "@babel/preset-env": "^7.7.1",
     "@babel/preset-react": "^7.7.0",
     "@rollup/plugin-replace": "^2.2.1",
+    "@types/react": "^18.3.12",
     "bundlesize": "^0.18.0",
     "escape-html": "^1.0.3",
     "eslint": "^6.6.0",
@@ -76,6 +77,6 @@
     "rollup-plugin-terser": "^5.1.2"
   },
   "dependencies": {
-    "prop-types": "^15.7.2"
+    "typescript": "^5.7.2"
   }
 }
diff --git a/src/index.js b/src/index.tsx
similarity index 82%
rename from src/index.js
rename to src/index.tsx
index e4a62f6..624754c 100644
--- a/src/index.js
+++ b/src/index.tsx
@@ -1,25 +1,58 @@
 import React, {
+  CSSProperties,
+  MutableRefObject,
+  ReactElement,
+  ReactNode,
   useContext,
   useEffect,
   useMemo,
   useReducer,
   useRef
 } from 'react';
-import PropTypes from 'prop-types';
 
-const Context = React.createContext();
+enum Direction {
+  up = 'up',
+  left = 'left',
+  right = 'right',
+  down = 'down'
+};
+
+interface CanScroll {
+  [Direction.up]: boolean
+  [Direction.left]: boolean
+  [Direction.right]: boolean
+  [Direction.down]: boolean
+}
+
+interface Dispatch {
+  type: string
+  direction: keyof typeof Direction
+  canScroll: boolean
+}
+
+interface OverflowContext {
+  tolerance?: number | string
+  refs: { viewport: MutableRefObject<HTMLDivElement | null> }
+  canScroll?: CanScroll
+  state: {
+    canScroll: CanScroll
+  }
+  dispatch: ({type, direction, canScroll}: Dispatch) => void
+}
+
+const Context = React.createContext<OverflowContext>({});
 
 export function useOverflow() {
   return useContext(Context);
 }
 
-const containerStyle = {
+const containerStyle: CSSProperties = {
   display: 'flex',
   flexDirection: 'column',
   position: 'relative'
 };
 
-const viewportStyle = {
+const viewportStyle: CSSProperties = {
   position: 'relative',
   flexBasis: '100%',
   flexShrink: 1,
@@ -27,14 +60,14 @@ const viewportStyle = {
   overflow: 'auto'
 };
 
-const contentStyle = {
+const contentStyle: CSSProperties = {
   display: 'inline-block',
   position: 'relative',
   minWidth: '100%',
   boxSizing: 'border-box'
 };
 
-function reducer(state, action) {
+function reducer(state, action: PayloadAction<>) {
   switch (action.type) {
     case 'CHANGE': {
       const currentValue = state.canScroll[action.direction];
@@ -104,10 +137,10 @@ export default function Overflow({
   style: styleProp,
   tolerance = 0,
   ...rest
-}) {
+}: Overflow) {
   const [state, dispatch] = useReducer(reducer, null, getInitialState);
   const hidden = rest.hidden;
-  const viewportRef = useRef();
+  const viewportRef = useRef<HTMLDivElement>(null);
 
   const style = useMemo(
     () => ({
@@ -151,18 +184,18 @@ export default function Overflow({
   );
 }
 
-Overflow.propTypes = {
+interface Overflow {
   /**
    * Elements to render inside the outer container. This should include an
    * `<Overflow.Content>` element at a minimum, but should also include your
    * scroll indicators if you’d like to overlay them on the scrollable viewport.
    */
-  children: PropTypes.node,
+  children: ReactNode,
   /**
    * Callback that receives the latest overflow state and an object of refs, if
    * you’d like to react to overflow in a custom way.
    */
-  onStateChange: PropTypes.func,
+  onStateChange: (state: string, refs: {viewport: MutableRefObject<HTMLDivElement | null>}) => void,
   /**
    * Distance (number of pixels or CSS length unit like `1em`) to the edge of
    * the content at which to consider the viewport fully scrolled. For example,
@@ -170,7 +203,9 @@ Overflow.propTypes = {
    * long as it’s within 10 pixels of the border. You can use this when your
    * content has padding and scrolling close to the edge should be good enough.
    */
-  tolerance: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
+  tolerance: number | string
+  style: CSSProperties
+  hidden: boolean
 };
 
 // For Firefox, update on a threshold of 0 in addition to any intersection at
@@ -188,20 +223,20 @@ const threshold = [0, 1e-12];
  * own element inside `<Overflow.Content>` instead – otherwise you risk
  * interfering with the styles this component needs to function.
  */
-function OverflowContent({ children, style: styleProp, ...rest }) {
+function OverflowContent({ children, style: styleProp, ...rest }: OverflowContent) {
   const { dispatch, tolerance, refs } = useOverflow();
   const { viewport: viewportRef } = refs;
-  const contentRef = useRef();
-  const toleranceRef = useRef();
+  const contentRef = useRef<HTMLDivElement | null>(null);
+  const toleranceRef = useRef<HTMLDivElement | null>(null);
   const watchRef = tolerance ? toleranceRef : contentRef;
-  const observersRef = useRef();
+  const observersRef = useRef<HTMLDivElement | null>(null);
 
   useEffect(() => {
     let ignore = false;
 
     const root = viewportRef.current;
 
-    const createObserver = (direction, rootMargin) => {
+    const createObserver = (direction: Direction, rootMargin: string) => {
       return new IntersectionObserver(
         ([entry]) => {
           if (ignore) {
@@ -230,10 +265,10 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
     };
 
     const observers = {
-      up: createObserver('up', '100% 0px -100% 0px'),
-      left: createObserver('left', '0px -100% 0px 100%'),
-      right: createObserver('right', '0px 100% 0px -100%'),
-      down: createObserver('down', '-100% 0px 100% 0px')
+      up: createObserver(Direction.up, '100% 0px -100% 0px'),
+      left: createObserver(Direction.left, '0px -100% 0px 100%'),
+      right: createObserver(Direction.right, '0px 100% 0px -100%'),
+      down: createObserver(Direction.down, '-100% 0px 100% 0px')
     };
 
     observersRef.current = observers;
@@ -251,7 +286,7 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
     const observers = observersRef.current;
     const watchNode = watchRef.current;
 
-    observers.up.observe(watchNode);
+    observers?.up.observe(watchNode);
     observers.left.observe(watchNode);
     observers.right.observe(watchNode);
     observers.down.observe(watchNode);
@@ -304,11 +339,12 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
 
 OverflowContent.displayName = 'Overflow.Content';
 
-OverflowContent.propTypes = {
+interface OverflowContent {
   /**
    * Content to render inside the scrollable viewport.
    */
-  children: PropTypes.node
+  children: ReactNode
+  style: CSSProperties
 };
 
 /**
@@ -352,7 +388,7 @@ OverflowContent.propTypes = {
  * </Overflow>
  * ```
  */
-function OverflowIndicator({ children, direction }) {
+function OverflowIndicator({ children, direction } : OverflowIndicator) {
   const { state, refs } = useOverflow();
   const { canScroll } = state;
   const isActive = direction
@@ -372,19 +408,19 @@ function OverflowIndicator({ children, direction }) {
 
 OverflowIndicator.displayName = 'Overflow.Indicator';
 
-OverflowIndicator.propTypes = {
+interface OverflowIndicator {
   /**
    * Indicator to render when scrolling is allowed in the requested direction.
    * If given a function, it will be passed the overflow state and an object
    * containing the `viewport` ref. You can use this `refs` parameter to render
    * an indicator that is also a button that scrolls the viewport (for example).
    */
-  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
+  children: ReactElement | ((stateArg: boolean | CanScroll, refs: OverflowContext['refs']) => ReactElement)
   /**
    * The scrollabe direction to watch for. If not supplied, the indicator will
    * be active when scrolling is allowed in any direction.
    */
-  direction: PropTypes.oneOf(['up', 'down', 'left', 'right'])
+  direction: keyof typeof Direction
 };
 
 Overflow.Indicator = OverflowIndicator;
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..a81f956
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,35 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "isolatedModules": true,
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "strictNullChecks": true,
+    "forceConsistentCasingInFileNames": true,
+    "module": "es2020",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "noEmit": true,
+    "experimentalDecorators": true,
+    "jsx": "preserve",
+    "baseUrl": "./src",
+    "useUnknownInCatchVariables": false,
+    "composite": true,
+    "incremental": true
+  },
+  "include": [
+    "./src/**/*",
+    "./src/**/*.json"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}

From c8a68b63d9976510d06c6d65ecb09b835f519f81 Mon Sep 17 00:00:00 2001
From: "Sinan Sonmez (Chaush)" <sinansonmez@outlook.com>
Date: Fri, 22 Nov 2024 21:46:26 +0000
Subject: [PATCH 2/4] finish index.js migration

---
 src/index.tsx | 105 ++++++++++++++++++++++++++++++--------------------
 1 file changed, 63 insertions(+), 42 deletions(-)

diff --git a/src/index.tsx b/src/index.tsx
index 624754c..ff94713 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -15,29 +15,29 @@ enum Direction {
   left = 'left',
   right = 'right',
   down = 'down'
-};
+}
 
 interface CanScroll {
-  [Direction.up]: boolean
-  [Direction.left]: boolean
-  [Direction.right]: boolean
-  [Direction.down]: boolean
+  [Direction.up]: boolean;
+  [Direction.left]: boolean;
+  [Direction.right]: boolean;
+  [Direction.down]: boolean;
 }
 
 interface Dispatch {
-  type: string
-  direction: keyof typeof Direction
-  canScroll: boolean
+  type: string;
+  direction: keyof typeof Direction;
+  canScroll: boolean;
 }
 
 interface OverflowContext {
-  tolerance?: number | string
-  refs: { viewport: MutableRefObject<HTMLDivElement | null> }
-  canScroll?: CanScroll
+  tolerance?: number | string;
+  refs: { viewport: MutableRefObject<HTMLDivElement | null> };
+  canScroll?: CanScroll;
   state: {
-    canScroll: CanScroll
-  }
-  dispatch: ({type, direction, canScroll}: Dispatch) => void
+    canScroll: CanScroll;
+  };
+  dispatch?: ({ type, direction, canScroll }: Dispatch) => void;
 }
 
 const Context = React.createContext<OverflowContext>({});
@@ -67,7 +67,7 @@ const contentStyle: CSSProperties = {
   boxSizing: 'border-box'
 };
 
-function reducer(state, action: PayloadAction<>) {
+function reducer(state: { canScroll: CanScroll }, action: Dispatch) {
   switch (action.type) {
     case 'CHANGE': {
       const currentValue = state.canScroll[action.direction];
@@ -190,12 +190,15 @@ interface Overflow {
    * `<Overflow.Content>` element at a minimum, but should also include your
    * scroll indicators if you’d like to overlay them on the scrollable viewport.
    */
-  children: ReactNode,
+  children: ReactNode;
   /**
    * Callback that receives the latest overflow state and an object of refs, if
    * you’d like to react to overflow in a custom way.
    */
-  onStateChange: (state: string, refs: {viewport: MutableRefObject<HTMLDivElement | null>}) => void,
+  onStateChange: (
+    state: OverflowContext['state'],
+    refs: OverflowContext['refs']
+  ) => void;
   /**
    * Distance (number of pixels or CSS length unit like `1em`) to the edge of
    * the content at which to consider the viewport fully scrolled. For example,
@@ -203,10 +206,10 @@ interface Overflow {
    * long as it’s within 10 pixels of the border. You can use this when your
    * content has padding and scrolling close to the edge should be good enough.
    */
-  tolerance: number | string
-  style: CSSProperties
-  hidden: boolean
-};
+  tolerance: number | string;
+  style: CSSProperties;
+  hidden: boolean;
+}
 
 // For Firefox, update on a threshold of 0 in addition to any intersection at
 // all (represented by a tiny tiny threshold).
@@ -223,20 +226,29 @@ const threshold = [0, 1e-12];
  * own element inside `<Overflow.Content>` instead – otherwise you risk
  * interfering with the styles this component needs to function.
  */
-function OverflowContent({ children, style: styleProp, ...rest }: OverflowContent) {
+function OverflowContent({
+  children,
+  style: styleProp,
+  ...rest
+}: OverflowContent) {
   const { dispatch, tolerance, refs } = useOverflow();
   const { viewport: viewportRef } = refs;
-  const contentRef = useRef<HTMLDivElement | null>(null);
-  const toleranceRef = useRef<HTMLDivElement | null>(null);
+  const contentRef = useRef<HTMLDivElement>(null);
+  const toleranceRef = useRef<HTMLDivElement>(null);
   const watchRef = tolerance ? toleranceRef : contentRef;
-  const observersRef = useRef<HTMLDivElement | null>(null);
+  const observersRef = useRef<{
+    [Direction.up]: IntersectionObserver;
+    [Direction.left]: IntersectionObserver;
+    [Direction.down]: IntersectionObserver;
+    [Direction.right]: IntersectionObserver;
+  } | null>(null);
 
   useEffect(() => {
     let ignore = false;
 
     const root = viewportRef.current;
 
-    const createObserver = (direction: Direction, rootMargin: string) => {
+    const createObserver = (direction: Direction, rootMargin?: string) => {
       return new IntersectionObserver(
         ([entry]) => {
           if (ignore) {
@@ -254,7 +266,7 @@ function OverflowContent({ children, style: styleProp, ...rest }: OverflowConten
             // case.
             entry.intersectionRatio !== 0 &&
             entry.isIntersecting;
-          dispatch({ type: 'CHANGE', direction, canScroll });
+          dispatch?.({ type: 'CHANGE', direction, canScroll });
         },
         {
           root,
@@ -286,16 +298,20 @@ function OverflowContent({ children, style: styleProp, ...rest }: OverflowConten
     const observers = observersRef.current;
     const watchNode = watchRef.current;
 
-    observers?.up.observe(watchNode);
-    observers.left.observe(watchNode);
-    observers.right.observe(watchNode);
-    observers.down.observe(watchNode);
+    if (watchNode) {
+      observers?.up.observe(watchNode);
+      observers?.left.observe(watchNode);
+      observers?.right.observe(watchNode);
+      observers?.down.observe(watchNode);
+    }
 
     return () => {
-      observers.up.unobserve(watchNode);
-      observers.left.unobserve(watchNode);
-      observers.right.unobserve(watchNode);
-      observers.down.unobserve(watchNode);
+      if (watchNode) {
+        observers?.up.unobserve(watchNode);
+        observers?.left.unobserve(watchNode);
+        observers?.right.unobserve(watchNode);
+        observers?.down.unobserve(watchNode);
+      }
     };
   }, [watchRef]);
 
@@ -343,9 +359,9 @@ interface OverflowContent {
   /**
    * Content to render inside the scrollable viewport.
    */
-  children: ReactNode
-  style: CSSProperties
-};
+  children: ReactNode;
+  style: CSSProperties;
+}
 
 /**
  * A helper component for rendering your custom indicator when the viewport is
@@ -388,7 +404,7 @@ interface OverflowContent {
  * </Overflow>
  * ```
  */
-function OverflowIndicator({ children, direction } : OverflowIndicator) {
+function OverflowIndicator({ children, direction }: OverflowIndicator) {
   const { state, refs } = useOverflow();
   const { canScroll } = state;
   const isActive = direction
@@ -415,13 +431,18 @@ interface OverflowIndicator {
    * containing the `viewport` ref. You can use this `refs` parameter to render
    * an indicator that is also a button that scrolls the viewport (for example).
    */
-  children: ReactElement | ((stateArg: boolean | CanScroll, refs: OverflowContext['refs']) => ReactElement)
+  children:
+    | ReactElement
+    | ((
+        stateArg: boolean | CanScroll,
+        refs: OverflowContext['refs']
+      ) => ReactElement);
   /**
    * The scrollabe direction to watch for. If not supplied, the indicator will
    * be active when scrolling is allowed in any direction.
    */
-  direction: keyof typeof Direction
-};
+  direction: keyof typeof Direction;
+}
 
 Overflow.Indicator = OverflowIndicator;
 Overflow.Content = OverflowContent;

From f8f5cd91e5e17333a6d61f45ecaf02f79c33a511 Mon Sep 17 00:00:00 2001
From: "Sinan Sonmez (Chaush)" <sinansonmez@outlook.com>
Date: Fri, 22 Nov 2024 21:48:49 +0000
Subject: [PATCH 3/4] replace index.js with index.tsx

---
 rollup.config.js        | 2 +-
 scripts/extract-docs.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/rollup.config.js b/rollup.config.js
index d4d38c0..7872f6e 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -6,7 +6,7 @@ import { terser } from 'rollup-plugin-terser';
 import pkg from './package.json';
 
 const baseConfig = {
-  input: 'src/index.js',
+  input: 'src/index.tsx',
   external: ['react', 'react-dom', 'prop-types'],
   output: [
     { file: pkg.main, format: 'cjs' },
diff --git a/scripts/extract-docs.js b/scripts/extract-docs.js
index e9056f3..e4f3421 100755
--- a/scripts/extract-docs.js
+++ b/scripts/extract-docs.js
@@ -3,7 +3,7 @@ const fs = require('fs');
 const reactDocs = require('react-docgen');
 const displayNameHandler = require('react-docgen-displayname-handler').default;
 
-const files = ['src/index.js'];
+const files = ['src/index.tsx'];
 
 const resolver = reactDocs.resolver.findAllComponentDefinitions;
 const handlers = reactDocs.defaultHandlers.concat([displayNameHandler]);

From be597a4d57abc99bcac9e08b05ae2c3501318e41 Mon Sep 17 00:00:00 2001
From: "Sinan Sonmez (Chaush)" <sinansonmez@outlook.com>
Date: Fri, 22 Nov 2024 21:51:51 +0000
Subject: [PATCH 4/4] update ts.config

---
 tsconfig.json | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tsconfig.json b/tsconfig.json
index a81f956..1391cda 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,8 +26,7 @@
     "incremental": true
   },
   "include": [
-    "./src/**/*",
-    "./src/**/*.json"
+    "./src/**/*"
   ],
   "exclude": [
     "node_modules"