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)
6 changes: 6 additions & 0 deletions src/DateTimePickerWindows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @format
* @flow strict-local
*/

export {DateTimePickerWindows} from './DateTimePickerWindows.windows';
126 changes: 126 additions & 0 deletions src/DateTimePickerWindows.windows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* @format
* @flow strict-local
*/
import {
DATE_SET_ACTION,
TIME_SET_ACTION,
DISMISS_ACTION,
WINDOWS_MODE,
} from './constants';
import invariant from 'invariant';

import type {WindowsNativeProps} from './types';
import NativeModuleDatePickerWindows from './specs/NativeModuleDatePickerWindows';
import NativeModuleTimePickerWindows from './specs/NativeModuleTimePickerWindows';
import {
createDateTimeSetEvtParams,
createDismissEvtParams,
} from './eventCreators';

function open(props: WindowsNativeProps) {
const {
mode = WINDOWS_MODE.date,
value: originalValue,
is24Hour,
minimumDate,
maximumDate,
minuteInterval,
timeZoneOffsetInSeconds,
onChange,
onError,
testID,
firstDayOfWeek,
dayOfWeekFormat,
dateFormat,
placeholderText,
} = props;

invariant(originalValue, 'A date or time must be specified as `value` prop.');

const valueTimestamp = originalValue.getTime();

const presentPicker = async () => {
try {
let result;

if (mode === WINDOWS_MODE.date) {
// Use DatePicker TurboModule
invariant(
NativeModuleDatePickerWindows,
'NativeModuleDatePickerWindows is not available'
);

result = await NativeModuleDatePickerWindows.open({
maximumDate: maximumDate ? maximumDate.getTime() : undefined,
minimumDate: minimumDate ? minimumDate.getTime() : undefined,
timeZoneOffsetInSeconds,
dayOfWeekFormat,
dateFormat,
firstDayOfWeek,
placeholderText,
testID,
});
} else if (mode === WINDOWS_MODE.time) {
// Use TimePicker TurboModule
invariant(
NativeModuleTimePickerWindows,
'NativeModuleTimePickerWindows is not available'
);

result = await NativeModuleTimePickerWindows.open({
selectedTime: valueTimestamp,
is24Hour,
minuteInterval,
testID,
});
} else {
throw new Error(`Unsupported mode: ${mode}`);
}

const {action} = result;

if (action === DATE_SET_ACTION || action === TIME_SET_ACTION || action === 'dateSetAction' || action === 'timeSetAction') {
const event = createDateTimeSetEvtParams(
mode === WINDOWS_MODE.date ? result.timestamp : (result.hour * 3600 + result.minute * 60) * 1000,
result.utcOffset || 0
);
onChange && onChange(event, new Date(event.nativeEvent.timestamp));
} else if (action === DISMISS_ACTION || action === 'dismissedAction') {
const event = createDismissEvtParams();
onChange && onChange(event);
}

return result;
} catch (error) {
onError && onError(error);
throw error;
}
};

return presentPicker();
}

async function dismiss() {
// Try to dismiss both pickers since we don't know which one is open
try {
if (NativeModuleDatePickerWindows) {
await NativeModuleDatePickerWindows.dismiss();
}
} catch (e) {
// Ignore if not open
}

try {
if (NativeModuleTimePickerWindows) {
await NativeModuleTimePickerWindows.dismiss();
}
} catch (e) {
// Ignore if not open
}
}

export const DateTimePickerWindows = {
open,
dismiss,
};
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
import RNDateTimePicker from './datetimepicker';
export * from './eventCreators';
export {DateTimePickerAndroid} from './DateTimePickerAndroid';
export {DateTimePickerWindows} from './DateTimePickerWindows';

export default RNDateTimePicker;
Loading