@blockle/form
Advanced tools
Comparing version 0.0.1-alpha.5 to 0.0.1-alpha.6
@@ -15,163 +15,201 @@ /** | ||
var FormContext = React__default.createContext({ | ||
register: function () { | ||
throw new Error('Forgot to wrap form element in <Form /> component?'); | ||
}, | ||
subscribe: function () { | ||
throw new Error('Forgot to wrap form element in <Form /> component?'); | ||
}, | ||
notify: function () { | ||
throw new Error('Forgot to wrap form element in <Form /> component?'); | ||
}, | ||
const FormContext = React__default.createContext({ | ||
getState: () => ({}), | ||
dispatch: () => { }, | ||
subscribe: () => () => { }, | ||
}); | ||
//# sourceMappingURL=context.js.map | ||
var Form = function (_a) { | ||
var autocomplete = _a.autocomplete, children = _a.children, className = _a.className, _b = _a.noValidate, noValidate = _b === void 0 ? true : _b, onSubmit = _a.onSubmit; | ||
// Create mutatable state | ||
var state = React.useMemo(function () { return new Map(); }, []); | ||
var listeners = React.useMemo(function () { return []; }, []); | ||
var context = { | ||
register: function (_a) { | ||
var name = _a.name, stateRef = _a.stateRef, validate = _a.validate; | ||
state.set(name, [stateRef, validate]); | ||
return function () { | ||
state.delete(name); | ||
const initialState = {}; | ||
const formReducer = (state = initialState, action) => { | ||
switch (action.type) { | ||
case 'INIT': | ||
return { | ||
...state, | ||
[action.payload.name]: { | ||
name: action.payload.name, | ||
dirty: false, | ||
touched: false, | ||
validationMessage: action.payload.state.validationMessage, | ||
value: action.payload.state.value, | ||
}, | ||
}; | ||
}, | ||
subscribe: function (listener) { | ||
listeners.push(listener); | ||
return function () { | ||
var index = listeners.indexOf(listener); | ||
listeners.splice(index, 1); | ||
case 'UPDATE_FIELD': | ||
return { | ||
...state, | ||
[action.payload.name]: { | ||
...state[action.payload.name], | ||
...action.payload.state, | ||
}, | ||
}; | ||
}, | ||
notify: function (name, state) { | ||
listeners.forEach(function (listener) { return listener(name, state); }); | ||
}, | ||
case 'SET_TOUCHED': | ||
return { | ||
...state, | ||
[action.payload.name]: { | ||
...state[action.payload.name], | ||
touched: true, | ||
}, | ||
}; | ||
case 'REMOVE_FIELD': | ||
const nextState = { ...state }; | ||
delete nextState[action.payload]; | ||
return nextState; | ||
default: | ||
return state; | ||
} | ||
}; | ||
//# sourceMappingURL=reducer.js.map | ||
const createStore = () => { | ||
let currentState = {}; | ||
const listeners = []; | ||
const getState = () => currentState; | ||
const subscribe = (listener) => { | ||
listeners.push(listener); | ||
return () => { | ||
const index = listeners.indexOf(listener); | ||
listeners.splice(index, 1); | ||
}; | ||
}; | ||
var submit = function (event) { | ||
var isValid = true; | ||
event.preventDefault(); | ||
state.forEach(function (_a) { | ||
var validate = _a[1]; | ||
if (!validate()) { | ||
isValid = false; | ||
} | ||
}); | ||
if (!isValid) { | ||
console.log('Form invalid'); | ||
return; | ||
} | ||
var formData = {}; | ||
state.forEach(function (_a, name) { | ||
var stateRef = _a[0]; | ||
formData[name] = stateRef.current.value; | ||
}); | ||
onSubmit(formData); | ||
const dispatch = (action) => { | ||
currentState = formReducer(currentState, action); | ||
listeners.forEach(listener => listener()); | ||
}; | ||
return (React__default.createElement(FormContext.Provider, { value: context }, | ||
React__default.createElement("form", { className: className, onSubmit: submit, autoComplete: autocomplete ? 'on' : 'off', noValidate: noValidate }, children))); | ||
return { | ||
getState, | ||
subscribe, | ||
dispatch, | ||
}; | ||
}; | ||
//# sourceMappingURL=createStore.js.map | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
***************************************************************************** */ | ||
var __assign = function() { | ||
__assign = Object.assign || function __assign(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
const getFormData = (state) => { | ||
const values = Object.values(state).map(({ name, value }) => ({ name, value })); | ||
const formData = {}; | ||
values.forEach(({ name, value }) => { | ||
formData[name] = value; | ||
}); | ||
return formData; | ||
}; | ||
const isFormInvalid = (state) => Object.values(state).some(({ validationMessage }) => validationMessage !== null); | ||
//# sourceMappingURL=selectors.js.map | ||
var createInitialState = function (value) { return ({ | ||
dirty: false, | ||
invalid: false, | ||
touched: false, | ||
validationMessage: '', | ||
value: value, | ||
}); }; | ||
var useForm = function (_a) { | ||
var name = _a.name, value = _a.value, validate = _a.validate; | ||
var form = React.useContext(FormContext); | ||
var _b = React.useState(createInitialState(value)), state = _b[0], setState = _b[1]; | ||
var stateRef = React.useRef(state); | ||
stateRef.current = state; | ||
React.useEffect(function () { | ||
return form.register({ | ||
name: name, | ||
stateRef: stateRef, | ||
validate: function () { | ||
var state = stateRef.current; | ||
var validity = validator(state.value, true); | ||
var nextState = __assign({}, state, { touched: true, invalid: !!validity, validationMessage: validity || '' }); | ||
setState(nextState); | ||
form.notify(name, nextState); | ||
return !validity; | ||
}, | ||
}); | ||
}, [name]); | ||
React.useEffect(function () { | ||
setValue(value); | ||
}, [JSON.stringify(value)]); | ||
function validator(value, force) { | ||
if (force === void 0) { force = false; } | ||
if (!validate) { | ||
return null; | ||
const Form = ({ render, className, autocomplete, onSubmit, noValidate = true }) => { | ||
const store = React.useMemo(() => createStore(), []); | ||
const [submitting, setSubmitting] = React.useState(false); | ||
const [invalid, setInvalid] = React.useState(false); | ||
// Listen to store updates to set validity | ||
React.useEffect(() => { | ||
const listener = () => { | ||
const state = store.getState(); | ||
const isInvalid = isFormInvalid(state); | ||
setInvalid(isInvalid); | ||
}; | ||
listener(); | ||
return store.subscribe(() => listener()); | ||
}, []); | ||
function submit(event) { | ||
event.preventDefault(); | ||
const state = store.getState(); | ||
const formData = getFormData(state); | ||
const isInvalid = isFormInvalid(state); | ||
if (isInvalid) { | ||
setInvalid(true); | ||
return; | ||
} | ||
if (force) { | ||
return validate ? validate(value) : null; | ||
setSubmitting(true); | ||
const result = onSubmit(formData); | ||
// TODO Use await and ship without polyfill? | ||
if (result && result.then) { | ||
result.then(() => setSubmitting(false)); | ||
} | ||
return (state.touched || state.dirty) && validate ? validate(value) : null; | ||
else { | ||
setSubmitting(false); | ||
} | ||
} | ||
function setValue(value) { | ||
var validity = validator(value); | ||
var nextState = __assign({}, state, { value: value, invalid: !!validity, validationMessage: validity || '', dirty: value !== state.value }); | ||
setState(nextState); | ||
// Emit change | ||
form.notify(name, nextState); | ||
return (React__default.createElement(FormContext.Provider, { value: store }, | ||
React__default.createElement("form", { className: className, onSubmit: submit, autoComplete: autocomplete ? 'on' : 'off', noValidate: noValidate }, render({ submitting, invalid })))); | ||
}; | ||
//# sourceMappingURL=Form.js.map | ||
const createActionWithPayload = (type, payload) => ({ | ||
type, | ||
payload, | ||
}); | ||
// Initialize field state | ||
const initField = (name, state) => createActionWithPayload('INIT', { name, state }); | ||
// Update field state | ||
const updateField = (name, state) => createActionWithPayload('UPDATE_FIELD', { name, state }); | ||
// Field is touched | ||
const setTouched = (name) => createActionWithPayload('SET_TOUCHED', { name }); | ||
// Remove field state | ||
const removeField = (name) => createActionWithPayload('REMOVE_FIELD', name); | ||
//# sourceMappingURL=actions.js.map | ||
const stateDidChange = (state, prevState) => { | ||
if (state.value !== prevState.value || | ||
state.dirty !== prevState.dirty || | ||
state.touched !== prevState.touched || | ||
state.validationMessage !== prevState.validationMessage) { | ||
return true; | ||
} | ||
function setTouched() { | ||
setState(__assign({}, state, { touched: true })); | ||
} | ||
return __assign({}, state, { setValue: setValue, setTouched: setTouched }); | ||
return false; | ||
}; | ||
var useFormField = function (target) { | ||
var context = React.useContext(FormContext); | ||
var _a = React.useState({ | ||
const useField = (name, options) => { | ||
const store = React.useContext(FormContext); | ||
const [state, setState] = React.useState({ | ||
name, | ||
dirty: false, | ||
invalid: false, | ||
touched: false, | ||
validationMessage: '', | ||
value: null, | ||
}), state = _a[0], setState = _a[1]; | ||
React.useEffect(function () { | ||
return context.subscribe(function (name, state) { | ||
if (name !== target) { | ||
return; | ||
validationMessage: null, | ||
value: options.value, | ||
}); | ||
const currentState = React.useRef(state); | ||
// Update value whenever value (given by props) changes | ||
React.useEffect(() => { | ||
store.dispatch(updateField(name, { | ||
value: options.value, | ||
dirty: false, | ||
validationMessage: options.validate(options.value), | ||
})); | ||
}, [options.value]); | ||
React.useEffect(() => { | ||
// Update local state | ||
const unsubscribe = store.subscribe(() => { | ||
const prevState = currentState.current; | ||
const nextState = store.getState()[name]; | ||
if (prevState && nextState && stateDidChange(nextState, prevState)) { | ||
setState(nextState); | ||
currentState.current = nextState; | ||
} | ||
setState(state); | ||
}); | ||
}, []); | ||
return state; | ||
store.dispatch(initField(name, { | ||
value: options.value, | ||
validationMessage: options.validate(options.value), | ||
})); | ||
return () => { | ||
unsubscribe(); | ||
store.dispatch(removeField(name)); | ||
}; | ||
}, [name]); | ||
return { | ||
...state, | ||
invalid: state.validationMessage !== null, | ||
setValue(value) { | ||
store.dispatch(updateField(name, { | ||
value, | ||
dirty: value !== options.value, | ||
validationMessage: options.validate(value), | ||
})); | ||
}, | ||
setTouched() { | ||
store.dispatch(setTouched(name)); | ||
}, | ||
}; | ||
}; | ||
//# sourceMappingURL=useField.js.map | ||
//# sourceMappingURL=index.js.map | ||
exports.Form = Form; | ||
exports.useForm = useForm; | ||
exports.useFormField = useFormField; | ||
exports.useField = useField; |
@@ -6,163 +6,202 @@ /** | ||
*/ | ||
import React, { useMemo, useContext, useState, useRef, useEffect } from 'react'; | ||
import React, { useMemo, useState, useEffect, useContext, useRef } from 'react'; | ||
var FormContext = React.createContext({ | ||
register: function () { | ||
throw new Error('Forgot to wrap form element in <Form /> component?'); | ||
}, | ||
subscribe: function () { | ||
throw new Error('Forgot to wrap form element in <Form /> component?'); | ||
}, | ||
notify: function () { | ||
throw new Error('Forgot to wrap form element in <Form /> component?'); | ||
}, | ||
const FormContext = React.createContext({ | ||
getState: () => ({}), | ||
dispatch: () => { }, | ||
subscribe: () => () => { }, | ||
}); | ||
//# sourceMappingURL=context.js.map | ||
var Form = function (_a) { | ||
var autocomplete = _a.autocomplete, children = _a.children, className = _a.className, _b = _a.noValidate, noValidate = _b === void 0 ? true : _b, onSubmit = _a.onSubmit; | ||
// Create mutatable state | ||
var state = useMemo(function () { return new Map(); }, []); | ||
var listeners = useMemo(function () { return []; }, []); | ||
var context = { | ||
register: function (_a) { | ||
var name = _a.name, stateRef = _a.stateRef, validate = _a.validate; | ||
state.set(name, [stateRef, validate]); | ||
return function () { | ||
state.delete(name); | ||
const initialState = {}; | ||
const formReducer = (state = initialState, action) => { | ||
switch (action.type) { | ||
case 'INIT': | ||
return { | ||
...state, | ||
[action.payload.name]: { | ||
name: action.payload.name, | ||
dirty: false, | ||
touched: false, | ||
validationMessage: action.payload.state.validationMessage, | ||
value: action.payload.state.value, | ||
}, | ||
}; | ||
}, | ||
subscribe: function (listener) { | ||
listeners.push(listener); | ||
return function () { | ||
var index = listeners.indexOf(listener); | ||
listeners.splice(index, 1); | ||
case 'UPDATE_FIELD': | ||
return { | ||
...state, | ||
[action.payload.name]: { | ||
...state[action.payload.name], | ||
...action.payload.state, | ||
}, | ||
}; | ||
}, | ||
notify: function (name, state) { | ||
listeners.forEach(function (listener) { return listener(name, state); }); | ||
}, | ||
case 'SET_TOUCHED': | ||
return { | ||
...state, | ||
[action.payload.name]: { | ||
...state[action.payload.name], | ||
touched: true, | ||
}, | ||
}; | ||
case 'REMOVE_FIELD': | ||
const nextState = { ...state }; | ||
delete nextState[action.payload]; | ||
return nextState; | ||
default: | ||
return state; | ||
} | ||
}; | ||
//# sourceMappingURL=reducer.js.map | ||
const createStore = () => { | ||
let currentState = {}; | ||
const listeners = []; | ||
const getState = () => currentState; | ||
const subscribe = (listener) => { | ||
listeners.push(listener); | ||
return () => { | ||
const index = listeners.indexOf(listener); | ||
listeners.splice(index, 1); | ||
}; | ||
}; | ||
var submit = function (event) { | ||
var isValid = true; | ||
event.preventDefault(); | ||
state.forEach(function (_a) { | ||
var validate = _a[1]; | ||
if (!validate()) { | ||
isValid = false; | ||
} | ||
}); | ||
if (!isValid) { | ||
console.log('Form invalid'); | ||
return; | ||
} | ||
var formData = {}; | ||
state.forEach(function (_a, name) { | ||
var stateRef = _a[0]; | ||
formData[name] = stateRef.current.value; | ||
}); | ||
onSubmit(formData); | ||
const dispatch = (action) => { | ||
currentState = formReducer(currentState, action); | ||
listeners.forEach(listener => listener()); | ||
}; | ||
return (React.createElement(FormContext.Provider, { value: context }, | ||
React.createElement("form", { className: className, onSubmit: submit, autoComplete: autocomplete ? 'on' : 'off', noValidate: noValidate }, children))); | ||
return { | ||
getState, | ||
subscribe, | ||
dispatch, | ||
}; | ||
}; | ||
//# sourceMappingURL=createStore.js.map | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
***************************************************************************** */ | ||
var __assign = function() { | ||
__assign = Object.assign || function __assign(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
const getFormData = (state) => { | ||
const values = Object.values(state).map(({ name, value }) => ({ name, value })); | ||
const formData = {}; | ||
values.forEach(({ name, value }) => { | ||
formData[name] = value; | ||
}); | ||
return formData; | ||
}; | ||
const isFormInvalid = (state) => Object.values(state).some(({ validationMessage }) => validationMessage !== null); | ||
//# sourceMappingURL=selectors.js.map | ||
var createInitialState = function (value) { return ({ | ||
dirty: false, | ||
invalid: false, | ||
touched: false, | ||
validationMessage: '', | ||
value: value, | ||
}); }; | ||
var useForm = function (_a) { | ||
var name = _a.name, value = _a.value, validate = _a.validate; | ||
var form = useContext(FormContext); | ||
var _b = useState(createInitialState(value)), state = _b[0], setState = _b[1]; | ||
var stateRef = useRef(state); | ||
stateRef.current = state; | ||
useEffect(function () { | ||
return form.register({ | ||
name: name, | ||
stateRef: stateRef, | ||
validate: function () { | ||
var state = stateRef.current; | ||
var validity = validator(state.value, true); | ||
var nextState = __assign({}, state, { touched: true, invalid: !!validity, validationMessage: validity || '' }); | ||
setState(nextState); | ||
form.notify(name, nextState); | ||
return !validity; | ||
}, | ||
}); | ||
}, [name]); | ||
useEffect(function () { | ||
setValue(value); | ||
}, [JSON.stringify(value)]); | ||
function validator(value, force) { | ||
if (force === void 0) { force = false; } | ||
if (!validate) { | ||
return null; | ||
const Form = ({ render, className, autocomplete, onSubmit, noValidate = true }) => { | ||
const store = useMemo(() => createStore(), []); | ||
const [submitting, setSubmitting] = useState(false); | ||
const [invalid, setInvalid] = useState(false); | ||
// Listen to store updates to set validity | ||
useEffect(() => { | ||
const listener = () => { | ||
const state = store.getState(); | ||
const isInvalid = isFormInvalid(state); | ||
setInvalid(isInvalid); | ||
}; | ||
listener(); | ||
return store.subscribe(() => listener()); | ||
}, []); | ||
function submit(event) { | ||
event.preventDefault(); | ||
const state = store.getState(); | ||
const formData = getFormData(state); | ||
const isInvalid = isFormInvalid(state); | ||
if (isInvalid) { | ||
setInvalid(true); | ||
return; | ||
} | ||
if (force) { | ||
return validate ? validate(value) : null; | ||
setSubmitting(true); | ||
const result = onSubmit(formData); | ||
// TODO Use await and ship without polyfill? | ||
if (result && result.then) { | ||
result.then(() => setSubmitting(false)); | ||
} | ||
return (state.touched || state.dirty) && validate ? validate(value) : null; | ||
else { | ||
setSubmitting(false); | ||
} | ||
} | ||
function setValue(value) { | ||
var validity = validator(value); | ||
var nextState = __assign({}, state, { value: value, invalid: !!validity, validationMessage: validity || '', dirty: value !== state.value }); | ||
setState(nextState); | ||
// Emit change | ||
form.notify(name, nextState); | ||
return (React.createElement(FormContext.Provider, { value: store }, | ||
React.createElement("form", { className: className, onSubmit: submit, autoComplete: autocomplete ? 'on' : 'off', noValidate: noValidate }, render({ submitting, invalid })))); | ||
}; | ||
//# sourceMappingURL=Form.js.map | ||
const createActionWithPayload = (type, payload) => ({ | ||
type, | ||
payload, | ||
}); | ||
// Initialize field state | ||
const initField = (name, state) => createActionWithPayload('INIT', { name, state }); | ||
// Update field state | ||
const updateField = (name, state) => createActionWithPayload('UPDATE_FIELD', { name, state }); | ||
// Field is touched | ||
const setTouched = (name) => createActionWithPayload('SET_TOUCHED', { name }); | ||
// Remove field state | ||
const removeField = (name) => createActionWithPayload('REMOVE_FIELD', name); | ||
//# sourceMappingURL=actions.js.map | ||
const stateDidChange = (state, prevState) => { | ||
if (state.value !== prevState.value || | ||
state.dirty !== prevState.dirty || | ||
state.touched !== prevState.touched || | ||
state.validationMessage !== prevState.validationMessage) { | ||
return true; | ||
} | ||
function setTouched() { | ||
setState(__assign({}, state, { touched: true })); | ||
} | ||
return __assign({}, state, { setValue: setValue, setTouched: setTouched }); | ||
return false; | ||
}; | ||
var useFormField = function (target) { | ||
var context = useContext(FormContext); | ||
var _a = useState({ | ||
const useField = (name, options) => { | ||
const store = useContext(FormContext); | ||
const [state, setState] = useState({ | ||
name, | ||
dirty: false, | ||
invalid: false, | ||
touched: false, | ||
validationMessage: '', | ||
value: null, | ||
}), state = _a[0], setState = _a[1]; | ||
useEffect(function () { | ||
return context.subscribe(function (name, state) { | ||
if (name !== target) { | ||
return; | ||
validationMessage: null, | ||
value: options.value, | ||
}); | ||
const currentState = useRef(state); | ||
// Update value whenever value (given by props) changes | ||
useEffect(() => { | ||
store.dispatch(updateField(name, { | ||
value: options.value, | ||
dirty: false, | ||
validationMessage: options.validate(options.value), | ||
})); | ||
}, [options.value]); | ||
useEffect(() => { | ||
// Update local state | ||
const unsubscribe = store.subscribe(() => { | ||
const prevState = currentState.current; | ||
const nextState = store.getState()[name]; | ||
if (prevState && nextState && stateDidChange(nextState, prevState)) { | ||
setState(nextState); | ||
currentState.current = nextState; | ||
} | ||
setState(state); | ||
}); | ||
}, []); | ||
return state; | ||
store.dispatch(initField(name, { | ||
value: options.value, | ||
validationMessage: options.validate(options.value), | ||
})); | ||
return () => { | ||
unsubscribe(); | ||
store.dispatch(removeField(name)); | ||
}; | ||
}, [name]); | ||
return { | ||
...state, | ||
invalid: state.validationMessage !== null, | ||
setValue(value) { | ||
store.dispatch(updateField(name, { | ||
value, | ||
dirty: value !== options.value, | ||
validationMessage: options.validate(value), | ||
})); | ||
}, | ||
setTouched() { | ||
store.dispatch(setTouched(name)); | ||
}, | ||
}; | ||
}; | ||
//# sourceMappingURL=useField.js.map | ||
export { Form, useForm, useFormField }; | ||
//# sourceMappingURL=index.js.map | ||
export { Form, useField }; |
import React from 'react'; | ||
import { FormContext as Context } from 'types'; | ||
export declare const FormContext: React.Context<Context>; | ||
export declare const FormContext: React.Context<{ | ||
getState: () => import("./store/reducer").FormReducer; | ||
subscribe: (listener: () => void) => () => void; | ||
dispatch: (action: import("./store/actions").ActionWithPayload<"INIT", { | ||
name: string; | ||
state: Pick<import("./types").FieldState<unknown>, "value" | "validationMessage">; | ||
}> | import("./store/actions").ActionWithPayload<"UPDATE_FIELD", { | ||
name: string; | ||
state: Partial<import("./types").FieldState<unknown>>; | ||
}> | import("./store/actions").ActionWithPayload<"SET_TOUCHED", { | ||
name: string; | ||
}> | import("./store/actions").ActionWithPayload<"REMOVE_FIELD", string>) => void; | ||
}>; |
import React from 'react'; | ||
import { FormData } from 'types'; | ||
interface Props { | ||
autocomplete?: boolean; | ||
children: React.ReactNode; | ||
className?: string; | ||
noValidate?: boolean; | ||
onSubmit: (formData: FormData) => void; | ||
} | ||
declare const Form: ({ autocomplete, children, className, noValidate, onSubmit, }: Props) => JSX.Element; | ||
import { FormData } from './types'; | ||
declare type Props = { | ||
onSubmit: (formData: FormData) => void | Promise<void>; | ||
className?: string; | ||
autocomplete?: boolean; | ||
noValidate?: boolean; | ||
render: (form: { invalid: boolean; submitting: boolean }) => React.ReactNode; | ||
}; | ||
declare const Form: ({ | ||
render, | ||
className, | ||
autocomplete, | ||
onSubmit, | ||
noValidate, | ||
}: Props) => JSX.Element; | ||
export default Form; |
@@ -1,4 +0,3 @@ | ||
export { BaseFieldProps, FormData } from './types'; | ||
export { default as Form } from './Form'; | ||
export { useForm } from './hooks/useForm'; | ||
export { useFormField } from './hooks/useFormField'; | ||
export { useField } from './useField'; | ||
export { FieldProps } from './types'; |
@@ -1,27 +0,14 @@ | ||
import { MutableRefObject } from 'react'; | ||
export declare type FormFieldListener = (name: string, state: FormFieldState) => void; | ||
export interface BaseFieldProps<V> { | ||
name: string; | ||
value?: V; | ||
} | ||
interface Register { | ||
name: string; | ||
stateRef: MutableRefObject<any>; | ||
validate: () => boolean; | ||
} | ||
export interface FormContext { | ||
register: (config: Register) => () => void; | ||
subscribe: (listener: FormFieldListener) => void; | ||
notify: (name: string, state: FormFieldState) => void; | ||
} | ||
export interface FormFieldState { | ||
dirty: boolean; | ||
invalid: boolean; | ||
touched: boolean; | ||
validationMessage: string; | ||
value: any; | ||
} | ||
export interface FormData { | ||
[key: string]: any; | ||
} | ||
export {}; | ||
export declare type FieldState<V> = { | ||
name: string; | ||
dirty: boolean; | ||
touched: boolean; | ||
validationMessage: null | string; | ||
value: V; | ||
}; | ||
export declare type FieldProps<V> = { | ||
name: string; | ||
value?: V; | ||
}; | ||
export declare type FormData = { | ||
[key: string]: unknown; | ||
}; |
{ | ||
"name": "@blockle/form", | ||
"version": "0.0.1-alpha.5", | ||
"version": "0.0.1-alpha.6", | ||
"description": "Blockle Form", | ||
@@ -30,6 +30,14 @@ "scripts": { | ||
"devDependencies": { | ||
"@testing-library/jest-dom": "^4.1.2", | ||
"@testing-library/react": "^9.3.0", | ||
"@types/jest": "^24.0.11", | ||
"@types/react": "^16.7.18", | ||
"@types/react-dom": "^16.0.11", | ||
"eslint-plugin-prettier": "^3.1.0", | ||
"@typescript-eslint/eslint-plugin": "^2.4.0", | ||
"@typescript-eslint/parser": "^2.4.0", | ||
"eslint": "^6.5.1", | ||
"eslint-config-prettier": "^6.4.0", | ||
"eslint-plugin-prettier": "^3.1.1", | ||
"eslint-plugin-react": "^7.16.0", | ||
"eslint-plugin-react-hooks": "^2.1.2", | ||
"husky": "^3.0.4", | ||
@@ -42,5 +50,4 @@ "jest": "^24.5.0", | ||
"pretty-quick": "^1.11.1", | ||
"react": "^16.7.0", | ||
"react-dom": "^16.7.0", | ||
"react-testing-library": "^6.0.0", | ||
"react": "^16.10.2", | ||
"react-dom": "^16.10.2", | ||
"rollup": "^1.0.0", | ||
@@ -47,0 +54,0 @@ "rollup-plugin-commonjs": "^9.2.0", |
@@ -13,3 +13,3 @@ # @blockle/form | ||
import React, { useState } from 'react'; | ||
import Form, { BaseFieldProps } from '@blockle/form'; | ||
import { Form, useField, FieldProps } from '@blockle/form'; | ||
@@ -21,7 +21,4 @@ interface FormData { | ||
const MyForm = () => { | ||
const [sending, setSending] = useState(false); | ||
const [errorMessage, setErrorMessage] = useState(null); | ||
const submit = async (formData: FormData) => { | ||
setSending(true); | ||
try { | ||
@@ -32,17 +29,21 @@ await xhr.send(formData); | ||
} | ||
setSending(false); | ||
} | ||
return ( | ||
<Form onSubmit={submit}> | ||
<Input name="name" type="text" required /> | ||
<ValidationWarning target="name" /> | ||
<Form | ||
onSubmit={submit} | ||
render={({ invalid, submitting }) => ( | ||
<Input name="name" type="text" required /> | ||
<button disabled={sending}>Submit</button> | ||
</Form> | ||
{errorMessage | ||
&& <div>{errorMessage}</div>} | ||
<button disabled={invalid || submitting}>Submit</button> | ||
)} | ||
/> | ||
); | ||
} | ||
interface InputProps extends BaseFieldProps<string> { | ||
// Input.tsx | ||
interface InputProps extends FieldProps<string> { | ||
type: 'text' | 'password'; | ||
@@ -52,6 +53,4 @@ required: boolean; | ||
// -- | ||
const Input = ({ name, value, type, required }: InputProps) => { | ||
const {value, touched, dirty, valid, validationMessage, setValue} = useForm({ | ||
name, | ||
const field = useField<string>(name, { | ||
value, | ||
@@ -64,34 +63,14 @@ validate(value) { | ||
return null; | ||
}, | ||
computeValue(value) { | ||
return value; | ||
} | ||
}); | ||
// Update value | ||
return (<input type={type} value={value} onInput={(event) => setValue(event.currentTarget.value)} />); | ||
return ( | ||
<input | ||
type={type} | ||
value={field.value} | ||
onChange={(event) => field.setValue(event.currentTarget.value)} | ||
onFocus={field.setTouched} | ||
/> | ||
); | ||
} | ||
interface ValidationWarningProps { | ||
target: string; | ||
} | ||
// -- | ||
const ValidationWarning = ({ target }: ValidationWarningProps) => { | ||
const { value, touched, dirty, valid, validationMessage } = useFormField(target); | ||
if(valid) { | ||
return null; | ||
} | ||
return <div>{validationMessage}</div> | ||
} | ||
``` | ||
```ts | ||
interface BaseFieldProps<V> { | ||
value: V; | ||
name: string; | ||
} | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
23720
18
557
28
72