@lucent-lang/lucent 0.0.4 → 0.1.1

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 (152) hide show
  1. package/README.md +97 -0
  2. package/app.plugin.js +46 -10
  3. package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
  7. package/dist/cli.js +62 -13
  8. package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
  9. package/dist/compiler.js +4 -4
  10. package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
  12. package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
  13. package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
  14. package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
  15. package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
  16. package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
  17. package/dist/pipeline-BM_VM2X_.js +700 -0
  18. package/dist/project-BKNoTEEp.js +342 -0
  19. package/dist/sdk-coverage-iiv3JqMp.js +113 -0
  20. package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
  21. package/dist/sdk-lock-BQn7IyRs.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
  25. package/dist/sdk-usage-0ZFMAYg1.js +95 -0
  26. package/dist/src-ChnmWLwn.js +40851 -0
  27. package/dist/trace-Bi4hkmPx.js +139 -0
  28. package/lib/core.cjs +368 -1
  29. package/lib/sdk/android.d.ts +54 -1
  30. package/lib/sdk/compose.d.ts +94 -0
  31. package/lib/sdk/compose.schemas.json.gz +0 -0
  32. package/lib/sdk/core.d.ts +201 -0
  33. package/lib/sdk/ios.d.ts +75 -2
  34. package/lib/sdk/ui.d.ts +129 -0
  35. package/metro/transformer.cjs +11 -2
  36. package/package.json +4 -3
  37. package/runtime/cpp/lucent/abort.cpp +59 -11
  38. package/runtime/cpp/lucent/abort.h +22 -6
  39. package/runtime/cpp/lucent/array.h +12 -2
  40. package/runtime/cpp/lucent/async.h +101 -21
  41. package/runtime/cpp/lucent/bigint.cpp +898 -0
  42. package/runtime/cpp/lucent/bigint.h +302 -0
  43. package/runtime/cpp/lucent/buffer.cpp +228 -0
  44. package/runtime/cpp/lucent/buffer.h +328 -0
  45. package/runtime/cpp/lucent/builtins.cpp +4 -1
  46. package/runtime/cpp/lucent/bytes.h +21 -2
  47. package/runtime/cpp/lucent/callback.cpp +29 -0
  48. package/runtime/cpp/lucent/callback.h +230 -0
  49. package/runtime/cpp/lucent/compute.cpp +370 -0
  50. package/runtime/cpp/lucent/compute.h +455 -0
  51. package/runtime/cpp/lucent/conversions.h +111 -0
  52. package/runtime/cpp/lucent/core.h +16 -5
  53. package/runtime/cpp/lucent/equality.h +107 -28
  54. package/runtime/cpp/lucent/execution.cpp +416 -0
  55. package/runtime/cpp/lucent/execution.h +270 -0
  56. package/runtime/cpp/lucent/extension.cpp +97 -0
  57. package/runtime/cpp/lucent/extension.h +210 -0
  58. package/runtime/cpp/lucent/helpers.h +12 -5
  59. package/runtime/cpp/lucent/items.h +52 -0
  60. package/runtime/cpp/lucent/jserror.cpp +9 -0
  61. package/runtime/cpp/lucent/jserror.h +11 -0
  62. package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
  63. package/runtime/cpp/lucent/jsi/convert.h +128 -59
  64. package/runtime/cpp/lucent/jsi/host.cpp +213 -35
  65. package/runtime/cpp/lucent/jsi/host.h +116 -16
  66. package/runtime/cpp/lucent/json.h +19 -1
  67. package/runtime/cpp/lucent/lifecycle.cpp +260 -0
  68. package/runtime/cpp/lucent/lifecycle.h +124 -0
  69. package/runtime/cpp/lucent/lucent.h +7 -0
  70. package/runtime/cpp/lucent/map.h +41 -7
  71. package/runtime/cpp/lucent/native.cpp +35 -47
  72. package/runtime/cpp/lucent/native.h +116 -26
  73. package/runtime/cpp/lucent/number.cpp +38 -38
  74. package/runtime/cpp/lucent/number.h +33 -0
  75. package/runtime/cpp/lucent/operation.h +65 -0
  76. package/runtime/cpp/lucent/ops.h +14 -6
  77. package/runtime/cpp/lucent/platform/android.cpp +223 -10
  78. package/runtime/cpp/lucent/platform/android.h +227 -4
  79. package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
  80. package/runtime/cpp/lucent/platform/android_requests.h +140 -0
  81. package/runtime/cpp/lucent/platform/compose.h +256 -0
  82. package/runtime/cpp/lucent/platform/ios.h +325 -25
  83. package/runtime/cpp/lucent/platform/ios.mm +1 -14
  84. package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
  85. package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
  86. package/runtime/cpp/lucent/platform/swiftui.h +190 -0
  87. package/runtime/cpp/lucent/presentation.cpp +11 -0
  88. package/runtime/cpp/lucent/presentation.h +109 -0
  89. package/runtime/cpp/lucent/reactive.cpp +406 -0
  90. package/runtime/cpp/lucent/reactive.h +566 -0
  91. package/runtime/cpp/lucent/report.cpp +40 -0
  92. package/runtime/cpp/lucent/report.h +16 -0
  93. package/runtime/cpp/lucent/resource.cpp +83 -0
  94. package/runtime/cpp/lucent/resource.h +75 -0
  95. package/runtime/cpp/lucent/scheduler.cpp +38 -124
  96. package/runtime/cpp/lucent/scheduler.h +102 -83
  97. package/runtime/cpp/lucent/scope.cpp +308 -0
  98. package/runtime/cpp/lucent/scope.h +452 -0
  99. package/runtime/cpp/lucent/sizing.h +263 -0
  100. package/runtime/cpp/lucent/slots.h +231 -0
  101. package/runtime/cpp/lucent/trace.cpp +539 -0
  102. package/runtime/cpp/lucent/trace.h +251 -0
  103. package/runtime/cpp/lucent/transport.cpp +9 -0
  104. package/runtime/cpp/lucent/transport.h +341 -0
  105. package/runtime/cpp/lucent/view.cpp +123 -0
  106. package/runtime/cpp/lucent/view.h +157 -0
  107. package/runtime/cpp/rn/LucentComponentView.h +121 -0
  108. package/runtime/cpp/rn/LucentComponentView.mm +479 -0
  109. package/runtime/cpp/rn/LucentModule.cpp +37 -7
  110. package/runtime/cpp/rn/LucentModule.h +3 -0
  111. package/runtime/cpp/rn/LucentViewChildren.h +80 -0
  112. package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
  113. package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
  114. package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
  115. package/runtime/cpp/rn/LucentViewRequests.h +104 -0
  116. package/runtime/cpp/rn/LucentViewSizing.h +345 -0
  117. package/runtime/cpp/rn/LucentViewSlots.h +242 -0
  118. package/runtime/cpp/rn/LucentViewValues.h +125 -0
  119. package/runtime/cpp/rn/LucentViews.h +440 -0
  120. package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
  121. package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
  122. package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
  123. package/runtime/cpp/third_party/dragonbox/README.md +16 -0
  124. package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
  125. package/runtime/js/index.d.ts +11 -1
  126. package/runtime/js/index.js +84 -8
  127. package/runtime/js/views.d.ts +38 -0
  128. package/runtime/js/views.js +226 -0
  129. package/runtime/native/android/CMakeLists.txt +4 -1
  130. package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
  131. package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
  132. package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
  133. package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
  134. package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
  135. package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
  136. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
  137. package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
  138. package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
  139. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
  140. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
  141. package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
  142. package/runtime/test/jsi/harness.cpp +9 -0
  143. package/schemas/build.schema.json +96 -1
  144. package/schemas/check.schema.json +56 -1
  145. package/schemas/lucent.schema.json +495 -9
  146. package/schemas/sdk-diff.schema.json +70 -0
  147. package/schemas/sdk-lock.schema.json +67 -0
  148. package/ts-plugin/index.js +85 -4
  149. package/dist/pipeline-CH7-2cxu.js +0 -232
  150. package/dist/project-D8tEet0k.js +0 -150
  151. package/dist/sdk-coverage-BoW_3Efd.js +0 -64
  152. package/dist/src-BD7f1_aZ.js +0 -11493
@@ -0,0 +1,479 @@
1
+ // Built only when the app's Lucent code has components (their Fabric
2
+ // sources are generated): the rest of the time React Native's view classes
3
+ // stay out of the Lucent pod.
4
+ #if __has_include(<views/lucent_views.h>)
5
+
6
+ #import "LucentComponentView.h"
7
+
8
+ #import <React/RCTComponentViewFactory.h>
9
+ #import <React/RCTUtils.h>
10
+ #import <react/utils/FollyConvert.h>
11
+
12
+ #include <lucent/execution.h>
13
+ #include <lucent/platform/ios.h>
14
+ #include <lucent/report.h>
15
+ #include <lucent/view.h>
16
+
17
+ #include <cmath>
18
+ #include <exception>
19
+ #include <utility>
20
+
21
+ #import "LucentViewChildren.h"
22
+ #include "LucentViewSizing.h"
23
+ #include "LucentViewSlots.h"
24
+
25
+ namespace lucent::views {
26
+
27
+ bool HostView::current() const {
28
+ LucentComponentView* view = view_;
29
+
30
+ return view != nil && view.lucentToken == token_;
31
+ }
32
+
33
+ std::shared_ptr<const facebook::react::EventEmitter> HostView::emitter() const {
34
+ LucentComponentView* view = view_;
35
+
36
+ return view != nil && view.lucentToken == token_ ? view.lucentEventEmitter : nullptr;
37
+ }
38
+
39
+ lucent::NativeRef HostView::slot() const {
40
+ LucentComponentView* view = view_;
41
+ UIView* slot = view != nil && view.lucentToken == token_ ? view.lucentSlot : nil;
42
+
43
+ return slot ? lucent::objc::wrap(slot, "the slot of a component's children") : lucent::NativeRef();
44
+ }
45
+
46
+ } // namespace lucent::views
47
+
48
+ namespace {
49
+
50
+ /** Runs `work` in Lucent's main context, reporting what it throws as `where`. */
51
+ template <class F>
52
+ void inMainContext(const char* where, F&& work) {
53
+ // Nothing escapes into UIKit, entering the context included.
54
+ try {
55
+ lucent::ContextEntry entry(lucent::ExecutionContext::main());
56
+
57
+ work();
58
+ } catch (...) {
59
+ lucent::reportUncaught(std::current_exception(), where);
60
+ }
61
+ }
62
+
63
+ } // namespace
64
+
65
+ @implementation LucentComponentView {
66
+ std::unique_ptr<lucent::views::Mounted> _mounted;
67
+ lucent::views::MountToken _token;
68
+ /// The props of the latest commit, and of the one the mount has.
69
+ facebook::react::Props::Shared _committed;
70
+ facebook::react::Props::Shared _applied;
71
+ lucent::views::HostSizing _sizing;
72
+ /// React Native's children of the component, and the mount's slot for them.
73
+ LucentChildren *_children;
74
+ /// Where the slot is, as the shadow tree lays the children out (LucentViewSlots.h).
75
+ lucent::views::HostSlot _place;
76
+ }
77
+
78
+ - (instancetype)initWithFrame:(CGRect)frame
79
+ {
80
+ if (self = [super initWithFrame:frame]) {
81
+ [[NSNotificationCenter defaultCenter] addObserver:self
82
+ selector:@selector(lucentContentSizeCategoryChanged:)
83
+ name:UIContentSizeCategoryDidChangeNotification
84
+ object:nil];
85
+ }
86
+
87
+ return self;
88
+ }
89
+
90
+ // The text size the user chose changed: content that follows it (Dynamic
91
+ // Type) changed size with no Lucent code running, and React Native lays
92
+ // nothing out again for it until something else changes. Measured on the
93
+ // next turn, once UIKit has updated the content's fonts.
94
+ - (void)lucentContentSizeCategoryChanged:(NSNotification *)notification
95
+ {
96
+ __weak LucentComponentView *weakSelf = self;
97
+
98
+ dispatch_async(dispatch_get_main_queue(), ^{
99
+ LucentComponentView *view = weakSelf;
100
+
101
+ if (view && view->_mounted) view->_sizing.contentChanged();
102
+ });
103
+ }
104
+
105
+ + (void)lucentRegister:(Class)componentViewClass
106
+ {
107
+ [[RCTComponentViewFactory currentComponentViewFactory] registerComponentViewClass:componentViewClass];
108
+ }
109
+
110
+ + (BOOL)lucentTakesChildren
111
+ {
112
+ return NO;
113
+ }
114
+
115
+ - (lucent::views::MountToken)lucentToken
116
+ {
117
+ return _token;
118
+ }
119
+
120
+ - (UIView *)lucentSlot
121
+ {
122
+ return _children.slot;
123
+ }
124
+
125
+ - (LucentChildren *)lucentChildren
126
+ {
127
+ if (!_children) {
128
+ __weak LucentComponentView *weakSelf = self;
129
+
130
+ _children = [[LucentChildren alloc] initWithHost:self
131
+ name:@([[self class] componentDescriptorProvider].name)
132
+ takesChildren:[[self class] lucentTakesChildren]
133
+ placed:^{ [weakSelf placeSlot]; }];
134
+ }
135
+
136
+ return _children;
137
+ }
138
+
139
+ /** Where the slot now is in the content box Yoga laid the component out with, for the shadow tree. */
140
+ - (void)placeSlot
141
+ {
142
+ UIView *slot = _children.slot;
143
+ UIView *parent = slot.superview;
144
+
145
+ if (!_mounted || !parent || ![slot isDescendantOfView:self]) return;
146
+
147
+ auto rect = [](auto r) {
148
+ return lucent::slots::Rect{
149
+ static_cast<float>(r.origin.x),
150
+ static_cast<float>(r.origin.y),
151
+ static_cast<float>(r.size.width),
152
+ static_cast<float>(r.size.height)};
153
+ };
154
+
155
+ _place.place(
156
+ lucent::slots::insetsOf(rect([parent convertRect:slot.frame toView:self]), rect(_layoutMetrics.getContentFrame())),
157
+ _layoutMetrics.layoutDirection == facebook::react::LayoutDirection::RightToLeft,
158
+ _layoutMetrics.wasLeftAndRightSwapped);
159
+ }
160
+
161
+ // React Native's children go in the mount's slot (LucentViewChildren.h), never
162
+ // in the host view beside the component's content.
163
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
164
+ {
165
+ [self.lucentChildren mount:childComponentView index:index];
166
+ }
167
+
168
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
169
+ {
170
+ [self.lucentChildren unmount:childComponentView index:index];
171
+ }
172
+
173
+ - (void)layoutSubviews
174
+ {
175
+ [super layoutSubviews];
176
+
177
+ // The content may have moved the slot within the host.
178
+ [_children.slot setNeedsLayout];
179
+ }
180
+
181
+ - (std::shared_ptr<const facebook::react::EventEmitter>)lucentEventEmitter
182
+ {
183
+ return _eventEmitter;
184
+ }
185
+
186
+ - (std::unique_ptr<lucent::views::Mounted>)lucentMount:(const facebook::react::Props &)props
187
+ host:(lucent::views::HostView)host
188
+ {
189
+ [NSException raise:NSInternalInconsistencyException
190
+ format:@"%@ does not implement lucentMount:host:", NSStringFromClass([self class])];
191
+ return nullptr;
192
+ }
193
+
194
+ - (std::optional<double>)lucentRequestId:(const std::string &)name args:(const folly::dynamic &)args
195
+ {
196
+ return std::nullopt;
197
+ }
198
+
199
+ - (void)updateProps:(const facebook::react::Props::Shared &)props
200
+ oldProps:(const facebook::react::Props::Shared &)oldProps
201
+ {
202
+ [super updateProps:props oldProps:oldProps];
203
+
204
+ _committed = props;
205
+ }
206
+
207
+ - (void)updateState:(const facebook::react::State::Shared &)state
208
+ oldState:(const facebook::react::State::Shared &)oldState
209
+ {
210
+ [super updateState:state oldState:oldState];
211
+
212
+ _sizing.setState(state);
213
+ _place.setState(state);
214
+ }
215
+
216
+ // A commit's updates all reached the view: mount, or apply the commit to
217
+ // the mount, now that it is committed and on the main thread (entering
218
+ // the mount, the content is measured again once it ran); then measure it
219
+ // if the commit's state asks for new constraints.
220
+ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
221
+ {
222
+ [super finalizeUpdates:updateMask];
223
+
224
+ if (_committed && _committed != _applied) {
225
+ if (!_mounted) {
226
+ [self mountWithProps:_committed];
227
+ } else {
228
+ auto previous = _applied;
229
+
230
+ inMainContext("a component's update", [&] {
231
+ lucent::ui::ContentEntry entry(_sizing.content());
232
+
233
+ _mounted->update(*_committed, *previous);
234
+ });
235
+ _place.contentChanged();
236
+ [_children contentChanged];
237
+ }
238
+
239
+ _applied = _committed;
240
+ [self showMountedView];
241
+ }
242
+
243
+ _sizing.measure();
244
+ }
245
+
246
+ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
247
+ oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics
248
+ {
249
+ [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
250
+ [_children.slot setNeedsLayout];
251
+
252
+ if (layoutMetrics.layoutDirection != oldLayoutMetrics.layoutDirection) [self applyLayoutDirection];
253
+
254
+ if (lucent::views::sizingTraced()) {
255
+ auto frame = layoutMetrics.frame.size;
256
+ auto content = self.contentView.frame.size;
257
+
258
+ lucent::views::traceSizing(
259
+ "%d frame %gx%g content %gx%g", static_cast<int>(self.tag), frame.width, frame.height, content.width, content.height);
260
+ }
261
+ }
262
+
263
+ /** The mount's view's size within `constraints` (sizeThatFits:), or nothing without one. */
264
+ - (std::optional<lucent::sizing::Size>)lucentMeasure:(lucent::sizing::Constraints)constraints
265
+ {
266
+ UIView *content = _mounted ? _mounted->view() : nil;
267
+
268
+ if (!content) return std::nullopt;
269
+
270
+ auto bound = [](float value) { return std::isinf(value) ? CGFLOAT_MAX : static_cast<CGFloat>(value); };
271
+ CGSize fits = [content sizeThatFits:CGSizeMake(bound(constraints.maxWidth), bound(constraints.maxHeight))];
272
+
273
+ return lucent::sizing::Size{static_cast<float>(fits.width), static_cast<float>(fits.height)};
274
+ }
275
+
276
+ - (void)mountWithProps:(const facebook::react::Props::Shared &)props
277
+ {
278
+ _token = lucent::views::MountToken{static_cast<facebook::react::Tag>(self.tag), lucent::views::nextGeneration()};
279
+
280
+ __weak LucentComponentView *weakSelf = self;
281
+
282
+ _sizing.start(
283
+ static_cast<float>(RCTScreenScale()),
284
+ static_cast<int>(self.tag),
285
+ [weakSelf](lucent::sizing::Constraints constraints) -> std::optional<lucent::sizing::Size> {
286
+ LucentComponentView *view = weakSelf;
287
+
288
+ return view ? [view lucentMeasure:constraints] : std::nullopt;
289
+ });
290
+ _place.start(static_cast<float>(RCTScreenScale()), static_cast<int>(self.tag));
291
+ [self.lucentChildren startMount];
292
+
293
+ // Which view the mount is on: a recycled one keeps its address.
294
+ lucent::views::traceSizing("%d on view %p", static_cast<int>(self.tag), (__bridge void *)self);
295
+
296
+ lucent::views::HostView host(self, _token);
297
+
298
+ inMainContext("a component's setup", [&] {
299
+ lucent::ui::ContentEntry entry(_sizing.content());
300
+
301
+ _mounted = [self lucentMount:*props host:host];
302
+ });
303
+ [_children mountedContent:_mounted ? _mounted->view() : nil];
304
+ }
305
+
306
+ /**
307
+ * The mount's view as the content view (none when its setup failed). Its
308
+ * controller, if it has one, becomes a child first, so that it appears
309
+ * with the views around it.
310
+ */
311
+ - (void)showMountedView
312
+ {
313
+ UIView *view = _mounted ? _mounted->view() : nil;
314
+
315
+ [self containController];
316
+
317
+ if (self.contentView == view) return;
318
+
319
+ self.contentView = view;
320
+ [self applyLayoutDirection];
321
+ }
322
+
323
+ // A view controller above the host now: contained before the content goes
324
+ // into a window, so that UIKit calls its viewWillAppear: and viewDidAppear:
325
+ // as its parent's (a pushed screen's is called before the screen's views go
326
+ // into the window).
327
+ - (void)didMoveToSuperview
328
+ {
329
+ [super didMoveToSuperview];
330
+
331
+ [self containController];
332
+ }
333
+
334
+ // The host's ancestors went under a view controller as they went into the window.
335
+ - (void)willMoveToWindow:(UIWindow *)newWindow
336
+ {
337
+ [super willMoveToWindow:newWindow];
338
+
339
+ if (newWindow) [self containController];
340
+ }
341
+
342
+ /** The nearest view controller above the view, if any. */
343
+ - (UIViewController *)lucentParentController
344
+ {
345
+ for (UIResponder *r = self.superview; r; r = r.nextResponder)
346
+ if ([r isKindOfClass:[UIViewController class]]) return (UIViewController *)r;
347
+
348
+ return nil;
349
+ }
350
+
351
+ /**
352
+ * The mount's controller as a child of the nearest view controller above
353
+ * the host, as soon as there is one, so that its parent's appearance,
354
+ * traits and safe area reach it. It stays that child when the host leaves
355
+ * (a popped screen's views leave the window before its viewDidDisappear:),
356
+ * until the host is under another view controller or the mount ends.
357
+ */
358
+ - (void)containController
359
+ {
360
+ UIViewController *controller = _mounted ? _mounted->controller() : nil;
361
+ UIViewController *parent = controller ? [self lucentParentController] : nil;
362
+
363
+ if (!parent || controller.parentViewController == parent) return;
364
+
365
+ [self releaseController:controller];
366
+ [parent addChildViewController:controller];
367
+ [self applyLayoutDirection];
368
+ [controller didMoveToParentViewController:parent];
369
+ }
370
+
371
+ - (void)releaseController:(UIViewController *)controller
372
+ {
373
+ if (!controller.parentViewController) return;
374
+
375
+ [controller willMoveToParentViewController:nil];
376
+ [controller removeFromParentViewController];
377
+ }
378
+
379
+ /**
380
+ * The host's layout direction (Yoga's, which React Native gives its views
381
+ * as their semantic content attribute) as the controller's trait: SwiftUI
382
+ * lays its content out by the trait alone. Before iOS 17, the parent
383
+ * overrides it for its child, once contained.
384
+ */
385
+ - (void)applyLayoutDirection
386
+ {
387
+ UIViewController *controller = _mounted ? _mounted->controller() : nil;
388
+
389
+ if (!controller) return;
390
+
391
+ auto direction = _layoutMetrics.layoutDirection == facebook::react::LayoutDirection::RightToLeft
392
+ ? UITraitEnvironmentLayoutDirectionRightToLeft
393
+ : UITraitEnvironmentLayoutDirectionLeftToRight;
394
+
395
+ if (@available(iOS 17.0, *)) {
396
+ controller.traitOverrides.layoutDirection = direction;
397
+ } else {
398
+ [controller.parentViewController setOverrideTraitCollection:[UITraitCollection traitCollectionWithLayoutDirection:direction]
399
+ forChildViewController:controller];
400
+ }
401
+ }
402
+
403
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
404
+ {
405
+ std::string name = commandName.UTF8String;
406
+
407
+ folly::dynamic arguments = facebook::react::convertIdToFollyDynamic(args);
408
+ auto requester = lucent::views::Requester::current();
409
+
410
+ if (!_mounted) {
411
+ std::string problem = "command " + name + " reached a view with no mount (its setup failed, or it is not mounted yet)";
412
+
413
+ // A request would otherwise never settle while the view stays.
414
+ if (auto id = [self lucentRequestId:name args:arguments])
415
+ requester.reject(*id, problem);
416
+ else
417
+ lucent::logError(("[lucent] " + problem).c_str());
418
+
419
+ return;
420
+ }
421
+
422
+ inMainContext("a component's command", [&] {
423
+ lucent::ui::ContentEntry entry(_sizing.content());
424
+
425
+ _mounted->command(name, arguments, requester);
426
+ });
427
+
428
+ // A command may move the slot.
429
+ _place.contentChanged();
430
+ [_children contentChanged];
431
+ }
432
+
433
+ // The view goes back to the renderer's pool: its mount ends here, and a
434
+ // later occupant gets a mount (and token) of its own.
435
+ - (void)prepareForRecycle
436
+ {
437
+ UIViewController *controller = _mounted ? _mounted->controller() : nil;
438
+ BOOL contained = controller.parentViewController != nil;
439
+
440
+ // Out of its parent in UIKit's order: told, its view gone (disappearing
441
+ // as its parent's child, if in a window), then removed.
442
+ if (contained) [controller willMoveToParentViewController:nil];
443
+
444
+ self.contentView = nil;
445
+
446
+ if (contained) [controller removeFromParentViewController];
447
+
448
+ _token = lucent::views::MountToken{};
449
+ [_children endMount];
450
+ // Its teardown runs the mount's code: nothing to measure any more.
451
+ _sizing.stop();
452
+
453
+ auto mounted = std::move(_mounted);
454
+
455
+ inMainContext("a component's teardown", [&] { mounted.reset(); });
456
+
457
+ _committed = nullptr;
458
+ _applied = nullptr;
459
+ _place = {};
460
+
461
+ [super prepareForRecycle];
462
+ }
463
+
464
+ - (void)dealloc
465
+ {
466
+ _sizing.stop();
467
+
468
+ if (_mounted) {
469
+ [self releaseController:_mounted->controller()];
470
+
471
+ auto mounted = std::move(_mounted);
472
+
473
+ inMainContext("a component's teardown", [&] { mounted.reset(); });
474
+ }
475
+ }
476
+
477
+ @end
478
+
479
+ #endif
@@ -3,6 +3,8 @@
3
3
  #include <lucent/jsi/host.h>
4
4
  #include <lucent/native.h>
5
5
 
6
+ #include "LucentViewRequests.h"
7
+
6
8
  #include <string>
7
9
 
8
10
  namespace facebook::react {
@@ -11,12 +13,18 @@ LucentModule::LucentModule(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule
11
13
 
12
14
  LucentModule::~LucentModule() {
13
15
  #ifndef NDEBUG
14
- // Platform objects Lucent still holds when the module goes (a reload, the
15
- // app's end): delegates and listeners never removed, cycles through them.
16
- if (long n = lucent::liveNativeRefs()) {
17
- std::string message = "[lucent] " + std::to_string(n) + " native reference(s) still held at teardown";
18
- lucent::logError(message.c_str());
19
- }
16
+ // Platform objects module code still holds when the module goes (a
17
+ // reload, the app's end): module state, delegates and listeners never
18
+ // removed, cycles through them. Not the views': the renderer releases
19
+ // them with its views, and the platform's garbage collector the
20
+ // callbacks their setups gave it, on their own schedule. Counted once
21
+ // the module context has run what the teardown left it.
22
+ lucent::postCallback([] {
23
+ if (long n = lucent::moduleNativeRefs()) {
24
+ std::string message = "[lucent] " + std::to_string(n) + " native reference(s) module code still holds at teardown";
25
+ lucent::logError(message.c_str());
26
+ }
27
+ });
20
28
  #endif
21
29
  }
22
30
 
@@ -29,7 +37,29 @@ jsi::Value LucentModule::create(jsi::Runtime& runtime, const jsi::PropNameID& pr
29
37
  }
30
38
  });
31
39
  }
32
- return host_->module(runtime, propName.utf8(runtime));
40
+ auto name = propName.utf8(runtime);
41
+
42
+ if (name == lucent::views::kRequestsName) return requestsConnector(runtime);
43
+
44
+ return host_->module(runtime, name);
45
+ }
46
+
47
+ jsi::Value LucentModule::requestsConnector(jsi::Runtime& runtime) {
48
+ std::weak_ptr<lucent::js::Host> weak = host_;
49
+
50
+ return jsi::Function::createFromHostFunction(
51
+ runtime,
52
+ jsi::PropNameID::forAscii(runtime, lucent::views::kRequestsName),
53
+ 1,
54
+ [weak](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
55
+ auto host = weak.lock();
56
+
57
+ if (!host || count < 1 || !args[0].isObject() || !args[0].asObject(rt).isFunction(rt))
58
+ throw jsi::JSError(rt, "Lucent: __lucentViewRequests takes the function that settles view requests");
59
+
60
+ lucent::views::connectRequests(host, rt, args[0].asObject(rt).asFunction(rt));
61
+ return jsi::Value::undefined();
62
+ });
33
63
  }
34
64
 
35
65
  } // namespace facebook::react
@@ -31,6 +31,9 @@ class LucentModule : public TurboModule {
31
31
  jsi::Value create(jsi::Runtime& runtime, const jsi::PropNameID& propName) override;
32
32
 
33
33
  private:
34
+ /// What JavaScript connects its view requests with (lucent::views::connectRequests).
35
+ jsi::Value requestsConnector(jsi::Runtime& runtime);
36
+
34
37
  std::shared_ptr<lucent::js::Host> host_;
35
38
  };
36
39
 
@@ -0,0 +1,80 @@
1
+ // Lucent runtime — the React children of a Lucent component on iOS, and
2
+ // the view they are mounted in: its slot (lucent:ui's `slot`).
3
+ //
4
+ // Who owns what:
5
+ //
6
+ // - React Native owns each child: it creates, updates, lays out (the
7
+ // child's frame, in the component's coordinates, as Yoga computed it)
8
+ // and destroys it, and says where it goes (mount or unmount at an index
9
+ // of the component's children).
10
+ // - The host (LucentComponentView, through LucentChildren) owns the slot
11
+ // and the children's place in it: it makes a slot for each mount, puts
12
+ // every child React Native mounts in it in React Native's order, takes
13
+ // out the ones React Native unmounts, and does nothing else to them.
14
+ // - The setup owns the slot's place: its ancestors, its z-order among its
15
+ // siblings, and its frame (it fills the view it is added to unless the
16
+ // setup sizes it).
17
+ //
18
+ // The slot shows its children in the component's coordinates wherever it
19
+ // is: its bounds' origin is its own origin in the host, so a child shows
20
+ // at the frame Yoga gave it. It realigns when it moves or resizes, when the
21
+ // host lays out, and after each commit and command the mount applies (UIKit
22
+ // tells a view nothing when an ancestor moves without resizing it), and
23
+ // then tells the host it was placed: the host reports where it is, so that
24
+ // Yoga lays the children out in its rectangle (LucentViewSlots.h). It
25
+ // clips its children to its bounds, so a child shows, and receives
26
+ // touches, where it falls inside the slot. It never lays them out.
27
+ #pragma once
28
+
29
+ #import <UIKit/UIKit.h>
30
+
31
+ /** A component's slot. Main thread. */
32
+ @interface LucentSlotView : UIView
33
+
34
+ /// A slot for the React children of a component shown by `host`, which
35
+ /// calls `placed` each time it has laid itself out.
36
+ - (instancetype)initWithHost:(UIView *)host placed:(void (^)(void))placed;
37
+
38
+ /// Shows its children where Yoga put them, as its place in the host is now.
39
+ - (void)lucentAlign;
40
+
41
+ @end
42
+
43
+ /**
44
+ * One host view's React children, in React Native's order, and the slot
45
+ * of its current mount. Main thread.
46
+ */
47
+ @interface LucentChildren : NSObject
48
+
49
+ /// The children of `host`, a view of the component registered as `name`,
50
+ /// which takes React children if `takesChildren`; each slot calls `placed`
51
+ /// after it lays itself out.
52
+ - (instancetype)initWithHost:(UIView *)host
53
+ name:(NSString *)name
54
+ takesChildren:(BOOL)takesChildren
55
+ placed:(void (^)(void))placed;
56
+
57
+ /// The current mount's slot, or nil (no mount, or no children taken).
58
+ @property (nonatomic, readonly) LucentSlotView *slot;
59
+
60
+ /// React Native mounts `child` at `index` of the component's children.
61
+ - (void)mount:(UIView *)child index:(NSInteger)index;
62
+
63
+ /// React Native unmounts `child`, at `index` of the component's children.
64
+ - (void)unmount:(UIView *)child index:(NSInteger)index;
65
+
66
+ /// A mount starts: a new slot holding every child, if the component takes them.
67
+ - (void)startMount;
68
+
69
+ /// The mount's setup returned `content` (nil if it failed): its slot should be in it.
70
+ - (void)mountedContent:(UIView *)content;
71
+
72
+ /// The mount's code ran (a commit, a command): its native views may have
73
+ /// moved the slot, which UIKit does not tell it; it realigns at the next
74
+ /// layout.
75
+ - (void)contentChanged;
76
+
77
+ /// The mount ends (the view is recycled): its slot goes.
78
+ - (void)endMount;
79
+
80
+ @end