react-native-platform-components 0.4.1 → 0.5.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 +57 -16
- package/android/src/main/java/com/platformcomponents/PCDatePickerView.kt +39 -21
- package/android/src/main/java/com/platformcomponents/PCDatePickerViewManager.kt +6 -4
- package/android/src/main/java/com/platformcomponents/PCSelectionMenuView.kt +28 -2
- package/ios/PCConstants.swift +9 -2
- package/ios/PCDatePicker.mm +6 -5
- package/ios/PCDatePickerView.swift +37 -10
- package/ios/PCSelectionMenu.swift +141 -49
- package/lib/module/DatePicker.js +16 -13
- package/lib/module/DatePicker.js.map +1 -1
- package/lib/module/DatePickerNativeComponent.ts +5 -4
- package/lib/typescript/src/DatePicker.d.ts.map +1 -1
- package/lib/typescript/src/DatePickerNativeComponent.d.ts +5 -4
- package/lib/typescript/src/DatePickerNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/SelectionMenu.d.ts +1 -1
- package/lib/typescript/src/SelectionMenu.d.ts.map +1 -1
- package/package.json +1 -1
- package/shared/PCSelectionMenuShadowNode-custom.cpp +1 -1
- package/src/DatePicker.tsx +21 -17
- package/src/DatePickerNativeComponent.ts +5 -4
- package/src/SelectionMenu.tsx +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,48 @@
|
|
|
1
1
|
# react-native-platform-components
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/react-native-platform-components)
|
|
4
|
+
[](https://www.npmjs.com/package/react-native-platform-components)
|
|
5
|
+
|
|
6
|
+
<table>
|
|
7
|
+
<tr>
|
|
8
|
+
<td valign="top">
|
|
9
|
+
<table>
|
|
10
|
+
<tr>
|
|
11
|
+
<td align="center"><strong>iOS DatePicker</strong></td>
|
|
12
|
+
<td align="center"><strong>Android DatePicker</strong></td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-datepicker.gif" height="350" /></td>
|
|
16
|
+
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/android-datepicker.gif" height="350" /></td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<td align="center"><strong>iOS SelectionMenu</strong></td>
|
|
20
|
+
<td align="center"><strong>Android SelectionMenu</strong></td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-selectionmenu.gif" height="350" /></td>
|
|
24
|
+
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/android-selectionmenu.gif" height="350" /></td>
|
|
25
|
+
</tr>
|
|
26
|
+
</table>
|
|
27
|
+
</td>
|
|
28
|
+
<td valign="top">
|
|
29
|
+
<blockquote>🚧 In development — not ready for public use.</blockquote>
|
|
30
|
+
<p>High-quality <strong>native UI components for React Native</strong>, implemented with platform-first APIs and exposed through clean, typed JavaScript interfaces.</p>
|
|
31
|
+
<p>This library focuses on <strong>true native behavior</strong>, not JavaScript re-implementations — providing:</p>
|
|
32
|
+
<ul>
|
|
33
|
+
<li><strong>SelectionMenu</strong> – native selection menus (Material on Android, system menus on iOS)</li>
|
|
34
|
+
<li><strong>DatePicker</strong> – native date & time pickers with modal and embedded presentations</li>
|
|
35
|
+
</ul>
|
|
36
|
+
<p>The goal is to provide components that:</p>
|
|
37
|
+
<ul>
|
|
38
|
+
<li>Feel <strong>100% native</strong> on each platform</li>
|
|
39
|
+
<li>Support modern platform design systems (Material 3 on Android, system pickers on iOS)</li>
|
|
40
|
+
<li>Offer <strong>headless</strong> and <strong>inline</strong> modes for maximum layout control</li>
|
|
41
|
+
<li>Integrate cleanly with <strong>React Native Codegen / Fabric</strong></li>
|
|
42
|
+
</ul>
|
|
43
|
+
</td>
|
|
44
|
+
</tr>
|
|
45
|
+
</table>
|
|
18
46
|
|
|
19
47
|
---
|
|
20
48
|
|
|
@@ -183,6 +211,8 @@ Native selection menu with **inline** and **headless** modes.
|
|
|
183
211
|
- **Headless mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
|
|
184
212
|
- **Inline mode** (`inlineMode={true}`): Native picker UI rendered inline. Menu managed internally.
|
|
185
213
|
|
|
214
|
+
> **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.
|
|
215
|
+
|
|
186
216
|
---
|
|
187
217
|
|
|
188
218
|
## DatePicker
|
|
@@ -218,7 +248,7 @@ Native date & time picker using **platform system pickers**.
|
|
|
218
248
|
| Prop | Type | Description |
|
|
219
249
|
|------|------|-------------|
|
|
220
250
|
| `firstDayOfWeek` | `number` | First day of week (1-7, Sunday=1) |
|
|
221
|
-
| `material` | `'system' \| 'm3'` | Material Design style |
|
|
251
|
+
| `material` | `'system' \| 'm3'` | Material Design style (modal only; embedded always uses system picker) |
|
|
222
252
|
| `dialogTitle` | `string` | Custom dialog title |
|
|
223
253
|
| `positiveButtonTitle` | `string` | Custom confirm button text |
|
|
224
254
|
| `negativeButtonTitle` | `string` | Custom cancel button text |
|
|
@@ -235,6 +265,17 @@ Native date & time picker using **platform system pickers**.
|
|
|
235
265
|
|
|
236
266
|
---
|
|
237
267
|
|
|
268
|
+
## Theming
|
|
269
|
+
|
|
270
|
+
This library does not expose theming props. Components inherit their appearance from your app's native platform theme.
|
|
271
|
+
|
|
272
|
+
- **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.
|
|
273
|
+
- **Android**: Components respect your app's Material Theme. Customize via your `styles.xml` or Material 3 theme configuration.
|
|
274
|
+
|
|
275
|
+
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.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
238
279
|
## Contributing
|
|
239
280
|
|
|
240
281
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
257
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
75
|
+
view.applyMaxDateMs(if (value > noDateSentinel) value.toLong() else null)
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
// --- platform objects ---
|
|
@@ -51,6 +51,7 @@ class PCSelectionMenuView(context: Context) : FrameLayout(context) {
|
|
|
51
51
|
private var headlessMenuShowing = false
|
|
52
52
|
private var headlessDismissProgrammatic = false
|
|
53
53
|
private var headlessDismissAfterSelect = false
|
|
54
|
+
private var headlessOpenToken = 0
|
|
54
55
|
|
|
55
56
|
init {
|
|
56
57
|
minimumHeight = 0
|
|
@@ -129,6 +130,13 @@ class PCSelectionMenuView(context: Context) : FrameLayout(context) {
|
|
|
129
130
|
if (anchorMode == newMode) return
|
|
130
131
|
anchorMode = newMode
|
|
131
132
|
Log.d(TAG, "applyAnchorMode anchorMode=$anchorMode")
|
|
133
|
+
if (anchorMode != "headless") {
|
|
134
|
+
headlessOpenToken += 1
|
|
135
|
+
if (headlessMenuShowing) {
|
|
136
|
+
headlessDismissProgrammatic = true
|
|
137
|
+
headlessMenu?.dismiss()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
132
140
|
rebuildUI()
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -138,11 +146,13 @@ class PCSelectionMenuView(context: Context) : FrameLayout(context) {
|
|
|
138
146
|
else -> "closed"
|
|
139
147
|
}
|
|
140
148
|
Log.d(TAG, "applyVisible visible=$visible anchorMode=$anchorMode")
|
|
149
|
+
headlessOpenToken += 1
|
|
150
|
+
val token = headlessOpenToken
|
|
141
151
|
|
|
142
152
|
if (anchorMode != "headless") return
|
|
143
153
|
|
|
144
154
|
if (visible == "open") {
|
|
145
|
-
presentHeadlessIfNeeded()
|
|
155
|
+
presentHeadlessIfNeeded(token)
|
|
146
156
|
} else {
|
|
147
157
|
Log.d(TAG, "applyVisible close -> dismiss")
|
|
148
158
|
if (headlessMenuShowing) {
|
|
@@ -162,6 +172,10 @@ class PCSelectionMenuView(context: Context) : FrameLayout(context) {
|
|
|
162
172
|
// ---- UI building ----
|
|
163
173
|
|
|
164
174
|
private fun rebuildUI() {
|
|
175
|
+
if (headlessMenuShowing) {
|
|
176
|
+
headlessDismissProgrammatic = true
|
|
177
|
+
headlessMenu?.dismiss()
|
|
178
|
+
}
|
|
165
179
|
inlineText?.dismissDropDown()
|
|
166
180
|
detachInlineDropdownOverlay()
|
|
167
181
|
inlineDropdownOverlay = null
|
|
@@ -465,7 +479,7 @@ class PCSelectionMenuView(context: Context) : FrameLayout(context) {
|
|
|
465
479
|
|
|
466
480
|
// ---- Headless open ----
|
|
467
481
|
|
|
468
|
-
private fun presentHeadlessIfNeeded() {
|
|
482
|
+
private fun presentHeadlessIfNeeded(token: Int) {
|
|
469
483
|
val popup = headlessMenu ?: return
|
|
470
484
|
if (interactivity != "enabled") {
|
|
471
485
|
Log.d(TAG, "presentHeadlessIfNeeded interactivity=$interactivity -> requestClose")
|
|
@@ -473,6 +487,18 @@ class PCSelectionMenuView(context: Context) : FrameLayout(context) {
|
|
|
473
487
|
return
|
|
474
488
|
}
|
|
475
489
|
post {
|
|
490
|
+
if (token != headlessOpenToken) {
|
|
491
|
+
Log.d(TAG, "presentHeadlessIfNeeded stale token -> skip")
|
|
492
|
+
return@post
|
|
493
|
+
}
|
|
494
|
+
if (anchorMode != "headless" || visible != "open") {
|
|
495
|
+
Log.d(TAG, "presentHeadlessIfNeeded no longer open -> skip")
|
|
496
|
+
return@post
|
|
497
|
+
}
|
|
498
|
+
if (interactivity != "enabled") {
|
|
499
|
+
Log.d(TAG, "presentHeadlessIfNeeded disabled -> skip")
|
|
500
|
+
return@post
|
|
501
|
+
}
|
|
476
502
|
if (!isAttachedToWindow) {
|
|
477
503
|
Log.d(TAG, "presentHeadlessIfNeeded not attached -> requestClose")
|
|
478
504
|
onRequestClose?.invoke()
|
package/ios/PCConstants.swift
CHANGED
|
@@ -16,8 +16,15 @@ enum PCConstants {
|
|
|
16
16
|
/// Maximum popover height before scrolling
|
|
17
17
|
static let popoverMaxHeight: CGFloat = 400
|
|
18
18
|
|
|
19
|
-
///
|
|
20
|
-
static let
|
|
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
|
package/ios/PCDatePicker.mm
CHANGED
|
@@ -117,20 +117,21 @@ using namespace facebook::react;
|
|
|
117
117
|
_datePickerView.open = newOpen;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
// dateMs (sentinel -
|
|
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
|
|
124
|
+
(newViewProps.dateMs > kNoDateSentinel) ? @(newViewProps.dateMs) : nil;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
// min/max (sentinel
|
|
127
|
+
// min/max (same sentinel)
|
|
127
128
|
if (oldViewProps.minDateMs != newViewProps.minDateMs) {
|
|
128
129
|
_datePickerView.minDateMs =
|
|
129
|
-
(newViewProps.minDateMs
|
|
130
|
+
(newViewProps.minDateMs > kNoDateSentinel) ? @(newViewProps.minDateMs) : nil;
|
|
130
131
|
}
|
|
131
132
|
if (oldViewProps.maxDateMs != newViewProps.maxDateMs) {
|
|
132
133
|
_datePickerView.maxDateMs =
|
|
133
|
-
(newViewProps.maxDateMs
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -97,6 +98,8 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
97
98
|
private let model = PCSelectionMenuModel()
|
|
98
99
|
private var hostingController: UIHostingController<AnyView>?
|
|
99
100
|
private var headlessMenuView: UIView?
|
|
101
|
+
private var headlessMenuVC: UIViewController?
|
|
102
|
+
private var headlessPresentationToken: Int = 0
|
|
100
103
|
|
|
101
104
|
private var parsedOptions: [PCSelectionMenuOption] {
|
|
102
105
|
options.compactMap { any in
|
|
@@ -131,12 +134,16 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
131
134
|
alpha = disabled ? 0.5 : 1.0
|
|
132
135
|
isUserInteractionEnabled = !disabled
|
|
133
136
|
accessibilityTraits = disabled ? [.notEnabled] : [.button]
|
|
137
|
+
if disabled {
|
|
138
|
+
dismissHeadlessIfNeeded()
|
|
139
|
+
}
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
// MARK: - Inline vs headless
|
|
137
143
|
|
|
138
144
|
private func updateAnchorMode() {
|
|
139
145
|
if anchorMode == "inline" {
|
|
146
|
+
dismissHeadlessIfNeeded()
|
|
140
147
|
uninstallHeadlessIfNeeded()
|
|
141
148
|
installInlineIfNeeded()
|
|
142
149
|
sync()
|
|
@@ -247,15 +254,22 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
247
254
|
|
|
248
255
|
private func updatePresentation() {
|
|
249
256
|
guard anchorMode != "inline" else { return }
|
|
250
|
-
|
|
257
|
+
headlessPresentationToken += 1
|
|
251
258
|
|
|
252
|
-
if visible == "open" {
|
|
253
|
-
presentHeadlessMenuIfNeeded()
|
|
259
|
+
if visible == "open" && interactivity != "disabled" {
|
|
260
|
+
presentHeadlessMenuIfNeeded(token: headlessPresentationToken)
|
|
261
|
+
} else {
|
|
262
|
+
dismissHeadlessIfNeeded()
|
|
254
263
|
}
|
|
255
|
-
// Note: dismissal is handled by the menu itself calling onRequestClose
|
|
256
264
|
}
|
|
257
265
|
|
|
258
|
-
private func
|
|
266
|
+
private func dismissHeadlessIfNeeded() {
|
|
267
|
+
guard let vc = headlessMenuVC else { return }
|
|
268
|
+
headlessMenuVC = nil
|
|
269
|
+
vc.dismiss(animated: true)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private func presentHeadlessMenuIfNeeded(token: Int) {
|
|
259
273
|
guard headlessMenuView != nil else { return }
|
|
260
274
|
guard let vc = nearestViewController() else { return }
|
|
261
275
|
|
|
@@ -263,7 +277,15 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
263
277
|
guard !opts.isEmpty else { return }
|
|
264
278
|
|
|
265
279
|
logger.debug("presentHeadlessMenuIfNeeded: scheduling presentation with \(opts.count) options")
|
|
266
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + PCConstants.headlessPresentationDelay) {
|
|
280
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + PCConstants.headlessPresentationDelay) { [weak self] in
|
|
281
|
+
guard let self else { return }
|
|
282
|
+
guard self.headlessPresentationToken == token else { return }
|
|
283
|
+
guard self.visible == "open" else { return }
|
|
284
|
+
guard self.anchorMode != "inline" else { return }
|
|
285
|
+
guard self.interactivity != "disabled" else { return }
|
|
286
|
+
guard self.headlessMenuVC == nil else { return }
|
|
287
|
+
guard self.window != nil else { return }
|
|
288
|
+
|
|
267
289
|
let menuVC = PCMenuViewController(
|
|
268
290
|
options: opts,
|
|
269
291
|
onSelect: { [weak self] idx in
|
|
@@ -276,26 +298,50 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
276
298
|
onCancel: { [weak self] in
|
|
277
299
|
logger.debug("headless menu cancelled")
|
|
278
300
|
self?.onRequestClose?()
|
|
301
|
+
},
|
|
302
|
+
onDismiss: { [weak self] in
|
|
303
|
+
self?.headlessMenuVC = nil
|
|
279
304
|
}
|
|
280
305
|
)
|
|
281
306
|
|
|
282
|
-
|
|
307
|
+
// Calculate menu position relative to source view
|
|
308
|
+
let sourceFrame = self.convert(self.bounds, to: vc.view)
|
|
309
|
+
let screenBounds = vc.view.bounds
|
|
283
310
|
let popoverHeight = min(
|
|
284
311
|
CGFloat(opts.count) * PCConstants.popoverRowHeight + PCConstants.popoverVerticalPadding,
|
|
285
312
|
PCConstants.popoverMaxHeight
|
|
286
313
|
)
|
|
287
|
-
|
|
314
|
+
let spacing: CGFloat = 8
|
|
315
|
+
|
|
316
|
+
// Check if menu fits below the source view
|
|
317
|
+
let rowHeight = PCConstants.popoverRowHeight
|
|
318
|
+
let wouldExtendBeyondBottom = sourceFrame.maxY + spacing + popoverHeight > screenBounds.maxY - 20
|
|
319
|
+
|
|
320
|
+
let menuY: CGFloat
|
|
321
|
+
if wouldExtendBeyondBottom {
|
|
322
|
+
// Position above the source view (no overlap offset)
|
|
323
|
+
menuY = sourceFrame.minY - spacing - popoverHeight
|
|
324
|
+
} else {
|
|
325
|
+
// Position below, but shift up by one row to overlap trigger (like system menu)
|
|
326
|
+
menuY = sourceFrame.maxY + spacing - rowHeight
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Center horizontally, but keep within screen bounds
|
|
330
|
+
var menuX = sourceFrame.midX - PCConstants.popoverWidth / 2
|
|
331
|
+
menuX = max(16, min(menuX, screenBounds.maxX - PCConstants.popoverWidth - 16))
|
|
332
|
+
|
|
333
|
+
let menuFrame = CGRect(
|
|
334
|
+
x: menuX,
|
|
335
|
+
y: menuY,
|
|
288
336
|
width: PCConstants.popoverWidth,
|
|
289
337
|
height: popoverHeight
|
|
290
338
|
)
|
|
291
339
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
popover.permittedArrowDirections = [] // Remove arrow to match inline
|
|
296
|
-
popover.delegate = menuVC
|
|
297
|
-
}
|
|
340
|
+
menuVC.modalPresentationStyle = .overCurrentContext
|
|
341
|
+
menuVC.modalTransitionStyle = .crossDissolve
|
|
342
|
+
menuVC.menuFrame = menuFrame
|
|
298
343
|
|
|
344
|
+
self.headlessMenuVC = menuVC
|
|
299
345
|
vc.present(menuVC, animated: true)
|
|
300
346
|
}
|
|
301
347
|
}
|
|
@@ -326,18 +372,52 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
326
372
|
}
|
|
327
373
|
}
|
|
328
374
|
|
|
375
|
+
// MARK: - Glass Menu Cell
|
|
376
|
+
|
|
377
|
+
private class PCGlassMenuCell: UITableViewCell {
|
|
378
|
+
static let reuseIdentifier = "PCGlassMenuCell"
|
|
379
|
+
|
|
380
|
+
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
381
|
+
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
382
|
+
setupCell()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
required init?(coder: NSCoder) {
|
|
386
|
+
super.init(coder: coder)
|
|
387
|
+
setupCell()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private func setupCell() {
|
|
391
|
+
backgroundColor = .clear
|
|
392
|
+
contentView.backgroundColor = .clear
|
|
393
|
+
selectionStyle = .none
|
|
394
|
+
textLabel?.font = .preferredFont(forTextStyle: .body)
|
|
395
|
+
textLabel?.adjustsFontForContentSizeCategory = true
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
329
399
|
// MARK: - Custom Menu View Controller (matches SwiftUI Menu appearance)
|
|
330
400
|
|
|
331
|
-
private class PCMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
|
|
401
|
+
private class PCMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
|
332
402
|
private let options: [PCSelectionMenuOption]
|
|
333
403
|
private let onSelect: (Int) -> Void
|
|
334
404
|
private let onCancel: () -> Void
|
|
405
|
+
private let onDismiss: () -> Void
|
|
335
406
|
private var tableView: UITableView!
|
|
407
|
+
private var menuContainer: UIView!
|
|
408
|
+
|
|
409
|
+
var menuFrame: CGRect = .zero
|
|
336
410
|
|
|
337
|
-
init(
|
|
411
|
+
init(
|
|
412
|
+
options: [PCSelectionMenuOption],
|
|
413
|
+
onSelect: @escaping (Int) -> Void,
|
|
414
|
+
onCancel: @escaping () -> Void,
|
|
415
|
+
onDismiss: @escaping () -> Void
|
|
416
|
+
) {
|
|
338
417
|
self.options = options
|
|
339
418
|
self.onSelect = onSelect
|
|
340
419
|
self.onCancel = onCancel
|
|
420
|
+
self.onDismiss = onDismiss
|
|
341
421
|
super.init(nibName: nil, bundle: nil)
|
|
342
422
|
}
|
|
343
423
|
|
|
@@ -348,37 +428,61 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
|
|
|
348
428
|
override func viewDidLoad() {
|
|
349
429
|
super.viewDidLoad()
|
|
350
430
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
431
|
+
view.backgroundColor = .clear
|
|
432
|
+
|
|
433
|
+
// Tap outside to dismiss
|
|
434
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap))
|
|
435
|
+
tapGesture.cancelsTouchesInView = false
|
|
436
|
+
view.addGestureRecognizer(tapGesture)
|
|
437
|
+
|
|
438
|
+
// Menu container positioned at menuFrame
|
|
439
|
+
menuContainer = UIView(frame: menuFrame)
|
|
440
|
+
menuContainer.backgroundColor = .clear
|
|
441
|
+
menuContainer.layer.cornerRadius = 12
|
|
442
|
+
menuContainer.clipsToBounds = true
|
|
443
|
+
view.addSubview(menuContainer)
|
|
444
|
+
|
|
445
|
+
// Use liquid glass on iOS 26+, fall back to system material blur on older versions
|
|
446
|
+
let effectView: UIVisualEffectView
|
|
447
|
+
if #available(iOS 26, *) {
|
|
448
|
+
var glassEffect = UIGlassEffect()
|
|
449
|
+
glassEffect.isInteractive = true
|
|
450
|
+
effectView = UIVisualEffectView(effect: glassEffect)
|
|
451
|
+
} else {
|
|
452
|
+
let blurEffect = UIBlurEffect(style: .systemMaterial)
|
|
453
|
+
effectView = UIVisualEffectView(effect: blurEffect)
|
|
454
|
+
}
|
|
455
|
+
effectView.frame = menuContainer.bounds
|
|
456
|
+
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
457
|
+
menuContainer.addSubview(effectView)
|
|
356
458
|
|
|
357
|
-
tableView = UITableView(frame: .
|
|
459
|
+
tableView = UITableView(frame: menuContainer.bounds, style: .plain)
|
|
358
460
|
tableView.delegate = self
|
|
359
461
|
tableView.dataSource = self
|
|
360
|
-
tableView.register(
|
|
462
|
+
tableView.register(PCGlassMenuCell.self, forCellReuseIdentifier: PCGlassMenuCell.reuseIdentifier)
|
|
361
463
|
tableView.backgroundColor = .clear
|
|
362
464
|
tableView.separatorStyle = .none
|
|
363
465
|
tableView.isScrollEnabled = true
|
|
364
466
|
tableView.rowHeight = PCConstants.popoverRowHeight
|
|
467
|
+
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
365
468
|
let verticalPad = PCConstants.popoverVerticalPadding / 2
|
|
366
469
|
tableView.contentInset = UIEdgeInsets(top: verticalPad, left: 0, bottom: verticalPad, right: 0)
|
|
367
|
-
tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
368
470
|
|
|
369
|
-
|
|
471
|
+
effectView.contentView.addSubview(tableView)
|
|
472
|
+
}
|
|
370
473
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
474
|
+
override func viewDidDisappear(_ animated: Bool) {
|
|
475
|
+
super.viewDidDisappear(animated)
|
|
476
|
+
onDismiss()
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
@objc private func handleBackgroundTap(_ gesture: UITapGestureRecognizer) {
|
|
480
|
+
let location = gesture.location(in: view)
|
|
481
|
+
if !menuContainer.frame.contains(location) {
|
|
482
|
+
dismiss(animated: true) { [weak self] in
|
|
483
|
+
self?.onCancel()
|
|
484
|
+
}
|
|
485
|
+
}
|
|
382
486
|
}
|
|
383
487
|
|
|
384
488
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
@@ -386,11 +490,8 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
|
|
|
386
490
|
}
|
|
387
491
|
|
|
388
492
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
389
|
-
let cell = tableView.dequeueReusableCell(withIdentifier:
|
|
493
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: PCGlassMenuCell.reuseIdentifier, for: indexPath)
|
|
390
494
|
cell.textLabel?.text = options[indexPath.row].label
|
|
391
|
-
cell.textLabel?.font = .systemFont(ofSize: 17)
|
|
392
|
-
cell.backgroundColor = .clear
|
|
393
|
-
cell.selectionStyle = .default
|
|
394
495
|
return cell
|
|
395
496
|
}
|
|
396
497
|
|
|
@@ -400,13 +501,4 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
|
|
|
400
501
|
self?.onSelect(indexPath.row)
|
|
401
502
|
}
|
|
402
503
|
}
|
|
403
|
-
|
|
404
|
-
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
405
|
-
onCancel()
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
|
|
409
|
-
return .none
|
|
410
|
-
}
|
|
411
504
|
}
|
|
412
|
-
|
package/lib/module/DatePicker.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
// DatePicker.tsx
|
|
4
|
-
import React, { useCallback
|
|
4
|
+
import React, { useCallback } 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
|
-
|
|
9
|
-
|
|
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
|
+
if (!d) return NO_DATE_SENTINEL;
|
|
13
|
+
const ms = d.getTime();
|
|
14
|
+
return Number.isFinite(ms) ? ms : NO_DATE_SENTINEL;
|
|
10
15
|
}
|
|
11
16
|
function normalizeVisible(presentation, visible) {
|
|
12
17
|
// Only meaningful in modal presentation. Keep undefined for inline to avoid noise.
|
|
@@ -30,13 +35,13 @@ export function DatePicker(props) {
|
|
|
30
35
|
android,
|
|
31
36
|
testID
|
|
32
37
|
} = props;
|
|
38
|
+
const isModal = presentation === 'modal';
|
|
33
39
|
const handleConfirm = useCallback(e => {
|
|
34
40
|
onConfirm?.(new Date(e.nativeEvent.timestampMs));
|
|
35
41
|
}, [onConfirm]);
|
|
36
42
|
const handleClosed = useCallback(() => {
|
|
37
43
|
onClosed?.();
|
|
38
44
|
}, [onClosed]);
|
|
39
|
-
const styles = useMemo(() => createStyles(), []);
|
|
40
45
|
const nativeProps = {
|
|
41
46
|
style: [styles.picker, style],
|
|
42
47
|
mode,
|
|
@@ -44,11 +49,11 @@ export function DatePicker(props) {
|
|
|
44
49
|
timeZoneName,
|
|
45
50
|
presentation,
|
|
46
51
|
visible: normalizeVisible(presentation, visible),
|
|
47
|
-
dateMs:
|
|
48
|
-
minDateMs:
|
|
49
|
-
maxDateMs:
|
|
52
|
+
dateMs: dateToMsOrSentinel(date),
|
|
53
|
+
minDateMs: dateToMsOrSentinel(minDate),
|
|
54
|
+
maxDateMs: dateToMsOrSentinel(maxDate),
|
|
50
55
|
onConfirm: onConfirm ? handleConfirm : undefined,
|
|
51
|
-
onClosed: onClosed ? handleClosed : undefined,
|
|
56
|
+
onClosed: isModal && onClosed ? handleClosed : undefined,
|
|
52
57
|
ios: ios ? {
|
|
53
58
|
preferredStyle: ios.preferredStyle,
|
|
54
59
|
countDownDurationSeconds: ios.countDownDurationSeconds,
|
|
@@ -68,9 +73,7 @@ export function DatePicker(props) {
|
|
|
68
73
|
...nativeProps
|
|
69
74
|
});
|
|
70
75
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
picker: {}
|
|
78
|
+
});
|
|
76
79
|
//# sourceMappingURL=DatePicker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useCallback","
|
|
1
|
+
{"version":3,"names":["React","useCallback","StyleSheet","NativeDatePicker","jsx","_jsx","NO_DATE_SENTINEL","Number","MIN_SAFE_INTEGER","dateToMsOrSentinel","d","ms","getTime","isFinite","normalizeVisible","presentation","visible","undefined","DatePicker","props","style","date","minDate","maxDate","locale","timeZoneName","mode","onConfirm","onClosed","ios","android","testID","isModal","handleConfirm","e","Date","nativeEvent","timestampMs","handleClosed","nativeProps","styles","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,QAAQ,OAAO;AAE1C,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,IAAI,CAACA,CAAC,EAAE,OAAOJ,gBAAgB;EAC/B,MAAMK,EAAE,GAAGD,CAAC,CAACE,OAAO,CAAC,CAAC;EACtB,OAAOL,MAAM,CAACM,QAAQ,CAACF,EAAE,CAAC,GAAGA,EAAE,GAAGL,gBAAgB;AACpD;AAEA,SAASQ,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,OAAO,GAAGjB,YAAY,KAAK,OAAO;EAExC,MAAMkB,aAAa,GAAGhC,WAAW,CAC9BiC,CAAwC,IAAK;IAC5CP,SAAS,GAAG,IAAIQ,IAAI,CAACD,CAAC,CAACE,WAAW,CAACC,WAAW,CAAC,CAAC;EAClD,CAAC,EACD,CAACV,SAAS,CACZ,CAAC;EAED,MAAMW,YAAY,GAAGrC,WAAW,CAAC,MAAM;IACrC2B,QAAQ,GAAG,CAAC;EACd,CAAC,EAAE,CAACA,QAAQ,CAAC,CAAC;EAEd,MAAMW,WAAkC,GAAG;IACzCnB,KAAK,EAAE,CAACoB,MAAM,CAACC,MAAM,EAAErB,KAAK,CAAC;IAE7BM,IAAI;IACJF,MAAM;IACNC,YAAY;IAEZV,YAAY;IACZC,OAAO,EAAEF,gBAAgB,CAACC,YAAY,EAAEC,OAAO,CAAC;IAEhD0B,MAAM,EAAEjC,kBAAkB,CAACY,IAAI,CAAC;IAChCsB,SAAS,EAAElC,kBAAkB,CAACa,OAAO,CAAC;IACtCsB,SAAS,EAAEnC,kBAAkB,CAACc,OAAO,CAAC;IAEtCI,SAAS,EAAEA,SAAS,GAAGM,aAAa,GAAGhB,SAAS;IAChDW,QAAQ,EAAEI,OAAO,IAAIJ,QAAQ,GAAGU,YAAY,GAAGrB,SAAS;IAExDY,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,QAAQ;MAC1BC,WAAW,EAAErB,OAAO,CAACqB,WAAW;MAChCC,mBAAmB,EAAEtB,OAAO,CAACsB,mBAAmB;MAChDC,mBAAmB,EAAEvB,OAAO,CAACuB;IAC/B,CAAC,GACDpC;EACN,CAAC;EAED,oBAAOZ,IAAA,CAACF,gBAAgB;IAAC4B,MAAM,EAAEA,MAAO;IAAA,GAAKQ;EAAW,CAAG,CAAC;AAC9D;AAEA,MAAMC,MAAM,GAAGtC,UAAU,CAACoD,MAAM,CAAC;EAC/Bb,MAAM,EAAE,CAAC;AACX,CAAC,CAAC","ignoreList":[]}
|
|
@@ -35,14 +35,15 @@ export type MacOSProps = Readonly<{}>;
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Sentinel convention:
|
|
38
|
-
* -
|
|
38
|
+
* - `Number.MIN_SAFE_INTEGER` means "no value / unbounded / unset".
|
|
39
|
+
* (Allows negative timestamps for pre-1970 dates.)
|
|
39
40
|
*/
|
|
40
41
|
export type CommonProps = {
|
|
41
42
|
mode?: string; // DatePickerMode
|
|
42
43
|
|
|
43
|
-
dateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
44
|
-
minDateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
45
|
-
maxDateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
44
|
+
dateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
45
|
+
minDateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
46
|
+
maxDateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
46
47
|
|
|
47
48
|
locale?: string;
|
|
48
49
|
timeZoneName?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/DatePicker.tsx"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/DatePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAC3C,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;AAqBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAqErE"}
|
|
@@ -25,13 +25,14 @@ export type WindowsProps = Readonly<{}>;
|
|
|
25
25
|
export type MacOSProps = Readonly<{}>;
|
|
26
26
|
/**
|
|
27
27
|
* Sentinel convention:
|
|
28
|
-
* -
|
|
28
|
+
* - `Number.MIN_SAFE_INTEGER` means "no value / unbounded / unset".
|
|
29
|
+
* (Allows negative timestamps for pre-1970 dates.)
|
|
29
30
|
*/
|
|
30
31
|
export type CommonProps = {
|
|
31
32
|
mode?: string;
|
|
32
|
-
dateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
33
|
-
minDateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
34
|
-
maxDateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
33
|
+
dateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
34
|
+
minDateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
35
|
+
maxDateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
35
36
|
locale?: string;
|
|
36
37
|
timeZoneName?: string;
|
|
37
38
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePickerNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/DatePickerNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG5D,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,YAAY,CAAC,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAChF,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,UAAU,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC/E,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,QAAQ,GAAG;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;IAC/C,cAAc,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,cAAc,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AACpC,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AACxC,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AAEtC
|
|
1
|
+
{"version":3,"file":"DatePickerNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/DatePickerNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG5D,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,YAAY,CAAC,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAChF,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,UAAU,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC/E,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,QAAQ,GAAG;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;IAC/C,cAAc,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,cAAc,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AACpC,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AACxC,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;AAEtC;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,MAAM,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAClE,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,gBAAgB,CAAC,CAAC;IACrE,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAErE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,WAAY,SAAQ,SAAS,EAAE,WAAW;IACzD,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,SAAS,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5D;;AAED,wBAAmE"}
|
|
@@ -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,
|
|
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
package/src/DatePicker.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// DatePicker.tsx
|
|
2
|
-
import React, { useCallback
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
3
|
import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
|
|
4
4
|
import { StyleSheet } from 'react-native';
|
|
5
5
|
|
|
@@ -59,8 +59,14 @@ export type DatePickerProps = {
|
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
if (!d) return NO_DATE_SENTINEL;
|
|
68
|
+
const ms = d.getTime();
|
|
69
|
+
return Number.isFinite(ms) ? ms : NO_DATE_SENTINEL;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
function normalizeVisible(
|
|
@@ -90,6 +96,8 @@ export function DatePicker(props: DatePickerProps): React.ReactElement {
|
|
|
90
96
|
testID,
|
|
91
97
|
} = props;
|
|
92
98
|
|
|
99
|
+
const isModal = presentation === 'modal';
|
|
100
|
+
|
|
93
101
|
const handleConfirm = useCallback(
|
|
94
102
|
(e: NativeSyntheticEvent<DateChangeEvent>) => {
|
|
95
103
|
onConfirm?.(new Date(e.nativeEvent.timestampMs));
|
|
@@ -101,24 +109,22 @@ export function DatePicker(props: DatePickerProps): React.ReactElement {
|
|
|
101
109
|
onClosed?.();
|
|
102
110
|
}, [onClosed]);
|
|
103
111
|
|
|
104
|
-
const styles = useMemo(() => createStyles(), []);
|
|
105
|
-
|
|
106
112
|
const nativeProps: NativeDatePickerProps = {
|
|
107
|
-
style: [styles.picker, style]
|
|
113
|
+
style: [styles.picker, style],
|
|
108
114
|
|
|
109
115
|
mode,
|
|
110
116
|
locale,
|
|
111
117
|
timeZoneName,
|
|
112
118
|
|
|
113
119
|
presentation,
|
|
114
|
-
visible: normalizeVisible(presentation, visible)
|
|
120
|
+
visible: normalizeVisible(presentation, visible),
|
|
115
121
|
|
|
116
|
-
dateMs:
|
|
117
|
-
minDateMs:
|
|
118
|
-
maxDateMs:
|
|
122
|
+
dateMs: dateToMsOrSentinel(date),
|
|
123
|
+
minDateMs: dateToMsOrSentinel(minDate),
|
|
124
|
+
maxDateMs: dateToMsOrSentinel(maxDate),
|
|
119
125
|
|
|
120
126
|
onConfirm: onConfirm ? handleConfirm : undefined,
|
|
121
|
-
onClosed: onClosed ? handleClosed : undefined,
|
|
127
|
+
onClosed: isModal && onClosed ? handleClosed : undefined,
|
|
122
128
|
|
|
123
129
|
ios: ios
|
|
124
130
|
? {
|
|
@@ -132,7 +138,7 @@ export function DatePicker(props: DatePickerProps): React.ReactElement {
|
|
|
132
138
|
android: android
|
|
133
139
|
? {
|
|
134
140
|
firstDayOfWeek: android.firstDayOfWeek,
|
|
135
|
-
material: android.material
|
|
141
|
+
material: android.material,
|
|
136
142
|
dialogTitle: android.dialogTitle,
|
|
137
143
|
positiveButtonTitle: android.positiveButtonTitle,
|
|
138
144
|
negativeButtonTitle: android.negativeButtonTitle,
|
|
@@ -143,8 +149,6 @@ export function DatePicker(props: DatePickerProps): React.ReactElement {
|
|
|
143
149
|
return <NativeDatePicker testID={testID} {...nativeProps} />;
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
});
|
|
150
|
-
}
|
|
152
|
+
const styles = StyleSheet.create({
|
|
153
|
+
picker: {},
|
|
154
|
+
});
|
|
@@ -35,14 +35,15 @@ export type MacOSProps = Readonly<{}>;
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Sentinel convention:
|
|
38
|
-
* -
|
|
38
|
+
* - `Number.MIN_SAFE_INTEGER` means "no value / unbounded / unset".
|
|
39
|
+
* (Allows negative timestamps for pre-1970 dates.)
|
|
39
40
|
*/
|
|
40
41
|
export type CommonProps = {
|
|
41
42
|
mode?: string; // DatePickerMode
|
|
42
43
|
|
|
43
|
-
dateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
44
|
-
minDateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
45
|
-
maxDateMs?: CodegenTypes.WithDefault<TimestampMs, -
|
|
44
|
+
dateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
45
|
+
minDateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
46
|
+
maxDateMs?: CodegenTypes.WithDefault<TimestampMs, -9007199254740991>;
|
|
46
47
|
|
|
47
48
|
locale?: string;
|
|
48
49
|
timeZoneName?: string;
|
package/src/SelectionMenu.tsx
CHANGED