svelte-formup
Version:
form helpers for svelte
381 lines (372 loc) • 14.4 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
const svelte = require('svelte');
const store = require('svelte/store');
const internal = require('svelte/internal');
const isString = (value) => typeof value === "string";
const isHTMLFormElement = (node) => node.tagName === "FORM";
const runAll = (callbacks) => {
callbacks && internal.run_all(callbacks.filter(Boolean));
};
const asArray = (value) => {
if (Array.isArray(value))
return value;
if (value)
return [value];
return [];
};
const validatePath = (path) => isString(path) && path;
const findSchemaPathById = (id) => id == null ? void 0 : id.split(/\s+/g).map((id2) => findSchemaPathForElement(document.getElementById(id2))).find(internal.identity);
const findSchemaPathForElement = (node) => {
var _a;
return node && !isHTMLFormElement(node) && (validatePath((_a = node.dataset) == null ? void 0 : _a.pathAt) || validatePath(node.name) || findSchemaPathById(node.htmlFor) || validatePath(node.id));
};
const withPathOf = (node, callback, path = findSchemaPathForElement(node)) => {
if (path)
return callback(path);
};
const listenOn = (node, events, listener) => asArray(events).map((event) => internal.listen(node, event, listener));
const VALIDATE_EVENTS = new WeakSet();
const DIRTY_EVENTS = new WeakSet();
const dedupeEvents = (events, listener) => (event) => {
if (!events.has(event)) {
events.add(event);
listener(event);
}
};
const listenWith = (callback) => (event) => withPathOf(event.target, callback);
function validate(context, node, options) {
let dispose;
const destroy = () => runAll(dispose);
const update = (options2 = {}) => {
destroy();
if (isString(options2))
options2 = {at: options2};
const {
at: path = findSchemaPathForElement(node),
debounce = context.debounce,
validateOn = options2.on || context.validateOn,
dirtyOn = options2.on || options2.validateOn || context.dirtyOn
} = options2;
const validateAt = (path2) => context.validateAt(path2, {debounce});
const dirtyAt = (path2) => context.setDirtyAt(path2);
if (path) {
dispose = asArray(path).flatMap((path2) => [
...listenOn(node, validateOn, dedupeEvents(VALIDATE_EVENTS, () => validateAt(path2))),
...listenOn(node, dirtyOn, dedupeEvents(DIRTY_EVENTS, () => dirtyAt(path2)))
]);
} else {
if (isHTMLFormElement(node)) {
node.noValidate = true;
node.autocomplete = context.autocomplete;
if (!node.role)
node.role = "form";
dispose = [
internal.listen(node, "submit", internal.prevent_default(context.submit)),
internal.listen(node, "reset", internal.prevent_default(context.reset))
];
}
dispose = [
...listenOn(node, validateOn, dedupeEvents(VALIDATE_EVENTS, listenWith(validateAt))),
...listenOn(node, dirtyOn, dedupeEvents(DIRTY_EVENTS, listenWith(dirtyAt)))
];
}
dispose.push(context.validity(node, options2).destroy);
};
update(options);
return {update, destroy};
}
const updateToggle = (node, classes, store, toggle, path) => withPathOf(node, (path2) => toggle(node, classes, store, path2), path);
const subscribeTo = (path, node, classes, store, toggle) => internal.subscribe(store, (store2) => updateToggle(node, classes, store2, toggle, path));
const subscribeToElements = (node, classes, store, toggle, combine) => internal.subscribe(store, (map) => combine(node, classes, internal.query_selector_all("input,select,textarea,[contenteditable],output,object,button", node).map((element) => updateToggle(element, classes, map, toggle))));
const isFormField = (node) => isHTMLFormElement(node) || "setCustomValidity" in node;
const toogleClass = (node, classes, state, key) => {
const className = `${isFormField(node) ? "is" : "has"}-${key}`;
internal.toggle_class(node, classes[className] || className, state);
return state;
};
const setCustomValidity = (node, error) => {
var _a;
(_a = node.setCustomValidity) == null ? void 0 : _a.call(node, (error == null ? void 0 : error.message) || "");
return error;
};
const updateDirty = (node, classes, dirty) => {
toogleClass(node, classes, !dirty, "pristine");
return toogleClass(node, classes, dirty, "dirty");
};
const updateSuccess = (node, classes, success) => toogleClass(node, classes, success, "success");
const updateError = (node, classes, error) => toogleClass(node, classes, error, "error");
const updateValidating = (node, classes, validating) => toogleClass(node, classes, validating, "validating");
const updateValidity = (node, classes, error) => updateError(node, classes, setCustomValidity(node, error));
const updateStoreDirty = (node, classes, store, path) => updateDirty(node, classes, store.has(path));
const updateStoreValidity = (node, classes, store, path) => updateValidity(node, classes, store.get(path));
const updateStoreSuccess = (node, classes, store, path) => updateSuccess(node, classes, store.has(path));
const updateStoreValidating = (node, classes, store, path) => updateValidating(node, classes, store.has(path));
const useFirstTo = (update) => (node, classes, results) => update(node, classes, results.find(internal.identity));
const useEveryTo = (update) => (node, classes, results) => update(node, classes, results.every(internal.identity));
function validity(context, node, options) {
let dispose;
const destroy = () => runAll(dispose);
const update = (options2 = {}) => {
destroy();
if (isString(options2))
options2 = {at: options2};
const {at: path = findSchemaPathForElement(node)} = options2;
const classes = {...context.classes, ...options2.classes};
if (path) {
dispose = asArray(path).flatMap((path2) => [
subscribeTo(path2, node, classes, context.dirty, updateStoreDirty),
subscribeTo(path2, node, classes, context.invalid, updateStoreValidity),
subscribeTo(path2, node, classes, context.valid, updateStoreSuccess),
subscribeTo(path2, node, classes, context.validating, updateStoreValidating)
]);
} else if (isHTMLFormElement(node)) {
dispose = [
internal.subscribe(context.isDirty, (dirty) => updateDirty(node, classes, dirty)),
internal.subscribe(context.isError, (error) => updateError(node, classes, error)),
internal.subscribe(context.isValidating, (validating) => updateValidating(node, classes, validating)),
internal.subscribe(context.isSubmitting, (submitting) => toogleClass(node, classes, submitting, "submitting")),
internal.subscribe(context.isSubmitted, (submitted) => toogleClass(node, classes, submitted, "submitted"))
];
} else {
dispose = [
subscribeToElements(node, classes, context.dirty, updateStoreDirty, useFirstTo(updateDirty)),
subscribeToElements(node, classes, context.invalid, updateStoreValidity, useFirstTo(updateValidity)),
subscribeToElements(node, classes, context.valid, updateStoreSuccess, useEveryTo(updateSuccess)),
subscribeToElements(node, classes, context.validating, updateStoreValidating, useFirstTo(updateValidating))
];
}
};
update(options);
return {update, destroy};
}
const isNode = typeof process !== "undefined" && (process == null ? void 0 : process.title) !== "browser";
const CONTEXT_KEY = Symbol.for("svelte-formup");
const setAt = (store2, path, value) => store2.update((set) => {
set[value ? "add" : "delete"](path);
return set;
});
const getFormupContext = () => svelte.getContext(CONTEXT_KEY);
const isEmpty = ({size}) => size === 0;
const negate = (value) => !value;
const formup = ({
schema,
onSubmit = internal.noop,
onReset = internal.noop,
getInitialValues = internal.blank_object,
validateInitialValues = false,
state = internal.blank_object(),
validateOn = "change",
dirtyOn = validateOn,
debounce = isNode ? 0 : 100,
classes = {},
autocomplete = "off"
}) => {
const values = store.writable(getInitialValues());
const errors = store.writable(new Map());
const dirty = store.writable(new Set());
const validating = store.writable(new Set());
const valid = store.derived([dirty, validating, errors], ([$dirty, $validating, $errors]) => {
const $valid = new Set();
for (const field of $dirty) {
if (!$errors.has(field) && !$validating.has(field)) {
$valid.add(field);
}
}
return $valid;
});
const invalid = store.derived([dirty, validating, errors], ([$dirty, $validating, $errors]) => {
const $invalid = new Map();
for (const [field, error] of $errors.entries()) {
if ($dirty.has(field) && !$validating.has(field)) {
$invalid.set(field, error);
}
}
return $invalid;
});
const isSubmitting = store.writable(false);
const isValidating = store.writable(false);
const isSubmitted = store.writable(false);
const isPristine = store.derived(dirty, isEmpty);
const isDirty = store.derived(isPristine, negate);
const isError = store.derived([isDirty, isValidating, validating, errors], ([$isDirty, $isValidating, $validating, $errors]) => $isDirty && !$isValidating && $validating.size === 0 && $errors.size > 0);
const submitCount = store.writable(0);
const setErrorAt = (path, error) => errors.update((errors2) => {
if (error) {
errors2.set(path, error);
} else {
errors2.delete(path);
}
return errors2;
});
const setFormError = (error) => setErrorAt("", error);
const setDirtyAt = (path, isDirty2 = true) => setAt(dirty, path, isDirty2);
const setValidatingAt = (path, isValidating2 = true) => setAt(validating, path, isValidating2);
const context = {
schema,
values,
state: store.writable(state),
formError: store.derived(errors, (errors2) => errors2.get("")),
errors,
dirty,
validating,
invalid,
valid,
isValidating,
isSubmitting,
isSubmitted,
submitCount,
isPristine,
isDirty,
isError,
async submit(event) {
if (store.get(isSubmitting))
return;
isSubmitted.set(false);
isSubmitting.set(true);
submitCount.update((count) => count + 1);
abortActiveValidateAt();
try {
const data = await validate2();
if (data) {
const result = await onSubmit(data, context, event);
submitCount.set(0);
isSubmitted.set(true);
return result;
}
} catch (error) {
setFormError(error);
} finally {
isSubmitting.set(false);
}
},
reset(event) {
if (store.get(isSubmitting))
return;
values.set(getInitialValues(event));
abortActiveValidateAt();
dirty.set(new Set());
errors.set(new Map());
isSubmitted.set(false);
submitCount.set(0);
if (validateInitialValues)
void validate2(event);
onReset(context, event);
},
setFormError,
setErrorAt,
setDirtyAt,
setValidatingAt,
validateAt,
validate: (node, options) => validate(context, node, options),
validity: (node, options) => validity(context, node, options),
validateOn: asArray(validateOn),
dirtyOn: asArray(dirtyOn),
debounce,
classes,
autocomplete
};
svelte.setContext(CONTEXT_KEY, context);
let currentValues;
internal.subscribe(values, (values2) => {
currentValues = values2;
});
const validateAtStates = new Map();
let validateAbortController;
const createValidateContext = (controller, event) => ({
formup: context,
signal: controller.signal,
event
});
if (validateInitialValues)
void validate2();
return context;
async function validate2(event) {
validateAbortController == null ? void 0 : validateAbortController.abort();
validateAbortController = new AbortController();
const currentController = validateAbortController;
isValidating.set(true);
try {
const data = await schema.validate(currentValues, {
abortEarly: false,
strict: false,
context: createValidateContext(validateAbortController, event)
});
if (currentController === validateAbortController) {
errors.set(new Map());
return data;
}
} catch (error) {
if (currentController === validateAbortController) {
const newErrors = new Map();
dirty.update((dirty2) => {
var _a;
[].concat(((_a = error.inner) == null ? void 0 : _a.length) ? error.inner : error).forEach((error2) => {
if (error2.path) {
newErrors.set(error2.path, error2);
dirty2.add(error2.path);
} else {
setFormError(error2);
}
});
return dirty2;
});
errors.set(newErrors);
}
} finally {
if (currentController === validateAbortController) {
isValidating.set(false);
}
}
}
function abortActiveValidateAt() {
validateAtStates.forEach((state2) => {
clearTimeout(state2.t);
if (state2.c)
state2.c.abort();
state2.l = 0;
state2.c = void 0;
state2.t = void 0;
});
validating.set(new Set());
}
function validateAt(path, {debounce: debounce2 = context.debounce} = {}) {
var _a;
if (store.get(isSubmitting))
return;
let state2 = validateAtStates.get(path);
if (!state2) {
validateAtStates.set(path, state2 = {l: 0});
}
(_a = state2.c) == null ? void 0 : _a.abort();
clearTimeout(state2.t);
state2.t = setTimeout(doValidateAt, debounce2, path, state2, debounce2);
}
async function doValidateAt(path, state2, debounce2) {
if (Date.now() - state2.l < debounce2) {
return validateAt(path, {debounce: debounce2});
}
const current = new AbortController();
state2.c = current;
state2.l = Date.now();
setValidatingAt(path);
let foundError;
try {
await schema.validateAt(path, currentValues, {
abortEarly: true,
strict: true,
context: createValidateContext(current)
});
} catch (error) {
foundError = error;
}
if (state2.c === current) {
state2.c = void 0;
setValidatingAt(path, false);
setErrorAt(path, foundError);
}
}
};
exports.formup = formup;
exports.getFormupContext = getFormupContext;
//# sourceMappingURL=svelte-formup.js.map