react-native-platform-components 0.4.0 → 0.5.0

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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # react-native-platform-components
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/react-native-platform-components.svg)](https://www.npmjs.com/package/react-native-platform-components)
4
+ [![npm downloads](https://img.shields.io/npm/dm/react-native-platform-components.svg)](https://www.npmjs.com/package/react-native-platform-components)
5
+
6
+ <table>
7
+ <tr>
8
+ <td align="center"><strong>iOS DatePicker</strong></td>
9
+ <td align="center"><strong>Android DatePicker</strong></td>
10
+ </tr>
11
+ <tr>
12
+ <td><video src="https://github.com/user-attachments/assets/a9fb6237-6078-496b-8a58-1f2fae4f1af5" height="400"></video></td>
13
+ <td><video src="https://github.com/user-attachments/assets/70e42d98-7ea6-40fe-90c5-c1efc2227f25" height="400"></video></td>
14
+ </tr>
15
+ <tr>
16
+ <td align="center"><strong>iOS SelectionMenu</strong></td>
17
+ <td align="center"><strong>Android SelectionMenu</strong></td>
18
+ </tr>
19
+ <tr>
20
+ <td><video src="https://github.com/user-attachments/assets/f1af8e91-4e98-4dbe-a2a6-57f91770678e" height="400"></video></td>
21
+ <td><video src="https://github.com/user-attachments/assets/dc61da04-45e5-43c3-b766-6048367775bb" height="400"></video></td>
22
+ </tr>
23
+ </table>
24
+
25
+ > 🚧 In development — not ready for public use.
26
+
3
27
  High-quality **native UI components for React Native**, implemented with platform-first APIs and exposed through clean, typed JavaScript interfaces.
4
28
 
5
29
  This library focuses on **true native behavior**, not JavaScript re-implementations — providing:
@@ -181,6 +205,8 @@ Native selection menu with **inline** and **headless** modes.
181
205
  - **Headless mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
182
206
  - **Inline mode** (`inlineMode={true}`): Native picker UI rendered inline. Menu managed internally.
183
207
 
208
+ > **Note:** On iOS, headless mode uses a custom popover to enable programmatic presentation. For the full native menu experience (system animations, scroll physics), use inline mode. This is an intentional trade-off: headless gives you control over the trigger UI, inline gives you the complete system menu behavior.
209
+
184
210
  ---
185
211
 
186
212
  ## DatePicker
@@ -216,7 +242,7 @@ Native date & time picker using **platform system pickers**.
216
242
  | Prop | Type | Description |
217
243
  |------|------|-------------|
218
244
  | `firstDayOfWeek` | `number` | First day of week (1-7, Sunday=1) |
219
- | `material` | `'system' \| 'm3'` | Material Design style |
245
+ | `material` | `'system' \| 'm3'` | Material Design style (modal only; embedded always uses system picker) |
220
246
  | `dialogTitle` | `string` | Custom dialog title |
221
247
  | `positiveButtonTitle` | `string` | Custom confirm button text |
222
248
  | `negativeButtonTitle` | `string` | Custom cancel button text |
@@ -233,6 +259,17 @@ Native date & time picker using **platform system pickers**.
233
259
 
234
260
  ---
235
261
 
262
+ ## Theming
263
+
264
+ This library does not expose theming props. Components inherit their appearance from your app's native platform theme.
265
+
266
+ - **iOS**: Components follow system appearance (light/dark mode) and use system-defined styles (e.g., `UIBlurEffect` for menu backgrounds). These are not customizable per-component.
267
+ - **Android**: Components respect your app's Material Theme. Customize via your `styles.xml` or Material 3 theme configuration.
268
+
269
+ This is intentional. The goal is native fidelity, not pixel-level customization. If you need custom styling beyond what the platform theme provides, this library may not be the right fit.
270
+
271
+ ---
272
+
236
273
  ## Contributing
237
274
 
238
275
  See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
@@ -136,14 +136,16 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
136
136
  minDateMs = value
137
137
  // clamp if needed
138
138
  dateMs = clamp(dateMs ?: System.currentTimeMillis())
139
- syncInlineFromState()
139
+ // Rebuild inline picker to apply new min date (avoids CalendarView bugs)
140
+ if (isInline()) rebuildUI() else syncInlineFromState()
140
141
  }
141
142
 
142
143
  fun applyMaxDateMs(value: Long?) {
143
144
  maxDateMs = value
144
145
  // clamp if needed
145
146
  dateMs = clamp(dateMs ?: System.currentTimeMillis())
146
- syncInlineFromState()
147
+ // Rebuild inline picker to apply new max date (avoids CalendarView bugs)
148
+ if (isInline()) rebuildUI() else syncInlineFromState()
147
149
  }
148
150
 
149
151
  /**
@@ -205,8 +207,11 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
205
207
  ViewGroup.LayoutParams.WRAP_CONTENT,
206
208
  ViewGroup.LayoutParams.WRAP_CONTENT
207
209
  )
208
- calendarViewShown = true
209
- spinnersShown = false
210
+ // Use spinner mode to avoid CalendarView rendering bugs when scrolling months
211
+ calendarViewShown = false
212
+ spinnersShown = true
213
+ minDateMs?.let { minDate = it }
214
+ maxDateMs?.let { maxDate = it }
210
215
  }
211
216
  container.addView(dp)
212
217
  inlineDatePicker = dp
@@ -239,7 +244,14 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
239
244
  inlineContainer = container
240
245
 
241
246
  syncInlineFromState()
242
- requestLayout()
247
+
248
+ // Force layout refresh - post to ensure React Native's layout system picks it up
249
+ post {
250
+ requestLayout()
251
+ invalidate()
252
+ // Also request layout from parent to notify React Native
253
+ (parent as? ViewGroup)?.requestLayout()
254
+ }
243
255
  }
244
256
 
245
257
  private fun syncInlineFromState() {
@@ -253,10 +265,8 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
253
265
  suppressInlineCallbacks = true
254
266
  try {
255
267
  inlineDatePicker?.let { dp ->
256
- // Apply min/max bounds on the widget itself where possible
257
- minDateMs?.let { dp.minDate = it }
258
- maxDateMs?.let { dp.maxDate = it }
259
-
268
+ // Note: min/max dates are set during picker creation in rebuildUI()
269
+ // to avoid CalendarView rendering bugs from repeated updates
260
270
  val y = cal.get(Calendar.YEAR)
261
271
  val m = cal.get(Calendar.MONTH)
262
272
  val d = cal.get(Calendar.DAY_OF_MONTH)
@@ -319,20 +329,28 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
319
329
 
320
330
  private fun presentIfNeeded() {
321
331
  if (showingModal) return
322
- val act = findFragmentActivity() ?: run {
323
- Log.w(TAG, "presentIfNeeded: no FragmentActivity found")
324
- onCancel?.invoke()
325
- showingModal = false
326
- return
327
- }
328
-
329
- Log.d(TAG, "presentIfNeeded mode=$mode material=$androidMaterialMode")
330
332
  showingModal = true
331
333
 
332
- when (mode) {
333
- "time" -> presentTime(act)
334
- "dateAndTime" -> presentDateThenTime(act)
335
- else -> presentDate(act)
334
+ // Defer presentation to the next frame to ensure all props from the current
335
+ // React Native batch are applied first. This guarantees dateMs reflects the
336
+ // latest value from React Native before we create the dialog.
337
+ post {
338
+ if (!showingModal) return@post
339
+
340
+ val act = findFragmentActivity() ?: run {
341
+ Log.w(TAG, "presentIfNeeded: no FragmentActivity found")
342
+ onCancel?.invoke()
343
+ showingModal = false
344
+ return@post
345
+ }
346
+
347
+ Log.d(TAG, "presentIfNeeded mode=$mode material=$androidMaterialMode")
348
+
349
+ when (mode) {
350
+ "time" -> presentTime(act)
351
+ "dateAndTime" -> presentDateThenTime(act)
352
+ else -> presentDate(act)
353
+ }
336
354
  }
337
355
  }
338
356
 
@@ -60,17 +60,19 @@ class PCDatePickerViewManager :
60
60
  view.applyTimeZoneName(value)
61
61
  }
62
62
 
63
- // WithDefault<double,-1> comes through as primitive Double
63
+ // Sentinel is MIN_SAFE_INTEGER to allow negative timestamps for pre-1970 dates
64
+ private val noDateSentinel = -9007199254740991.0
65
+
64
66
  override fun setDateMs(view: PCDatePickerView, value: Double) {
65
- view.applyDateMs(if (value >= 0.0) value.toLong() else null)
67
+ view.applyDateMs(if (value > noDateSentinel) value.toLong() else null)
66
68
  }
67
69
 
68
70
  override fun setMinDateMs(view: PCDatePickerView, value: Double) {
69
- view.applyMinDateMs(if (value >= 0.0) value.toLong() else null)
71
+ view.applyMinDateMs(if (value > noDateSentinel) value.toLong() else null)
70
72
  }
71
73
 
72
74
  override fun setMaxDateMs(view: PCDatePickerView, value: Double) {
73
- view.applyMaxDateMs(if (value >= 0.0) value.toLong() else null)
75
+ view.applyMaxDateMs(if (value > noDateSentinel) value.toLong() else null)
74
76
  }
75
77
 
76
78
  // --- platform objects ---
@@ -16,8 +16,15 @@ enum PCConstants {
16
16
  /// Maximum popover height before scrolling
17
17
  static let popoverMaxHeight: CGFloat = 400
18
18
 
19
- /// Row height in selection menu popover
20
- static let popoverRowHeight: CGFloat = 44
19
+ /// Minimum row height in selection menu popover (used as baseline)
20
+ static let popoverRowHeightMin: CGFloat = 44
21
+
22
+ /// Dynamic row height that respects user's preferred content size
23
+ static var popoverRowHeight: CGFloat {
24
+ let bodyFont = UIFont.preferredFont(forTextStyle: .body)
25
+ // Row height = font line height + vertical padding (16pt total)
26
+ return max(popoverRowHeightMin, ceil(bodyFont.lineHeight) + 16)
27
+ }
21
28
 
22
29
  /// Vertical padding in selection menu popover (top + bottom)
23
30
  static let popoverVerticalPadding: CGFloat = 16
@@ -117,20 +117,21 @@ using namespace facebook::react;
117
117
  _datePickerView.open = newOpen;
118
118
  }
119
119
 
120
- // dateMs (sentinel -1)
120
+ // dateMs (sentinel is MIN_SAFE_INTEGER to allow negative timestamps for pre-1970 dates)
121
+ static const double kNoDateSentinel = -9007199254740991.0;
121
122
  if (oldViewProps.dateMs != newViewProps.dateMs) {
122
123
  _datePickerView.dateMs =
123
- (newViewProps.dateMs >= 0) ? @(newViewProps.dateMs) : nil;
124
+ (newViewProps.dateMs > kNoDateSentinel) ? @(newViewProps.dateMs) : nil;
124
125
  }
125
126
 
126
- // min/max (sentinel -1)
127
+ // min/max (same sentinel)
127
128
  if (oldViewProps.minDateMs != newViewProps.minDateMs) {
128
129
  _datePickerView.minDateMs =
129
- (newViewProps.minDateMs >= 0) ? @(newViewProps.minDateMs) : nil;
130
+ (newViewProps.minDateMs > kNoDateSentinel) ? @(newViewProps.minDateMs) : nil;
130
131
  }
131
132
  if (oldViewProps.maxDateMs != newViewProps.maxDateMs) {
132
133
  _datePickerView.maxDateMs =
133
- (newViewProps.maxDateMs >= 0) ? @(newViewProps.maxDateMs) : nil;
134
+ (newViewProps.maxDateMs > kNoDateSentinel) ? @(newViewProps.maxDateMs) : nil;
134
135
  }
135
136
 
136
137
  // locale
@@ -1,5 +1,5 @@
1
- import os.log
2
1
  import UIKit
2
+ import os.log
3
3
 
4
4
  private let logger = Logger(subsystem: "com.platformcomponents", category: "DatePicker")
5
5
 
@@ -134,7 +134,9 @@ public final class PCDatePickerView: UIControl,
134
134
  picker.setNeedsLayout()
135
135
  picker.layoutIfNeeded()
136
136
  let fitted = picker.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
137
- return CGSize(width: UIView.noIntrinsicMetric, height: max(PCConstants.minTouchTargetHeight, fitted.height))
137
+ return CGSize(
138
+ width: UIView.noIntrinsicMetric,
139
+ height: max(PCConstants.minTouchTargetHeight, fitted.height))
138
140
  }
139
141
 
140
142
  /// ✅ Called by your measuring pipeline.
@@ -153,7 +155,9 @@ public final class PCDatePickerView: UIControl,
153
155
  withHorizontalFittingPriority: .required,
154
156
  verticalFittingPriority: .fittingSizeLevel
155
157
  )
156
- return CGSize(width: constrainedSize.width, height: max(PCConstants.minTouchTargetHeight, fitted.height))
158
+ return CGSize(
159
+ width: constrainedSize.width,
160
+ height: max(PCConstants.minTouchTargetHeight, fitted.height))
157
161
  }
158
162
 
159
163
  /// Separate sizing for popover content.
@@ -213,12 +217,16 @@ public final class PCDatePickerView: UIControl,
213
217
  }
214
218
  logger.debug("presentIfNeeded: presenting modal picker")
215
219
 
216
- // Prevent “settle” events right as we present.
217
- suppressNextChangesBriefly()
218
-
219
220
  // Ensure picker is not inline.
220
221
  detachInlinePickerIfNeeded()
221
222
 
223
+ // Sync the picker's date to the current prop value before presenting.
224
+ // This ensures React Native's date is always respected as the source of truth.
225
+ applyDateMs(animated: false)
226
+
227
+ // Prevent "settle" events right as we present.
228
+ suppressNextChangesBriefly()
229
+
222
230
  let vc = UIViewController()
223
231
  vc.view.backgroundColor = .clear
224
232
  vc.view.isOpaque = false
@@ -226,8 +234,14 @@ public final class PCDatePickerView: UIControl,
226
234
  picker.translatesAutoresizingMaskIntoConstraints = false
227
235
  vc.view.addSubview(picker)
228
236
 
237
+ // Position picker at top-leading (constraints required to avoid freeze with inline style)
238
+ NSLayoutConstraint.activate([
239
+ picker.topAnchor.constraint(equalTo: vc.view.topAnchor),
240
+ picker.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor),
241
+ ])
242
+
229
243
  let size = popoverContentSize()
230
- vc.preferredContentSize = size
244
+ vc.preferredContentSize = size
231
245
 
232
246
  // ✅ Anchored popover-style (not a full sheet)
233
247
  vc.modalPresentationStyle = .popover
@@ -236,7 +250,15 @@ public final class PCDatePickerView: UIControl,
236
250
  if let pop = vc.popoverPresentationController {
237
251
  pop.delegate = self
238
252
  pop.sourceView = self
239
- pop.sourceRect = bounds
253
+ // Use a minimum height for sourceRect to help popover positioning
254
+ // (modal presentation views have zero intrinsic height)
255
+ let sourceRect = CGRect(
256
+ x: bounds.minX,
257
+ y: bounds.minY,
258
+ width: max(bounds.width, 44),
259
+ height: max(bounds.height, 44)
260
+ )
261
+ pop.sourceRect = sourceRect
240
262
  pop.permittedArrowDirections = [.up, .down]
241
263
  }
242
264
 
@@ -288,9 +310,14 @@ public final class PCDatePickerView: UIControl,
288
310
  // MARK: - Apply props (avoid firing valueChanged)
289
311
 
290
312
  private func applyDateMs(animated: Bool) {
291
- guard let ms = dateMs?.doubleValue else { return }
292
313
  suppressNextChangesBriefly()
293
- picker.setDate(Date(timeIntervalSince1970: ms / 1000.0), animated: animated)
314
+ if let ms = dateMs?.doubleValue {
315
+ picker.setDate(Date(timeIntervalSince1970: ms / 1000.0), animated: animated)
316
+ } else {
317
+ // When no date is provided, default to now to avoid layout issues
318
+ // (especially with inline style in modal presentation)
319
+ picker.setDate(Date(), animated: animated)
320
+ }
294
321
  }
295
322
 
296
323
  private func applyMinMax() {
@@ -41,6 +41,7 @@ private struct PCSelectionMenuInlinePickerView: View {
41
41
  } label: {
42
42
  HStack(spacing: 8) {
43
43
  Text(model.displayTitle)
44
+ .font(.body)
44
45
  .lineLimit(1)
45
46
  .truncationMode(.tail)
46
47
 
@@ -279,22 +280,42 @@ public final class PCSelectionMenuView: UIControl {
279
280
  }
280
281
  )
281
282
 
282
- menuVC.modalPresentationStyle = .popover
283
+ // Calculate menu position relative to source view
284
+ let sourceFrame = self.convert(self.bounds, to: vc.view)
285
+ let screenBounds = vc.view.bounds
283
286
  let popoverHeight = min(
284
287
  CGFloat(opts.count) * PCConstants.popoverRowHeight + PCConstants.popoverVerticalPadding,
285
288
  PCConstants.popoverMaxHeight
286
289
  )
287
- menuVC.preferredContentSize = CGSize(
290
+ let spacing: CGFloat = 8
291
+
292
+ // Check if menu fits below the source view
293
+ let rowHeight = PCConstants.popoverRowHeight
294
+ let wouldExtendBeyondBottom = sourceFrame.maxY + spacing + popoverHeight > screenBounds.maxY - 20
295
+
296
+ let menuY: CGFloat
297
+ if wouldExtendBeyondBottom {
298
+ // Position above the source view (no overlap offset)
299
+ menuY = sourceFrame.minY - spacing - popoverHeight
300
+ } else {
301
+ // Position below, but shift up by one row to overlap trigger (like system menu)
302
+ menuY = sourceFrame.maxY + spacing - rowHeight
303
+ }
304
+
305
+ // Center horizontally, but keep within screen bounds
306
+ var menuX = sourceFrame.midX - PCConstants.popoverWidth / 2
307
+ menuX = max(16, min(menuX, screenBounds.maxX - PCConstants.popoverWidth - 16))
308
+
309
+ let menuFrame = CGRect(
310
+ x: menuX,
311
+ y: menuY,
288
312
  width: PCConstants.popoverWidth,
289
313
  height: popoverHeight
290
314
  )
291
315
 
292
- if let popover = menuVC.popoverPresentationController {
293
- popover.sourceView = self
294
- popover.sourceRect = self.bounds
295
- popover.permittedArrowDirections = [] // Remove arrow to match inline
296
- popover.delegate = menuVC
297
- }
316
+ menuVC.modalPresentationStyle = .overCurrentContext
317
+ menuVC.modalTransitionStyle = .crossDissolve
318
+ menuVC.menuFrame = menuFrame
298
319
 
299
320
  vc.present(menuVC, animated: true)
300
321
  }
@@ -326,13 +347,40 @@ public final class PCSelectionMenuView: UIControl {
326
347
  }
327
348
  }
328
349
 
350
+ // MARK: - Glass Menu Cell
351
+
352
+ private class PCGlassMenuCell: UITableViewCell {
353
+ static let reuseIdentifier = "PCGlassMenuCell"
354
+
355
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
356
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
357
+ setupCell()
358
+ }
359
+
360
+ required init?(coder: NSCoder) {
361
+ super.init(coder: coder)
362
+ setupCell()
363
+ }
364
+
365
+ private func setupCell() {
366
+ backgroundColor = .clear
367
+ contentView.backgroundColor = .clear
368
+ selectionStyle = .none
369
+ textLabel?.font = .preferredFont(forTextStyle: .body)
370
+ textLabel?.adjustsFontForContentSizeCategory = true
371
+ }
372
+ }
373
+
329
374
  // MARK: - Custom Menu View Controller (matches SwiftUI Menu appearance)
330
375
 
331
- private class PCMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPopoverPresentationControllerDelegate {
376
+ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
332
377
  private let options: [PCSelectionMenuOption]
333
378
  private let onSelect: (Int) -> Void
334
379
  private let onCancel: () -> Void
335
380
  private var tableView: UITableView!
381
+ private var menuContainer: UIView!
382
+
383
+ var menuFrame: CGRect = .zero
336
384
 
337
385
  init(options: [PCSelectionMenuOption], onSelect: @escaping (Int) -> Void, onCancel: @escaping () -> Void) {
338
386
  self.options = options
@@ -348,37 +396,56 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
348
396
  override func viewDidLoad() {
349
397
  super.viewDidLoad()
350
398
 
351
- // Add blur effect (liquid glass)
352
- let blurEffect = UIBlurEffect(style: .systemMaterial)
353
- let blurView = UIVisualEffectView(effect: blurEffect)
354
- blurView.translatesAutoresizingMaskIntoConstraints = false
355
- view.addSubview(blurView)
399
+ view.backgroundColor = .clear
400
+
401
+ // Tap outside to dismiss
402
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap))
403
+ tapGesture.cancelsTouchesInView = false
404
+ view.addGestureRecognizer(tapGesture)
405
+
406
+ // Menu container positioned at menuFrame
407
+ menuContainer = UIView(frame: menuFrame)
408
+ menuContainer.backgroundColor = .clear
409
+ menuContainer.layer.cornerRadius = 12
410
+ menuContainer.clipsToBounds = true
411
+ view.addSubview(menuContainer)
412
+
413
+ // Use liquid glass on iOS 26+, fall back to system material blur on older versions
414
+ let effectView: UIVisualEffectView
415
+ if #available(iOS 26, *) {
416
+ var glassEffect = UIGlassEffect()
417
+ glassEffect.isInteractive = true
418
+ effectView = UIVisualEffectView(effect: glassEffect)
419
+ } else {
420
+ let blurEffect = UIBlurEffect(style: .systemMaterial)
421
+ effectView = UIVisualEffectView(effect: blurEffect)
422
+ }
423
+ effectView.frame = menuContainer.bounds
424
+ effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
425
+ menuContainer.addSubview(effectView)
356
426
 
357
- tableView = UITableView(frame: .zero, style: .plain)
427
+ tableView = UITableView(frame: menuContainer.bounds, style: .plain)
358
428
  tableView.delegate = self
359
429
  tableView.dataSource = self
360
- tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
430
+ tableView.register(PCGlassMenuCell.self, forCellReuseIdentifier: PCGlassMenuCell.reuseIdentifier)
361
431
  tableView.backgroundColor = .clear
362
432
  tableView.separatorStyle = .none
363
433
  tableView.isScrollEnabled = true
364
434
  tableView.rowHeight = PCConstants.popoverRowHeight
435
+ tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
365
436
  let verticalPad = PCConstants.popoverVerticalPadding / 2
366
437
  tableView.contentInset = UIEdgeInsets(top: verticalPad, left: 0, bottom: verticalPad, right: 0)
367
- tableView.translatesAutoresizingMaskIntoConstraints = false
368
438
 
369
- blurView.contentView.addSubview(tableView)
439
+ effectView.contentView.addSubview(tableView)
440
+ }
370
441
 
371
- NSLayoutConstraint.activate([
372
- blurView.topAnchor.constraint(equalTo: view.topAnchor),
373
- blurView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
374
- blurView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
375
- blurView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
376
-
377
- tableView.topAnchor.constraint(equalTo: blurView.contentView.topAnchor),
378
- tableView.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor),
379
- tableView.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor),
380
- tableView.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor),
381
- ])
442
+ @objc private func handleBackgroundTap(_ gesture: UITapGestureRecognizer) {
443
+ let location = gesture.location(in: view)
444
+ if !menuContainer.frame.contains(location) {
445
+ dismiss(animated: true) { [weak self] in
446
+ self?.onCancel()
447
+ }
448
+ }
382
449
  }
383
450
 
384
451
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@@ -386,11 +453,8 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
386
453
  }
387
454
 
388
455
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
389
- let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
456
+ let cell = tableView.dequeueReusableCell(withIdentifier: PCGlassMenuCell.reuseIdentifier, for: indexPath)
390
457
  cell.textLabel?.text = options[indexPath.row].label
391
- cell.textLabel?.font = .systemFont(ofSize: 17)
392
- cell.backgroundColor = .clear
393
- cell.selectionStyle = .default
394
458
  return cell
395
459
  }
396
460
 
@@ -400,13 +464,5 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
400
464
  self?.onSelect(indexPath.row)
401
465
  }
402
466
  }
403
-
404
- func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
405
- onCancel()
406
- }
407
-
408
- func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
409
- return .none
410
- }
411
467
  }
412
468
 
@@ -5,8 +5,11 @@ import React, { useCallback, useMemo } from 'react';
5
5
  import { StyleSheet } from 'react-native';
6
6
  import NativeDatePicker from './DatePickerNativeComponent';
7
7
  import { jsx as _jsx } from "react/jsx-runtime";
8
- function dateToMsOrMinusOne(d) {
9
- return d ? d.getTime() : -1;
8
+ // Sentinel value for "no date". Using MIN_SAFE_INTEGER ensures we don't
9
+ // conflict with valid negative timestamps (dates before 1970).
10
+ const NO_DATE_SENTINEL = Number.MIN_SAFE_INTEGER;
11
+ function dateToMsOrSentinel(d) {
12
+ return d ? d.getTime() : NO_DATE_SENTINEL;
10
13
  }
11
14
  function normalizeVisible(presentation, visible) {
12
15
  // Only meaningful in modal presentation. Keep undefined for inline to avoid noise.
@@ -44,9 +47,9 @@ export function DatePicker(props) {
44
47
  timeZoneName,
45
48
  presentation,
46
49
  visible: normalizeVisible(presentation, visible),
47
- dateMs: dateToMsOrMinusOne(date),
48
- minDateMs: dateToMsOrMinusOne(minDate ?? null),
49
- maxDateMs: dateToMsOrMinusOne(maxDate ?? null),
50
+ dateMs: dateToMsOrSentinel(date),
51
+ minDateMs: dateToMsOrSentinel(minDate ?? null),
52
+ maxDateMs: dateToMsOrSentinel(maxDate ?? null),
50
53
  onConfirm: onConfirm ? handleConfirm : undefined,
51
54
  onClosed: onClosed ? handleClosed : undefined,
52
55
  ios: ios ? {
@@ -1 +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","testID","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;AA+CrC,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,OAAO;IACPC;EACF,CAAC,GAAGZ,KAAK;EAET,MAAMa,aAAa,GAAG3B,WAAW,CAC9B4B,CAAwC,IAAK;IAC5CN,SAAS,GAAG,IAAIO,IAAI,CAACD,CAAC,CAACE,WAAW,CAACC,WAAW,CAAC,CAAC;EAClD,CAAC,EACD,CAACT,SAAS,CACZ,CAAC;EAED,MAAMU,YAAY,GAAGhC,WAAW,CAAC,MAAM;IACrCuB,QAAQ,GAAG,CAAC;EACd,CAAC,EAAE,CAACA,QAAQ,CAAC,CAAC;EAEd,MAAMU,MAAM,GAAGhC,OAAO,CAAC,MAAMiC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC;EAEhD,MAAMC,WAAkC,GAAG;IACzCpB,KAAK,EAAE,CAACkB,MAAM,CAACG,MAAM,EAAErB,KAAK,CAAQ;IAEpCM,IAAI;IACJF,MAAM;IACNC,YAAY;IAEZV,YAAY;IACZC,OAAO,EAAEF,gBAAgB,CAACC,YAAY,EAAEC,OAAO,CAAQ;IAEvD0B,MAAM,EAAE/B,kBAAkB,CAACU,IAAI,CAAQ;IACvCsB,SAAS,EAAEhC,kBAAkB,CAACW,OAAO,IAAI,IAAI,CAAQ;IACrDsB,SAAS,EAAEjC,kBAAkB,CAACY,OAAO,IAAI,IAAI,CAAQ;IAErDI,SAAS,EAAEA,SAAS,GAAGK,aAAa,GAAGf,SAAS;IAChDW,QAAQ,EAAEA,QAAQ,GAAGS,YAAY,GAAGpB,SAAS;IAE7CY,GAAG,EAAEA,GAAG,GACJ;MACEgB,cAAc,EAAEhB,GAAG,CAACgB,cAAc;MAClCC,wBAAwB,EAAEjB,GAAG,CAACiB,wBAAwB;MACtDC,cAAc,EAAElB,GAAG,CAACkB,cAAc;MAClCC,sBAAsB,EAAEnB,GAAG,CAACmB;IAC9B,CAAC,GACD/B,SAAS;IAEba,OAAO,EAAEA,OAAO,GACZ;MACEmB,cAAc,EAAEnB,OAAO,CAACmB,cAAc;MACtCC,QAAQ,EAAEpB,OAAO,CAACoB,QAAe;MACjCC,WAAW,EAAErB,OAAO,CAACqB,WAAW;MAChCC,mBAAmB,EAAEtB,OAAO,CAACsB,mBAAmB;MAChDC,mBAAmB,EAAEvB,OAAO,CAACuB;IAC/B,CAAC,GACDpC;EACN,CAAC;EAED,oBAAOP,IAAA,CAACF,gBAAgB;IAACuB,MAAM,EAAEA,MAAO;IAAA,GAAKS;EAAW,CAAG,CAAC;AAC9D;AAEA,SAASD,YAAYA,CAAA,EAAG;EACtB,OAAOhC,UAAU,CAAC+C,MAAM,CAAC;IACvBb,MAAM,EAAE,CAAC;EACX,CAAC,CAAC;AACJ","ignoreList":[]}
1
+ {"version":3,"names":["React","useCallback","useMemo","StyleSheet","NativeDatePicker","jsx","_jsx","NO_DATE_SENTINEL","Number","MIN_SAFE_INTEGER","dateToMsOrSentinel","d","getTime","normalizeVisible","presentation","visible","undefined","DatePicker","props","style","date","minDate","maxDate","locale","timeZoneName","mode","onConfirm","onClosed","ios","android","testID","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;AA+CrC;AACA;AACA,MAAMC,gBAAgB,GAAGC,MAAM,CAACC,gBAAgB;AAEhD,SAASC,kBAAkBA,CAACC,CAA0B,EAAU;EAC9D,OAAOA,CAAC,GAAGA,CAAC,CAACC,OAAO,CAAC,CAAC,GAAGL,gBAAgB;AAC3C;AAEA,SAASM,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,OAAO;IACPC;EACF,CAAC,GAAGZ,KAAK;EAET,MAAMa,aAAa,GAAG9B,WAAW,CAC9B+B,CAAwC,IAAK;IAC5CN,SAAS,GAAG,IAAIO,IAAI,CAACD,CAAC,CAACE,WAAW,CAACC,WAAW,CAAC,CAAC;EAClD,CAAC,EACD,CAACT,SAAS,CACZ,CAAC;EAED,MAAMU,YAAY,GAAGnC,WAAW,CAAC,MAAM;IACrC0B,QAAQ,GAAG,CAAC;EACd,CAAC,EAAE,CAACA,QAAQ,CAAC,CAAC;EAEd,MAAMU,MAAM,GAAGnC,OAAO,CAAC,MAAMoC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC;EAEhD,MAAMC,WAAkC,GAAG;IACzCpB,KAAK,EAAE,CAACkB,MAAM,CAACG,MAAM,EAAErB,KAAK,CAAQ;IAEpCM,IAAI;IACJF,MAAM;IACNC,YAAY;IAEZV,YAAY;IACZC,OAAO,EAAEF,gBAAgB,CAACC,YAAY,EAAEC,OAAO,CAAQ;IAEvD0B,MAAM,EAAE/B,kBAAkB,CAACU,IAAI,CAAQ;IACvCsB,SAAS,EAAEhC,kBAAkB,CAACW,OAAO,IAAI,IAAI,CAAQ;IACrDsB,SAAS,EAAEjC,kBAAkB,CAACY,OAAO,IAAI,IAAI,CAAQ;IAErDI,SAAS,EAAEA,SAAS,GAAGK,aAAa,GAAGf,SAAS;IAChDW,QAAQ,EAAEA,QAAQ,GAAGS,YAAY,GAAGpB,SAAS;IAE7CY,GAAG,EAAEA,GAAG,GACJ;MACEgB,cAAc,EAAEhB,GAAG,CAACgB,cAAc;MAClCC,wBAAwB,EAAEjB,GAAG,CAACiB,wBAAwB;MACtDC,cAAc,EAAElB,GAAG,CAACkB,cAAc;MAClCC,sBAAsB,EAAEnB,GAAG,CAACmB;IAC9B,CAAC,GACD/B,SAAS;IAEba,OAAO,EAAEA,OAAO,GACZ;MACEmB,cAAc,EAAEnB,OAAO,CAACmB,cAAc;MACtCC,QAAQ,EAAEpB,OAAO,CAACoB,QAAe;MACjCC,WAAW,EAAErB,OAAO,CAACqB,WAAW;MAChCC,mBAAmB,EAAEtB,OAAO,CAACsB,mBAAmB;MAChDC,mBAAmB,EAAEvB,OAAO,CAACuB;IAC/B,CAAC,GACDpC;EACN,CAAC;EAED,oBAAOV,IAAA,CAACF,gBAAgB;IAAC0B,MAAM,EAAEA,MAAO;IAAA,GAAKS;EAAW,CAAG,CAAC;AAC9D;AAEA,SAASD,YAAYA,CAAA,EAAG;EACtB,OAAOnC,UAAU,CAACkD,MAAM,CAAC;IACvBb,MAAM,EAAE,CAAC;EACX,CAAC,CAAC;AACJ","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/DatePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAwB,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAyB,EAGvB,KAAK,QAAQ,IAAI,cAAc,EAC/B,KAAK,YAAY,IAAI,kBAAkB,EACvC,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAW,MAAM,eAAe,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B,2DAA2D;IAC3D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,mDAAmD;IACnD,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,GAAG,CAAC,EAAE;QACJ,cAAc,CAAC,EAAE,kBAAkB,CAAC;QACpC,wBAAwB,CAAC,EAAE,cAAc,CAAC,0BAA0B,CAAC,CAAC;QACtE,cAAc,CAAC,EAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;QAClD,sBAAsB,CAAC,EAAE,yBAAyB,CAAC;KACpD,CAAC;IAEF,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QACtD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;QAC/B,WAAW,CAAC,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;QAChD,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;QAChE,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;KACjE,CAAC;CACH,CAAC;AAeF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAqErE"}
1
+ {"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/DatePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAwB,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAyB,EAGvB,KAAK,QAAQ,IAAI,cAAc,EAC/B,KAAK,YAAY,IAAI,kBAAkB,EACvC,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAW,MAAM,eAAe,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B,2DAA2D;IAC3D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,mDAAmD;IACnD,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,GAAG,CAAC,EAAE;QACJ,cAAc,CAAC,EAAE,kBAAkB,CAAC;QACpC,wBAAwB,CAAC,EAAE,cAAc,CAAC,0BAA0B,CAAC,CAAC;QACtE,cAAc,CAAC,EAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;QAClD,sBAAsB,CAAC,EAAE,yBAAyB,CAAC;KACpD,CAAC;IAEF,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QACtD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;QAC/B,WAAW,CAAC,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;QAChD,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;QAChE,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;KACjE,CAAC;CACH,CAAC;AAmBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAqErE"}
@@ -36,7 +36,7 @@ export interface SelectionMenuProps extends ViewProps {
36
36
  */
37
37
  ios?: {};
38
38
  android?: {
39
- /** Material preference ("auto" | "m2" | "m3"). */
39
+ /** Material preference ('system' | 'm3'). */
40
40
  material?: AndroidMaterialMode;
41
41
  };
42
42
  /** Test identifier */
@@ -1 +1 @@
1
- {"version":3,"file":"SelectionMenu.d.ts","sourceRoot":"","sources":["../../../src/SelectionMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAA4B,EAC1B,KAAK,mBAAmB,EAEzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,yCAAyC;IACzC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAExC;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhE;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC;IAET,OAAO,CAAC,EAAE;QACR,kDAAkD;QAClD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;KAChC,CAAC;IAEF,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,CAkE3E"}
1
+ {"version":3,"file":"SelectionMenu.d.ts","sourceRoot":"","sources":["../../../src/SelectionMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAA4B,EAC1B,KAAK,mBAAmB,EAEzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,yCAAyC;IACzC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAExC;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhE;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC;IAET,OAAO,CAAC,EAAE;QACR,6CAA6C;QAC7C,QAAQ,CAAC,EAAE,mBAAmB,CAAC;KAChC,CAAC;IAEF,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,CAkE3E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-platform-components",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A cross-platform toolkit of native UI components for React Native.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -59,8 +59,12 @@ export type DatePickerProps = {
59
59
  };
60
60
  };
61
61
 
62
- function dateToMsOrMinusOne(d: Date | null | undefined): number {
63
- return d ? d.getTime() : -1;
62
+ // Sentinel value for "no date". Using MIN_SAFE_INTEGER ensures we don't
63
+ // conflict with valid negative timestamps (dates before 1970).
64
+ const NO_DATE_SENTINEL = Number.MIN_SAFE_INTEGER;
65
+
66
+ function dateToMsOrSentinel(d: Date | null | undefined): number {
67
+ return d ? d.getTime() : NO_DATE_SENTINEL;
64
68
  }
65
69
 
66
70
  function normalizeVisible(
@@ -113,9 +117,9 @@ export function DatePicker(props: DatePickerProps): React.ReactElement {
113
117
  presentation,
114
118
  visible: normalizeVisible(presentation, visible) as any,
115
119
 
116
- dateMs: dateToMsOrMinusOne(date) as any,
117
- minDateMs: dateToMsOrMinusOne(minDate ?? null) as any,
118
- maxDateMs: dateToMsOrMinusOne(maxDate ?? null) as any,
120
+ dateMs: dateToMsOrSentinel(date) as any,
121
+ minDateMs: dateToMsOrSentinel(minDate ?? null) as any,
122
+ maxDateMs: dateToMsOrSentinel(maxDate ?? null) as any,
119
123
 
120
124
  onConfirm: onConfirm ? handleConfirm : undefined,
121
125
  onClosed: onClosed ? handleClosed : undefined,
@@ -51,7 +51,7 @@ export interface SelectionMenuProps extends ViewProps {
51
51
  ios?: {};
52
52
 
53
53
  android?: {
54
- /** Material preference ("auto" | "m2" | "m3"). */
54
+ /** Material preference ('system' | 'm3'). */
55
55
  material?: AndroidMaterialMode;
56
56
  };
57
57