@atlaskit/form 9.0.12 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/form
2
2
 
3
+ ## 9.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#83175](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/83175) [`03e4aaa5a468`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/03e4aaa5a468) - Adds ability to subscribe to form state using the useFormState hook. This can be helpful in situations such as forms with conditional fields, or for previewing a form response. [Read the docs here.](https://atlassian.design/components/form/examples#listening-to-form-state-with-useformstate)
8
+
3
9
  ## 9.0.12
4
10
 
5
11
  ### Patch Changes
package/dist/cjs/form.js CHANGED
@@ -25,6 +25,9 @@ var FormContext = exports.FormContext = /*#__PURE__*/(0, _react.createContext)({
25
25
  },
26
26
  getCurrentValue: function getCurrentValue() {
27
27
  return undefined;
28
+ },
29
+ subscribe: function subscribe() {
30
+ return function () {};
28
31
  }
29
32
  });
30
33
 
@@ -134,9 +137,10 @@ function Form(props) {
134
137
  var FormContextValue = (0, _react.useMemo)(function () {
135
138
  return {
136
139
  registerField: registerField,
137
- getCurrentValue: getCurrentValue
140
+ getCurrentValue: getCurrentValue,
141
+ subscribe: form.subscribe
138
142
  };
139
- }, [registerField, getCurrentValue]);
143
+ }, [registerField, getCurrentValue, form.subscribe]);
140
144
  return /*#__PURE__*/_react.default.createElement(FormContext.Provider, {
141
145
  value: FormContextValue
142
146
  }, /*#__PURE__*/_react.default.createElement(IsDisabledContext.Provider, {
package/dist/cjs/index.js CHANGED
@@ -88,6 +88,12 @@ Object.defineProperty(exports, "default", {
88
88
  return _form.default;
89
89
  }
90
90
  });
91
+ Object.defineProperty(exports, "useFormState", {
92
+ enumerable: true,
93
+ get: function get() {
94
+ return _useFormState.useFormState;
95
+ }
96
+ });
91
97
  var _form = _interopRequireDefault(require("./form"));
92
98
  var _formHeader = _interopRequireDefault(require("./form-header"));
93
99
  var _formFooter = _interopRequireDefault(require("./form-footer"));
@@ -98,4 +104,5 @@ var _rangeField = _interopRequireDefault(require("./range-field"));
98
104
  var _label = require("./label");
99
105
  var _messages = require("./messages");
100
106
  var _fieldset = _interopRequireDefault(require("./fieldset"));
101
- var _requiredAsterisk = _interopRequireDefault(require("./required-asterisk"));
107
+ var _requiredAsterisk = _interopRequireDefault(require("./required-asterisk"));
108
+ var _useFormState = require("./use-form-state");
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.useFormState = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ var _react = require("react");
10
+ var _form = require("./form");
11
+ // Constantized to avoid a new object reference built every call
12
+ var defaultSubscriptionConfig = {
13
+ values: true
14
+ };
15
+
16
+ /**
17
+ * Build a simple hash for a given subscription object for use in a `useMemo` dependencies array.
18
+ * This is because `{ values: true } !== { values: true }`, but `'values:true|' === 'values:true|'`
19
+ *
20
+ * @example { values: true, dirty: false } => 'values:true|dirty:false|'
21
+ */
22
+ var getSubscriptionHash = function getSubscriptionHash(subscriptionConfig) {
23
+ var hash = '';
24
+ for (var key in subscriptionConfig) {
25
+ if (subscriptionConfig.hasOwnProperty(key)) {
26
+ hash += "".concat(key, ":").concat(subscriptionConfig[key], "|");
27
+ }
28
+ }
29
+ return hash;
30
+ };
31
+
32
+ /**
33
+ * A hook to return a recent form state for use within the `<Form>` as it requires context access.
34
+ * This is useful for previewing form state, or for building custom fields that need to react to form state.
35
+ *
36
+ * This should not be used as a way to persist form state into another form state, use `onSubmit` for proper form handling.
37
+ *
38
+ * @note On the initial render, this should be `undefined` as our form has not provided any state.
39
+ */
40
+ var useFormState = exports.useFormState = function useFormState() {
41
+ var subscriptionConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultSubscriptionConfig;
42
+ var _useContext = (0, _react.useContext)(_form.FormContext),
43
+ subscribe = _useContext.subscribe;
44
+ var _useState = (0, _react.useState)(),
45
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
46
+ state = _useState2[0],
47
+ setState = _useState2[1];
48
+
49
+ /**
50
+ * A hash for us to shallow compare the subscriptionConfig object to react to shallow changes, but avoid referential changes.
51
+ * We avoid computing the hash if the subscription config has referential equality altogether.
52
+ */
53
+ var subscriptionConfigHash = (0, _react.useMemo)(function () {
54
+ return getSubscriptionHash(subscriptionConfig);
55
+ }, [subscriptionConfig]);
56
+
57
+ /**
58
+ * Return a memoized version of the subscription config to only react to shallow changes, not referential changes.
59
+ * Eg. calling `useFormState({ values: true })` twice will result in two different objects by reference, but not by shallow comparison.
60
+ * This will ensure we don't re-subscribe to the form state when the subscription config is the same.
61
+ */
62
+ var config = (0, _react.useMemo)(function () {
63
+ return subscriptionConfig;
64
+ },
65
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally controlled with a hash to have an explicit shallow comparison
66
+ [subscriptionConfigHash]);
67
+ (0, _react.useEffect)(function () {
68
+ var unsubscribe = subscribe(function (formState) {
69
+ setState(formState);
70
+ }, config);
71
+ return function () {
72
+ return unsubscribe();
73
+ };
74
+ }, [subscribe, config]);
75
+ return state;
76
+ };
@@ -11,7 +11,10 @@ export const FormContext = /*#__PURE__*/createContext({
11
11
  registerField: function () {
12
12
  return () => {};
13
13
  },
14
- getCurrentValue: () => undefined
14
+ getCurrentValue: () => undefined,
15
+ subscribe: function () {
16
+ return () => {};
17
+ }
15
18
  });
16
19
 
17
20
  /**
@@ -112,9 +115,10 @@ export default function Form(props) {
112
115
  const FormContextValue = useMemo(() => {
113
116
  return {
114
117
  registerField,
115
- getCurrentValue
118
+ getCurrentValue,
119
+ subscribe: form.subscribe
116
120
  };
117
- }, [registerField, getCurrentValue]);
121
+ }, [registerField, getCurrentValue, form.subscribe]);
118
122
  return /*#__PURE__*/React.createElement(FormContext.Provider, {
119
123
  value: FormContextValue
120
124
  }, /*#__PURE__*/React.createElement(IsDisabledContext.Provider, {
@@ -9,4 +9,6 @@ export { Label, Legend } from './label';
9
9
  export { HelperMessage, ErrorMessage, ValidMessage } from './messages';
10
10
  export { default as Fieldset } from './fieldset';
11
11
  export { default as RequiredAsterisk } from './required-asterisk';
12
- // eslint-disable-next-line import/no-unresolved
12
+ // eslint-disable-next-line import/no-unresolved
13
+
14
+ export { useFormState } from './use-form-state';
@@ -0,0 +1,60 @@
1
+ import { useContext, useEffect, useMemo, useState } from 'react';
2
+ import { FormContext } from './form';
3
+
4
+ // Constantized to avoid a new object reference built every call
5
+ const defaultSubscriptionConfig = {
6
+ values: true
7
+ };
8
+
9
+ /**
10
+ * Build a simple hash for a given subscription object for use in a `useMemo` dependencies array.
11
+ * This is because `{ values: true } !== { values: true }`, but `'values:true|' === 'values:true|'`
12
+ *
13
+ * @example { values: true, dirty: false } => 'values:true|dirty:false|'
14
+ */
15
+ const getSubscriptionHash = subscriptionConfig => {
16
+ let hash = '';
17
+ for (const key in subscriptionConfig) {
18
+ if (subscriptionConfig.hasOwnProperty(key)) {
19
+ hash += `${key}:${subscriptionConfig[key]}|`;
20
+ }
21
+ }
22
+ return hash;
23
+ };
24
+
25
+ /**
26
+ * A hook to return a recent form state for use within the `<Form>` as it requires context access.
27
+ * This is useful for previewing form state, or for building custom fields that need to react to form state.
28
+ *
29
+ * This should not be used as a way to persist form state into another form state, use `onSubmit` for proper form handling.
30
+ *
31
+ * @note On the initial render, this should be `undefined` as our form has not provided any state.
32
+ */
33
+ export const useFormState = (subscriptionConfig = defaultSubscriptionConfig) => {
34
+ const {
35
+ subscribe
36
+ } = useContext(FormContext);
37
+ const [state, setState] = useState();
38
+
39
+ /**
40
+ * A hash for us to shallow compare the subscriptionConfig object to react to shallow changes, but avoid referential changes.
41
+ * We avoid computing the hash if the subscription config has referential equality altogether.
42
+ */
43
+ const subscriptionConfigHash = useMemo(() => getSubscriptionHash(subscriptionConfig), [subscriptionConfig]);
44
+
45
+ /**
46
+ * Return a memoized version of the subscription config to only react to shallow changes, not referential changes.
47
+ * Eg. calling `useFormState({ values: true })` twice will result in two different objects by reference, but not by shallow comparison.
48
+ * This will ensure we don't re-subscribe to the form state when the subscription config is the same.
49
+ */
50
+ const config = useMemo(() => subscriptionConfig,
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally controlled with a hash to have an explicit shallow comparison
52
+ [subscriptionConfigHash]);
53
+ useEffect(() => {
54
+ const unsubscribe = subscribe(formState => {
55
+ setState(formState);
56
+ }, config);
57
+ return () => unsubscribe();
58
+ }, [subscribe, config]);
59
+ return state;
60
+ };
package/dist/esm/form.js CHANGED
@@ -14,6 +14,9 @@ export var FormContext = /*#__PURE__*/createContext({
14
14
  },
15
15
  getCurrentValue: function getCurrentValue() {
16
16
  return undefined;
17
+ },
18
+ subscribe: function subscribe() {
19
+ return function () {};
17
20
  }
18
21
  });
19
22
 
@@ -123,9 +126,10 @@ export default function Form(props) {
123
126
  var FormContextValue = useMemo(function () {
124
127
  return {
125
128
  registerField: registerField,
126
- getCurrentValue: getCurrentValue
129
+ getCurrentValue: getCurrentValue,
130
+ subscribe: form.subscribe
127
131
  };
128
- }, [registerField, getCurrentValue]);
132
+ }, [registerField, getCurrentValue, form.subscribe]);
129
133
  return /*#__PURE__*/React.createElement(FormContext.Provider, {
130
134
  value: FormContextValue
131
135
  }, /*#__PURE__*/React.createElement(IsDisabledContext.Provider, {
package/dist/esm/index.js CHANGED
@@ -9,4 +9,6 @@ export { Label, Legend } from './label';
9
9
  export { HelperMessage, ErrorMessage, ValidMessage } from './messages';
10
10
  export { default as Fieldset } from './fieldset';
11
11
  export { default as RequiredAsterisk } from './required-asterisk';
12
- // eslint-disable-next-line import/no-unresolved
12
+ // eslint-disable-next-line import/no-unresolved
13
+
14
+ export { useFormState } from './use-form-state';
@@ -0,0 +1,70 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import { useContext, useEffect, useMemo, useState } from 'react';
3
+ import { FormContext } from './form';
4
+
5
+ // Constantized to avoid a new object reference built every call
6
+ var defaultSubscriptionConfig = {
7
+ values: true
8
+ };
9
+
10
+ /**
11
+ * Build a simple hash for a given subscription object for use in a `useMemo` dependencies array.
12
+ * This is because `{ values: true } !== { values: true }`, but `'values:true|' === 'values:true|'`
13
+ *
14
+ * @example { values: true, dirty: false } => 'values:true|dirty:false|'
15
+ */
16
+ var getSubscriptionHash = function getSubscriptionHash(subscriptionConfig) {
17
+ var hash = '';
18
+ for (var key in subscriptionConfig) {
19
+ if (subscriptionConfig.hasOwnProperty(key)) {
20
+ hash += "".concat(key, ":").concat(subscriptionConfig[key], "|");
21
+ }
22
+ }
23
+ return hash;
24
+ };
25
+
26
+ /**
27
+ * A hook to return a recent form state for use within the `<Form>` as it requires context access.
28
+ * This is useful for previewing form state, or for building custom fields that need to react to form state.
29
+ *
30
+ * This should not be used as a way to persist form state into another form state, use `onSubmit` for proper form handling.
31
+ *
32
+ * @note On the initial render, this should be `undefined` as our form has not provided any state.
33
+ */
34
+ export var useFormState = function useFormState() {
35
+ var subscriptionConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultSubscriptionConfig;
36
+ var _useContext = useContext(FormContext),
37
+ subscribe = _useContext.subscribe;
38
+ var _useState = useState(),
39
+ _useState2 = _slicedToArray(_useState, 2),
40
+ state = _useState2[0],
41
+ setState = _useState2[1];
42
+
43
+ /**
44
+ * A hash for us to shallow compare the subscriptionConfig object to react to shallow changes, but avoid referential changes.
45
+ * We avoid computing the hash if the subscription config has referential equality altogether.
46
+ */
47
+ var subscriptionConfigHash = useMemo(function () {
48
+ return getSubscriptionHash(subscriptionConfig);
49
+ }, [subscriptionConfig]);
50
+
51
+ /**
52
+ * Return a memoized version of the subscription config to only react to shallow changes, not referential changes.
53
+ * Eg. calling `useFormState({ values: true })` twice will result in two different objects by reference, but not by shallow comparison.
54
+ * This will ensure we don't re-subscribe to the form state when the subscription config is the same.
55
+ */
56
+ var config = useMemo(function () {
57
+ return subscriptionConfig;
58
+ },
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally controlled with a hash to have an explicit shallow comparison
60
+ [subscriptionConfigHash]);
61
+ useEffect(function () {
62
+ var unsubscribe = subscribe(function (formState) {
63
+ setState(formState);
64
+ }, config);
65
+ return function () {
66
+ return unsubscribe();
67
+ };
68
+ }, [subscribe, config]);
69
+ return state;
70
+ };
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { FieldConfig, FieldSubscriber, FieldSubscription, FormState, Unsubscribe } from 'final-form';
2
+ import { FieldConfig, FieldSubscriber, FieldSubscription, FormApi, FormState, Unsubscribe } from 'final-form';
3
3
  import { OnSubmitHandler } from './types';
4
4
  type DefaultValue<FieldValue> = (value?: FieldValue) => FieldValue;
5
5
  type RegisterField = <FieldValue>(name: string, defaultValue: FieldValue | DefaultValue<FieldValue>, subscriber: FieldSubscriber<FieldValue>, subscription: FieldSubscription, config: FieldConfig<FieldValue>) => Unsubscribe;
@@ -12,6 +12,7 @@ type GetCurrentValue = <FormValues>(name: string) => FormValues[keyof FormValues
12
12
  export declare const FormContext: React.Context<{
13
13
  registerField: RegisterField;
14
14
  getCurrentValue: GetCurrentValue;
15
+ subscribe: FormApi['subscribe'];
15
16
  }>;
16
17
  /**
17
18
  * __Is disabled context__
@@ -15,3 +15,4 @@ export { HelperMessage, ErrorMessage, ValidMessage } from './messages';
15
15
  export { default as Fieldset } from './fieldset';
16
16
  export { default as RequiredAsterisk } from './required-asterisk';
17
17
  export type { OnSubmitHandler, FormApi } from './types';
18
+ export { useFormState } from './use-form-state';
@@ -0,0 +1,10 @@
1
+ import { FormState, FormSubscription } from 'final-form';
2
+ /**
3
+ * A hook to return a recent form state for use within the `<Form>` as it requires context access.
4
+ * This is useful for previewing form state, or for building custom fields that need to react to form state.
5
+ *
6
+ * This should not be used as a way to persist form state into another form state, use `onSubmit` for proper form handling.
7
+ *
8
+ * @note On the initial render, this should be `undefined` as our form has not provided any state.
9
+ */
10
+ export declare const useFormState: <FormValues extends Record<string, any>>(subscriptionConfig?: FormSubscription) => FormState<FormValues> | undefined;
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { FieldConfig, FieldSubscriber, FieldSubscription, FormState, Unsubscribe } from 'final-form';
2
+ import { FieldConfig, FieldSubscriber, FieldSubscription, FormApi, FormState, Unsubscribe } from 'final-form';
3
3
  import { OnSubmitHandler } from './types';
4
4
  type DefaultValue<FieldValue> = (value?: FieldValue) => FieldValue;
5
5
  type RegisterField = <FieldValue>(name: string, defaultValue: FieldValue | DefaultValue<FieldValue>, subscriber: FieldSubscriber<FieldValue>, subscription: FieldSubscription, config: FieldConfig<FieldValue>) => Unsubscribe;
@@ -12,6 +12,7 @@ type GetCurrentValue = <FormValues>(name: string) => FormValues[keyof FormValues
12
12
  export declare const FormContext: React.Context<{
13
13
  registerField: RegisterField;
14
14
  getCurrentValue: GetCurrentValue;
15
+ subscribe: FormApi['subscribe'];
15
16
  }>;
16
17
  /**
17
18
  * __Is disabled context__
@@ -15,3 +15,4 @@ export { HelperMessage, ErrorMessage, ValidMessage } from './messages';
15
15
  export { default as Fieldset } from './fieldset';
16
16
  export { default as RequiredAsterisk } from './required-asterisk';
17
17
  export type { OnSubmitHandler, FormApi } from './types';
18
+ export { useFormState } from './use-form-state';
@@ -0,0 +1,10 @@
1
+ import { FormState, FormSubscription } from 'final-form';
2
+ /**
3
+ * A hook to return a recent form state for use within the `<Form>` as it requires context access.
4
+ * This is useful for previewing form state, or for building custom fields that need to react to form state.
5
+ *
6
+ * This should not be used as a way to persist form state into another form state, use `onSubmit` for proper form handling.
7
+ *
8
+ * @note On the initial render, this should be `undefined` as our form has not provided any state.
9
+ */
10
+ export declare const useFormState: <FormValues extends Record<string, any>>(subscriptionConfig?: FormSubscription) => FormState<FormValues> | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/form",
3
- "version": "9.0.12",
3
+ "version": "9.1.0",
4
4
  "description": "A form allows users to input information.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -46,6 +46,7 @@
46
46
  "@af/accessibility-testing": "*",
47
47
  "@af/integration-testing": "*",
48
48
  "@af/visual-regression": "*",
49
+ "@atlaskit/banner": "^12.1.22",
49
50
  "@atlaskit/button": "^17.7.0",
50
51
  "@atlaskit/checkbox": "^13.1.0",
51
52
  "@atlaskit/ds-lib": "^2.2.0",
@@ -56,6 +57,7 @@
56
57
  "@atlaskit/visual-regression": "*",
57
58
  "@atlassian/atlassian-frontend-prettier-config-1.0.1": "npm:@atlassian/atlassian-frontend-prettier-config@1.0.1",
58
59
  "@testing-library/react": "^12.1.5",
60
+ "@testing-library/react-hooks": "^8.0.1",
59
61
  "@testing-library/user-event": "^14.4.3",
60
62
  "@types/final-form-focus": "^1.1.1",
61
63
  "jest-in-case": "^1.0.2",