@homebound/form-state
Advanced tools
Comparing version 2.19.0 to 2.20.0
@@ -10,14 +10,14 @@ import { Fragment, ObjectState } from "./fields/objectField"; | ||
*/ | ||
export declare type ObjectConfig<T> = { | ||
export type ObjectConfig<T> = { | ||
[P in keyof OmitIf<T, Function>]: T[P] extends Fragment<infer V> ? FragmentFieldConfig : T[P] extends Array<infer U> | null | undefined ? U extends Builtin ? ValueFieldConfig<T, T[P]> : ListFieldConfig<T, U> : ValueFieldConfig<T, T[P]> | ObjectFieldConfig<T[P]>; | ||
}; | ||
declare type OmitIf<Base, Condition> = Pick<Base, { | ||
type OmitIf<Base, Condition> = Pick<Base, { | ||
[Key in keyof Base]: Base[Key] extends Condition ? never : Key; | ||
}[keyof Base]>; | ||
/** Field configuration for an opaque value that we don't actually want to include. */ | ||
export declare type FragmentFieldConfig = { | ||
export type FragmentFieldConfig = { | ||
type: "fragment"; | ||
}; | ||
/** Field configuration for primitive values, i.e. strings/numbers/Dates/user-defined types. */ | ||
export declare type ValueFieldConfig<T, V> = { | ||
export type ValueFieldConfig<T, V> = { | ||
type: "value"; | ||
@@ -49,3 +49,3 @@ rules?: Rule<V | null | undefined>[]; | ||
/** Field configuration for list values, i.e. `U` is `Book` in a form with `books: Book[]`. */ | ||
export declare type ListFieldConfig<T, U> = { | ||
export type ListFieldConfig<T, U> = { | ||
type: "list"; | ||
@@ -71,3 +71,3 @@ /** Rules that can run on the full list of children. */ | ||
}; | ||
export declare type ObjectFieldConfig<U> = { | ||
export type ObjectFieldConfig<U> = { | ||
type: "object"; | ||
@@ -74,0 +74,0 @@ /** Config for the child's form state, i.e. each book. */ |
@@ -9,3 +9,3 @@ "use strict"; | ||
// we want to pretend that it's observable, so use a tick to force it. | ||
const _tick = mobx_1.observable({ value: 1 }); | ||
const _tick = (0, mobx_1.observable)({ value: 1 }); | ||
// We steal the fragment from our parent, so that it doesn't | ||
@@ -17,3 +17,3 @@ // accidentally end up on the wire | ||
get value() { | ||
_tick.value > 0 || utils_1.fail(); | ||
_tick.value > 0 || (0, utils_1.fail)(); | ||
return value; | ||
@@ -26,4 +26,4 @@ }, | ||
}; | ||
return mobx_1.makeAutoObservable(obj, { value: false }); | ||
return (0, mobx_1.makeAutoObservable)(obj, { value: false }); | ||
} | ||
exports.newFragmentField = newFragmentField; |
@@ -11,4 +11,4 @@ "use strict"; | ||
const rowMap = new Map(); | ||
const _tick = mobx_1.observable({ value: 1 }); | ||
const _childTick = mobx_1.observable({ value: 1 }); | ||
const _tick = (0, mobx_1.observable)({ value: 1 }); | ||
const _childTick = (0, mobx_1.observable)({ value: 1 }); | ||
// this is for dirty checking, not object identity | ||
@@ -20,3 +20,3 @@ let originalCopy = [...(parentInstance[key] || [])]; | ||
get value() { | ||
return _tick.value > 0 && _childTick.value > 0 ? parentInstance[key] : utils_1.fail(); | ||
return _tick.value > 0 && _childTick.value > 0 ? parentInstance[key] : (0, utils_1.fail)(); | ||
}, | ||
@@ -73,3 +73,3 @@ _focused: false, | ||
if (_tick.value < 0) | ||
utils_1.fail(); | ||
(0, utils_1.fail)(); | ||
// Avoid using `this.value` to avoid registering `_childTick` as a dependency | ||
@@ -81,3 +81,3 @@ const value = parentInstance[key]; | ||
if (!childState) { | ||
childState = objectField_1.newObjectState(config, parentState, list, child, undefined, maybeAutoSave); | ||
childState = (0, objectField_1.newObjectState)(config, parentState, list, child, undefined, maybeAutoSave); | ||
rowMap.set(child, childState); | ||
@@ -106,3 +106,3 @@ } | ||
if (_tick.value < 0) | ||
utils_1.fail(); | ||
(0, utils_1.fail)(); | ||
const opts = { value: this.rows, key: key, originalValue: originalCopy || [], object: parentState() }; | ||
@@ -146,3 +146,3 @@ return this.rules.map((r) => r(opts)).filter(utils_1.isNotUndefined); | ||
if (this.readOnly && !opts.resetting && !opts.refreshing) { | ||
throw new Error(`${key} is currently readOnly`); | ||
throw new Error(`${String(key)} is currently readOnly`); | ||
} | ||
@@ -162,3 +162,3 @@ // We should be passed values that are non-proxies. | ||
// If we didn't have an existing child, just make a new object state | ||
childState = objectField_1.createObjectState(config, value, { maybeAutoSave }); | ||
childState = (0, objectField_1.createObjectState)(config, value, { maybeAutoSave }); | ||
rowMap.set(value, childState); | ||
@@ -177,3 +177,3 @@ } | ||
// This is called by the user, so value should be a non-proxy value we should keep | ||
const childState = objectField_1.createObjectState(config, value, { maybeAutoSave }); | ||
const childState = (0, objectField_1.createObjectState)(config, value, { maybeAutoSave }); | ||
rowMap.set(value, childState); | ||
@@ -217,11 +217,11 @@ this.ensureSet(); | ||
}; | ||
const proxy = mobx_1.makeAutoObservable(list, { | ||
const proxy = (0, mobx_1.makeAutoObservable)(list, { | ||
// See other makeAutoObservable comment | ||
value: mobx_1.computed({ equals: () => false }), | ||
value: (0, mobx_1.computed)({ equals: () => false }), | ||
}); | ||
// Any time a row's value changes, percolate that to our `.value` (so the callers to our | ||
// `.value` will rerun given the value they saw has deeply changed.) | ||
mobx_1.reaction(() => proxy.rows.map((r) => r.value), () => _childTick.value++); | ||
(0, mobx_1.reaction)(() => proxy.rows.map((r) => r.value), () => _childTick.value++); | ||
return proxy; | ||
} | ||
exports.newListFieldState = newListFieldState; |
@@ -31,3 +31,3 @@ import { ObjectConfig } from "../config"; | ||
*/ | ||
export declare type ObjectState<T> = FieldStates<T> & FieldState<T> & { | ||
export type ObjectState<T> = FieldStates<T> & FieldState<T> & { | ||
/** Sets the state of fields in `state`. */ | ||
@@ -38,7 +38,7 @@ set(state: Partial<T>, opts?: SetOpts): void; | ||
}; | ||
export declare type ObjectStateInternal<T> = ObjectState<T> & { | ||
export type ObjectStateInternal<T> = ObjectState<T> & { | ||
set(value: T, opts?: InternalSetOpts): void; | ||
}; | ||
declare const fragmentSym: unique symbol; | ||
export declare type Fragment<V> = V & { | ||
export type Fragment<V> = V & { | ||
[fragmentSym]: true; | ||
@@ -48,3 +48,3 @@ }; | ||
/** For a given input type `T`, decorate each field into the "field state" type that holds our form-relevant state, i.e. valid/touched/etc. */ | ||
declare type FieldStates<T> = { | ||
type FieldStates<T> = { | ||
[K in keyof T]-?: T[K] extends Fragment<infer V> ? FragmentField<V> : T[K] extends Array<infer U> | null | undefined ? [U] extends [Builtin] ? FieldState<T[K]> : ListFieldState<U> : T[K] extends Builtin | null | undefined ? FieldState<T[K]> : ObjectState<T[K]>; | ||
@@ -51,0 +51,0 @@ }; |
@@ -44,3 +44,3 @@ "use strict"; | ||
if (config.type === "value") { | ||
field = valueField_1.newValueFieldState(instance, getObjectState, key, config.rules || [], config.isIdKey || | ||
field = (0, valueField_1.newValueFieldState)(instance, getObjectState, key, config.rules || [], config.isIdKey || | ||
// Default the id key to "id" unless some other field has isIdKey set | ||
@@ -51,3 +51,3 @@ (key === "id" && | ||
else if (config.type === "list") { | ||
field = listField_1.newListFieldState(instance, getObjectState, key, config.rules || [], config, config.config, (_b = config.strictOrder) !== null && _b !== void 0 ? _b : true, maybeAutoSave); | ||
field = (0, listField_1.newListFieldState)(instance, getObjectState, key, config.rules || [], config, config.config, (_b = config.strictOrder) !== null && _b !== void 0 ? _b : true, maybeAutoSave); | ||
} | ||
@@ -61,3 +61,3 @@ else if (config.type === "object") { | ||
else if (config.type === "fragment") { | ||
field = fragmentField_1.newFragmentField(instance, key); | ||
field = (0, fragmentField_1.newFragmentField)(instance, key); | ||
} | ||
@@ -71,3 +71,3 @@ else { | ||
// we want to pretend that it's observable, so use a tick to force it. | ||
const _tick = mobx_1.observable({ value: 1 }); | ||
const _tick = (0, mobx_1.observable)({ value: 1 }); | ||
const fieldNames = Object.keys(config); | ||
@@ -81,3 +81,3 @@ function getFields(proxyThis) { | ||
get value() { | ||
_tick.value > 0 || utils_1.fail(); | ||
_tick.value > 0 || (0, utils_1.fail)(); | ||
return instance; | ||
@@ -145,6 +145,6 @@ }, | ||
if (this.readOnly && !opts.resetting && !opts.refreshing) { | ||
throw new Error(`${key || "formState"} is currently readOnly`); | ||
throw new Error(`${String(key) || "formState"} is currently readOnly`); | ||
} | ||
getFields(this).forEach((field) => { | ||
if (field.key in value) { | ||
if (value && typeof value === "object" && field.key in value) { | ||
field.set(value[field.key], opts); | ||
@@ -196,3 +196,3 @@ } | ||
}; | ||
proxy = mobx_1.makeAutoObservable(obj, { | ||
proxy = (0, mobx_1.makeAutoObservable)(obj, { | ||
// Use custom equality on `value` that is _never_ equals. This sounds weird, but | ||
@@ -202,8 +202,8 @@ // because our `value` is always the same `instance` that was passed to `newObjectState`, | ||
// even with our tick-based hacks. | ||
value: mobx_1.computed({ equals: () => false }), | ||
value: (0, mobx_1.computed)({ equals: () => false }), | ||
}); | ||
// Any time a field changes, percolate that change up to us | ||
mobx_1.reaction(() => getFields(proxy).map((f) => f.value), () => _tick.value++); | ||
(0, mobx_1.reaction)(() => getFields(proxy).map((f) => f.value), () => _tick.value++); | ||
return proxy; | ||
} | ||
exports.newObjectState = newObjectState; |
@@ -39,3 +39,16 @@ import { ObjectState } from "./objectField"; | ||
commitChanges(): void; | ||
/** Creates a new FieldState with a transformation of the value, i.e. string to int, or feet to inches. */ | ||
adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2>; | ||
} | ||
/** | ||
* Allows changing a type in the formState (like a string) to a different type in the UI (like a number). | ||
* | ||
* Or doing unit of measure conversions within the same type, like from meters to feet. | ||
*/ | ||
export interface ValueAdapter<V, V2 = V> { | ||
/** Converts the original FieldState's value `V` into new `V2` type. */ | ||
toValue(value: V): V2; | ||
/** Converts the adapted FieldState's value `V2` back into the original `V` type. */ | ||
fromValue(value: V2): V; | ||
} | ||
/** Public options for our `set` command. */ | ||
@@ -42,0 +55,0 @@ export interface SetOpts { |
@@ -6,2 +6,3 @@ "use strict"; | ||
const mobx_1 = require("mobx"); | ||
const proxies_1 = require("../proxies"); | ||
const rules_1 = require("../rules"); | ||
@@ -12,9 +13,9 @@ const utils_1 = require("../utils"); | ||
const value = parentInstance[key]; | ||
let _originalValue = value === null ? undefined : is_plain_object_1.isPlainObject(value) ? mobx_1.toJS(value) : value; | ||
let _originalValue = value === null ? undefined : (0, is_plain_object_1.isPlainObject)(value) ? (0, mobx_1.toJS)(value) : value; | ||
// Because we read/write the value directly back into parentInstance[key], | ||
// which itself is not a proxy, we use this as our "value changed" trigger. | ||
const _tick = mobx_1.observable({ value: 1 }); | ||
const _originalValueTick = mobx_1.observable({ value: 1 }); | ||
const _tick = (0, mobx_1.observable)({ value: 1 }); | ||
const _originalValueTick = (0, mobx_1.observable)({ value: 1 }); | ||
const field = { | ||
key, | ||
key: key, | ||
touched: false, | ||
@@ -31,7 +32,7 @@ /** Current readOnly value. */ | ||
// If we're wrapping a mobx store, then we'll get reactivity from parentInstance[key] | ||
const value = _tick.value > 0 ? parentInstance[key] : utils_1.fail(); | ||
const value = _tick.value > 0 ? parentInstance[key] : (0, utils_1.fail)(); | ||
// Re-create the `keepNull` logic on sets but for our initial read where our | ||
// originalValue is null (empty) but we want to expose it as undefined for | ||
// consistency of "empty-ness" to our UI components. | ||
return value === null && utils_1.isEmpty(_originalValue) ? undefined : value; | ||
return value === null && (0, utils_1.isEmpty)(_originalValue) ? undefined : value; | ||
}, | ||
@@ -42,3 +43,3 @@ set value(v) { | ||
get dirty() { | ||
return !utils_1.areEqual(this.originalValue, this.value, strictOrder); | ||
return !(0, utils_1.areEqual)(this.originalValue, this.value, strictOrder); | ||
}, | ||
@@ -95,3 +96,3 @@ /** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */ | ||
if (this.readOnly && !opts.resetting && !opts.refreshing) { | ||
throw new Error(`${key} is currently readOnly`); | ||
throw new Error(`${String(key)} is currently readOnly`); | ||
} | ||
@@ -110,8 +111,8 @@ if (opts.refreshing && this.dirty && this.value !== value) { | ||
// so that our partial update to the backend correctly unsets it. | ||
const keepNull = !utils_1.isEmpty(this.originalValue) && utils_1.isEmpty(value) && !opts.refreshing; | ||
const keepNull = !(0, utils_1.isEmpty)(this.originalValue) && (0, utils_1.isEmpty)(value) && !opts.refreshing; | ||
// If a list of primitives was originally undefined, coerce `[]` to `undefined` | ||
const coerceEmptyList = value && value instanceof Array && value.length === 0 && utils_1.isEmpty(this.originalValue); | ||
const newValue = keepNull ? null : utils_1.isEmpty(value) || coerceEmptyList ? undefined : value; | ||
const coerceEmptyList = value && value instanceof Array && value.length === 0 && (0, utils_1.isEmpty)(this.originalValue); | ||
const newValue = keepNull ? null : (0, utils_1.isEmpty)(value) || coerceEmptyList ? undefined : value; | ||
// Set the value on our parent object | ||
const changed = !utils_1.areEqual(newValue, this.value, strictOrder); | ||
const changed = !(0, utils_1.areEqual)(newValue, this.value, strictOrder); | ||
parentInstance[key] = newValue; | ||
@@ -128,2 +129,5 @@ _tick.value++; | ||
}, | ||
adapt(adapter) { | ||
return adapt(this, adapter); | ||
}, | ||
revertChanges() { | ||
@@ -136,4 +140,4 @@ if (!computed) { | ||
commitChanges() { | ||
if (is_plain_object_1.isPlainObject(this.originalValue)) { | ||
this.originalValue = mobx_1.toJS(this.value); | ||
if ((0, is_plain_object_1.isPlainObject)(this.originalValue)) { | ||
this.originalValue = (0, mobx_1.toJS)(this.value); | ||
} | ||
@@ -170,1 +174,31 @@ else { | ||
exports.newValueFieldState = newValueFieldState; | ||
/** | ||
* Returns a proxy that looks exactly like the original `field`, in terms of valid/touched/errors/etc., but | ||
* has any methods that use `V` overridden to use be `V2`. | ||
* | ||
* Note that `V2` can be a new type, like string -> number, or just a transformation on the same | ||
* type, i.e. feet -> inches where both are `number`s. | ||
*/ | ||
function adapt(field, adapter) { | ||
return (0, proxies_1.newDelegateProxy)(field, { | ||
rules: [], | ||
get value() { | ||
return adapter.toValue(field.value); | ||
}, | ||
set value(v) { | ||
field.value = adapter.fromValue(v); | ||
}, | ||
set: (v) => { | ||
field.value = adapter.fromValue(v); | ||
}, | ||
get changedValue() { | ||
return this.value; | ||
}, | ||
adapt(adapter) { | ||
return adapt(this, adapter); | ||
}, | ||
get originalValue() { | ||
return adapter.toValue(field.originalValue); | ||
}, | ||
}); | ||
} |
@@ -1,5 +0,3 @@ | ||
/// <reference types="react" /> | ||
import { Meta } from "@storybook/react"; | ||
declare const _default: Meta<import("@storybook/react").Args>; | ||
declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react/dist/types-0a347bb9").R, import("@storybook/types").Args>; | ||
export default _default; | ||
export declare function AppExample(): JSX.Element; | ||
export declare function AppExample(): import("react/jsx-runtime").JSX.Element; |
@@ -11,4 +11,4 @@ "use strict"; | ||
function AppExample() { | ||
return jsx_runtime_1.jsx(FormStateApp_1.FormStateApp, {}, void 0); | ||
return (0, jsx_runtime_1.jsx)(FormStateApp_1.FormStateApp, {}); | ||
} | ||
exports.AppExample = AppExample; |
@@ -1,6 +0,5 @@ | ||
/// <reference types="react" /> | ||
import { FieldState } from "./index"; | ||
export declare function FormStateApp(): JSX.Element; | ||
export declare function FormStateApp(): import("react/jsx-runtime").JSX.Element; | ||
export declare function TextField(props: { | ||
field: FieldState<string | null | undefined>; | ||
}): JSX.Element; | ||
}): import("react/jsx-runtime").JSX.Element; |
@@ -8,3 +8,3 @@ "use strict"; | ||
function FormStateApp() { | ||
const formState = index_1.useFormState({ | ||
const formState = (0, index_1.useFormState)({ | ||
config: formConfig, | ||
@@ -28,34 +28,8 @@ // Simulate getting the initial form state back from a server call | ||
}); | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => { | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => { | ||
var _a; | ||
return (jsx_runtime_1.jsx("div", Object.assign({ className: "App" }, { children: jsx_runtime_1.jsxs("header", Object.assign({ className: "App-header" }, { children: [jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("b", { children: "Author" }, void 0), | ||
jsx_runtime_1.jsx(TextField, { field: formState.firstName }, void 0), | ||
jsx_runtime_1.jsx(TextField, { field: formState.lastName }, void 0)] }, void 0), | ||
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsxs("strong", { children: ["Books ", jsx_runtime_1.jsx("button", Object.assign({ onClick: () => formState.books.add({}) }, { children: "Add book" }), void 0)] }, void 0), | ||
(_a = formState.books.rows) === null || _a === void 0 ? void 0 : _a.map((row, i) => { | ||
return (jsx_runtime_1.jsxs("div", { children: ["Book ", i, | ||
jsx_runtime_1.jsx("button", Object.assign({ onClick: () => formState.books.remove(row.value) }, { children: "X" }), void 0), | ||
jsx_runtime_1.jsx(TextField, { field: row.title }, void 0)] }, i)); | ||
})] }, void 0), | ||
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("strong", { children: "Rows" }, void 0), | ||
jsx_runtime_1.jsxs("table", Object.assign({ cellPadding: "4px" }, { children: [jsx_runtime_1.jsx("thead", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("th", { children: "touched" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "valid" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "dirty" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "errors" }, void 0)] }, void 0) }, void 0), | ||
jsx_runtime_1.jsx("tbody", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("td", { children: formState.books.touched.toString() }, void 0), | ||
jsx_runtime_1.jsx("td", { children: formState.books.valid.toString() }, void 0), | ||
jsx_runtime_1.jsx("td", { children: formState.books.dirty.toString() }, void 0), | ||
jsx_runtime_1.jsx("td", { children: formState.books.errors }, void 0)] }, void 0) }, void 0)] }), void 0)] }, void 0), | ||
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("strong", { children: "Form" }, void 0), | ||
jsx_runtime_1.jsxs("table", Object.assign({ cellPadding: "4px" }, { children: [jsx_runtime_1.jsx("thead", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("th", { children: "touched" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "valid" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "dirty" }, void 0)] }, void 0) }, void 0), | ||
jsx_runtime_1.jsx("tbody", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("td", { children: formState.touched.toString() }, void 0), | ||
jsx_runtime_1.jsx("td", { children: formState.valid.toString() }, void 0), | ||
jsx_runtime_1.jsx("td", { children: formState.dirty.toString() }, void 0)] }, void 0) }, void 0)] }), void 0), | ||
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "touch", onClick: () => (formState.touched = !formState.touched) }, { children: "touch" }), void 0), | ||
jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "revertChanges", onClick: () => formState.revertChanges() }, { children: "revert" }), void 0), | ||
jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "commitChanges", onClick: () => formState.commitChanges() }, { children: "commit" }), void 0), | ||
jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "set", onClick: () => formState.set({ firstName: "a2" }) }, { children: "set" }), void 0)] }, void 0)] }, void 0)] }), void 0) }), void 0)); | ||
} }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)("div", { className: "App", children: (0, jsx_runtime_1.jsxs)("header", { className: "App-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("b", { children: "Author" }), (0, jsx_runtime_1.jsx)(TextField, { field: formState.firstName }), (0, jsx_runtime_1.jsx)(TextField, { field: formState.lastName })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("strong", { children: ["Books ", (0, jsx_runtime_1.jsx)("button", { onClick: () => formState.books.add({}), children: "Add book" })] }), (_a = formState.books.rows) === null || _a === void 0 ? void 0 : _a.map((row, i) => { | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: ["Book ", i, (0, jsx_runtime_1.jsx)("button", { onClick: () => formState.books.remove(row.value), children: "X" }), (0, jsx_runtime_1.jsx)(TextField, { field: row.title })] }, i)); | ||
})] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Rows" }), (0, jsx_runtime_1.jsxs)("table", { cellPadding: "4px", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "touched" }), (0, jsx_runtime_1.jsx)("th", { children: "valid" }), (0, jsx_runtime_1.jsx)("th", { children: "dirty" }), (0, jsx_runtime_1.jsx)("th", { children: "errors" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: formState.books.touched.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.books.valid.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.books.dirty.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.books.errors })] }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Form" }), (0, jsx_runtime_1.jsxs)("table", { cellPadding: "4px", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "touched" }), (0, jsx_runtime_1.jsx)("th", { children: "valid" }), (0, jsx_runtime_1.jsx)("th", { children: "dirty" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: formState.touched.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.valid.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.dirty.toString() })] }) })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { "data-testid": "touch", onClick: () => (formState.touched = !formState.touched), children: "touch" }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "revertChanges", onClick: () => formState.revertChanges(), children: "revert" }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "commitChanges", onClick: () => formState.commitChanges(), children: "commit" }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "set", onClick: () => formState.set({ firstName: "a2" }), children: "set" })] })] })] }) })); | ||
} })); | ||
} | ||
@@ -79,19 +53,6 @@ exports.FormStateApp = FormStateApp; | ||
// parent uses `<Observer>` | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsxs("span", { children: [field.key, ":"] }, void 0), | ||
jsx_runtime_1.jsx("div", { children: jsx_runtime_1.jsx("input", { "data-testid": field.key, value: field.value || "", onBlur: () => field.blur(), readOnly: field.readOnly, onChange: (e) => { | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("span", { children: [field.key, ":"] }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("input", { "data-testid": field.key, value: field.value || "", onBlur: () => field.blur(), readOnly: field.readOnly, onChange: (e) => { | ||
field.set(e.target.value); | ||
} }, void 0) }, void 0), | ||
jsx_runtime_1.jsxs("table", Object.assign({ cellPadding: "4px" }, { children: [jsx_runtime_1.jsx("thead", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("th", { children: "touched" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "valid" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "dirty" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "readOnly" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "errors" }, void 0), | ||
jsx_runtime_1.jsx("th", { children: "original value" }, void 0)] }, void 0) }, void 0), | ||
jsx_runtime_1.jsx("tbody", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_touched` }, { children: field.touched.toString() }), void 0), | ||
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_valid` }, { children: field.valid.toString() }), void 0), | ||
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_dirty` }, { children: field.dirty.toString() }), void 0), | ||
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_readOnly` }, { children: field.readOnly.toString() }), void 0), | ||
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_errors` }, { children: field.errors }), void 0), | ||
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_original` }, { children: field.originalValue }), void 0)] }, void 0) }, void 0)] }), void 0)] }, void 0)) }, void 0)); | ||
} }) }), (0, jsx_runtime_1.jsxs)("table", { cellPadding: "4px", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "touched" }), (0, jsx_runtime_1.jsx)("th", { children: "valid" }), (0, jsx_runtime_1.jsx)("th", { children: "dirty" }), (0, jsx_runtime_1.jsx)("th", { children: "readOnly" }), (0, jsx_runtime_1.jsx)("th", { children: "errors" }), (0, jsx_runtime_1.jsx)("th", { children: "original value" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_touched`, children: field.touched.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_valid`, children: field.valid.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_dirty`, children: field.dirty.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_readOnly`, children: field.readOnly.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_errors`, children: field.errors }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_original`, children: field.originalValue })] }) })] })] })) })); | ||
} | ||
exports.TextField = TextField; |
@@ -9,19 +9,19 @@ "use strict"; | ||
it("save resets dirty reactively", async () => { | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(FormStateApp_1.FormStateApp, {}, void 0)); | ||
expect(r.firstName_dirty()).toHaveTextContent("false"); | ||
rtl_utils_1.type(r.firstName, "changed"); | ||
expect(r.firstName_dirty()).toHaveTextContent("true"); | ||
react_1.fireEvent.blur(r.firstName()); | ||
expect(r.firstName_touched()).toHaveTextContent("true"); | ||
rtl_utils_1.click(r.commitChanges); | ||
expect(r.firstName_dirty()).toHaveTextContent("false"); | ||
expect(r.firstName_touched()).toHaveTextContent("false"); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(FormStateApp_1.FormStateApp, {})); | ||
expect(r.firstName_dirty).toHaveTextContent("false"); | ||
(0, rtl_utils_1.type)(r.firstName, "changed"); | ||
expect(r.firstName_dirty).toHaveTextContent("true"); | ||
react_1.fireEvent.blur(r.firstName); | ||
expect(r.firstName_touched).toHaveTextContent("true"); | ||
(0, rtl_utils_1.click)(r.commitChanges); | ||
expect(r.firstName_dirty).toHaveTextContent("false"); | ||
expect(r.firstName_touched).toHaveTextContent("false"); | ||
}); | ||
it("originalValue is reactive", async () => { | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(FormStateApp_1.FormStateApp, {}, void 0)); | ||
expect(r.firstName_original()).toHaveTextContent("a1"); | ||
rtl_utils_1.click(r.set); | ||
rtl_utils_1.click(r.commitChanges); | ||
expect(r.firstName_original()).toHaveTextContent("a2"); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(FormStateApp_1.FormStateApp, {})); | ||
expect(r.firstName_original).toHaveTextContent("a1"); | ||
(0, rtl_utils_1.click)(r.set); | ||
(0, rtl_utils_1.click)(r.commitChanges); | ||
expect(r.firstName_original).toHaveTextContent("a2"); | ||
}); | ||
}); |
@@ -16,3 +16,3 @@ "use strict"; | ||
Color["Green"] = "GREEN"; | ||
})(Color = exports.Color || (exports.Color = {})); | ||
})(Color || (exports.Color = Color = {})); | ||
class DateOnly { | ||
@@ -19,0 +19,0 @@ constructor(date) { |
export { ObjectConfig } from "./config"; | ||
export { ListFieldState } from "./fields/listField"; | ||
export { createObjectState, ObjectState } from "./fields/objectField"; | ||
export { FieldState } from "./fields/valueField"; | ||
export { required, Rule } from "./rules"; | ||
export { ObjectState, createObjectState } from "./fields/objectField"; | ||
export { FieldState, ValueAdapter } from "./fields/valueField"; | ||
export { Rule, required } from "./rules"; | ||
export { useFormState } from "./useFormState"; | ||
export { useFormStates } from "./useFormStates"; |
/** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */ | ||
export declare type Rule<V> = (opts: { | ||
export type Rule<V> = (opts: { | ||
value: V; | ||
@@ -4,0 +4,0 @@ key: string; |
@@ -8,5 +8,5 @@ "use strict"; | ||
// and breaks our ability to do `rules.some(r => r === required)`. | ||
exports.required = mobx_1.action(({ value: v }) => { | ||
exports.required = (0, mobx_1.action)(({ value: v }) => { | ||
const isEmptyString = typeof v === "string" ? v.trim() === "" : false; | ||
return v !== undefined && v !== null && !isEmptyString ? undefined : "Required"; | ||
}); |
@@ -6,5 +6,5 @@ "use strict"; | ||
// formState doesn't use actions | ||
mobx_1.configure({ enforceActions: "never" }); | ||
(0, mobx_1.configure)({ enforceActions: "never" }); | ||
beforeEach(() => { | ||
jest.useFakeTimers("modern"); | ||
jest.useFakeTimers(); | ||
}); | ||
@@ -11,0 +11,0 @@ afterEach(() => { |
import { ObjectConfig } from "./config"; | ||
import { ObjectState } from "./fields/objectField"; | ||
export declare type Query<I> = { | ||
export type Query<I> = { | ||
data: I; | ||
@@ -8,3 +8,3 @@ loading: boolean; | ||
}; | ||
export declare type InputAndMap<T, I> = { | ||
export type InputAndMap<T, I> = { | ||
input: I; | ||
@@ -14,3 +14,3 @@ map: (input: Exclude<I, null | undefined>) => T; | ||
}; | ||
export declare type QueryAndMap<T, I> = { | ||
export type QueryAndMap<T, I> = { | ||
query: Query<I>; | ||
@@ -20,3 +20,3 @@ map: (input: Exclude<I, null | undefined>) => T; | ||
}; | ||
export declare type UseFormStateOpts<T, I> = { | ||
export type UseFormStateOpts<T, I> = { | ||
/** The form configuration, should be a module-level const or useMemo'd. */ | ||
@@ -23,0 +23,0 @@ config: ObjectConfig<T>; |
@@ -21,11 +21,11 @@ "use strict"; | ||
// Use a ref so our memo'ized `onBlur` always see the latest value | ||
const autoSaveRef = react_1.useRef(autoSave); | ||
const autoSaveRef = (0, react_1.useRef)(autoSave); | ||
autoSaveRef.current = autoSave; | ||
const firstRunRef = react_1.useRef(true); | ||
const firstRunRef = (0, react_1.useRef)(true); | ||
// This is a little weird, but we need to know ahead of time, before the form useMemo, if we're working with classes/mobx proxies | ||
const [firstInitValue] = react_1.useState(() => utils_1.initValue(config, init)); | ||
const isWrappingMobxProxy = !is_plain_object_1.isPlainObject(firstInitValue); | ||
const [firstInitValue] = (0, react_1.useState)(() => (0, utils_1.initValue)(config, init)); | ||
const isWrappingMobxProxy = !(0, is_plain_object_1.isPlainObject)(firstInitValue); | ||
// If they're using init.input, useMemo on it (and it might be an array), otherwise allow the identity of init be unstable | ||
const dep = utils_1.isInput(init) ? makeArray(init.input) : utils_1.isQuery(init) ? [init.query.data, init.query.loading] : []; | ||
const form = react_1.useMemo(() => { | ||
const dep = (0, utils_1.isInput)(init) ? makeArray(init.input) : (0, utils_1.isQuery)(init) ? [init.query.data, init.query.loading] : []; | ||
const form = (0, react_1.useMemo)(() => { | ||
function maybeAutoSave() { | ||
@@ -68,4 +68,4 @@ if (isAutoSaving === "in-flight") { | ||
} | ||
const value = firstRunRef.current ? firstInitValue : utils_1.initValue(config, init); | ||
const form = objectField_1.createObjectState(config, value, { maybeAutoSave }); | ||
const value = firstRunRef.current ? firstInitValue : (0, utils_1.initValue)(config, init); | ||
const form = (0, objectField_1.createObjectState)(config, value, { maybeAutoSave }); | ||
form.readOnly = readOnly; | ||
@@ -83,3 +83,3 @@ setLoading(form, opts); | ||
// queue their components' render), don't happen during our render, per https://fb.me/setstate-in-render. | ||
react_1.useEffect(() => { | ||
(0, react_1.useEffect)(() => { | ||
// Ignore the 1st run b/c our 1st useMemo already initialized `form` with the current `init` value. | ||
@@ -99,6 +99,6 @@ // Also for mobx proxies, we recreate a new form-state every time init changes, so that our | ||
// component, but it's unlikely the user will always remember to do this). | ||
form.set(utils_1.initValue(config, init), { refreshing: true }); | ||
form.set((0, utils_1.initValue)(config, init), { refreshing: true }); | ||
}, [form, ...dep]); | ||
// Use useEffect so that we don't touch the form.init proxy during a render | ||
react_1.useEffect(() => { | ||
(0, react_1.useEffect)(() => { | ||
form.readOnly = readOnly; | ||
@@ -118,7 +118,7 @@ if (loading !== undefined) { | ||
} | ||
else if (utils_1.isInput(init) && !init.ifUndefined) { | ||
else if ((0, utils_1.isInput)(init) && !init.ifUndefined) { | ||
// Otherwise, check for `init.input` | ||
form.loading = init.input === undefined; | ||
} | ||
else if (utils_1.isQuery(init)) { | ||
else if ((0, utils_1.isQuery)(init)) { | ||
// Or `query.loading` | ||
@@ -125,0 +125,0 @@ form.loading = init.query.loading; |
@@ -19,3 +19,3 @@ "use strict"; | ||
const data = { firstName: "bob" }; | ||
const form = useFormState_1.useFormState({ | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -25,5 +25,5 @@ // Then the lambda is passed the "de-undefined" data, i.e. `d.firstName` is not a compile error | ||
}); | ||
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0); | ||
return (0, jsx_runtime_1.jsx)("div", { children: form.firstName.value }); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.baseElement).toHaveTextContent("bob"); | ||
@@ -34,4 +34,4 @@ }); | ||
function TestComponent() { | ||
const [, setTick] = react_2.useState(0); | ||
const form = useFormState_1.useFormState({ | ||
const [, setTick] = (0, react_2.useState)(0); | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -41,9 +41,8 @@ init: { input: ["a", "b"], map: ([a, b]) => ({ firstName: a + b }) }, | ||
const onClick = () => [form.firstName.set("fred"), setTick(1)]; | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "change", onClick: onClick }, void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0)] }, void 0)); | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { "data-testid": "change", onClick: onClick }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: form.firstName.value })] })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
expect(r.firstName()).toHaveTextContent("ab"); | ||
rtl_utils_1.click(r.change); | ||
expect(r.firstName()).toHaveTextContent("fred"); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.firstName).toHaveTextContent("ab"); | ||
(0, rtl_utils_1.click)(r.change); | ||
expect(r.firstName).toHaveTextContent("fred"); | ||
}); | ||
@@ -56,9 +55,9 @@ it("uses default if init.input is undefined", async () => { | ||
const data = Math.random() >= 0 ? undefined : { firstName: "bob" }; | ||
const form = useFormState_1.useFormState({ | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
init: { input: data, map: (d) => ({ firstName: d.firstName }) }, | ||
}); | ||
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0); | ||
return (0, jsx_runtime_1.jsx)("div", { children: form.firstName.value }); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// Then we init.map wasn't called, and we used {} instead | ||
@@ -76,3 +75,3 @@ expect(r.baseElement.textContent).toEqual(""); | ||
const data = Math.random() >= 0 ? undefined : { firstName: "bob" }; | ||
const form = useFormState_1.useFormState({ | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -86,6 +85,5 @@ // And we pass `ifUndefined` | ||
}); | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "changedValue" }, { children: JSON.stringify(form.changedValue) }), void 0)] }, void 0)); | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: form.firstName.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "changedValue", children: JSON.stringify(form.changedValue) })] })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// Then we use the ifUndefined value | ||
@@ -98,4 +96,4 @@ expect(r.firstName.textContent).toEqual("default"); | ||
function TestComponent() { | ||
const [, setTick] = react_2.useState(0); | ||
const form = useFormState_1.useFormState({ | ||
const [, setTick] = (0, react_2.useState)(0); | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -105,3 +103,3 @@ // That's using a raw init value | ||
}); | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "change", onClick: () => { | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { "data-testid": "change", onClick: () => { | ||
// When that value changes | ||
@@ -111,10 +109,9 @@ form.firstName.set("fred"); | ||
setTick(1); | ||
} }, void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0)] }, void 0)); | ||
} }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: form.firstName.value })] })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
expect(r.firstName()).toHaveTextContent("bob"); | ||
rtl_utils_1.click(r.change); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.firstName).toHaveTextContent("bob"); | ||
(0, rtl_utils_1.click)(r.change); | ||
// Then the change didn't get dropped due to init being unstable | ||
expect(r.firstName()).toHaveTextContent("fred"); | ||
expect(r.firstName).toHaveTextContent("fred"); | ||
}); | ||
@@ -124,6 +121,6 @@ it("doesn't required an init value", async () => { | ||
const config = { firstName: { type: "value" } }; | ||
const form = useFormState_1.useFormState({ config }); | ||
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0); | ||
const form = (0, useFormState_1.useFormState)({ config }); | ||
return (0, jsx_runtime_1.jsx)("div", { children: form.firstName.value }); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.baseElement.textContent).toEqual(""); | ||
@@ -170,4 +167,4 @@ }); | ||
// And we start out with data1 | ||
const [data, setData] = react_2.useState(data1); | ||
const form = useFormState_1.useFormState({ config, init: { input: data, map: (d) => d } }); | ||
const [data, setData] = (0, react_2.useState)(data1); | ||
const form = (0, useFormState_1.useFormState)({ config, init: { input: data, map: (d) => d } }); | ||
function makeLocalChanges() { | ||
@@ -178,26 +175,16 @@ form.firstName.value = "local"; | ||
} | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "lastName" }, { children: form.lastName.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "street" }, { children: form.address.street.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "city" }, { children: form.address.city.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "title1" }, { children: form.books.rows[0].title.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "title2" }, { children: form.books.rows[1].title.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "booksLength" }, { children: form.books.rows.length }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "makeLocalChanges", onClick: makeLocalChanges }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "saveData", onClick: () => setData(data3) }, void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "changedValue" }, { children: JSON.stringify(form.changedValue) }), void 0)] }, void 0)) }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: form.firstName.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "lastName", children: form.lastName.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "street", children: form.address.street.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "city", children: form.address.city.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "title1", children: form.books.rows[0].title.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "title2", children: form.books.rows[1].title.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "booksLength", children: form.books.rows.length }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "makeLocalChanges", onClick: makeLocalChanges }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "refreshData", onClick: () => setData(data2) }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "saveData", onClick: () => setData(data3) }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "changedValue", children: JSON.stringify(form.changedValue) })] })) })); | ||
} | ||
// And we start out with the initial query data | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
expect(r.firstName().textContent).toEqual("f1"); | ||
expect(r.street().textContent).toEqual("s1"); | ||
expect(r.title1().textContent).toEqual("a1"); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.firstName.textContent).toEqual("f1"); | ||
expect(r.street.textContent).toEqual("s1"); | ||
expect(r.title1.textContent).toEqual("a1"); | ||
// When we make some local changes | ||
rtl_utils_1.click(r.makeLocalChanges); | ||
(0, rtl_utils_1.click)(r.makeLocalChanges); | ||
// Then we see them | ||
expect(r.firstName().textContent).toEqual("local"); | ||
expect(r.street().textContent).toEqual("local"); | ||
expect(r.title1().textContent).toEqual("local"); | ||
expect(JSON.parse(r.changedValue().textContent)).toEqual({ | ||
expect(r.firstName.textContent).toEqual("local"); | ||
expect(r.street.textContent).toEqual("local"); | ||
expect(r.title1.textContent).toEqual("local"); | ||
expect(JSON.parse(r.changedValue.textContent)).toEqual({ | ||
id: "a:1", | ||
@@ -209,13 +196,13 @@ address: { id: "address:1", street: "local" }, | ||
// And when the new query is ran i.e. due to a cache refresh | ||
rtl_utils_1.click(r.refreshData); | ||
(0, rtl_utils_1.click)(r.refreshData); | ||
// Then we kept our local changes | ||
expect(r.firstName().textContent).toEqual("local"); | ||
expect(r.street().textContent).toEqual("local"); | ||
expect(r.title1().textContent).toEqual("local"); | ||
expect(r.firstName.textContent).toEqual("local"); | ||
expect(r.street.textContent).toEqual("local"); | ||
expect(r.title1.textContent).toEqual("local"); | ||
// But we also see the new data for fields we have not changed | ||
expect(r.lastName().textContent).toEqual("l2"); | ||
expect(r.city().textContent).toEqual("c2"); | ||
expect(r.title2().textContent).toEqual("b2"); | ||
expect(r.booksLength().textContent).toEqual("3"); | ||
expect(JSON.parse(r.changedValue().textContent)).toEqual({ | ||
expect(r.lastName.textContent).toEqual("l2"); | ||
expect(r.city.textContent).toEqual("c2"); | ||
expect(r.title2.textContent).toEqual("b2"); | ||
expect(r.booksLength.textContent).toEqual("3"); | ||
expect(JSON.parse(r.changedValue.textContent)).toEqual({ | ||
id: "a:1", | ||
@@ -227,5 +214,5 @@ address: { id: "address:1", street: "local" }, | ||
// And then when our mutation results come back | ||
rtl_utils_1.click(r.saveData); | ||
(0, rtl_utils_1.click)(r.saveData); | ||
// Then changedValue doesn't show our local changes anymore | ||
expect(JSON.parse(r.changedValue().textContent)).toEqual({ | ||
expect(JSON.parse(r.changedValue.textContent)).toEqual({ | ||
id: "a:1", | ||
@@ -250,4 +237,4 @@ }); | ||
// And we start out with data1 | ||
const [data, setData] = react_2.useState(data1); | ||
const form = useFormState_1.useFormState({ | ||
const [data, setData] = (0, react_2.useState)(data1); | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -258,18 +245,15 @@ init: { input: data, map: (d) => d }, | ||
}); | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "street" }, { children: form.address.street.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "title1" }, { children: form.books.rows[0].title.value }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0)] }, void 0)) }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: form.firstName.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "street", children: form.address.street.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "title1", children: form.books.rows[0].title.value }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "refreshData", onClick: () => setData(data2) })] })) })); | ||
} | ||
// And we start out with the initial query data | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
expect(r.firstName().textContent).toEqual("f1"); | ||
expect(r.street().textContent).toEqual("s1"); | ||
expect(r.title1().textContent).toEqual("a1"); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.firstName.textContent).toEqual("f1"); | ||
expect(r.street.textContent).toEqual("s1"); | ||
expect(r.title1.textContent).toEqual("a1"); | ||
// When the new query is ran i.e. due to a cache refresh | ||
rtl_utils_1.click(r.refreshData); | ||
(0, rtl_utils_1.click)(r.refreshData); | ||
// Then we see the latest data | ||
expect(r.firstName().textContent).toEqual("f2"); | ||
expect(r.street().textContent).toEqual("s2"); | ||
expect(r.title1().textContent).toEqual("a2"); | ||
expect(r.firstName.textContent).toEqual("f2"); | ||
expect(r.street.textContent).toEqual("s2"); | ||
expect(r.title1.textContent).toEqual("a2"); | ||
}); | ||
@@ -293,5 +277,5 @@ it("useFormState can accept new data with computed fields", async () => { | ||
// And we start out with data1 | ||
const [data, setData] = react_2.useState(data1); | ||
const author = react_2.useMemo(() => new AuthorRow(data.firstName, data.lastName), [data]); | ||
const config = react_2.useMemo(() => ({ | ||
const [data, setData] = (0, react_2.useState)(data1); | ||
const author = (0, react_2.useMemo)(() => new AuthorRow(data.firstName, data.lastName), [data]); | ||
const config = (0, react_2.useMemo)(() => ({ | ||
firstName: { type: "value" }, | ||
@@ -301,15 +285,13 @@ lastName: { type: "value" }, | ||
}), []); | ||
const form = useFormState_1.useFormState({ config, init: { input: author, map: (a) => a } }); | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "fullName" }, { children: form.fullName.value }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0)] }, void 0)) }, void 0)); | ||
const form = (0, useFormState_1.useFormState)({ config, init: { input: author, map: (a) => a } }); | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: form.firstName.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "fullName", children: form.fullName.value }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "refreshData", onClick: () => setData(data2) })] })) })); | ||
} | ||
// And we start out with the initial query data | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
expect(r.firstName().textContent).toEqual("f1"); | ||
expect(r.fullName().textContent).toEqual("f1 l1"); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
expect(r.firstName.textContent).toEqual("f1"); | ||
expect(r.fullName.textContent).toEqual("f1 l1"); | ||
// When the new query is ran i.e. due to a cache refresh | ||
rtl_utils_1.click(r.refreshData); | ||
expect(r.firstName().textContent).toEqual("f2"); | ||
expect(r.fullName().textContent).toEqual("f2 l2"); | ||
(0, rtl_utils_1.click)(r.refreshData); | ||
expect(r.firstName.textContent).toEqual("f2"); | ||
expect(r.fullName.textContent).toEqual("f2 l2"); | ||
}); | ||
@@ -321,5 +303,5 @@ it("can trigger auto save for fields in list that were initially undefined", async () => { | ||
// When the data is initially undefined | ||
const [data, setData] = react_2.useState(); | ||
const [data, setData] = (0, react_2.useState)(); | ||
const data2 = { books: [{ title: "Title 1" }] }; | ||
const form = useFormState_1.useFormState({ | ||
const form = (0, useFormState_1.useFormState)({ | ||
config: authorWithBooksConfig, | ||
@@ -329,17 +311,14 @@ init: { input: data, map: (d) => d, ifUndefined: { books: [] } }, | ||
}); | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "add", onClick: () => form.books.add({ title: "New Book" }) }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "blurBookOne", onClick: () => focusAndBlur(form.books.rows[0].title) }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "blurBookTwo", onClick: () => focusAndBlur(form.books.rows[1].title) }, void 0)] }, void 0)) }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { "data-testid": "refreshData", onClick: () => setData(data2) }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "add", onClick: () => form.books.add({ title: "New Book" }) }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "blurBookOne", onClick: () => focusAndBlur(form.books.rows[0].title) }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "blurBookTwo", onClick: () => focusAndBlur(form.books.rows[1].title) })] })) })); | ||
} | ||
// Given a formState with `onBlur` set | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// When the data/child is now available | ||
rtl_utils_1.click(r.refreshData); | ||
(0, rtl_utils_1.click)(r.refreshData); | ||
// And the field is blurred | ||
await rtl_utils_1.clickAndWait(r.blurBookOne); | ||
await (0, rtl_utils_1.clickAndWait)(r.blurBookOne); | ||
// Then we don't auto-save because nothing has changed | ||
expect(autoSave).toBeCalledTimes(0); | ||
// And when adding a new book | ||
await rtl_utils_1.clickAndWait(r.add); | ||
await (0, rtl_utils_1.clickAndWait)(r.add); | ||
// We autoSave the new row right away (because we don't have any validation rules | ||
@@ -349,3 +328,3 @@ // that say the new row can't be empty) | ||
// And the new book is blurred | ||
await rtl_utils_1.clickAndWait(r.blurBookTwo); | ||
await (0, rtl_utils_1.clickAndWait)(r.blurBookTwo); | ||
// Then we auto save again | ||
@@ -360,3 +339,3 @@ expect(autoSave).toBeCalledTimes(2); | ||
const data = { firstName: "f1", lastName: "f1" }; | ||
const form = useFormState_1.useFormState({ | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -370,7 +349,7 @@ init: data, | ||
}); | ||
return (jsx_runtime_1.jsx("div", { children: jsx_runtime_1.jsx("button", { "data-testid": "setFirst", onClick: () => form.firstName.set("f2") }, void 0) }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("button", { "data-testid": "setFirst", onClick: () => form.firstName.set("f2") }) })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// When we change firstName | ||
await rtl_utils_1.clickAndWait(r.setFirst); | ||
await (0, rtl_utils_1.clickAndWait)(r.setFirst); | ||
// When autoSave didn't infinite loop | ||
@@ -385,3 +364,3 @@ expect(autoSaves).toEqual(1); | ||
const data = { firstName: "f1", lastName: "f1" }; | ||
const form = useFormState_1.useFormState({ | ||
const form = (0, useFormState_1.useFormState)({ | ||
config, | ||
@@ -391,11 +370,11 @@ init: data, | ||
}); | ||
return (jsx_runtime_1.jsx("div", { children: jsx_runtime_1.jsx("button", { "data-testid": "set", onClick: () => { | ||
return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("button", { "data-testid": "set", onClick: () => { | ||
// And it sets two fields in a single callback | ||
form.firstName.set("f2"); | ||
form.lastName.set("l2"); | ||
} }, void 0) }, void 0)); | ||
} }) })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// When we change both | ||
await rtl_utils_1.clickAndWait(r.set); | ||
await (0, rtl_utils_1.clickAndWait)(r.set); | ||
// Then we only call autoSave once | ||
@@ -414,4 +393,4 @@ expect(autoSave).toBeCalledTimes(1); | ||
function TestComponent() { | ||
const [apiData, setApiData] = react_2.useState({ id: "a:1", firstName: "Brandon", lastName: "Dow" }); | ||
const state = useFormState_1.useFormState({ config, autoSave, init: { input: apiData, map: (v) => v } }); | ||
const [apiData, setApiData] = (0, react_2.useState)({ id: "a:1", firstName: "Brandon", lastName: "Dow" }); | ||
const state = (0, useFormState_1.useFormState)({ config, autoSave, init: { input: apiData, map: (v) => v } }); | ||
async function autoSave(form) { | ||
@@ -424,4 +403,3 @@ autoSaveStub(form.changedValue); | ||
} | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsxs("div", Object.assign({ "data-testid": "name" }, { children: [apiData.firstName, " ", apiData.lastName] }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "focusSetAndSaveField", onClick: () => { | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { "data-testid": "name", children: [apiData.firstName, " ", apiData.lastName] }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "focusSetAndSaveField", onClick: () => { | ||
state.firstName.focus(); | ||
@@ -431,24 +409,23 @@ state.firstName.set("Foo"); | ||
state.firstName.blur(); | ||
} }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "focusSetAndSaveFieldLastName", onClick: () => { | ||
} }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "focusSetAndSaveFieldLastName", onClick: () => { | ||
state.lastName.focus(); | ||
state.lastName.set("Bar"); | ||
state.lastName.maybeAutoSave(); | ||
} }, void 0)] }, void 0)); | ||
} })] })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// And triggering the auto save behavior before awaiting the initial promise to | ||
// resolve so we have pending changes. | ||
rtl_utils_1.click(r.focusSetAndSaveField(), { allowAsync: true }); | ||
(0, rtl_utils_1.click)(r.focusSetAndSaveField, { allowAsync: true }); | ||
// Let the initial autoSave be called | ||
react_1.act(() => { | ||
(0, react_1.act)(() => { | ||
jest.runOnlyPendingTimers(); | ||
}); | ||
expect(r.name()).toHaveTextContent("Brandon Dow"); | ||
expect(r.name).toHaveTextContent("Brandon Dow"); | ||
expect(autoSaveStub).toBeCalledTimes(1); | ||
expect(autoSaveStub).toBeCalledWith({ id: "a:1", firstName: "Foo" }); | ||
// And while that is in flight, trigger another user action | ||
rtl_utils_1.click(r.focusSetAndSaveFieldLastName(), { allowAsync: true }); | ||
(0, rtl_utils_1.click)(r.focusSetAndSaveFieldLastName, { allowAsync: true }); | ||
// (Use `wait` so that our timer flushes before the Promise.resolve(1) is ran) | ||
await rtl_utils_1.wait(); | ||
await (0, rtl_utils_1.wait)(); | ||
// Then expect the auto save to only have been called twice. Once with each changedValues. | ||
@@ -468,7 +445,7 @@ expect(autoSaveStub).toBeCalledTimes(2); | ||
function TestComponent() { | ||
const fs = useFormState_1.useFormState({ | ||
const fs = (0, useFormState_1.useFormState)({ | ||
config, | ||
addRules(fs) { | ||
// And also has calculated values | ||
mobx_1.reaction(() => fs.firstName.value, (curr) => (fs.lastName.value = curr)); | ||
(0, mobx_1.reaction)(() => fs.firstName.value, (curr) => (fs.lastName.value = curr)); | ||
}, | ||
@@ -478,7 +455,7 @@ autoSave: (fs) => autoSaveStub(fs.changedValue), | ||
}); | ||
return jsx_runtime_1.jsx(FormStateApp_1.TextField, { field: fs.firstName }, void 0); | ||
return (0, jsx_runtime_1.jsx)(FormStateApp_1.TextField, { field: fs.firstName }); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// When the user sets one field | ||
await rtl_utils_1.typeAndWait(r.firstName, "first"); | ||
await (0, rtl_utils_1.typeAndWait)(r.firstName, "first"); | ||
// Then we only called autoSave once | ||
@@ -491,13 +468,13 @@ expect(autoSaveStub).toBeCalledTimes(1); | ||
function TestComponent({ loading }) { | ||
const form = useFormState_1.useFormState({ config, loading }); | ||
return jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "loading" }, { children: String(form.loading) }), void 0) }, void 0); | ||
const form = (0, useFormState_1.useFormState)({ config, loading }); | ||
return (0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => (0, jsx_runtime_1.jsx)("div", { "data-testid": "loading", children: String(form.loading) }) }); | ||
} | ||
// And we initially pass in `init.query.loading: true` | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { loading: true }, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { loading: true })); | ||
// Then the form is marked as loading | ||
expect(r.loading()).toHaveTextContent("true"); | ||
expect(r.loading).toHaveTextContent("true"); | ||
// And when the query is not loading | ||
await r.rerender(jsx_runtime_1.jsx(TestComponent, { loading: false }, void 0)); | ||
await r.rerender((0, jsx_runtime_1.jsx)(TestComponent, { loading: false })); | ||
// Then the form is marked as not loading | ||
expect(r.loading()).toHaveTextContent("false"); | ||
expect(r.loading).toHaveTextContent("false"); | ||
}); | ||
@@ -507,13 +484,13 @@ it("sets loading if input.data is undefined", async () => { | ||
function TestComponent({ data }) { | ||
const form = useFormState_1.useFormState({ config, init: { input: data, map: (d) => d } }); | ||
return jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "loading" }, { children: String(form.loading) }), void 0) }, void 0); | ||
const form = (0, useFormState_1.useFormState)({ config, init: { input: data, map: (d) => d } }); | ||
return (0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => (0, jsx_runtime_1.jsx)("div", { "data-testid": "loading", children: String(form.loading) }) }); | ||
} | ||
// And we initially pass in `init.input: undefined` | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { data: undefined }, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { data: undefined })); | ||
// Then the form is marked as loading | ||
expect(r.loading()).toHaveTextContent("true"); | ||
expect(r.loading).toHaveTextContent("true"); | ||
// And when the data is no longer undefined | ||
await r.rerender(jsx_runtime_1.jsx(TestComponent, { data: { firstName: "first" } }, void 0)); | ||
await r.rerender((0, jsx_runtime_1.jsx)(TestComponent, { data: { firstName: "first" } })); | ||
// Then the form is marked as not loading | ||
expect(r.loading()).toHaveTextContent("false"); | ||
expect(r.loading).toHaveTextContent("false"); | ||
}); | ||
@@ -523,13 +500,13 @@ it("sets loading if query.loading is true", async () => { | ||
function TestComponent({ loading, data }) { | ||
const form = useFormState_1.useFormState({ config, init: { query: { data, loading, error: null }, map: (d) => d } }); | ||
return jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "loading" }, { children: String(form.loading) }), void 0) }, void 0); | ||
const form = (0, useFormState_1.useFormState)({ config, init: { query: { data, loading, error: null }, map: (d) => d } }); | ||
return (0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => (0, jsx_runtime_1.jsx)("div", { "data-testid": "loading", children: String(form.loading) }) }); | ||
} | ||
// And we initially pass in `init.query.loading: true` | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { loading: true, data: undefined }, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { loading: true, data: undefined })); | ||
// Then the form is marked as loading | ||
expect(r.loading()).toHaveTextContent("true"); | ||
expect(r.loading).toHaveTextContent("true"); | ||
// And when the query is not loading | ||
await r.rerender(jsx_runtime_1.jsx(TestComponent, { loading: false, data: { firstName: "first" } }, void 0)); | ||
await r.rerender((0, jsx_runtime_1.jsx)(TestComponent, { loading: false, data: { firstName: "first" } })); | ||
// Then the form is marked as not loading | ||
expect(r.loading()).toHaveTextContent("false"); | ||
expect(r.loading).toHaveTextContent("false"); | ||
}); | ||
@@ -540,32 +517,27 @@ it("treats the id changing as a whole new entity instead of a delete", async () => { | ||
// Given an initial author a1 | ||
const [author, setAuthor] = react_2.useState({ id: "a:1", firstName: "a1" }); | ||
const form = useFormState_1.useFormState({ config, init: { input: author, map: (a) => a } }); | ||
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "value" }, { children: String(form.firstName.value) }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "dirty" }, { children: String(form.firstName.dirty) }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "originalValue" }, { children: String(form.firstName.originalValue) }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "objectValue" }, { children: String(form.value.firstName) }), void 0), | ||
jsx_runtime_1.jsx("div", { "data-testid": "a1", onClick: () => setAuthor({ id: "a:1", firstName: "a1" }) }, void 0), | ||
jsx_runtime_1.jsx("div", { "data-testid": "a2", onClick: () => setAuthor({ id: "a:2", firstName: undefined }) }, void 0)] }, void 0)) }, void 0)); | ||
const [author, setAuthor] = (0, react_2.useState)({ id: "a:1", firstName: "a1" }); | ||
const form = (0, useFormState_1.useFormState)({ config, init: { input: author, map: (a) => a } }); | ||
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "value", children: String(form.firstName.value) }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "dirty", children: String(form.firstName.dirty) }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "originalValue", children: String(form.firstName.originalValue) }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "objectValue", children: String(form.value.firstName) }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "a1", onClick: () => setAuthor({ id: "a:1", firstName: "a1" }) }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "a2", onClick: () => setAuthor({ id: "a:2", firstName: undefined }) })] })) })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// And the value is initially a1/and not dirty | ||
expect(r.value()).toHaveTextContent("a1"); | ||
expect(r.dirty()).toHaveTextContent("false"); | ||
expect(r.originalValue()).toHaveTextContent("a1"); | ||
expect(r.objectValue()).toHaveTextContent("a1"); | ||
expect(r.value).toHaveTextContent("a1"); | ||
expect(r.dirty).toHaveTextContent("false"); | ||
expect(r.originalValue).toHaveTextContent("a1"); | ||
expect(r.objectValue).toHaveTextContent("a1"); | ||
// When we switch to a completely separate author | ||
rtl_utils_1.click(r.a2); | ||
(0, rtl_utils_1.click)(r.a2); | ||
// Then it switches to the next author | ||
expect(r.value()).toHaveTextContent("undefined"); | ||
expect(r.value).toHaveTextContent("undefined"); | ||
// And the field is not dirty (which had been the case before this bug fix) | ||
expect(r.dirty()).toHaveTextContent("false"); | ||
expect(r.originalValue()).toHaveTextContent("undefined"); | ||
expect(r.objectValue()).toHaveTextContent("undefined"); | ||
expect(r.dirty).toHaveTextContent("false"); | ||
expect(r.originalValue).toHaveTextContent("undefined"); | ||
expect(r.objectValue).toHaveTextContent("undefined"); | ||
// And when we switch back to the original author | ||
rtl_utils_1.click(r.a1); | ||
(0, rtl_utils_1.click)(r.a1); | ||
// It again restores the value and does not think an edit was WIP | ||
expect(r.value()).toHaveTextContent("a1"); | ||
expect(r.dirty()).toHaveTextContent("false"); | ||
expect(r.originalValue()).toHaveTextContent("a1"); | ||
expect(r.objectValue()).toHaveTextContent("a1"); | ||
expect(r.value).toHaveTextContent("a1"); | ||
expect(r.dirty).toHaveTextContent("false"); | ||
expect(r.originalValue).toHaveTextContent("a1"); | ||
expect(r.objectValue).toHaveTextContent("a1"); | ||
}); | ||
@@ -572,0 +544,0 @@ }); |
import { ObjectConfig } from "./config"; | ||
import { ObjectState } from "./fields/objectField"; | ||
export declare type ObjectStateCache<T, I> = Record<string, [ObjectState<T>, I]>; | ||
declare type UseFormStatesOpts<T, I> = { | ||
export type ObjectStateCache<T, I> = Record<string, [ObjectState<T>, I]>; | ||
type UseFormStatesOpts<T, I> = { | ||
/** | ||
@@ -43,3 +43,3 @@ * The config to use for each form state. | ||
}; | ||
declare type UseFormStatesHook<T, I> = { | ||
type UseFormStatesHook<T, I> = { | ||
getFormState: (input: I, opts?: { | ||
@@ -46,0 +46,0 @@ readOnly?: boolean; |
@@ -9,3 +9,3 @@ "use strict"; | ||
const { config, autoSave, getId, map, addRules, readOnly = false } = opts; | ||
const objectStateCache = react_1.useMemo(() => ({}), | ||
const objectStateCache = (0, react_1.useMemo)(() => ({}), | ||
// Force resetting the cache if config changes | ||
@@ -15,10 +15,10 @@ // eslint-disable-next-line react-hooks/exhaustive-deps | ||
// Keep track of ObjectStates that triggered auto-save when a save was already in progress. | ||
const pendingAutoSaves = react_1.useRef(new Set()); | ||
const pendingAutoSaves = (0, react_1.useRef)(new Set()); | ||
// Use a ref so our memo'ized `autoSave` always see the latest value | ||
const autoSaveRef = react_1.useRef(autoSave); | ||
const autoSaveRef = (0, react_1.useRef)(autoSave); | ||
autoSaveRef.current = autoSave; | ||
// Use a ref b/c we're memod | ||
const readOnlyRef = react_1.useRef(readOnly); | ||
const readOnlyRef = (0, react_1.useRef)(readOnly); | ||
readOnlyRef.current = readOnly; | ||
const getFormState = react_1.useCallback((input, opts = {}) => { | ||
const getFormState = (0, react_1.useCallback)((input, opts = {}) => { | ||
const existing = objectStateCache[getId(input)]; | ||
@@ -59,3 +59,3 @@ let form = existing === null || existing === void 0 ? void 0 : existing[0]; | ||
if (!form) { | ||
form = objectField_1.createObjectState(config, utils_1.initValue(config, map ? { map, input } : input), { | ||
form = (0, objectField_1.createObjectState)(config, (0, utils_1.initValue)(config, map ? { map, input } : input), { | ||
maybeAutoSave: () => maybeAutoSave(form), | ||
@@ -70,3 +70,3 @@ }); | ||
if (existing && existing[1] !== input) { | ||
form.set(utils_1.initValue(config, map ? { map, input } : input), { refreshing: true }); | ||
form.set((0, utils_1.initValue)(config, map ? { map, input } : input), { refreshing: true }); | ||
existing[1] = input; | ||
@@ -73,0 +73,0 @@ } |
@@ -15,7 +15,7 @@ "use strict"; | ||
function ChildComponent({ os }) { | ||
return jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: os.firstName.value }), void 0); | ||
return (0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: os.firstName.value }); | ||
} | ||
function TestComponent() { | ||
const config = { id: { type: "value" }, firstName: { type: "value" } }; | ||
const { getFormState } = useFormStates_1.useFormStates({ | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ | ||
config, | ||
@@ -25,7 +25,7 @@ autoSave, | ||
}); | ||
return (jsx_runtime_1.jsx("div", { children: jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }, void 0) }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }) })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// And the child component has defined state | ||
expect(r.firstName()).toHaveTextContent("Brandon"); | ||
expect(r.firstName).toHaveTextContent("Brandon"); | ||
}); | ||
@@ -37,21 +37,19 @@ it("can update existing object state from cache with new values", async () => { | ||
function TestComponent() { | ||
const [apiData, setApiData] = react_1.useState({ id: "a:1", firstName: "Brandon" }); | ||
const { getFormState } = useFormStates_1.useFormStates({ config, autoSave, getId: (o) => o.id }); | ||
const [apiData, setApiData] = (0, react_1.useState)({ id: "a:1", firstName: "Brandon" }); | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ config, autoSave, getId: (o) => o.id }); | ||
// Memoize an original for comparing the update against. | ||
const originalState = react_1.useMemo(() => getFormState(apiData), [getFormState]); | ||
const originalState = (0, react_1.useMemo)(() => getFormState(apiData), [getFormState]); | ||
const state = getFormState(apiData); | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: state.firstName.value }), void 0), | ||
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "statesEqual" }, { children: JSON.stringify(state === originalState) }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "updateApiData", onClick: () => setApiData((prevState) => ({ ...prevState, firstName: "Bob" })) }, void 0)] }, void 0)); | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: state.firstName.value }), (0, jsx_runtime_1.jsx)("div", { "data-testid": "statesEqual", children: JSON.stringify(state === originalState) }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "updateApiData", onClick: () => setApiData((prevState) => ({ ...prevState, firstName: "Bob" })) })] })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// And the initial values for the form state display | ||
expect(r.firstName()).toHaveTextContent("Brandon"); | ||
expect(r.statesEqual()).toHaveTextContent("true"); | ||
expect(r.firstName).toHaveTextContent("Brandon"); | ||
expect(r.statesEqual).toHaveTextContent("true"); | ||
// When updating the API data | ||
await rtl_utils_1.clickAndWait(r.updateApiData()); | ||
await (0, rtl_utils_1.clickAndWait)(r.updateApiData); | ||
// Then the new value is shown in the component | ||
expect(r.firstName()).toHaveTextContent("Bob"); | ||
expect(r.firstName).toHaveTextContent("Bob"); | ||
// And the two states are using the same reference | ||
expect(r.statesEqual()).toHaveTextContent("true"); | ||
expect(r.statesEqual).toHaveTextContent("true"); | ||
}); | ||
@@ -67,5 +65,5 @@ it("can queue up changes for auto save if a save is already in progress - works across multiple states", async () => { | ||
function TestComponent() { | ||
const [apiData, setApiData] = react_1.useState({ id: "a:1", firstName: "Tony", lastName: "Stark" }); | ||
const [apiData2, setApiData2] = react_1.useState({ id: "a:2", firstName: "Steve", lastName: "Rogers" }); | ||
const { getFormState } = useFormStates_1.useFormStates({ config, autoSave, getId: (o) => o.id }); | ||
const [apiData, setApiData] = (0, react_1.useState)({ id: "a:1", firstName: "Tony", lastName: "Stark" }); | ||
const [apiData2, setApiData2] = (0, react_1.useState)({ id: "a:2", firstName: "Steve", lastName: "Rogers" }); | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ config, autoSave, getId: (o) => o.id }); | ||
const state = getFormState(apiData); | ||
@@ -84,26 +82,23 @@ const state2 = getFormState(apiData2); | ||
} | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: state.firstName.value }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "focusSetAndSaveField", onClick: () => { | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", children: state.firstName.value }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "focusSetAndSaveField", onClick: () => { | ||
state.firstName.focus(); | ||
state.firstName.set("Foo"); | ||
state.firstName.maybeAutoSave(); | ||
} }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "focusSetAndSaveFieldLastName", onClick: () => { | ||
} }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "focusSetAndSaveFieldLastName", onClick: () => { | ||
state.lastName.focus(); | ||
state.lastName.set("Bar"); | ||
state.lastName.maybeAutoSave(); | ||
} }, void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "focusSetAndSaveField2", onClick: () => { | ||
} }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "focusSetAndSaveField2", onClick: () => { | ||
state2.lastName.focus(); | ||
state2.lastName.set("Bar"); | ||
state2.lastName.maybeAutoSave(); | ||
} }, void 0)] }, void 0)); | ||
} })] })); | ||
} | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// And triggering the auto save behavior before awaiting the initial promise to resolve so we have pending changes. | ||
rtl_utils_1.click(r.focusSetAndSaveField()); | ||
rtl_utils_1.click(r.focusSetAndSaveFieldLastName()); | ||
rtl_utils_1.click(r.focusSetAndSaveField2()); | ||
(0, rtl_utils_1.click)(r.focusSetAndSaveField); | ||
(0, rtl_utils_1.click)(r.focusSetAndSaveFieldLastName); | ||
(0, rtl_utils_1.click)(r.focusSetAndSaveField2); | ||
// Awaits the promises for all methods triggered above | ||
await rtl_utils_1.wait(); | ||
await (0, rtl_utils_1.wait)(); | ||
// Then expect the auto save to only have been called two times. Once with each set of changedValues. | ||
@@ -125,19 +120,18 @@ expect(autoSaveStub).toBeCalledTimes(2); | ||
function TestComponent() { | ||
const [config, setConfig] = react_1.useState(originalConfig); | ||
const { getFormState } = useFormStates_1.useFormStates({ config, autoSave, getId: (o) => o.id }); | ||
const [config, setConfig] = (0, react_1.useState)(originalConfig); | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ config, autoSave, getId: (o) => o.id }); | ||
// Memoize an original for comparing the update against. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
const originalState = react_1.useMemo(() => getFormState(apiData), []); | ||
const originalState = (0, react_1.useMemo)(() => getFormState(apiData), []); | ||
const state = getFormState(apiData); | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "statesEqual" }, { children: JSON.stringify(state === originalState) }), void 0), | ||
jsx_runtime_1.jsx("button", { "data-testid": "updateConfig", onClick: () => setConfig(updatedConfig) }, void 0)] }, void 0)); | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { "data-testid": "statesEqual", children: JSON.stringify(state === originalState) }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "updateConfig", onClick: () => setConfig(updatedConfig) })] })); | ||
} | ||
// When rendered with the original configuration | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// Then the two form-states generated are equal | ||
expect(r.statesEqual()).toHaveTextContent("true"); | ||
expect(r.statesEqual).toHaveTextContent("true"); | ||
// When updating the configuration object | ||
rtl_utils_1.click(r.updateConfig); | ||
(0, rtl_utils_1.click)(r.updateConfig); | ||
// Then the two form-states are no longer equal. | ||
expect(r.statesEqual()).toHaveTextContent("false"); | ||
expect(r.statesEqual).toHaveTextContent("false"); | ||
}); | ||
@@ -149,3 +143,3 @@ it("calls addRules once per form state", async () => { | ||
const config = { id: { type: "value" }, firstName: { type: "value" } }; | ||
const { getFormState } = useFormStates_1.useFormStates({ | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ | ||
config, | ||
@@ -155,7 +149,6 @@ addRules, | ||
}); | ||
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }, void 0), | ||
jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }, void 0)] }, void 0)); | ||
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }), (0, jsx_runtime_1.jsx)(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) })] })); | ||
} | ||
// When we render | ||
await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// Then addRules was only called once | ||
@@ -168,3 +161,3 @@ expect(addRules).toHaveBeenCalledTimes(1); | ||
function TestComponent() { | ||
const { getFormState } = useFormStates_1.useFormStates({ | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ | ||
config, | ||
@@ -174,3 +167,3 @@ getId: (o) => o.id, | ||
// And they have a reactive true that calculates last name | ||
mobx_1.reaction(() => fs.firstName.value, (curr) => { | ||
(0, mobx_1.reaction)(() => fs.firstName.value, (curr) => { | ||
fs.lastName.set(curr); | ||
@@ -183,8 +176,8 @@ }); | ||
}); | ||
return jsx_runtime_1.jsx(FormStateApp_1.TextField, { field: getFormState({ id: "a:1", firstName: "Brandon" }).firstName }, void 0); | ||
return (0, jsx_runtime_1.jsx)(FormStateApp_1.TextField, { field: getFormState({ id: "a:1", firstName: "Brandon" }).firstName }); | ||
} | ||
// When we render | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {})); | ||
// And update the firstName | ||
await rtl_utils_1.typeAndWait(r.firstName(), "first"); | ||
await (0, rtl_utils_1.typeAndWait)(r.firstName, "first"); | ||
// Then autoSave was called once with both input+calc'd values | ||
@@ -197,3 +190,3 @@ expect(autoSave).toHaveBeenCalledTimes(1); | ||
function TestComponent({ readOnly }) { | ||
const { getFormState } = useFormStates_1.useFormStates({ | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ | ||
config, | ||
@@ -204,12 +197,12 @@ getId: (o) => o.id, | ||
}); | ||
return jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }, void 0); | ||
return (0, jsx_runtime_1.jsx)(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }); | ||
} | ||
// When we render | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { readOnly: true }, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { readOnly: true })); | ||
// Then it's read only | ||
expect(r.firstName()).toHaveAttribute("data-readonly", "true"); | ||
expect(r.firstName).toHaveAttribute("data-readonly", "true"); | ||
// And when we rerender | ||
await r.rerender(jsx_runtime_1.jsx(TestComponent, { readOnly: false }, void 0)); | ||
await r.rerender((0, jsx_runtime_1.jsx)(TestComponent, { readOnly: false })); | ||
// Then it's not read only | ||
expect(r.firstName()).toHaveAttribute("data-readonly", "false"); | ||
expect(r.firstName).toHaveAttribute("data-readonly", "false"); | ||
}); | ||
@@ -219,3 +212,3 @@ it("can set readOnly via the getFormState function", async () => { | ||
function TestComponent({ readOnly }) { | ||
const { getFormState } = useFormStates_1.useFormStates({ | ||
const { getFormState } = (0, useFormStates_1.useFormStates)({ | ||
config, | ||
@@ -225,12 +218,12 @@ getId: (o) => o.id, | ||
// And it passes readOnly directly to getFormState | ||
return jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }, { readOnly }) }, void 0); | ||
return (0, jsx_runtime_1.jsx)(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }, { readOnly }) }); | ||
} | ||
// When we render | ||
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { readOnly: true }, void 0)); | ||
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { readOnly: true })); | ||
// Then it's read only | ||
expect(r.firstName()).toHaveAttribute("data-readonly", "true"); | ||
expect(r.firstName).toHaveAttribute("data-readonly", "true"); | ||
// And when we rerender | ||
await r.rerender(jsx_runtime_1.jsx(TestComponent, { readOnly: false }, void 0)); | ||
await r.rerender((0, jsx_runtime_1.jsx)(TestComponent, { readOnly: false })); | ||
// Then it's not read only | ||
expect(r.firstName()).toHaveAttribute("data-readonly", "false"); | ||
expect(r.firstName).toHaveAttribute("data-readonly", "false"); | ||
}); | ||
@@ -244,3 +237,3 @@ }); | ||
function ChildComponent({ os }) { | ||
return (jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName", "data-readonly": os.firstName.readOnly }, { children: os.firstName.value }), void 0)); | ||
return ((0, jsx_runtime_1.jsx)("div", { "data-testid": "firstName", "data-readonly": os.firstName.readOnly, children: os.firstName.value })); | ||
} |
import { ObjectConfig } from "./config"; | ||
import { InputAndMap, QueryAndMap, UseFormStateOpts } from "./useFormState"; | ||
export declare type Builtin = Date | Function | Uint8Array | string | number | boolean; | ||
export declare type Primitive = undefined | null | boolean | string | number | Function | Date | { | ||
export type Builtin = Date | Function | Uint8Array | string | number | boolean; | ||
export type Primitive = undefined | null | boolean | string | number | Function | Date | { | ||
toJSON(): any; | ||
}; | ||
/** Makes the keys in `T` required while keeping the values undefined. */ | ||
export declare type DeepRequired<T> = T extends Primitive ? T : { | ||
export type DeepRequired<T> = T extends Primitive ? T : { | ||
[P in keyof Required<T>]: T[P] extends Array<infer U> ? Array<DeepRequired<U>> : T[P] extends ReadonlyArray<infer U2> ? ReadonlyArray<DeepRequired<U2>> : DeepRequired<T[P]>; | ||
@@ -10,0 +10,0 @@ }; |
@@ -34,7 +34,7 @@ "use strict"; | ||
function isInput(init) { | ||
return !!init && "input" in init && "map" in init; | ||
return !!init && typeof init === "object" && "input" in init && "map" in init; | ||
} | ||
exports.isInput = isInput; | ||
function isQuery(init) { | ||
return !!init && "query" in init && "map" in init; | ||
return !!init && typeof init === "object" && "query" in init && "map" in init; | ||
} | ||
@@ -53,3 +53,3 @@ exports.isQuery = isQuery; | ||
// If the caller is using classes, i.e. with their own custom observable behavior, then just use as-is | ||
if (!is_plain_object_1.isPlainObject(instance)) { | ||
if (!(0, is_plain_object_1.isPlainObject)(instance)) { | ||
return instance; | ||
@@ -69,3 +69,3 @@ } | ||
else if (keyConfig.type === "list") { | ||
if (mobx_1.isObservable(value)) { | ||
if ((0, mobx_1.isObservable)(value)) { | ||
// If we hit an observable array, leave it as the existing proxy so the our | ||
@@ -109,4 +109,4 @@ // ListFieldState will react to changes in the original array. | ||
function areEqual(a, b, strictOrder) { | ||
if (is_plain_object_1.isPlainObject(a)) { | ||
return fast_deep_equal_1.default(mobx_1.toJS(a), mobx_1.toJS(b)); | ||
if ((0, is_plain_object_1.isPlainObject)(a)) { | ||
return (0, fast_deep_equal_1.default)((0, mobx_1.toJS)(a), (0, mobx_1.toJS)(b)); | ||
} | ||
@@ -116,7 +116,7 @@ if (hasToJSON(a) || hasToJSON(b)) { | ||
const b1 = hasToJSON(b) ? b.toJSON() : b; | ||
return fast_deep_equal_1.default(a1, b1); | ||
return (0, fast_deep_equal_1.default)(a1, b1); | ||
} | ||
if (a && b && a instanceof Array && b instanceof Array) { | ||
if (strictOrder !== false) { | ||
return fast_deep_equal_1.default(a, b); | ||
return (0, fast_deep_equal_1.default)(a, b); | ||
} | ||
@@ -123,0 +123,0 @@ if (a.length !== b.length) |
@@ -9,68 +9,68 @@ "use strict"; | ||
it("can pick a value field", () => { | ||
const a = utils_1.pickFields( | ||
const a = (0, utils_1.pickFields)( | ||
// | ||
{ firstName: { type: "value" } }, { firstName: "a", b: "ignored" }); | ||
expect(a).toMatchInlineSnapshot(` | ||
Object { | ||
"firstName": "a", | ||
} | ||
`); | ||
{ | ||
"firstName": "a", | ||
} | ||
`); | ||
}); | ||
it("can pick an unset object fields", () => { | ||
const a = utils_1.pickFields(authorWithAddressConfig, { firstName: "a", b: "ignored" }); | ||
const a = (0, utils_1.pickFields)(authorWithAddressConfig, { firstName: "a", b: "ignored" }); | ||
expect(a).toMatchInlineSnapshot(` | ||
Object { | ||
"address": undefined, | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
{ | ||
"address": undefined, | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
}); | ||
it("can pick a set object field", () => { | ||
const a = utils_1.pickFields(authorWithAddressConfig, { firstName: "a", b: "ignored", address: {} }); | ||
const a = (0, utils_1.pickFields)(authorWithAddressConfig, { firstName: "a", b: "ignored", address: {} }); | ||
expect(a).toMatchInlineSnapshot(` | ||
Object { | ||
"address": Object { | ||
"city": undefined, | ||
"street": undefined, | ||
}, | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
{ | ||
"address": { | ||
"city": undefined, | ||
"street": undefined, | ||
}, | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
}); | ||
it("can pick an unset list field", () => { | ||
const a = utils_1.pickFields(authorWithBooksConfig, { firstName: "a", b: "ignored" }); | ||
const a = (0, utils_1.pickFields)(authorWithBooksConfig, { firstName: "a", b: "ignored" }); | ||
expect(a).toMatchInlineSnapshot(` | ||
Object { | ||
"books": undefined, | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
{ | ||
"books": undefined, | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
}); | ||
it("can pick a set list field", () => { | ||
const a = utils_1.pickFields(authorWithBooksConfig, { firstName: "a", b: "ignored", books: [{}] }); | ||
const a = (0, utils_1.pickFields)(authorWithBooksConfig, { firstName: "a", b: "ignored", books: [{}] }); | ||
expect(a).toMatchInlineSnapshot(` | ||
Object { | ||
"books": Array [ | ||
Object { | ||
"classification": undefined, | ||
"id": undefined, | ||
"title": undefined, | ||
}, | ||
], | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
{ | ||
"books": [ | ||
{ | ||
"classification": undefined, | ||
"id": undefined, | ||
"title": undefined, | ||
}, | ||
], | ||
"firstName": "a", | ||
"id": undefined, | ||
"lastName": undefined, | ||
} | ||
`); | ||
}); | ||
it("can pick a set observable list field", () => { | ||
const books = mobx_1.observable([]); | ||
const a = utils_1.pickFields(authorWithBooksConfig, { firstName: "a", b: "ignored", books }); | ||
expect(mobx_1.isObservable(a.books)).toEqual(true); | ||
const books = (0, mobx_1.observable)([]); | ||
const a = (0, utils_1.pickFields)(authorWithBooksConfig, { firstName: "a", b: "ignored", books }); | ||
expect((0, mobx_1.isObservable)(a.books)).toEqual(true); | ||
}); | ||
@@ -77,0 +77,0 @@ }); |
{ | ||
"name": "@homebound/form-state", | ||
"version": "2.19.0", | ||
"version": "2.20.0", | ||
"author": "Homebound", | ||
@@ -16,7 +16,7 @@ "license": "MIT", | ||
"scripts": { | ||
"build": "ttsc", | ||
"build:storybook": "build-storybook", | ||
"build": "tspc", | ||
"build:storybook": "storybook build", | ||
"test": "jest", | ||
"lint": "eslint . --cache --fix --ext .js,.ts,.tsx", | ||
"storybook": "start-storybook -p 9000", | ||
"storybook": "storybook dev -p 9000", | ||
"format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,md}\"", | ||
@@ -36,4 +36,4 @@ "semantic-release": "semantic-release" | ||
"is-plain-object": "^5.0.0", | ||
"mobx": "^6.3.2", | ||
"mobx-react": "^7.2.0" | ||
"mobx": "^6.10.2", | ||
"mobx-react": "^9.0.1" | ||
}, | ||
@@ -45,18 +45,20 @@ "peerDependencies": { | ||
"@babel/core": "^7.13.1", | ||
"@homebound/rtl-utils": "^2.59.2", | ||
"@semantic-release/exec": "^5.0.0", | ||
"@semantic-release/git": "^9.0.0", | ||
"@storybook/addon-essentials": "^6.2.9", | ||
"@storybook/addon-info": "^5.3.21", | ||
"@storybook/addon-links": "^6.2.9", | ||
"@storybook/addons": "^6.2.9", | ||
"@storybook/react": "^6.2.9", | ||
"@testing-library/dom": "^8.11.3", | ||
"@testing-library/jest-dom": "^5.16.2", | ||
"@testing-library/react": "^12.1.2", | ||
"@testing-library/react-hooks": "^8.0.0", | ||
"@tsconfig/recommended": "^1.0.1", | ||
"@types/jest": "^26.0.20", | ||
"@types/react": "^16.14.5", | ||
"@types/react-dom": "^16.9.11", | ||
"@babel/preset-env": "^7.22.15", | ||
"@babel/preset-react": "^7.22.15", | ||
"@babel/preset-typescript": "^7.22.15", | ||
"@homebound/rtl-utils": "2.65.0", | ||
"@semantic-release/exec": "^6.0.3", | ||
"@semantic-release/git": "^10.0.1", | ||
"@storybook/addon-essentials": "^7.4.0", | ||
"@storybook/addon-links": "^7.4.0", | ||
"@storybook/addons": "^7.4.0", | ||
"@storybook/react": "^7.4.0", | ||
"@storybook/react-webpack5": "^7.4.0", | ||
"@testing-library/dom": "^9.3.1", | ||
"@testing-library/jest-dom": "^6.1.3", | ||
"@testing-library/react": "^14.0.0", | ||
"@tsconfig/recommended": "^1.0.2", | ||
"@types/jest": "^29.5.4", | ||
"@types/react": "^18.2.21", | ||
"@types/react-dom": "^18.2.7", | ||
"@typescript-eslint/eslint-plugin": "^4.17.0", | ||
@@ -75,19 +77,22 @@ "@typescript-eslint/parser": "^4.17.0", | ||
"eslint-plugin-react-hooks": "^4.2.0", | ||
"husky": "^3.0.9", | ||
"jest": "^27.0.2", | ||
"lint-staged": "^10.1.2", | ||
"mobx": "^6.3.2", | ||
"mobx-react": "^7.2.0", | ||
"prettier": "^2.2.1", | ||
"prettier-plugin-organize-imports": "^1.1.1", | ||
"react": "^16.14.0", | ||
"react-dom": "^16.14.0", | ||
"semantic-release": "^17.4.0", | ||
"ts-jest": "^27.0.1", | ||
"ts-node": "^9.1.1", | ||
"tslib": "^2.1.0", | ||
"ttypescript": "^1.5.12", | ||
"typescript": "^4.3.2", | ||
"typescript-transform-paths": "^2.0.1" | ||
} | ||
"husky": "^3.1.0", | ||
"jest": "^29.6.4", | ||
"jest-environment-jsdom": "^29.6.4", | ||
"lint-staged": "^14.0.1", | ||
"mobx": "^6.10.2", | ||
"mobx-react": "^9.0.1", | ||
"prettier": "2.8.8", | ||
"prettier-plugin-organize-imports": "^3.2.3", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"semantic-release": "^21.1.1", | ||
"storybook": "^7.4.0", | ||
"ts-jest": "^29.1.1", | ||
"ts-node": "^10.9.1", | ||
"ts-patch": "^3.0.2", | ||
"tslib": "^2.6.2", | ||
"typescript": "^5.2.2", | ||
"typescript-transform-paths": "^3.4.6" | ||
}, | ||
"packageManager": "yarn@3.6.3" | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
200616
43
4082
50
+ Addedmobx-react@9.1.1(transitive)
+ Addedmobx-react-lite@4.0.7(transitive)
+ Addeduse-sync-external-store@1.2.2(transitive)
- Removedmobx-react@7.6.0(transitive)
- Removedmobx-react-lite@3.4.3(transitive)
Updatedmobx@^6.10.2
Updatedmobx-react@^9.0.1