@lodev09/react-native-true-sheet 3.11.0-beta.12 → 3.11.0-beta.14
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.
|
@@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
31
31
|
@optional
|
|
32
32
|
|
|
33
33
|
- (void)containerViewHeaderDidChangeSize:(CGSize)newSize;
|
|
34
|
+
- (void)containerViewFooterDidChangeSize:(CGSize)newSize;
|
|
34
35
|
|
|
35
36
|
@end
|
|
36
37
|
|
|
@@ -71,6 +72,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
71
72
|
*/
|
|
72
73
|
- (void)layoutFooter;
|
|
73
74
|
|
|
75
|
+
- (BOOL)hasAccessibilityFooterElements;
|
|
76
|
+
|
|
74
77
|
/**
|
|
75
78
|
* Setup scrollable content
|
|
76
79
|
*/
|
|
@@ -72,10 +72,38 @@ using namespace facebook::react;
|
|
|
72
72
|
_headerView = nil;
|
|
73
73
|
_footerView = nil;
|
|
74
74
|
_scrollableSet = NO;
|
|
75
|
+
self.isAccessibilityElement = NO;
|
|
75
76
|
}
|
|
76
77
|
return self;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
#pragma mark - Accessibility
|
|
81
|
+
|
|
82
|
+
- (NSArray *)accessibilityElements {
|
|
83
|
+
NSMutableArray *elements = [NSMutableArray array];
|
|
84
|
+
if (_headerView) {
|
|
85
|
+
[elements addObject:_headerView];
|
|
86
|
+
}
|
|
87
|
+
if (_contentView) {
|
|
88
|
+
[elements addObject:_contentView];
|
|
89
|
+
}
|
|
90
|
+
if (_footerView) {
|
|
91
|
+
// Footer hosts are layout containers; expose their children when present so
|
|
92
|
+
// VoiceOver and XCTest can target the actual footer controls.
|
|
93
|
+
NSArray *footerElements = _footerView.accessibilityElements;
|
|
94
|
+
if (footerElements.count > 0) {
|
|
95
|
+
[elements addObjectsFromArray:footerElements];
|
|
96
|
+
} else {
|
|
97
|
+
[elements addObject:_footerView];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return elements;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
- (BOOL)hasAccessibilityFooterElements {
|
|
104
|
+
return _footerView && _footerView.accessibilityElements.count > 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
#pragma mark - Layout
|
|
80
108
|
|
|
81
109
|
- (void)layoutSubviews {
|
|
@@ -234,6 +262,7 @@ using namespace facebook::react;
|
|
|
234
262
|
#pragma mark - TrueSheetFooterViewDelegate
|
|
235
263
|
|
|
236
264
|
- (void)footerViewDidChangeSize:(CGSize)newSize {
|
|
265
|
+
[self.delegate containerViewFooterDidChangeSize:newSize];
|
|
237
266
|
if (@available(iOS 26.0, *)) {
|
|
238
267
|
[self setupEdgeInteractions];
|
|
239
268
|
}
|
|
@@ -37,6 +37,7 @@ using namespace facebook::react;
|
|
|
37
37
|
_props = defaultProps;
|
|
38
38
|
|
|
39
39
|
self.backgroundColor = [UIColor clearColor];
|
|
40
|
+
self.isAccessibilityElement = NO;
|
|
40
41
|
|
|
41
42
|
_lastHeight = 0;
|
|
42
43
|
_pendingHeight = 0;
|
|
@@ -47,6 +48,30 @@ using namespace facebook::react;
|
|
|
47
48
|
return self;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
#pragma mark - Accessibility
|
|
52
|
+
|
|
53
|
+
- (NSArray *)accessibilityElements {
|
|
54
|
+
NSMutableArray *elements = [NSMutableArray array];
|
|
55
|
+
[self collectAccessibilityElementsFromView:self into:elements];
|
|
56
|
+
if (elements.count > 0) {
|
|
57
|
+
return elements;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [super accessibilityElements];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
- (void)collectAccessibilityElementsFromView:(UIView *)view into:(NSMutableArray *)elements {
|
|
64
|
+
for (UIView *subview in view.subviews) {
|
|
65
|
+
if (subview.isAccessibilityElement || subview.accessibilityLabel || subview.accessibilityIdentifier) {
|
|
66
|
+
[elements addObject:subview];
|
|
67
|
+
} else if (subview.accessibilityElements.count > 0) {
|
|
68
|
+
[elements addObjectsFromArray:subview.accessibilityElements];
|
|
69
|
+
} else {
|
|
70
|
+
[self collectAccessibilityElementsFromView:subview into:elements];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
50
75
|
#pragma mark - Layout
|
|
51
76
|
|
|
52
77
|
- (void)setupConstraintsWithHeight:(CGFloat)height {
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -373,6 +373,9 @@ using namespace facebook::react;
|
|
|
373
373
|
[_controller.view addSubview:_containerView];
|
|
374
374
|
[LayoutUtil pinView:_containerView toParentView:_controller.view edges:UIRectEdgeAll];
|
|
375
375
|
[_controller.view bringSubviewToFront:_containerView];
|
|
376
|
+
_containerView.accessibilityViewIsModal = YES;
|
|
377
|
+
_controller.accessibilityContentView = _containerView;
|
|
378
|
+
[_controller setupAccessibilityContainer];
|
|
376
379
|
|
|
377
380
|
CGFloat contentHeight = [_containerView contentHeight];
|
|
378
381
|
if (contentHeight > 0) {
|
|
@@ -570,6 +573,10 @@ using namespace facebook::react;
|
|
|
570
573
|
[self setupSheetDetentsForSizeChange];
|
|
571
574
|
}
|
|
572
575
|
|
|
576
|
+
- (void)containerViewFooterDidChangeSize:(CGSize)newSize {
|
|
577
|
+
[_controller setupAccessibilityContainer];
|
|
578
|
+
}
|
|
579
|
+
|
|
573
580
|
// When the ScrollView changes (e.g. conditional remount), re-pin the new ScrollView.
|
|
574
581
|
- (void)containerViewScrollViewDidChange {
|
|
575
582
|
[self setupScrollable];
|
|
@@ -75,9 +75,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
75
75
|
@property (nonatomic, assign) BOOL isPresented;
|
|
76
76
|
@property (nonatomic, assign) NSInteger activeDetentIndex;
|
|
77
77
|
@property (nonatomic, readonly) BOOL isTopmostPresentedController;
|
|
78
|
+
@property (nonatomic, weak, nullable) UIView *accessibilityContentView;
|
|
78
79
|
|
|
79
80
|
@property (nonatomic, readonly) CGFloat screenHeight;
|
|
80
81
|
|
|
82
|
+
- (void)setupAccessibilityContainer;
|
|
81
83
|
- (void)applyActiveDetent;
|
|
82
84
|
- (void)setupActiveDetentWithIndex:(NSInteger)index;
|
|
83
85
|
- (void)resizeToDetentIndex:(NSInteger)index;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
#import "TrueSheetViewController.h"
|
|
10
|
+
#import "TrueSheetContainerView.h"
|
|
10
11
|
#import "TrueSheetContentView.h"
|
|
11
12
|
#import "core/TrueSheetBlurView.h"
|
|
12
13
|
#import "core/TrueSheetDetentCalculator.h"
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
|
|
19
20
|
#import <React/RCTLog.h>
|
|
20
21
|
#import <React/RCTScrollViewComponentView.h>
|
|
22
|
+
#import <objc/runtime.h>
|
|
21
23
|
#import <react/renderer/components/TrueSheetSpec/Props.h>
|
|
22
24
|
|
|
23
25
|
using namespace facebook::react;
|
|
@@ -32,8 +34,16 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
32
34
|
return fabs(a.position - b.position) <= 0.01 && fabs(a.detent - b.detent) <= 0.01 && fabs(a.index - b.index) <= 0.01;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
static char TrueSheetAccessibilityWindowOwnerKey;
|
|
38
|
+
static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
39
|
+
|
|
35
40
|
@interface TrueSheetViewController ()
|
|
36
41
|
|
|
42
|
+
- (UIViewController *)accessibilityPresentingViewController;
|
|
43
|
+
- (void)restoreWindowAccessibilityElements;
|
|
44
|
+
- (void)setSheetAccessibilityElementsHidden:(BOOL)hidden;
|
|
45
|
+
- (void)setAccessibilityContentElement:(UIView *)contentView;
|
|
46
|
+
|
|
37
47
|
@end
|
|
38
48
|
|
|
39
49
|
@implementation TrueSheetViewController {
|
|
@@ -55,6 +65,8 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
55
65
|
|
|
56
66
|
UIView *_anchorView;
|
|
57
67
|
|
|
68
|
+
__weak UIWindow *_accessibilityWindow;
|
|
69
|
+
|
|
58
70
|
TrueSheetBlurView *_blurView;
|
|
59
71
|
TrueSheetGrabberView *_grabberView;
|
|
60
72
|
TrueSheetDetentCalculator *_detentCalculator;
|
|
@@ -95,6 +107,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
- (void)dealloc {
|
|
110
|
+
[self restoreWindowAccessibilityElements];
|
|
98
111
|
[_transitioningTimer invalidate];
|
|
99
112
|
_transitioningTimer = nil;
|
|
100
113
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
@@ -207,6 +220,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
207
220
|
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
208
221
|
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
209
222
|
[_parentSheetController.delegate viewControllerWillBlur];
|
|
223
|
+
[_parentSheetController setAccessibilityContentElement:self.accessibilityContentView ?: self.view];
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -224,6 +238,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
224
238
|
|
|
225
239
|
- (void)viewDidAppear:(BOOL)animated {
|
|
226
240
|
[super viewDidAppear:animated];
|
|
241
|
+
[self setSheetAccessibilityElementsHidden:NO];
|
|
227
242
|
|
|
228
243
|
if (!_isPresented) {
|
|
229
244
|
[_parentSheetController.delegate viewControllerDidBlur];
|
|
@@ -243,6 +258,95 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
243
258
|
[self setupGestureRecognizer];
|
|
244
259
|
_isPresented = YES;
|
|
245
260
|
}
|
|
261
|
+
|
|
262
|
+
[self setupAccessibilityContainer];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
- (void)setupAccessibilityContainer {
|
|
266
|
+
UIView *contentView = self.accessibilityContentView;
|
|
267
|
+
if (!contentView) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
[self setAccessibilityContentElement:contentView];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
- (UIViewController *)accessibilityPresentingViewController {
|
|
275
|
+
UIViewController *presentingViewController = self.presentingViewController;
|
|
276
|
+
while ([presentingViewController isKindOfClass:[TrueSheetViewController class]] &&
|
|
277
|
+
presentingViewController.presentingViewController) {
|
|
278
|
+
presentingViewController = presentingViewController.presentingViewController;
|
|
279
|
+
}
|
|
280
|
+
return presentingViewController;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
- (void)restoreWindowAccessibilityElements {
|
|
284
|
+
UIWindow *window = _accessibilityWindow;
|
|
285
|
+
if (window) {
|
|
286
|
+
// The active sheet may temporarily own window accessibility traversal. Only
|
|
287
|
+
// restore the previous value when this controller still owns that override.
|
|
288
|
+
NSValue *ownerValue = objc_getAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey);
|
|
289
|
+
if (ownerValue && ownerValue.nonretainedObjectValue == self) {
|
|
290
|
+
id previousElements = objc_getAssociatedObject(window, &TrueSheetAccessibilityWindowPreviousElementsKey);
|
|
291
|
+
window.accessibilityElements = previousElements == [NSNull null] ? nil : previousElements;
|
|
292
|
+
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
293
|
+
objc_setAssociatedObject(
|
|
294
|
+
window, &TrueSheetAccessibilityWindowPreviousElementsKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_accessibilityWindow = nil;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
- (void)setSheetAccessibilityElementsHidden:(BOOL)hidden {
|
|
302
|
+
self.view.accessibilityElementsHidden = hidden;
|
|
303
|
+
|
|
304
|
+
UIView *presentedView = self.presentationController.presentedView;
|
|
305
|
+
if (presentedView) {
|
|
306
|
+
presentedView.accessibilityElementsHidden = hidden;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
- (void)setAccessibilityContentElement:(UIView *)contentView {
|
|
311
|
+
NSArray *contentElements = contentView.accessibilityElements;
|
|
312
|
+
NSArray *accessibilityElements = contentElements.count > 0 ? contentElements : @[ contentView ];
|
|
313
|
+
BOOL hasAccessibilityFooterElements = [contentView isKindOfClass:[TrueSheetContainerView class]] &&
|
|
314
|
+
[(TrueSheetContainerView *)contentView hasAccessibilityFooterElements];
|
|
315
|
+
// Footer controls exposed as separate accessibility elements can be skipped
|
|
316
|
+
// by XCTest when the presented sheet is a hard modal accessibility boundary.
|
|
317
|
+
BOOL isAccessibilityModal = _dimmed && !hasAccessibilityFooterElements;
|
|
318
|
+
|
|
319
|
+
self.view.isAccessibilityElement = NO;
|
|
320
|
+
self.view.accessibilityViewIsModal = YES;
|
|
321
|
+
self.view.accessibilityElements = accessibilityElements;
|
|
322
|
+
|
|
323
|
+
UIView *presentedView = self.presentationController.presentedView;
|
|
324
|
+
if (presentedView) {
|
|
325
|
+
presentedView.isAccessibilityElement = NO;
|
|
326
|
+
presentedView.accessibilityViewIsModal = isAccessibilityModal;
|
|
327
|
+
presentedView.accessibilityElements = accessibilityElements;
|
|
328
|
+
|
|
329
|
+
UIWindow *window = self.view.window;
|
|
330
|
+
UIViewController *presentingViewController = [self accessibilityPresentingViewController];
|
|
331
|
+
if (window && presentingViewController.view) {
|
|
332
|
+
if (!_accessibilityWindow) {
|
|
333
|
+
_accessibilityWindow = window;
|
|
334
|
+
id previousElements = objc_getAssociatedObject(window, &TrueSheetAccessibilityWindowPreviousElementsKey);
|
|
335
|
+
if (!previousElements) {
|
|
336
|
+
previousElements = window.accessibilityElements ?: [NSNull null];
|
|
337
|
+
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowPreviousElementsKey, previousElements,
|
|
338
|
+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey, [NSValue valueWithNonretainedObject:self],
|
|
342
|
+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
343
|
+
window.accessibilityElements =
|
|
344
|
+
isAccessibilityModal ? @[ presentedView ] : @[ presentingViewController.view, presentedView ];
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
[self restoreWindowAccessibilityElements];
|
|
246
350
|
}
|
|
247
351
|
|
|
248
352
|
- (void)emitWillDismissEvents {
|
|
@@ -257,6 +361,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
257
361
|
|
|
258
362
|
- (void)emitDidDismissEvents {
|
|
259
363
|
if (self.isBeingDismissed) {
|
|
364
|
+
[self restoreWindowAccessibilityElements];
|
|
260
365
|
_isPresented = NO;
|
|
261
366
|
_isWillDismissEmitted = NO;
|
|
262
367
|
|
|
@@ -264,6 +369,8 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
264
369
|
_anchorView = nil;
|
|
265
370
|
|
|
266
371
|
[_parentSheetController.delegate viewControllerDidFocus];
|
|
372
|
+
[_parentSheetController setSheetAccessibilityElementsHidden:NO];
|
|
373
|
+
[_parentSheetController setupAccessibilityContainer];
|
|
267
374
|
_parentSheetController = nil;
|
|
268
375
|
|
|
269
376
|
[self.delegate viewControllerDidBlur];
|
|
@@ -273,6 +380,8 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
|
|
|
273
380
|
|
|
274
381
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
275
382
|
[super viewWillDisappear:animated];
|
|
383
|
+
[self restoreWindowAccessibilityElements];
|
|
384
|
+
[self setSheetAccessibilityElementsHidden:YES];
|
|
276
385
|
|
|
277
386
|
// Dispatch to allow pan gesture to set _isDragging before checking
|
|
278
387
|
// handleTransitionTracker will emit when sheet is transitioning to dismiss
|
package/package.json
CHANGED