├── .eslintrc.js ├── .gitignore ├── .husky └── pre-commit ├── .idea ├── .gitignore ├── ContinuousContainer.iml ├── modules.xml └── vcs.xml ├── .nvmrc ├── .size-limit.js ├── .size.json ├── .travis.yml ├── README.md ├── __tests__ └── index.tsx ├── jest.config.js ├── package.json ├── src ├── components │ └── index.tsx ├── hooks │ ├── base.ts │ ├── scattered.ts │ ├── state.ts │ └── utils │ │ └── index.ts └── index.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:@typescript-eslint/recommended', 4 | 'plugin:import/typescript', 5 | 'plugin:react-hooks/recommended' 6 | ], 7 | parser: '@typescript-eslint/parser', 8 | plugins: ['@typescript-eslint', 'prettier', 'import'], 9 | rules: { 10 | '@typescript-eslint/ban-ts-comment': 0, 11 | '@typescript-eslint/ban-ts-ignore': 0, 12 | '@typescript-eslint/no-var-requires': 0, 13 | '@typescript-eslint/camelcase': 0, 14 | 'import/order': [ 15 | 'error', 16 | { 17 | 'newlines-between': 'always-and-inside-groups', 18 | alphabetize: { 19 | order: 'asc', 20 | }, 21 | groups: ['builtin', 'external', 'internal', ['parent', 'index', 'sibling']], 22 | }, 23 | ], 24 | "padding-line-between-statements": [ 25 | "error", 26 | // IMPORT 27 | { 28 | blankLine: "always", 29 | prev: "import", 30 | next: "*", 31 | }, 32 | { 33 | blankLine: "any", 34 | prev: "import", 35 | next: "import", 36 | }, 37 | // EXPORT 38 | { 39 | blankLine: "always", 40 | prev: "*", 41 | next: "export", 42 | }, 43 | { 44 | blankLine: "any", 45 | prev: "export", 46 | next: "export", 47 | }, 48 | { 49 | blankLine: "always", 50 | prev: "*", 51 | next: ["const", "let"], 52 | }, 53 | { 54 | blankLine: "any", 55 | prev: ["const", "let"], 56 | next: ["const", "let"], 57 | }, 58 | // BLOCKS 59 | { 60 | blankLine: "always", 61 | prev: ["block", "block-like", "class", "function", "multiline-expression"], 62 | next: "*", 63 | }, 64 | { 65 | blankLine: "always", 66 | prev: "*", 67 | next: ["block", "block-like", "class", "function", "return", "multiline-expression"], 68 | }, 69 | ], 70 | }, 71 | settings: { 72 | 'import/parsers': { 73 | '@typescript-eslint/parser': ['.ts', '.tsx'], 74 | }, 75 | 'import/resolver': { 76 | typescript: { 77 | alwaysTryTypes: true, 78 | }, 79 | }, 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | yarn-debug.log* 3 | yarn-error.log* 4 | 5 | lib-cov 6 | coverage 7 | .nyc_output 8 | 9 | .docz 10 | dist 11 | node_modules/ 12 | 13 | .eslintcache -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | lint-staged 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/ContinuousContainer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10 -------------------------------------------------------------------------------- /.size-limit.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | path: 'dist/es2015/index.js', 4 | limit: '0.7 KB', 5 | }, 6 | ]; 7 | -------------------------------------------------------------------------------- /.size.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "dist/es2015/index.js", 4 | "passed": true, 5 | "size": 655 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | cache: yarn 5 | script: 6 | - yarn 7 | - yarn test:ci 8 | - codecov 9 | notifications: 10 | email: true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 🧒 - 👨‍🦱 - 👨‍🦳 3 |

Continuous Container

4 |
5 | Something with known past, present, and the future 6 |
7 | a `useState` aware of continuous nature of time 8 |
9 | 10 |

11 | 12 | Bundlephobia 13 | 14 | 15 | Types 16 | 17 | 18 | MIT License 19 | 20 |

21 | 22 |
npm i @theuiteam/continuous-container
23 |
24 | 25 | 26 |
27 | 28 | ## Idea 29 | 30 | This is almost `react transition group`, but for state management... 31 | 32 | Why people [react transition group](https://github.com/reactjs/react-transition-group)? Because it 1) **takes a pause** 33 | between steps letting classNames be applied and 2) **keeps children** after you remove them, to perform a fade 34 | animation. 35 | 36 | We are doing the same, but using state. 37 | 38 | It's all about tracking what the value `would be`, what is `right now`, and what it `was`. 39 | We call this ContinuousState 40 | 41 | ### ContinuousState 42 | 43 | - `future` - the next value. The value you just set. 44 | - `present` - to be synchronized with the `future` "later" 45 | - `past` - to be synchronized with the `present` "later" 46 | 47 | and 48 | 49 | - `defined` - an indication that any of future, past or present are truthy. 50 | 51 | # API 52 | 53 | ## useContinuousState(value, options) 54 | 55 | - `value` - a value to assign to the `future` 56 | - `options` 57 | - `delayPresent` - time in ms between `future` becoming `present` 58 | - `delayPast` - time in ms between `present` becoming `past`. For transitions, it usually equals to exist animation duration 59 | - `initialValue` - a value to be set as initial to the `past` and `present`. `future` is always equal to the `value` given as a first arg 60 | 61 | ## useScatteredContinuousState(value, options) 62 | 63 | Call signature is equal to `useContinuousState`, returns an object with extra property `DefinePresent`. See example below. 64 | 65 | ## Usage 66 | 67 | ### Problem statement 68 | 69 | Let's imagine you have a switch. Which controls visibility of something, but you also want to add some animation. 70 | 71 | Let's handle these cases separately: 72 | 73 | ```typescript jsx 74 | const App = () => { 75 | const [on, setOn] = useState(false); 76 | 77 | return ( 78 |
79 | 80 | // would be instanly hidden and shown 81 | {on && } 82 | // would be animated, but would be ALWAYS rendered 83 | } 84 |
85 | ); 86 | }; 87 | ``` 88 | 89 | Now let's imagine you want to **not render Content** _when_ it's not visible and not required. 90 | 91 | Ok, "when is this _when_"? 92 | 93 | - render `ContentWithAnimation` when it is _about_ to be displayed 94 | - render `ContentWithAnimation` when it is displayed 95 | - render `ContentWithAnimation` when it is no longer visible, but still animating toward hidden state 96 | 97 | ```typescript jsx 98 | import { ContinuousContainer, useContinuousState } from '@theuiteam/continuous-container'; 99 | 100 | const App = () => { 101 | const [on, setOn] = useState(false); 102 | const continuousState = useContinuousState(on); 103 | 104 | return ( 105 |
106 | 107 | {/*render if any of past/preset/future is set to true*/} 108 | {continuousState.defined && ( 109 | 110 | // wire the "present" state 111 | )} 112 | {/* or */} 113 | 114 | { 115 | ({past, present, future}) => (past || present || future) && 116 | // ^^ use the "present" value 117 | } 118 | 119 |
120 | ); 121 | }; 122 | ``` 123 | 124 | ## Scattered 125 | 126 | There are more sophisticated situations, when **setting up** something to display does not mean "display". Lazy loading 127 | is a good case 128 | 129 | ```tsx 130 | const App = () => { 131 | const continuousState = useContinuousState(on); 132 | return continuousState.defined && ; 133 | }; 134 | ``` 135 | 136 | In such case ContinuousState will update from `future` to `present` before `LazyLoadedContentWithAnimation` component is 137 | loaded, breaking a connection between states. 138 | 139 | In order to handle this problem one might need to _tap_ into rendering process using `useScatteredContinuousState` 140 | 141 | ```tsx 142 | const continuousState = useScatteredContinuousState(on); 143 | return ( 144 | continuousState.defined && ( 145 | 146 | 147 | 148 | {/*this component will advance ContinuousState once rendered*/} 149 | 150 | 151 | ) 152 | ); 153 | ``` 154 | 155 | For readability purposes we recommend putting DefinePresent to a separate slot different from `children`. 156 | 157 | ```tsx 158 | } /> 159 | ``` 160 | 161 | ###### ⚠️⚠️⚠️⚠️⚠️⚠️⚠️ 162 | 163 | The following code will NOT work as `DefinePresent` will be rendered instantly, even if suspense will be in fallback 164 | 165 | ```tsx 166 | 167 | // will not be rendred until ready 168 | 169 | // will be rendered too early 170 | 171 | 172 | ``` 173 | 174 | # See also 175 | 176 | - [Phased](https://github.com/theKashey/recondition#phased) Container from a `recondition` library 177 | 178 | # License 179 | 180 | MIT 181 | -------------------------------------------------------------------------------- /__tests__/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useContinuousState, useScatteredContinuousState} from '../src'; 3 | import {render, fireEvent, act} from '@testing-library/react'; 4 | import {FC, lazy, Suspense, useState} from 'react'; 5 | 6 | describe('useContinuousState', () => { 7 | describe('initialization', () => { 8 | it('control flow - default', () => { 9 | const stateCallback = jest.fn(); 10 | const TestSuite = () => { 11 | const state = useContinuousState(true); 12 | stateCallback(state.past, state.present, state.future); 13 | 14 | return null; 15 | }; 16 | 17 | render(); 18 | expect(stateCallback).toHaveBeenCalledTimes(1); 19 | expect(stateCallback).toHaveBeenNthCalledWith(1, true, true, true); 20 | }); 21 | it('control flow - init true', () => { 22 | const stateCallback = jest.fn(); 23 | const TestSuite = () => { 24 | const state = useContinuousState(true, {initialValue: true}); 25 | stateCallback(state.past, state.present, state.future); 26 | 27 | return null; 28 | }; 29 | 30 | render(); 31 | expect(stateCallback).toHaveBeenCalledTimes(1); 32 | expect(stateCallback).toHaveBeenNthCalledWith(1, true, true, true); 33 | }); 34 | it('control flow - init false', () => { 35 | const stateCallback = jest.fn(); 36 | const TestSuite = () => { 37 | const state = useContinuousState(true, {initialValue: false}); 38 | stateCallback(state.past, state.present, state.future); 39 | 40 | return null; 41 | }; 42 | 43 | render(); 44 | expect(stateCallback).toHaveBeenCalledTimes(2); 45 | expect(stateCallback).toHaveBeenNthCalledWith(1, false, false, false); 46 | expect(stateCallback).toHaveBeenNthCalledWith(2, false, false, true); 47 | }); 48 | }); 49 | 50 | describe('advance', () => { 51 | const advance = () => { 52 | act(() => { 53 | jest.advanceTimersByTime(1) 54 | }) 55 | }; 56 | 57 | it('off-on-off', () => { 58 | jest.useFakeTimers(); 59 | 60 | const TestSuite = () => { 61 | const [on, setOn] = useState(false); 62 | const state = useContinuousState(on); 63 | 64 | return ( 65 | 70 | ); 71 | }; 72 | 73 | const probe = render(); 74 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
"`); 75 | // -> on 76 | fireEvent.click(probe.getByText('toggle fff')); 77 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
"`); 78 | // -> advance | set present 79 | advance(); 80 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
"`); 81 | 82 | // -> off 83 | fireEvent.click(probe.getByText('toggle ftt')); 84 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
"`); 85 | // -> advance | set present 86 | advance(); 87 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
"`); 88 | }); 89 | 90 | it('scattered', async () => { 91 | jest.useFakeTimers(); 92 | 93 | let resolve:any; 94 | const lazyPromise = new Promise<{default:FC}>(r => { 95 | resolve=r; 96 | }) 97 | 98 | const LazyComponent = lazy(() => lazyPromise) 99 | 100 | const TestSuite = () => { 101 | const [on, setOn] = useState(false); 102 | const state = useScatteredContinuousState(on); 103 | 104 | return ( 105 | <> 106 | 111 | {state.defined && ( 112 | 113 | }/> 114 | 115 | )} 116 | 117 | ); 118 | }; 119 | 120 | const probe = render(); 121 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
"`); 122 | // -> on 123 | fireEvent.click(probe.getByText('toggle fff')); 124 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
loading
"`); 125 | // -> advance | set present 126 | advance(); 127 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
loading
"`); 128 | // -> resolve 129 | resolve({default: ({effector}: any) => <>resolved{effector}}); 130 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
loading
"`); 131 | // wait for lazy 132 | await act(async () => {}); 133 | 134 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
resolved
"`); 135 | advance(); 136 | expect(probe.baseElement.innerHTML).toMatchInlineSnapshot(`"
resolved
"`); 137 | }) 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@theuiteam/continuous-container", 3 | "version": "2.0.1", 4 | "description": "State container with the known past, present, and the future", 5 | "main": "dist/es5/index.js", 6 | "author": "Anton Korzunov ", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "@theuiteam/lib-builder": "^0.2.1", 10 | "@size-limit/preset-small-lib": "^2.1.6" 11 | }, 12 | "module": "dist/es2015/index.js", 13 | "types": "dist/es5/index.d.ts", 14 | "engines": { 15 | "node": ">=10" 16 | }, 17 | "scripts": { 18 | "dev": "lib-builder dev", 19 | "test": "jest", 20 | "test:ci": "jest --runInBand --coverage", 21 | "build": "lib-builder build && yarn size:report", 22 | "release": "yarn build && yarn test", 23 | "size": "npx size-limit", 24 | "size:report": "npx size-limit --json > .size.json", 25 | "lint": "lib-builder lint", 26 | "format": "lib-builder format", 27 | "update": "lib-builder update", 28 | "docz:dev": "docz dev", 29 | "docz:build": "docz build", 30 | "prepublish": "yarn build", 31 | "prepublish-only": "yarn build && yarn changelog", 32 | "prepare": "husky install", 33 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", 34 | "changelog:rewrite": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" 35 | }, 36 | "peerDependencies": { 37 | "react": "^16.9.0 || ^17.0.0 || ^18.0.0", 38 | "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0" 39 | }, 40 | "dependencies": { 41 | "tslib": "^2.1.0" 42 | }, 43 | "files": [ 44 | "dist" 45 | ], 46 | "keywords": [], 47 | "repository": "https://github.com/TheUiTeam/ContinuousContainer", 48 | "husky": { 49 | "hooks": { 50 | "pre-commit": "lint-staged" 51 | } 52 | }, 53 | "lint-staged": { 54 | "*.{ts,tsx}": [ 55 | "prettier --write", 56 | "eslint --fix" 57 | ], 58 | "*.{js,css,json,md}": [ 59 | "prettier --write" 60 | ] 61 | }, 62 | "prettier": { 63 | "printWidth": 120, 64 | "trailingComma": "es5", 65 | "tabWidth": 2, 66 | "semi": true, 67 | "singleQuote": true 68 | }, 69 | "module:es2019": "dist/es2019/index.js", 70 | "peerDependenciesMeta": { 71 | "@types/react": { 72 | "optional": true 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import {useContinuousState} from '../hooks/state'; 4 | import {ContinuousState} from "../hooks/state"; 5 | 6 | type LazyContainerProps = { 7 | /** 8 | * Value to be used 9 | */ 10 | value: T; 11 | /** 12 | * timeout for the "past" propagation 13 | */ 14 | exitTimeout: number | ((t: T) => number); 15 | 16 | /** 17 | * the render function 18 | */ 19 | children(state: ContinuousState): React.ReactElement | null; 20 | } 21 | 22 | /** 23 | * 24 | * @param value 25 | * @param timeout 26 | * @param children 27 | * @constructor 28 | */ 29 | export function ContinuousContainer({ 30 | value, 31 | exitTimeout, 32 | children, 33 | }: LazyContainerProps): React.ReactElement | null { 34 | return children(useContinuousState(value, {delayPast: exitTimeout})); 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/base.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | 3 | import { getTimeValue, type TimeConfiguration } from './utils'; 4 | 5 | type BaseContinuousState = { 6 | defined: boolean; 7 | past: T; 8 | present: T; 9 | future: T; 10 | definePresent(): void; 11 | }; 12 | 13 | export const useContinuousStateBase = ( 14 | value: T, 15 | options: { 16 | delayPast?: TimeConfiguration; 17 | initialValue?: T; 18 | }, 19 | ): BaseContinuousState => { 20 | // phased state 21 | const initialValue = 22 | 'initialValue' in options ? options.initialValue! : value; 23 | const { delayPast } = options; 24 | const [past, setPastState] = useState(initialValue); 25 | const [present, setPresentState] = useState(initialValue); 26 | const [future, setFutureState] = useState(initialValue); 27 | 28 | // set the future 29 | useEffect(() => { 30 | setFutureState(value); 31 | }, [value]); 32 | 33 | const definePresent = useCallback( 34 | () => setPresentState(future), 35 | [setPresentState, future], 36 | ); 37 | 38 | // forget the past 39 | useEffect(() => { 40 | const tm = setTimeout( 41 | setPastState, 42 | delayPast ? getTimeValue(delayPast, present) : 1, 43 | present, 44 | ); 45 | 46 | return () => clearTimeout(tm); 47 | }, [present, delayPast]); 48 | 49 | return { 50 | defined: Boolean(past) || Boolean(present) || Boolean(future), 51 | past, 52 | present, 53 | future, 54 | definePresent, 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /src/hooks/scattered.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from 'react'; 2 | 3 | import { useContinuousStateBase } from './base'; 4 | import { getTimeValue, type TimeConfiguration } from './utils'; 5 | 6 | export type EmptyDelayComponent = (props: { 7 | timeout?: TimeConfiguration; 8 | }) => null; 9 | 10 | export type ScatteredContinuousState = { 11 | defined: boolean; 12 | past: T; 13 | present: T; 14 | future: T; 15 | DefinePresent: EmptyDelayComponent; 16 | }; 17 | 18 | /** 19 | * Special version of {@link useContinuousState} "scattered" in space and time. 20 | * One has to `` in order to move from "past" into present state 21 | */ 22 | export const useScatteredContinuousState = ( 23 | value: T, 24 | options: { 25 | delayPast?: TimeConfiguration; 26 | initialValue?: T; 27 | } = {}, 28 | ): ScatteredContinuousState => { 29 | const { definePresent, ...state } = useContinuousStateBase( 30 | value, 31 | options, 32 | ); 33 | const { future } = state; 34 | 35 | const DefinePresent: EmptyDelayComponent = useCallback( 36 | ({ timeout = 0 }) => { 37 | // eslint-disable-next-line react-hooks/rules-of-hooks 38 | useEffect(() => { 39 | const timer = setTimeout(() => { 40 | definePresent(); 41 | }, getTimeValue(timeout, future)); 42 | 43 | return () => { 44 | clearTimeout(timer); 45 | }; 46 | // eslint-disable-next-line react-hooks/exhaustive-deps 47 | }, [definePresent]); 48 | 49 | return null; 50 | }, 51 | [future, definePresent], 52 | ); 53 | 54 | return { 55 | ...state, 56 | DefinePresent, 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/hooks/state.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | import {useContinuousStateBase} from './base'; 4 | import {getTimeValue, type TimeConfiguration} from './utils'; 5 | 6 | export type ContinuousState = { 7 | defined: boolean; 8 | past: T; 9 | present: T; 10 | future: T; 11 | }; 12 | 13 | /** 14 | * returns 3 values representing 1) the past, 2) present and 3) future values letting you better understand change in time 15 | * @example 16 | * ```tsx 17 | * const state = useContinuousState(isEnabled); 18 | * // isEnabled == true? 19 | * // state.defined -> true 20 | * // state.past -> false 21 | * // state.present -> false 22 | * // state.future -> true ⬅️ 23 | * 24 | * const state = useContinuousState(isEnabled, {initialState:isEnabled}); 25 | * // state.past == state.present == true 26 | * ``` 27 | */ 28 | export const useContinuousState = ( 29 | value: T, 30 | options: { 31 | delayPast?: TimeConfiguration; 32 | delayPresent?: TimeConfiguration; 33 | initialValue?: T; 34 | } = {}, 35 | ): ContinuousState => { 36 | const {definePresent, ...state} = useContinuousStateBase( 37 | value, 38 | options, 39 | ); 40 | const {delayPresent = 1} = options; 41 | const {future} = state; 42 | 43 | useEffect(() => { 44 | // remember the present 45 | 46 | const timeout = setTimeout( 47 | definePresent, 48 | getTimeValue(delayPresent, future), 49 | future, 50 | ); 51 | 52 | return () => clearTimeout(timeout); 53 | }, 54 | [definePresent, future], 55 | ); 56 | 57 | return state; 58 | }; 59 | -------------------------------------------------------------------------------- /src/hooks/utils/index.ts: -------------------------------------------------------------------------------- 1 | export type TimeConfiguration = number | ((t: T) => number); 2 | export const getTimeValue = ( 3 | timeout: TimeConfiguration, 4 | value: T, 5 | ): number => (typeof timeout === 'function' ? timeout(value) : timeout); 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { ContinuousContainer } from './components'; 2 | export { useContinuousState, type ContinuousState } from './hooks/state'; 3 | export { useScatteredContinuousState, type ScatteredContinuousState } from './hooks/scattered'; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "allowSyntheticDefaultImports": true, 5 | "strict": true, 6 | "strictNullChecks": true, 7 | "strictFunctionTypes": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitAny": true, 15 | "removeComments": true, 16 | "importHelpers": true, 17 | "target": "es6", 18 | "moduleResolution": "node", 19 | "lib": ["dom", "es5", "scripthost", "es2015.collection", "es2015.symbol", "es2015.iterable", "es2015.promise"], 20 | "types": ["node", "jest"], 21 | "typeRoots": ["./node_modules/@types"], 22 | "jsx": "react" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-react-hooks", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": ["node_modules/**/*.ts"] 5 | }, 6 | "rules": { 7 | "no-bitwise": false, 8 | "quotemark": [true, "single", "jsx-double"], 9 | "no-unused-expression": [true, "allow-fast-null-checks"], 10 | "object-literal-sort-keys": false, 11 | "interface-name": false, 12 | "no-var-requires": false, 13 | "jsx-no-lambda": false, 14 | "max-classes-per-file": false, 15 | "jsx-self-close": false 16 | } 17 | } 18 | --------------------------------------------------------------------------------