feat: implement form validation hook
parent
3abc6a2779
commit
e11c8449b9
@ -0,0 +1,154 @@
|
||||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createSignal,
|
||||
FlowComponent,
|
||||
onCleanup,
|
||||
onMount,
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
|
||||
const makeStore = () =>
|
||||
createStore({
|
||||
valid: true as boolean,
|
||||
values: {} as Record<string, any>,
|
||||
errors: {} as Record<string, ValidityState>,
|
||||
});
|
||||
type Store = ReturnType<typeof makeStore>;
|
||||
const ValidationContext = createContext<Store>();
|
||||
|
||||
export const createValidation = function () {
|
||||
const store = makeStore();
|
||||
const isValid = createEffect(() => {
|
||||
store[1]("valid", Object.keys(store[0].errors).length === 0);
|
||||
});
|
||||
|
||||
const ContextProvider: FlowComponent = (props) => (
|
||||
<ValidationContext.Provider value={store}>
|
||||
{props.children}
|
||||
</ValidationContext.Provider>
|
||||
);
|
||||
const result = [ContextProvider, store[0]] as [
|
||||
typeof ContextProvider,
|
||||
typeof store[0]
|
||||
];
|
||||
return result;
|
||||
};
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
|
||||
export const validateInput = function ({
|
||||
value: getValue,
|
||||
listen,
|
||||
}: {
|
||||
value?: () => any;
|
||||
listen?: {
|
||||
input?: boolean;
|
||||
blur?: boolean;
|
||||
change?: boolean;
|
||||
observer?: boolean;
|
||||
};
|
||||
} = {}) {
|
||||
// TODO: Default listen values could be set from the ValidationContext
|
||||
listen = listen || { input: true, blur: true, change: false, observer: true };
|
||||
const store = useContext(ValidationContext);
|
||||
const [validity, setValidity] = createSignal<ValidityState>(
|
||||
{ valid: true } as any,
|
||||
{ equals: false }
|
||||
);
|
||||
|
||||
let name = "";
|
||||
let value: any = null;
|
||||
let init = true;
|
||||
let lastBadInput = false;
|
||||
let isCheckbox = false;
|
||||
let el: HTMLInputElement = undefined!;
|
||||
|
||||
const onChange = function () {
|
||||
if (!el) return;
|
||||
let nextValue = isCheckbox ? el.checked : el.value;
|
||||
const validity_ = el.validity || EMPTY_OBJECT;
|
||||
if (
|
||||
init ||
|
||||
nextValue !== value ||
|
||||
// Update state on badInput (value stays unchanged on badInput), but only update the state once
|
||||
validity_.badInput != lastBadInput
|
||||
) {
|
||||
name = name || el.name || el.id;
|
||||
setValidity(validity_);
|
||||
lastBadInput = validity_.badInput;
|
||||
|
||||
if (validity_.valid) {
|
||||
store && store[1]("errors", name, undefined as any);
|
||||
} else {
|
||||
store && store[1]("errors", name, validity_);
|
||||
}
|
||||
|
||||
value = nextValue;
|
||||
store && store[1]("values", name, value === undefined ? null : value);
|
||||
}
|
||||
|
||||
init = false;
|
||||
};
|
||||
|
||||
const directive = function (el_: HTMLInputElement) {
|
||||
el = el_;
|
||||
isCheckbox = el.type === "checkbox";
|
||||
|
||||
onCleanup(function () {
|
||||
if (name) {
|
||||
store && store[1]("errors", name, undefined as any);
|
||||
store && store[1]("values", name, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
if (getValue) {
|
||||
createEffect(function () {
|
||||
getValue();
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
|
||||
if (listen?.observer) {
|
||||
const observer = new MutationObserver(onChange);
|
||||
|
||||
onMount(function () {
|
||||
observer.observe(el, { attributes: true });
|
||||
});
|
||||
|
||||
onCleanup(function () {
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
if (listen?.input) {
|
||||
el.addEventListener("input", function () {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
if (listen?.change) {
|
||||
el.addEventListener("change", function () {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
if (listen?.blur) {
|
||||
el.addEventListener("blur", function () {
|
||||
requestAnimationFrame(onChange);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return [directive, validity, onChange] as [
|
||||
typeof directive,
|
||||
typeof validity,
|
||||
typeof onChange
|
||||
];
|
||||
};
|
||||
|
||||
declare module "solid-js" {
|
||||
namespace JSX {
|
||||
interface Directives {
|
||||
validate: boolean;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue