@lodev09/react-native-true-sheet 3.11.0-beta.13 → 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
  */
@@ -88,11 +88,22 @@ using namespace facebook::react;
88
88
  [elements addObject:_contentView];
89
89
  }
90
90
  if (_footerView) {
91
- [elements addObject:_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
+ }
92
99
  }
93
100
  return elements;
94
101
  }
95
102
 
103
+ - (BOOL)hasAccessibilityFooterElements {
104
+ return _footerView && _footerView.accessibilityElements.count > 0;
105
+ }
106
+
96
107
  #pragma mark - Layout
97
108
 
98
109
  - (void)layoutSubviews {
@@ -251,6 +262,7 @@ using namespace facebook::react;
251
262
  #pragma mark - TrueSheetFooterViewDelegate
252
263
 
253
264
  - (void)footerViewDidChangeSize:(CGSize)newSize {
265
+ [self.delegate containerViewFooterDidChangeSize:newSize];
254
266
  if (@available(iOS 26.0, *)) {
255
267
  [self setupEdgeInteractions];
256
268
  }
@@ -53,13 +53,19 @@ using namespace facebook::react;
53
53
  - (NSArray *)accessibilityElements {
54
54
  NSMutableArray *elements = [NSMutableArray array];
55
55
  [self collectAccessibilityElementsFromView:self into:elements];
56
- return elements;
56
+ if (elements.count > 0) {
57
+ return elements;
58
+ }
59
+
60
+ return [super accessibilityElements];
57
61
  }
58
62
 
59
63
  - (void)collectAccessibilityElementsFromView:(UIView *)view into:(NSMutableArray *)elements {
60
64
  for (UIView *subview in view.subviews) {
61
65
  if (subview.isAccessibilityElement || subview.accessibilityLabel || subview.accessibilityIdentifier) {
62
66
  [elements addObject:subview];
67
+ } else if (subview.accessibilityElements.count > 0) {
68
+ [elements addObjectsFromArray:subview.accessibilityElements];
63
69
  } else {
64
70
  [self collectAccessibilityElementsFromView:subview into:elements];
65
71
  }
@@ -573,6 +573,10 @@ using namespace facebook::react;
573
573
  [self setupSheetDetentsForSizeChange];
574
574
  }
575
575
 
576
+ - (void)containerViewFooterDidChangeSize:(CGSize)newSize {
577
+ [_controller setupAccessibilityContainer];
578
+ }
579
+
576
580
  // When the ScrollView changes (e.g. conditional remount), re-pin the new ScrollView.
577
581
  - (void)containerViewScrollViewDidChange {
578
582
  [self setupScrollable];
@@ -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,14 @@ 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;
37
45
  - (void)setAccessibilityContentElement:(UIView *)contentView;
38
46
 
39
47
  @end
@@ -57,6 +65,8 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
57
65
 
58
66
  UIView *_anchorView;
59
67
 
68
+ __weak UIWindow *_accessibilityWindow;
69
+
60
70
  TrueSheetBlurView *_blurView;
61
71
  TrueSheetGrabberView *_grabberView;
62
72
  TrueSheetDetentCalculator *_detentCalculator;
@@ -97,6 +107,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
97
107
  }
98
108
 
99
109
  - (void)dealloc {
110
+ [self restoreWindowAccessibilityElements];
100
111
  [_transitioningTimer invalidate];
101
112
  _transitioningTimer = nil;
102
113
  [[NSNotificationCenter defaultCenter] removeObserver:self];
@@ -227,6 +238,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
227
238
 
228
239
  - (void)viewDidAppear:(BOOL)animated {
229
240
  [super viewDidAppear:animated];
241
+ [self setSheetAccessibilityElementsHidden:NO];
230
242
 
231
243
  if (!_isPresented) {
232
244
  [_parentSheetController.delegate viewControllerDidBlur];
@@ -244,9 +256,10 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
244
256
  });
245
257
 
246
258
  [self setupGestureRecognizer];
247
- [self setupAccessibilityContainer];
248
259
  _isPresented = YES;
249
260
  }
261
+
262
+ [self setupAccessibilityContainer];
250
263
  }
251
264
 
252
265
  - (void)setupAccessibilityContainer {
@@ -258,17 +271,82 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
258
271
  [self setAccessibilityContentElement:contentView];
259
272
  }
260
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
+
261
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
+
262
319
  self.view.isAccessibilityElement = NO;
263
320
  self.view.accessibilityViewIsModal = YES;
264
- self.view.accessibilityElements = @[ contentView ];
321
+ self.view.accessibilityElements = accessibilityElements;
265
322
 
266
323
  UIView *presentedView = self.presentationController.presentedView;
267
324
  if (presentedView) {
268
325
  presentedView.isAccessibilityElement = NO;
269
- presentedView.accessibilityViewIsModal = YES;
270
- presentedView.accessibilityElements = @[ contentView ];
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
+ }
271
347
  }
348
+
349
+ [self restoreWindowAccessibilityElements];
272
350
  }
273
351
 
274
352
  - (void)emitWillDismissEvents {
@@ -283,6 +361,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
283
361
 
284
362
  - (void)emitDidDismissEvents {
285
363
  if (self.isBeingDismissed) {
364
+ [self restoreWindowAccessibilityElements];
286
365
  _isPresented = NO;
287
366
  _isWillDismissEmitted = NO;
288
367
 
@@ -290,6 +369,7 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
290
369
  _anchorView = nil;
291
370
 
292
371
  [_parentSheetController.delegate viewControllerDidFocus];
372
+ [_parentSheetController setSheetAccessibilityElementsHidden:NO];
293
373
  [_parentSheetController setupAccessibilityContainer];
294
374
  _parentSheetController = nil;
295
375
 
@@ -300,6 +380,8 @@ static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPosi
300
380
 
301
381
  - (void)viewWillDisappear:(BOOL)animated {
302
382
  [super viewWillDisappear:animated];
383
+ [self restoreWindowAccessibilityElements];
384
+ [self setSheetAccessibilityElementsHidden:YES];
303
385
 
304
386
  // Dispatch to allow pan gesture to set _isDragging before checking
305
387
  // handleTransitionTracker will emit when sheet is transitioning to dismiss
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodev09/react-native-true-sheet",
3
- "version": "3.11.0-beta.13",
3
+ "version": "3.11.0-beta.14",
4
4
  "description": "The true native bottom sheet experience for your React Native Apps.",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",