Skip to content

Commit

Permalink
Chrome extension support (#150)
Browse files Browse the repository at this point in the history
* chore(ts): upgrade `target` version to `es6`

* feat: add chrome extension support

* ci: upgrade node version to 16

* refactor: optimize getting control
  • Loading branch information
jsun969 authored Jul 9, 2022
1 parent a9f75d3 commit 0af2e20
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 24 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ jobs:
run: |
npm run lint:types
npm run lint
# - name: Test
# run: npm test
# env:
# CI: true
# - name: Test
# run: npm test
# env:
# CI: true
- name: Build
run: npm run build
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
with:
fetch-depth: 0

- name: Use Node.js 12.x
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
version: 12.x
version: 16.x

- name: Install Dependencies
run: yarn --frozen-lockfile
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@
"@types/lodash": "^4.14.168",
"little-state-machine": "^4.1.0",
"lodash": "^4.17.21",
"react-simple-animate": "^3.3.12"
"nanoid": "^4.0.0",
"react-simple-animate": "^3.3.12",
"use-deep-compare-effect": "^1.8.1"
},
"devDependencies": {
"@babel/core": "^7.13.16",
Expand Down
12 changes: 10 additions & 2 deletions src/devTool.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createStore, StateMachineProvider } from 'little-state-machine';
import * as React from 'react';
import { StateMachineProvider, createStore } from 'little-state-machine';
import { Control, FieldValues, useFormContext } from 'react-hook-form';
import { DevToolUI } from './devToolUI';
import { useExportControlToExtension } from './extension/useExportControlToExtension';
import type { PLACEMENT } from './position';

if (typeof window !== 'undefined') {
Expand All @@ -25,10 +26,17 @@ export const DevTool = <T extends FieldValues>(props?: {
}) => {
const methods = useFormContext();

const { isExtensionEnabled } = useExportControlToExtension(
props?.control ?? methods.control,
);
if (isExtensionEnabled) {
return null;
}

return (
<StateMachineProvider>
<DevToolUI
control={(props && props.control) || methods.control}
control={props?.control ?? methods.control}
placement={props?.placement}
/>
</StateMachineProvider>
Expand Down
38 changes: 38 additions & 0 deletions src/extension/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export type UpdatePayload<
TFieldValues extends Record<string, any> = Record<string, any>,
> = {
id: string;
data: {
formValues: TFieldValues;
formState: {
errors: Record<keyof TFieldValues, { type?: string; message?: string }>;
dirtyFields: Record<keyof TFieldValues, boolean>;
touchedFields: Record<keyof TFieldValues, boolean>;
nativeFields: Record<keyof TFieldValues, boolean>;
submitCount: number;
isSubmitted: boolean;
isSubmitting: boolean;
isSubmitSuccessful: boolean;
isValid: boolean;
isValidating: boolean;
isDirty: boolean;
};
};
};

type InitMessageData = {
source: string;
type: 'INIT' | 'WELCOME';
};

type UpdateMessageData<
TFieldValues extends Record<string, any> = Record<string, any>,
> = {
source: string;
type: 'UPDATE';
payload: UpdatePayload<TFieldValues>;
};

export type MessageData<
TFieldValues extends Record<string, any> = Record<string, any>,
> = InitMessageData | UpdateMessageData<TFieldValues>;
96 changes: 96 additions & 0 deletions src/extension/useExportControlToExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import _ from 'lodash';
import { nanoid } from 'nanoid';
import { useEffect, useState } from 'react';
import { Control, useFormState, useWatch } from 'react-hook-form';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { MessageData, UpdatePayload } from './types';
import { proxyToObject } from './utils';

const id = nanoid();

export function useExportControlToExtension(control: Control<any>) {
const nestedFormValues = useWatch({ control });
const formState = useFormState({ control });

const [isExtensionEnabled, setIsExtensionEnabled] = useState(false);

const handleInitMessage = (message: MessageEvent<MessageData>) => {
if (
message.data.source !== 'react-hook-form-bridge' ||
message.data.type !== 'INIT'
) {
return;
}
window.postMessage({
source: 'react-hook-form-bridge',
type: 'WELCOME',
} as MessageData);
setIsExtensionEnabled(true);
};

useEffect(() => {
window.addEventListener('message', handleInitMessage);
return () => window.removeEventListener('message', handleInitMessage);
}, []);

const toFlat = <V>(obj: object, defaultValue?: V) => {
return [...control._names.mount].reduce((perv, name) => {
// nested field may be `undefined`
perv[name] = _.get(obj, name) || defaultValue;
return perv;
}, {} as Record<string, V>);
};

useDeepCompareEffect(() => {
if (!isExtensionEnabled) {
return;
}

const {
errors: nestedErrors,
dirtyFields: nestedDirtyFields,
touchedFields: nestedTouchedFields,
...formStatus
} = proxyToObject(formState);

const formValues = toFlat(nestedFormValues, '');
const dirtyFields = toFlat(nestedDirtyFields, false);
const touchedFields = toFlat(nestedTouchedFields, false);

const errors = Object.entries(
toFlat<{ type: string; message: string }>(nestedErrors),
).reduce((perv, [key, value]) => {
perv[key] = {
type: value?.type as string,
message: value?.message as string,
};
return perv;
}, {} as Record<string, { type?: string; message?: string }>);

const nativeFields = [...control._names.mount].reduce((perv, name) => {
perv[name] = !!_.get(control._fields, name)?._f?.ref?.type;
return perv;
}, {} as Record<string, boolean>);

const updateMessagePayload: UpdatePayload = {
id,
data: {
formValues,
formState: {
errors,
dirtyFields,
touchedFields,
nativeFields,
...formStatus,
},
},
};
window.postMessage({
source: 'react-hook-form-bridge',
type: 'UPDATE',
payload: updateMessagePayload,
} as MessageData);
}, [isExtensionEnabled, nestedFormValues, proxyToObject(formState)]);

return { isExtensionEnabled };
}
6 changes: 6 additions & 0 deletions src/extension/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function proxyToObject<T extends Record<string, any>>(proxy: T) {
return Reflect.ownKeys(proxy).reduce((perv, key) => {
perv[key as keyof T] = proxy[key as keyof T];
return perv;
}, {} as T);
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"sourceMap": true,
"module": "esnext",
"target": "es5",
"target": "es6",
"moduleResolution": "node",
"outDir": "./dist",
"jsx": "react",
Expand Down
33 changes: 19 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5486,6 +5486,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==

dequal@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==

des.js@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
Expand Down Expand Up @@ -6402,11 +6407,6 @@ fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==
dependencies:
extend-shallow "^2.0.1"
is-number "^3.0.0"
repeat-string "^1.6.1"
to-regex-range "^2.1.0"

fill-range@^7.0.1:
version "7.0.1"
Expand Down Expand Up @@ -9188,6 +9188,11 @@ nanoid@^3.3.1:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==

nanoid@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5"
integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==

nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
Expand Down Expand Up @@ -10692,7 +10697,7 @@ repeat-element@^1.1.2:
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9"
integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==

repeat-string@^1.5.4, repeat-string@^1.6.1:
repeat-string@^1.5.4:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
Expand Down Expand Up @@ -11928,14 +11933,6 @@ to-object-path@^0.3.0:
dependencies:
kind-of "^3.0.2"

to-regex-range@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==
dependencies:
is-number "^3.0.0"
repeat-string "^1.6.1"

to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
Expand Down Expand Up @@ -12381,6 +12378,14 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"

use-deep-compare-effect@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.8.1.tgz#ef0ce3b3271edb801da1ec23bf0754ef4189d0c6"
integrity sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==
dependencies:
"@babel/runtime" "^7.12.5"
dequal "^2.0.2"

use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
Expand Down

0 comments on commit 0af2e20

Please sign in to comment.