react-native-platform-components 0.0.2

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 (63) hide show
  1. package/LICENSE +20 -0
  2. package/PlatformComponents.podspec +20 -0
  3. package/README.md +233 -0
  4. package/android/build.gradle +78 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/platformcomponents/DateConstraints.kt +83 -0
  8. package/android/src/main/java/com/platformcomponents/Helper.kt +27 -0
  9. package/android/src/main/java/com/platformcomponents/PCDatePickerView.kt +684 -0
  10. package/android/src/main/java/com/platformcomponents/PCDatePickerViewManager.kt +149 -0
  11. package/android/src/main/java/com/platformcomponents/PCMaterialMode.kt +16 -0
  12. package/android/src/main/java/com/platformcomponents/PCSelectionMenuView.kt +410 -0
  13. package/android/src/main/java/com/platformcomponents/PCSelectionMenuViewManager.kt +114 -0
  14. package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +22 -0
  15. package/ios/PCDatePicker.h +11 -0
  16. package/ios/PCDatePicker.mm +248 -0
  17. package/ios/PCDatePickerView.swift +405 -0
  18. package/ios/PCSelectionMenu.h +10 -0
  19. package/ios/PCSelectionMenu.mm +182 -0
  20. package/ios/PCSelectionMenu.swift +434 -0
  21. package/lib/module/DatePicker.js +74 -0
  22. package/lib/module/DatePicker.js.map +1 -0
  23. package/lib/module/DatePickerNativeComponent.ts +68 -0
  24. package/lib/module/SelectionMenu.js +79 -0
  25. package/lib/module/SelectionMenu.js.map +1 -0
  26. package/lib/module/SelectionMenu.web.js +57 -0
  27. package/lib/module/SelectionMenu.web.js.map +1 -0
  28. package/lib/module/SelectionMenuNativeComponent.ts +106 -0
  29. package/lib/module/index.js +6 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/package.json +1 -0
  32. package/lib/module/sharedTypes.js +4 -0
  33. package/lib/module/sharedTypes.js.map +1 -0
  34. package/lib/typescript/package.json +1 -0
  35. package/lib/typescript/src/DatePicker.d.ts +38 -0
  36. package/lib/typescript/src/DatePicker.d.ts.map +1 -0
  37. package/lib/typescript/src/DatePickerNativeComponent.d.ts +53 -0
  38. package/lib/typescript/src/DatePickerNativeComponent.d.ts.map +1 -0
  39. package/lib/typescript/src/SelectionMenu.d.ts +50 -0
  40. package/lib/typescript/src/SelectionMenu.d.ts.map +1 -0
  41. package/lib/typescript/src/SelectionMenu.web.d.ts +19 -0
  42. package/lib/typescript/src/SelectionMenu.web.d.ts.map +1 -0
  43. package/lib/typescript/src/SelectionMenuNativeComponent.d.ts +85 -0
  44. package/lib/typescript/src/SelectionMenuNativeComponent.d.ts.map +1 -0
  45. package/lib/typescript/src/index.d.ts +4 -0
  46. package/lib/typescript/src/index.d.ts.map +1 -0
  47. package/lib/typescript/src/sharedTypes.d.ts +10 -0
  48. package/lib/typescript/src/sharedTypes.d.ts.map +1 -0
  49. package/package.json +178 -0
  50. package/shared/PCDatePickerComponentDescriptors-custom.h +52 -0
  51. package/shared/PCDatePickerShadowNode-custom.cpp +1 -0
  52. package/shared/PCDatePickerShadowNode-custom.h +27 -0
  53. package/shared/PCDatePickerState-custom.h +13 -0
  54. package/shared/PCSelectionMenuComponentDescriptors-custom.h +25 -0
  55. package/shared/PCSelectionMenuShadowNode-custom.cpp +36 -0
  56. package/shared/PCSelectionMenuShadowNode-custom.h +46 -0
  57. package/src/DatePicker.tsx +146 -0
  58. package/src/DatePickerNativeComponent.ts +68 -0
  59. package/src/SelectionMenu.tsx +170 -0
  60. package/src/SelectionMenu.web.tsx +93 -0
  61. package/src/SelectionMenuNativeComponent.ts +106 -0
  62. package/src/index.tsx +3 -0
  63. package/src/sharedTypes.ts +14 -0
@@ -0,0 +1,182 @@
1
+ // PCSelectionMenu.mm
2
+
3
+ #import "PCSelectionMenu.h"
4
+
5
+ #import <React/RCTComponentViewFactory.h>
6
+ #import <React/RCTConversions.h>
7
+ #import <React/RCTFabricComponentsPlugins.h>
8
+
9
+ #import <react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h>
10
+ #import <react/renderer/components/PlatformComponentsViewSpec/EventEmitters.h>
11
+ #import <react/renderer/components/PlatformComponentsViewSpec/Props.h>
12
+
13
+ #if __has_include(<PlatformComponents/PlatformComponents-Swift.h>)
14
+ #import <PlatformComponents/PlatformComponents-Swift.h>
15
+ #else
16
+ #import "PlatformComponents-Swift.h"
17
+ #endif
18
+
19
+ #import "PCSelectionMenuComponentDescriptors-custom.h"
20
+
21
+ using namespace facebook::react;
22
+
23
+ namespace {
24
+ static inline bool OptionsEqual(
25
+ const std::vector<facebook::react::PCSelectionMenuOptionsStruct> &a,
26
+ const std::vector<facebook::react::PCSelectionMenuOptionsStruct> &b) {
27
+ if (a.size() != b.size()) return false;
28
+ for (size_t i = 0; i < a.size(); i++) {
29
+ if (a[i].label != b[i].label) return false;
30
+ if (a[i].data != b[i].data) return false;
31
+ }
32
+ return true;
33
+ }
34
+ } // namespace
35
+
36
+ @implementation PCSelectionMenu {
37
+ PCSelectionMenuView *_view;
38
+ }
39
+
40
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
41
+ return concreteComponentDescriptorProvider<
42
+ MeasuringPCSelectionMenuComponentDescriptor>();
43
+ }
44
+
45
+ - (instancetype)initWithFrame:(CGRect)frame {
46
+ if (self = [super initWithFrame:frame]) {
47
+ _view = [PCSelectionMenuView new];
48
+ self.contentView = _view;
49
+
50
+ __weak __typeof(self) weakSelf = self;
51
+
52
+ _view.onSelect = ^(NSInteger index, NSString *label, NSString *data) {
53
+ __typeof(self) strongSelf = weakSelf;
54
+ if (!strongSelf) return;
55
+
56
+ auto eventEmitter =
57
+ std::static_pointer_cast<const PCSelectionMenuEventEmitter>(
58
+ strongSelf->_eventEmitter);
59
+ if (!eventEmitter) return;
60
+
61
+ PCSelectionMenuEventEmitter::OnSelect payload = {
62
+ .index = (int)index,
63
+ .label = label.UTF8String,
64
+ .data = data.UTF8String,
65
+ };
66
+
67
+ eventEmitter->onSelect(payload);
68
+ };
69
+
70
+ _view.onRequestClose = ^{
71
+ __typeof(self) strongSelf = weakSelf;
72
+ if (!strongSelf) return;
73
+
74
+ auto eventEmitter =
75
+ std::static_pointer_cast<const PCSelectionMenuEventEmitter>(
76
+ strongSelf->_eventEmitter);
77
+ if (!eventEmitter) return;
78
+
79
+ eventEmitter->onRequestClose({});
80
+ };
81
+ }
82
+ return self;
83
+ }
84
+
85
+ - (void)updateProps:(Props::Shared const &)props
86
+ oldProps:(Props::Shared const &)oldProps {
87
+ const auto &newProps =
88
+ *std::static_pointer_cast<const PCSelectionMenuProps>(props);
89
+ const auto prevProps =
90
+ std::static_pointer_cast<const PCSelectionMenuProps>(oldProps);
91
+
92
+ // options: [{label,data}]
93
+ if (!prevProps || !OptionsEqual(newProps.options, prevProps->options)) {
94
+ NSMutableArray *arr = [NSMutableArray new];
95
+ for (const auto &opt : newProps.options) {
96
+ NSString *label = opt.label.empty()
97
+ ? @""
98
+ : [NSString stringWithUTF8String:opt.label.c_str()];
99
+ NSString *data = opt.data.empty()
100
+ ? @""
101
+ : [NSString stringWithUTF8String:opt.data.c_str()];
102
+ [arr addObject:@{@"label" : label, @"data" : data}];
103
+ }
104
+ _view.options = arr;
105
+ }
106
+
107
+ // selectedData (default "")
108
+ if (!prevProps || newProps.selectedData != prevProps->selectedData) {
109
+ if (!newProps.selectedData.empty()) {
110
+ _view.selectedData =
111
+ [NSString stringWithUTF8String:newProps.selectedData.c_str()];
112
+ } else {
113
+ _view.selectedData = @""; // sentinel
114
+ }
115
+ }
116
+
117
+ // interactivity: "enabled" | "disabled"
118
+ if (!prevProps || newProps.interactivity != prevProps->interactivity) {
119
+ if (!newProps.interactivity.empty()) {
120
+ _view.interactivity =
121
+ [NSString stringWithUTF8String:newProps.interactivity.c_str()];
122
+ } else {
123
+ _view.interactivity = @"enabled";
124
+ }
125
+ }
126
+
127
+ // placeholder
128
+ if (!prevProps || newProps.placeholder != prevProps->placeholder) {
129
+ if (!newProps.placeholder.empty()) {
130
+ _view.placeholder =
131
+ [NSString stringWithUTF8String:newProps.placeholder.c_str()];
132
+ } else {
133
+ _view.placeholder = nil;
134
+ }
135
+ }
136
+
137
+ // anchorMode: "inline" | "headless"
138
+ if (!prevProps || newProps.anchorMode != prevProps->anchorMode) {
139
+ if (!newProps.anchorMode.empty()) {
140
+ _view.anchorMode =
141
+ [NSString stringWithUTF8String:newProps.anchorMode.c_str()];
142
+ } else {
143
+ _view.anchorMode = @"headless";
144
+ }
145
+ }
146
+
147
+ // presentation: "auto" | "popover" | "sheet"
148
+ if (!prevProps || newProps.presentation != prevProps->presentation) {
149
+ if (!newProps.presentation.empty()) {
150
+ _view.presentation =
151
+ [NSString stringWithUTF8String:newProps.presentation.c_str()];
152
+ } else {
153
+ _view.presentation = @"auto";
154
+ }
155
+ }
156
+
157
+ // visible: "open" | "closed"
158
+ if (!prevProps || newProps.visible != prevProps->visible) {
159
+ if (!newProps.visible.empty()) {
160
+ _view.visible = [NSString stringWithUTF8String:newProps.visible.c_str()];
161
+ } else {
162
+ _view.visible = @"closed";
163
+ }
164
+ }
165
+
166
+ // android.material (plumbed through; iOS can ignore)
167
+ const auto &newAndroid = newProps.android;
168
+ const auto &oldAndroid =
169
+ prevProps ? prevProps->android : PCSelectionMenuAndroidStruct{};
170
+ if (!prevProps || newAndroid.material != oldAndroid.material) {
171
+ if (!newAndroid.material.empty()) {
172
+ _view.androidMaterial =
173
+ [NSString stringWithUTF8String:newAndroid.material.c_str()];
174
+ } else {
175
+ _view.androidMaterial = nil;
176
+ }
177
+ }
178
+
179
+ [super updateProps:props oldProps:oldProps];
180
+ }
181
+
182
+ @end
@@ -0,0 +1,434 @@
1
+ import SwiftUI
2
+ import UIKit
3
+
4
+ // MARK: - Option model (bridged from ObjC++ as dictionaries)
5
+
6
+ struct PCSelectionMenuOption {
7
+ let label: String
8
+ let data: String
9
+ }
10
+
11
+ final class PCSelectionMenuModel: ObservableObject {
12
+ @Published var options: [PCSelectionMenuOption] = []
13
+ @Published var selectedData: String = "" // sentinel = no selection
14
+ @Published var placeholder: String = "Select"
15
+ @Published var interactivity: String = "enabled" // "enabled" | "disabled"
16
+
17
+ var isDisabled: Bool { interactivity == "disabled" }
18
+
19
+ var displayTitle: String {
20
+ if let opt = options.first(where: { $0.data == selectedData }), !selectedData.isEmpty {
21
+ return opt.label
22
+ }
23
+ return placeholder
24
+ }
25
+
26
+ var hasOptions: Bool { !options.isEmpty }
27
+ }
28
+
29
+ private struct PCSelectionMenuInlinePickerView: View {
30
+ @ObservedObject var model: PCSelectionMenuModel
31
+ let onSelectIndex: (Int) -> Void
32
+
33
+ var body: some View {
34
+ Menu {
35
+ ForEach(Array(model.options.enumerated()), id: \.offset) { i, opt in
36
+ Button(opt.label) { onSelectIndex(i) }
37
+ }
38
+ } label: {
39
+ HStack(spacing: 8) {
40
+ Text(model.displayTitle)
41
+ .lineLimit(1)
42
+ .truncationMode(.tail)
43
+
44
+ Spacer(minLength: 0)
45
+
46
+ Image(systemName: "chevron.up.chevron.down")
47
+ .imageScale(.small)
48
+ .opacity(0.7)
49
+ }
50
+ .frame(maxWidth: .infinity, alignment: .leading)
51
+ .contentShape(Rectangle())
52
+ .padding(.vertical, 10)
53
+ }
54
+ .disabled(model.isDisabled || !model.hasOptions)
55
+ }
56
+ }
57
+
58
+ @objcMembers
59
+ public final class PCSelectionMenuView: UIControl,
60
+ UIPopoverPresentationControllerDelegate,
61
+ UIAdaptivePresentationControllerDelegate
62
+ {
63
+ // MARK: - Props (set from ObjC++)
64
+
65
+ /// ObjC++ sets this as an array of dictionaries: [{label,data}]
66
+ public var options: [Any] = [] { didSet { sync() } }
67
+
68
+ /// Controlled selection by data. "" = no selection.
69
+ public var selectedData: String = "" { didSet { sync() } }
70
+
71
+ /// "enabled" | "disabled"
72
+ public var interactivity: String = "enabled" {
73
+ didSet {
74
+ updateEnabled()
75
+ sync()
76
+ }
77
+ }
78
+
79
+ public var placeholder: String? { didSet { sync() } }
80
+
81
+ /// "open" | "closed" (headless only)
82
+ public var visible: String = "closed" { didSet { updatePresentation() } }
83
+
84
+ /// "auto" | "popover" | "sheet" (headless only)
85
+ public var presentation: String = "auto" { didSet { updatePresentation() } }
86
+
87
+ /// "inline" | "headless"
88
+ public var anchorMode: String = "headless" { didSet { updateAnchorMode() } }
89
+
90
+ /// Android material preference (ignored on iOS; retained for debugging/log parity)
91
+ public var androidMaterial: String? = nil
92
+
93
+ // MARK: - Events back to ObjC++
94
+
95
+ public var onSelect: ((Int, String, String) -> Void)? // (index,label,data)
96
+ public var onRequestClose: (() -> Void)?
97
+
98
+ // MARK: - Internal
99
+
100
+ private weak var presentedVC: UIViewController?
101
+
102
+ private let model = PCSelectionMenuModel()
103
+ private var hostingController: UIHostingController<PCSelectionMenuInlinePickerView>?
104
+
105
+ private var parsedOptions: [PCSelectionMenuOption] {
106
+ options.compactMap { any in
107
+ guard let dict = any as? [String: Any] else { return nil }
108
+ let label = (dict["label"] as? String) ?? ""
109
+ let data = (dict["data"] as? String) ?? ""
110
+ return PCSelectionMenuOption(label: label, data: data)
111
+ }
112
+ }
113
+
114
+ // MARK: - Init
115
+
116
+ public override init(frame: CGRect) {
117
+ super.init(frame: frame)
118
+ setup()
119
+ }
120
+
121
+ public required init?(coder: NSCoder) {
122
+ super.init(coder: coder)
123
+ setup()
124
+ }
125
+
126
+ private func setup() {
127
+ backgroundColor = .clear
128
+ updateEnabled()
129
+ updateAnchorMode()
130
+ sync()
131
+ }
132
+
133
+ private func updateEnabled() {
134
+ let disabled = (interactivity == "disabled")
135
+ alpha = disabled ? 0.5 : 1.0
136
+ isUserInteractionEnabled = !disabled
137
+ accessibilityTraits = disabled ? [.notEnabled] : [.button]
138
+ }
139
+
140
+ // MARK: - Inline vs headless
141
+
142
+ private func updateAnchorMode() {
143
+ if anchorMode == "inline" {
144
+ dismissIfNeeded(emitClose: false)
145
+ installInlineIfNeeded()
146
+ sync()
147
+ } else {
148
+ uninstallInlineIfNeeded()
149
+ updatePresentation()
150
+ }
151
+ }
152
+
153
+ private func sync() {
154
+ model.options = parsedOptions
155
+ model.selectedData = selectedData
156
+ model.placeholder = placeholder ?? "Select"
157
+ model.interactivity = interactivity
158
+
159
+ if anchorMode == "inline" {
160
+ hostingController?.rootView = makeInlineRootView()
161
+ }
162
+
163
+ invalidateIntrinsicContentSize()
164
+ setNeedsLayout()
165
+ }
166
+
167
+ private func makeInlineRootView() -> PCSelectionMenuInlinePickerView {
168
+ PCSelectionMenuInlinePickerView(
169
+ model: model,
170
+ onSelectIndex: { [weak self] idx in
171
+ guard let self else { return }
172
+ let opts = self.parsedOptions
173
+ guard idx >= 0, idx < opts.count else { return }
174
+ let opt = opts[idx]
175
+ self.selectedData = opt.data
176
+ self.onSelect?(idx, opt.label, opt.data)
177
+ }
178
+ )
179
+ }
180
+
181
+ private func installInlineIfNeeded() {
182
+ guard hostingController == nil else { return }
183
+
184
+ let host = UIHostingController(rootView: makeInlineRootView())
185
+ host.view.translatesAutoresizingMaskIntoConstraints = false
186
+ host.view.backgroundColor = .clear
187
+
188
+ addSubview(host.view)
189
+ NSLayoutConstraint.activate([
190
+ host.view.topAnchor.constraint(equalTo: topAnchor),
191
+ host.view.bottomAnchor.constraint(equalTo: bottomAnchor),
192
+ host.view.leadingAnchor.constraint(equalTo: leadingAnchor),
193
+ host.view.trailingAnchor.constraint(equalTo: trailingAnchor),
194
+ ])
195
+
196
+ if let parent = nearestViewController() {
197
+ parent.addChild(host)
198
+ host.didMove(toParent: parent)
199
+ }
200
+
201
+ hostingController = host
202
+ }
203
+
204
+ private func uninstallInlineIfNeeded() {
205
+ guard let host = hostingController else { return }
206
+ hostingController = nil
207
+
208
+ host.willMove(toParent: nil)
209
+ host.view.removeFromSuperview()
210
+ host.removeFromParent()
211
+ }
212
+
213
+ private func nearestViewController() -> UIViewController? {
214
+ var r: UIResponder? = self
215
+ while let next = r?.next {
216
+ if let vc = next as? UIViewController { return vc }
217
+ r = next
218
+ }
219
+ return nil
220
+ }
221
+
222
+ // MARK: - Headless presentation
223
+
224
+ private func updatePresentation() {
225
+ guard anchorMode != "inline" else { return }
226
+ guard interactivity != "disabled" else { return }
227
+
228
+ if visible == "open" {
229
+ presentIfNeeded()
230
+ } else {
231
+ dismissIfNeeded(emitClose: false)
232
+ }
233
+ }
234
+
235
+ private func presentIfNeeded() {
236
+ guard presentedVC == nil else { return }
237
+ guard let top = topViewController() else { return }
238
+
239
+ let opts = parsedOptions
240
+ let list = PCSelectionMenuListViewController(
241
+ titleText: placeholder ?? "Select",
242
+ options: opts,
243
+ selectedData: selectedData,
244
+ onSelect: { [weak self] idx in
245
+ guard let self else { return }
246
+ guard idx >= 0, idx < opts.count else { return }
247
+ let opt = opts[idx]
248
+ self.selectedData = opt.data
249
+ self.onSelect?(idx, opt.label, opt.data)
250
+ self.dismissIfNeeded(emitClose: true)
251
+ },
252
+ onCancel: { [weak self] in
253
+ self?.dismissIfNeeded(emitClose: true)
254
+ }
255
+ )
256
+
257
+ let nav = UINavigationController(rootViewController: list)
258
+ let style = resolvedModalStyle(for: top)
259
+ nav.modalPresentationStyle = style
260
+
261
+ if style == .popover {
262
+ nav.preferredContentSize = list.computePreferredSize()
263
+
264
+ if let pop = nav.popoverPresentationController {
265
+ pop.delegate = self
266
+ pop.sourceView = top.view
267
+ let rectInTopView = self.convert(self.bounds, to: top.view)
268
+ pop.sourceRect = rectInTopView
269
+ pop.permittedArrowDirections = [.up, .down]
270
+ pop.backgroundColor = list.view.backgroundColor
271
+ }
272
+ } else {
273
+ nav.modalPresentationStyle = .pageSheet
274
+ nav.presentationController?.delegate = self
275
+ }
276
+
277
+ nav.presentationController?.delegate = self
278
+
279
+ presentedVC = nav
280
+ top.present(nav, animated: true)
281
+ }
282
+
283
+ private func dismissIfNeeded(emitClose: Bool) {
284
+ guard let vc = presentedVC else { return }
285
+ presentedVC = nil
286
+ vc.dismiss(animated: true) { [weak self] in
287
+ guard let self else { return }
288
+ if emitClose { self.onRequestClose?() }
289
+ }
290
+ }
291
+
292
+ private func resolvedModalStyle(for top: UIViewController) -> UIModalPresentationStyle {
293
+ let isPad = (top.traitCollection.userInterfaceIdiom == .pad)
294
+ switch presentation {
295
+ case "popover":
296
+ return .popover
297
+ case "sheet":
298
+ return .pageSheet
299
+ default:
300
+ return isPad ? .popover : .pageSheet
301
+ }
302
+ }
303
+
304
+ // MARK: - Dismiss callbacks
305
+
306
+ public func popoverPresentationControllerDidDismissPopover(
307
+ _ popoverPresentationController: UIPopoverPresentationController
308
+ ) {
309
+ presentedVC = nil
310
+ onRequestClose?()
311
+ }
312
+
313
+ public func presentationControllerDidDismiss(_ presentationController: UIPresentationController)
314
+ {
315
+ presentedVC = nil
316
+ onRequestClose?()
317
+ }
318
+
319
+ public func adaptivePresentationStyle(for controller: UIPresentationController)
320
+ -> UIModalPresentationStyle
321
+ {
322
+ .none
323
+ }
324
+
325
+ // MARK: - Top VC helper
326
+
327
+ private func topViewController() -> UIViewController? {
328
+ guard
329
+ var top = UIApplication.shared.connectedScenes
330
+ .compactMap({ $0 as? UIWindowScene })
331
+ .flatMap({ $0.windows })
332
+ .first(where: { $0.isKeyWindow })?.rootViewController
333
+ else { return nil }
334
+
335
+ while true {
336
+ if let presented = top.presentedViewController {
337
+ top = presented
338
+ continue
339
+ }
340
+ if let nav = top as? UINavigationController, let visible = nav.visibleViewController {
341
+ top = visible
342
+ continue
343
+ }
344
+ if let tab = top as? UITabBarController, let selected = tab.selectedViewController {
345
+ top = selected
346
+ continue
347
+ }
348
+ break
349
+ }
350
+ return top
351
+ }
352
+
353
+ // MARK: - sizing (inline only)
354
+
355
+ public override func sizeThatFits(_ size: CGSize) -> CGSize {
356
+ if anchorMode != "inline" { return CGSize(width: size.width, height: 0) }
357
+
358
+ let minH: CGFloat = 44
359
+ guard let host = hostingController else {
360
+ return CGSize(width: size.width, height: minH)
361
+ }
362
+
363
+ let w = (size.width > 1) ? size.width : 320
364
+ let fitted = host.sizeThatFits(in: CGSize(width: w, height: .greatestFiniteMagnitude))
365
+ return CGSize(width: size.width, height: max(minH, fitted.height))
366
+ }
367
+
368
+ public override var intrinsicContentSize: CGSize {
369
+ if anchorMode != "inline" {
370
+ return CGSize(width: UIView.noIntrinsicMetric, height: 0)
371
+ }
372
+ let h = max(
373
+ 44, sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude)).height)
374
+ return CGSize(width: UIView.noIntrinsicMetric, height: h)
375
+ }
376
+ }
377
+
378
+ // MARK: - List VC (headless)
379
+
380
+ private final class PCSelectionMenuListViewController: UITableViewController {
381
+
382
+ private let titleText: String
383
+ private let options: [PCSelectionMenuOption]
384
+ private let selectedData: String
385
+ private let onSelectIndex: (Int) -> Void
386
+ private let onCancel: () -> Void
387
+
388
+ init(
389
+ titleText: String,
390
+ options: [PCSelectionMenuOption],
391
+ selectedData: String,
392
+ onSelect: @escaping (Int) -> Void,
393
+ onCancel: @escaping () -> Void
394
+ ) {
395
+ self.titleText = titleText
396
+ self.options = options
397
+ self.selectedData = selectedData
398
+ self.onSelectIndex = onSelect
399
+ self.onCancel = onCancel
400
+ super.init(style: .insetGrouped)
401
+ title = titleText
402
+ }
403
+
404
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
405
+
406
+ override func viewDidLoad() {
407
+ super.viewDidLoad()
408
+ view.backgroundColor = .systemBackground
409
+ }
410
+
411
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
412
+ options.count
413
+ }
414
+
415
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
416
+ -> UITableViewCell
417
+ {
418
+ let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
419
+ let opt = options[indexPath.row]
420
+ cell.textLabel?.text = opt.label
421
+ cell.accessoryType =
422
+ (opt.data == selectedData && !selectedData.isEmpty) ? .checkmark : .none
423
+ return cell
424
+ }
425
+
426
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
427
+ tableView.deselectRow(at: indexPath, animated: true)
428
+ onSelectIndex(indexPath.row)
429
+ }
430
+
431
+ func computePreferredSize() -> CGSize {
432
+ CGSize(width: 320, height: min(480, CGFloat(options.count) * 44))
433
+ }
434
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+
3
+ // DatePicker.tsx
4
+ import React, { useCallback, useMemo } from 'react';
5
+ import { StyleSheet } from 'react-native';
6
+ import NativeDatePicker from './DatePickerNativeComponent';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ function dateToMsOrMinusOne(d) {
9
+ return d ? d.getTime() : -1;
10
+ }
11
+ function normalizeVisible(presentation, visible) {
12
+ // Only meaningful in modal presentation. Keep undefined for inline to avoid noise.
13
+ if (presentation !== 'modal') return undefined;
14
+ return visible ? 'open' : 'closed';
15
+ }
16
+ export function DatePicker(props) {
17
+ const {
18
+ style,
19
+ date,
20
+ minDate,
21
+ maxDate,
22
+ locale,
23
+ timeZoneName,
24
+ mode,
25
+ presentation = 'modal',
26
+ visible,
27
+ onConfirm,
28
+ onClosed,
29
+ ios,
30
+ android
31
+ } = props;
32
+ const handleConfirm = useCallback(e => {
33
+ onConfirm?.(new Date(e.nativeEvent.timestampMs));
34
+ }, [onConfirm]);
35
+ const handleClosed = useCallback(() => {
36
+ onClosed?.();
37
+ }, [onClosed]);
38
+ const styles = useMemo(() => createStyles(), []);
39
+ const nativeProps = {
40
+ style: [styles.picker, style],
41
+ mode,
42
+ locale,
43
+ timeZoneName,
44
+ presentation,
45
+ visible: normalizeVisible(presentation, visible),
46
+ dateMs: dateToMsOrMinusOne(date),
47
+ minDateMs: dateToMsOrMinusOne(minDate ?? null),
48
+ maxDateMs: dateToMsOrMinusOne(maxDate ?? null),
49
+ onConfirm: onConfirm ? handleConfirm : undefined,
50
+ onClosed: onClosed ? handleClosed : undefined,
51
+ ios: ios ? {
52
+ preferredStyle: ios.preferredStyle,
53
+ countDownDurationSeconds: ios.countDownDurationSeconds,
54
+ minuteInterval: ios.minuteInterval,
55
+ roundsToMinuteInterval: ios.roundsToMinuteInterval
56
+ } : undefined,
57
+ android: android ? {
58
+ firstDayOfWeek: android.firstDayOfWeek,
59
+ material: android.material,
60
+ dialogTitle: android.dialogTitle,
61
+ positiveButtonTitle: android.positiveButtonTitle,
62
+ negativeButtonTitle: android.negativeButtonTitle
63
+ } : undefined
64
+ };
65
+ return /*#__PURE__*/_jsx(NativeDatePicker, {
66
+ ...nativeProps
67
+ });
68
+ }
69
+ function createStyles() {
70
+ return StyleSheet.create({
71
+ picker: {}
72
+ });
73
+ }
74
+ //# sourceMappingURL=DatePicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","useCallback","useMemo","StyleSheet","NativeDatePicker","jsx","_jsx","dateToMsOrMinusOne","d","getTime","normalizeVisible","presentation","visible","undefined","DatePicker","props","style","date","minDate","maxDate","locale","timeZoneName","mode","onConfirm","onClosed","ios","android","handleConfirm","e","Date","nativeEvent","timestampMs","handleClosed","styles","createStyles","nativeProps","picker","dateMs","minDateMs","maxDateMs","preferredStyle","countDownDurationSeconds","minuteInterval","roundsToMinuteInterval","firstDayOfWeek","material","dialogTitle","positiveButtonTitle","negativeButtonTitle","create"],"sourceRoot":"../../src","sources":["DatePicker.tsx"],"mappings":";;AAAA;AACA,OAAOA,KAAK,IAAIC,WAAW,EAAEC,OAAO,QAAQ,OAAO;AAEnD,SAASC,UAAU,QAAQ,cAAc;AAEzC,OAAOC,gBAAgB,MAShB,6BAA6B;AAAC,SAAAC,GAAA,IAAAC,IAAA;AA4CrC,SAASC,kBAAkBA,CAACC,CAA0B,EAAU;EAC9D,OAAOA,CAAC,GAAGA,CAAC,CAACC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;AAC7B;AAEA,SAASC,gBAAgBA,CACvBC,YAA+D,EAC/DC,OAA4B,EACP;EACrB;EACA,IAAID,YAAY,KAAK,OAAO,EAAE,OAAOE,SAAS;EAC9C,OAAOD,OAAO,GAAG,MAAM,GAAG,QAAQ;AACpC;AAEA,OAAO,SAASE,UAAUA,CAACC,KAAsB,EAAsB;EACrE,MAAM;IACJC,KAAK;IACLC,IAAI;IACJC,OAAO;IACPC,OAAO;IACPC,MAAM;IACNC,YAAY;IACZC,IAAI;IACJX,YAAY,GAAG,OAAO;IACtBC,OAAO;IACPW,SAAS;IACTC,QAAQ;IACRC,GAAG;IACHC;EACF,CAAC,GAAGX,KAAK;EAET,MAAMY,aAAa,GAAG1B,WAAW,CAC9B2B,CAAwC,IAAK;IAC5CL,SAAS,GAAG,IAAIM,IAAI,CAACD,CAAC,CAACE,WAAW,CAACC,WAAW,CAAC,CAAC;EAClD,CAAC,EACD,CAACR,SAAS,CACZ,CAAC;EAED,MAAMS,YAAY,GAAG/B,WAAW,CAAC,MAAM;IACrCuB,QAAQ,GAAG,CAAC;EACd,CAAC,EAAE,CAACA,QAAQ,CAAC,CAAC;EAEd,MAAMS,MAAM,GAAG/B,OAAO,CAAC,MAAMgC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC;EAEhD,MAAMC,WAAkC,GAAG;IACzCnB,KAAK,EAAE,CAACiB,MAAM,CAACG,MAAM,EAAEpB,KAAK,CAAQ;IAEpCM,IAAI;IACJF,MAAM;IACNC,YAAY;IAEZV,YAAY;IACZC,OAAO,EAAEF,gBAAgB,CAACC,YAAY,EAAEC,OAAO,CAAQ;IAEvDyB,MAAM,EAAE9B,kBAAkB,CAACU,IAAI,CAAQ;IACvCqB,SAAS,EAAE/B,kBAAkB,CAACW,OAAO,IAAI,IAAI,CAAQ;IACrDqB,SAAS,EAAEhC,kBAAkB,CAACY,OAAO,IAAI,IAAI,CAAQ;IAErDI,SAAS,EAAEA,SAAS,GAAGI,aAAa,GAAGd,SAAS;IAChDW,QAAQ,EAAEA,QAAQ,GAAGQ,YAAY,GAAGnB,SAAS;IAE7CY,GAAG,EAAEA,GAAG,GACJ;MACEe,cAAc,EAAEf,GAAG,CAACe,cAAc;MAClCC,wBAAwB,EAAEhB,GAAG,CAACgB,wBAAwB;MACtDC,cAAc,EAAEjB,GAAG,CAACiB,cAAc;MAClCC,sBAAsB,EAAElB,GAAG,CAACkB;IAC9B,CAAC,GACD9B,SAAS;IAEba,OAAO,EAAEA,OAAO,GACZ;MACEkB,cAAc,EAAElB,OAAO,CAACkB,cAAc;MACtCC,QAAQ,EAAEnB,OAAO,CAACmB,QAAe;MACjCC,WAAW,EAAEpB,OAAO,CAACoB,WAAW;MAChCC,mBAAmB,EAAErB,OAAO,CAACqB,mBAAmB;MAChDC,mBAAmB,EAAEtB,OAAO,CAACsB;IAC/B,CAAC,GACDnC;EACN,CAAC;EAED,oBAAOP,IAAA,CAACF,gBAAgB;IAAA,GAAK+B;EAAW,CAAG,CAAC;AAC9C;AAEA,SAASD,YAAYA,CAAA,EAAG;EACtB,OAAO/B,UAAU,CAAC8C,MAAM,CAAC;IACvBb,MAAM,EAAE,CAAC;EACX,CAAC,CAAC;AACJ","ignoreList":[]}