Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@domonda/form

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@domonda/form - npm Package Compare versions

Comparing version 1.2.2 to 2.0.0

21

CHANGELOG.md

@@ -6,2 +6,23 @@ # Change Log

# [2.0.0](https://github.com/domonda/domonda-js/compare/@domonda/form@1.2.2...@domonda/form@2.0.0) (2019-08-01)
### Bug Fixes
* **Form:** plumb might be disposed before submit finishes ([e8e06d9](https://github.com/domonda/domonda-js/commit/e8e06d9))
### Features
* **plumb:** replace RxJS with plumb ([d37439b](https://github.com/domonda/domonda-js/commit/d37439b))
### BREAKING CHANGES
* **plumb:** RxJS is not used for the form anymore. We use domonda-plumb instead.
## [1.2.2](https://github.com/domonda/domonda-js/compare/@domonda/form@1.2.1...@domonda/form@1.2.2) (2019-07-31)

@@ -8,0 +29,0 @@

5

createForm.d.ts

@@ -6,4 +6,3 @@ /**

*/
import { FormDefaultValues, FormConfig, Form, FormDestroy, FormFields } from './Form';
export declare function setChangedOnAllFormFields(fields: FormFields, changed: boolean): FormFields;
export declare function createForm<DefaultValues extends FormDefaultValues>(defaultValues?: DefaultValues, initialConfig?: FormConfig<DefaultValues>): [Form<DefaultValues>, FormDestroy];
import { FormDefaultValues, FormConfig, Form, FormDispose } from './Form';
export declare function createForm<DefaultValues extends FormDefaultValues>(defaultValues?: DefaultValues, initialConfig?: FormConfig<DefaultValues>): [Form<DefaultValues>, FormDispose];

95

createForm.js

@@ -16,6 +16,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const equality_1 = require("./equality");
// $
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const plumb_1 = require("@domonda/plumb");
// form

@@ -25,14 +22,6 @@ const Form_1 = require("./Form");

const DEFAULT_AUTO_SUBMIT_DELAY = 300;
function setChangedOnAllFormFields(fields, changed) {
return Object.keys(fields).reduce((acc, curr) => {
return Object.assign({}, acc, {
// if the value under a path does not exist, the field definitely changed!
[curr]: Object.assign({}, fields[curr], { changed }) });
}, {});
}
exports.setChangedOnAllFormFields = setChangedOnAllFormFields;
function createForm(defaultValues = {}, initialConfig = {}) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const configRef = new Form_1.FormConfigRef(initialConfig, handleSubmit);
const $ = new rxjs_1.BehaviorSubject({
const plumb = plumb_1.createPlumb({
defaultValues,

@@ -47,8 +36,29 @@ values: defaultValues,

if (autoSubmit) {
const submit$ = autoSubmitDelay > 0
? $.pipe(operators_1.skip(1), operators_1.debounceTime(autoSubmitDelay), operators_1.distinctUntilChanged(({ values: prevValues }, { values: currValues }) => equality_1.equal(prevValues, currValues)), operators_1.filter(({ defaultValues, values }) => !equality_1.equal(defaultValues, values)))
: $.pipe(operators_1.skip(1), operators_1.distinctUntilChanged(({ values: prevValues }, { values: currValues }) => equality_1.equal(prevValues, currValues)), operators_1.filter(({ defaultValues, values }) => !equality_1.equal(defaultValues, values)));
// since functions are hoisted
// eslint-disable-next-line @typescript-eslint/no-use-before-define
submit$.subscribe(submit);
let currState = plumb.state;
let currTimeout;
plumb.subscribe((nextState) => {
(() => {
if (nextState.submitting) {
return;
}
if (plumb_1.equal(currState.values, nextState.values) ||
plumb_1.equal(nextState.defaultValues, nextState.values)) {
return;
}
if (autoSubmitDelay > 0) {
if (currTimeout) {
clearTimeout(currTimeout);
}
currTimeout = setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
submit();
}, autoSubmitDelay);
}
else {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
submit();
}
})();
currState = nextState;
});
}

@@ -58,8 +68,8 @@ }

const form = {
$,
plumb,
get state() {
return $.value;
return plumb.state;
},
get values() {
return $.value.values;
return plumb.state.values;
},

@@ -69,5 +79,9 @@ configRef,

submit,
reset: () => $.next(Object.assign({}, $.value, { values: $.value.defaultValues, submitting: false, submitError: null, fields: setChangedOnAllFormFields($.value.fields, false) })),
resetSubmitError: () => $.next(Object.assign({}, $.value, { submitting: false, submitError: null })),
makeFormField: (path, config) => createFormField_1.createFormField($, path, config),
reset: () => {
plumb.next(Object.assign({}, plumb.state, { values: plumb.state.defaultValues, submitting: false, submitError: null }));
},
resetSubmitError: () => {
plumb.next(Object.assign({}, plumb.state, { submitting: false, submitError: null }));
},
makeFormField: (path, config) => createFormField_1.createFormField(plumb, path, config),
};

@@ -81,6 +95,5 @@ function handleSubmit(event) {

}
$.next(Object.assign({}, $.value, { submitting: true, submitError: null }));
// TODO-db-190626 validate fields which haven't changed
// wait for all validations to finish before continuing
const validityMessages = yield $.pipe(operators_1.map(({ fields }) => Object.keys(fields).reduce((acc, key) => {
plumb.next(Object.assign({}, plumb.state, { submitting: true, submitError: null }));
const { fields } = plumb.state;
const validityMessages = Object.keys(fields).reduce((acc, key) => {
const field = fields[key];

@@ -91,9 +104,5 @@ if (!field) {

return [...acc, field.validityMessage];
}, [])), operators_1.takeWhile(
// complete the observable when all validations have finished loading
(validityMessages) => validityMessages.some((validityMessage) => validityMessage === undefined), true))
// covert to promise which resolves once the stream completes
.toPromise();
}, []);
if (validityMessages.some((validityMessages) => validityMessages != null)) {
$.next(Object.assign({}, $.value, { submitting: false }));
plumb.next(Object.assign({}, plumb.state, { submitting: false }));
return;

@@ -103,11 +112,11 @@ }

try {
yield onSubmit($.value.values, form);
$.next(Object.assign({}, $.value, { submitting: false, values: resetOnSuccessfulSubmit ? $.value.defaultValues : $.value.values, fields: resetOnSuccessfulSubmit
? setChangedOnAllFormFields($.value.fields, false)
: $.value.fields }));
yield onSubmit(plumb.state.values, form);
if (!plumb.disposed) {
plumb.next(Object.assign({}, plumb.state, { submitting: false, values: resetOnSuccessfulSubmit ? plumb.state.defaultValues : plumb.state.values }));
}
}
catch (error) {
$.next(Object.assign({}, $.value, { submitting: false, submitError: error, values: resetOnFailedSubmit ? $.value.defaultValues : $.value.values, fields: resetOnFailedSubmit
? setChangedOnAllFormFields($.value.fields, false)
: $.value.fields }));
if (!plumb.disposed) {
plumb.next(Object.assign({}, plumb.state, { submitting: false, submitError: error, values: resetOnFailedSubmit ? plumb.state.defaultValues : plumb.state.values }));
}
}

@@ -127,4 +136,4 @@ }

}
return [form, () => $.complete()];
return [form, () => plumb.dispose()];
}
exports.createForm = createForm;

@@ -6,5 +6,6 @@ /**

*/
import { FormDefaultValues, Form$ } from './Form';
import { FormField, FormFieldConfig, FormFieldDestroy } from './FormField';
export declare function createFormField<DefaultValues extends FormDefaultValues, Value>(form$: Form$<DefaultValues>, path: string, // [K in keyof FormDefaultValues]
config?: FormFieldConfig<Value>): [FormField<Value>, FormFieldDestroy];
import { Plumb } from '@domonda/plumb';
import { FormDefaultValues, FormState } from './Form';
import { FormField, FormFieldConfig, FormFieldDispose } from './FormField';
export declare function createFormField<DefaultValues extends FormDefaultValues, Value>(form: Plumb<FormState<DefaultValues>>, path: string, // [K in keyof FormDefaultValues]
config?: FormFieldConfig<Value>): [FormField<Value>, FormFieldDispose];

@@ -22,3 +22,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const equality_1 = require("./equality");
const plumb_1 = require("@domonda/plumb");
const get_1 = __importDefault(require("lodash/get"));

@@ -28,135 +28,82 @@ const setWith_1 = __importDefault(require("lodash/fp/setWith"));

const omit_1 = __importDefault(require("lodash/fp/omit"));
const pick_1 = __importDefault(require("lodash/fp/pick"));
const Subject_1 = require("rxjs/internal/Subject");
// $
const operators_1 = require("rxjs/operators");
function deriveState(path, state, setState, isLocalNext) {
function selector(path, state) {
const { fields, defaultValues, values } = state;
const defaultValue = get_1.default(defaultValues, path);
const value = get_1.default(values, path);
const field = fields[path];
if (!field) {
return undefined;
return {
defaultValue,
value,
changed: false,
validityMessage: null,
};
}
const defaultValue = get_1.default(defaultValues, path);
const value = get_1.default(values, path);
if (isLocalNext) {
return Object.assign({}, field, { defaultValue,
value });
}
// update the `changed` flag if it is not consistent with the field itself
// this case will happen when the value gets changed externally (from the form)
const changed = !equality_1.equal(defaultValue, value);
if (field.changed !== changed) {
setState(Object.assign({}, state, { fields: Object.assign({}, state.fields, { [path]: Object.assign({}, field, { changed }) }) }));
}
return Object.assign({}, field, { defaultValue,
value,
changed });
value });
}
function createFormField(form$, path, // [K in keyof FormDefaultValues]
function createFormField(form, path, // [K in keyof FormDefaultValues]
config = {}) {
const initialDefaultValue = get_1.default(form$.value.values, path);
const initialValue = get_1.default(form$.value.defaultValues, path);
form$.next(Object.assign({}, form$.value, { fields: Object.assign({}, form$.value.fields, { [path]: {
changed: !equality_1.equal(initialDefaultValue, initialValue),
validityMessage: null,
} }) }));
// a flag which prevents double checking the value equality
// when the field sends the next signal. if the form sends a new
// value then the check will be performed while deriving the state
let localNext = false;
const $ = new Subject_1.AnonymousSubject({
next: (field) => {
localNext = true;
form$.next(Object.assign({}, form$.value, { values: setWith_1.default(clone_1.default, path, field.value, form$.value.values), fields: Object.assign({}, form$.value.fields, { [path]: pick_1.default(['changed', 'validityMessage'], field) }) }));
localNext = false;
const { validate, immediateValidate } = config;
let defaultValue;
let value;
let initialTransform = true;
const plumb = form.chain({
selector: (state) => selector(path, state),
transformer: (selectedState) => {
const changed = !plumb_1.shallowEqual(selectedState.defaultValue, selectedState.value);
let validityMessage = selectedState.validityMessage;
if (validate && (changed || (immediateValidate && initialTransform))) {
validityMessage = validate(selectedState.value);
}
initialTransform = false;
return Object.assign({}, selectedState, { changed,
validityMessage });
},
error: () => {
form$.next(Object.assign({}, form$.value, { fields: omit_1.default(path, form$.value.fields) }));
updater: (state, _a) => {
var { changed, validityMessage } = _a, rest = __rest(_a, ["changed", "validityMessage"]);
return (Object.assign({}, state, { values: setWith_1.default(clone_1.default, path, rest.value, form.state.values), fields: Object.assign({}, form.state.fields, { [path]: {
validityMessage,
changed,
} }) }));
},
complete: () => {
form$.next(Object.assign({}, form$.value, { fields: omit_1.default(path, form$.value.fields) }));
filter: (selectedState) => {
const changed = !plumb_1.shallowEqual(value, selectedState.value) ||
!plumb_1.shallowEqual(defaultValue, selectedState.defaultValue);
defaultValue = selectedState.defaultValue;
value = selectedState.value;
return changed;
},
}, form$.pipe(
// we assert non-null here because the stream will complete when
// this map gets an undefined value (field is removed from form)
// its just a type hack so that we dont assert everywhere else...
operators_1.map((state) => deriveState(path, state, (nextState) => form$.next(nextState), localNext)),
// complete stream when the field gets removed
operators_1.takeWhile((state) => !!state),
// publish only changed state
operators_1.distinctUntilChanged(equality_1.equal)));
const { immediateValidate, validate, validateDebounce = 0 } = config;
if (validate) {
const validator = validateDebounce > 0
? $.pipe(
// we skip the first iteration since we don't want to invalidate initially
operators_1.skip(immediateValidate ? 0 : 1),
// we don't care about the validity, just the value
operators_1.distinctUntilChanged(({ value: prev }, { value: curr }) => equality_1.equal(prev, curr)), operators_1.debounceTime(validateDebounce))
: $.pipe(
// we skip the first iteration since we don't want to invalidate initially
operators_1.skip(immediateValidate ? 0 : 1),
// we don't care about the validity, just the value
operators_1.distinctUntilChanged(({ value: prev }, { value: curr }) => equality_1.equal(prev, curr)));
let currValidationMessage = null;
let counter = 0;
validator.subscribe((state) =>
// we perform the validation after all subscribers have been notified about the value change
setTimeout(() => {
const _a = state, { value } = _a, rest = __rest(_a, ["value"]);
const pendingValidityMessage = validate(value);
if (!(pendingValidityMessage instanceof Promise)) {
if (pendingValidityMessage !== currValidationMessage) {
currValidationMessage = pendingValidityMessage;
$.next(Object.assign({}, rest, { value, validityMessage: pendingValidityMessage }));
}
return;
}
// we use the counter as a simple cancel mechanism
const internalCounter = counter;
counter++;
if (currValidationMessage !== undefined) {
currValidationMessage = undefined;
$.next(Object.assign({}, rest, { value, validityMessage: currValidationMessage }));
}
pendingValidityMessage.then((nextValidityMessage) => {
// if the internalCounter does not match the outer counter that means that another, newer, validity check is pending
if (internalCounter + 1 === counter) {
currValidationMessage = nextValidityMessage;
$.next(Object.assign({}, rest, { value, validityMessage: nextValidityMessage }));
}
});
}, 0));
}
function getState() {
const state = deriveState(path, form$.value, (nextState) => form$.next(nextState), false);
if (!state) {
throw new Error('domonda-form: Field state should be available here!');
}
return state;
}
function getValue() {
return getState().value;
}
});
plumb.subscribe({
dispose: () => {
form.next(Object.assign({}, form.state, { fields: omit_1.default(path, form.state.fields) }));
},
});
return [
{
$,
plumb,
get state() {
return getState();
return plumb.state;
},
get value() {
return getValue();
return plumb.state.value;
},
setValue: (nextValue) => {
const curr = getState();
$.next(Object.assign({}, curr, { changed: !equality_1.equal(curr.defaultValue, nextValue), value: nextValue }));
value = nextValue;
if (!plumb_1.shallowEqual(plumb.state.value, value)) {
plumb.next(Object.assign({}, plumb.state, { value: nextValue }));
}
},
resetValue: () => {
const curr = getState();
$.next(Object.assign({}, curr, { changed: false, value: curr.defaultValue }));
defaultValue = plumb.state.defaultValue;
value = plumb.state.value;
if (!plumb_1.shallowEqual(value, defaultValue)) {
plumb.next(Object.assign({}, plumb.state, { value: plumb.state.defaultValue }));
}
},
},
() => $.complete(),
() => plumb.dispose(),
];
}
exports.createFormField = createFormField;

@@ -6,4 +6,4 @@ /**

*/
import { BehaviorSubject } from 'rxjs';
import { FormField, FormFieldConfig, FormFieldDestroy } from './FormField';
import { FormField, FormFieldConfig, FormFieldValidityMessage, FormFieldDispose } from './FormField';
import { Plumb } from '@domonda/plumb';
export declare class FormConfigRef<DefaultValues extends FormDefaultValues> {

@@ -44,3 +44,3 @@ private submitHandler;

changed: boolean;
validityMessage: string | null | undefined;
validityMessage: FormFieldValidityMessage;
}

@@ -57,5 +57,4 @@ export interface FormFields {

}
export declare type Form$<T extends FormDefaultValues> = BehaviorSubject<FormState<T>>;
export interface Form<T extends FormDefaultValues> {
readonly $: Form$<T>;
readonly plumb: Plumb<FormState<T>>;
readonly state: FormState<T>;

@@ -67,4 +66,4 @@ readonly values: T;

resetSubmitError: () => void;
makeFormField: <T>(path: string, config?: FormFieldConfig<T>) => [FormField<T>, FormFieldDestroy];
makeFormField: <T>(path: string, config?: FormFieldConfig<T>) => [FormField<T>, FormFieldDispose];
}
export declare type FormDestroy = () => void;
export declare type FormDispose = () => void;

@@ -7,3 +7,3 @@ /**

import { FormFieldState } from './Form';
import { AnonymousSubject } from 'rxjs/internal/Subject';
import { Plumb } from '@domonda/plumb';
export interface FormFieldStateWithValues<T> extends FormFieldState {

@@ -13,11 +13,10 @@ defaultValue: Readonly<T>;

}
export declare type FormFieldValidate<T> = (value: Readonly<T>) => Promise<string | null> | (string | null);
export declare type FormFieldValidityMessage = string | null;
export declare type FormFieldValidate<T> = (value: Readonly<T>) => FormFieldValidityMessage;
export interface FormFieldConfig<T> {
validate?: FormFieldValidate<T>;
validateDebounce?: number;
immediateValidate?: boolean;
}
export declare type FormField$<T> = AnonymousSubject<FormFieldStateWithValues<T>>;
export interface FormField<T> {
readonly $: FormField$<T>;
readonly plumb: Plumb<FormFieldStateWithValues<T>>;
readonly state: FormFieldStateWithValues<T>;

@@ -28,2 +27,2 @@ readonly value: T;

}
export declare type FormFieldDestroy = () => void;
export declare type FormFieldDispose = () => void;

@@ -5,2 +5,1 @@ export * from './Form';

export * from './createFormField';
export * from './equality';

@@ -9,2 +9,1 @@ "use strict";

__export(require("./createFormField"));
__export(require("./equality"));
{
"name": "@domonda/form",
"version": "1.2.2",
"description": "Powerful yet simple form library built on top of RxJS.",
"version": "2.0.0",
"description": "Powerful yet simple form library built using @domonda/plumb.",
"keywords": [
"domonda",
"form",
"rxjs"
"plumb"
],

@@ -23,7 +23,7 @@ "homepage": "https://github.com/domonda/domonda-js#readme",

"dependencies": {
"@domonda/plumb": "^2.0.0",
"lodash.clone": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.omit": "^4.5.0",
"lodash.setwith": "^4.3.2",
"rxjs": "^6.5.2"
"lodash.setwith": "^4.3.2"
},

@@ -30,0 +30,0 @@ "main": "index.js",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc