diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml
index 633dd20875..0bee1fd2b4 100644
--- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml
+++ b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml
@@ -37,8 +37,8 @@
-
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
().Tag().as();
+ const auto extPkgVM = sender.as().Tag().as();
_ViewModel.CurrentExtensionPackage(extPkgVM);
}
void Extensions::NavigateToProfile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
{
- const auto& profileGuid = sender.as().Tag().as();
+ const auto element = sender.as();
+ const auto profileGuid = winrt::unbox_value(element.Tag());
get_self(_ViewModel)->NavigateToProfile(profileGuid);
}
void Extensions::NavigateToColorScheme_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
{
- const auto& schemeVM = sender.as().Tag().as();
+ const auto element = sender.as();
+ const auto schemeVM = element.Tag().as();
get_self(_ViewModel)->NavigateToColorScheme(schemeVM);
}
diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml
index 9659f8f467..22d03d8832 100644
--- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml
+++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml
@@ -127,122 +127,78 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -403,41 +359,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
@@ -500,11 +432,13 @@
-
+
+
+
SettingContainer.idl
+
+ SettingsCard.idl
+
+
+ SettingsExpander.idl
+
+
+ StyleExtensions.idl
+
+
+ ControlSizeTrigger.idl
+
+
+ CornerRadiusFilterConverters.idl
+
+
+ StringDefaultTemplateSelector.idl
+
@@ -255,6 +273,12 @@
DefaultStyle
+
+ DefaultStyle
+
+
+ DefaultStyle
+
@@ -388,6 +412,24 @@
SettingContainer.idl
+
+ SettingsCard.idl
+
+
+ SettingsExpander.idl
+
+
+ StyleExtensions.idl
+
+
+ ControlSizeTrigger.idl
+
+
+ CornerRadiusFilterConverters.idl
+
+
+ StringDefaultTemplateSelector.idl
+
PreviewConnection.h
@@ -498,6 +540,24 @@
Code
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters
index dd276d82ec..4110f8d8b0 100644
--- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters
+++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters
@@ -28,7 +28,12 @@
-
+
+
+
+
+
+
@@ -50,6 +55,8 @@
+
+
diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml
index b00c1753bb..9adea2aa01 100644
--- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml
+++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml
@@ -35,254 +35,222 @@
Style="{StaticResource DisclaimerStyle}"
Visibility="{x:Bind Profile.IsBaseLayer}" />
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.cpp
index 8a24895a57..e817b20020 100644
--- a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.cpp
+++ b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.cpp
@@ -70,7 +70,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
TraceLoggingValue(_Profile.IsBaseLayer(), "IsProfileDefaults", "If the modified profile is the profile.defaults object"),
TraceLoggingValue(static_cast(_Profile.Guid()), "ProfileGuid", "The guid of the profile that was navigated to"),
TraceLoggingValue(_Profile.Source().c_str(), "ProfileSource", "The source of the profile that was navigated to"),
- TraceLoggingValue(_Profile.DefaultAppearance().BackgroundImageSettingsVisible(), "HasBackgroundImage", "If the profile has a background image defined"),
+ TraceLoggingValue(_Profile.DefaultAppearance().BackgroundImageSettingsEnabled(), "HasBackgroundImage", "If the profile has a background image defined"),
TraceLoggingValue(_Profile.HasUnfocusedAppearance(), "HasUnfocusedAppearance", "If the profile has an unfocused appearance defined"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp
index edd4bdbcf5..3258d5e9b3 100644
--- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp
+++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp
@@ -22,11 +22,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Automation::AutomationProperties::SetFullDescription(StartingDirectoryUseParentCheckbox(), unbox_value(startingDirCheckboxTooltip));
Automation::AutomationProperties::SetName(DeleteButton(), RS_(L"Profile_DeleteButton/Text"));
- AppearanceNavigator().Content(box_value(RS_(L"Profile_Appearance/Header")));
- AppearanceNavigator().Tag(box_value(RS_(L"Profile_AppearanceNavigator/HelpText")));
- TerminalNavigator().Content(box_value(RS_(L"Profile_Terminal/Header")));
- TerminalNavigator().Tag(box_value(RS_(L"Profile_TerminalNavigator/HelpText")));
- AdvancedNavigator().Content(box_value(RS_(L"Profile_Advanced/Header")));
+ AppearanceNavigator().Header(box_value(RS_(L"Profile_Appearance/Header")));
+ AppearanceNavigator().Description(box_value(RS_(L"Profile_AppearanceNavigator/HelpText")));
+ TerminalNavigator().Header(box_value(RS_(L"Profile_Terminal/Header")));
+ TerminalNavigator().Description(box_value(RS_(L"Profile_TerminalNavigator/HelpText")));
+ AdvancedNavigator().Header(box_value(RS_(L"Profile_Advanced/Header")));
}
void Profiles_Base::OnNavigatedTo(const NavigationEventArgs& e)
diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.h b/src/cascadia/TerminalSettingsEditor/Profiles_Base.h
index 2fa4ab7225..8b8594c6b9 100644
--- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.h
+++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.h
@@ -22,6 +22,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Appearance_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void Terminal_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void Advanced_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
+ void ResetProfileConfirmation_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
void DeleteConfirmation_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
til::property_changed_event PropertyChanged;
diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml
index 3e165cedb9..a46dcbff76 100644
--- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml
+++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml
@@ -190,15 +190,15 @@
Margin="0,32,0,4"
Style="{StaticResource TextBlockSubHeaderStyle}" />
-
-
-
+
+
+
-
-
+
+
@@ -38,25 +38,25 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Terminal.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Terminal.xaml
index b8701d7132..9d6d165b36 100644
--- a/src/cascadia/TerminalSettingsEditor/Profiles_Terminal.xaml
+++ b/src/cascadia/TerminalSettingsEditor/Profiles_Terminal.xaml
@@ -29,69 +29,58 @@
Style="{StaticResource DisclaimerStyle}"
Visibility="{x:Bind Profile.IsBaseLayer}" />
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw
index 3b20dcb8ee..0e84a3de2f 100644
--- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw
+++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw
@@ -1,4 +1,4 @@
-
+
-
@@ -64,16 +48,6 @@
-
-
-
-
-
-
@@ -84,8 +58,6 @@
-
@@ -97,28 +69,14 @@
-
-
-
-
-
-
- SemiBold
-
-
- 0,4,8,4
16
@@ -198,7 +156,7 @@
BasedOn="{StaticResource SettingsPageItemDescriptionStyle}"
TargetType="TextBlock">
-
+
@@ -209,6 +167,9 @@
Text="{Binding}" />
+
+
-
-
-
-
-
-
+
+
diff --git a/src/cascadia/TerminalSettingsEditor/SettingsControlsStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingsControlsStyle.xaml
new file mode 100644
index 0000000000..a57be63b3b
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/SettingsControlsStyle.xaml
@@ -0,0 +1,1100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 16,16,16,16
+ 148
+ 68
+ 12
+ 20
+ 0
+ 120
+ 2,0,20,0
+ 14,0,0,0
+ 13
+ 0.4
+ 8
+ 476
+ 286
+
+ 16,16,4,16
+ 58,8,44,8
+ 58,8,16,8
+ 0,1,0,0
+ 16
+ 32
+ 32
+
+
+
+ Show all settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cascadia/TerminalSettingsEditor/SettingsExpander.cpp b/src/cascadia/TerminalSettingsEditor/SettingsExpander.cpp
new file mode 100644
index 0000000000..1048a66b35
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/SettingsExpander.cpp
@@ -0,0 +1,342 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "SettingsExpander.h"
+#include "SettingsExpander.g.cpp"
+#include "SettingsExpanderAutomationPeer.g.cpp"
+#include "SettingsExpanderItemStyleSelector.g.cpp"
+#include "StyleExtensions.h"
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Foundation::Collections;
+using namespace winrt::Windows::UI::Xaml;
+using namespace winrt::Windows::UI::Xaml::Automation;
+using namespace winrt::Windows::UI::Xaml::Automation::Peers;
+using namespace winrt::Windows::UI::Xaml::Controls;
+
+namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
+{
+ DependencyProperty SettingsExpander::_HeaderProperty{ nullptr };
+ DependencyProperty SettingsExpander::_DescriptionProperty{ nullptr };
+ DependencyProperty SettingsExpander::_HeaderIconProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ContentProperty{ nullptr };
+ DependencyProperty SettingsExpander::_IsExpandedProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ItemsHeaderProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ItemsFooterProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ItemsProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ItemsSourceProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ItemTemplateProperty{ nullptr };
+ DependencyProperty SettingsExpander::_ItemContainerStyleSelectorProperty{ nullptr };
+
+ static constexpr std::wstring_view PART_ItemsHost{ L"PART_ItemsHost" };
+
+ SettingsExpander::SettingsExpander()
+ {
+ _InitializeProperties();
+
+ // Items is backed by an observable vector so post-construction mutations
+ // (e.g. user code that adds cards after the expander is on screen) also
+ // refresh the inner ItemsControl. The XAML parser populates Items via
+ // Append before OnApplyTemplate runs, so the eager population path also
+ // works.
+ Items(single_threaded_observable_vector());
+ }
+
+ void SettingsExpander::_InitializeProperties()
+ {
+ if (!_HeaderProperty)
+ {
+ _HeaderProperty = DependencyProperty::Register(
+ L"Header",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_DescriptionProperty)
+ {
+ _DescriptionProperty = DependencyProperty::Register(
+ L"Description",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_HeaderIconProperty)
+ {
+ _HeaderIconProperty = DependencyProperty::Register(
+ L"HeaderIcon",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_ContentProperty)
+ {
+ _ContentProperty = DependencyProperty::Register(
+ L"Content",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_IsExpandedProperty)
+ {
+ _IsExpandedProperty = DependencyProperty::Register(
+ L"IsExpanded",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ box_value(false), PropertyChangedCallback{ &SettingsExpander::_OnIsExpandedChanged } });
+ }
+ if (!_ItemsHeaderProperty)
+ {
+ _ItemsHeaderProperty = DependencyProperty::Register(
+ L"ItemsHeader",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_ItemsFooterProperty)
+ {
+ _ItemsFooterProperty = DependencyProperty::Register(
+ L"ItemsFooter",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_ItemsProperty)
+ {
+ _ItemsProperty = DependencyProperty::Register(
+ L"Items",
+ xaml_typename>(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsExpander::_OnItemsConnectedPropertyChanged } });
+ }
+ if (!_ItemsSourceProperty)
+ {
+ _ItemsSourceProperty = DependencyProperty::Register(
+ L"ItemsSource",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsExpander::_OnItemsConnectedPropertyChanged } });
+ }
+ if (!_ItemTemplateProperty)
+ {
+ _ItemTemplateProperty = DependencyProperty::Register(
+ L"ItemTemplate",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ if (!_ItemContainerStyleSelectorProperty)
+ {
+ _ItemContainerStyleSelectorProperty = DependencyProperty::Register(
+ L"ItemContainerStyleSelector",
+ xaml_typename(),
+ xaml_typename(),
+ PropertyMetadata{ nullptr });
+ }
+ }
+
+ AutomationPeer SettingsExpander::OnCreateAutomationPeer()
+ {
+ return winrt::make(*this);
+ }
+
+ void SettingsExpander::OnApplyTemplate()
+ {
+ // Same implicit-styles injection as SettingsCard so a ToggleSwitch /
+ // Slider / ComboBox / TextBox placed directly as SettingsExpander.Content
+ // gets the same Windows 11 defaults.
+ StyleExtensions::EnsureImplicitStylesMergedInto(*this);
+
+ _SetAccessibleName();
+
+ // Drop the prior template's host before locating the new one.
+ _itemsHost = nullptr;
+
+ if (const auto child{ GetTemplateChild(hstring{ PART_ItemsHost }) })
+ {
+ _itemsHost = child.try_as();
+ }
+
+ if (_itemsHost)
+ {
+ // Push our initial ItemsSource through to the host and stamp item-container styles.
+ _UpdateItemsSource();
+ }
+ }
+
+ void SettingsExpander::_SetAccessibleName()
+ {
+ if (!AutomationProperties::GetName(*this).empty())
+ {
+ return;
+ }
+ if (const auto headerString{ unbox_value_or(Header(), hstring{}) }; !headerString.empty())
+ {
+ AutomationProperties::SetName(*this, headerString);
+ }
+ }
+
+ void SettingsExpander::_UpdateItemsSource()
+ {
+ if (!_itemsHost)
+ {
+ return;
+ }
+ // ItemsSource wins when set; otherwise fall back to the inline Items collection.
+ if (const auto source{ ItemsSource() })
+ {
+ _itemsHost.ItemsSource(source);
+ }
+ else
+ {
+ _itemsHost.ItemsSource(Items());
+ }
+
+ _ApplyItemContainerStyles();
+ _SubscribeToItemsVectorChanged();
+ }
+
+ // Watch our inline Items vector for changes so containers added after
+ // OnApplyTemplate also get the proper SettingsCard item style. (When
+ // ItemsSource is set, this is a no-op since the parser only touches Items.)
+ void SettingsExpander::_SubscribeToItemsVectorChanged()
+ {
+ _itemsVectorChangedRevoker.revoke();
+
+ if (ItemsSource())
+ {
+ return;
+ }
+
+ if (const auto observable{ Items().try_as>() })
+ {
+ _itemsVectorChangedRevoker = observable.VectorChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
+ if (const auto strongThis{ weakThis.get() })
+ {
+ strongThis->_ApplyItemContainerStyles();
+ }
+ });
+ }
+ }
+
+ // Apply the per-item style produced by ItemContainerStyleSelector. ItemsControl
+ // only generates ContentPresenter containers for non-UIElement items, so when
+ // SettingsCards are added directly the cards themselves are the "containers"
+ // and we have to set Style on them ourselves. Mirrors the ElementPrepared path
+ // we used when this was an ItemsRepeater.
+ void SettingsExpander::_ApplyItemContainerStyles()
+ {
+ const auto selector{ ItemContainerStyleSelector() };
+ if (!selector)
+ {
+ return;
+ }
+
+ const auto items{ Items() };
+ if (!items)
+ {
+ return;
+ }
+
+ for (uint32_t i = 0; i < items.Size(); ++i)
+ {
+ if (const auto element{ items.GetAt(i).try_as() })
+ {
+ if (element.ReadLocalValue(FrameworkElement::StyleProperty()) == DependencyProperty::UnsetValue())
+ {
+ element.Style(selector.SelectStyle(items.GetAt(i), element));
+ }
+ }
+ }
+ }
+
+ void SettingsExpander::_OnItemsConnectedPropertyChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
+ {
+ if (const auto obj{ d.try_as() })
+ {
+ get_self(obj)->_UpdateItemsSource();
+ }
+ }
+
+ void SettingsExpander::_OnIsExpandedChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& e)
+ {
+ const auto obj{ d.try_as() };
+ if (!obj)
+ {
+ return;
+ }
+ const auto self = get_self(obj);
+ const auto newValue = unbox_value_or(e.NewValue(), false);
+
+ // Notify the automation peer so screen readers see the expand/collapse state change.
+ if (const auto peer{ FrameworkElementAutomationPeer::FromElement(obj).try_as() })
+ {
+ peer.RaiseExpandedChangedEvent(newValue);
+ }
+
+ if (newValue)
+ {
+ self->Expanded.raise(obj, nullptr);
+ }
+ else
+ {
+ self->Collapsed.raise(obj, nullptr);
+ }
+ }
+
+ SettingsExpanderAutomationPeer::SettingsExpanderAutomationPeer(const Editor::SettingsExpander& owner) :
+ SettingsExpanderAutomationPeerT(owner)
+ {
+ }
+
+ AutomationControlType SettingsExpanderAutomationPeer::GetAutomationControlTypeCore() const
+ {
+ return AutomationControlType::Group;
+ }
+
+ hstring SettingsExpanderAutomationPeer::GetClassNameCore() const
+ {
+ return hstring{ L"SettingsExpander" };
+ }
+
+ hstring SettingsExpanderAutomationPeer::GetNameCore() const
+ {
+ if (const auto expander{ Owner().try_as() })
+ {
+ if (const auto manualName{ AutomationProperties::GetName(expander) }; !manualName.empty())
+ {
+ return manualName;
+ }
+ if (const auto headerString{ unbox_value_or(expander.Header(), hstring{}) }; !headerString.empty())
+ {
+ return headerString;
+ }
+ }
+ return {};
+ }
+
+ void SettingsExpanderAutomationPeer::RaiseExpandedChangedEvent(bool newValue)
+ {
+ // Mirrors the toolkit's SettingsExpanderAutomationPeer.RaiseExpandedChangedEvent.
+ // Narrator doesn't actually announce this today (microsoft/microsoft-ui-xaml#3469),
+ // but other AT can subscribe and we keep parity with the toolkit.
+ const auto newState = newValue ? ExpandCollapseState::Expanded : ExpandCollapseState::Collapsed;
+ const auto oldState = newValue ? ExpandCollapseState::Collapsed : ExpandCollapseState::Expanded;
+ RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers::ExpandCollapseStateProperty(), box_value(oldState), box_value(newState));
+ }
+
+ Windows::UI::Xaml::Style SettingsExpanderItemStyleSelector::SelectStyleCore(const winrt::Windows::Foundation::IInspectable& /*item*/, const Windows::UI::Xaml::DependencyObject& container)
+ {
+ // When the prepared container is a clickable SettingsCard, give it the
+ // clickable item style (which adds the right padding for the chevron).
+ // Otherwise fall back to the default item style.
+ if (const auto card{ container.try_as() })
+ {
+ if (card.IsClickEnabled())
+ {
+ return _ClickableStyle;
+ }
+ }
+ return _DefaultStyle;
+ }
+}
diff --git a/src/cascadia/TerminalSettingsEditor/SettingsExpander.h b/src/cascadia/TerminalSettingsEditor/SettingsExpander.h
new file mode 100644
index 0000000000..f77855171a
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/SettingsExpander.h
@@ -0,0 +1,97 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- SettingsExpander
+
+Abstract:
+- A collapsable container for grouping multiple SettingsCards. Based
+ on the Windows Community Toolkit's SettingsExpander.
+
+Author(s):
+- Carlos Zamora - May 2026 (port from CommunityToolkit.WinUI.Controls.SettingsExpander)
+
+--*/
+
+#pragma once
+
+#include "SettingsExpander.g.h"
+#include "SettingsExpanderAutomationPeer.g.h"
+#include "SettingsExpanderItemStyleSelector.g.h"
+#include "Utils.h"
+
+namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
+{
+ struct SettingsExpander : SettingsExpanderT
+ {
+ public:
+ SettingsExpander();
+
+ void OnApplyTemplate();
+
+ Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
+
+ til::typed_event Expanded;
+ til::typed_event Collapsed;
+
+ DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Header);
+ DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Description);
+ DEPENDENCY_PROPERTY(Windows::UI::Xaml::Controls::IconElement, HeaderIcon);
+ DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Content);
+ DEPENDENCY_PROPERTY(bool, IsExpanded);
+ DEPENDENCY_PROPERTY(Windows::UI::Xaml::UIElement, ItemsHeader);
+ DEPENDENCY_PROPERTY(Windows::UI::Xaml::UIElement, ItemsFooter);
+ DEPENDENCY_PROPERTY(Windows::Foundation::Collections::IVector, Items);
+ DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ItemsSource);
+ DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ItemTemplate);
+ DEPENDENCY_PROPERTY(Windows::UI::Xaml::Controls::StyleSelector, ItemContainerStyleSelector);
+
+ private:
+ static void _InitializeProperties();
+ static void _OnIsExpandedChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
+ static void _OnItemsConnectedPropertyChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
+
+ void _SetAccessibleName();
+ void _UpdateItemsSource();
+ void _SubscribeToItemsVectorChanged();
+ void _ApplyItemContainerStyles();
+
+ Windows::UI::Xaml::Controls::ItemsControl _itemsHost{ nullptr };
+ Windows::Foundation::Collections::IObservableVector::VectorChanged_revoker _itemsVectorChangedRevoker;
+ };
+
+ // AutomationPeer for SettingsExpander. Reports class name and falls back to
+ // Header text for the name when AutomationProperties.Name is unset.
+ struct SettingsExpanderAutomationPeer : SettingsExpanderAutomationPeerT
+ {
+ public:
+ SettingsExpanderAutomationPeer(const Editor::SettingsExpander& owner);
+
+ Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
+ hstring GetClassNameCore() const;
+ hstring GetNameCore() const;
+
+ void RaiseExpandedChangedEvent(bool newValue);
+ };
+
+ // StyleSelector used by SettingsExpander to choose between a clickable vs.
+ // non-clickable SettingsCard container style for items in its ItemsControl.
+ // Ported from the Windows Community Toolkit's SettingsExpanderItemStyleSelector.
+ struct SettingsExpanderItemStyleSelector : SettingsExpanderItemStyleSelectorT
+ {
+ SettingsExpanderItemStyleSelector() = default;
+
+ Windows::UI::Xaml::Style SelectStyleCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container);
+
+ WINRT_PROPERTY(Windows::UI::Xaml::Style, DefaultStyle, nullptr);
+ WINRT_PROPERTY(Windows::UI::Xaml::Style, ClickableStyle, nullptr);
+ };
+}
+
+namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
+{
+ BASIC_FACTORY(SettingsExpander);
+ BASIC_FACTORY(SettingsExpanderAutomationPeer);
+ BASIC_FACTORY(SettingsExpanderItemStyleSelector);
+}
diff --git a/src/cascadia/TerminalSettingsEditor/SettingsExpander.idl b/src/cascadia/TerminalSettingsEditor/SettingsExpander.idl
new file mode 100644
index 0000000000..86fea0bd72
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/SettingsExpander.idl
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+namespace Microsoft.Terminal.Settings.Editor
+{
+ [contentproperty("Content")]
+ [default_interface] runtimeclass SettingsExpander : Windows.UI.Xaml.Controls.Control
+ {
+ SettingsExpander();
+
+ Object Header;
+ static Windows.UI.Xaml.DependencyProperty HeaderProperty { get; };
+
+ Object Description;
+ static Windows.UI.Xaml.DependencyProperty DescriptionProperty { get; };
+
+ Windows.UI.Xaml.Controls.IconElement HeaderIcon;
+ static Windows.UI.Xaml.DependencyProperty HeaderIconProperty { get; };
+
+ Object Content;
+ static Windows.UI.Xaml.DependencyProperty ContentProperty { get; };
+
+ Boolean IsExpanded;
+ static Windows.UI.Xaml.DependencyProperty IsExpandedProperty { get; };
+
+ Windows.UI.Xaml.UIElement ItemsHeader;
+ static Windows.UI.Xaml.DependencyProperty ItemsHeaderProperty { get; };
+
+ Windows.UI.Xaml.UIElement ItemsFooter;
+ static Windows.UI.Xaml.DependencyProperty ItemsFooterProperty { get; };
+
+ Windows.Foundation.Collections.IVector