@legendapp/spark 0.0.1-next.3 → 0.0.1-next.4

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.
Files changed (83) hide show
  1. package/camera.ts +13 -0
  2. package/package.json +21 -11
  3. package/patches/camera/react-native-nitro-image.patch +320 -0
  4. package/patches/camera/react-native-nitro-modules.patch +50 -0
  5. package/patches/camera/react-native-vision-camera.patch +2662 -0
  6. package/patches/camera/upstream.json +22 -0
  7. package/patches/dependencies.json +8 -0
  8. package/patches/react-native-enriched-markdown@1.0.2.patch +8684 -0
  9. package/postinstall.cjs +1 -0
  10. package/ui-swipe.ts +1 -0
  11. package/vendor/node_modules/@legendapp/spark-ai/package.json +3 -3
  12. package/vendor/node_modules/@legendapp/spark-audio/package.json +2 -2
  13. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +3 -3
  14. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +62 -11
  15. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +2 -1
  16. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +3 -2
  17. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +6 -3
  18. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +41 -26
  19. package/vendor/node_modules/@legendapp/spark-cli/dist/dependency-pins.js +132 -0
  20. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +1 -1
  21. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +1 -1
  22. package/vendor/node_modules/@legendapp/spark-cli/dist/errors.js +100 -0
  23. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
  24. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-native.cjs +5 -1
  25. package/vendor/node_modules/@legendapp/spark-cli/dist/helpers.js +1 -1
  26. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +43 -7
  27. package/vendor/node_modules/@legendapp/spark-cli/dist/json.js +16 -0
  28. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +3 -1
  29. package/vendor/node_modules/@legendapp/spark-cli/dist/process.js +7 -0
  30. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +27 -22
  31. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +2 -1
  32. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +19 -19
  33. package/vendor/node_modules/@legendapp/spark-cli/dist/universal.cjs +2 -1
  34. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +1 -8
  35. package/vendor/node_modules/@legendapp/spark-cli/package.json +3 -3
  36. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +2 -2
  37. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +2 -2
  38. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +2 -2
  39. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  40. package/vendor/node_modules/@legendapp/spark-codex/package.json +3 -3
  41. package/vendor/node_modules/@legendapp/spark-commands/package.json +5 -5
  42. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +2 -2
  43. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +1 -1
  44. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +6 -4
  45. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +2 -0
  46. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.cjs +1 -0
  47. package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.cjs +106 -0
  48. package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.d.cts +1 -0
  49. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +5 -4
  50. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  51. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  52. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +2 -2
  53. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +39 -0
  54. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +3 -3
  55. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +3 -1
  56. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +1 -1
  57. package/vendor/node_modules/@legendapp/spark-documents/package.json +7 -7
  58. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +2 -2
  59. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +2 -2
  60. package/vendor/node_modules/@legendapp/spark-file-system/package.json +2 -2
  61. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +2 -2
  62. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  63. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +2 -2
  64. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  65. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +16 -11
  66. package/vendor/node_modules/@legendapp/spark-processes/package.json +2 -2
  67. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  68. package/vendor/node_modules/@legendapp/spark-settings/package.json +3 -3
  69. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +4 -4
  70. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  71. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  72. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  73. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +263 -26
  74. package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.h +10 -0
  75. package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.mm +422 -0
  76. package/vendor/node_modules/@legendapp/spark-ui/package.json +3 -2
  77. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +6 -0
  78. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +20 -11
  79. package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/SwipeActionsNativeComponent.ts +11 -0
  80. package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/index.tsx +41 -0
  81. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  82. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  83. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-settings-ui",
3
- "version": "0.0.1-next.3",
3
+ "version": "0.0.1-next.4",
4
4
  "private": true,
5
5
  "license": "MIT",
6
6
  "main": "src/index.tsx",
@@ -8,9 +8,9 @@
8
8
  "src"
9
9
  ],
10
10
  "dependencies": {
11
- "@legendapp/spark-ui": "0.0.1-next.3",
12
- "@legendapp/spark-desktop-windows": "0.0.1-next.3",
13
- "@legendapp/spark-desktop-app": "0.0.1-next.3"
11
+ "@legendapp/spark-ui": "0.0.1-next.4",
12
+ "@legendapp/spark-desktop-windows": "0.0.1-next.4",
13
+ "@legendapp/spark-desktop-app": "0.0.1-next.4"
14
14
  },
15
15
  "peerDependencies": {
16
16
  "react": "19.1.4",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-sqlite",
3
- "version": "0.0.1-next.3",
3
+ "version": "0.0.1-next.4",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -9,8 +9,8 @@
9
9
  ],
10
10
  "dependencies": {
11
11
  "@op-engineering/op-sqlite": "18.2.1",
12
- "@legendapp/spark-desktop-app": "0.0.1-next.3",
13
- "@legendapp/spark-file-system": "0.0.1-next.3"
12
+ "@legendapp/spark-desktop-app": "0.0.1-next.4",
13
+ "@legendapp/spark-file-system": "0.0.1-next.4"
14
14
  },
15
15
  "private": true
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-system",
3
- "version": "0.0.1-next.3",
3
+ "version": "0.0.1-next.4",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "dependencies": {
43
- "@legendapp/spark-desktop-app": "0.0.1-next.3"
43
+ "@legendapp/spark-desktop-app": "0.0.1-next.4"
44
44
  },
45
45
  "private": true
46
46
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-tray",
3
- "version": "0.0.1-next.3",
3
+ "version": "0.0.1-next.4",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "dependencies": {
43
- "@legendapp/spark-desktop-app": "0.0.1-next.3"
43
+ "@legendapp/spark-desktop-app": "0.0.1-next.4"
44
44
  },
45
45
  "private": true
46
46
  }
@@ -203,6 +203,73 @@ static void RNSidebarSplitViewApplyColorOverlay(NSView *view, NSColor *color, CG
203
203
  }
204
204
  }
205
205
 
206
+ // Pane containers are transparent, so a thin divider showed the window's (white) background through it,
207
+ // which is invisible in light mode and a bright stripe in dark mode. Paint it opaquely with semantic colors
208
+ // that resolve against the drawing appearance.
209
+ @interface RNSidebarSplitViewNativeSplitView : NSSplitView
210
+ /// YES while the user drags a divider.
211
+ @property (nonatomic, readonly) BOOL trackingDivider;
212
+ @end
213
+
214
+ @implementation RNSidebarSplitViewNativeSplitView
215
+
216
+ // NSSplitView only receives mouseDown: on a divider, and tracks the whole drag inside it.
217
+ - (void)mouseDown:(NSEvent *)event
218
+ {
219
+ _trackingDivider = YES;
220
+ [super mouseDown:event];
221
+ _trackingDivider = NO;
222
+ }
223
+
224
+ // While a divider drags, panes resize under a still cursor and pane edges pass beneath it. React views take
225
+ // hover from a window hit test, so claiming the hit here keeps rows from flickering hover states (and starting
226
+ // their hover animations) every frame of the drag.
227
+ - (NSView *)hitTest:(NSPoint)point
228
+ {
229
+ if (_trackingDivider) {
230
+ return NSPointInRect([self convertPoint:point fromView:self.superview], self.bounds) ? self : nil;
231
+ }
232
+ return [super hitTest:point];
233
+ }
234
+
235
+ - (NSColor *)dividerColor
236
+ {
237
+ return NSColor.separatorColor;
238
+ }
239
+
240
+ - (void)drawDividerInRect:(NSRect)rect
241
+ {
242
+ [NSColor.windowBackgroundColor setFill];
243
+ NSRectFill(rect);
244
+ [self.dividerColor setFill];
245
+ NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver);
246
+ }
247
+ @end
248
+
249
+ // List and content panes are opaque in the window background color, as AppKit panes are. React content
250
+ // follows a native resize one commit later; until then the newly exposed strip matches the window instead
251
+ // of showing a white stripe. The sidebar container stays clear so its native material shows.
252
+ @interface RNSidebarSplitViewPaneView : NSView
253
+ @end
254
+
255
+ @implementation RNSidebarSplitViewPaneView
256
+ - (BOOL)wantsUpdateLayer
257
+ {
258
+ return YES;
259
+ }
260
+
261
+ - (void)updateLayer
262
+ {
263
+ self.layer.backgroundColor = NSColor.windowBackgroundColor.CGColor;
264
+ }
265
+
266
+ - (void)viewDidChangeEffectiveAppearance
267
+ {
268
+ [super viewDidChangeEffectiveAppearance];
269
+ self.needsDisplay = YES;
270
+ }
271
+ @end
272
+
206
273
  static char RNSidebarSplitViewStartupKey;
207
274
 
208
275
  // Startup must stay entirely in AppKit: constructing a Fabric view here would
@@ -356,22 +423,35 @@ static char RNSidebarSplitViewStartupKey;
356
423
  NSSplitViewController *_splitViewController;
357
424
  NSViewController *_sidebarViewController;
358
425
  NSViewController *_contentViewController;
426
+ NSViewController *_listViewController;
359
427
  NSSplitViewItem *_sidebarItem;
360
428
  NSSplitViewItem *_contentItem;
429
+ NSSplitViewItem *_listItem;
361
430
  NSView *_sidebarContainer;
362
431
  NSView *_contentContainer;
432
+ NSView *_listContainer;
363
433
  RCTUIView<RCTComponentViewProtocol> *_sidebarReactView;
364
434
  RCTUIView<RCTComponentViewProtocol> *_contentReactView;
435
+ RCTUIView<RCTComponentViewProtocol> *_listReactView;
365
436
  id _resizeObserver;
366
437
  LayoutMetrics _currentLayoutMetrics;
367
438
  LayoutMetrics _sidebarReactLayoutMetrics;
368
439
  LayoutMetrics _contentReactLayoutMetrics;
440
+ LayoutMetrics _listReactLayoutMetrics;
369
441
  CGFloat _sidebarMinWidth;
370
442
  CGFloat _sidebarWidth;
371
443
  CGFloat _contentMinWidth;
444
+ CGFloat _listMinWidth;
445
+ CGFloat _listWidth;
446
+ BOOL _hasList;
372
447
  BOOL _sidebarCollapsed;
448
+ // Preferred widths seed the dividers once; afterwards the user's drag owns them.
449
+ BOOL _needsPreferredDividerPositions;
450
+ // Our own layout pass resizes subviews several times; only its final state is published.
451
+ BOOL _layingOutSplitView;
373
452
  CGFloat _lastSidebarWidth;
374
453
  CGFloat _lastContentWidth;
454
+ CGFloat _lastListWidth;
375
455
  CGFloat _lastHeight;
376
456
  BOOL _lastLayoutReady;
377
457
  BOOL _didPublishMount;
@@ -401,12 +481,19 @@ static char RNSidebarSplitViewStartupKey;
401
481
  _currentLayoutMetrics = EmptyLayoutMetrics;
402
482
  _sidebarReactLayoutMetrics = EmptyLayoutMetrics;
403
483
  _contentReactLayoutMetrics = EmptyLayoutMetrics;
484
+ _listReactLayoutMetrics = EmptyLayoutMetrics;
404
485
  _sidebarMinWidth = 180;
405
486
  _sidebarWidth = 0;
406
487
  _contentMinWidth = 320;
488
+ _listMinWidth = 240;
489
+ _listWidth = 0;
490
+ _hasList = NO;
407
491
  _sidebarCollapsed = NO;
492
+ _needsPreferredDividerPositions = YES;
493
+ _layingOutSplitView = NO;
408
494
  _lastSidebarWidth = -1;
409
495
  _lastContentWidth = -1;
496
+ _lastListWidth = -1;
410
497
  _lastHeight = -1;
411
498
  _lastLayoutReady = NO;
412
499
  _appearanceName = @"system";
@@ -415,33 +502,47 @@ static char RNSidebarSplitViewStartupKey;
415
502
  _contentTitlebarOverlayOpacity = 0;
416
503
  _sidebarTitlebarOverlayOpacity = 0;
417
504
  _sidebarContainer = [NSView new];
418
- _contentContainer = [NSView new];
505
+ _contentContainer = [RNSidebarSplitViewPaneView new];
506
+ _listContainer = [RNSidebarSplitViewPaneView new];
419
507
  _sidebarContainer.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
420
508
  _contentContainer.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
509
+ _listContainer.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
421
510
  _sidebarContainer.wantsLayer = YES;
422
511
  _contentContainer.wantsLayer = YES;
512
+ _listContainer.wantsLayer = YES;
423
513
  _sidebarContainer.layer.masksToBounds = NO;
424
514
  _contentContainer.layer.masksToBounds = NO;
515
+ _listContainer.layer.masksToBounds = YES;
425
516
  _sidebarContainer.layer.zPosition = 10;
426
517
  _contentContainer.layer.zPosition = 0;
518
+ _listContainer.layer.zPosition = 0;
427
519
 
428
520
  _sidebarViewController = [NSViewController new];
429
521
  _contentViewController = [NSViewController new];
522
+ _listViewController = [NSViewController new];
430
523
  _sidebarViewController.view = _sidebarContainer;
431
524
  _contentViewController.view = _contentContainer;
525
+ _listViewController.view = _listContainer;
432
526
 
433
527
  _sidebarItem = [NSSplitViewItem sidebarWithViewController:_sidebarViewController];
434
528
  _contentItem = [NSSplitViewItem splitViewItemWithViewController:_contentViewController];
529
+ // Mail-style middle column. Inserted between sidebar and content only while a list pane is supplied.
530
+ _listItem = [NSSplitViewItem contentListWithViewController:_listViewController];
435
531
  _sidebarItem.canCollapse = YES;
436
532
  _contentItem.canCollapse = NO;
533
+ _listItem.canCollapse = NO;
534
+ // Window resizes go to the content column; sidebar (260) and list keep their widths.
535
+ _listItem.holdingPriority = NSLayoutPriorityDefaultLow + 1;
437
536
  [self updateSplitItemSizing];
438
537
 
439
538
  if (@available(macOS 11.0, *)) {
440
539
  _sidebarItem.allowsFullHeightLayout = YES;
441
540
  _contentItem.allowsFullHeightLayout = YES;
541
+ _listItem.allowsFullHeightLayout = YES;
442
542
  }
443
543
 
444
544
  _splitViewController = [NSSplitViewController new];
545
+ _splitViewController.splitView = [RNSidebarSplitViewNativeSplitView new];
445
546
  _splitViewController.minimumThicknessForInlineSidebars = 0;
446
547
  _splitViewController.splitView.vertical = YES;
447
548
  _splitViewController.splitView.dividerStyle = NSSplitViewDividerStyleThin;
@@ -450,12 +551,13 @@ static char RNSidebarSplitViewStartupKey;
450
551
  [_splitViewController addSplitViewItem:_contentItem];
451
552
  [self applyAppearance];
452
553
 
554
+ __weak RNSidebarSplitViewComponent *weakSelf = self;
453
555
  _resizeObserver = [[NSNotificationCenter defaultCenter]
454
556
  addObserverForName:NSSplitViewDidResizeSubviewsNotification
455
557
  object:_splitViewController.splitView
456
558
  queue:NSOperationQueue.mainQueue
457
559
  usingBlock:^(__unused NSNotification *notification) {
458
- [self publishSplitViewLayoutAllowEstimatedReady:NO];
560
+ [weakSelf splitViewDidResize];
459
561
  }];
460
562
 
461
563
  [self addSubview:_splitViewController.view];
@@ -480,17 +582,44 @@ static char RNSidebarSplitViewStartupKey;
480
582
  _sidebarItem.minimumThickness = MAX(120, _sidebarMinWidth);
481
583
  _sidebarItem.preferredThicknessFraction = 0.26;
482
584
  _contentItem.minimumThickness = MAX(240, _contentMinWidth);
585
+ _listItem.minimumThickness = MAX(120, _listMinWidth);
586
+ }
587
+
588
+ - (void)updateListItem
589
+ {
590
+ BOOL installed = [_splitViewController.splitViewItems containsObject:_listItem];
591
+ if (_hasList && !installed) {
592
+ [_splitViewController insertSplitViewItem:_listItem atIndex:1];
593
+ } else if (!_hasList && installed) {
594
+ [_splitViewController removeSplitViewItem:_listItem];
595
+ }
596
+ }
597
+
598
+ - (CGFloat)listReserveWidth
599
+ {
600
+ return _hasList ? _listItem.minimumThickness + _splitViewController.splitView.dividerThickness : 0;
483
601
  }
484
602
 
485
603
  - (CGFloat)preferredSidebarWidthForBounds:(CGRect)bounds
486
604
  {
487
605
  CGFloat dividerThickness = _splitViewController.splitView.dividerThickness;
488
- CGFloat maxSidebarWidth = bounds.size.width - _contentMinWidth - dividerThickness;
606
+ CGFloat maxSidebarWidth = bounds.size.width - _contentItem.minimumThickness - dividerThickness - [self listReserveWidth];
489
607
  CGFloat preferredSidebarWidth = _sidebarWidth > 0 ? _sidebarWidth : _sidebarMinWidth;
490
- CGFloat sidebarWidth = MIN(MAX(_sidebarMinWidth, preferredSidebarWidth), maxSidebarWidth);
608
+ CGFloat sidebarWidth = MIN(MAX(_sidebarItem.minimumThickness, preferredSidebarWidth), maxSidebarWidth);
491
609
  return MAX(0, sidebarWidth);
492
610
  }
493
611
 
612
+ - (CGFloat)preferredListWidthForBounds:(CGRect)bounds sidebarExtent:(CGFloat)sidebarExtent
613
+ {
614
+ if (!_hasList) {
615
+ return 0;
616
+ }
617
+ CGFloat dividerThickness = _splitViewController.splitView.dividerThickness;
618
+ CGFloat maxListWidth = bounds.size.width - sidebarExtent - _contentItem.minimumThickness - dividerThickness;
619
+ CGFloat preferredListWidth = _listWidth > 0 ? _listWidth : _listMinWidth;
620
+ return MAX(0, MIN(MAX(_listItem.minimumThickness, preferredListWidth), maxListWidth));
621
+ }
622
+
494
623
  - (void)updateSidebarCollapsed
495
624
  {
496
625
  if (_sidebarItem.collapsed != _sidebarCollapsed) {
@@ -506,6 +635,7 @@ static char RNSidebarSplitViewStartupKey;
506
635
  _splitViewController.splitView.appearance = appearance;
507
636
  _sidebarContainer.appearance = appearance;
508
637
  _contentContainer.appearance = appearance;
638
+ _listContainer.appearance = appearance;
509
639
  [_splitViewController.view setNeedsDisplay:YES];
510
640
  [_splitViewController.splitView setNeedsDisplay:YES];
511
641
  [_sidebarContainer setNeedsDisplay:YES];
@@ -653,6 +783,9 @@ static char RNSidebarSplitViewStartupKey;
653
783
  [self syncReactSubview:_contentReactView
654
784
  nativeBounds:contentBounds
655
785
  previousLayoutMetrics:&_contentReactLayoutMetrics];
786
+ [self syncReactSubview:_listReactView
787
+ nativeBounds:_hasList ? _listContainer.bounds : CGRectZero
788
+ previousLayoutMetrics:&_listReactLayoutMetrics];
656
789
  }
657
790
 
658
791
  - (void)syncReactSubview:(nullable RCTUIView<RCTComponentViewProtocol> *)subview
@@ -692,6 +825,8 @@ static char RNSidebarSplitViewStartupKey;
692
825
  sidebarHeight:(CGFloat)sidebarHeight
693
826
  contentHeight:(CGFloat)contentHeight
694
827
  height:(CGFloat)height
828
+ listWidth:(CGFloat)listWidth
829
+ listX:(CGFloat)listX
695
830
  layoutReady:(BOOL)layoutReady
696
831
  {
697
832
  const auto eventEmitter = std::static_pointer_cast<const SidebarSplitViewEventEmitter>(_eventEmitter);
@@ -703,7 +838,7 @@ static char RNSidebarSplitViewStartupKey;
703
838
  return;
704
839
  }
705
840
 
706
- BOOL panesReady = layoutReady && _sidebarReactView && _contentReactView;
841
+ BOOL panesReady = layoutReady && _sidebarReactView && _contentReactView && (!_hasList || _listReactView);
707
842
  if (panesReady && self.window && !_didPublishMount) {
708
843
  // This also runs when an already-laid-out Fabric tree joins its window.
709
844
  // Keep it before metrics deduplication so the startup cover cannot linger.
@@ -713,6 +848,7 @@ static char RNSidebarSplitViewStartupKey;
713
848
  }
714
849
  if (fabs(sidebarWidth - _lastSidebarWidth) < 0.5 &&
715
850
  fabs(contentWidth - _lastContentWidth) < 0.5 &&
851
+ fabs(listWidth - _lastListWidth) < 0.5 &&
716
852
  fabs(height - _lastHeight) < 0.5 &&
717
853
  panesReady == _lastLayoutReady) {
718
854
  return;
@@ -720,6 +856,7 @@ static char RNSidebarSplitViewStartupKey;
720
856
 
721
857
  _lastSidebarWidth = sidebarWidth;
722
858
  _lastContentWidth = contentWidth;
859
+ _lastListWidth = listWidth;
723
860
  _lastHeight = height;
724
861
  _lastLayoutReady = panesReady;
725
862
 
@@ -730,6 +867,9 @@ static char RNSidebarSplitViewStartupKey;
730
867
  .height = height,
731
868
  .isLayoutReady = static_cast<bool>(panesReady),
732
869
  .isVertical = true,
870
+ .listHeight = listWidth > 0 ? height : 0,
871
+ .listWidth = listWidth,
872
+ .listX = listX,
733
873
  .sidebarHeight = sidebarHeight,
734
874
  .sidebarWidth = sidebarWidth,
735
875
  });
@@ -746,16 +886,20 @@ static char RNSidebarSplitViewStartupKey;
746
886
  if (!_sidebarCollapsed) {
747
887
  sidebarWidth = [self preferredSidebarWidthForBounds:bounds];
748
888
  }
749
- CGFloat contentX = sidebarWidth > 0 ? sidebarWidth + dividerThickness : 0;
889
+ CGFloat listX = sidebarWidth > 0 ? sidebarWidth + dividerThickness : 0;
890
+ CGFloat listWidth = [self preferredListWidthForBounds:bounds sidebarExtent:listX];
891
+ CGFloat contentX = listWidth > 0 ? listX + listWidth + dividerThickness : listX;
750
892
  CGFloat contentWidth = MAX(0, bounds.size.width - contentX);
751
893
  if (contentWidth <= 0) {
752
894
  contentWidth = bounds.size.width;
753
895
  contentX = 0;
896
+ listWidth = 0;
754
897
  }
755
898
 
756
899
  _splitViewController.view.frame = bounds;
757
900
  _splitViewController.splitView.frame = bounds;
758
901
  _sidebarContainer.frame = CGRectMake(0, 0, sidebarWidth, bounds.size.height);
902
+ _listContainer.frame = CGRectMake(listX, 0, listWidth, bounds.size.height);
759
903
  _contentContainer.frame = CGRectMake(contentX, 0, contentWidth, bounds.size.height);
760
904
 
761
905
  CGRect sidebarBounds = CGRectMake(0, 0, sidebarWidth, bounds.size.height);
@@ -766,14 +910,21 @@ static char RNSidebarSplitViewStartupKey;
766
910
  [self syncReactSubview:_contentReactView
767
911
  nativeBounds:contentBounds
768
912
  previousLayoutMetrics:&_contentReactLayoutMetrics];
913
+ [self syncReactSubview:_listReactView
914
+ nativeBounds:CGRectMake(0, 0, listWidth, bounds.size.height)
915
+ previousLayoutMetrics:&_listReactLayoutMetrics];
769
916
  [self layoutContentTitlebarMaterial];
770
- [self emitSplitViewDidResizeWithSidebarWidth:sidebarWidth
771
- contentWidth:contentWidth
772
- contentX:contentX
773
- sidebarHeight:bounds.size.height
774
- contentHeight:bounds.size.height
775
- height:bounds.size.height
776
- layoutReady:layoutReady];
917
+ if (!_layingOutSplitView) {
918
+ [self emitSplitViewDidResizeWithSidebarWidth:sidebarWidth
919
+ contentWidth:contentWidth
920
+ contentX:contentX
921
+ sidebarHeight:bounds.size.height
922
+ contentHeight:bounds.size.height
923
+ height:bounds.size.height
924
+ listWidth:listWidth
925
+ listX:_hasList ? listX : 0
926
+ layoutReady:layoutReady];
927
+ }
777
928
 
778
929
  return YES;
779
930
  }
@@ -785,13 +936,16 @@ static char RNSidebarSplitViewStartupKey;
785
936
  CGFloat contentX = [_contentContainer convertRect:_contentContainer.bounds toView:self].origin.x;
786
937
  CGFloat sidebarHeight = _sidebarContainer.bounds.size.height;
787
938
  CGFloat contentHeight = _contentContainer.bounds.size.height;
939
+ CGFloat listWidth = _hasList ? _listContainer.bounds.size.width : 0;
940
+ CGFloat listX = _hasList ? [_listContainer convertRect:_listContainer.bounds toView:self].origin.x : 0;
788
941
  CGFloat height = MAX(sidebarHeight, contentHeight);
789
942
  CGRect bounds = [self currentLayoutBounds];
790
943
 
791
944
  if (contentWidth <= 0 || height <= 0 ||
792
945
  contentX < -0.5 ||
793
946
  contentX + contentWidth > bounds.size.width + 0.5 ||
794
- fabs(_contentContainer.bounds.size.height - bounds.size.height) >= 0.5) {
947
+ fabs(_contentContainer.bounds.size.height - bounds.size.height) >= 0.5 ||
948
+ (_hasList && listWidth <= 0)) {
795
949
  [self applyEstimatedSplitViewLayoutForBounds:bounds layoutReady:allowEstimatedReady];
796
950
  return;
797
951
  }
@@ -804,6 +958,8 @@ static char RNSidebarSplitViewStartupKey;
804
958
  sidebarHeight:sidebarHeight
805
959
  contentHeight:contentHeight
806
960
  height:height
961
+ listWidth:listWidth
962
+ listX:listX
807
963
  layoutReady:YES];
808
964
  }
809
965
 
@@ -820,25 +976,64 @@ static char RNSidebarSplitViewStartupKey;
820
976
  return bounds;
821
977
  }
822
978
 
979
+ /// Seeds dividers from the preferred widths. Runs only until it succeeds once (and again when those props
980
+ /// change), so a later layout pass never snaps a divider the user dragged back to its preferred position.
823
981
  - (void)applyDividerPositionForBounds:(CGRect)bounds
824
982
  {
825
- if (_sidebarCollapsed || bounds.size.width <= 0 || _splitViewController.splitView.subviews.count < 2) {
983
+ if (!_needsPreferredDividerPositions || bounds.size.width <= 0 || _splitViewController.splitView.subviews.count < 2) {
826
984
  return;
827
985
  }
986
+ CGFloat dividerThickness = _splitViewController.splitView.dividerThickness;
987
+ CGFloat sidebarExtent = 0;
988
+ if (!_sidebarCollapsed) {
989
+ CGFloat sidebarWidth = [self preferredSidebarWidthForBounds:bounds];
990
+ if (sidebarWidth <= 0) {
991
+ return;
992
+ }
993
+ [_splitViewController.splitView setPosition:sidebarWidth ofDividerAtIndex:0];
994
+ sidebarExtent = sidebarWidth + dividerThickness;
995
+ }
996
+ if (_hasList) {
997
+ if (_splitViewController.splitView.subviews.count < 3) {
998
+ return;
999
+ }
1000
+ CGFloat listWidth = [self preferredListWidthForBounds:bounds sidebarExtent:sidebarExtent];
1001
+ if (listWidth <= 0) {
1002
+ return;
1003
+ }
1004
+ [_splitViewController.splitView setPosition:sidebarExtent + listWidth ofDividerAtIndex:1];
1005
+ }
1006
+ // A small startup frame can clamp a preference. Retry on later layouts until it fits,
1007
+ // unless a real divider drag takes ownership in splitViewDidResize.
1008
+ CGFloat desiredSidebarWidth = MAX(_sidebarItem.minimumThickness, _sidebarWidth > 0 ? _sidebarWidth : _sidebarMinWidth);
1009
+ CGFloat desiredListWidth = MAX(_listItem.minimumThickness, _listWidth > 0 ? _listWidth : _listMinWidth);
1010
+ _needsPreferredDividerPositions =
1011
+ (!_sidebarCollapsed && [self preferredSidebarWidthForBounds:bounds] < desiredSidebarWidth - 0.5) ||
1012
+ (_hasList && [self preferredListWidthForBounds:bounds sidebarExtent:sidebarExtent] < desiredListWidth - 0.5);
1013
+ }
828
1014
 
829
- CGFloat sidebarWidth = [self preferredSidebarWidthForBounds:bounds];
830
- if (sidebarWidth <= 0) {
1015
+ - (void)splitViewDidResize
1016
+ {
1017
+ if (_layingOutSplitView) {
831
1018
  return;
832
1019
  }
833
-
834
- [_splitViewController.splitView setPosition:sidebarWidth ofDividerAtIndex:0];
1020
+ if (((RNSidebarSplitViewNativeSplitView *)_splitViewController.splitView).trackingDivider) {
1021
+ _needsPreferredDividerPositions = NO;
1022
+ }
1023
+ [self publishSplitViewLayoutAllowEstimatedReady:NO];
835
1024
  }
836
1025
 
837
1026
  - (void)layoutSplitView
838
1027
  {
1028
+ if (_layingOutSplitView) {
1029
+ return;
1030
+ }
1031
+ _layingOutSplitView = YES;
839
1032
  CGRect bounds = [self currentLayoutBounds];
840
1033
  _splitViewController.view.frame = bounds;
841
1034
  _splitViewController.splitView.frame = bounds;
1035
+ [self updateSplitItemSizing];
1036
+ [self updateListItem];
842
1037
  [self updateSidebarCollapsed];
843
1038
  if (_contentContainer.bounds.size.width <= 0 ||
844
1039
  _contentContainer.bounds.size.height <= 0 ||
@@ -846,9 +1041,9 @@ static char RNSidebarSplitViewStartupKey;
846
1041
  [self applyEstimatedSplitViewLayoutForBounds:bounds layoutReady:NO];
847
1042
  }
848
1043
  [self applyDividerPositionForBounds:bounds];
849
- [_splitViewController.splitView adjustSubviews];
850
1044
  [_splitViewController.view layoutSubtreeIfNeeded];
851
1045
  [self layoutContentTitlebarMaterial];
1046
+ _layingOutSplitView = NO;
852
1047
  [self publishSplitViewLayoutAllowEstimatedReady:YES];
853
1048
  }
854
1049
 
@@ -903,13 +1098,28 @@ static char RNSidebarSplitViewStartupKey;
903
1098
  });
904
1099
  return;
905
1100
  }
1101
+
1102
+ if (index == 2) {
1103
+ [_listReactView removeFromSuperview];
1104
+ _listReactView = childComponentView;
1105
+ _listReactLayoutMetrics = EmptyLayoutMetrics;
1106
+ childComponentView.hidden = YES;
1107
+ [_listContainer addSubview:childComponentView];
1108
+ [self syncReactSubview:_listReactView
1109
+ nativeBounds:_hasList ? _listContainer.bounds : CGRectZero
1110
+ previousLayoutMetrics:&_listReactLayoutMetrics];
1111
+ dispatch_async(dispatch_get_main_queue(), ^{
1112
+ [self publishSplitViewLayoutAllowEstimatedReady:NO];
1113
+ });
1114
+ return;
1115
+ }
906
1116
  #else
907
1117
  if (index == 0) {
908
1118
  [_sidebarContainer addSubview:childComponentView];
909
1119
  return;
910
1120
  }
911
1121
 
912
- if (index == 1) {
1122
+ if (index == 1 || index == 2) {
913
1123
  [_contentContainer addSubview:childComponentView];
914
1124
  return;
915
1125
  }
@@ -935,8 +1145,15 @@ static char RNSidebarSplitViewStartupKey;
935
1145
  [childComponentView removeFromSuperview];
936
1146
  return;
937
1147
  }
1148
+
1149
+ if (childComponentView == _listReactView) {
1150
+ _listReactView = nil;
1151
+ _listReactLayoutMetrics = EmptyLayoutMetrics;
1152
+ [childComponentView removeFromSuperview];
1153
+ return;
1154
+ }
938
1155
  #else
939
- if (index == 0 || index == 1) {
1156
+ if (index == 0 || index == 1 || index == 2) {
940
1157
  [childComponentView removeFromSuperview];
941
1158
  return;
942
1159
  }
@@ -954,11 +1171,16 @@ static char RNSidebarSplitViewStartupKey;
954
1171
  if (nextAppearanceName.length == 0) {
955
1172
  nextAppearanceName = @"system";
956
1173
  }
1174
+ BOOL preferredWidthsChanged =
1175
+ fabs(_sidebarWidth - newProps.sidebarWidth) >= 0.5 ||
1176
+ fabs(_listWidth - newProps.listWidth) >= 0.5 ||
1177
+ _hasList != newProps.hasList ||
1178
+ _sidebarCollapsed != newProps.sidebarCollapsed;
957
1179
  BOOL shouldRelayout =
1180
+ preferredWidthsChanged ||
958
1181
  fabs(_sidebarMinWidth - newProps.sidebarMinWidth) >= 0.5 ||
959
- fabs(_sidebarWidth - newProps.sidebarWidth) >= 0.5 ||
960
1182
  fabs(_contentMinWidth - newProps.contentMinWidth) >= 0.5 ||
961
- _sidebarCollapsed != newProps.sidebarCollapsed ||
1183
+ fabs(_listMinWidth - newProps.listMinWidth) >= 0.5 ||
962
1184
  fabs(_contentTitlebarHeight - newProps.contentTitlebarHeight) >= 0.5;
963
1185
  NSString *nextContentTitlebarMaterialName = [NSString stringWithUTF8String:newProps.contentTitlebarMaterial.c_str()];
964
1186
  if (nextContentTitlebarMaterialName.length == 0) {
@@ -988,7 +1210,13 @@ static char RNSidebarSplitViewStartupKey;
988
1210
  _sidebarMinWidth = newProps.sidebarMinWidth;
989
1211
  _sidebarWidth = newProps.sidebarWidth;
990
1212
  _contentMinWidth = newProps.contentMinWidth;
1213
+ _listMinWidth = newProps.listMinWidth;
1214
+ _listWidth = newProps.listWidth;
1215
+ _hasList = newProps.hasList;
991
1216
  _sidebarCollapsed = newProps.sidebarCollapsed;
1217
+ if (preferredWidthsChanged) {
1218
+ _needsPreferredDividerPositions = YES;
1219
+ }
992
1220
  _contentTitlebarHeight = newProps.contentTitlebarHeight;
993
1221
  _contentTitlebarMaterialName = nextContentTitlebarMaterialName;
994
1222
  _contentTitlebarOverlayColorValue = nextContentTitlebarOverlayColorValue;
@@ -1005,8 +1233,6 @@ static char RNSidebarSplitViewStartupKey;
1005
1233
  _appearanceName = nextAppearanceName;
1006
1234
  [self applyAppearance];
1007
1235
  }
1008
- [self updateSidebarCollapsed];
1009
- [self updateSplitItemSizing];
1010
1236
  #endif
1011
1237
 
1012
1238
  [super updateProps:props oldProps:oldProps];
@@ -1015,6 +1241,7 @@ static char RNSidebarSplitViewStartupKey;
1015
1241
  if (shouldRelayout || shouldRecreateContentTitlebarMaterial || shouldRecreateSidebarTitlebarMaterial) {
1016
1242
  _lastSidebarWidth = -1;
1017
1243
  _lastContentWidth = -1;
1244
+ _lastListWidth = -1;
1018
1245
  _lastHeight = -1;
1019
1246
  _lastLayoutReady = NO;
1020
1247
  [self layoutSplitView];
@@ -1086,17 +1313,26 @@ static char RNSidebarSplitViewStartupKey;
1086
1313
  #if TARGET_OS_OSX
1087
1314
  [_sidebarReactView removeFromSuperview];
1088
1315
  [_contentReactView removeFromSuperview];
1316
+ [_listReactView removeFromSuperview];
1089
1317
  _sidebarReactView = nil;
1090
1318
  _contentReactView = nil;
1319
+ _listReactView = nil;
1091
1320
  _currentLayoutMetrics = EmptyLayoutMetrics;
1092
1321
  _sidebarReactLayoutMetrics = EmptyLayoutMetrics;
1093
1322
  _contentReactLayoutMetrics = EmptyLayoutMetrics;
1323
+ _listReactLayoutMetrics = EmptyLayoutMetrics;
1094
1324
  _sidebarMinWidth = 180;
1095
1325
  _sidebarWidth = 0;
1096
1326
  _contentMinWidth = 320;
1327
+ _listMinWidth = 240;
1328
+ _listWidth = 0;
1329
+ _hasList = NO;
1097
1330
  _sidebarCollapsed = NO;
1331
+ _needsPreferredDividerPositions = YES;
1332
+ _layingOutSplitView = NO;
1098
1333
  _lastSidebarWidth = -1;
1099
1334
  _lastContentWidth = -1;
1335
+ _lastListWidth = -1;
1100
1336
  _lastHeight = -1;
1101
1337
  _lastLayoutReady = NO;
1102
1338
  _appearanceName = @"system";
@@ -1110,6 +1346,7 @@ static char RNSidebarSplitViewStartupKey;
1110
1346
  [self removeContentTitlebarMaterial];
1111
1347
  [self removeSidebarTitlebarMaterial];
1112
1348
  [self applyAppearance];
1349
+ [self updateListItem];
1113
1350
  [self updateSidebarCollapsed];
1114
1351
  [self updateSplitItemSizing];
1115
1352
  #else
@@ -0,0 +1,10 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <AppKit/AppKit.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ /** Hosts one React child that two-finger horizontal swipes slide aside to reveal row actions. */
7
+ @interface RNSwipeActions : RCTViewComponentView
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END