Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
237 changes: 237 additions & 0 deletions docs/windows-xaml-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Windows XAML Support for React Native DateTimePicker

## Overview

This document describes the XAML-based implementation for Windows platform using React Native's new architecture (Fabric + TurboModules).

## Implementation Details

### Architecture

The Windows implementation now supports both:
1. **Legacy Architecture**: Using ViewManagers (`DateTimePickerViewManager`, `TimePickerViewManager`)
2. **New Architecture (Fabric + TurboModules)**:
- **Fabric Components**: Using XAML Islands with `CalendarDatePicker` control for declarative UI
- **TurboModules**: Using imperative API similar to Android (`DateTimePickerWindows.open()`)

The implementation automatically selects the appropriate architecture based on the `RNW_NEW_ARCH` compile-time flag.

### Key Components

#### 1. Native Component Spec
- **File**: `src/specs/DateTimePickerNativeComponent.js` (existing cross-platform spec)
- Defines the component interface using React Native's codegen
- Specifies props and events for the component

#### 2. TurboModule Specs
- **DatePicker**: `src/specs/NativeModuleDatePickerWindows.js`
- **TimePicker**: `src/specs/NativeModuleTimePickerWindows.js`
- Follow the same pattern as Android TurboModules
- Provide imperative API for opening pickers programmatically

#### 3. Codegen Headers

**Fabric Component (New Architecture)**:
- **File**: `windows/DateTimePickerWindows/codegen/react/components/DateTimePicker/DateTimePicker.g.h`
- Auto-generated-style header following React Native Windows codegen patterns
- Defines:
- `DateTimePickerProps`: Component properties
- `DateTimePickerEventEmitter`: Event handling
- `BaseDateTimePicker<T>`: Base template class for the component view
- `RegisterDateTimePickerNativeComponent<T>`: Registration helper

**TurboModules (New Architecture)**:
- **File**: `windows/DateTimePickerWindows/NativeModulesWindows.g.h`
- Defines specs for both DatePicker and TimePicker TurboModules
- Includes parameter structs and result structs
- Follows React Native TurboModule patterns

#### 4. Fabric Implementation
- **Header**: `windows/DateTimePickerWindows/DateTimePickerFabric.h`
- **Implementation**: `windows/DateTimePickerWindows/DateTimePickerFabric.cpp`
- **Component**: `DateTimePickerComponentView`
- Implements `BaseDateTimePicker<DateTimePickerComponentView>`
- Uses `Microsoft.UI.Xaml.XamlIsland` to host XAML content
- Uses `Microsoft.UI.Xaml.Controls.CalendarDatePicker` as the actual picker control

#### 5. TurboModule Implementations

**DatePicker TurboModule**:
- **Header**: `windows/DateTimePickerWindows/DatePickerModuleWindows.h`
- **Implementation**: `windows/DateTimePickerWindows/DatePickerModuleWindows.cpp`
- Provides imperative `open()` and `dismiss()` methods
- Returns promises with selected date or dismissal action

**TimePicker TurboModule**:
- **Header**: `windows/DateTimePickerWindows/TimePickerModuleWindows.h`
- **Implementation**: `windows/DateTimePickerWindows/TimePickerModuleWindows.cpp`
- Provides imperative `open()` and `dismiss()` methods
- Returns promises with selected time or dismissal action

#### 6. Package Provider
- **File**: `windows/DateTimePickerWindows/ReactPackageProvider.cpp`
- Updated to:
- Register Fabric component when `RNW_NEW_ARCH` is defined
- Register TurboModules using `AddAttributedModules()` for auto-discovery
- Register legacy ViewManagers otherwise

#### 7. JavaScript API
- **File**: `src/DateTimePickerWindows.windows.js`
- Provides `DateTimePickerWindows.open()` and `DateTimePickerWindows.dismiss()` methods
- Similar to `DateTimePickerAndroid` API
- Exported from main `index.js` for easy access

### XAML Integration

The Fabric implementation uses **XAML Islands** to host native XAML controls within the Composition-based Fabric renderer:

```cpp
// Initialize XAML Application
winrt::Microsoft::ReactNative::Xaml::implementation::XamlApplication::EnsureCreated();

// Create XamlIsland
m_xamlIsland = winrt::Microsoft::UI::Xaml::XamlIsland{};

// Create and set XAML control
m_calendarDatePicker = winrt::Microsoft::UI::Xaml::Controls::CalendarDatePicker{};
m_xamlIsland.Content(m_calendarDatePicker);

// Connect to Fabric's ContentIsland
islandView.Connect(m_xamlIsland.ContentIsland());
```

### Usage

#### Declarative Component (Fabric)

```javascript
import DateTimePicker from '@react-native-community/datetimepicker';

<DateTimePicker
value={new Date()}
mode="date"
onChange={handleDateChange}
/>
```

#### Imperative API (TurboModules)

```javascript
import {DateTimePickerWindows} from '@react-native-community/datetimepicker';

// Open date picker
DateTimePickerWindows.open({
value: new Date(),
mode: 'date',
minimumDate: new Date(2020, 0, 1),
maximumDate: new Date(2025, 11, 31),
onChange: (event, date) => {
if (event.type === 'set') {
console.log('Selected date:', date);
}
},
onError: (error) => {
console.error('Picker error:', error);
}
});

// Dismiss picker
DateTimePickerWindows.dismiss();
```

### Supported Properties

**Fabric Component** supports:
- `selectedDate`: Current date (milliseconds timestamp)
- `minimumDate`: Minimum selectable date
- `maximumDate`: Maximum selectable date
- `timeZoneOffsetInSeconds`: Timezone offset for date calculations
- `dayOfWeekFormat`: Format string for day of week display
- `dateFormat`: Format string for date display
- `firstDayOfWeek`: First day of the week (0-6)
- `placeholderText`: Placeholder text when no date is selected
- `accessibilityLabel`: Accessibility label for the control

**TurboModule API** supports:
- All the above properties via the `open()` method parameters
- Returns promises with action results (`dateSetAction`, `dismissedAction`)

### Events

**Fabric Component**:
- `onChange`: Fired when the date changes
- Event payload: `{ newDate: number }` (milliseconds timestamp)

**TurboModule API**:
- Promise-based: Resolves with `{action, timestamp, utcOffset}` for dates
- Or `{action, hour, minute}` for times

### Date/Time Conversion

The implementation includes helper functions to convert between JavaScript timestamps (milliseconds) and Windows `DateTime`:

- `DateTimeFrom(milliseconds, timezoneOffset)`: Converts JS timestamp to Windows DateTime
- `DateTimeToMilliseconds(dateTime, timezoneOffset)`: Converts Windows DateTime to JS timestamp

### Build Configuration

To build with XAML/Fabric/TurboModule support:
1. Ensure `RNW_NEW_ARCH` is defined in your build configuration
2. Include the new Fabric and TurboModule implementation files in your project
3. Link against required XAML libraries

## Comparison with Reference Implementation

This implementation follows the pattern established in the `xaml-calendar-view` sample from the React Native Windows repository (PR #15368), but extends it with TurboModules:

**Similarities**:
- Uses `XamlIsland` for hosting XAML content
- Implements codegen-based component registration
- Uses `ContentIslandComponentView` initializer pattern
- Follows the `BaseXXXX<T>` template pattern

**Extensions**:
- **TurboModule Support**: Added imperative API similar to Android
- **Promise-based API**: Modern async/await pattern for picker operations
- **Comprehensive property set**: Supports all date/time picker scenarios
- **Dual architecture**: Works with both legacy and new architecture

**Differences from Android**:
- Windows uses XAML Islands instead of native Android dialogs
- Different property names for some platform-specific features
- Windows TurboModules registered via `AddAttributedModules()`

## Testing

To test the implementation:

**Legacy Architecture**:
```javascript
import DateTimePicker from '@react-native-community/datetimepicker';
// Use as normal component
```

**New Architecture (Fabric Component)**:
1. Build with `RNW_NEW_ARCH` enabled
2. Use the component declaratively as shown above

**New Architecture (TurboModule API)**:
1. Build with `RNW_NEW_ARCH` enabled
2. Use `DateTimePickerWindows.open()` imperatively

## Future Enhancements

Potential improvements:
- Implement ContentDialog/Flyout for better picker presentation
- Add support for date range pickers
- Implement state management for complex scenarios
- Add more XAML-specific styling properties
- Performance optimizations for rapid prop updates
- Custom themes and styling support

## References

- [React Native Windows New Architecture](https://microsoft.github.io/react-native-windows/docs/new-architecture)
- [React Native TurboModules](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules)
- [XAML Islands Overview](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands)
- [Sample CalendarView PR #15368](https://github.com/microsoft/react-native-windows/pull/15368)
1 change: 1 addition & 0 deletions example/windows/DateTimePickerDemo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/Bundle
93 changes: 93 additions & 0 deletions example/windows/DateTimePickerDemo/App.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "pch.h"

#include "App.h"

#include "AutolinkedNativeModules.g.h"
#include "ReactPackageProvider.h"

using namespace winrt;
using namespace xaml;
using namespace xaml::Controls;
using namespace xaml::Navigation;

using namespace Windows::ApplicationModel;
namespace winrt::DateTimePickerDemo::implementation
{
/// <summary>
/// Initializes the singleton application object. This is the first line of
/// authored code executed, and as such is the logical equivalent of main() or
/// WinMain().
/// </summary>
App::App() noexcept
{
#if BUNDLE
JavaScriptBundleFile(L"index.windows");
InstanceSettings().UseFastRefresh(false);
#else
JavaScriptBundleFile(L"index");
InstanceSettings().UseFastRefresh(true);
#endif

#if _DEBUG
InstanceSettings().UseDirectDebugger(true);
InstanceSettings().UseDeveloperSupport(true);
#else
InstanceSettings().UseDirectDebugger(false);
InstanceSettings().UseDeveloperSupport(false);
#endif

RegisterAutolinkedNativeModulePackages(PackageProviders()); // Includes any autolinked modules

PackageProviders().Append(make<ReactPackageProvider>()); // Includes all modules in this project

InitializeComponent();
}

/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
void App::OnLaunched(activation::LaunchActivatedEventArgs const& e)
{
super::OnLaunched(e);

Frame rootFrame = Window::Current().Content().as<Frame>();
rootFrame.Navigate(xaml_typename<MainPage>(), box_value(e.Arguments()));
}

/// <summary>
/// Invoked when the application is activated by some means other than normal launching.
/// </summary>
void App::OnActivated(Activation::IActivatedEventArgs const &e) {
auto preActivationContent = Window::Current().Content();
super::OnActivated(e);
if (!preActivationContent && Window::Current()) {
Frame rootFrame = Window::Current().Content().as<Frame>();
rootFrame.Navigate(xaml_typename<MainPage>(), nullptr);
}
}

/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
void App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e)
{
// Save application state and stop any background activity
}

/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e)
{
throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name);
}

} // namespace winrt::DateTimePickerDemo::implementation
21 changes: 21 additions & 0 deletions example/windows/DateTimePickerDemo/App.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include "App.xaml.g.h"

#include <CppWinRTIncludes.h>

namespace activation = winrt::Windows::ApplicationModel::Activation;

namespace winrt::DateTimePickerDemo::implementation
{
struct App : AppT<App>
{
App() noexcept;
void OnLaunched(activation::LaunchActivatedEventArgs const&);
void OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs const &e);
void OnSuspending(IInspectable const&, Windows::ApplicationModel::SuspendingEventArgs const&);
void OnNavigationFailed(IInspectable const&, xaml::Navigation::NavigationFailedEventArgs const&);
private:
using super = AppT<App>;
};
} // namespace winrt::DateTimePickerDemo::implementation
3 changes: 3 additions & 0 deletions example/windows/DateTimePickerDemo/App.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace DateTimePickerDemo
{
}
10 changes: 10 additions & 0 deletions example/windows/DateTimePickerDemo/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<react:ReactApplication
x:Class="DateTimePickerDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DateTimePickerDemo"
xmlns:react="using:Microsoft.ReactNative">
<Application.Resources>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
</Application.Resources>
</react:ReactApplication>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions example/windows/DateTimePickerDemo/AutolinkedNativeModules.g.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows"
// clang-format off
#include "pch.h"
#include "AutolinkedNativeModules.g.h"

// Includes from @react-native-community/datetimepicker
#include <winrt/DateTimePicker.h>

namespace winrt::Microsoft::ReactNative
{

void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::ReactNative::IReactPackageProvider> const& packageProviders)
{
// IReactPackageProviders from @react-native-community/datetimepicker
packageProviders.Append(winrt::DateTimePicker::ReactPackageProvider());
}

}
Loading