You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.0 KiB
TypeScript
170 lines
4.0 KiB
TypeScript
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,
|
|
invalidate: invalidate,
|
|
listen,
|
|
}: {
|
|
value?: () => any;
|
|
invalidate?: (v: any) => string | boolean;
|
|
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
|
|
) {
|
|
if (invalidate) {
|
|
const invalidateResult = invalidate(nextValue);
|
|
if (invalidateResult) {
|
|
el.setCustomValidity(
|
|
invalidateResult === true
|
|
? "Input value is invalid."
|
|
: invalidateResult
|
|
);
|
|
} else {
|
|
el.setCustomValidity("");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|