Skip to content

[Controls] Add CalendarDatePicker inner content slots - #605

Merged
Xavier Fortin (xfortin-devolutions) merged 7 commits into
masterfrom
CalendarDatePicker-add-inner-slots
Jul 30, 2026
Merged

[Controls] Add CalendarDatePicker inner content slots#605
Xavier Fortin (xfortin-devolutions) merged 7 commits into
masterfrom
CalendarDatePicker-add-inner-slots

Conversation

@xfortin-devolutions

@xfortin-devolutions Xavier Fortin (xfortin-devolutions) commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds inner content slots to CalendarDatePicker, mirroring the existing ComboBox inner-content slots, so consumers can drop icons / badges / buttons into the control without subclassing.

New attached-property class Devolutions.AvaloniaControls.AttachedProperties.CalendarDatePickerContent with three slots:

  • InnerLeftContent — before the date text
  • InnerLeftOfCalendarButtonContent — between the date and the calendar button
  • InnerRightContent — after the calendar button

Wired into all three themes (MacOS, DevExpress, Linux).

Implementation notes

  • MacOS / DevExpress: the calendar button moves into PART_TextBox's InnerRightContent, between the slots. Because that content sits outside the CalendarDatePicker template namescope, the icon / chevron state colours are driven by $parent[CalendarDatePicker] bindings (and the DevExpress chevron background by local styles on the border) rather than /template/ styles, which cannot reach it.
  • Linux (Yaru): the date and slots are overlaid on PART_Button as siblings, so a slot click bubbles up the overlay instead of opening the calendar; PART_Button's min size follows the overlay bounds (with the normal input-height floor) so it always covers the slots.
  • TextBox theme tweak (MacOS + DevExpress): the inner Prefix/Suffix content presenters now apply their padding via a /template/ style setter instead of a direct attribute, so an instance can override it. The picker always assigns inner content, which would otherwise leave the presenters padding the layout even when a slot is empty; the picker now locally zeroes that padding. No visual change to regular TextBoxes.
  • SampleApp: the CalendarDatePicker demo page exercises the three slots.

Testing

Built the solution and manually verified each theme in the SampleApp:

  • slots render in the correct positions; the calendar still opens; slot controls act independently (a slot click does not open the calendar);
  • icon / chevron hover / open / disabled colours; error state; empty slots no longer shift the layout;
  • regular TextBoxes unaffected by the theme tweak.

Note: visual-regression baselines were not regenerated (they fail wholesale on the author's local machine); verification was manual via the SampleApp.

Add CalendarDatePickerContent attached properties (InnerLeftContent,
InnerLeftOfCalendarButtonContent, InnerRightContent), mirroring the
ComboBox inner-content slots, and wire them into the MacOS, DevExpress
and Linux CalendarDatePicker templates.

- MacOS / DevExpress: the calendar button moves into PART_TextBox's
  InnerRightContent, between the slots. Because that content sits outside
  the CalendarDatePicker template namescope, the icon/chevron state
  colours are driven by bindings to $parent[CalendarDatePicker] (and the
  chevron background by local styles on the border) rather than
  /template/ styles.
- Linux: the date and slots are overlaid on PART_Button as siblings so a
  slot click does not open the calendar; PART_Button's Min size follows
  the overlay bounds (with the normal input-height floor) so it always
  covers the slots.
- SampleApp: demonstrate the three slots on the CalendarDatePicker page.
Copilot AI review requested due to automatic review settings July 29, 2026 13:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds three CalendarDatePicker content slots across the MacOS, DevExpress, and Linux themes.

Changes:

  • Introduces attached properties for the three slots.
  • Updates themes and TextBox padding overrides.
  • Adds an interactive SampleApp demonstration.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
MacOS/Controls/TextBox.axaml Makes slot padding overrideable.
MacOS/Controls/CalendarDatePicker.axaml Integrates slots and calendar button.
Linux/Controls/CalendarDatePicker.axaml Adds an overlaid slot layout.
DevExpress/Controls/TextBox.axaml Makes slot padding overrideable.
DevExpress/Controls/CalendarDatePicker.axaml Integrates slots and chevron states.
AttachedProperties/CalendarDatePickerContent.cs Defines the slot API.
CalendarDatePickerViewModel.cs Adds demo commands and state.
CalendarDatePickerDemo.axaml Demonstrates the new slots.
Comments suppressed due to low confidence (2)

samples/SampleApp/DemoPages/CalendarDatePickerDemo.axaml:71

  • This color-only button has no accessible name, so assistive technology cannot communicate that it selects red. Add an explicit automation name.
              <Button Command="{Binding RedCommand}"><Border Width="8" Height="8" Background="Red" /></Button>

samples/SampleApp/DemoPages/CalendarDatePickerDemo.axaml:68

  • This color-only button has no accessible name, so assistive technology cannot communicate that it selects orange. Add an explicit automation name.
              <Button Command="{Binding OrangeCommand}"><Border Width="8" Height="8" Background="Orange" /></Button>

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread samples/SampleApp/DemoPages/CalendarDatePickerDemo.axaml
Comment thread samples/SampleApp/DemoPages/CalendarDatePickerDemo.axaml
randy-but-a-ro[bot]
randy-but-a-ro Bot previously approved these changes Jul 29, 2026

@randy-but-a-ro randy-but-a-ro Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Pull request was approved automatically: the AI review is complete and all its review threads are resolved. 🎉

Integration Details
{
	"deliveryId": "31875d50-8b52-11f1-9cd1-e3fa99782534",
	"headSha": "dff2b14fa5bc8434451ca4341e4d1aef1d1deb44",
	"reviewer": "copilot-pull-request-reviewer[bot]"
}

@xfortin-devolutions

Copy link
Copy Markdown
Contributor Author

Anna Malchow-Perryman (@apman) For this one, I'd still rather have a review from you since I'm touching the themes of already existing controls.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for the most part. Just a couple of changes I noticed:

The TextBox keeps focus when the calendar view is opened. Functionally that doesn't make much of a difference to before, because we don't have keyboard navigation for the calendar popup yet anyway. But maybe keeping the focus in the box makes that situation less clear (e.g. when days happen to be selected in the TextBox and you in-/decrease them with the arrow keys, it appears as if the popup calendar is responding to keyboard input as it updates based on the TextBox values (but that's not working for year or month).

Also, when you tab while the popup is open, the focus moves to the next without closing the current one (so the focussed control is hidden by the previously opened one)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/Devolutions.AvaloniaControls/AttachedProperties/CalendarDatePickerContent.cs:44

  • The lazily returned AvaloniaList can be mutated after the template is applied, but each theme computes slot visibility by converting only the attached-property value. Collection changes do not change that property value, so adding the first item leaves its ItemsControl collapsed (and removing the last leaves its spacing visible). Drive visibility from each ItemsControl's collection-aware :empty state (or another item-count notification) instead.
        AvaloniaList<Control> list = [];

randy-but-a-ro[bot]
randy-but-a-ro Bot previously approved these changes Jul 29, 2026

@randy-but-a-ro randy-but-a-ro Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Pull request was approved automatically: the AI review is complete and all its review threads are resolved. 🎉

Integration Details
{
	"deliveryId": "e844b050-8b7b-11f1-986c-132840bd9384",
	"headSha": "539ba4cb984a3aed3810702ac8fcfb5ee4210a50",
	"reviewer": "copilot-pull-request-reviewer[bot]"
}

@xfortin-devolutions

Copy link
Copy Markdown
Contributor Author

The TextBox keeps focus when the calendar view is opened. Functionally that doesn't make much of a difference to before, because we don't have keyboard navigation for the calendar popup yet anyway. But maybe keeping the focus in the box makes that situation less clear (e.g. when days happen to be selected in the TextBox and you in-/decrease them with the arrow keys, it appears as if the popup calendar is responding to keyboard input as it updates based on the TextBox values (but that's not working for year or month).

Also, when you tab while the popup is open, the focus moves to the next without closing the current one (so the focussed control is hidden by the previously opened one)

True! I didn't notice this in my test. I'll look into fixing both.

Moving the calendar button into PART_TextBox (to host the inner-content
slots) trapped keyboard focus in the text box when the popup opened: the
box kept focus (its segmented arrow-keys looked like calendar input) and
Tab no longer closed the popup.

Restore the original behaviour:
- CalendarDatePickerBehavior focuses the calendar when the popup opens
  (so focus leaves the text box, and Avalonia's light-dismiss closes it
  on Tab again) and returns focus to the picker's text box (or the button
  on Yaru, where the text box is hidden) when it closes, keeping the
  normal tab order.
- PART_Calendar is marked Focusable="True" in the MacOS, DevExpress and
  Linux templates so that Focus() takes effect.
@xfortin-devolutions

Copy link
Copy Markdown
Contributor Author

Anna Malchow-Perryman (@apman) I believe I've covered everything with the last three commits. The plus side of this, now, just by focusing the Calendar when shown, we get in bonus the keyboard navigation in it.

I've notice one more bug, which is not a regression though (it was already a bug before my changes). Tab navigation doesn't work with CalendarDatePicker in the macOS theme. That is, one a CalendarDatePicker gets the focus, it never relinquishes it again through tab key press. I did not address this as par of this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/Devolutions.AvaloniaControls/Behaviors/CalendarDatePickerBehavior.cs:62

  • This unconditionally re-focuses the picker after every close. When light-dismiss is caused by clicking another focusable control, the click first focuses that control, then this background callback steals focus back to the picker. Only restore focus when it is still in the popup (or no element received focus); preserve focus established by an external click.
        Dispatcher.UIThread.Post(() => ReturnFocusToPicker(datePicker), DispatcherPriority.Background);

src/Devolutions.AvaloniaControls/AttachedProperties/CalendarDatePickerContent.cs:45

  • The returned list is mutable, but each theme computes slot visibility from the attached-property value only. Mutating this AvaloniaList raises collection changes, not an attached-property change, so an initially empty slot remains collapsed after its first item is added (and removing its last item does not re-collapse it). Make visibility observe collection changes, or structure the templates so empty ItemsControls do not require a converted IsVisible binding.
        AvaloniaList<Control> list = [];
        element.SetValue(property, list);

src/Devolutions.AvaloniaTheme.Linux/Controls/CalendarDatePicker.axaml:75

  • PART_Button is the focusable Linux picker surface, but it no longer has content or an automation name. Its automation peer therefore loses the selected-date/placeholder text that previously came from Button.Content, leaving screen-reader users on an unnamed button. Bind AutomationProperties.Name to the same first-non-empty text while keeping the visual text in the overlay.
              <Button Name="PART_Button"
                      Foreground="{Binding Path=#PART_TextBox.Foreground}"
                      MinWidth="{Binding #SlotOverlay.Bounds.Width}"
                      MinHeight="{Binding #SlotOverlay.Bounds.Height}" />

@randy-but-a-ro randy-but-a-ro Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Pull request was approved automatically: the AI review is complete and all its review threads are resolved. 🎉

Integration Details
{
	"deliveryId": "25ab89f0-8b8b-11f1-8bcf-561651bef420",
	"headSha": "8d1cfdf6b56ae84e651d4b27a14bdbe89883a094",
	"reviewer": "copilot-pull-request-reviewer[bot]"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've notice one more bug, which is not a regression though (it was already a bug before my changes). Tab navigation doesn't work with CalendarDatePicker in the macOS theme. That is, one a CalendarDatePicker gets the focus, it never relinquishes it again through tab key press. I did not address this as par of this PR.

Xavier Fortin (@xfortin-devolutions) - yes, and in DevExpress tabbing only works forward ... also didn't mention that since it wasn't due to your changes. I can make a note to address both some time.

Thanks for fixing the other stuff!

@xfortin-devolutions

Copy link
Copy Markdown
Contributor Author

Anna Malchow-Perryman (@apman) My pleasure!

@xfortin-devolutions
Xavier Fortin (xfortin-devolutions) merged commit 4ea2603 into master Jul 30, 2026
1 check passed
@xfortin-devolutions
Xavier Fortin (xfortin-devolutions) deleted the CalendarDatePicker-add-inner-slots branch July 30, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants