@@ -61,56 +61,16 @@ echo $set->render();
6161$set->save( $_POST['my_plugin'] ?? [] );
6262```
6363
64- ## Validation
65-
66- Sanitising coerces; it does not refuse. A field that must hold something, or
67- something of a particular shape, says so. A value that fails is left where it
68- was — the stored one stays, every other field is saved — and the reason is
69- kept for the next render:
70-
71- ``` php
72- $set = new FieldSet(
73- [
74- 'contact' => [ 'type' => 'email', 'label' => __( 'Contact', 'my-plugin' ), 'required' => true ],
75- 'handle' => [ 'type' => 'text', 'label' => __( 'Handle', 'my-plugin' ), 'validate' => 'slug' ],
76- 'api_key' => [
77- 'type' => 'text',
78- 'label' => __( 'API key', 'my-plugin' ),
79- 'validate' => static fn( $value, $field ) => str_starts_with( $value, 'sk_' ) ? true : __( 'An API key starts with sk_.', 'my-plugin' ),
80- ],
81- 'notify' => [ 'type' => 'tags', 'label' => __( 'Notify', 'my-plugin' ), 'validate' => 'email' ],
82- ],
83- new OptionContext( 'my_plugin' ),
84- 'my_plugin'
85- );
86-
87- $set->save( $_POST['my_plugin'] ?? [] );
88-
89- // Rendered after a save in the same request, each failing field is marked
90- // and shows its message. errors() has the messages for a notice of your own.
91- echo $set->render();
92- ```
93-
94- The rules are ` email ` , ` url ` , ` numeric ` , ` integer ` , ` slug ` and ` alphanumeric ` .
95- A callable is given the sanitised value and the field, and returns ` true ` , a
96- message, or a ` WP_Error ` . On ` tags ` and ` list ` the rule is applied to each
97- item. To check a submission without storing any of it, ` validate() ` takes the
98- same input as ` save() ` and returns the messages keyed by field.
99-
100- ` required ` and ` validate ` are checked on the set's own fields. A field inside
101- a group or a repeater row is marked required in the markup, which the browser
102- enforces, but is not checked on the server.
103-
10464## Field types
10565
10666Text and numeric
107- : ` text ` ` email ` ` url ` ` tel ` ` password ` ` hidden ` ` number ` ` range ` ` textarea ` ` code ` ` wysiwyg ` ` code_generator `
67+ : ` text ` ` email ` ` url ` ` tel ` ` password ` ` hidden ` ` number ` ` percentage ` ` range ` ` textarea ` ` code ` ` wysiwyg ` ` code_generator `
10868
10969Date, time and colour
11070: ` date ` ` time ` ` datetime ` ` color ` ` gradient ` ` date_range ` ` time_range `
11171
11272Choice
113- : ` select ` ` enhanced_select ` ` select_multiple ` ` checkbox ` ` toggle ` ` radio ` ` checkbox_group ` ` button_group ` ` card_choice ` ` country ` ` currency `
73+ : ` select ` ` enhanced_select ` ` select_multiple ` ` checkbox ` ` toggle ` ` radio ` ` checkbox_group ` ` button_group ` ` card_choice ` ` country ` ` region ` ` currency `
11474
11575Relational — one search endpoint behind all of them
11676: ` post ` ` page ` ` user ` ` taxonomy ` ` ajax ` ` tags `
@@ -127,86 +87,55 @@ Layout and display
12787Purpose-built
12888: ` license ` ` email_editor ` ` action_button `
12989
130- ## Options from a preset
131-
132- The lists every settings screen ends up needing — the site's roles, its
133- timezones, its page templates — are presets, and a select asks for one by
134- name rather than building it again:
135-
136- ``` php
137- use ArrayPress\FieldKit\FieldSet;
138- use ArrayPress\FieldKit\Context\OptionContext;
139-
140- $set = new FieldSet(
141- [
142- 'notify_role' => [ 'type' => 'select', 'label' => __( 'Notify', 'my-plugin' ), 'options' => 'roles' ],
143- 'timezone' => [ 'type' => 'select', 'label' => __( 'Timezone', 'my-plugin' ), 'options' => 'timezones' ],
144- 'country' => [ 'type' => 'country', 'label' => __( 'Country', 'my-plugin' ), 'continents' => true ],
145- 'currency' => [ 'type' => 'currency', 'label' => __( 'Currency', 'my-plugin' ) ],
146- ],
147- new OptionContext( 'my_plugin' ),
148- 'my_plugin'
149- );
150- ```
15190
152- The built-ins are ` roles ` , ` post_types ` , ` taxonomies ` , ` pages ` , ` timezones ` ,
153- ` image_sizes ` , ` page_templates ` , ` cron_schedules ` , ` countries ` and
154- ` currencies ` . The last two draw on ` arraypress/wp-countries ` and
155- ` arraypress/wp-money ` ; the ` country ` and ` currency ` types use them by default.
91+ ## Beyond the quick start
15692
157- A preset is resolved when the field renders, not when it is declared, and its
158- options pass through the ` field_kit_preset_options ` filter. Register your own
159- with ` Presets::register( 'products', fn() => $options ) ` and any field can say
160- ` 'options' => 'products' ` .
161-
162- ## A unit in the box, a reveal, a count
163-
164- Any single-line input takes a ` prefix ` or a ` suffix ` , drawn inside the box
165- and announced with the value. A ` percentage ` is a number from 0 to 100 with
166- the sign already in it. A password shows what is typed on request, and
167- anything with a ` maxlength ` counts as you go.
93+ Sanitising coerces; it does not refuse. ` required ` and ` validate ` do the
94+ refusing on the server: a value that fails is left where it was, every other
95+ field is saved, and the message is kept for the next render. Options can name
96+ a preset instead of listing choices. Any single-line input takes a ` prefix `
97+ or a ` suffix ` , a password can be revealed, and a ` maxlength ` shows a count. A
98+ ` region ` follows the country beside it.
16899
169100``` php
170101$set = new FieldSet(
171102 [
103+ 'contact' => [ 'type' => 'email', 'label' => __( 'Contact', 'my-plugin' ), 'required' => true ],
104+ 'notify' => [ 'type' => 'tags', 'label' => __( 'Notify', 'my-plugin' ), 'validate' => 'email' ],
105+ 'api_key' => [
106+ 'type' => 'password',
107+ 'label' => __( 'API key', 'my-plugin' ),
108+ 'reveal' => true,
109+ 'validate' => static fn( $value ) => str_starts_with( $value, 'sk_' ) ? true : __( 'An API key starts with sk_.', 'my-plugin' ),
110+ ],
111+ 'role' => [ 'type' => 'select', 'label' => __( 'Notify role', 'my-plugin' ), 'options' => 'roles' ],
172112 'price' => [ 'type' => 'number', 'label' => __( 'Price', 'my-plugin' ), 'prefix' => '$', 'step' => 0.01 ],
173113 'discount' => [ 'type' => 'percentage', 'label' => __( 'Discount', 'my-plugin' ) ],
174- 'expires' => [ 'type' => 'number', 'label' => __( 'Expires after', 'my-plugin' ), 'suffix' => 'days' ],
175- 'api_key' => [ 'type' => 'password', 'label' => __( 'API key', 'my-plugin' ), 'reveal' => true ],
176114 'tagline' => [ 'type' => 'text', 'label' => __( 'Tagline', 'my-plugin' ), 'maxlength' => 80 ],
115+ 'country' => [ 'type' => 'country', 'label' => __( 'Country', 'my-plugin' ), 'continents' => true ],
116+ 'region' => [ 'type' => 'region', 'label' => __( 'State or province', 'my-plugin' ), 'country_key' => 'country' ],
117+ 'currency' => [ 'type' => 'currency', 'label' => __( 'Currency', 'my-plugin' ) ],
177118 ],
178119 new OptionContext( 'my_plugin' ),
179120 'my_plugin'
180121);
181- ```
182-
183- ## A country and its regions
184122
185- A ` region ` is a state, a province or a county. Given a fixed ` country ` it is
186- a select over that country's subdivisions, storing the code the way ` country `
187- stores its own. Given ` country_key ` — the key of the country field beside it
188- — it follows whatever is chosen there: a select where the country has
189- subdivisions on file, a text input where it does not, swapped by the script
190- on load and on every change.
191-
192- ``` php
193- use ArrayPress\FieldKit\FieldSet;
194- use ArrayPress\FieldKit\Context\OptionContext;
123+ $set->save( $_POST['my_plugin'] ?? [] );
195124
196- $set = new FieldSet(
197- [
198- 'country' => [ 'type' => 'country', 'label' => __( 'Country', 'my-plugin' ) ],
199- 'region' => [ 'type' => 'region', 'label' => __( 'State or province', 'my-plugin' ), 'country_key' => 'country' ],
200- ],
201- new OptionContext( 'my_plugin' ),
202- 'my_plugin'
203- );
125+ // Rendered after a save in the same request, each failing field is marked
126+ // and shows its message; errors() has them for a notice of your own.
127+ echo $set->render();
204128```
205129
206- Without the script the text input is what submits, so a typed code still
207- saves. Inside a repeater the lookup is scoped to the row, so each row's
208- region follows that row's country. The subdivisions are WooCommerce's, by
209- way of ` arraypress/wp-countries ` .
130+ Rules are ` email ` , ` url ` , ` numeric ` , ` integer ` , ` slug ` and ` alphanumeric ` ; a
131+ callable returns ` true ` , a message or a ` WP_Error ` ; on ` tags ` and ` list ` the
132+ rule applies to each item; ` validate() ` checks without storing. Presets are
133+ ` roles ` , ` post_types ` , ` taxonomies ` , ` pages ` , ` timezones ` , ` image_sizes ` ,
134+ ` page_templates ` , ` cron_schedules ` , ` countries ` and ` currencies ` , resolved on
135+ render and filterable, with ` Presets::register() ` for your own. Countries and
136+ regions come from ` arraypress/wp-countries ` , currencies from
137+ ` arraypress/wp-money ` ; a region is a select where the country has
138+ subdivisions on file and a text input where it does not.
210139
211140## Requirements
212141
0 commit comments