UNPKG

svelte-formup

Version:
381 lines (372 loc) 14.4 kB
'use strict'; 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