@wordpress/dataviews 6.0.0 → 7.0.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 +25 -1
- package/README.md +42 -14
- package/build/components/dataviews/index.js +38 -6
- package/build/components/dataviews/index.js.map +1 -1
- package/build/components/dataviews-context/index.js +4 -1
- package/build/components/dataviews-context/index.js.map +1 -1
- package/build/components/dataviews-item-actions/index.js +1 -10
- package/build/components/dataviews-item-actions/index.js.map +1 -1
- package/build/components/dataviews-pagination/index.js +1 -1
- package/build/components/dataviews-pagination/index.js.map +1 -1
- package/build/components/dataviews-view-config/index.js +8 -5
- package/build/components/dataviews-view-config/index.js.map +1 -1
- package/build/components/dataviews-view-config/infinite-scroll-toggle.js +47 -0
- package/build/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -0
- package/build/dataform-controls/array.js +70 -0
- package/build/dataform-controls/array.js.map +1 -0
- package/build/dataform-controls/boolean.js +15 -7
- package/build/dataform-controls/boolean.js.map +1 -1
- package/build/dataform-controls/email.js +14 -7
- package/build/dataform-controls/email.js.map +1 -1
- package/build/dataform-controls/index.js +3 -1
- package/build/dataform-controls/index.js.map +1 -1
- package/build/dataform-controls/integer.js +14 -7
- package/build/dataform-controls/integer.js.map +1 -1
- package/build/dataform-controls/text.js +14 -7
- package/build/dataform-controls/text.js.map +1 -1
- package/build/dataforms-layouts/card/index.js +137 -0
- package/build/dataforms-layouts/card/index.js.map +1 -0
- package/build/dataforms-layouts/data-form-layout.js +2 -2
- package/build/dataforms-layouts/data-form-layout.js.map +1 -1
- package/build/dataforms-layouts/index.js +4 -0
- package/build/dataforms-layouts/index.js.map +1 -1
- package/build/dataforms-layouts/panel/dropdown.js +124 -0
- package/build/dataforms-layouts/panel/dropdown.js.map +1 -0
- package/build/dataforms-layouts/panel/index.js +34 -149
- package/build/dataforms-layouts/panel/index.js.map +1 -1
- package/build/dataforms-layouts/panel/modal.js +125 -0
- package/build/dataforms-layouts/panel/modal.js.map +1 -0
- package/build/dataforms-layouts/regular/index.js +10 -21
- package/build/dataforms-layouts/regular/index.js.map +1 -1
- package/build/dataviews-layouts/grid/index.js +24 -7
- package/build/dataviews-layouts/grid/index.js.map +1 -1
- package/build/dataviews-layouts/grid/preview-size-picker.js +11 -11
- package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
- package/build/dataviews-layouts/list/index.js +45 -27
- package/build/dataviews-layouts/list/index.js.map +1 -1
- package/build/dataviews-layouts/table/column-header-menu.js +3 -0
- package/build/dataviews-layouts/table/column-header-menu.js.map +1 -1
- package/build/dataviews-layouts/table/index.js +23 -8
- package/build/dataviews-layouts/table/index.js.map +1 -1
- package/build/field-types/array.js +2 -2
- package/build/field-types/array.js.map +1 -1
- package/build/normalize-form-fields.js +52 -13
- package/build/normalize-form-fields.js.map +1 -1
- package/build/types.js.map +1 -1
- package/build-module/components/dataviews/index.js +40 -8
- package/build-module/components/dataviews/index.js.map +1 -1
- package/build-module/components/dataviews-context/index.js +4 -1
- package/build-module/components/dataviews-context/index.js.map +1 -1
- package/build-module/components/dataviews-item-actions/index.js +1 -10
- package/build-module/components/dataviews-item-actions/index.js.map +1 -1
- package/build-module/components/dataviews-pagination/index.js +1 -1
- package/build-module/components/dataviews-pagination/index.js.map +1 -1
- package/build-module/components/dataviews-view-config/index.js +8 -5
- package/build-module/components/dataviews-view-config/index.js.map +1 -1
- package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js +39 -0
- package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -0
- package/build-module/dataform-controls/array.js +63 -0
- package/build-module/dataform-controls/array.js.map +1 -0
- package/build-module/dataform-controls/boolean.js +15 -7
- package/build-module/dataform-controls/boolean.js.map +1 -1
- package/build-module/dataform-controls/email.js +15 -8
- package/build-module/dataform-controls/email.js.map +1 -1
- package/build-module/dataform-controls/index.js +3 -1
- package/build-module/dataform-controls/index.js.map +1 -1
- package/build-module/dataform-controls/integer.js +15 -8
- package/build-module/dataform-controls/integer.js.map +1 -1
- package/build-module/dataform-controls/text.js +15 -8
- package/build-module/dataform-controls/text.js.map +1 -1
- package/build-module/dataforms-layouts/card/index.js +128 -0
- package/build-module/dataforms-layouts/card/index.js.map +1 -0
- package/build-module/dataforms-layouts/data-form-layout.js +2 -2
- package/build-module/dataforms-layouts/data-form-layout.js.map +1 -1
- package/build-module/dataforms-layouts/index.js +4 -0
- package/build-module/dataforms-layouts/index.js.map +1 -1
- package/build-module/dataforms-layouts/panel/dropdown.js +118 -0
- package/build-module/dataforms-layouts/panel/dropdown.js.map +1 -0
- package/build-module/dataforms-layouts/panel/index.js +37 -152
- package/build-module/dataforms-layouts/panel/index.js.map +1 -1
- package/build-module/dataforms-layouts/panel/modal.js +119 -0
- package/build-module/dataforms-layouts/panel/modal.js.map +1 -0
- package/build-module/dataforms-layouts/regular/index.js +10 -21
- package/build-module/dataforms-layouts/regular/index.js.map +1 -1
- package/build-module/dataviews-layouts/grid/index.js +25 -8
- package/build-module/dataviews-layouts/grid/index.js.map +1 -1
- package/build-module/dataviews-layouts/grid/preview-size-picker.js +11 -11
- package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
- package/build-module/dataviews-layouts/list/index.js +47 -29
- package/build-module/dataviews-layouts/list/index.js.map +1 -1
- package/build-module/dataviews-layouts/table/column-header-menu.js +3 -0
- package/build-module/dataviews-layouts/table/column-header-menu.js.map +1 -1
- package/build-module/dataviews-layouts/table/index.js +23 -8
- package/build-module/dataviews-layouts/table/index.js.map +1 -1
- package/build-module/field-types/array.js +2 -2
- package/build-module/field-types/array.js.map +1 -1
- package/build-module/normalize-form-fields.js +50 -13
- package/build-module/normalize-form-fields.js.map +1 -1
- package/build-module/types.js.map +1 -1
- package/build-style/style-rtl.css +53 -16
- package/build-style/style.css +53 -16
- package/build-types/components/dataform/stories/index.story.d.ts +41 -17
- package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
- package/build-types/components/dataviews/index.d.ts +5 -2
- package/build-types/components/dataviews/index.d.ts.map +1 -1
- package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
- package/build-types/components/dataviews/stories/index.story.d.ts +2 -1
- package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
- package/build-types/components/dataviews-context/index.d.ts +4 -1
- package/build-types/components/dataviews-context/index.d.ts.map +1 -1
- package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
- package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
- package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts +2 -0
- package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts.map +1 -0
- package/build-types/dataform-controls/array.d.ts +6 -0
- package/build-types/dataform-controls/array.d.ts.map +1 -0
- package/build-types/dataform-controls/boolean.d.ts.map +1 -1
- package/build-types/dataform-controls/email.d.ts.map +1 -1
- package/build-types/dataform-controls/index.d.ts.map +1 -1
- package/build-types/dataform-controls/integer.d.ts.map +1 -1
- package/build-types/dataform-controls/text.d.ts.map +1 -1
- package/build-types/dataforms-layouts/card/index.d.ts +13 -0
- package/build-types/dataforms-layouts/card/index.d.ts.map +1 -0
- package/build-types/dataforms-layouts/index.d.ts.map +1 -1
- package/build-types/dataforms-layouts/panel/dropdown.d.ts +14 -0
- package/build-types/dataforms-layouts/panel/dropdown.d.ts.map +1 -0
- package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
- package/build-types/dataforms-layouts/panel/modal.d.ts +13 -0
- package/build-types/dataforms-layouts/panel/modal.d.ts.map +1 -0
- package/build-types/dataforms-layouts/regular/index.d.ts.map +1 -1
- package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
- package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts +1 -1
- package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
- package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
- package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
- package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
- package/build-types/field-types/boolean.d.ts +1 -1
- package/build-types/normalize-form-fields.d.ts +10 -3
- package/build-types/normalize-form-fields.d.ts.map +1 -1
- package/build-types/test/normalize-form-fields.d.ts +2 -0
- package/build-types/test/normalize-form-fields.d.ts.map +1 -0
- package/build-types/types.d.ts +54 -6
- package/build-types/types.d.ts.map +1 -1
- package/build-wp/index.js +3062 -1147
- package/package.json +15 -15
- package/src/components/dataform/stories/index.story.tsx +478 -91
- package/src/components/dataviews/index.tsx +50 -14
- package/src/components/dataviews/stories/fixtures.tsx +98 -7
- package/src/components/dataviews/stories/index.story.tsx +137 -4
- package/src/components/dataviews/style.scss +4 -0
- package/src/components/dataviews-context/index.ts +6 -2
- package/src/components/dataviews-item-actions/index.tsx +7 -16
- package/src/components/dataviews-pagination/index.tsx +1 -1
- package/src/components/dataviews-view-config/index.tsx +13 -5
- package/src/components/dataviews-view-config/infinite-scroll-toggle.tsx +39 -0
- package/src/dataform-controls/array.tsx +85 -0
- package/src/dataform-controls/boolean.tsx +24 -10
- package/src/dataform-controls/email.tsx +24 -11
- package/src/dataform-controls/index.tsx +3 -1
- package/src/dataform-controls/integer.tsx +27 -13
- package/src/dataform-controls/text.tsx +24 -11
- package/src/dataforms-layouts/card/index.tsx +154 -0
- package/src/dataforms-layouts/card/style.scss +3 -0
- package/src/dataforms-layouts/data-form-layout.tsx +2 -2
- package/src/dataforms-layouts/index.tsx +5 -0
- package/src/dataforms-layouts/panel/dropdown.tsx +160 -0
- package/src/dataforms-layouts/panel/index.tsx +49 -189
- package/src/dataforms-layouts/panel/modal.tsx +165 -0
- package/src/dataforms-layouts/panel/style.scss +4 -0
- package/src/dataforms-layouts/regular/index.tsx +20 -23
- package/src/dataviews-layouts/grid/index.tsx +32 -5
- package/src/dataviews-layouts/grid/preview-size-picker.tsx +15 -13
- package/src/dataviews-layouts/grid/style.scss +3 -1
- package/src/dataviews-layouts/list/index.tsx +65 -31
- package/src/dataviews-layouts/list/style.scss +7 -3
- package/src/dataviews-layouts/table/column-header-menu.tsx +4 -0
- package/src/dataviews-layouts/table/index.tsx +27 -1
- package/src/field-types/array.tsx +1 -1
- package/src/normalize-form-fields.ts +63 -17
- package/src/test/dataform.tsx +181 -3
- package/src/test/dataviews.tsx +38 -0
- package/src/test/filter-and-sort-data-view.js +123 -64
- package/src/test/normalize-form-fields.ts +247 -0
- package/src/types.ts +72 -6
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { FormTokenField } from '@wordpress/components';
|
|
5
|
+
import { useCallback, useMemo } from '@wordpress/element';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { DataFormControlProps } from '../types';
|
|
11
|
+
|
|
12
|
+
export default function ArrayControl< Item >( {
|
|
13
|
+
data,
|
|
14
|
+
field,
|
|
15
|
+
onChange,
|
|
16
|
+
hideLabelFromVision,
|
|
17
|
+
}: DataFormControlProps< Item > ) {
|
|
18
|
+
const { id, label, placeholder, elements } = field;
|
|
19
|
+
const value = field.getValue( { item: data } );
|
|
20
|
+
|
|
21
|
+
const findElementByValue = useCallback(
|
|
22
|
+
( suggestionValue: string ) => {
|
|
23
|
+
return elements?.find(
|
|
24
|
+
( suggestion ) => suggestion.value === suggestionValue
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
[ elements ]
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const findElementByLabel = useCallback(
|
|
31
|
+
( suggestionLabel: string ) => {
|
|
32
|
+
return elements?.find(
|
|
33
|
+
( suggestion ) => suggestion.label === suggestionLabel
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
[ elements ]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Ensure value is an array
|
|
40
|
+
const arrayValue = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
Array.isArray( value )
|
|
43
|
+
? value.map( ( token ) => {
|
|
44
|
+
const tokenLabel = findElementByValue( token )?.label;
|
|
45
|
+
return tokenLabel || token;
|
|
46
|
+
} )
|
|
47
|
+
: [],
|
|
48
|
+
[ value, findElementByValue ]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const onChangeControl = useCallback(
|
|
52
|
+
( tokens: ( string | { value: string } )[] ) => {
|
|
53
|
+
// Convert TokenItem objects to strings
|
|
54
|
+
const stringTokens = tokens.map( ( token ) => {
|
|
55
|
+
if ( typeof token !== 'string' ) {
|
|
56
|
+
return token.value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tokenByLabel = findElementByLabel( token );
|
|
60
|
+
|
|
61
|
+
return tokenByLabel?.value || token;
|
|
62
|
+
} );
|
|
63
|
+
|
|
64
|
+
onChange( {
|
|
65
|
+
[ id ]: stringTokens,
|
|
66
|
+
} );
|
|
67
|
+
},
|
|
68
|
+
[ id, onChange, findElementByLabel ]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<FormTokenField
|
|
73
|
+
label={ hideLabelFromVision ? undefined : label }
|
|
74
|
+
value={ arrayValue }
|
|
75
|
+
onChange={ onChangeControl }
|
|
76
|
+
placeholder={ placeholder }
|
|
77
|
+
suggestions={
|
|
78
|
+
elements?.map( ( suggestion ) => suggestion.label ) ?? []
|
|
79
|
+
}
|
|
80
|
+
__experimentalExpandOnFocus={ elements && elements.length > 0 }
|
|
81
|
+
__next40pxDefaultSize
|
|
82
|
+
__nextHasNoMarginBottom
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { privateApis } from '@wordpress/components';
|
|
5
|
+
import { useState } from '@wordpress/element';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Internal dependencies
|
|
@@ -18,23 +19,36 @@ export default function Boolean< Item >( {
|
|
|
18
19
|
hideLabelFromVision,
|
|
19
20
|
}: DataFormControlProps< Item > ) {
|
|
20
21
|
const { id, getValue, label } = field;
|
|
22
|
+
const [ customValidity, setCustomValidity ] =
|
|
23
|
+
useState<
|
|
24
|
+
React.ComponentProps<
|
|
25
|
+
typeof ValidatedToggleControl
|
|
26
|
+
>[ 'customValidity' ]
|
|
27
|
+
>( undefined );
|
|
21
28
|
|
|
22
29
|
return (
|
|
23
30
|
<ValidatedToggleControl
|
|
24
31
|
required={ !! field.isValid.required }
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
onValidate={ ( newValue: any ) => {
|
|
33
|
+
const message = field.isValid?.custom?.(
|
|
34
|
+
{
|
|
35
|
+
...data,
|
|
36
|
+
[ id ]: newValue,
|
|
37
|
+
},
|
|
38
|
+
field
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if ( message ) {
|
|
42
|
+
setCustomValidity( {
|
|
43
|
+
type: 'invalid',
|
|
44
|
+
message,
|
|
45
|
+
} );
|
|
46
|
+
return;
|
|
34
47
|
}
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
setCustomValidity( undefined );
|
|
37
50
|
} }
|
|
51
|
+
customValidity={ customValidity }
|
|
38
52
|
hidden={ hideLabelFromVision }
|
|
39
53
|
__nextHasNoMarginBottom
|
|
40
54
|
label={ label }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { privateApis } from '@wordpress/components';
|
|
5
|
-
import { useCallback } from '@wordpress/element';
|
|
5
|
+
import { useCallback, useState } from '@wordpress/element';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Internal dependencies
|
|
@@ -20,6 +20,12 @@ export default function Email< Item >( {
|
|
|
20
20
|
}: DataFormControlProps< Item > ) {
|
|
21
21
|
const { id, label, placeholder, description } = field;
|
|
22
22
|
const value = field.getValue( { item: data } );
|
|
23
|
+
const [ customValidity, setCustomValidity ] =
|
|
24
|
+
useState<
|
|
25
|
+
React.ComponentProps<
|
|
26
|
+
typeof ValidatedTextControl
|
|
27
|
+
>[ 'customValidity' ]
|
|
28
|
+
>( undefined );
|
|
23
29
|
|
|
24
30
|
const onChangeControl = useCallback(
|
|
25
31
|
( newValue: string ) =>
|
|
@@ -32,19 +38,26 @@ export default function Email< Item >( {
|
|
|
32
38
|
return (
|
|
33
39
|
<ValidatedTextControl
|
|
34
40
|
required={ !! field.isValid?.required }
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
onValidate={ ( newValue: any ) => {
|
|
42
|
+
const message = field.isValid?.custom?.(
|
|
43
|
+
{
|
|
44
|
+
...data,
|
|
45
|
+
[ id ]: newValue,
|
|
46
|
+
},
|
|
47
|
+
field
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if ( message ) {
|
|
51
|
+
setCustomValidity( {
|
|
52
|
+
type: 'invalid',
|
|
53
|
+
message,
|
|
54
|
+
} );
|
|
55
|
+
return;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
setCustomValidity( undefined );
|
|
47
59
|
} }
|
|
60
|
+
customValidity={ customValidity }
|
|
48
61
|
type="email"
|
|
49
62
|
label={ label }
|
|
50
63
|
placeholder={ placeholder }
|
|
@@ -21,12 +21,14 @@ import select from './select';
|
|
|
21
21
|
import text from './text';
|
|
22
22
|
import toggleGroup from './toggle-group';
|
|
23
23
|
import boolean from './boolean';
|
|
24
|
+
import array from './array';
|
|
24
25
|
|
|
25
26
|
interface FormControls {
|
|
26
27
|
[ key: string ]: ComponentType< DataFormControlProps< any > >;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const FORM_CONTROLS: FormControls = {
|
|
31
|
+
array,
|
|
30
32
|
boolean,
|
|
31
33
|
checkbox,
|
|
32
34
|
datetime,
|
|
@@ -51,7 +53,7 @@ export function getControl< Item >(
|
|
|
51
53
|
return getControlByType( field.Edit );
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
if ( field.elements ) {
|
|
56
|
+
if ( field.elements && field.type !== 'array' ) {
|
|
55
57
|
return getControlByType( 'select' );
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
__experimentalNumberControl as NumberControl,
|
|
8
8
|
privateApis,
|
|
9
9
|
} from '@wordpress/components';
|
|
10
|
-
import { useCallback } from '@wordpress/element';
|
|
10
|
+
import { useCallback, useState } from '@wordpress/element';
|
|
11
11
|
import { __ } from '@wordpress/i18n';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -84,6 +84,13 @@ export default function Integer< Item >( {
|
|
|
84
84
|
}: DataFormControlProps< Item > ) {
|
|
85
85
|
const { id, label, description } = field;
|
|
86
86
|
const value = field.getValue( { item: data } ) ?? '';
|
|
87
|
+
const [ customValidity, setCustomValidity ] =
|
|
88
|
+
useState<
|
|
89
|
+
React.ComponentProps<
|
|
90
|
+
typeof ValidatedNumberControl
|
|
91
|
+
>[ 'customValidity' ]
|
|
92
|
+
>( undefined );
|
|
93
|
+
|
|
87
94
|
const onChangeControl = useCallback(
|
|
88
95
|
( newValue: string | undefined ) => {
|
|
89
96
|
onChange( {
|
|
@@ -112,21 +119,28 @@ export default function Integer< Item >( {
|
|
|
112
119
|
return (
|
|
113
120
|
<ValidatedNumberControl
|
|
114
121
|
required={ !! field.isValid?.required }
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
onValidate={ ( newValue: any ) => {
|
|
123
|
+
const message = field.isValid?.custom?.(
|
|
124
|
+
{
|
|
125
|
+
...data,
|
|
126
|
+
[ id ]: [ undefined, '', null ].includes( newValue )
|
|
127
|
+
? undefined
|
|
128
|
+
: Number( newValue ),
|
|
129
|
+
},
|
|
130
|
+
field
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if ( message ) {
|
|
134
|
+
setCustomValidity( {
|
|
135
|
+
type: 'invalid',
|
|
136
|
+
message,
|
|
137
|
+
} );
|
|
138
|
+
return;
|
|
126
139
|
}
|
|
127
140
|
|
|
128
|
-
|
|
141
|
+
setCustomValidity( undefined );
|
|
129
142
|
} }
|
|
143
|
+
customValidity={ customValidity }
|
|
130
144
|
label={ label }
|
|
131
145
|
help={ description }
|
|
132
146
|
value={ value }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { privateApis } from '@wordpress/components';
|
|
5
|
-
import { useCallback } from '@wordpress/element';
|
|
5
|
+
import { useCallback, useState } from '@wordpress/element';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Internal dependencies
|
|
@@ -20,6 +20,12 @@ export default function Text< Item >( {
|
|
|
20
20
|
}: DataFormControlProps< Item > ) {
|
|
21
21
|
const { id, label, placeholder, description } = field;
|
|
22
22
|
const value = field.getValue( { item: data } );
|
|
23
|
+
const [ customValidity, setCustomValidity ] =
|
|
24
|
+
useState<
|
|
25
|
+
React.ComponentProps<
|
|
26
|
+
typeof ValidatedTextControl
|
|
27
|
+
>[ 'customValidity' ]
|
|
28
|
+
>( undefined );
|
|
23
29
|
|
|
24
30
|
const onChangeControl = useCallback(
|
|
25
31
|
( newValue: string ) =>
|
|
@@ -32,19 +38,26 @@ export default function Text< Item >( {
|
|
|
32
38
|
return (
|
|
33
39
|
<ValidatedTextControl
|
|
34
40
|
required={ !! field.isValid?.required }
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
onValidate={ ( newValue: any ) => {
|
|
42
|
+
const message = field.isValid?.custom?.(
|
|
43
|
+
{
|
|
44
|
+
...data,
|
|
45
|
+
[ id ]: newValue,
|
|
46
|
+
},
|
|
47
|
+
field
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if ( message ) {
|
|
51
|
+
setCustomValidity( {
|
|
52
|
+
type: 'invalid',
|
|
53
|
+
message,
|
|
54
|
+
} );
|
|
55
|
+
return;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
setCustomValidity( undefined );
|
|
47
59
|
} }
|
|
60
|
+
customValidity={ customValidity }
|
|
48
61
|
label={ label }
|
|
49
62
|
placeholder={ placeholder }
|
|
50
63
|
value={ value ?? '' }
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WordPress dependencies
|
|
7
|
+
*/
|
|
8
|
+
import { Button, Card, CardBody, CardHeader } from '@wordpress/components';
|
|
9
|
+
import { useCallback, useContext, useMemo, useState } from '@wordpress/element';
|
|
10
|
+
import { chevronDown, chevronUp } from '@wordpress/icons';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import { getFormFieldLayout } from '..';
|
|
16
|
+
import DataFormContext from '../../components/dataform-context';
|
|
17
|
+
import type { NormalizedCardLayout, FieldLayoutProps, Form } from '../../types';
|
|
18
|
+
import { DataFormLayout } from '../data-form-layout';
|
|
19
|
+
import { isCombinedField } from '../is-combined-field';
|
|
20
|
+
import { DEFAULT_LAYOUT, normalizeLayout } from '../../normalize-form-fields';
|
|
21
|
+
|
|
22
|
+
export function useCollapsibleCard( initialIsOpen: boolean = true ) {
|
|
23
|
+
const [ isOpen, setIsOpen ] = useState( initialIsOpen );
|
|
24
|
+
|
|
25
|
+
const toggle = useCallback( () => {
|
|
26
|
+
setIsOpen( ( prev ) => ! prev );
|
|
27
|
+
}, [] );
|
|
28
|
+
|
|
29
|
+
const CollapsibleCardHeader = useCallback(
|
|
30
|
+
( {
|
|
31
|
+
children,
|
|
32
|
+
...props
|
|
33
|
+
}: {
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
[ key: string ]: any;
|
|
36
|
+
} ) => (
|
|
37
|
+
<CardHeader
|
|
38
|
+
{ ...props }
|
|
39
|
+
onClick={ toggle }
|
|
40
|
+
style={ {
|
|
41
|
+
cursor: 'pointer',
|
|
42
|
+
...props.style,
|
|
43
|
+
} }
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
style={ {
|
|
47
|
+
width: '100%',
|
|
48
|
+
display: 'flex',
|
|
49
|
+
justifyContent: 'space-between',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
} }
|
|
52
|
+
>
|
|
53
|
+
{ children }
|
|
54
|
+
</div>
|
|
55
|
+
<Button
|
|
56
|
+
__next40pxDefaultSize
|
|
57
|
+
variant="tertiary"
|
|
58
|
+
icon={ isOpen ? chevronUp : chevronDown }
|
|
59
|
+
aria-expanded={ isOpen }
|
|
60
|
+
aria-label={ isOpen ? 'Collapse' : 'Expand' }
|
|
61
|
+
/>
|
|
62
|
+
</CardHeader>
|
|
63
|
+
),
|
|
64
|
+
[ toggle, isOpen ]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return { isOpen, CollapsibleCardHeader };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default function FormCardField< Item >( {
|
|
71
|
+
data,
|
|
72
|
+
field,
|
|
73
|
+
onChange,
|
|
74
|
+
hideLabelFromVision,
|
|
75
|
+
}: FieldLayoutProps< Item > ) {
|
|
76
|
+
const { fields } = useContext( DataFormContext );
|
|
77
|
+
|
|
78
|
+
const layout: NormalizedCardLayout = normalizeLayout( {
|
|
79
|
+
...field.layout,
|
|
80
|
+
type: 'card',
|
|
81
|
+
} ) as NormalizedCardLayout;
|
|
82
|
+
|
|
83
|
+
const form: Form = useMemo(
|
|
84
|
+
(): Form => ( {
|
|
85
|
+
layout: DEFAULT_LAYOUT,
|
|
86
|
+
fields: isCombinedField( field ) ? field.children : [],
|
|
87
|
+
} ),
|
|
88
|
+
[ field ]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const { isOpen, CollapsibleCardHeader } = useCollapsibleCard(
|
|
92
|
+
layout.isOpened
|
|
93
|
+
);
|
|
94
|
+
if ( isCombinedField( field ) ) {
|
|
95
|
+
const withHeader = !! field.label && layout.withHeader;
|
|
96
|
+
return (
|
|
97
|
+
<Card className="dataforms-layouts-card__field">
|
|
98
|
+
{ withHeader && (
|
|
99
|
+
<CollapsibleCardHeader className="dataforms-layouts-card__field-label">
|
|
100
|
+
{ field.label }
|
|
101
|
+
</CollapsibleCardHeader>
|
|
102
|
+
) }
|
|
103
|
+
{ ( isOpen || ! withHeader ) && (
|
|
104
|
+
// If it doesn't have a header, keep it open.
|
|
105
|
+
// Otherwise, the card will not be visible.
|
|
106
|
+
<CardBody className="dataforms-layouts-card__field-control">
|
|
107
|
+
<DataFormLayout
|
|
108
|
+
data={ data }
|
|
109
|
+
form={ form }
|
|
110
|
+
onChange={ onChange }
|
|
111
|
+
/>
|
|
112
|
+
</CardBody>
|
|
113
|
+
) }
|
|
114
|
+
</Card>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const fieldDefinition = fields.find(
|
|
119
|
+
( fieldDef ) => fieldDef.id === field.id
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if ( ! fieldDefinition || ! fieldDefinition.Edit ) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const RegularLayout = getFormFieldLayout( 'regular' )?.component;
|
|
127
|
+
if ( ! RegularLayout ) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const withHeader = !! fieldDefinition.label && layout.withHeader;
|
|
131
|
+
return (
|
|
132
|
+
<Card className="dataforms-layouts-card__field">
|
|
133
|
+
{ withHeader && (
|
|
134
|
+
<CollapsibleCardHeader className="dataforms-layouts-card__field-label">
|
|
135
|
+
{ fieldDefinition.label }
|
|
136
|
+
</CollapsibleCardHeader>
|
|
137
|
+
) }
|
|
138
|
+
{ ( isOpen || ! withHeader ) && (
|
|
139
|
+
// If it doesn't have a header, keep it open.
|
|
140
|
+
// Otherwise, the card will not be visible.
|
|
141
|
+
<CardBody className="dataforms-layouts-card__field-control">
|
|
142
|
+
<RegularLayout
|
|
143
|
+
data={ data }
|
|
144
|
+
field={ field }
|
|
145
|
+
onChange={ onChange }
|
|
146
|
+
hideLabelFromVision={
|
|
147
|
+
hideLabelFromVision || withHeader
|
|
148
|
+
}
|
|
149
|
+
/>
|
|
150
|
+
</CardBody>
|
|
151
|
+
) }
|
|
152
|
+
</Card>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -48,9 +48,9 @@ export function DataFormLayout< Item >( {
|
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
|
-
<VStack spacing={ form?.type === 'panel' ? 2 : 4 }>
|
|
51
|
+
<VStack spacing={ form.layout?.type === 'panel' ? 2 : 4 }>
|
|
52
52
|
{ normalizedFormFields.map( ( formField ) => {
|
|
53
|
-
const FieldLayout = getFormFieldLayout( formField.layout )
|
|
53
|
+
const FieldLayout = getFormFieldLayout( formField.layout.type )
|
|
54
54
|
?.component;
|
|
55
55
|
|
|
56
56
|
if ( ! FieldLayout ) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import FormRegularField from './regular';
|
|
5
5
|
import FormPanelField from './panel';
|
|
6
|
+
import FormCardField from './card';
|
|
6
7
|
|
|
7
8
|
const FORM_FIELD_LAYOUTS = [
|
|
8
9
|
{
|
|
@@ -13,6 +14,10 @@ const FORM_FIELD_LAYOUTS = [
|
|
|
13
14
|
type: 'panel',
|
|
14
15
|
component: FormPanelField,
|
|
15
16
|
},
|
|
17
|
+
{
|
|
18
|
+
type: 'card',
|
|
19
|
+
component: FormCardField,
|
|
20
|
+
},
|
|
16
21
|
];
|
|
17
22
|
|
|
18
23
|
export function getFormFieldLayout( type: string ) {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
__experimentalVStack as VStack,
|
|
6
|
+
__experimentalHStack as HStack,
|
|
7
|
+
__experimentalHeading as Heading,
|
|
8
|
+
__experimentalSpacer as Spacer,
|
|
9
|
+
Dropdown,
|
|
10
|
+
Button,
|
|
11
|
+
} from '@wordpress/components';
|
|
12
|
+
import { sprintf, __, _x } from '@wordpress/i18n';
|
|
13
|
+
import { useMemo } from '@wordpress/element';
|
|
14
|
+
import { closeSmall } from '@wordpress/icons';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal dependencies
|
|
18
|
+
*/
|
|
19
|
+
import type { Form, FormField, NormalizedField } from '../../types';
|
|
20
|
+
import { DataFormLayout } from '../data-form-layout';
|
|
21
|
+
import { isCombinedField } from '../is-combined-field';
|
|
22
|
+
import { DEFAULT_LAYOUT } from '../../normalize-form-fields';
|
|
23
|
+
|
|
24
|
+
function DropdownHeader( {
|
|
25
|
+
title,
|
|
26
|
+
onClose,
|
|
27
|
+
}: {
|
|
28
|
+
title?: string;
|
|
29
|
+
onClose: () => void;
|
|
30
|
+
} ) {
|
|
31
|
+
return (
|
|
32
|
+
<VStack
|
|
33
|
+
className="dataforms-layouts-panel__dropdown-header"
|
|
34
|
+
spacing={ 4 }
|
|
35
|
+
>
|
|
36
|
+
<HStack alignment="center">
|
|
37
|
+
{ title && (
|
|
38
|
+
<Heading level={ 2 } size={ 13 }>
|
|
39
|
+
{ title }
|
|
40
|
+
</Heading>
|
|
41
|
+
) }
|
|
42
|
+
<Spacer />
|
|
43
|
+
{ onClose && (
|
|
44
|
+
<Button
|
|
45
|
+
label={ __( 'Close' ) }
|
|
46
|
+
icon={ closeSmall }
|
|
47
|
+
onClick={ onClose }
|
|
48
|
+
size="small"
|
|
49
|
+
/>
|
|
50
|
+
) }
|
|
51
|
+
</HStack>
|
|
52
|
+
</VStack>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function PanelDropdown< Item >( {
|
|
57
|
+
fieldDefinition,
|
|
58
|
+
popoverAnchor,
|
|
59
|
+
labelPosition = 'side',
|
|
60
|
+
data,
|
|
61
|
+
onChange,
|
|
62
|
+
field,
|
|
63
|
+
}: {
|
|
64
|
+
fieldDefinition: NormalizedField< Item >;
|
|
65
|
+
popoverAnchor: HTMLElement | null;
|
|
66
|
+
labelPosition: 'side' | 'top' | 'none';
|
|
67
|
+
data: Item;
|
|
68
|
+
onChange: ( value: any ) => void;
|
|
69
|
+
field: FormField;
|
|
70
|
+
} ) {
|
|
71
|
+
const fieldLabel = isCombinedField( field )
|
|
72
|
+
? field.label
|
|
73
|
+
: fieldDefinition?.label;
|
|
74
|
+
|
|
75
|
+
const form: Form = useMemo(
|
|
76
|
+
(): Form => ( {
|
|
77
|
+
layout: DEFAULT_LAYOUT,
|
|
78
|
+
fields: isCombinedField( field )
|
|
79
|
+
? field.children
|
|
80
|
+
: // If not explicit children return the field id itself.
|
|
81
|
+
[ { id: field.id } ],
|
|
82
|
+
} ),
|
|
83
|
+
[ field ]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Memoize popoverProps to avoid returning a new object every time.
|
|
87
|
+
const popoverProps = useMemo(
|
|
88
|
+
() => ( {
|
|
89
|
+
// Anchor the popover to the middle of the entire row so that it doesn't
|
|
90
|
+
// move around when the label changes.
|
|
91
|
+
anchor: popoverAnchor,
|
|
92
|
+
placement: 'left-start',
|
|
93
|
+
offset: 36,
|
|
94
|
+
shift: true,
|
|
95
|
+
} ),
|
|
96
|
+
[ popoverAnchor ]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Dropdown
|
|
101
|
+
contentClassName="dataforms-layouts-panel__field-dropdown"
|
|
102
|
+
popoverProps={ popoverProps }
|
|
103
|
+
focusOnMount
|
|
104
|
+
toggleProps={ {
|
|
105
|
+
size: 'compact',
|
|
106
|
+
variant: 'tertiary',
|
|
107
|
+
tooltipPosition: 'middle left',
|
|
108
|
+
} }
|
|
109
|
+
renderToggle={ ( { isOpen, onToggle } ) => (
|
|
110
|
+
<Button
|
|
111
|
+
className="dataforms-layouts-panel__field-control"
|
|
112
|
+
size="compact"
|
|
113
|
+
variant={
|
|
114
|
+
[ 'none', 'top' ].includes( labelPosition )
|
|
115
|
+
? 'link'
|
|
116
|
+
: 'tertiary'
|
|
117
|
+
}
|
|
118
|
+
aria-expanded={ isOpen }
|
|
119
|
+
aria-label={ sprintf(
|
|
120
|
+
// translators: %s: Field name.
|
|
121
|
+
_x( 'Edit %s', 'field' ),
|
|
122
|
+
fieldLabel || ''
|
|
123
|
+
) }
|
|
124
|
+
onClick={ onToggle }
|
|
125
|
+
disabled={ fieldDefinition.readOnly === true }
|
|
126
|
+
accessibleWhenDisabled
|
|
127
|
+
>
|
|
128
|
+
<fieldDefinition.render
|
|
129
|
+
item={ data }
|
|
130
|
+
field={ fieldDefinition }
|
|
131
|
+
/>
|
|
132
|
+
</Button>
|
|
133
|
+
) }
|
|
134
|
+
renderContent={ ( { onClose } ) => (
|
|
135
|
+
<>
|
|
136
|
+
<DropdownHeader title={ fieldLabel } onClose={ onClose } />
|
|
137
|
+
<DataFormLayout
|
|
138
|
+
data={ data }
|
|
139
|
+
form={ form }
|
|
140
|
+
onChange={ onChange }
|
|
141
|
+
>
|
|
142
|
+
{ ( FieldLayout, nestedField ) => (
|
|
143
|
+
<FieldLayout
|
|
144
|
+
key={ nestedField.id }
|
|
145
|
+
data={ data }
|
|
146
|
+
field={ nestedField }
|
|
147
|
+
onChange={ onChange }
|
|
148
|
+
hideLabelFromVision={
|
|
149
|
+
( form?.fields ?? [] ).length < 2
|
|
150
|
+
}
|
|
151
|
+
/>
|
|
152
|
+
) }
|
|
153
|
+
</DataFormLayout>
|
|
154
|
+
</>
|
|
155
|
+
) }
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export default PanelDropdown;
|