@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.
- package/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
- package/dist/cli.js +62 -13
- package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
- package/dist/compiler.js +4 -4
- package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
- package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
- package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
- package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
- package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
- package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
- package/dist/pipeline-BM_VM2X_.js +700 -0
- package/dist/project-BKNoTEEp.js +342 -0
- package/dist/sdk-coverage-iiv3JqMp.js +113 -0
- package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
- package/dist/sdk-lock-BQn7IyRs.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
- package/dist/sdk-usage-0ZFMAYg1.js +95 -0
- package/dist/src-ChnmWLwn.js +40851 -0
- package/dist/trace-Bi4hkmPx.js +139 -0
- package/lib/core.cjs +368 -1
- package/lib/sdk/android.d.ts +54 -1
- package/lib/sdk/compose.d.ts +94 -0
- package/lib/sdk/compose.schemas.json.gz +0 -0
- package/lib/sdk/core.d.ts +201 -0
- package/lib/sdk/ios.d.ts +75 -2
- package/lib/sdk/ui.d.ts +129 -0
- package/metro/transformer.cjs +11 -2
- package/package.json +4 -3
- package/runtime/cpp/lucent/abort.cpp +59 -11
- package/runtime/cpp/lucent/abort.h +22 -6
- package/runtime/cpp/lucent/array.h +12 -2
- package/runtime/cpp/lucent/async.h +101 -21
- package/runtime/cpp/lucent/bigint.cpp +898 -0
- package/runtime/cpp/lucent/bigint.h +302 -0
- package/runtime/cpp/lucent/buffer.cpp +228 -0
- package/runtime/cpp/lucent/buffer.h +328 -0
- package/runtime/cpp/lucent/builtins.cpp +4 -1
- package/runtime/cpp/lucent/bytes.h +21 -2
- package/runtime/cpp/lucent/callback.cpp +29 -0
- package/runtime/cpp/lucent/callback.h +230 -0
- package/runtime/cpp/lucent/compute.cpp +370 -0
- package/runtime/cpp/lucent/compute.h +455 -0
- package/runtime/cpp/lucent/conversions.h +111 -0
- package/runtime/cpp/lucent/core.h +16 -5
- package/runtime/cpp/lucent/equality.h +107 -28
- package/runtime/cpp/lucent/execution.cpp +416 -0
- package/runtime/cpp/lucent/execution.h +270 -0
- package/runtime/cpp/lucent/extension.cpp +97 -0
- package/runtime/cpp/lucent/extension.h +210 -0
- package/runtime/cpp/lucent/helpers.h +12 -5
- package/runtime/cpp/lucent/items.h +52 -0
- package/runtime/cpp/lucent/jserror.cpp +9 -0
- package/runtime/cpp/lucent/jserror.h +11 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
- package/runtime/cpp/lucent/jsi/convert.h +128 -59
- package/runtime/cpp/lucent/jsi/host.cpp +213 -35
- package/runtime/cpp/lucent/jsi/host.h +116 -16
- package/runtime/cpp/lucent/json.h +19 -1
- package/runtime/cpp/lucent/lifecycle.cpp +260 -0
- package/runtime/cpp/lucent/lifecycle.h +124 -0
- package/runtime/cpp/lucent/lucent.h +7 -0
- package/runtime/cpp/lucent/map.h +41 -7
- package/runtime/cpp/lucent/native.cpp +35 -47
- package/runtime/cpp/lucent/native.h +116 -26
- package/runtime/cpp/lucent/number.cpp +38 -38
- package/runtime/cpp/lucent/number.h +33 -0
- package/runtime/cpp/lucent/operation.h +65 -0
- package/runtime/cpp/lucent/ops.h +14 -6
- package/runtime/cpp/lucent/platform/android.cpp +223 -10
- package/runtime/cpp/lucent/platform/android.h +227 -4
- package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
- package/runtime/cpp/lucent/platform/android_requests.h +140 -0
- package/runtime/cpp/lucent/platform/compose.h +256 -0
- package/runtime/cpp/lucent/platform/ios.h +325 -25
- package/runtime/cpp/lucent/platform/ios.mm +1 -14
- package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
- package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
- package/runtime/cpp/lucent/platform/swiftui.h +190 -0
- package/runtime/cpp/lucent/presentation.cpp +11 -0
- package/runtime/cpp/lucent/presentation.h +109 -0
- package/runtime/cpp/lucent/reactive.cpp +406 -0
- package/runtime/cpp/lucent/reactive.h +566 -0
- package/runtime/cpp/lucent/report.cpp +40 -0
- package/runtime/cpp/lucent/report.h +16 -0
- package/runtime/cpp/lucent/resource.cpp +83 -0
- package/runtime/cpp/lucent/resource.h +75 -0
- package/runtime/cpp/lucent/scheduler.cpp +38 -124
- package/runtime/cpp/lucent/scheduler.h +102 -83
- package/runtime/cpp/lucent/scope.cpp +308 -0
- package/runtime/cpp/lucent/scope.h +452 -0
- package/runtime/cpp/lucent/sizing.h +263 -0
- package/runtime/cpp/lucent/slots.h +231 -0
- package/runtime/cpp/lucent/trace.cpp +539 -0
- package/runtime/cpp/lucent/trace.h +251 -0
- package/runtime/cpp/lucent/transport.cpp +9 -0
- package/runtime/cpp/lucent/transport.h +341 -0
- package/runtime/cpp/lucent/view.cpp +123 -0
- package/runtime/cpp/lucent/view.h +157 -0
- package/runtime/cpp/rn/LucentComponentView.h +121 -0
- package/runtime/cpp/rn/LucentComponentView.mm +479 -0
- package/runtime/cpp/rn/LucentModule.cpp +37 -7
- package/runtime/cpp/rn/LucentModule.h +3 -0
- package/runtime/cpp/rn/LucentViewChildren.h +80 -0
- package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
- package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
- package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
- package/runtime/cpp/rn/LucentViewRequests.h +104 -0
- package/runtime/cpp/rn/LucentViewSizing.h +345 -0
- package/runtime/cpp/rn/LucentViewSlots.h +242 -0
- package/runtime/cpp/rn/LucentViewValues.h +125 -0
- package/runtime/cpp/rn/LucentViews.h +440 -0
- package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
- package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
- package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
- package/runtime/cpp/third_party/dragonbox/README.md +16 -0
- package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
- package/runtime/js/index.d.ts +11 -1
- package/runtime/js/index.js +84 -8
- package/runtime/js/views.d.ts +38 -0
- package/runtime/js/views.js +226 -0
- package/runtime/native/android/CMakeLists.txt +4 -1
- package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
- package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
- package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
- package/runtime/test/jsi/harness.cpp +9 -0
- package/schemas/build.schema.json +96 -1
- package/schemas/check.schema.json +56 -1
- package/schemas/lucent.schema.json +495 -9
- package/schemas/sdk-diff.schema.json +70 -0
- package/schemas/sdk-lock.schema.json +67 -0
- package/ts-plugin/index.js +85 -4
- package/dist/pipeline-CH7-2cxu.js +0 -232
- package/dist/project-D8tEet0k.js +0 -150
- package/dist/sdk-coverage-BoW_3Efd.js +0 -64
- 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
|
|
15
|
-
// app's end): delegates and listeners never
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|