diff --git a/package-lock.json b/package-lock.json index 84a9373..73cebac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "libs/**", "packages/**" ], + "dependencies": { + "react": "^19.0.0" + }, "devDependencies": { "@eslint/js": "^9.8.0", "@nx/eslint": "20.3.0", @@ -23,6 +26,7 @@ "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", "@types/node": "18.16.9", + "@types/react": "^19.0.2", "@vitest/coverage-v8": "^1.0.4", "@vitest/ui": "^1.3.1", "eslint": "^9.8.0", @@ -2279,6 +2283,10 @@ "resolved": "packages/core", "link": true }, + "node_modules/@focus/react-connect": { + "resolved": "packages/react-connect", + "link": true + }, "node_modules/@focus/store": { "resolved": "packages/store", "link": true @@ -3401,6 +3409,15 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, + "node_modules/@types/react": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz", + "integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==", + "dev": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", @@ -5562,6 +5579,12 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -9067,6 +9090,14 @@ "node": ">=0.10.0" } }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11047,9 +11078,19 @@ "tslib": "^2.3.0" } }, + "packages/react-connect": { + "name": "@focus/react-connect", + "version": "0.0.1", + "dependencies": { + "@focus/core": "0.0.1", + "tslib": "^2.3.0" + } + }, "packages/store": { + "name": "@focus/store", "version": "0.0.1", "dependencies": { + "@focus/core": "0.0.1", "tslib": "^2.3.0" } } diff --git a/package.json b/package.json index fd89bac..c930b9c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "license": "MIT", "scripts": {}, "private": true, - "dependencies": {}, + "dependencies": { + "react": "^19.0.0" + }, "devDependencies": { "@eslint/js": "^9.8.0", "@nx/eslint": "20.3.0", @@ -16,6 +18,7 @@ "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", "@types/node": "18.16.9", + "@types/react": "^19.0.2", "@vitest/coverage-v8": "^1.0.4", "@vitest/ui": "^1.3.1", "eslint": "^9.8.0", diff --git a/packages/react-connect/README.md b/packages/react-connect/README.md new file mode 100644 index 0000000..f1ef2af --- /dev/null +++ b/packages/react-connect/README.md @@ -0,0 +1,11 @@ +# react-connect + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build react-connect` to build the library. + +## Running unit tests + +Run `nx test react-connect` to execute the unit tests via [Vitest](https://vitest.dev/). diff --git a/packages/react-connect/eslint.config.cjs b/packages/react-connect/eslint.config.cjs new file mode 100644 index 0000000..aa7aea5 --- /dev/null +++ b/packages/react-connect/eslint.config.cjs @@ -0,0 +1,22 @@ +const baseConfig = require('../../eslint.config.cjs'); + +module.exports = [ + ...baseConfig, + { + files: ['**/*.json'], + rules: { + '@nx/dependency-checks': [ + 'error', + { + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + '{projectRoot}/vite.config.{js,ts,mjs,mts}', + ], + }, + ], + }, + languageOptions: { + parser: require('jsonc-eslint-parser'), + }, + }, +]; diff --git a/packages/react-connect/package.json b/packages/react-connect/package.json new file mode 100644 index 0000000..b0df58b --- /dev/null +++ b/packages/react-connect/package.json @@ -0,0 +1,33 @@ +{ + "name": "@focus/react-connect", + "version": "0.0.1", + "dependencies": { + "react": "^19.0.0", + "tslib": "^2.3.0", + "@focus/core": "0.0.1", + "@focus/store": "0.0.1" + }, + "type": "commonjs", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "dist", + "!**/*.tsbuildinfo" + ], + "nx": { + "sourceRoot": "packages/react-connect/src", + "projectType": "library", + "name": "react-connect", + "targets": { + "test": { + "executor": "@nx/vite:test", + "outputs": [ + "{options.reportsDirectory}" + ], + "options": { + "reportsDirectory": "../../coverage/packages/react-connect" + } + } + } + } +} diff --git a/packages/react-connect/src/index.ts b/packages/react-connect/src/index.ts new file mode 100644 index 0000000..f41a696 --- /dev/null +++ b/packages/react-connect/src/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/packages/react-connect/src/lib/index.spec.ts b/packages/react-connect/src/lib/index.spec.ts new file mode 100644 index 0000000..df20782 --- /dev/null +++ b/packages/react-connect/src/lib/index.spec.ts @@ -0,0 +1,5 @@ +describe('Test to be added soon', () => { + test('Test to be added soon', () => { + expect(true).toEqual(true); + }); +}); diff --git a/packages/react-connect/src/lib/index.ts b/packages/react-connect/src/lib/index.ts new file mode 100644 index 0000000..4de5b6e --- /dev/null +++ b/packages/react-connect/src/lib/index.ts @@ -0,0 +1,38 @@ +// https://react.dev/reference/react/useSyncExternalStore + +import { useSyncExternalStore } from 'react'; +import { Lens, noop } from '@focus/core'; +import { createStore, Logger } from '@focus/store'; + +export const connect = (initialState: State, logger: Logger = noop) => { + const store = createStore(initialState, logger); + + const useGlobalState = () => { + const { getState, select, updateState, batchUpdates, subscribe } = store; + + const state = useSyncExternalStore(subscribe, getState); + + return { + state, + getSynchronousState: getState, + select, + updateState, + batchUpdates, + focus: (lens: Lens) => { + const focusedStore = store.focus(lens); + + const { getState, select, updateState, batchUpdates } = focusedStore; + + return { + state: lens.get(state), + getSynchronousState: getState, + select, + updateState, + batchUpdates, + }; + }, + }; + }; + + return useGlobalState; +}; diff --git a/packages/react-connect/tsconfig.json b/packages/react-connect/tsconfig.json new file mode 100644 index 0000000..a2bef73 --- /dev/null +++ b/packages/react-connect/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "../store" + }, + { + "path": "../core" + }, + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/react-connect/tsconfig.lib.json b/packages/react-connect/tsconfig.lib.json new file mode 100644 index 0000000..d177fec --- /dev/null +++ b/packages/react-connect/tsconfig.lib.json @@ -0,0 +1,34 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../store/tsconfig.lib.json" + }, + { + "path": "../core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] +} diff --git a/packages/react-connect/tsconfig.spec.json b/packages/react-connect/tsconfig.spec.json new file mode 100644 index 0000000..bb0d087 --- /dev/null +++ b/packages/react-connect/tsconfig.spec.json @@ -0,0 +1,33 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out-tsc/vitest", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest" + ] + }, + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/react-connect/vite.config.ts b/packages/react-connect/vite.config.ts new file mode 100644 index 0000000..ac58600 --- /dev/null +++ b/packages/react-connect/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/react-connect', + plugins: [], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + test: { + watch: false, + globals: true, + environment: 'node', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: './test-output/vitest/coverage', + provider: 'v8', + }, + }, +}); diff --git a/tsconfig.json b/tsconfig.json index c3a7b0b..a318700 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,9 @@ }, { "path": "./packages/store" + }, + { + "path": "./packages/react-connect" } ] }