diff --git a/resources/dist/build/assets/addon-Dnt1-tBd.js b/resources/dist/build/assets/addon-Dnt1-tBd.js
new file mode 100644
index 0000000..da23103
--- /dev/null
+++ b/resources/dist/build/assets/addon-Dnt1-tBd.js
@@ -0,0 +1 @@
+const c=window.Vue,{BaseTransition:g,BaseTransitionPropsValidators:h,Comment:T,DeprecationTypes:f,EffectScope:v,ErrorCodes:b,ErrorTypeStrings:P,Fragment:R,KeepAlive:y,ReactiveEffect:w,Static:x,Suspense:M,Teleport:I,Text:F,TrackOpTypes:E,Transition:D,TransitionGroup:H,TriggerOpTypes:L,VueElement:k,__esModule:A,assertNumber:V,callWithAsyncErrorHandling:_,callWithErrorHandling:B,camelize:O,capitalize:z,cloneVNode:N,compatUtils:G,compile:U,computed:K,createApp:W,createBlock:p,createCommentVNode:j,createElementBlock:q,createElementVNode:$,createHydrationRenderer:Q,createPropsRestProxy:J,createRenderer:X,createSSRApp:Y,createSlots:Z,createStaticVNode:ee,createTextVNode:te,createVNode:oe,customRef:re,defineAsyncComponent:ie,defineComponent:ne,defineCustomElement:ae,defineEmits:se,defineExpose:le,defineModel:de,defineOptions:ce,defineProps:pe,defineSSRCustomElement:ue,defineSlots:me,devtools:Ce,effect:Se,effectScope:ge,getCurrentInstance:he,getCurrentScope:Te,getCurrentWatcher:fe,getTransitionRawChildren:ve,guardReactiveProps:be,h:Pe,handleError:Re,hasInjectionContext:ye,hydrate:we,hydrateOnIdle:xe,hydrateOnInteraction:Me,hydrateOnMediaQuery:Ie,hydrateOnVisible:Fe,initCustomFormatter:Ee,initDirectivesForSSR:De,inject:He,isMemoSame:Le,isProxy:ke,isReactive:Ae,isReadonly:Ve,isRef:_e,isRuntimeOnly:Be,isShallow:Oe,isVNode:ze,markRaw:Ne,mergeDefaults:Ge,mergeModels:Ue,mergeProps:Ke,nextTick:We,nodeOps:je,normalizeClass:qe,normalizeProps:$e,normalizeStyle:Qe,onActivated:Je,onBeforeMount:Xe,onBeforeUnmount:Ye,onBeforeUpdate:Ze,onDeactivated:et,onErrorCaptured:tt,onMounted:ot,onRenderTracked:rt,onRenderTriggered:it,onScopeDispose:nt,onServerPrefetch:at,onUnmounted:st,onUpdated:lt,onWatcherCleanup:dt,openBlock:u,patchProp:ct,popScopeId:pt,provide:ut,proxyRefs:mt,pushScopeId:Ct,queuePostFlushCb:St,reactive:gt,readonly:ht,ref:Tt,registerRuntimeCompiler:ft,render:vt,renderList:bt,renderSlot:Pt,resolveComponent:Rt,resolveDirective:yt,resolveDynamicComponent:wt,resolveFilter:xt,resolveTransitionHooks:Mt,setBlockTracking:It,setDevtoolsHook:Ft,setTransitionHooks:Et,shallowReactive:Dt,shallowReadonly:Ht,shallowRef:Lt,ssrContextKey:kt,ssrUtils:At,stop:Vt,toDisplayString:_t,toHandlerKey:Bt,toHandlers:Ot,toRaw:zt,toRef:Nt,toRefs:Gt,toValue:Ut,transformVNodeArgs:Kt,triggerRef:Wt,unref:o,useAttrs:jt,useCssModule:qt,useCssVars:$t,useHost:Qt,useId:Jt,useModel:Xt,useSSRContext:Yt,useShadowRoot:Zt,useSlots:eo,useTemplateRef:to,useTransitionState:oo,vModelCheckbox:ro,vModelDynamic:io,vModelRadio:no,vModelSelect:ao,vModelText:so,vShow:lo,version:co,warn:po,watch:uo,watchEffect:mo,watchPostEffect:Co,watchSyncEffect:So,withAsyncContext:go,withCtx:ho,withDefaults:To,withDirectives:fo,withKeys:vo,withMemo:bo,withModifiers:Po,withScopeId:Ro}=c,{Fieldtype:e,IndexFieldtype:yo,FieldtypeMixin:wo,HasActionsMixin:xo,HasInputOptionsMixin:Mo,HasPreferencesMixin:Io,IndexFieldtypeMixin:Fo,InlineEditForm:Eo,DateFormatter:Do,NumberFormatter:Ho,ItemActions:Lo,RelatedItem:ko,RestoreRevision:Ao,RevisionHistory:Vo,RevisionPreview:_o,SaveButtonOptions:Bo,SortableList:Oo,requireElevatedSession:zo,requireElevatedSessionIf:No,clone:Go,debounce:Uo,deepClone:Ko,resetValuesFromResponse:Wo}=__STATAMIC__.core,{Alert:jo,AuthCard:qo,Avatar:$o,Badge:Qo,Button:Jo,ButtonGroup:Xo,Calendar:Yo,Card:Zo,CardList:er,CardListItem:tr,CardPanel:or,CharacterCounter:rr,Checkbox:ir,CheckboxGroup:nr,CodeEditor:ar,Combobox:m,CommandPaletteItem:sr,ConfirmationModal:lr,Context:dr,ContextFooter:cr,ContextHeader:pr,ContextItem:ur,ContextLabel:mr,ContextMenu:Cr,ContextSeparator:Sr,CreateForm:gr,DatePicker:hr,DateRangePicker:Tr,Description:fr,DocsCallout:vr,DragHandle:br,Dropdown:Pr,DropdownItem:Rr,DropdownLabel:yr,DropdownMenu:wr,DropdownSeparator:xr,DropdownFooter:Mr,DropdownHeader:Ir,Editable:Fr,ErrorMessage:Er,EmptyStateItem:Dr,EmptyStateMenu:Hr,Field:Lr,Header:kr,Heading:Ar,HoverCard:Vr,Icon:_r,Input:Br,InputGroup:Or,InputGroupAppend:zr,InputGroupPrepend:Nr,Label:Gr,Listing:Ur,ListingCustomizeColumns:Kr,ListingFilters:Wr,ListingHeaderCell:jr,ListingPagination:qr,ListingPresets:$r,ListingPresetTrigger:Qr,ListingRowActions:Jr,ListingSearch:Xr,ListingTable:Yr,ListingTableBody:Zr,ListingTableHead:ei,ListingToggleAll:ti,LivePreview:oi,LivePreviewPopout:ri,MiddleEllipsis:ii,Modal:ni,ModalClose:ai,ModalTitle:si,Pagination:li,Panel:di,PanelFooter:ci,PanelHeader:pi,Popover:ui,PublishComponents:mi,PublishContainer:Ci,publishContextKey:Si,injectPublishContext:gi,PublishField:hi,PublishFields:Ti,PublishFieldsProvider:fi,PublishForm:vi,PublishLocalizations:bi,PublishSections:Pi,PublishTabs:Ri,Radio:yi,RadioGroup:wi,Select:xi,Separator:Mi,Slider:Ii,Skeleton:Fi,SplitterGroup:Ei,SplitterPanel:Di,SplitterResizeHandle:Hi,StatusIndicator:Li,Subheading:ki,Switch:Ai,TabContent:Vi,Stack:_i,StackClose:Bi,StackHeader:Oi,StackFooter:zi,StackContent:Ni,Table:Gi,TableCell:Ui,TableColumn:Ki,TableColumns:Wi,TableRow:ji,TableRows:qi,TabList:$i,TabProvider:Qi,Tabs:Ji,TabTrigger:Xi,Text:Yi,Textarea:Zi,TimePicker:en,TimezoneHoverCard:tn,Timezones:on,ToggleGroup:rn,ToggleItem:nn,Widget:an,registerIconSet:sn,registerIconSetFromStrings:ln}=__STATAMIC__.ui,C={__name:"MailerLiteFormFieldsFieldtype",props:e.props,emits:e.emits,setup(r,{expose:i,emit:n}){const a=n,s=r,{expose:l,update:d}=e.use(a,s);return i(l),(t,S)=>(u(),p(o(m),{class:"w-full",clearable:"",searchable:"","model-value":t.value,options:t.meta.options,"option-label":"label","option-value":"value","onUpdate:modelValue":o(d)},null,8,["model-value","options","onUpdate:modelValue"]))}};Statamic.booting(()=>{Statamic.$components.register("mailer_lite_form_fields-fieldtype",C)});
diff --git a/resources/dist/build/assets/addon-l0sNRNKZ.js b/resources/dist/build/assets/addon-l0sNRNKZ.js
deleted file mode 100644
index 8b13789..0000000
--- a/resources/dist/build/assets/addon-l0sNRNKZ.js
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/resources/dist/build/manifest.json b/resources/dist/build/manifest.json
index 3de1dd2..06f944f 100644
--- a/resources/dist/build/manifest.json
+++ b/resources/dist/build/manifest.json
@@ -1,6 +1,6 @@
{
"resources/js/addon.js": {
- "file": "assets/addon-l0sNRNKZ.js",
+ "file": "assets/addon-Dnt1-tBd.js",
"name": "addon",
"src": "resources/js/addon.js",
"isEntry": true
diff --git a/resources/js/addon.js b/resources/js/addon.js
index 459b729..36ada1c 100644
--- a/resources/js/addon.js
+++ b/resources/js/addon.js
@@ -1,3 +1,5 @@
-// No custom Control Panel components. MailerLite configuration lives on the
-// native form configure page, injected via Form::appendConfigFields() in the
-// addon service provider.
+import MailerLiteFormFieldsFieldtype from './components/fieldtypes/MailerLiteFormFieldsFieldtype.vue';
+
+Statamic.booting(() => {
+ Statamic.$components.register('mailer_lite_form_fields-fieldtype', MailerLiteFormFieldsFieldtype);
+});
diff --git a/resources/js/components/fieldtypes/MailerLiteFormFieldsFieldtype.vue b/resources/js/components/fieldtypes/MailerLiteFormFieldsFieldtype.vue
new file mode 100644
index 0000000..cde3740
--- /dev/null
+++ b/resources/js/components/fieldtypes/MailerLiteFormFieldsFieldtype.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/src/Fieldtypes/MailerLiteFormFields.php b/src/Fieldtypes/MailerLiteFormFields.php
new file mode 100644
index 0000000..b616ee7
--- /dev/null
+++ b/src/Fieldtypes/MailerLiteFormFields.php
@@ -0,0 +1,51 @@
+}
+ */
+ public function preload(): array
+ {
+ return [
+ 'options' => $this->formFieldOptions(),
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ private function formFieldOptions(): array
+ {
+ $form = request()->route('form');
+
+ if (is_string($form)) {
+ $form = Form::find($form);
+ }
+
+ if (! $form instanceof FormContract) {
+ return [];
+ }
+
+ return $form->blueprint()->fields()->all()
+ ->map(fn (Field $field): array => [
+ 'label' => $field->display(),
+ 'value' => $field->handle(),
+ ])
+ ->values()
+ ->all();
+ }
+}
diff --git a/src/StatamicMailerLiteServiceProvider.php b/src/StatamicMailerLiteServiceProvider.php
index 03b0de8..02718dd 100644
--- a/src/StatamicMailerLiteServiceProvider.php
+++ b/src/StatamicMailerLiteServiceProvider.php
@@ -3,10 +3,8 @@
namespace Concept7\StatamicMailerLite;
use Concept7\StatamicMailerLite\Listeners\SubmissionCreatedListener;
-use Statamic\Contracts\Forms\Form as FormContract;
use Statamic\Events\SubmissionCreated;
use Statamic\Facades\Form;
-use Statamic\Fields\Field;
use Statamic\Providers\AddonServiceProvider;
class StatamicMailerLiteServiceProvider extends AddonServiceProvider
@@ -24,28 +22,24 @@ class StatamicMailerLiteServiceProvider extends AddonServiceProvider
public function bootAddon(): void
{
- // bootAddon() already runs inside Statamic's booted phase, so the form
- // repository is available and config fields can be appended directly.
- // Wrapping this in Statamic::booted() would queue a callback that never
- // runs — runBootedCallbacks() is already mid-drain at this point.
- Form::all()->each(fn (FormContract $form) => Form::appendConfigFields(
- $form->handle(),
+ // Registered with the '*' wildcard so booting never enumerates forms —
+ // with statamic/eloquent-driver that would query the database, which
+ // may be unreachable or unmigrated while the app still has to boot
+ // (package:discover, Larastan analysis, first deploy).
+ Form::appendConfigFields(
+ '*',
'MailerLite',
- static::configFields($form),
- ));
+ static::configFields(),
+ );
}
/**
- * Build the MailerLite section appended to a form's native configure page.
+ * Build the MailerLite section appended to every form's native configure page.
*
* @return array>
*/
- public static function configFields(FormContract $form): array
+ public static function configFields(): array
{
- $formFieldOptions = $form->blueprint()->fields()->all()
- ->mapWithKeys(fn (Field $field): array => [$field->handle() => $field->display()])
- ->all();
-
return [
'mailerlite_enabled' => [
'type' => 'toggle',
@@ -70,10 +64,8 @@ public static function configFields(FormContract $form): array
[
'handle' => 'form_field',
'field' => [
- 'type' => 'select',
+ 'type' => 'mailer_lite_form_fields',
'display' => __('Form field'),
- 'options' => $formFieldOptions,
- 'clearable' => false,
],
],
[
diff --git a/tests/Feature/MailerLiteConfigFieldsTest.php b/tests/Feature/MailerLiteConfigFieldsTest.php
index 040817f..854cf35 100644
--- a/tests/Feature/MailerLiteConfigFieldsTest.php
+++ b/tests/Feature/MailerLiteConfigFieldsTest.php
@@ -1,12 +1,9 @@
toHaveKeys(['mailerlite_enabled', 'mailerlite_group', 'mailerlite_field_mapping']);
expect($fields['mailerlite_enabled']['type'])->toBe('toggle');
@@ -17,7 +14,7 @@
});
it('uses a single-select MailerLite field inside the mapping grid', function () {
- $fields = StatamicMailerLiteServiceProvider::configFields(Form::make('contact'));
+ $fields = StatamicMailerLiteServiceProvider::configFields();
$mailerLiteField = collect($fields['mailerlite_field_mapping']['fields'])
->firstWhere('handle', 'mailerlite_field')['field'];
@@ -26,18 +23,11 @@
expect($mailerLiteField['max_items'])->toBe(1);
});
-it('offers the form fields as form_field options', function () {
- $blueprint = Blueprint::makeFromFields([
- 'email' => ['type' => 'text'],
- 'name' => ['type' => 'text'],
- ])->setHandle('contact')->setNamespace('forms');
+it('uses the dynamic form fields fieldtype inside the mapping grid', function () {
+ $fields = StatamicMailerLiteServiceProvider::configFields();
- $form = Mockery::mock(FormContract::class);
- $form->shouldReceive('handle')->andReturn('contact');
- $form->shouldReceive('blueprint')->andReturn($blueprint);
+ $formField = collect($fields['mailerlite_field_mapping']['fields'])
+ ->firstWhere('handle', 'form_field')['field'];
- $options = collect(StatamicMailerLiteServiceProvider::configFields($form)['mailerlite_field_mapping']['fields'])
- ->firstWhere('handle', 'form_field')['field']['options'];
-
- expect($options)->toHaveKeys(['email', 'name']);
+ expect($formField['type'])->toBe('mailer_lite_form_fields');
});
diff --git a/tests/Feature/MailerLiteFormFieldsFieldtypeTest.php b/tests/Feature/MailerLiteFormFieldsFieldtypeTest.php
new file mode 100644
index 0000000..47df0c6
--- /dev/null
+++ b/tests/Feature/MailerLiteFormFieldsFieldtypeTest.php
@@ -0,0 +1,45 @@
+ ['type' => 'text'],
+ 'name' => ['type' => 'text'],
+ ])->setHandle('contact')->setNamespace('forms');
+
+ $form = Mockery::mock(FormContract::class);
+ $form->shouldReceive('blueprint')->andReturn($blueprint);
+
+ $route = new Route(['GET'], 'cp/forms/{form}/edit', []);
+ $route->parameters = ['form' => $form];
+
+ request()->setRouteResolver(fn (): Route => $route);
+
+ $options = (new MailerLiteFormFields)->preload()['options'];
+
+ expect(collect($options)->pluck('value')->all())->toBe([
+ 'email',
+ 'name',
+ ]);
+ expect(collect($options)->pluck('label')->all())->toBe([
+ 'Email',
+ 'Name',
+ ]);
+});
+
+it('preloads an empty option list when the route has no form', function () {
+ expect((new MailerLiteFormFields)->preload()['options'])->toBe([]);
+});
+
+it('preloads an empty option list when a form handle cannot be resolved', function () {
+ $route = new Route(['GET'], 'cp/forms/{form}/edit', []);
+ $route->parameters = ['form' => 'missing'];
+
+ request()->setRouteResolver(fn (): Route => $route);
+
+ expect((new MailerLiteFormFields)->preload()['options'])->toBe([]);
+});
diff --git a/tests/Feature/SectionRegistrationTest.php b/tests/Feature/SectionRegistrationTest.php
index 8ad9c39..9793585 100644
--- a/tests/Feature/SectionRegistrationTest.php
+++ b/tests/Feature/SectionRegistrationTest.php
@@ -3,11 +3,7 @@
use Concept7\StatamicMailerLite\StatamicMailerLiteServiceProvider;
use Statamic\Facades\Form;
-it('appends the MailerLite section to forms during bootAddon', function () {
- $form = Form::make('contact');
-
- Form::partialMock()->shouldReceive('all')->andReturn(collect([$form]));
-
+it('appends the MailerLite section to every form during bootAddon', function () {
app()->getProvider(StatamicMailerLiteServiceProvider::class)->bootAddon();
$sections = Form::extraConfigFor('contact');
@@ -16,3 +12,9 @@
expect(array_keys($sections['mailer_lite']['fields']))
->toBe(['mailerlite_enabled', 'mailerlite_group', 'mailerlite_field_mapping']);
});
+
+it('boots without enumerating forms', function () {
+ Form::partialMock()->shouldReceive('all')->never();
+
+ app()->getProvider(StatamicMailerLiteServiceProvider::class)->bootAddon();
+});