Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
149 commits
Select commit Hold shift + click to select a range
6217c48
extract VueComponent into Statamic\Support for reuse beyond widgets
duncanmcclean Jul 21, 2026
c36dab8
connections config
duncanmcclean Jul 21, 2026
c843711
concept of a "form connection"
duncanmcclean Jul 21, 2026
738f566
connections index
duncanmcclean Jul 21, 2026
6182915
connection show page
duncanmcclean Jul 21, 2026
56cc9bf
add webhooks connection
duncanmcclean Jul 21, 2026
ea95006
delete some components we don't need anymore
duncanmcclean Jul 21, 2026
d2cdcca
update webhook tests
duncanmcclean Jul 21, 2026
0715b38
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Jul 22, 2026
4aba03f
ability to duplicate webhooks
duncanmcclean Jul 22, 2026
d1a071b
add `count()` method when registering connections
duncanmcclean Jul 22, 2026
46eeec7
move translation calls inside getter to avoid boot errors
duncanmcclean Jul 22, 2026
0ffbf29
extract list & logic into re-usable components
duncanmcclean Jul 22, 2026
e176c03
add email connection
duncanmcclean Jul 22, 2026
c4af17c
combine slots and avoid exporting internal component
duncanmcclean Jul 22, 2026
d3e0676
extract logic conditions summary
duncanmcclean Jul 22, 2026
5c45400
add "connections" column to forms index
duncanmcclean Jul 22, 2026
388c398
support conditions for webhooks
duncanmcclean Jul 22, 2026
30bb04b
create `ConnectionLogic` class to handle logic stuff on the backend
duncanmcclean Jul 22, 2026
07d7327
avoid triggering a save by hitting return
duncanmcclean Jul 22, 2026
62de534
avoid leaking submission data in webhook payload example
duncanmcclean Jul 22, 2026
d7d36c1
refactor how form connections are registered
duncanmcclean Jul 22, 2026
29d7e78
chain file upload & connection jobs together
duncanmcclean Jul 22, 2026
5c6b7e8
garbage collect form file uploads that aren't deleted by `DeleteTempo…
duncanmcclean Jul 22, 2026
1840e70
exclude informational and structural fieldtypes from email fields loop
duncanmcclean Jul 22, 2026
335e65b
make emails fields dropdowns so users can select fields, rather than …
duncanmcclean Jul 23, 2026
16c4271
only show "x" button when hovering over conditions
duncanmcclean Jul 23, 2026
1615514
add validation to email address fields
duncanmcclean Jul 23, 2026
807f565
make sender field clearable
duncanmcclean Jul 23, 2026
eea1cea
rename
duncanmcclean Jul 23, 2026
eb895f1
mark as deprecated
duncanmcclean Jul 23, 2026
46ff8ea
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Jul 23, 2026
a31a7f3
formatting
duncanmcclean Jul 23, 2026
ad589c8
add missing function import
duncanmcclean Jul 23, 2026
5ce9ef6
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Jul 23, 2026
55d6ae3
fix file upload garbage collection
duncanmcclean Jul 23, 2026
66848a3
don't split from field. it doesn't need it
duncanmcclean Jul 23, 2026
170a760
grab a fresh copy of the submission before sending emails
duncanmcclean Jul 23, 2026
df5f35b
fix failing tests
duncanmcclean Jul 27, 2026
7fba2a5
make phpstan happy
duncanmcclean Jul 27, 2026
1aa857a
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 5, 2026
e0b6204
connection rows should be expanded when you visit the page
duncanmcclean Aug 5, 2026
70f25e9
ensure all emails/webhook rows have ids
duncanmcclean Aug 5, 2026
9683c0a
rename
duncanmcclean Aug 5, 2026
82ddf76
typehints
duncanmcclean Aug 5, 2026
9bfed2c
wip
duncanmcclean Aug 5, 2026
7d1e005
be consistent. use a blueprint here too.
duncanmcclean Aug 5, 2026
d706918
wip
duncanmcclean Aug 5, 2026
78a0193
show confirmation modal before deleting emails/webhooks
duncanmcclean Aug 5, 2026
917c6ce
clean up field instructions
duncanmcclean Aug 5, 2026
36742d7
avoid tracking dirty state on the publish container. we're already tr…
duncanmcclean Aug 5, 2026
009daa0
wip
duncanmcclean Aug 6, 2026
4844526
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 6, 2026
ee76e7a
export `conditionsSummary`
duncanmcclean Aug 6, 2026
db1141b
todo
duncanmcclean Aug 6, 2026
861e849
wip
duncanmcclean Aug 6, 2026
da22b07
wip
duncanmcclean Aug 6, 2026
36a8bdf
improve form fields dropdown
duncanmcclean Aug 7, 2026
5d1f31e
Merge branch '6.x' into forms-2-connections
duncanmcclean Aug 7, 2026
2d2273a
wip
duncanmcclean Aug 7, 2026
64906f4
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 7, 2026
fa30aa9
wip
duncanmcclean Aug 7, 2026
d6d65a1
legacy email configs now get an id
duncanmcclean Aug 7, 2026
ffe0497
skip emails with no resolvable recipients
duncanmcclean Aug 7, 2026
1145c55
wip
duncanmcclean Aug 7, 2026
6c79964
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 7, 2026
5956794
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 10, 2026
309e2e9
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 10, 2026
e721358
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 12, 2026
90669a4
wip
duncanmcclean Aug 12, 2026
e5a9fce
wip
duncanmcclean Aug 12, 2026
51a1b1a
address code review feedback on connections
duncanmcclean Aug 12, 2026
bc3bc98
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 13, 2026
a2ca5a1
align form connections with core extension conventions
duncanmcclean Aug 13, 2026
9b43992
fold the enabled check into `ConnectionLogic::passes()`
duncanmcclean Aug 13, 2026
a0ca056
rework connection components around rows and rules
duncanmcclean Aug 13, 2026
e67c0d7
improve connection empty states
duncanmcclean Aug 13, 2026
8df215e
handle legacy email configs without a `to` key
duncanmcclean Aug 13, 2026
84e169c
return null from `Form::email()` when no emails are configured
duncanmcclean Aug 13, 2026
62553c3
validate `enabled` and `conditions` when updating connections
duncanmcclean Aug 13, 2026
e0e1a95
remove `aria-hidden` from the connection rule comboboxes
duncanmcclean Aug 13, 2026
dd8f1d3
reserve the `connections` and `fields` config keys
duncanmcclean Aug 13, 2026
2d0f0b7
exclude partial submissions from the example webhook payload
duncanmcclean Aug 13, 2026
fcc7e56
remove the unused `RoutelessConnection` fixture
duncanmcclean Aug 13, 2026
908ab67
drop legacy email configs that aren't arrays
duncanmcclean Aug 13, 2026
e29787e
test the example payload ignores partial submissions
duncanmcclean Aug 13, 2026
b50174f
allow a null `verify_ssl` when updating webhooks
duncanmcclean Aug 13, 2026
54a0cfe
give the connect grid tiles an accessible name
duncanmcclean Aug 13, 2026
796a52f
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 17, 2026
1689741
add email body field
duncanmcclean Aug 17, 2026
ed60151
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 17, 2026
b5cc0ff
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 24, 2026
3df5f65
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 24, 2026
cc46989
move connection saving into `FormConnectController` via `preProcess()…
duncanmcclean Aug 25, 2026
a2f15d9
move connection save handling to the connect page
duncanmcclean Aug 25, 2026
873c48c
make `show` page `edit` instead
duncanmcclean Aug 25, 2026
40d298e
clean up connection component wiring — row errors from `ConnectionRow…
duncanmcclean Aug 25, 2026
ac1c67e
move condition `preProcess`/`process` handling into `ConnectionLogic`
duncanmcclean Aug 25, 2026
e054f3b
wip
duncanmcclean Aug 25, 2026
c41dd6c
tweak error handling
duncanmcclean Aug 25, 2026
7c59fe2
avoid showing "save" button when connection still needs to be configured
duncanmcclean Aug 25, 2026
8957d38
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Aug 31, 2026
cc98fb0
give connections a config() they carry themselves
duncanmcclean Aug 31, 2026
e94bdac
read connections from the dispatched submission's form instance
duncanmcclean Sep 1, 2026
5410c53
add `pages` and `sections` variables to custom form emails
duncanmcclean Sep 1, 2026
7203506
translate field display values in the connection rules summary
duncanmcclean Sep 1, 2026
d057123
ensure connection rows always have ids
duncanmcclean Sep 1, 2026
72b9e33
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Sep 2, 2026
e436e93
add missing import
duncanmcclean Sep 2, 2026
bf1c6a8
add another missing import. darn merge conflicts
duncanmcclean Sep 2, 2026
5b9a1e9
remove stale `DeleteTemporaryFiles` tests from `SendEmailsTest`
duncanmcclean Sep 2, 2026
3c71676
Connect - improve icons wip
jaygeorge Sep 2, 2026
9a2cc3c
Work on webhook icon
jaygeorge Sep 3, 2026
ae88c4a
Connection icon work
jaygeorge Sep 3, 2026
9559972
Add flat version of the icon for breadcrumbs
jaygeorge Sep 3, 2026
b88344b
Optimise icons
jaygeorge Sep 3, 2026
fdf8892
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Sep 7, 2026
385b551
[6.x] Forms 2: Connections tweaks (#15366)
jaygeorge Sep 7, 2026
06035f5
[6.x] Forms 2: Email Previews (#15439)
duncanmcclean Sep 10, 2026
56888b8
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Sep 10, 2026
3ef4bac
Merge branch 'forms-2' into forms-2-connections
duncanmcclean Sep 14, 2026
51beb05
Merge remote-tracking branch 'origin/forms-2' into forms-2-connections
jasonvarga Sep 23, 2026
a46c816
Merge remote-tracking branch 'origin/forms-2' into forms-2-connections
jasonvarga Sep 25, 2026
13bde77
Fix connections seeing raw upload paths on non-storing forms
jasonvarga Sep 25, 2026
e79c046
Dispatch connection jobs independently instead of chaining them
jasonvarga Sep 29, 2026
6e9cc5f
Require a public middleware property on connection jobs rather than t…
jasonvarga Sep 29, 2026
87350d2
Add default enabled and conditions keys when pre-processing connections
jasonvarga Sep 30, 2026
f2a5700
Keep connection conditions that have a blank value
jasonvarga Sep 30, 2026
4c8c464
Validate email connection config before previewing, and restrict emai…
jasonvarga Sep 30, 2026
ac40765
Replace connection editor state with the saved values
jasonvarga Sep 30, 2026
e82d6f5
Restore the untyped Widgets\VueComponent
jasonvarga Sep 30, 2026
8d8555b
Block webhooks to internal addresses
jasonvarga Sep 30, 2026
43717bb
Validate that webhook URLs use a public address when saving
jasonvarga Sep 30, 2026
bf761c7
Fix PHPStan unsafe new static in RecordConnectionSuccess
jasonvarga Sep 30, 2026
feb1cfb
Fall back to an empty array when suggestableFields isn't on the page
jasonvarga Sep 30, 2026
4571069
Translate ConnectionRows default labels at render time
jasonvarga Sep 30, 2026
74d2a43
Return 6.x-compatible email configs from the deprecated email() getter
jasonvarga Sep 30, 2026
875e329
Make connection configs rows-only and move row plumbing into the base…
jasonvarga Oct 7, 2026
b598fa2
Return pre-processed config from connection updates when not saving
jasonvarga Oct 7, 2026
9fe171d
Rename the form connection type to "connector"
jasonvarga Oct 7, 2026
2338549
Merge remote-tracking branch 'origin/forms-2' into forms-2-connections
jasonvarga Oct 7, 2026
fed6080
Let connectors describe their fields with a blueprint
jasonvarga Oct 8, 2026
bd580a1
Add ConnectionFields and let ConnectionList render each connection's …
jasonvarga Oct 8, 2026
7564969
Keep Rule::forEach connection rules intact
jasonvarga Oct 8, 2026
c281493
Give connectors the form and connections they're working with
jasonvarga Oct 8, 2026
796f8a5
Require connectors to define a job and a blueprint
jasonvarga Oct 8, 2026
8ce79f9
Don't save connectors with no connections
jasonvarga Oct 8, 2026
3277a7f
Run every connector when generating a fake submission through the ful…
jasonvarga Oct 8, 2026
7d98acc
Scope {this} in connector blueprint rules to each connection
jasonvarga Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config/forms.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@

'send_email_job' => \Statamic\Forms\SendEmail::class,

/*
|--------------------------------------------------------------------------
| Connections Cache Store
|--------------------------------------------------------------------------
|
| The cache store used to track when a submission's connections have all
| finished, so its temporary files can be deleted. A persistent store
| such as "database" or "redis" is recommended. Null uses the default.
|
*/

'connections_cache_store' => null,

/*
|--------------------------------------------------------------------------
| Partial Submissions
Expand Down
27 changes: 17 additions & 10 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
'elevated_session_verification_code_notification_body' => 'Copy and paste the code shown below to verify your identity. If you didn\'t request this code, you can ignore this email.',
'elevated_session_verification_code_notification_subject' => 'Your Verification Code',
'elevated_session_verification_code_sent' => 'Verification code has been sent to your email.',
'email_connection_delete_confirmation' => 'Are you sure you want to delete this email?',
'email_connection_instructions' => 'Set up emails to be sent automatically when this form is submitted. Use them to notify users or team members.',
'email_connection_preview_attachments' => 'Files uploaded with the submission will be attached.',
'email_connection_preview_latest_instructions' => 'Rendered with the latest submission to this form.',
'email_connection_preview_sample_instructions' => 'Rendered with sample submission data. Real emails will contain the submitted values.',
'email_connector_description' => 'Send email notifications.',
'email_utility_configuration_description' => 'Mail settings are configured in <code>:path</code>',
'email_utility_description' => 'Check email configuration settings and send test emails.',
'entry_count' => ':count entry|:count entries',
Expand Down Expand Up @@ -123,22 +129,18 @@
'forgot_password_enter_email' => 'Enter your email address to receive a password reset link.',
'forgot_password_sent' => 'If an account with that email address exists, a password reset link has been sent.',
'form_configure_blueprint_instructions' => 'Choose from existing blueprints or create a new one.',
'form_configure_email_attachments_instructions' => 'Attach uploaded assets to this email.',
'form_configure_email_bcc_instructions' => 'Email address of the BCC recipient(s) - comma separated.',
'form_configure_email_cc_instructions' => 'Email address of the CC recipient(s) - comma separated.',
'form_configure_email_attachments_instructions' => 'Attach files uploaded with the submission.',
'form_configure_email_body_instructions' => 'You can include submitted values using [Antlers](https://statamic.dev/frontend/antlers). Leave blank to list all submitted fields instead.',
'form_configure_email_from_instructions' => 'Leave blank to fall back to the site default.',
'form_configure_email_html_instructions' => 'The view for the HTML version of this email.',
'form_configure_email_instructions' => 'Configure emails to be sent when new form submissions are received.',
'form_configure_email_views_instructions' => 'Leave both blank to use the default layout.',
'form_configure_email_description' => 'Be sure your email settings are configured to send and receive notifications when forms are submitted.',
'form_configure_email_markdown_instructions' => 'Render the HTML version of this email using Markdown.',
'form_configure_email_reply_to_instructions' => 'Leave blank to fall back to sender.',
'form_configure_email_subject_instructions' => 'Email subject line.',
'form_configure_email_text_instructions' => 'The view for the text version of this email.',
'form_configure_email_to_instructions' => 'Email address of the recipient(s) - comma separated.',
'form_configure_email_reply_to_instructions' => 'Leave blank to fall back to the sender.',
'form_configure_email_to_instructions' => 'Choose a form field or enter an email address.',
'form_configure_handle_instructions' => 'Used to reference this form on the frontend. This cannot be easily changed later.',
'form_configure_honeypot_instructions' => 'Field name to use as a honeypot. Honeypots are special fields used to reduce bot spam.',
'form_configure_intro' => 'Forms collect information from visitors and can trigger events and send notifications when submissions are received.',
'form_configure_mailer_instructions' => 'Choose the mailer for sending this email. Leave blank to fall back to the default mailer.',
'form_configure_mailer_instructions' => 'Leave blank to fall back to the default mailer.',
'form_configure_generate_fake_submissions_instructions' => 'Allow generating fake submissions and workflow testing from the submissions screen.',
'form_configure_store_instructions' => 'Disable to stop storing submissions. Events and email notifications will still be sent.',
'form_configure_title_instructions' => 'Use a call to action, such as \'Contact Us\'.',
Expand Down Expand Up @@ -331,5 +333,10 @@
'user_wizard_invitation_subject' => 'Activate your new Statamic account on :site',
'user_wizard_roles_groups_intro' => 'Users can be assigned to roles that customize their permissions, access, and abilities throughout the Control Panel.',
'user_wizard_super_admin_instructions' => 'Super admins have complete control and access to everything in the control panel. Grant this role wisely.',
'webhook_connection_delete_confirmation' => 'Are you sure you want to delete this webhook?',
'webhook_connection_instructions' => 'Each webhook is sent when this form is submitted. Notify other services or trigger automations with the submission data.',
'webhook_connection_payload_instructions' => 'Each submission is sent to the webhook URL as a JSON POST request.',
'webhook_connection_verify_ssl_instructions' => 'Only disable this if the receiving server uses a self-signed certificate.',
'webhook_connector_description' => 'Send submissions to an external URL.',
'width_x_height' => ':width × :height',
];
2 changes: 2 additions & 0 deletions lang/en/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
'elevated_session_resend_code_unavailable' => 'Resend code is only available for verification code method.',
'elevated_session_verification_code' => 'The verification code is incorrect.',
'email_available' => 'A user with this email already exists.',
'email_field_reference' => "References a field that doesn't exist on this form.",
'fieldset_imported_recursively' => 'Fieldset :handle is being imported recursively.',
'form_chart_hidden_field' => 'Field :field is hidden and cannot be charted.',
'form_chart_not_applicable' => 'Chart :chart cannot be used for field :field.',
Expand All @@ -218,6 +219,7 @@
'unique_user_value' => 'This value has already been taken.',
'unique_uri' => 'This URI has already been taken.',
'time' => 'Not a valid time.',
'webhook_url_not_public' => 'Webhooks must use a public address.',
'asset_current_filename' => 'This is the current filename.',
'asset_file_exists' => 'A file already exists with this name.',
'asset_file_exists_same_content' => 'A file already exists with this name and has the same content. You may want to delete this rather than rename it.',
Expand Down
4 changes: 4 additions & 0 deletions packages/cms/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const {
RevisionPreview,
SaveButtonOptions,
SortableList,
ConnectionFields,
ConnectionList,
ConnectionRules,
ConnectionSummary,
requireElevatedSession,
requireElevatedSessionIf,
clone,
Expand Down
2 changes: 2 additions & 0 deletions resources/css/dark.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@


.dark {
color-scheme: dark;

input:-webkit-autofill {
-webkit-box-shadow: inset 0 0 0 50px var(--color-gray-500);
-webkit-text-fill-color: var(--color-gray-300);
Expand Down
2 changes: 2 additions & 0 deletions resources/css/elements/base.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* VARIABLES
=================================================== */
:root {
color-scheme: light;

/* For animating height */
interpolate-size: allow-keywords;

Expand Down
4 changes: 4 additions & 0 deletions resources/js/bootstrap/cms/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export { default as resetValuesFromResponse } from '../../util/resetValuesFromRe
export { requireElevatedSession, requireElevatedSessionIf } from '../../components/elevated-sessions';
export { default as clone, deepClone } from '../../util/clone.js';
export { default as debounce } from '../../util/debounce.js';
export { default as ConnectionFields } from '../../components/forms/connections/ConnectionFields.vue';
export { default as ConnectionList } from '../../components/forms/connections/ConnectionList.vue';
export { default as ConnectionRules } from '../../components/forms/connections/ConnectionRules.vue';
export { default as ConnectionSummary } from '../../components/forms/connections/ConnectionSummary.vue';
4 changes: 4 additions & 0 deletions resources/js/bootstrap/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export default function registerGlobalComponents(app) {
app.component('min-max-insight', MinMaxInsight);
app.component('star-rating-insight', StarRatingInsight);

// Form Connections
app.component('email-connector', defineAsyncComponent(() => import('../components/forms/connections/EmailConnector.vue')));
app.component('webhook-connector', defineAsyncComponent(() => import('../components/forms/connections/WebhookConnector.vue')));

// Reusable
app.component('file-icon', FileIcon);

Expand Down
2 changes: 2 additions & 0 deletions resources/js/bootstrap/fieldtypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import Sites from '../components/globals/Sites.vue';
import Grid from '../components/fieldtypes/grid/Grid.vue';
import GridIndex from '../components/fieldtypes/grid/GridIndex.vue';
import GroupFieldtype from '../components/fieldtypes/GroupFieldtype.vue';
import FormFieldsFieldtype from '../components/fieldtypes/FormFieldsFieldtype.vue';
import FormHeadingFieldtype from '../components/fieldtypes/FormHeadingFieldtype.vue';
import FormParagraphFieldtype from '@/components/fieldtypes/FormParagraphFieldtype.vue';
import FormUploadFieldtype from '@/components/fieldtypes/FormUploadFieldtype.vue';
Expand Down Expand Up @@ -114,6 +115,7 @@ export default function registerFieldtypes(app) {
app.component('grid-fieldtype', Grid);
app.component('grid-fieldtype-index', GridIndex);
app.component('group-fieldtype', GroupFieldtype);
app.component('form_fields-fieldtype', FormFieldsFieldtype);
app.component('form_heading-fieldtype', FormHeadingFieldtype);
app.component('form_paragraph-fieldtype', FormParagraphFieldtype);
app.component('form_upload-fieldtype', FormUploadFieldtype);
Expand Down
51 changes: 51 additions & 0 deletions resources/js/components/fieldtypes/FormFieldsFieldtype.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script setup>
import Fieldtype from '@/components/fieldtypes/fieldtype.js';
import { Combobox, Icon } from '@/components/ui';
import { categories, categoryColorClasses } from '@/components/forms/builder/categories.js';
import { computed } from 'vue';

const emit = defineEmits(Fieldtype.emits);
const props = defineProps(Fieldtype.props);
const { isReadOnly, update } = Fieldtype.use(emit, props);

const options = computed(() => props.meta.options ?? []);

const category = (handle) => categories[handle] ?? categories.other;
const categoryTitle = (handle) => category(handle).title;
const iconClasses = (handle) =>
`size-4 shrink-0 ${categoryColorClasses[category(handle).color]?.icon ?? 'text-gray-600 dark:text-gray-400'}`;
</script>

<template>
<Combobox
:id
:clearable="config.clearable"
:disabled="config.disabled"
:max-selections="config.max_items"
:model-value="value"
:multiple="config.multiple"
:options
:placeholder="__(config.placeholder)"
:read-only="isReadOnly"
:searchable="(config.searchable ?? true) || config.taggable"
:taggable="config.taggable"
:close-on-select="(config.taggable && !options.length) || !config.multiple"
@update:modelValue="update"
>
<template #option="option">
<span class="flex w-full items-center gap-2">
<Icon v-if="option.icon" :name="option.icon" :class="iconClasses(option.category)" />
<span class="truncate">{{ __(option.label) }}</span>
<span v-if="option.category" class="ms-auto text-2xs text-gray-500 dark:text-gray-400">
{{ categoryTitle(option.category) }}
</span>
</span>
</template>
<template #selected-option="{ option }">
<span class="flex items-center gap-2 truncate">
<Icon v-if="option.icon" :name="option.icon" :class="iconClasses(option.category)" />
<span class="truncate">{{ __(option.label) }}</span>
</span>
</template>
</Combobox>
</template>
78 changes: 78 additions & 0 deletions resources/js/components/forms/connections/ConnectionFields.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script setup lang="ts">
// Renders one PublishContainer per connection, with the connection as the container root.
// Fieldtypes that read sibling values through injectPublishContext() rely on that, so don't
// render two of these for the same connection. Use the default slot and pick() instead.
import { computed, inject } from 'vue';
import { PublishContainer, PublishFields, PublishFieldsProvider } from '@ui';
import { connectionContextKey } from './context';

type BlueprintField = { handle: string; [key: string]: unknown };

const props = withDefaults(defineProps<{
/** Handles of the fields to render, in order. Defaults to every field in the blueprint's first tab. */
fields?: string[];
/** Handles of fields to leave out. */
except?: string[];
/** Extra values made available to field conditions. */
extraValues?: Record<string, unknown>;
/** Wraps the fields in a bordered box. */
bordered?: boolean;
/** The connection being edited. Defaults to the one provided by ConnectionList. */
connection?: Record<string, unknown>;
/** The blueprint's publish array. Defaults to the one passed to ConnectionList. */
blueprint?: Record<string, any>;
/** Fieldtype meta for this connection. Defaults to the one managed by ConnectionList. */
meta?: Record<string, unknown>;
/** Validation errors for this connection, keyed by field path. Defaults to the ones from ConnectionList. */
errors?: Record<string, string[]>;
/** Prefix for the container name. Defaults to ConnectionList's name. */
name?: string;
}>(), {
bordered: true,
});

const context = inject(connectionContextKey, null);

const connection = computed(() => props.connection ?? context?.value.connection ?? {});
const blueprint = computed(() => props.blueprint ?? context?.value.blueprint);
const meta = computed(() => props.meta ?? context?.value.meta ?? {});
const errors = computed(() => props.errors ?? context?.value.errors ?? {});
const containerName = computed(() => `${props.name ?? context?.value.name ?? 'connection'}-${connection.value.id}`);

const allFields = computed<BlueprintField[]>(() =>
(blueprint.value?.tabs?.[0]?.sections ?? []).flatMap((section) => section.fields ?? []),
);

const pick = (...handles: string[]): BlueprintField[] =>
handles
.map((handle) => allFields.value.find((field) => field.handle === handle))
.filter((field): field is BlueprintField => field !== undefined);

const visibleFields = computed<BlueprintField[]>(() => {
const fields = props.fields ? pick(...props.fields) : allFields.value;

return props.except ? fields.filter((field) => !props.except.includes(field.handle)) : fields;
});
</script>

<template>
<div :class="{ 'rounded-lg border border-gray-300 bg-white p-4 dark:border-gray-700 dark:bg-gray-900': bordered }">
<PublishContainer
:blueprint
:errors
:model-value="connection"
:meta
:extra-values="extraValues"
:name="containerName"
:track-dirty-state="false"
>
<slot name="before" :connection />
<slot :connection :fields="visibleFields" :pick>
<PublishFieldsProvider :fields="visibleFields">
<PublishFields />
</PublishFieldsProvider>
</slot>
<slot name="after" :connection />
</PublishContainer>
</div>
</template>
73 changes: 73 additions & 0 deletions resources/js/components/forms/connections/ConnectionItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup>
import { computed, provide } from 'vue';
import { Button, DragHandle, Dropdown, DropdownItem, DropdownMenu, Switch } from '@ui';
import { connectionContextKey } from './context';

const emit = defineEmits(['collapsed', 'expanded', 'duplicated', 'removed', 'update:enabled']);

const props = defineProps({
collapsed: Boolean,
enabled: Boolean,
hasError: Boolean,
handleClass: String,
context: Object,
});

provide(connectionContextKey, computed(() => props.context));

function toggleCollapsedState() {
props.collapsed ? emit('expanded') : emit('collapsed');
}
</script>

<template>
<div
class="@container relative w-full rounded-lg border border-gray-300 text-base dark:border-white/10 bg-white dark:bg-gray-900 dark:inset-shadow-2xs dark:inset-shadow-black shadow-ui-sm"
:class="{ 'border-red-500': hasError }"
>
<header
class="group/header animate-border-color flex items-center show-focus-within rounded-[calc(var(--radius-lg)-1px)] px-1.5 antialiased duration-200 border-gray-300 dark:shadow-md"
:class="{
'bg-white dark:bg-gray-900': collapsed,
'bg-gray-100 dark:bg-gray-925 rounded-b-none': !collapsed,
}"
>
<DragHandle :class="handleClass" class="ms-1 cursor-grab [&_svg]:opacity-75 dark:[&_svg]:opacity-50" />
<button type="button" class="show-focus-within_target flex flex-1 items-center gap-1.75 p-2 py-1.75 min-w-0 focus:outline-none cursor-pointer" @click="toggleCollapsedState">
<slot name="header" />
</button>
<div class="flex items-center gap-2">
<Switch
size="xs"
:model-value="enabled"
@update:model-value="emit('update:enabled', $event)"
v-tooltip="enabled ? __('Enabled') : __('Disabled')"
/>
<Dropdown>
<template #trigger>
<Button icon="dots" variant="ghost" size="xs" class="me-2" :aria-label="__('Open connection actions')" />
</template>
<DropdownMenu>
<DropdownItem
:text="__('Duplicate')"
icon="duplicate"
@click="emit('duplicated')"
/>
<DropdownItem
:text="__('Delete')"
icon="trash"
variant="destructive"
@click="emit('removed')"
/>
</DropdownMenu>
</Dropdown>
</div>
</header>

<div v-show="!collapsed" class="isolate border-t border-t-gray-300! dark:border-t-white/10!">
<div class="p-4">
<slot />
</div>
</div>
</div>
</template>
Loading
Loading