feat: implement form validation hook

master
Katja Lutz 2 years ago
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…
Cancel
Save