react-native-windows 0.80.0-preview.8 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Directory.Build.props +6 -0
- package/Folly/TEMP_UntilFollyUpdate/json/json.cpp +1 -1
- package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +21 -156
- package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +24 -0
- package/Microsoft.ReactNative/Fabric/Composition/TextDrawing.cpp +37 -5
- package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +33 -15
- package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +216 -0
- package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +17 -0
- package/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/PlatformColorUtils.cpp +64 -0
- package/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/PlatformColorUtils.h +11 -0
- package/Microsoft.ReactNative/Utils/ThemeUtils.cpp +49 -0
- package/Microsoft.ReactNative/Utils/ThemeUtils.h +31 -0
- package/PropertySheets/Generated/PackageVersion.g.props +2 -2
- package/PropertySheets/Warnings.props +45 -0
- package/README.md +4 -0
- package/Scripts/rnw-dependencies.ps1 +16 -2
- package/package.json +5 -5
- package/templates/cpp-lib/example/metro.config.js +1 -1
package/Directory.Build.props
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
-->
|
|
20
20
|
<EnableSourceLink Condition="'$(EnableSourceLink)' == '' AND '$(BuildingInRnwRepo)' == 'true'">true</EnableSourceLink>
|
|
21
21
|
<EnableSourceLink Condition="'$(EnableSourceLink)' == ''">false</EnableSourceLink>
|
|
22
|
+
|
|
23
|
+
<!-- Symbol Publishing Compliance (Work Item 59264834) - Generate PDB files for Release builds -->
|
|
24
|
+
<DebugSymbols Condition="'$(Configuration)' == 'Release'">true</DebugSymbols>
|
|
25
|
+
<DebugType Condition="'$(Configuration)' == 'Release'">pdbonly</DebugType>
|
|
26
|
+
<IncludeSymbols Condition="'$(Configuration)' == 'Release'">true</IncludeSymbols>
|
|
27
|
+
<IncludeSource Condition="'$(Configuration)' == 'Release'">false</IncludeSource>
|
|
22
28
|
<!-- When bumping the Folly version, be sure to bump the git hash of that version's commit, find the matching fastfloat dependency and build Folly.vcxproj (to update its cgmanifest.json) too. -->
|
|
23
29
|
<FollyVersion>2024.10.14.00</FollyVersion>
|
|
24
30
|
<FastFloatVersion>6.1.4</FastFloatVersion>
|
|
@@ -908,7 +908,7 @@ void escapeStringImpl(
|
|
|
908
908
|
|
|
909
909
|
if (encodeUnicode) {
|
|
910
910
|
// note that this if condition captures utf8 chars
|
|
911
|
-
// with value > 127, so size > 1 byte (or they are
|
|
911
|
+
// with value > 127, so size > 1 byte (or they are allowlisted for
|
|
912
912
|
// Unicode encoding).
|
|
913
913
|
// NOTE: char32_t / char16_t are both unsigned.
|
|
914
914
|
char32_t cp = utf8ToCodePoint(p, e, opts.skip_invalid_utf8);
|
|
@@ -313,161 +313,6 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTE
|
|
|
313
313
|
return S_OK;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
long GetControlTypeFromString(const std::string &role) noexcept {
|
|
317
|
-
if (role == "adjustable") {
|
|
318
|
-
return UIA_SliderControlTypeId;
|
|
319
|
-
} else if (role == "group" || role == "search" || role == "radiogroup" || role == "timer" || role.empty()) {
|
|
320
|
-
return UIA_GroupControlTypeId;
|
|
321
|
-
} else if (role == "button" || role == "imagebutton" || role == "switch" || role == "togglebutton") {
|
|
322
|
-
return UIA_ButtonControlTypeId;
|
|
323
|
-
} else if (role == "checkbox") {
|
|
324
|
-
return UIA_CheckBoxControlTypeId;
|
|
325
|
-
} else if (role == "combobox") {
|
|
326
|
-
return UIA_ComboBoxControlTypeId;
|
|
327
|
-
} else if (role == "alert" || role == "header" || role == "summary" || role == "text") {
|
|
328
|
-
return UIA_TextControlTypeId;
|
|
329
|
-
} else if (role == "image") {
|
|
330
|
-
return UIA_ImageControlTypeId;
|
|
331
|
-
} else if (role == "keyboardkey") {
|
|
332
|
-
return UIA_CustomControlTypeId;
|
|
333
|
-
} else if (role == "link") {
|
|
334
|
-
return UIA_HyperlinkControlTypeId;
|
|
335
|
-
}
|
|
336
|
-
// list and listitem were added by RNW to better support UIA Control Types
|
|
337
|
-
else if (role == "list") {
|
|
338
|
-
return UIA_ListControlTypeId;
|
|
339
|
-
} else if (role == "listitem") {
|
|
340
|
-
return UIA_ListItemControlTypeId;
|
|
341
|
-
} else if (role == "menu") {
|
|
342
|
-
return UIA_MenuControlTypeId;
|
|
343
|
-
} else if (role == "menubar") {
|
|
344
|
-
return UIA_MenuBarControlTypeId;
|
|
345
|
-
} else if (role == "menuitem") {
|
|
346
|
-
return UIA_MenuItemControlTypeId;
|
|
347
|
-
}
|
|
348
|
-
// If role is "none", remove the element from the control tree
|
|
349
|
-
// and expose it as a plain element would in the raw tree.
|
|
350
|
-
else if (role == "none") {
|
|
351
|
-
return UIA_GroupControlTypeId;
|
|
352
|
-
} else if (role == "progressbar") {
|
|
353
|
-
return UIA_ProgressBarControlTypeId;
|
|
354
|
-
} else if (role == "radio") {
|
|
355
|
-
return UIA_RadioButtonControlTypeId;
|
|
356
|
-
} else if (role == "scrollbar") {
|
|
357
|
-
return UIA_ScrollBarControlTypeId;
|
|
358
|
-
} else if (role == "spinbutton") {
|
|
359
|
-
return UIA_SpinnerControlTypeId;
|
|
360
|
-
} else if (role == "splitbutton") {
|
|
361
|
-
return UIA_SplitButtonControlTypeId;
|
|
362
|
-
} else if (role == "tab") {
|
|
363
|
-
return UIA_TabItemControlTypeId;
|
|
364
|
-
} else if (role == "tablist") {
|
|
365
|
-
return UIA_TabControlTypeId;
|
|
366
|
-
} else if (role == "textinput" || role == "searchbox") {
|
|
367
|
-
return UIA_EditControlTypeId;
|
|
368
|
-
} else if (role == "toolbar") {
|
|
369
|
-
return UIA_ToolBarControlTypeId;
|
|
370
|
-
} else if (role == "tree") {
|
|
371
|
-
return UIA_TreeControlTypeId;
|
|
372
|
-
} else if (role == "treeitem") {
|
|
373
|
-
return UIA_TreeItemControlTypeId;
|
|
374
|
-
} else if (role == "pane") {
|
|
375
|
-
return UIA_PaneControlTypeId;
|
|
376
|
-
}
|
|
377
|
-
assert(false);
|
|
378
|
-
return UIA_GroupControlTypeId;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
long GetControlTypeFromRole(const facebook::react::Role &role) noexcept {
|
|
382
|
-
switch (role) {
|
|
383
|
-
case facebook::react::Role::Alert:
|
|
384
|
-
return UIA_TextControlTypeId;
|
|
385
|
-
case facebook::react::Role::Application:
|
|
386
|
-
return UIA_WindowControlTypeId;
|
|
387
|
-
case facebook::react::Role::Button:
|
|
388
|
-
return UIA_ButtonControlTypeId;
|
|
389
|
-
case facebook::react::Role::Checkbox:
|
|
390
|
-
return UIA_CheckBoxControlTypeId;
|
|
391
|
-
case facebook::react::Role::Columnheader:
|
|
392
|
-
return UIA_HeaderControlTypeId;
|
|
393
|
-
case facebook::react::Role::Combobox:
|
|
394
|
-
return UIA_ComboBoxControlTypeId;
|
|
395
|
-
case facebook::react::Role::Document:
|
|
396
|
-
return UIA_DocumentControlTypeId;
|
|
397
|
-
case facebook::react::Role::Grid:
|
|
398
|
-
return UIA_GroupControlTypeId;
|
|
399
|
-
case facebook::react::Role::Group:
|
|
400
|
-
return UIA_GroupControlTypeId;
|
|
401
|
-
case facebook::react::Role::Heading:
|
|
402
|
-
return UIA_TextControlTypeId;
|
|
403
|
-
case facebook::react::Role::Img:
|
|
404
|
-
return UIA_ImageControlTypeId;
|
|
405
|
-
case facebook::react::Role::Link:
|
|
406
|
-
return UIA_HyperlinkControlTypeId;
|
|
407
|
-
case facebook::react::Role::List:
|
|
408
|
-
return UIA_ListControlTypeId;
|
|
409
|
-
case facebook::react::Role::Listitem:
|
|
410
|
-
return UIA_ListItemControlTypeId;
|
|
411
|
-
case facebook::react::Role::Menu:
|
|
412
|
-
return UIA_MenuControlTypeId;
|
|
413
|
-
case facebook::react::Role::Menubar:
|
|
414
|
-
return UIA_MenuBarControlTypeId;
|
|
415
|
-
case facebook::react::Role::Menuitem:
|
|
416
|
-
return UIA_MenuItemControlTypeId;
|
|
417
|
-
case facebook::react::Role::None:
|
|
418
|
-
return UIA_GroupControlTypeId;
|
|
419
|
-
case facebook::react::Role::Presentation:
|
|
420
|
-
return UIA_GroupControlTypeId;
|
|
421
|
-
case facebook::react::Role::Progressbar:
|
|
422
|
-
return UIA_ProgressBarControlTypeId;
|
|
423
|
-
case facebook::react::Role::Radio:
|
|
424
|
-
return UIA_RadioButtonControlTypeId;
|
|
425
|
-
case facebook::react::Role::Radiogroup:
|
|
426
|
-
return UIA_GroupControlTypeId;
|
|
427
|
-
case facebook::react::Role::Rowgroup:
|
|
428
|
-
return UIA_GroupControlTypeId;
|
|
429
|
-
case facebook::react::Role::Rowheader:
|
|
430
|
-
return UIA_HeaderControlTypeId;
|
|
431
|
-
case facebook::react::Role::Scrollbar:
|
|
432
|
-
return UIA_ScrollBarControlTypeId;
|
|
433
|
-
case facebook::react::Role::Searchbox:
|
|
434
|
-
return UIA_EditControlTypeId;
|
|
435
|
-
case facebook::react::Role::Separator:
|
|
436
|
-
return UIA_SeparatorControlTypeId;
|
|
437
|
-
case facebook::react::Role::Slider:
|
|
438
|
-
return UIA_SliderControlTypeId;
|
|
439
|
-
case facebook::react::Role::Spinbutton:
|
|
440
|
-
return UIA_SpinnerControlTypeId;
|
|
441
|
-
case facebook::react::Role::Status:
|
|
442
|
-
return UIA_StatusBarControlTypeId;
|
|
443
|
-
case facebook::react::Role::Summary:
|
|
444
|
-
return UIA_GroupControlTypeId;
|
|
445
|
-
case facebook::react::Role::Switch:
|
|
446
|
-
return UIA_ButtonControlTypeId;
|
|
447
|
-
case facebook::react::Role::Tab:
|
|
448
|
-
return UIA_TabItemControlTypeId;
|
|
449
|
-
case facebook::react::Role::Table:
|
|
450
|
-
return UIA_TableControlTypeId;
|
|
451
|
-
case facebook::react::Role::Tablist:
|
|
452
|
-
return UIA_TabControlTypeId;
|
|
453
|
-
case facebook::react::Role::Tabpanel:
|
|
454
|
-
return UIA_TabControlTypeId;
|
|
455
|
-
case facebook::react::Role::Timer:
|
|
456
|
-
return UIA_ButtonControlTypeId;
|
|
457
|
-
case facebook::react::Role::Toolbar:
|
|
458
|
-
return UIA_ToolBarControlTypeId;
|
|
459
|
-
case facebook::react::Role::Tooltip:
|
|
460
|
-
return UIA_ToolTipControlTypeId;
|
|
461
|
-
case facebook::react::Role::Tree:
|
|
462
|
-
return UIA_TreeControlTypeId;
|
|
463
|
-
case facebook::react::Role::Treegrid:
|
|
464
|
-
return UIA_TreeControlTypeId;
|
|
465
|
-
case facebook::react::Role::Treeitem:
|
|
466
|
-
return UIA_TreeItemControlTypeId;
|
|
467
|
-
}
|
|
468
|
-
return UIA_GroupControlTypeId;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
316
|
HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERTYID propertyId, VARIANT *pRetVal) {
|
|
472
317
|
if (pRetVal == nullptr)
|
|
473
318
|
return E_POINTER;
|
|
@@ -561,7 +406,18 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT
|
|
|
561
406
|
}
|
|
562
407
|
case UIA_IsOffscreenPropertyId: {
|
|
563
408
|
pRetVal->vt = VT_BOOL;
|
|
564
|
-
|
|
409
|
+
|
|
410
|
+
// Check if element is offscreen - consider modal content special case
|
|
411
|
+
bool isOffscreen = (compositionView->getClipState() == ClipState::FullyClipped);
|
|
412
|
+
|
|
413
|
+
// Modal content may appear clipped but is visible in its own window
|
|
414
|
+
if (isOffscreen) {
|
|
415
|
+
if (const auto hwnd = compositionView->GetHwndForParenting()) {
|
|
416
|
+
isOffscreen = !(IsWindowVisible(hwnd) && !IsIconic(hwnd));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
pRetVal->boolVal = isOffscreen ? VARIANT_TRUE : VARIANT_FALSE;
|
|
565
421
|
break;
|
|
566
422
|
}
|
|
567
423
|
case UIA_HelpTextPropertyId: {
|
|
@@ -618,6 +474,11 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT
|
|
|
618
474
|
pRetVal->bstrVal = SysAllocString(desc.c_str());
|
|
619
475
|
break;
|
|
620
476
|
}
|
|
477
|
+
case UIA_HeadingLevelPropertyId: {
|
|
478
|
+
pRetVal->vt = VT_I4;
|
|
479
|
+
pRetVal->lVal = GetHeadingLevel(props->accessibilityLevel, props->accessibilityRole, props->role);
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
621
482
|
}
|
|
622
483
|
return hr;
|
|
623
484
|
}
|
|
@@ -1009,7 +870,9 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::Expand() {
|
|
|
1009
870
|
|
|
1010
871
|
if (!strongView)
|
|
1011
872
|
return UIA_E_ELEMENTNOTAVAILABLE;
|
|
873
|
+
|
|
1012
874
|
DispatchAccessibilityAction(m_view, "expand");
|
|
875
|
+
|
|
1013
876
|
return S_OK;
|
|
1014
877
|
}
|
|
1015
878
|
|
|
@@ -1018,7 +881,9 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::Collapse() {
|
|
|
1018
881
|
|
|
1019
882
|
if (!strongView)
|
|
1020
883
|
return UIA_E_ELEMENTNOTAVAILABLE;
|
|
884
|
+
|
|
1021
885
|
DispatchAccessibilityAction(m_view, "collapse");
|
|
886
|
+
|
|
1022
887
|
return S_OK;
|
|
1023
888
|
}
|
|
1024
889
|
|
|
@@ -831,6 +831,30 @@ void ComponentView::updateAccessibilityProps(
|
|
|
831
831
|
oldViewProps.accessibilityValue.text,
|
|
832
832
|
newViewProps.accessibilityValue.text);
|
|
833
833
|
|
|
834
|
+
// Handle annotation properties with single call
|
|
835
|
+
winrt::Microsoft::ReactNative::implementation::UpdateUiaPropertiesForAnnotation(
|
|
836
|
+
EnsureUiaProvider(), oldViewProps.accessibilityAnnotation, newViewProps.accessibilityAnnotation);
|
|
837
|
+
|
|
838
|
+
// Handle expand/collapse state changes
|
|
839
|
+
if (oldViewProps.accessibilityState.has_value() != newViewProps.accessibilityState.has_value() ||
|
|
840
|
+
(oldViewProps.accessibilityState.has_value() && newViewProps.accessibilityState.has_value() &&
|
|
841
|
+
oldViewProps.accessibilityState->expanded != newViewProps.accessibilityState->expanded)) {
|
|
842
|
+
auto oldExpanded =
|
|
843
|
+
oldViewProps.accessibilityState.has_value() && oldViewProps.accessibilityState->expanded.has_value()
|
|
844
|
+
? oldViewProps.accessibilityState->expanded.value()
|
|
845
|
+
: false;
|
|
846
|
+
auto newExpanded =
|
|
847
|
+
newViewProps.accessibilityState.has_value() && newViewProps.accessibilityState->expanded.has_value()
|
|
848
|
+
? newViewProps.accessibilityState->expanded.value()
|
|
849
|
+
: false;
|
|
850
|
+
|
|
851
|
+
winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty(
|
|
852
|
+
EnsureUiaProvider(),
|
|
853
|
+
UIA_ExpandCollapseExpandCollapseStatePropertyId,
|
|
854
|
+
static_cast<int>(winrt::Microsoft::ReactNative::implementation::GetExpandCollapseState(oldExpanded)),
|
|
855
|
+
static_cast<int>(winrt::Microsoft::ReactNative::implementation::GetExpandCollapseState(newExpanded)));
|
|
856
|
+
}
|
|
857
|
+
|
|
834
858
|
if ((oldViewProps.accessibilityState.has_value() && oldViewProps.accessibilityState->selected.has_value()) !=
|
|
835
859
|
((newViewProps.accessibilityState.has_value() && newViewProps.accessibilityState->selected.has_value()))) {
|
|
836
860
|
auto compProvider =
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include "TextDrawing.h"
|
|
8
8
|
|
|
9
9
|
#include <AutoDraw.h>
|
|
10
|
+
#include <Fabric/platform/react/renderer/graphics/PlatformColorUtils.h>
|
|
10
11
|
#include <Utils/ValueUtils.h>
|
|
11
12
|
#include <unicode.h>
|
|
12
13
|
#include <windows.ui.composition.interop.h>
|
|
@@ -35,11 +36,27 @@ void RenderText(
|
|
|
35
36
|
// to cache and reuse a brush across all text elements instead, taking care to recreate
|
|
36
37
|
// it in the event of device removed.
|
|
37
38
|
winrt::com_ptr<ID2D1SolidColorBrush> brush;
|
|
39
|
+
|
|
40
|
+
// Check if we should use theme-aware default color instead of hardcoded black
|
|
41
|
+
bool useDefaultColor = false;
|
|
38
42
|
if (textAttributes.foregroundColor) {
|
|
43
|
+
auto &color = *textAttributes.foregroundColor;
|
|
44
|
+
// If it's black (or very dark) without explicit PlatformColor, use theme-aware color
|
|
45
|
+
if (color.m_platformColor.empty() && color.m_color.R <= 10 && color.m_color.G <= 10 && color.m_color.B <= 10) {
|
|
46
|
+
useDefaultColor = true;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
useDefaultColor = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (useDefaultColor) {
|
|
53
|
+
// Use theme-aware TextFillColorPrimary which adapts to light/dark mode
|
|
54
|
+
auto d2dColor = theme.D2DPlatformColor("TextFillColorPrimary");
|
|
55
|
+
winrt::check_hresult(deviceContext.CreateSolidColorBrush(d2dColor, brush.put()));
|
|
56
|
+
} else {
|
|
57
|
+
// User set explicit color or PlatformColor - use it
|
|
39
58
|
auto color = theme.D2DColor(*textAttributes.foregroundColor);
|
|
40
59
|
winrt::check_hresult(deviceContext.CreateSolidColorBrush(color, brush.put()));
|
|
41
|
-
} else {
|
|
42
|
-
winrt::check_hresult(deviceContext.CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 1.0f), brush.put()));
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
if (textAttributes.textDecorationLineType) {
|
|
@@ -72,12 +89,27 @@ void RenderText(
|
|
|
72
89
|
(fragment.textAttributes.foregroundColor != textAttributes.foregroundColor) ||
|
|
73
90
|
!isnan(fragment.textAttributes.opacity)) {
|
|
74
91
|
winrt::com_ptr<ID2D1SolidColorBrush> fragmentBrush;
|
|
92
|
+
|
|
93
|
+
// Check if we should use theme-aware default color for this fragment
|
|
94
|
+
bool useFragmentDefaultColor = false;
|
|
75
95
|
if (fragment.textAttributes.foregroundColor) {
|
|
96
|
+
auto &color = *fragment.textAttributes.foregroundColor;
|
|
97
|
+
// If it's black (or very dark) without explicit PlatformColor, use theme-aware color
|
|
98
|
+
if (color.m_platformColor.empty() && color.m_color.R <= 10 && color.m_color.G <= 10 && color.m_color.B <= 10) {
|
|
99
|
+
useFragmentDefaultColor = true;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
useFragmentDefaultColor = true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (useFragmentDefaultColor) {
|
|
106
|
+
// Use theme-aware TextFillColorPrimary which adapts to light/dark mode
|
|
107
|
+
auto d2dColor = theme.D2DPlatformColor("TextFillColorPrimary");
|
|
108
|
+
winrt::check_hresult(deviceContext.CreateSolidColorBrush(d2dColor, fragmentBrush.put()));
|
|
109
|
+
} else {
|
|
110
|
+
// User set explicit color or PlatformColor - use it
|
|
76
111
|
auto color = theme.D2DColor(*fragment.textAttributes.foregroundColor);
|
|
77
112
|
winrt::check_hresult(deviceContext.CreateSolidColorBrush(color, fragmentBrush.put()));
|
|
78
|
-
} else {
|
|
79
|
-
winrt::check_hresult(
|
|
80
|
-
deviceContext.CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 1.0f), fragmentBrush.put()));
|
|
81
113
|
}
|
|
82
114
|
|
|
83
115
|
if (fragment.textAttributes.textDecorationLineType) {
|
package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
#include <AutoDraw.h>
|
|
9
9
|
#include <Fabric/Composition/CompositionDynamicAutomationProvider.h>
|
|
10
10
|
#include <Fabric/Composition/UiaHelpers.h>
|
|
11
|
+
#include <Fabric/platform/react/renderer/graphics/PlatformColorUtils.h>
|
|
12
|
+
#include <Utils/ThemeUtils.h>
|
|
11
13
|
#include <Utils/ValueUtils.h>
|
|
12
14
|
#include <react/renderer/components/textinput/TextInputState.h>
|
|
15
|
+
#include <react/renderer/graphics/HostPlatformColor.h>
|
|
13
16
|
#include <react/renderer/textlayoutmanager/WindowsTextLayoutManager.h>
|
|
14
17
|
#include <tom.h>
|
|
15
18
|
#include <unicode.h>
|
|
@@ -316,8 +319,10 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
|
|
|
316
319
|
|
|
317
320
|
switch (nIndex) {
|
|
318
321
|
case COLOR_WINDOWTEXT:
|
|
319
|
-
if (m_outer->windowsTextInputProps().textAttributes.foregroundColor)
|
|
320
|
-
|
|
322
|
+
if (m_outer->windowsTextInputProps().textAttributes.foregroundColor) {
|
|
323
|
+
auto color = m_outer->theme()->Color(*m_outer->windowsTextInputProps().textAttributes.foregroundColor);
|
|
324
|
+
return RGB(color.R, color.G, color.B);
|
|
325
|
+
}
|
|
321
326
|
// cr = 0x000000FF;
|
|
322
327
|
break;
|
|
323
328
|
case COLOR_WINDOW:
|
|
@@ -326,8 +331,10 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
|
|
|
326
331
|
break;
|
|
327
332
|
|
|
328
333
|
case COLOR_HIGHLIGHT:
|
|
329
|
-
if (m_outer->windowsTextInputProps().selectionColor)
|
|
330
|
-
|
|
334
|
+
if (m_outer->windowsTextInputProps().selectionColor) {
|
|
335
|
+
auto color = m_outer->theme()->Color(*m_outer->windowsTextInputProps().selectionColor);
|
|
336
|
+
return RGB(color.R, color.G, color.B);
|
|
337
|
+
}
|
|
331
338
|
break;
|
|
332
339
|
|
|
333
340
|
case COLOR_HIGHLIGHTTEXT:
|
|
@@ -340,8 +347,9 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
|
|
|
340
347
|
int r = GetRValue(selectionColor);
|
|
341
348
|
int g = GetGValue(selectionColor);
|
|
342
349
|
int b = GetBValue(selectionColor);
|
|
343
|
-
int brightness = (r
|
|
344
|
-
return brightness >
|
|
350
|
+
int brightness = ::Microsoft::ReactNative::CalculateColorBrightness(r, g, b);
|
|
351
|
+
return brightness > ::Microsoft::ReactNative::kCaretSelectionBrightnessThreshold ? RGB(0, 0, 0)
|
|
352
|
+
: RGB(255, 255, 255);
|
|
345
353
|
}
|
|
346
354
|
break;
|
|
347
355
|
|
|
@@ -1077,13 +1085,9 @@ std::string WindowsTextInputComponentView::DefaultHelpText() const noexcept {
|
|
|
1077
1085
|
void WindowsTextInputComponentView::updateCursorColor(
|
|
1078
1086
|
const facebook::react::SharedColor &cursorColor,
|
|
1079
1087
|
const facebook::react::SharedColor &foregroundColor) noexcept {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
m_caretVisual.Brush(theme()->Brush(*foregroundColor));
|
|
1084
|
-
} else {
|
|
1085
|
-
m_caretVisual.Brush(theme()->PlatformBrush("TextControlForeground"));
|
|
1086
|
-
}
|
|
1088
|
+
const auto &props = windowsTextInputProps();
|
|
1089
|
+
auto caretColor = ::Microsoft::ReactNative::GetCaretColor(cursorColor, foregroundColor, props.backgroundColor);
|
|
1090
|
+
m_caretVisual.Brush(theme()->Brush(*caretColor));
|
|
1087
1091
|
}
|
|
1088
1092
|
|
|
1089
1093
|
void WindowsTextInputComponentView::updateProps(
|
|
@@ -1595,6 +1599,8 @@ void WindowsTextInputComponentView::ensureDrawingSurface() noexcept {
|
|
|
1595
1599
|
|
|
1596
1600
|
void WindowsTextInputComponentView::ShowCaret(bool show) noexcept {
|
|
1597
1601
|
ensureVisual();
|
|
1602
|
+
const auto &props = windowsTextInputProps();
|
|
1603
|
+
updateCursorColor(props.cursorColor, props.textAttributes.foregroundColor);
|
|
1598
1604
|
m_caretVisual.IsVisible(show);
|
|
1599
1605
|
}
|
|
1600
1606
|
|
|
@@ -1689,8 +1695,20 @@ void WindowsTextInputComponentView::DrawText() noexcept {
|
|
|
1689
1695
|
auto color = theme()->D2DColor(*props.placeholderTextColor);
|
|
1690
1696
|
winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(color, brush.put()));
|
|
1691
1697
|
} else {
|
|
1692
|
-
|
|
1693
|
-
|
|
1698
|
+
// Use theme-aware placeholder color based on focus state and background
|
|
1699
|
+
// Color selection follows Windows 11 design system semantic colors:
|
|
1700
|
+
// - High contrast: System GrayText for accessibility
|
|
1701
|
+
// - Light backgrounds: Darker grays for better contrast
|
|
1702
|
+
// - Dark backgrounds: Lighter grays for readability
|
|
1703
|
+
winrt::Windows::UI::Color backgroundColor = {};
|
|
1704
|
+
if (facebook::react::isColorMeaningful(props.backgroundColor)) {
|
|
1705
|
+
auto bgColor = (*props.backgroundColor).AsWindowsColor();
|
|
1706
|
+
backgroundColor = bgColor;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
auto placeholderColor = facebook::react::GetTextInputPlaceholderColor(m_hasFocus, backgroundColor);
|
|
1710
|
+
auto d2dColor = theme()->D2DColor(*placeholderColor);
|
|
1711
|
+
winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(d2dColor, brush.put()));
|
|
1694
1712
|
}
|
|
1695
1713
|
|
|
1696
1714
|
// Create placeholder text layout
|
|
@@ -175,6 +175,15 @@ void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, int oldV
|
|
|
175
175
|
UiaRaiseAutomationPropertyChangedEvent(spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue));
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, long oldValue, long newValue) noexcept {
|
|
179
|
+
auto spProviderSimple = provider.try_as<IRawElementProviderSimple>();
|
|
180
|
+
|
|
181
|
+
if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId))
|
|
182
|
+
return;
|
|
183
|
+
|
|
184
|
+
UiaRaiseAutomationPropertyChangedEvent(spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue));
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
void UpdateUiaProperty(
|
|
179
188
|
winrt::IInspectable provider,
|
|
180
189
|
PROPERTYID propId,
|
|
@@ -199,6 +208,29 @@ void UpdateUiaProperty(
|
|
|
199
208
|
UpdateUiaProperty(provider, propId, oldData, newData);
|
|
200
209
|
}
|
|
201
210
|
|
|
211
|
+
void UpdateUiaPropertiesForAnnotation(
|
|
212
|
+
winrt::IInspectable provider,
|
|
213
|
+
const std::optional<facebook::react::AccessibilityAnnotation> &oldAnnotation,
|
|
214
|
+
const std::optional<facebook::react::AccessibilityAnnotation> &newAnnotation) noexcept {
|
|
215
|
+
// if no value fall back to a default value.
|
|
216
|
+
const auto &old_annotation = oldAnnotation.value_or(facebook::react::AccessibilityAnnotation());
|
|
217
|
+
const auto &new_annotation = newAnnotation.value_or(facebook::react::AccessibilityAnnotation());
|
|
218
|
+
|
|
219
|
+
// Update all annotation properties
|
|
220
|
+
UpdateUiaProperty(
|
|
221
|
+
provider,
|
|
222
|
+
UIA_AnnotationAnnotationTypeIdPropertyId,
|
|
223
|
+
GetAnnotationTypeId(old_annotation.typeID),
|
|
224
|
+
GetAnnotationTypeId(new_annotation.typeID));
|
|
225
|
+
|
|
226
|
+
UpdateUiaProperty(
|
|
227
|
+
provider, UIA_AnnotationAnnotationTypeNamePropertyId, old_annotation.typeName, new_annotation.typeName);
|
|
228
|
+
|
|
229
|
+
UpdateUiaProperty(provider, UIA_AnnotationAuthorPropertyId, old_annotation.author, new_annotation.author);
|
|
230
|
+
|
|
231
|
+
UpdateUiaProperty(provider, UIA_AnnotationDateTimePropertyId, old_annotation.dateTime, new_annotation.dateTime);
|
|
232
|
+
}
|
|
233
|
+
|
|
202
234
|
long GetLiveSetting(const std::string &liveRegion) noexcept {
|
|
203
235
|
if (liveRegion == "polite") {
|
|
204
236
|
return LiveSetting::Polite;
|
|
@@ -259,6 +291,190 @@ long GetAnnotationTypeId(const std::string &annotationType) noexcept {
|
|
|
259
291
|
return AnnotationType_Unknown;
|
|
260
292
|
}
|
|
261
293
|
|
|
294
|
+
long GetControlTypeFromString(const std::string &role) noexcept {
|
|
295
|
+
if (role == "adjustable") {
|
|
296
|
+
return UIA_SliderControlTypeId;
|
|
297
|
+
} else if (role == "group" || role == "search" || role == "radiogroup" || role == "timer" || role.empty()) {
|
|
298
|
+
return UIA_GroupControlTypeId;
|
|
299
|
+
} else if (role == "button" || role == "imagebutton" || role == "switch" || role == "togglebutton") {
|
|
300
|
+
return UIA_ButtonControlTypeId;
|
|
301
|
+
} else if (role == "checkbox") {
|
|
302
|
+
return UIA_CheckBoxControlTypeId;
|
|
303
|
+
} else if (role == "combobox") {
|
|
304
|
+
return UIA_ComboBoxControlTypeId;
|
|
305
|
+
} else if (role == "alert" || role == "header" || role == "summary" || role == "text") {
|
|
306
|
+
return UIA_TextControlTypeId;
|
|
307
|
+
} else if (role == "image") {
|
|
308
|
+
return UIA_ImageControlTypeId;
|
|
309
|
+
} else if (role == "keyboardkey") {
|
|
310
|
+
return UIA_CustomControlTypeId;
|
|
311
|
+
} else if (role == "link") {
|
|
312
|
+
return UIA_HyperlinkControlTypeId;
|
|
313
|
+
}
|
|
314
|
+
// list and listitem were added by RNW to better support UIA Control Types
|
|
315
|
+
else if (role == "list") {
|
|
316
|
+
return UIA_ListControlTypeId;
|
|
317
|
+
} else if (role == "listitem") {
|
|
318
|
+
return UIA_ListItemControlTypeId;
|
|
319
|
+
} else if (role == "menu") {
|
|
320
|
+
return UIA_MenuControlTypeId;
|
|
321
|
+
} else if (role == "menubar") {
|
|
322
|
+
return UIA_MenuBarControlTypeId;
|
|
323
|
+
} else if (role == "menuitem") {
|
|
324
|
+
return UIA_MenuItemControlTypeId;
|
|
325
|
+
}
|
|
326
|
+
// If role is "none", remove the element from the control tree
|
|
327
|
+
// and expose it as a plain element would in the raw tree.
|
|
328
|
+
else if (role == "none") {
|
|
329
|
+
return UIA_GroupControlTypeId;
|
|
330
|
+
} else if (role == "progressbar") {
|
|
331
|
+
return UIA_ProgressBarControlTypeId;
|
|
332
|
+
} else if (role == "radio") {
|
|
333
|
+
return UIA_RadioButtonControlTypeId;
|
|
334
|
+
} else if (role == "scrollbar") {
|
|
335
|
+
return UIA_ScrollBarControlTypeId;
|
|
336
|
+
} else if (role == "spinbutton") {
|
|
337
|
+
return UIA_SpinnerControlTypeId;
|
|
338
|
+
} else if (role == "splitbutton") {
|
|
339
|
+
return UIA_SplitButtonControlTypeId;
|
|
340
|
+
} else if (role == "tab") {
|
|
341
|
+
return UIA_TabItemControlTypeId;
|
|
342
|
+
} else if (role == "tablist") {
|
|
343
|
+
return UIA_TabControlTypeId;
|
|
344
|
+
} else if (role == "textinput" || role == "searchbox") {
|
|
345
|
+
return UIA_EditControlTypeId;
|
|
346
|
+
} else if (role == "toolbar") {
|
|
347
|
+
return UIA_ToolBarControlTypeId;
|
|
348
|
+
} else if (role == "tree") {
|
|
349
|
+
return UIA_TreeControlTypeId;
|
|
350
|
+
} else if (role == "treeitem") {
|
|
351
|
+
return UIA_TreeItemControlTypeId;
|
|
352
|
+
} else if (role == "pane") {
|
|
353
|
+
return UIA_PaneControlTypeId;
|
|
354
|
+
}
|
|
355
|
+
assert(false);
|
|
356
|
+
return UIA_GroupControlTypeId;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
long GetControlTypeFromRole(const facebook::react::Role &role) noexcept {
|
|
360
|
+
switch (role) {
|
|
361
|
+
case facebook::react::Role::Alert:
|
|
362
|
+
return UIA_TextControlTypeId;
|
|
363
|
+
case facebook::react::Role::Application:
|
|
364
|
+
return UIA_WindowControlTypeId;
|
|
365
|
+
case facebook::react::Role::Button:
|
|
366
|
+
return UIA_ButtonControlTypeId;
|
|
367
|
+
case facebook::react::Role::Checkbox:
|
|
368
|
+
return UIA_CheckBoxControlTypeId;
|
|
369
|
+
case facebook::react::Role::Columnheader:
|
|
370
|
+
return UIA_HeaderControlTypeId;
|
|
371
|
+
case facebook::react::Role::Combobox:
|
|
372
|
+
return UIA_ComboBoxControlTypeId;
|
|
373
|
+
case facebook::react::Role::Document:
|
|
374
|
+
return UIA_DocumentControlTypeId;
|
|
375
|
+
case facebook::react::Role::Grid:
|
|
376
|
+
return UIA_GroupControlTypeId;
|
|
377
|
+
case facebook::react::Role::Group:
|
|
378
|
+
return UIA_GroupControlTypeId;
|
|
379
|
+
case facebook::react::Role::Heading:
|
|
380
|
+
return UIA_TextControlTypeId;
|
|
381
|
+
case facebook::react::Role::Img:
|
|
382
|
+
return UIA_ImageControlTypeId;
|
|
383
|
+
case facebook::react::Role::Link:
|
|
384
|
+
return UIA_HyperlinkControlTypeId;
|
|
385
|
+
case facebook::react::Role::List:
|
|
386
|
+
return UIA_ListControlTypeId;
|
|
387
|
+
case facebook::react::Role::Listitem:
|
|
388
|
+
return UIA_ListItemControlTypeId;
|
|
389
|
+
case facebook::react::Role::Menu:
|
|
390
|
+
return UIA_MenuControlTypeId;
|
|
391
|
+
case facebook::react::Role::Menubar:
|
|
392
|
+
return UIA_MenuBarControlTypeId;
|
|
393
|
+
case facebook::react::Role::Menuitem:
|
|
394
|
+
return UIA_MenuItemControlTypeId;
|
|
395
|
+
case facebook::react::Role::None:
|
|
396
|
+
return UIA_GroupControlTypeId;
|
|
397
|
+
case facebook::react::Role::Presentation:
|
|
398
|
+
return UIA_GroupControlTypeId;
|
|
399
|
+
case facebook::react::Role::Progressbar:
|
|
400
|
+
return UIA_ProgressBarControlTypeId;
|
|
401
|
+
case facebook::react::Role::Radio:
|
|
402
|
+
return UIA_RadioButtonControlTypeId;
|
|
403
|
+
case facebook::react::Role::Radiogroup:
|
|
404
|
+
return UIA_GroupControlTypeId;
|
|
405
|
+
case facebook::react::Role::Rowgroup:
|
|
406
|
+
return UIA_GroupControlTypeId;
|
|
407
|
+
case facebook::react::Role::Rowheader:
|
|
408
|
+
return UIA_HeaderControlTypeId;
|
|
409
|
+
case facebook::react::Role::Scrollbar:
|
|
410
|
+
return UIA_ScrollBarControlTypeId;
|
|
411
|
+
case facebook::react::Role::Searchbox:
|
|
412
|
+
return UIA_EditControlTypeId;
|
|
413
|
+
case facebook::react::Role::Separator:
|
|
414
|
+
return UIA_SeparatorControlTypeId;
|
|
415
|
+
case facebook::react::Role::Slider:
|
|
416
|
+
return UIA_SliderControlTypeId;
|
|
417
|
+
case facebook::react::Role::Spinbutton:
|
|
418
|
+
return UIA_SpinnerControlTypeId;
|
|
419
|
+
case facebook::react::Role::Status:
|
|
420
|
+
return UIA_StatusBarControlTypeId;
|
|
421
|
+
case facebook::react::Role::Summary:
|
|
422
|
+
return UIA_GroupControlTypeId;
|
|
423
|
+
case facebook::react::Role::Switch:
|
|
424
|
+
return UIA_ButtonControlTypeId;
|
|
425
|
+
case facebook::react::Role::Tab:
|
|
426
|
+
return UIA_TabItemControlTypeId;
|
|
427
|
+
case facebook::react::Role::Table:
|
|
428
|
+
return UIA_TableControlTypeId;
|
|
429
|
+
case facebook::react::Role::Tablist:
|
|
430
|
+
return UIA_TabControlTypeId;
|
|
431
|
+
case facebook::react::Role::Tabpanel:
|
|
432
|
+
return UIA_TabControlTypeId;
|
|
433
|
+
case facebook::react::Role::Timer:
|
|
434
|
+
return UIA_ButtonControlTypeId;
|
|
435
|
+
case facebook::react::Role::Toolbar:
|
|
436
|
+
return UIA_ToolBarControlTypeId;
|
|
437
|
+
case facebook::react::Role::Tooltip:
|
|
438
|
+
return UIA_ToolTipControlTypeId;
|
|
439
|
+
case facebook::react::Role::Tree:
|
|
440
|
+
return UIA_TreeControlTypeId;
|
|
441
|
+
case facebook::react::Role::Treegrid:
|
|
442
|
+
return UIA_TreeControlTypeId;
|
|
443
|
+
case facebook::react::Role::Treeitem:
|
|
444
|
+
return UIA_TreeItemControlTypeId;
|
|
445
|
+
}
|
|
446
|
+
return UIA_GroupControlTypeId;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
long GetHeadingLevel(int headingLevel, const std::string &strRole, const facebook::react::Role &role) noexcept {
|
|
450
|
+
if (strRole != "header" && role != facebook::react::Role::Heading) {
|
|
451
|
+
return HeadingLevel_None;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
switch (headingLevel) {
|
|
455
|
+
case 1:
|
|
456
|
+
return HeadingLevel1;
|
|
457
|
+
case 2:
|
|
458
|
+
return HeadingLevel2;
|
|
459
|
+
case 3:
|
|
460
|
+
return HeadingLevel3;
|
|
461
|
+
case 4:
|
|
462
|
+
return HeadingLevel4;
|
|
463
|
+
case 5:
|
|
464
|
+
return HeadingLevel5;
|
|
465
|
+
case 6:
|
|
466
|
+
return HeadingLevel6;
|
|
467
|
+
case 7:
|
|
468
|
+
return HeadingLevel7;
|
|
469
|
+
case 8:
|
|
470
|
+
return HeadingLevel8;
|
|
471
|
+
case 9:
|
|
472
|
+
return HeadingLevel9;
|
|
473
|
+
default:
|
|
474
|
+
return HeadingLevel_None;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
262
478
|
bool accessibilityAnnotationHasValue(
|
|
263
479
|
const std::optional<facebook::react::AccessibilityAnnotation> &annotation) noexcept {
|
|
264
480
|
return annotation.has_value() &&
|
|
@@ -35,6 +35,12 @@ void UpdateUiaProperty(
|
|
|
35
35
|
int oldValue,
|
|
36
36
|
int newValue) noexcept;
|
|
37
37
|
|
|
38
|
+
void UpdateUiaProperty(
|
|
39
|
+
winrt::Windows::Foundation::IInspectable provider,
|
|
40
|
+
PROPERTYID propId,
|
|
41
|
+
long oldValue,
|
|
42
|
+
long newValue) noexcept;
|
|
43
|
+
|
|
38
44
|
void UpdateUiaProperty(
|
|
39
45
|
winrt::Windows::Foundation::IInspectable provider,
|
|
40
46
|
PROPERTYID propId,
|
|
@@ -47,10 +53,21 @@ void UpdateUiaProperty(
|
|
|
47
53
|
const std::optional<std::string> &oldValue,
|
|
48
54
|
const std::optional<std::string> &newValue) noexcept;
|
|
49
55
|
|
|
56
|
+
void UpdateUiaPropertiesForAnnotation(
|
|
57
|
+
winrt::Windows::Foundation::IInspectable provider,
|
|
58
|
+
const std::optional<facebook::react::AccessibilityAnnotation> &oldAnnotation,
|
|
59
|
+
const std::optional<facebook::react::AccessibilityAnnotation> &newAnnotation) noexcept;
|
|
60
|
+
|
|
50
61
|
long GetLiveSetting(const std::string &liveRegion) noexcept;
|
|
51
62
|
|
|
52
63
|
long GetAnnotationTypeId(const std::string &annotationType) noexcept;
|
|
53
64
|
|
|
65
|
+
long GetControlTypeFromRole(const facebook::react::Role &role) noexcept;
|
|
66
|
+
|
|
67
|
+
long GetControlTypeFromString(const std::string &role) noexcept;
|
|
68
|
+
|
|
69
|
+
long GetHeadingLevel(int headingLevel, const std::string &strRole, const facebook::react::Role &role) noexcept;
|
|
70
|
+
|
|
54
71
|
bool accessibilityAnnotationHasValue(
|
|
55
72
|
const std::optional<facebook::react::AccessibilityAnnotation> &annotation) noexcept;
|
|
56
73
|
|
package/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/PlatformColorUtils.cpp
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
#include "PlatformColorUtils.h"
|
|
5
5
|
#include <UI.Xaml.Media.h>
|
|
6
|
+
#include <Utils/ThemeUtils.h>
|
|
6
7
|
#include <Utils/ValueUtils.h>
|
|
7
8
|
#ifndef CORE_ABI
|
|
8
9
|
#include <XamlUtils.h>
|
|
9
10
|
#endif // CORE_ABI
|
|
11
|
+
#include <react/renderer/graphics/Color.h>
|
|
10
12
|
#include <winrt/Windows.UI.ViewManagement.h>
|
|
13
|
+
#include "HostPlatformColor.h"
|
|
11
14
|
|
|
12
15
|
namespace facebook::react {
|
|
13
16
|
|
|
@@ -142,4 +145,65 @@ winrt::Windows::UI::Color ResolvePlatformColor(const std::vector<std::string> &s
|
|
|
142
145
|
return {};
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
SharedColor GetTextInputPlaceholderColor(bool isFocused, const winrt::Windows::UI::Color &backgroundColor) {
|
|
149
|
+
// In high contrast mode, always use system GrayText for accessibility
|
|
150
|
+
auto accessibilitySettings{winrt::Windows::UI::ViewManagement::AccessibilitySettings()};
|
|
151
|
+
if (accessibilitySettings.HighContrast()) {
|
|
152
|
+
auto uiSettings{winrt::Windows::UI::ViewManagement::UISettings()};
|
|
153
|
+
auto grayText = uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::GrayText);
|
|
154
|
+
return hostPlatformColorFromRGBA(grayText.R, grayText.G, grayText.B, grayText.A);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// When no background color provided (transparent), use Windows system default
|
|
158
|
+
if (backgroundColor.A == 0) {
|
|
159
|
+
// Use system WindowText for default placeholder - matches RN Core behavior
|
|
160
|
+
auto uiSettings{winrt::Windows::UI::ViewManagement::UISettings()};
|
|
161
|
+
auto windowText = uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::WindowText);
|
|
162
|
+
// Make placeholder text lighter (60% opacity)
|
|
163
|
+
return hostPlatformColorFromRGBA(
|
|
164
|
+
windowText.R, windowText.G, windowText.B, static_cast<uint8_t>(windowText.A * 0.6f));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Use ITU-R BT.601 luminance calculation to determine background brightness
|
|
168
|
+
bool isLightBackground = Microsoft::ReactNative::IsColorLight(backgroundColor);
|
|
169
|
+
|
|
170
|
+
// Use Windows 11 design system semantic colors for optimal contrast and consistency
|
|
171
|
+
// Light backgrounds: TextFillColorPrimary (darker) for focus, TextFillColorSecondary for unfocused
|
|
172
|
+
// Dark backgrounds: TextFillColorPrimary (lighter) for focus, TextFillColorSecondary for unfocused
|
|
173
|
+
if (isLightBackground) {
|
|
174
|
+
if (isFocused) {
|
|
175
|
+
auto color = ResolvePlatformColor({"TextFillColorPrimary"});
|
|
176
|
+
return hostPlatformColorFromRGBA(color.R, color.G, color.B, color.A);
|
|
177
|
+
} else {
|
|
178
|
+
auto color = ResolvePlatformColor({"TextFillColorSecondary"});
|
|
179
|
+
return hostPlatformColorFromRGBA(color.R, color.G, color.B, color.A);
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
if (isFocused) {
|
|
183
|
+
auto color = ResolvePlatformColor({"TextFillColorPrimary"});
|
|
184
|
+
return hostPlatformColorFromRGBA(color.R, color.G, color.B, color.A);
|
|
185
|
+
} else {
|
|
186
|
+
auto color = ResolvePlatformColor({"TextFillColorSecondary"});
|
|
187
|
+
return hostPlatformColorFromRGBA(color.R, color.G, color.B, color.A);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
SharedColor GetDefaultTextColor() {
|
|
193
|
+
// In high contrast mode, always use system WindowText for accessibility
|
|
194
|
+
auto accessibilitySettings{winrt::Windows::UI::ViewManagement::AccessibilitySettings()};
|
|
195
|
+
if (accessibilitySettings.HighContrast()) {
|
|
196
|
+
auto uiSettings{winrt::Windows::UI::ViewManagement::UISettings()};
|
|
197
|
+
auto windowText = uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::WindowText);
|
|
198
|
+
return hostPlatformColorFromRGBA(windowText.R, windowText.G, windowText.B, windowText.A);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Use Windows 11 design system semantic color TextFillColorPrimary
|
|
202
|
+
// This automatically adapts to light/dark mode themes:
|
|
203
|
+
// - Light mode: rgba(0, 0, 0, 0.894) - nearly black for good contrast
|
|
204
|
+
// - Dark mode: rgba(255, 255, 255, 1.0) - white for readability
|
|
205
|
+
auto color = ResolvePlatformColor({"TextFillColorPrimary"});
|
|
206
|
+
return hostPlatformColorFromRGBA(color.R, color.G, color.B, color.A);
|
|
207
|
+
}
|
|
208
|
+
|
|
145
209
|
} // namespace facebook::react
|
|
@@ -5,8 +5,19 @@
|
|
|
5
5
|
|
|
6
6
|
#include <winrt/Windows.UI.h>
|
|
7
7
|
|
|
8
|
+
// Forward declaration to avoid circular dependencies
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
class SharedColor;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
namespace facebook::react {
|
|
9
14
|
|
|
10
15
|
winrt::Windows::UI::Color ResolvePlatformColor(const std::vector<std::string> &semanticItems);
|
|
11
16
|
|
|
17
|
+
// Get appropriate placeholder text color for TextInput based on focus state and background
|
|
18
|
+
SharedColor GetTextInputPlaceholderColor(bool isFocused, const winrt::Windows::UI::Color &backgroundColor = {});
|
|
19
|
+
|
|
20
|
+
// Get default text foreground color for Text component (theme-aware)
|
|
21
|
+
SharedColor GetDefaultTextColor();
|
|
22
|
+
|
|
12
23
|
} // namespace facebook::react
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
#include "pch.h"
|
|
5
5
|
#include <Utils/ThemeUtils.h>
|
|
6
6
|
|
|
7
|
+
#ifdef USE_FABRIC
|
|
8
|
+
#include <react/renderer/graphics/Color.h>
|
|
9
|
+
#include <react/renderer/graphics/HostPlatformColor.h>
|
|
10
|
+
#endif
|
|
7
11
|
#include <winuser.h>
|
|
8
12
|
|
|
9
13
|
namespace Microsoft::ReactNative {
|
|
@@ -30,4 +34,49 @@ bool IsInHighContrastWin32() noexcept {
|
|
|
30
34
|
return false;
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
int CalculateColorBrightness(const winrt::Windows::UI::Color &color) noexcept {
|
|
38
|
+
return CalculateColorBrightness(color.R, color.G, color.B);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
int CalculateColorBrightness(int r, int g, int b) noexcept {
|
|
42
|
+
return (r * kColorBrightnessRedWeight + g * kColorBrightnessGreenWeight + b * kColorBrightnessBlueWeight) /
|
|
43
|
+
kColorBrightnessDivisor;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#ifdef USE_FABRIC
|
|
47
|
+
bool isColorMeaningful(const facebook::react::SharedColor &color) noexcept {
|
|
48
|
+
return facebook::react::isColorMeaningful(color);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
facebook::react::SharedColor GetCaretColor(
|
|
52
|
+
const facebook::react::SharedColor &cursorColor,
|
|
53
|
+
const facebook::react::SharedColor &foregroundColor,
|
|
54
|
+
const facebook::react::SharedColor &backgroundColor) noexcept {
|
|
55
|
+
const auto defaultCaretColor =
|
|
56
|
+
facebook::react::hostPlatformColorFromRGBA(0, 0, 0, 0xFF); // Default caret color is black
|
|
57
|
+
|
|
58
|
+
if (cursorColor) {
|
|
59
|
+
return cursorColor;
|
|
60
|
+
} else if (foregroundColor) {
|
|
61
|
+
// Extra Caution if Background color is present
|
|
62
|
+
auto fgWindows = (*foregroundColor).AsWindowsColor();
|
|
63
|
+
int fgBrightness = CalculateColorBrightness(fgWindows);
|
|
64
|
+
|
|
65
|
+
// If foreground is very light and background is also very light, force black caret.
|
|
66
|
+
if (fgBrightness > kCaretLightForegroundThreshold && Microsoft::ReactNative::isColorMeaningful(backgroundColor)) {
|
|
67
|
+
auto bgWindows = (*backgroundColor).AsWindowsColor();
|
|
68
|
+
int bgBrightness = CalculateColorBrightness(bgWindows);
|
|
69
|
+
if (bgBrightness > kCaretLightBackgroundThreshold) {
|
|
70
|
+
// Use opaque black caret
|
|
71
|
+
return defaultCaretColor;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return foregroundColor;
|
|
76
|
+
} else {
|
|
77
|
+
return defaultCaretColor;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
#endif
|
|
81
|
+
|
|
33
82
|
} // namespace Microsoft::ReactNative
|
|
@@ -4,10 +4,41 @@
|
|
|
4
4
|
#pragma once
|
|
5
5
|
|
|
6
6
|
#include <winrt/Windows.UI.h>
|
|
7
|
+
#ifdef USE_FABRIC
|
|
8
|
+
#include <react/renderer/graphics/Color.h>
|
|
9
|
+
#endif
|
|
7
10
|
|
|
8
11
|
namespace Microsoft::ReactNative {
|
|
9
12
|
|
|
13
|
+
// RGB luminance weights per ITU-R BT.601 standard (legacy NTSC weights)
|
|
14
|
+
constexpr int kColorBrightnessRedWeight = 299; // Red coefficient: 299/1000 = 0.299
|
|
15
|
+
constexpr int kColorBrightnessGreenWeight = 587; // Green coefficient: 587/1000 = 0.587
|
|
16
|
+
constexpr int kColorBrightnessBlueWeight = 114; // Blue coefficient: 114/1000 = 0.114
|
|
17
|
+
constexpr int kColorBrightnessDivisor = 1000;
|
|
18
|
+
|
|
19
|
+
// Caret color threshold constants
|
|
20
|
+
constexpr int kCaretLightForegroundThreshold = 240;
|
|
21
|
+
constexpr int kCaretLightBackgroundThreshold = 186;
|
|
22
|
+
constexpr int kCaretSelectionBrightnessThreshold = 125;
|
|
23
|
+
|
|
10
24
|
bool IsColorLight(const winrt::Windows::UI::Color &clr) noexcept;
|
|
11
25
|
bool IsInHighContrastWin32() noexcept;
|
|
12
26
|
|
|
27
|
+
// Calculate brightness using ITU-R BT.601 luminance formula
|
|
28
|
+
int CalculateColorBrightness(const winrt::Windows::UI::Color &color) noexcept;
|
|
29
|
+
|
|
30
|
+
// Calculate brightness from RGB values
|
|
31
|
+
int CalculateColorBrightness(int r, int g, int b) noexcept;
|
|
32
|
+
|
|
33
|
+
#ifdef USE_FABRIC
|
|
34
|
+
// Check if a color is meaningful (handles both PAPER and Fabric architectures)
|
|
35
|
+
bool isColorMeaningful(const facebook::react::SharedColor &color) noexcept;
|
|
36
|
+
|
|
37
|
+
// Determine appropriate caret color based on foreground and background colors
|
|
38
|
+
facebook::react::SharedColor GetCaretColor(
|
|
39
|
+
const facebook::react::SharedColor &cursorColor,
|
|
40
|
+
const facebook::react::SharedColor &foregroundColor,
|
|
41
|
+
const facebook::react::SharedColor &backgroundColor) noexcept;
|
|
42
|
+
#endif
|
|
43
|
+
|
|
13
44
|
} // namespace Microsoft::ReactNative
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
-->
|
|
11
11
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
12
12
|
<PropertyGroup>
|
|
13
|
-
<ReactNativeWindowsVersion>0.80.0
|
|
13
|
+
<ReactNativeWindowsVersion>0.80.0</ReactNativeWindowsVersion>
|
|
14
14
|
<ReactNativeWindowsMajor>0</ReactNativeWindowsMajor>
|
|
15
15
|
<ReactNativeWindowsMinor>80</ReactNativeWindowsMinor>
|
|
16
16
|
<ReactNativeWindowsPatch>0</ReactNativeWindowsPatch>
|
|
17
17
|
<ReactNativeWindowsCanary>false</ReactNativeWindowsCanary>
|
|
18
|
-
<ReactNativeWindowsCommitId>
|
|
18
|
+
<ReactNativeWindowsCommitId>45c78c8f8ca7dcf3d894156d53f437b3e496d7cc</ReactNativeWindowsCommitId>
|
|
19
19
|
</PropertyGroup>
|
|
20
20
|
</Project>
|
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
3
3
|
|
|
4
4
|
<PropertyGroup>
|
|
5
|
+
<!-- SDL MANDATORY WARNINGS (Microsoft Security Development Lifecycle) -->
|
|
6
|
+
<!-- These warnings MUST be enabled and fixed per SDL requirements -->
|
|
7
|
+
<!-- Work Item: #58386089 - Fix warnings identified by native code compiler -->
|
|
8
|
+
<SDLMandatoryWarnings>
|
|
9
|
+
4018; <!-- 'expression' : signed/unsigned mismatch -->
|
|
10
|
+
4055; <!-- 'conversion' : from data pointer to function pointer -->
|
|
11
|
+
4146; <!-- unary minus operator applied to unsigned type -->
|
|
12
|
+
4242; <!-- 'identifier' : conversion with possible loss of data -->
|
|
13
|
+
4244; <!-- 'conversion' conversion with possible loss of data -->
|
|
14
|
+
4267; <!-- 'var' : conversion from size_t with possible loss of data -->
|
|
15
|
+
4302; <!-- 'conversion' : truncation from type1 to type2 -->
|
|
16
|
+
4308; <!-- negative integral constant converted to unsigned type -->
|
|
17
|
+
4509; <!-- nonstandard extension: SEH with destructor -->
|
|
18
|
+
4510; <!-- 'class' : default constructor could not be generated -->
|
|
19
|
+
4532; <!-- jump out of __finally/finally block undefined behavior -->
|
|
20
|
+
4533; <!-- initialization skipped by instruction -->
|
|
21
|
+
4610; <!-- object can never be instantiated -->
|
|
22
|
+
4611; <!-- interaction between function and C++ destruction non-portable -->
|
|
23
|
+
4700; <!-- uninitialized local variable used -->
|
|
24
|
+
4701; <!-- potentially uninitialized local variable used -->
|
|
25
|
+
4703; <!-- potentially uninitialized local pointer variable used -->
|
|
26
|
+
4789; <!-- destination of memory copy too small -->
|
|
27
|
+
4995; <!-- function marked as pragma deprecated -->
|
|
28
|
+
4996 <!-- deprecated function (including std::) -->
|
|
29
|
+
</SDLMandatoryWarnings>
|
|
30
|
+
|
|
31
|
+
<!-- SDL RECOMMENDED WARNINGS (Strongly recommended to fix) -->
|
|
32
|
+
<SDLRecommendedWarnings>
|
|
33
|
+
4287; <!-- unsigned/negative constant mismatch -->
|
|
34
|
+
4365; <!-- signed/unsigned mismatch -->
|
|
35
|
+
4388; <!-- signed/unsigned mismatch in comparison -->
|
|
36
|
+
4545; <!-- expression before comma evaluates to function missing argument list -->
|
|
37
|
+
4546; <!-- function call before comma missing argument list -->
|
|
38
|
+
4547; <!-- operator before comma has no effect -->
|
|
39
|
+
4549 <!-- operator before comma has no effect -->
|
|
40
|
+
</SDLRecommendedWarnings>
|
|
41
|
+
|
|
5
42
|
<!-- Office pre-disabled warnings -->
|
|
6
43
|
<!--
|
|
7
44
|
C4201 - nonstandard extension used : nameless struct/union
|
|
@@ -31,8 +68,16 @@
|
|
|
31
68
|
<!-- /permissive- by default to enforce standards conformance, unless ENABLEPermissive has been set -->
|
|
32
69
|
<AdditionalOptions Condition="'$(ENABLEPermissive)' == ''">/permissive- %(AdditionalOptions)</AdditionalOptions>
|
|
33
70
|
<DisableSpecificWarnings>$(OfficePreDisabledWarnings);$(ExtraWarningsToDisable);$(DisableSpecificWarnings)</DisableSpecificWarnings>
|
|
71
|
+
|
|
72
|
+
<!-- SDL REQUIREMENT: Treat warnings as errors -->
|
|
34
73
|
<TreatWarningAsError>true</TreatWarningAsError>
|
|
74
|
+
|
|
75
|
+
<!-- SDL REQUIREMENT: Use /W4 warning level -->
|
|
35
76
|
<WarningLevel>Level4</WarningLevel>
|
|
77
|
+
|
|
78
|
+
<!-- SDL REQUIREMENT: Explicitly enable mandatory warnings as errors -->
|
|
79
|
+
<!-- This ensures SDL mandatory warnings are NEVER disabled -->
|
|
80
|
+
<WarningAsError>$(SDLMandatoryWarnings);%(WarningAsError)</WarningAsError>
|
|
36
81
|
</ClCompile>
|
|
37
82
|
<Link>
|
|
38
83
|
<!--
|
package/README.md
CHANGED
|
@@ -58,6 +58,10 @@ Search the [existing issues](https://github.com/microsoft/react-native-windows/i
|
|
|
58
58
|
## Documentation
|
|
59
59
|
React Native has [great documentation](https://reactnative.dev/docs/getting-started). React Native for Windows adds its own separate [Windows and macOS documentation](https://microsoft.github.io/react-native-windows/) for desktop platform information like API docs and blog updates.
|
|
60
60
|
|
|
61
|
+
### Security Documentation
|
|
62
|
+
- **[Security Configuration Guide](https://github.com/microsoft/react-native-windows/blob/main/docs/security-configuration.md)** - Comprehensive guide for SDL-compliant security configurations
|
|
63
|
+
- **[Security Best Practices](https://github.com/microsoft/react-native-windows/blob/main/docs/security-best-practices.md)** - Secure coding patterns and security API usage
|
|
64
|
+
|
|
61
65
|
### Examples
|
|
62
66
|
- Using the CLI in the [Getting Started](https://microsoft.github.io/react-native-windows/docs/getting-started) guide will set you up with a sample React Native for Windows app that you can begin editing right away.
|
|
63
67
|
- Check the [samples repo](https://github.com/microsoft/react-native-windows-samples) for more standalone samples.
|
|
@@ -459,9 +459,23 @@ $requirements = @(
|
|
|
459
459
|
Install = {
|
|
460
460
|
$ProgressPreference = 'Ignore';
|
|
461
461
|
$url = "https://github.com/microsoft/WinAppDriver/releases/download/v1.2.1/WindowsApplicationDriver_1.2.1.msi";
|
|
462
|
+
$downloadPath = "$env:TEMP\WindowsApplicationDriver.msi"
|
|
462
463
|
Write-Verbose "Downloading WinAppDriver from $url";
|
|
463
|
-
Invoke-WebRequest -UseBasicParsing $url -OutFile $
|
|
464
|
-
|
|
464
|
+
Invoke-WebRequest -UseBasicParsing $url -OutFile $downloadPath
|
|
465
|
+
|
|
466
|
+
# SDL Compliance: Verify signature (Work Item 58386093)
|
|
467
|
+
$signature = Get-AuthenticodeSignature $downloadPath
|
|
468
|
+
if ($signature.Status -ne "Valid") {
|
|
469
|
+
Remove-Item $downloadPath -ErrorAction SilentlyContinue
|
|
470
|
+
throw "WinAppDriver signature verification failed"
|
|
471
|
+
}
|
|
472
|
+
if ($signature.SignerCertificate.Subject -notlike "*Microsoft*") {
|
|
473
|
+
Remove-Item $downloadPath -ErrorAction SilentlyContinue
|
|
474
|
+
throw "WinAppDriver not signed by Microsoft"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
& $downloadPath /q
|
|
478
|
+
Remove-Item $downloadPath -ErrorAction SilentlyContinue
|
|
465
479
|
};
|
|
466
480
|
HasVerboseOutput = $true;
|
|
467
481
|
Optional = $true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-windows",
|
|
3
|
-
"version": "0.80.0
|
|
3
|
+
"version": "0.80.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@react-native-community/cli": "17.0.0",
|
|
27
27
|
"@react-native-community/cli-platform-android": "17.0.0",
|
|
28
28
|
"@react-native-community/cli-platform-ios": "17.0.0",
|
|
29
|
-
"@react-native-windows/cli": "0.80.0
|
|
29
|
+
"@react-native-windows/cli": "0.80.0",
|
|
30
30
|
"@react-native/assets": "1.0.0",
|
|
31
31
|
"@react-native/assets-registry": "0.80.0",
|
|
32
32
|
"@react-native/codegen": "0.80.0",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"yargs": "^17.6.2"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@react-native-windows/codegen": "0.80.0
|
|
71
|
+
"@react-native-windows/codegen": "0.80.0",
|
|
72
72
|
"@react-native/metro-config": "0.80.0",
|
|
73
73
|
"@rnw-scripts/babel-react-native-config": "0.0.0",
|
|
74
74
|
"@rnw-scripts/eslint-config": "1.2.36",
|
|
@@ -95,11 +95,11 @@
|
|
|
95
95
|
"react-native": "^0.80.0"
|
|
96
96
|
},
|
|
97
97
|
"beachball": {
|
|
98
|
-
"defaultNpmTag": "
|
|
98
|
+
"defaultNpmTag": "latest",
|
|
99
99
|
"disallowedChangeTypes": [
|
|
100
100
|
"major",
|
|
101
101
|
"minor",
|
|
102
|
-
"
|
|
102
|
+
"prerelease",
|
|
103
103
|
"premajor",
|
|
104
104
|
"preminor",
|
|
105
105
|
"prepatch"
|
|
@@ -33,7 +33,7 @@ const config = {
|
|
|
33
33
|
// We need to make sure that only one version is loaded for peerDependencies
|
|
34
34
|
// So we block them at the root, and alias them to the versions in example's node_modules
|
|
35
35
|
resolver: {
|
|
36
|
-
|
|
36
|
+
blocklistRE: exclusionList(
|
|
37
37
|
modules.map(
|
|
38
38
|
(m) =>
|
|
39
39
|
new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)
|