Skip to content

Commit 1b98b62

Browse files
committed
Merge branch 'feature/presets'
2 parents 93427a2 + 9a1d8b8 commit 1b98b62

15 files changed

Lines changed: 1682 additions & 31 deletions

‎README.md‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ wherever the screen happens to be.
2525
* Group fields into tabs or collapsible sections
2626
* Sit two short fields on one line with `'width' => 'half'`
2727
* Take an amount with its currency symbol inside the control
28+
* Fill a select from a named preset — roles, timezones, countries — with one string
2829
* Fold a repeater's rows away, each titled by one of its own fields
2930
* Reorder a repeater by dragging its handle, or from the keyboard
3031
* Refuse a value that fails a rule — required, email, url, your own callable — and say why
@@ -107,7 +108,7 @@ Date, time and colour
107108
: `date` `time` `datetime` `color` `gradient` `date_range` `time_range`
108109

109110
Choice
110-
: `select` `enhanced_select` `select_multiple` `checkbox` `toggle` `radio` `checkbox_group` `button_group` `card_choice`
111+
: `select` `enhanced_select` `select_multiple` `checkbox` `toggle` `radio` `checkbox_group` `button_group` `card_choice` `country` `currency`
111112

112113
Relational — one search endpoint behind all of them
113114
: `post` `page` `user` `taxonomy` `ajax` `tags`
@@ -124,6 +125,38 @@ Layout and display
124125
Purpose-built
125126
: `license` `email_editor` `action_button`
126127

128+
## Options from a preset
129+
130+
The lists every settings screen ends up needing — the site's roles, its
131+
timezones, its page templates — are presets, and a select asks for one by
132+
name rather than building it again:
133+
134+
```php
135+
use ArrayPress\FieldKit\FieldSet;
136+
use ArrayPress\FieldKit\Context\OptionContext;
137+
138+
$set = new FieldSet(
139+
[
140+
'notify_role' => [ 'type' => 'select', 'label' => __( 'Notify', 'my-plugin' ), 'options' => 'roles' ],
141+
'timezone' => [ 'type' => 'select', 'label' => __( 'Timezone', 'my-plugin' ), 'options' => 'timezones' ],
142+
'country' => [ 'type' => 'country', 'label' => __( 'Country', 'my-plugin' ), 'continents' => true ],
143+
'currency' => [ 'type' => 'currency', 'label' => __( 'Currency', 'my-plugin' ) ],
144+
],
145+
new OptionContext( 'my_plugin' ),
146+
'my_plugin'
147+
);
148+
```
149+
150+
The built-ins are `roles`, `post_types`, `taxonomies`, `pages`, `timezones`,
151+
`image_sizes`, `page_templates`, `cron_schedules`, `countries` and
152+
`currencies`. The last two draw on `arraypress/wp-countries` and
153+
`arraypress/wp-money`; the `country` and `currency` types use them by default.
154+
155+
A preset is resolved when the field renders, not when it is declared, and its
156+
options pass through the `field_kit_preset_options` filter. Register your own
157+
with `Presets::register( 'products', fn() => $options )` and any field can say
158+
`'options' => 'products'`.
159+
127160
## Requirements
128161

129162
* PHP 8.3 or later

‎composer.json‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@
1111
}
1212
],
1313
"require": {
14-
"php": ">=8.3"
14+
"php": ">=8.3",
15+
"arraypress/wp-countries": "^1.0"
1516
},
1617
"require-dev": {
1718
"phpcompatibility/phpcompatibility-wp": "^2.1",
1819
"phpunit/phpunit": "^12.0",
1920
"squizlabs/php_codesniffer": "^3.13.5",
2021
"wp-coding-standards/wpcs": "^3.4"
2122
},
23+
"suggest": {
24+
"arraypress/wp-money": "Supplies the currencies preset and the options of the currency field type."
25+
},
2226
"autoload": {
2327
"psr-4": {
2428
"ArrayPress\\FieldKit\\": "src/"
@@ -29,7 +33,8 @@
2933
"ArrayPress\\FieldKit\\Tests\\": "tests/"
3034
},
3135
"exclude-from-classmap": [
32-
"/tests/bootstrap.php"
36+
"/tests/bootstrap.php",
37+
"/tests/stubs/"
3338
]
3439
},
3540
"scripts": {

‎src/Field.php‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace ArrayPress\FieldKit;
1414

1515
use ArrayPress\FieldKit\Contracts\FieldType;
16+
use ArrayPress\FieldKit\Support\Presets;
1617

1718
/**
1819
* A single field, normalized once and then read-only.
@@ -429,13 +430,24 @@ public function placeholder(): string {
429430
* Choice options as value => label.
430431
*
431432
* Accepts a callable so options can be resolved at render time rather
432-
* than at registration, which is what dynamic sources need.
433+
* than at registration, which is what dynamic sources need. Accepts a
434+
* name, too -- `'options' => 'roles'` -- which is a preset, built in or
435+
* registered by the consumer, and resolved the same way.
436+
*
437+
* The name is checked before the callable, because a string is a
438+
* callable too whenever a function of that name exists: a preset called
439+
* `pages` must not turn into a call to some plugin's `pages()`. A
440+
* function name that is not a preset still works as it always did.
433441
*
434442
* @return array<string, string>
435443
*/
436444
public function options(): array {
437445
$options = $this->get( 'options', [] );
438446

447+
if ( is_string( $options ) && '' !== $options && ( Presets::has( $options ) || ! is_callable( $options ) ) ) {
448+
return Presets::options( $options, $this );
449+
}
450+
439451
if ( is_callable( $options ) ) {
440452
$options = $options( $this );
441453
}

‎src/Registry.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ final class Registry {
6262
'radio' => Types\RadioType::class,
6363
'checkbox_group' => Types\CheckboxGroupType::class,
6464
'button_group' => Types\ButtonGroupType::class,
65+
'country' => Types\CountryType::class,
66+
'currency' => Types\CurrencyType::class,
6567

6668
// Media.
6769
'image' => Types\ImageType::class,

0 commit comments

Comments
 (0)