react-native-nitro-modal 0.3.0-rc → 0.3.1-rc
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 +5 -1
- package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +73 -9
- package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +4 -0
- package/android/src/main/java/com/margelo/nitro/nitromodal/ModalConfig.kt +4 -0
- package/ios/HybridNitroModal.swift +5 -1
- package/ios/ModalConfig.swift +4 -0
- package/ios/SheetView.swift +82 -10
- package/lib/module/NitroModal.js +16 -1
- package/lib/module/NitroModal.js.map +1 -1
- package/lib/module/NitroModalView.web.js +32 -2
- package/lib/module/NitroModalView.web.js.map +1 -1
- package/lib/module/web/ModalConfig.js +3 -1
- package/lib/module/web/ModalConfig.js.map +1 -1
- package/lib/module/web/ModalPresenter.js.map +1 -1
- package/lib/module/web/SheetPresenter.js +66 -7
- package/lib/module/web/SheetPresenter.js.map +1 -1
- package/lib/module/web/dom.js +3 -0
- package/lib/module/web/dom.js.map +1 -1
- package/lib/typescript/src/NitroModal.d.ts +10 -1
- package/lib/typescript/src/NitroModal.d.ts.map +1 -1
- package/lib/typescript/src/NitroModal.nitro.d.ts +8 -0
- package/lib/typescript/src/NitroModal.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroModalView.web.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalConfig.d.ts +4 -0
- package/lib/typescript/src/web/ModalConfig.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalPresenter.d.ts +4 -0
- package/lib/typescript/src/web/ModalPresenter.d.ts.map +1 -1
- package/lib/typescript/src/web/SheetPresenter.d.ts +12 -1
- package/lib/typescript/src/web/SheetPresenter.d.ts.map +1 -1
- package/lib/typescript/src/web/dom.d.ts +2 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroModalSpec.cpp +18 -0
- package/nitrogen/generated/android/c++/JHybridNitroModalSpec.hpp +4 -0
- package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.cpp +10 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/HybridNitroModalSpec.kt +12 -0
- package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Bridge.hpp +15 -0
- package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/c++/views/HybridNitroModalComponent.mm +12 -0
- package/nitrogen/generated/ios/swift/HybridNitroModalSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridNitroModalSpec_cxx.swift +48 -0
- package/nitrogen/generated/shared/c++/HybridNitroModalSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridNitroModalSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.cpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.hpp +6 -0
- package/nitrogen/generated/shared/json/NitroModalConfig.json +2 -0
- package/package.json +1 -1
- package/src/NitroModal.nitro.ts +8 -0
- package/src/NitroModal.tsx +25 -0
- package/src/NitroModalView.web.tsx +26 -1
- package/src/web/ModalConfig.ts +6 -0
- package/src/web/ModalPresenter.ts +4 -0
- package/src/web/SheetPresenter.ts +72 -5
- package/src/web/dom.ts +3 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
- 📄 **Native bottom sheets** with multiple detents (`small`, `medium`, `large`, `fitContent` or a height) and swipe-to-dismiss
|
|
21
21
|
- 📌 **Inline sheets** — a persistent sheet that is part of your screen, with the views behind it still usable
|
|
22
|
-
- 🔄 **Pull to refresh** — pulling a sheet down past its lowest detent
|
|
22
|
+
- 🔄 **Pull to refresh** — pulling a sheet down past its lowest detent, with a native activity indicator
|
|
23
23
|
- 🪟 **Centered popups** with native `scale` / `fade` transitions
|
|
24
24
|
- 📏 **Content-sized sheets** — `fitContent` measures your React content and grows with it
|
|
25
25
|
- ⌨️ **Keyboard aware** — the sheet or card follows the keyboard animation (`pan` or `resize`)
|
|
@@ -126,6 +126,7 @@ Detents are listed smallest first. `initialDetentIndex` selects where the sheet
|
|
|
126
126
|
presentation="inline"
|
|
127
127
|
style={{ top: headerHeight }}
|
|
128
128
|
detents={[320, 'large']}
|
|
129
|
+
refreshing={isRefreshing}
|
|
129
130
|
onPullToRefresh={refresh}
|
|
130
131
|
>
|
|
131
132
|
<FlatList data={rows} renderItem={renderRow} nestedScrollEnabled />
|
|
@@ -142,6 +143,7 @@ A sheet hands drags off to the scroll view under the finger:
|
|
|
142
143
|
- Dragging up expands the sheet first, then scrolls the content.
|
|
143
144
|
- Dragging down scrolls the content back to its top. Once the content has scrolled during a drag, that drag never moves the sheet; the next drag collapses it.
|
|
144
145
|
- With `onPullToRefresh` set, a drag that starts with the sheet resting on its lowest detent can pull it further down. Letting go past the threshold calls `onPullToRefresh` (with haptic feedback when it is crossed). This needs a sheet that can't be swiped away: inline, or `dismissOnSwipe={false}`.
|
|
146
|
+
- With `refreshing` set too, an activity indicator shows in the gap the pull uncovers, and the sheet stays pulled down while `refreshing` is `true`, like `RefreshControl`. Set it to `true` in `onPullToRefresh` and back to `false` when the refresh is done. Setting it to `true` yourself shows the indicator without a pull.
|
|
145
147
|
|
|
146
148
|
On Android, scrollable content takes part through nested scrolling, so set `nestedScrollEnabled` on it.
|
|
147
149
|
|
|
@@ -217,6 +219,8 @@ const sheet = useRef<NitroModalRef>(null);
|
|
|
217
219
|
| `backgroundColor` | `ColorValue` | system surface | Background of the sheet/card. |
|
|
218
220
|
| `keyboardBehavior` | `'pan' \| 'resize' \| 'none'` | `'pan'` | How the modal reacts to the software keyboard. |
|
|
219
221
|
| `popupAnimation` | `'scale' \| 'fade' \| 'none'` | `'scale'` | Enter/exit transition of a popup. |
|
|
222
|
+
| `refreshing` | `boolean` | — | A refresh is in progress: the sheet stays pulled down with an activity indicator. See [pull to refresh](#scrolling-content-and-pull-to-refresh). |
|
|
223
|
+
| `refreshIndicatorColor` | `ColorValue` | platform default | Color of the refresh indicator. |
|
|
220
224
|
| `contentContainerStyle` | `StyleProp<ViewStyle>` | — | Style of the view that wraps `children` inside the modal. |
|
|
221
225
|
| `testID` | `string` | — | Applied to the content container. |
|
|
222
226
|
| `ref` | `Ref<NitroModalRef>` | — | Imperative handle. |
|
|
@@ -4,6 +4,7 @@ import android.animation.ValueAnimator
|
|
|
4
4
|
import android.annotation.SuppressLint
|
|
5
5
|
import android.app.Activity
|
|
6
6
|
import android.content.Context
|
|
7
|
+
import android.content.res.ColorStateList
|
|
7
8
|
import android.graphics.Color
|
|
8
9
|
import android.graphics.Outline
|
|
9
10
|
import android.graphics.drawable.GradientDrawable
|
|
@@ -18,7 +19,9 @@ import android.view.ViewOutlineProvider
|
|
|
18
19
|
import android.view.animation.DecelerateInterpolator
|
|
19
20
|
import android.view.animation.PathInterpolator
|
|
20
21
|
import android.widget.FrameLayout
|
|
22
|
+
import android.widget.ProgressBar
|
|
21
23
|
import androidx.activity.BackEventCompat
|
|
24
|
+
import androidx.core.animation.doOnEnd
|
|
22
25
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
23
26
|
import androidx.core.graphics.Insets
|
|
24
27
|
import androidx.core.view.ViewCompat
|
|
@@ -56,6 +59,7 @@ internal class BottomSheetPresenter(
|
|
|
56
59
|
private val coordinator = SheetLayout(activity)
|
|
57
60
|
private val sheet = FrameLayout(activity)
|
|
58
61
|
private val grabber = View(activity)
|
|
62
|
+
private val refreshIndicator = ProgressBar(activity)
|
|
59
63
|
private val behavior = SheetBehavior()
|
|
60
64
|
private val sheetBackground = GradientDrawable()
|
|
61
65
|
private var cornerRadiusPx = 0f
|
|
@@ -72,6 +76,11 @@ internal class BottomSheetPresenter(
|
|
|
72
76
|
|
|
73
77
|
/** How far the sheet is pulled below its lowest detent, in px. */
|
|
74
78
|
private var pullOffset = 0f
|
|
79
|
+
private var pullAnimator: ValueAnimator? = null
|
|
80
|
+
private val refreshThreshold = activity.dpToPx(REFRESH_THRESHOLD_DP)
|
|
81
|
+
|
|
82
|
+
/** The sheet rests below its lowest detent, the indicator spinning above it. */
|
|
83
|
+
private var refreshing = config.refreshing ?: false
|
|
75
84
|
|
|
76
85
|
/** A predictive back gesture was committed; the behavior is sliding the sheet out. */
|
|
77
86
|
private var committingBack = false
|
|
@@ -86,6 +95,8 @@ internal class BottomSheetPresenter(
|
|
|
86
95
|
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
|
|
87
96
|
sheetDragging = true
|
|
88
97
|
coordinator.notifyNativeGesture(started = true)
|
|
98
|
+
// A refreshing sheet follows the finger from its lowest detent.
|
|
99
|
+
animatePullOffset(0f)
|
|
89
100
|
} else if (sheetDragging) {
|
|
90
101
|
sheetDragging = false
|
|
91
102
|
coordinator.notifyNativeGesture(started = false)
|
|
@@ -97,6 +108,7 @@ internal class BottomSheetPresenter(
|
|
|
97
108
|
behavior.isHideable = this@BottomSheetPresenter.config.dismissOnSwipe
|
|
98
109
|
notifyPresented()
|
|
99
110
|
reportRestingTop()
|
|
111
|
+
animatePullOffset(restingPullOffset())
|
|
100
112
|
val index = sortedDetents.getOrNull(position)?.index ?: return
|
|
101
113
|
if (index != lastDetentIndex) {
|
|
102
114
|
lastDetentIndex = index
|
|
@@ -107,6 +119,7 @@ internal class BottomSheetPresenter(
|
|
|
107
119
|
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
|
108
120
|
// slideOffset is -1 when hidden and 0 at the lowest detent.
|
|
109
121
|
backdrop.alpha = (1f + slideOffset).coerceIn(0f, 1f)
|
|
122
|
+
layoutRefreshIndicator()
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
|
@@ -117,8 +130,20 @@ internal class BottomSheetPresenter(
|
|
|
117
130
|
if (r - l != oldR - oldL || b - t != oldB - oldT) coordinator.post { updateDetents() }
|
|
118
131
|
// Detent heights can move the resting sheet without a state change.
|
|
119
132
|
reportRestingTop()
|
|
133
|
+
layoutRefreshIndicator()
|
|
120
134
|
}
|
|
121
135
|
|
|
136
|
+
// Behind the sheet, which uncovers it as it's pulled down.
|
|
137
|
+
refreshIndicator.isIndeterminate = true
|
|
138
|
+
refreshIndicator.visibility = View.INVISIBLE
|
|
139
|
+
refreshIndicator.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
140
|
+
coordinator.addView(
|
|
141
|
+
refreshIndicator,
|
|
142
|
+
CoordinatorLayout.LayoutParams(dp(REFRESH_INDICATOR_SIZE_DP), dp(REFRESH_INDICATOR_SIZE_DP)).apply {
|
|
143
|
+
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
|
|
122
147
|
sheet.background = sheetBackground
|
|
123
148
|
sheet.outlineProvider = object : ViewOutlineProvider() {
|
|
124
149
|
override fun getOutline(view: View, outline: Outline) {
|
|
@@ -207,6 +232,8 @@ internal class BottomSheetPresenter(
|
|
|
207
232
|
|
|
208
233
|
override fun onConfigChanged(previous: ModalConfig) {
|
|
209
234
|
applyAppearance()
|
|
235
|
+
if (previous.refreshing != config.refreshing) setRefreshing(config.refreshing ?: false)
|
|
236
|
+
layoutRefreshIndicator()
|
|
210
237
|
if (presented && !isDismissing) behavior.isHideable = config.dismissOnSwipe
|
|
211
238
|
if (previous.detents != config.detents || previous.keyboardBehavior != config.keyboardBehavior) {
|
|
212
239
|
updateDetents()
|
|
@@ -245,6 +272,7 @@ internal class BottomSheetPresenter(
|
|
|
245
272
|
cornerRadius = activity.dpToPx(2f)
|
|
246
273
|
setColor(Color.argb(0x66, 0x80, 0x80, 0x80))
|
|
247
274
|
}
|
|
275
|
+
refreshIndicator.indeterminateTintList = config.refreshIndicatorColor?.let(ColorStateList::valueOf)
|
|
248
276
|
}
|
|
249
277
|
|
|
250
278
|
/** Recomputes detent heights and maps them onto the behavior's states. */
|
|
@@ -364,6 +392,43 @@ internal class BottomSheetPresenter(
|
|
|
364
392
|
private fun setPullOffset(offset: Float) {
|
|
365
393
|
pullOffset = offset
|
|
366
394
|
applyTranslation()
|
|
395
|
+
layoutRefreshIndicator()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private fun animatePullOffset(target: Float) {
|
|
399
|
+
if (coordinator.isPulling) return
|
|
400
|
+
if (pullAnimator == null && pullOffset == target) return
|
|
401
|
+
pullAnimator?.cancel()
|
|
402
|
+
pullAnimator = ValueAnimator.ofFloat(pullOffset, target).apply {
|
|
403
|
+
duration = PULL_RELEASE_DURATION
|
|
404
|
+
interpolator = DecelerateInterpolator()
|
|
405
|
+
addUpdateListener { setPullOffset(it.animatedValue as Float) }
|
|
406
|
+
doOnEnd { if (pullAnimator === it) pullAnimator = null }
|
|
407
|
+
start()
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Where the sheet rests below its lowest detent: held there while refreshing. */
|
|
412
|
+
private fun restingPullOffset(): Float =
|
|
413
|
+
if (refreshing && behavior.state == states().firstOrNull()) refreshThreshold else 0f
|
|
414
|
+
|
|
415
|
+
private fun setRefreshing(value: Boolean) {
|
|
416
|
+
if (value == refreshing) return
|
|
417
|
+
refreshing = value
|
|
418
|
+
if (presented && !isDismissing) animatePullOffset(restingPullOffset())
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Centered in the gap a pull uncovers below the lowest detent, fading in
|
|
423
|
+
* as the pull nears the threshold.
|
|
424
|
+
*/
|
|
425
|
+
private fun layoutRefreshIndicator() {
|
|
426
|
+
val size = refreshIndicator.layoutParams?.height ?: return
|
|
427
|
+
refreshIndicator.translationY = sheet.top + sheet.translationY - (refreshThreshold + size) / 2
|
|
428
|
+
val progress = if (config.refreshing == null || isDismissing) 0f else (pullOffset / refreshThreshold).coerceIn(0f, 1f)
|
|
429
|
+
refreshIndicator.alpha = progress
|
|
430
|
+
// Hidden, the indeterminate animation stops.
|
|
431
|
+
refreshIndicator.visibility = if (progress > 0f) View.VISIBLE else View.INVISIBLE
|
|
367
432
|
}
|
|
368
433
|
|
|
369
434
|
/**
|
|
@@ -435,14 +500,15 @@ internal class BottomSheetPresenter(
|
|
|
435
500
|
@SuppressLint("ViewConstructor")
|
|
436
501
|
private inner class SheetLayout(context: Context) : CoordinatorLayout(context), ReactPointerEventsView {
|
|
437
502
|
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
|
438
|
-
private val refreshThreshold = context.dpToPx(REFRESH_THRESHOLD_DP)
|
|
439
503
|
private var lastEvent: MotionEvent? = null
|
|
440
504
|
private var downX = 0f
|
|
441
505
|
private var downY = 0f
|
|
442
506
|
private var canPull = false
|
|
443
507
|
private var pulling = false
|
|
444
508
|
private var armed = false
|
|
445
|
-
|
|
509
|
+
|
|
510
|
+
val isPulling: Boolean
|
|
511
|
+
get() = pulling
|
|
446
512
|
|
|
447
513
|
override val pointerEvents: PointerEvents
|
|
448
514
|
get() = PointerEvents.BOX_NONE
|
|
@@ -513,7 +579,7 @@ internal class BottomSheetPresenter(
|
|
|
513
579
|
*/
|
|
514
580
|
private fun canStartPull(x: Float, y: Float): Boolean {
|
|
515
581
|
if (!config.pullToRefreshEnabled || config.dismissOnSwipe || !presented || isDismissing) return false
|
|
516
|
-
if (pullOffset != 0f || behavior.state != states().firstOrNull()) return false
|
|
582
|
+
if (refreshing || pullOffset != 0f || behavior.state != states().firstOrNull()) return false
|
|
517
583
|
val top = sheet.top + sheet.translationY
|
|
518
584
|
if (y < top || x < sheet.left || x >= sheet.right) return false
|
|
519
585
|
return !canScrollUpAt(sheet, x - sheet.left, y - top)
|
|
@@ -524,12 +590,9 @@ internal class BottomSheetPresenter(
|
|
|
524
590
|
armed = false
|
|
525
591
|
canPull = false
|
|
526
592
|
notifyNativeGesture(started = false, event = ev)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
addUpdateListener { setPullOffset(it.animatedValue as Float) }
|
|
531
|
-
start()
|
|
532
|
-
}
|
|
593
|
+
// Settles on the refreshing position; React confirms or ends it.
|
|
594
|
+
if (refresh && config.refreshing != null) refreshing = true
|
|
595
|
+
animatePullOffset(restingPullOffset())
|
|
533
596
|
if (refresh) listener.onPullToRefresh()
|
|
534
597
|
}
|
|
535
598
|
|
|
@@ -572,6 +635,7 @@ internal class BottomSheetPresenter(
|
|
|
572
635
|
|
|
573
636
|
/** How far below its lowest detent the sheet must be pulled to refresh. */
|
|
574
637
|
private const val REFRESH_THRESHOLD_DP = 56f
|
|
638
|
+
private const val REFRESH_INDICATOR_SIZE_DP = 32f
|
|
575
639
|
private const val PULL_RELEASE_DURATION = 300L
|
|
576
640
|
private val THRESHOLD_FEEDBACK =
|
|
577
641
|
if (Build.VERSION.SDK_INT >= 34) {
|
|
@@ -52,6 +52,8 @@ class HybridNitroModal(private val reactContext: ThemedReactContext) :
|
|
|
52
52
|
override var keyboardBehavior: KeyboardBehavior = KeyboardBehavior.PAN
|
|
53
53
|
override var popupAnimation: PopupAnimation = PopupAnimation.SCALE
|
|
54
54
|
override var pullToRefreshEnabled: Boolean = false
|
|
55
|
+
override var refreshing: Boolean? = null
|
|
56
|
+
override var refreshIndicatorColor: Double? = null
|
|
55
57
|
|
|
56
58
|
override var onPresent: (() -> Unit)? = null
|
|
57
59
|
override var onDismiss: ((reason: DismissReason) -> Unit)? = null
|
|
@@ -337,6 +339,8 @@ class HybridNitroModal(private val reactContext: ThemedReactContext) :
|
|
|
337
339
|
keyboardBehavior = keyboardBehavior,
|
|
338
340
|
popupAnimation = popupAnimation,
|
|
339
341
|
pullToRefreshEnabled = pullToRefreshEnabled,
|
|
342
|
+
refreshing = refreshing,
|
|
343
|
+
refreshIndicatorColor = refreshIndicatorColor?.toProcessedColor(),
|
|
340
344
|
)
|
|
341
345
|
}
|
|
342
346
|
|
|
@@ -49,6 +49,10 @@ internal data class ModalConfig(
|
|
|
49
49
|
val keyboardBehavior: KeyboardBehavior = KeyboardBehavior.PAN,
|
|
50
50
|
val popupAnimation: PopupAnimation = PopupAnimation.SCALE,
|
|
51
51
|
val pullToRefreshEnabled: Boolean = false,
|
|
52
|
+
/** A refresh is in progress; `null` shows no refresh indicator. */
|
|
53
|
+
val refreshing: Boolean? = null,
|
|
54
|
+
/** `null` uses the platform default. */
|
|
55
|
+
val refreshIndicatorColor: Int? = null,
|
|
52
56
|
) {
|
|
53
57
|
/** A `bottomSheet` living in the host view's bounds. Popups are always modal. */
|
|
54
58
|
val isInline: Boolean
|
|
@@ -47,6 +47,8 @@ final class HybridNitroModal: HybridNitroModalSpec {
|
|
|
47
47
|
var keyboardBehavior: KeyboardBehavior = .pan
|
|
48
48
|
var popupAnimation: PopupAnimation = .scale
|
|
49
49
|
var pullToRefreshEnabled = false
|
|
50
|
+
var refreshing: Bool?
|
|
51
|
+
var refreshIndicatorColor: Double?
|
|
50
52
|
|
|
51
53
|
var onPresent: (() -> Void)?
|
|
52
54
|
var onDismiss: ((DismissReason) -> Void)?
|
|
@@ -326,7 +328,9 @@ final class HybridNitroModal: HybridNitroModalSpec {
|
|
|
326
328
|
contentBackgroundColor: contentBackgroundColor.map(UIColor.init(processedColor:)) ?? .systemBackground,
|
|
327
329
|
keyboardBehavior: keyboardBehavior,
|
|
328
330
|
popupAnimation: popupAnimation,
|
|
329
|
-
pullToRefreshEnabled: pullToRefreshEnabled
|
|
331
|
+
pullToRefreshEnabled: pullToRefreshEnabled,
|
|
332
|
+
refreshing: refreshing,
|
|
333
|
+
refreshIndicatorColor: refreshIndicatorColor.map(UIColor.init(processedColor:))
|
|
330
334
|
)
|
|
331
335
|
}
|
|
332
336
|
|
package/ios/ModalConfig.swift
CHANGED
|
@@ -53,6 +53,10 @@ struct ModalConfig: Equatable {
|
|
|
53
53
|
var keyboardBehavior: KeyboardBehavior = .pan
|
|
54
54
|
var popupAnimation: PopupAnimation = .scale
|
|
55
55
|
var pullToRefreshEnabled = false
|
|
56
|
+
/// A refresh is in progress; `nil` shows no refresh indicator.
|
|
57
|
+
var refreshing: Bool?
|
|
58
|
+
/// `nil` uses the platform default.
|
|
59
|
+
var refreshIndicatorColor: UIColor?
|
|
56
60
|
|
|
57
61
|
/// A `bottomSheet` living in the host view's bounds. Popups are always modal.
|
|
58
62
|
var isInline: Bool {
|
package/ios/SheetView.swift
CHANGED
|
@@ -27,7 +27,8 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
27
27
|
private static let defaultCornerRadius: CGFloat = 16
|
|
28
28
|
private static let regularMaxWidth: CGFloat = 704
|
|
29
29
|
private static let grabberSize = CGSize(width: 36, height: 5)
|
|
30
|
-
/// How far below its lowest detent the sheet must be pulled to refresh
|
|
30
|
+
/// How far below its lowest detent the sheet must be pulled to refresh,
|
|
31
|
+
/// and where it rests while refreshing.
|
|
31
32
|
private static let refreshThreshold: CGFloat = 56
|
|
32
33
|
private static let touchHandlerClass: AnyClass? = NSClassFromString("RCTSurfaceTouchHandler")
|
|
33
34
|
|
|
@@ -36,6 +37,7 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
36
37
|
private let backdrop = BackdropView()
|
|
37
38
|
private let sheetView = UIView()
|
|
38
39
|
private let grabber = UIView()
|
|
40
|
+
private let refreshIndicator = UIActivityIndicatorView(style: .medium)
|
|
39
41
|
private var config: ModalConfig
|
|
40
42
|
private var selectedIndex: Int
|
|
41
43
|
private var keyboardHeight: CGFloat = 0
|
|
@@ -49,6 +51,8 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
49
51
|
private var releaseVelocity: CGFloat = 0
|
|
50
52
|
private var reportedRestingTop: CGFloat?
|
|
51
53
|
private var refreshFeedback: UIImpactFeedbackGenerator?
|
|
54
|
+
/// The sheet rests below its lowest detent, the indicator spinning above it.
|
|
55
|
+
private var isRefreshing = false
|
|
52
56
|
|
|
53
57
|
private var lockedScrollView: UIScrollView?
|
|
54
58
|
private var lockedOffsetY: CGFloat = 0
|
|
@@ -78,6 +82,7 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
78
82
|
self.config = config
|
|
79
83
|
self.isInline = isInline
|
|
80
84
|
selectedIndex = config.clampedInitialDetentIndex
|
|
85
|
+
isRefreshing = config.refreshing ?? false
|
|
81
86
|
super.init(frame: .zero)
|
|
82
87
|
backgroundColor = .clear
|
|
83
88
|
|
|
@@ -104,6 +109,12 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
104
109
|
grabber.backgroundColor = .tertiaryLabel
|
|
105
110
|
grabber.layer.cornerRadius = Self.grabberSize.height / 2
|
|
106
111
|
sheetView.addSubview(grabber)
|
|
112
|
+
|
|
113
|
+
// Behind the sheet, which uncovers it as it's pulled down.
|
|
114
|
+
refreshIndicator.hidesWhenStopped = false
|
|
115
|
+
refreshIndicator.isUserInteractionEnabled = false
|
|
116
|
+
refreshIndicator.alpha = 0
|
|
117
|
+
addSubview(refreshIndicator)
|
|
107
118
|
addSubview(sheetView)
|
|
108
119
|
|
|
109
120
|
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
|
|
@@ -138,8 +149,12 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
138
149
|
// MARK: Presentation
|
|
139
150
|
|
|
140
151
|
func apply(_ newConfig: ModalConfig) {
|
|
152
|
+
let refreshingChanged = newConfig.refreshing != config.refreshing
|
|
141
153
|
config = newConfig
|
|
142
154
|
selectedIndex = min(selectedIndex, max(detents.count - 1, 0))
|
|
155
|
+
if refreshingChanged {
|
|
156
|
+
setRefreshing(newConfig.refreshing ?? false)
|
|
157
|
+
}
|
|
143
158
|
applyAppearance()
|
|
144
159
|
animateToRest()
|
|
145
160
|
}
|
|
@@ -181,7 +196,10 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
181
196
|
damping: offscreen ? 1 : 0.88,
|
|
182
197
|
duration: duration,
|
|
183
198
|
animations: { self.layoutSheet() },
|
|
184
|
-
completion:
|
|
199
|
+
completion: {
|
|
200
|
+
self.stopRefreshIndicatorIfHidden()
|
|
201
|
+
completion()
|
|
202
|
+
}
|
|
185
203
|
)
|
|
186
204
|
}
|
|
187
205
|
|
|
@@ -227,9 +245,21 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
227
245
|
detents.indices.map(restingTop(for:))
|
|
228
246
|
}
|
|
229
247
|
|
|
248
|
+
/// Where the sheet settles on `detents[index]`: held below the lowest
|
|
249
|
+
/// detent while refreshing.
|
|
250
|
+
private func settledTop(for index: Int) -> CGFloat {
|
|
251
|
+
let top = restingTop(for: index)
|
|
252
|
+
guard isRefreshing, let lowest = detentTops.max(), abs(top - lowest) < 1 else { return top }
|
|
253
|
+
return top + Self.refreshThreshold
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private var settledTops: [CGFloat] {
|
|
257
|
+
detents.indices.map(settledTop(for:))
|
|
258
|
+
}
|
|
259
|
+
|
|
230
260
|
private var currentTop: CGFloat {
|
|
231
261
|
if isOffscreen { return bounds.height }
|
|
232
|
-
return dragTop ??
|
|
262
|
+
return dragTop ?? settledTop(for: selectedIndex)
|
|
233
263
|
}
|
|
234
264
|
|
|
235
265
|
private func layoutSheet() {
|
|
@@ -243,9 +273,42 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
243
273
|
size: Self.grabberSize
|
|
244
274
|
)
|
|
245
275
|
backdrop.alpha = backdropAlpha(forTop: top)
|
|
276
|
+
layoutRefreshIndicator(sheetTop: top)
|
|
246
277
|
reportRestingTop()
|
|
247
278
|
}
|
|
248
279
|
|
|
280
|
+
/// Centered in the gap a pull uncovers below the lowest detent, fading in
|
|
281
|
+
/// as the pull nears the threshold.
|
|
282
|
+
private func layoutRefreshIndicator(sheetTop top: CGFloat) {
|
|
283
|
+
let size = refreshIndicator.intrinsicContentSize
|
|
284
|
+
refreshIndicator.bounds = CGRect(origin: .zero, size: size)
|
|
285
|
+
refreshIndicator.center = CGPoint(x: bounds.midX, y: top - Self.refreshThreshold / 2)
|
|
286
|
+
guard config.refreshing != nil, !isOffscreen, let lowest = detentTops.max() else {
|
|
287
|
+
refreshIndicator.alpha = 0
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
let progress = min(max((top - lowest) / Self.refreshThreshold, 0), 1)
|
|
291
|
+
refreshIndicator.alpha = progress
|
|
292
|
+
if progress > 0, !refreshIndicator.isAnimating {
|
|
293
|
+
refreshIndicator.startAnimating()
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Called once the sheet settles, so a fading indicator keeps spinning.
|
|
298
|
+
private func stopRefreshIndicatorIfHidden() {
|
|
299
|
+
if drag == nil, refreshIndicator.alpha == 0 {
|
|
300
|
+
refreshIndicator.stopAnimating()
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private func setRefreshing(_ refreshing: Bool) {
|
|
305
|
+
guard refreshing != isRefreshing else { return }
|
|
306
|
+
isRefreshing = refreshing
|
|
307
|
+
if refreshing, !isOffscreen, config.refreshing != nil {
|
|
308
|
+
refreshIndicator.startAnimating()
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
249
312
|
/// Fully dimmed at every detent; fades out below the lowest one.
|
|
250
313
|
private func backdropAlpha(forTop top: CGFloat) -> CGFloat {
|
|
251
314
|
let bottom = bounds.height
|
|
@@ -259,15 +322,18 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
259
322
|
sheetView.backgroundColor = config.contentBackgroundColor
|
|
260
323
|
sheetView.layer.cornerRadius = config.cornerRadius ?? Self.defaultCornerRadius
|
|
261
324
|
grabber.isHidden = !config.grabberVisible
|
|
325
|
+
refreshIndicator.color = config.refreshIndicatorColor
|
|
262
326
|
}
|
|
263
327
|
|
|
264
328
|
private func animateToRest() {
|
|
265
329
|
guard drag == nil, !isOffscreen else { return }
|
|
266
330
|
dragTop = nil
|
|
267
|
-
UIView.animate(withDuration: 0.3, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction]
|
|
331
|
+
UIView.animate(withDuration: 0.3, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
|
|
268
332
|
self.setNeedsLayout()
|
|
269
333
|
self.layoutIfNeeded()
|
|
270
|
-
}
|
|
334
|
+
}, completion: { _ in
|
|
335
|
+
self.stopRefreshIndicatorIfHidden()
|
|
336
|
+
})
|
|
271
337
|
}
|
|
272
338
|
|
|
273
339
|
private func animate(
|
|
@@ -372,7 +438,7 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
372
438
|
if let scrollView = state.scrollView {
|
|
373
439
|
lockedOffsetY = scrollView.contentOffset.y
|
|
374
440
|
}
|
|
375
|
-
if config.pullToRefreshEnabled, !config.dismissOnSwipe, isSettled,
|
|
441
|
+
if config.pullToRefreshEnabled, !config.dismissOnSwipe, !isRefreshing, isSettled,
|
|
376
442
|
let lowest = detentTops.max(), abs(top - lowest) < 1 {
|
|
377
443
|
state.canRefresh = true
|
|
378
444
|
refreshFeedback = UIImpactFeedbackGenerator(style: .medium)
|
|
@@ -430,8 +496,13 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
430
496
|
drag = nil
|
|
431
497
|
refreshFeedback = nil
|
|
432
498
|
let velocity = state.sheetDrove ? pan.velocity(in: self).y : 0
|
|
499
|
+
let refresh = state.refreshArmed && pan.state == .ended
|
|
500
|
+
if refresh {
|
|
501
|
+
// Settles on the refreshing position; React confirms or ends it.
|
|
502
|
+
setRefreshing(config.refreshing != nil)
|
|
503
|
+
}
|
|
433
504
|
endDrag(at: dragTop ?? currentTop, velocity: velocity, holdingScroll: state.sheetDrove)
|
|
434
|
-
if
|
|
505
|
+
if refresh {
|
|
435
506
|
delegate?.modalPresentationDidPullToRefresh()
|
|
436
507
|
}
|
|
437
508
|
|
|
@@ -444,7 +515,7 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
444
515
|
if !holdingScroll {
|
|
445
516
|
unlockScroll()
|
|
446
517
|
}
|
|
447
|
-
let tops =
|
|
518
|
+
let tops = settledTops
|
|
448
519
|
guard let lowest = tops.max() else { return }
|
|
449
520
|
// Where a fling of this velocity would come to rest.
|
|
450
521
|
let projected = top + velocity * 0.2
|
|
@@ -465,16 +536,17 @@ final class SheetView: UIView, UIGestureRecognizerDelegate {
|
|
|
465
536
|
if changed {
|
|
466
537
|
delegate?.modalPresentationDidChangeDetent(index)
|
|
467
538
|
}
|
|
468
|
-
animate(distance:
|
|
539
|
+
animate(distance: settledTop(for: index) - top, velocity: velocity, animations: { self.layoutSheet() }) { [weak self] in
|
|
469
540
|
guard let self, self.drag == nil else { return }
|
|
470
541
|
self.unlockScroll()
|
|
542
|
+
self.stopRefreshIndicatorIfHidden()
|
|
471
543
|
}
|
|
472
544
|
}
|
|
473
545
|
|
|
474
546
|
/// Rubber-bands above the tallest detent, and below the lowest one when
|
|
475
547
|
/// swiping cannot dismiss.
|
|
476
548
|
private func constrained(_ top: CGFloat) -> CGFloat {
|
|
477
|
-
let tops =
|
|
549
|
+
let tops = settledTops
|
|
478
550
|
guard let minTop = tops.min(), let maxTop = tops.max() else { return top }
|
|
479
551
|
let dimension = max(bounds.height, 1)
|
|
480
552
|
if top < minTop {
|
package/lib/module/NitroModal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { processColor, StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
5
5
|
import { callback, NitroModalView } from './NitroModalView';
|
|
6
6
|
|
|
@@ -49,6 +49,8 @@ export function NitroModal({
|
|
|
49
49
|
onBackdropPress,
|
|
50
50
|
onBackButtonPress,
|
|
51
51
|
onPullToRefresh,
|
|
52
|
+
refreshing,
|
|
53
|
+
refreshIndicatorColor,
|
|
52
54
|
children,
|
|
53
55
|
testID,
|
|
54
56
|
ref
|
|
@@ -72,12 +74,21 @@ export function NitroModal({
|
|
|
72
74
|
// is laid out there so `measure()`, which presses rely on, matches the
|
|
73
75
|
// screen; native cancels the offset out on screen.
|
|
74
76
|
const [restingTop, setRestingTop] = useState(0);
|
|
77
|
+
// Native starts refreshing as soon as a pull is released. Reporting `true`
|
|
78
|
+
// for the render that follows, then `refreshing` again, stops it if the
|
|
79
|
+
// handler didn't start a refresh (unchanged props never reach native).
|
|
80
|
+
const isRefreshControlled = refreshing !== undefined;
|
|
81
|
+
const [pullPending, setPullPending] = useState(false);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (pullPending) setPullPending(false);
|
|
84
|
+
}, [pullPending]);
|
|
75
85
|
const hybridRef = useRef(null);
|
|
76
86
|
|
|
77
87
|
// Native callbacks are created once (stable props, no native updates on
|
|
78
88
|
// re-render) and read the latest handlers from this ref.
|
|
79
89
|
const latest = useRef({
|
|
80
90
|
isControlled,
|
|
91
|
+
isRefreshControlled,
|
|
81
92
|
onPresent,
|
|
82
93
|
onDismiss,
|
|
83
94
|
onDetentChange,
|
|
@@ -88,6 +99,7 @@ export function NitroModal({
|
|
|
88
99
|
useLayoutEffect(() => {
|
|
89
100
|
latest.current = {
|
|
90
101
|
isControlled,
|
|
102
|
+
isRefreshControlled,
|
|
91
103
|
onPresent,
|
|
92
104
|
onDismiss,
|
|
93
105
|
onDetentChange,
|
|
@@ -119,6 +131,7 @@ export function NitroModal({
|
|
|
119
131
|
latest.current.onBackButtonPress?.();
|
|
120
132
|
}),
|
|
121
133
|
onPullToRefresh: callback(() => {
|
|
134
|
+
if (latest.current.isRefreshControlled) setPullPending(true);
|
|
122
135
|
latest.current.onPullToRefresh?.();
|
|
123
136
|
}),
|
|
124
137
|
onRestingTopChange: callback(top => {
|
|
@@ -188,6 +201,8 @@ export function NitroModal({
|
|
|
188
201
|
keyboardBehavior: keyboardBehavior,
|
|
189
202
|
popupAnimation: popupAnimation,
|
|
190
203
|
pullToRefreshEnabled: onPullToRefresh != null,
|
|
204
|
+
refreshing: isRefreshControlled ? refreshing || pullPending : undefined,
|
|
205
|
+
refreshIndicatorColor: toNativeColor(refreshIndicatorColor),
|
|
191
206
|
...nativeCallbacks,
|
|
192
207
|
children: renderContent ? /*#__PURE__*/_jsx(View, {
|
|
193
208
|
collapsable: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useImperativeHandle","useLayoutEffect","useMemo","useRef","useState","processColor","StyleSheet","useWindowDimensions","View","callback","NitroModalView","jsx","_jsx","DEFAULT_DETENTS","BLACK","parseDetent","value","points","Number","isFinite","toNativeColor","color","undefined","processed","NitroModal","isOpen","mode","presentation","style","detents","initialDetentIndex","backdropColor","backdropOpacity","backdropBlur","dismissOnBackdropPress","dismissOnSwipe","dismissOnBackButton","showGrabber","cornerRadius","backgroundColor","keyboardBehavior","popupAnimation","contentContainerStyle","onPresent","onDismiss","onDetentChange","onBackdropPress","onBackButtonPress","onPullToRefresh","children","testID","ref","isControlled","isInline","internalOpen","setInternalOpen","open","sessionActive","setSessionActive","prevOpen","setPrevOpen","renderContent","area","setArea","restingTop","setRestingTop","hybridRef","latest","current","nativeCallbacks","instance","reason","index","onRestingTopChange","top","onContentAreaChange","next","width","height","present","console","warn","dismiss","snapToDetent","detentsKey","join","nativeDetents","Set","split","map","windowSize","contentArea","containerStyle","maxWidth","maxHeight","includes","absoluteFill","styles","host","pointerEvents","backdropBlurRadius","grabberVisible","contentBackgroundColor","pullToRefreshEnabled","collapsable","container","create","position","left"],"sourceRoot":"../../src","sources":["NitroModal.tsx"],"mappings":";;AAAA,SACEA,mBAAmB,EACnBC,eAAe,EACfC,OAAO,EACPC,MAAM,EACNC,QAAQ,QAGH,OAAO;AACd,SACEC,YAAY,EACZC,UAAU,EACVC,mBAAmB,EACnBC,IAAI,QAIC,cAAc;AAWrB,SAASC,QAAQ,EAAEC,cAAc,QAAQ,kBAAkB;;AAE3D;AACA;AACA;AACA;AACA;AACA;AACA;AANA,SAAAC,GAAA,IAAAC,IAAA;
|
|
1
|
+
{"version":3,"names":["useEffect","useImperativeHandle","useLayoutEffect","useMemo","useRef","useState","processColor","StyleSheet","useWindowDimensions","View","callback","NitroModalView","jsx","_jsx","DEFAULT_DETENTS","BLACK","parseDetent","value","points","Number","isFinite","toNativeColor","color","undefined","processed","NitroModal","isOpen","mode","presentation","style","detents","initialDetentIndex","backdropColor","backdropOpacity","backdropBlur","dismissOnBackdropPress","dismissOnSwipe","dismissOnBackButton","showGrabber","cornerRadius","backgroundColor","keyboardBehavior","popupAnimation","contentContainerStyle","onPresent","onDismiss","onDetentChange","onBackdropPress","onBackButtonPress","onPullToRefresh","refreshing","refreshIndicatorColor","children","testID","ref","isControlled","isInline","internalOpen","setInternalOpen","open","sessionActive","setSessionActive","prevOpen","setPrevOpen","renderContent","area","setArea","restingTop","setRestingTop","isRefreshControlled","pullPending","setPullPending","hybridRef","latest","current","nativeCallbacks","instance","reason","index","onRestingTopChange","top","onContentAreaChange","next","width","height","present","console","warn","dismiss","snapToDetent","detentsKey","join","nativeDetents","Set","split","map","windowSize","contentArea","containerStyle","maxWidth","maxHeight","includes","absoluteFill","styles","host","pointerEvents","backdropBlurRadius","grabberVisible","contentBackgroundColor","pullToRefreshEnabled","collapsable","container","create","position","left"],"sourceRoot":"../../src","sources":["NitroModal.tsx"],"mappings":";;AAAA,SACEA,SAAS,EACTC,mBAAmB,EACnBC,eAAe,EACfC,OAAO,EACPC,MAAM,EACNC,QAAQ,QAGH,OAAO;AACd,SACEC,YAAY,EACZC,UAAU,EACVC,mBAAmB,EACnBC,IAAI,QAIC,cAAc;AAWrB,SAASC,QAAQ,EAAEC,cAAc,QAAQ,kBAAkB;;AAE3D;AACA;AACA;AACA;AACA;AACA;AACA;AANA,SAAAC,GAAA,IAAAC,IAAA;AAiGA,MAAMC,eAA8B,GAAG,CAAC,YAAY,CAAC;AACrD,MAAMC,KAAK,GAAG,UAAU;AAExB,SAASC,WAAWA,CAACC,KAAa,EAAe;EAC/C,MAAMC,MAAM,GAAGC,MAAM,CAACF,KAAK,CAAC;EAC5B,OAAOA,KAAK,KAAK,EAAE,IAAIE,MAAM,CAACC,QAAQ,CAACF,MAAM,CAAC,GAC1CA,MAAM,GACLD,KAA0B;AACjC;AAEA,SAASI,aAAaA,CAACC,KAA6B,EAAsB;EACxE,IAAIA,KAAK,IAAI,IAAI,EAAE,OAAOC,SAAS;EACnC,MAAMC,SAAS,GAAGlB,YAAY,CAACgB,KAAK,CAAC;EACrC;EACA,OAAO,OAAOE,SAAS,KAAK,QAAQ,GAAGA,SAAS,GAAGD,SAAS;AAC9D;AAEA,OAAO,SAASE,UAAUA,CAAC;EACzBC,MAAM;EACNC,IAAI,GAAG,aAAa;EACpBC,YAAY,GAAG,OAAO;EACtBC,KAAK;EACLC,OAAO,GAAGhB,eAAe;EACzBiB,kBAAkB,GAAG,CAAC;EACtBC,aAAa,GAAG,OAAO;EACvBC,eAAe,GAAG,GAAG;EACrBC,YAAY,GAAG,CAAC;EAChBC,sBAAsB,GAAG,IAAI;EAC7BC,cAAc,GAAG,IAAI;EACrBC,mBAAmB,GAAG,IAAI;EAC1BC,WAAW,GAAG,KAAK;EACnBC,YAAY;EACZC,eAAe;EACfC,gBAAgB,GAAG,KAAK;EACxBC,cAAc,GAAG,OAAO;EACxBC,qBAAqB;EACrBC,SAAS;EACTC,SAAS;EACTC,cAAc;EACdC,eAAe;EACfC,iBAAiB;EACjBC,eAAe;EACfC,UAAU;EACVC,qBAAqB;EACrBC,QAAQ;EACRC,MAAM;EACNC;AACe,CAAC,EAAE;EAClB,MAAMC,YAAY,GAAG7B,MAAM,KAAKH,SAAS;EACzC,MAAMiC,QAAQ,GAAG5B,YAAY,KAAK,QAAQ,IAAID,IAAI,KAAK,aAAa;EACpE,MAAM,CAAC8B,YAAY,EAAEC,eAAe,CAAC,GAAGrD,QAAQ,CAAC,KAAK,CAAC;EACvD,MAAMsD,IAAI,GAAGJ,YAAY,GAAG7B,MAAM,GAAG+B,YAAY;;EAEjD;EACA;EACA,MAAM,CAACG,aAAa,EAAEC,gBAAgB,CAAC,GAAGxD,QAAQ,CAACsD,IAAI,CAAC;EACxD,MAAM,CAACG,QAAQ,EAAEC,WAAW,CAAC,GAAG1D,QAAQ,CAACsD,IAAI,CAAC;EAC9C,IAAIA,IAAI,KAAKG,QAAQ,EAAE;IACrBC,WAAW,CAACJ,IAAI,CAAC;IACjB,IAAIA,IAAI,EAAEE,gBAAgB,CAAC,IAAI,CAAC;EAClC;EACA,MAAMG,aAAa,GAAGL,IAAI,IAAIC,aAAa;EAE3C,MAAM,CAACK,IAAI,EAAEC,OAAO,CAAC,GAAG7D,QAAQ,CAA0B,IAAI,CAAC;EAC/D;EACA;EACA;EACA,MAAM,CAAC8D,UAAU,EAAEC,aAAa,CAAC,GAAG/D,QAAQ,CAAC,CAAC,CAAC;EAC/C;EACA;EACA;EACA,MAAMgE,mBAAmB,GAAGnB,UAAU,KAAK3B,SAAS;EACpD,MAAM,CAAC+C,WAAW,EAAEC,cAAc,CAAC,GAAGlE,QAAQ,CAAC,KAAK,CAAC;EACrDL,SAAS,CAAC,MAAM;IACd,IAAIsE,WAAW,EAAEC,cAAc,CAAC,KAAK,CAAC;EACxC,CAAC,EAAE,CAACD,WAAW,CAAC,CAAC;EACjB,MAAME,SAAS,GAAGpE,MAAM,CAA8B,IAAI,CAAC;;EAE3D;EACA;EACA,MAAMqE,MAAM,GAAGrE,MAAM,CAAC;IACpBmD,YAAY;IACZc,mBAAmB;IACnBzB,SAAS;IACTC,SAAS;IACTC,cAAc;IACdC,eAAe;IACfC,iBAAiB;IACjBC;EACF,CAAC,CAAC;EACF/C,eAAe,CAAC,MAAM;IACpBuE,MAAM,CAACC,OAAO,GAAG;MACfnB,YAAY;MACZc,mBAAmB;MACnBzB,SAAS;MACTC,SAAS;MACTC,cAAc;MACdC,eAAe;MACfC,iBAAiB;MACjBC;IACF,CAAC;EACH,CAAC,CAAC;EAEF,MAAM0B,eAAe,GAAGxE,OAAO,CAC7B,OAAO;IACLqE,SAAS,EAAE9D,QAAQ,CAAEkE,QAA8B,IAAK;MACtDJ,SAAS,CAACE,OAAO,GAAGE,QAAQ;IAC9B,CAAC,CAAC;IACFhC,SAAS,EAAElC,QAAQ,CAAC,MAAM;MACxBmD,gBAAgB,CAAC,IAAI,CAAC;MACtBY,MAAM,CAACC,OAAO,CAAC9B,SAAS,GAAG,CAAC;IAC9B,CAAC,CAAC;IACFC,SAAS,EAAEnC,QAAQ,CAAEmE,MAAqB,IAAK;MAC7ChB,gBAAgB,CAAC,KAAK,CAAC;MACvB,IAAI,CAACY,MAAM,CAACC,OAAO,CAACnB,YAAY,EAAEG,eAAe,CAAC,KAAK,CAAC;MACxDe,MAAM,CAACC,OAAO,CAAC7B,SAAS,GAAGgC,MAAM,CAAC;IACpC,CAAC,CAAC;IACF/B,cAAc,EAAEpC,QAAQ,CAAEoE,KAAa,IAAK;MAC1CL,MAAM,CAACC,OAAO,CAAC5B,cAAc,GAAGgC,KAAK,CAAC;IACxC,CAAC,CAAC;IACF/B,eAAe,EAAErC,QAAQ,CAAC,MAAM;MAC9B+D,MAAM,CAACC,OAAO,CAAC3B,eAAe,GAAG,CAAC;IACpC,CAAC,CAAC;IACFC,iBAAiB,EAAEtC,QAAQ,CAAC,MAAM;MAChC+D,MAAM,CAACC,OAAO,CAAC1B,iBAAiB,GAAG,CAAC;IACtC,CAAC,CAAC;IACFC,eAAe,EAAEvC,QAAQ,CAAC,MAAM;MAC9B,IAAI+D,MAAM,CAACC,OAAO,CAACL,mBAAmB,EAAEE,cAAc,CAAC,IAAI,CAAC;MAC5DE,MAAM,CAACC,OAAO,CAACzB,eAAe,GAAG,CAAC;IACpC,CAAC,CAAC;IACF8B,kBAAkB,EAAErE,QAAQ,CAAEsE,GAAW,IAAK;MAC5CZ,aAAa,CAACY,GAAG,CAAC;IACpB,CAAC,CAAC;IACFC,mBAAmB,EAAEvE,QAAQ,CAAEwE,IAAsB,IAAK;MACxDhB,OAAO,CAAEQ,OAAO,IACdA,OAAO,EAAES,KAAK,KAAKD,IAAI,CAACC,KAAK,IAAIT,OAAO,CAACU,MAAM,KAAKF,IAAI,CAACE,MAAM,GAC3DV,OAAO,GACPQ,IACN,CAAC;IACH,CAAC;EACH,CAAC,CAAC,EACF,EACF,CAAC;EAEDjF,mBAAmB,CACjBqD,GAAG,EACH,OAAO;IACL+B,OAAO,EAAEA,CAAA,KAAM;MACb,IAAIZ,MAAM,CAACC,OAAO,CAACnB,YAAY,EAAE;QAC/B+B,OAAO,CAACC,IAAI,CACV,gEACF,CAAC;QACD;MACF;MACA7B,eAAe,CAAC,IAAI,CAAC;IACvB,CAAC;IACD8B,OAAO,EAAEA,CAAA,KAAM;MACb,IAAIf,MAAM,CAACC,OAAO,CAACnB,YAAY,EAAE;QAC/BiB,SAAS,CAACE,OAAO,EAAEc,OAAO,CAAC,CAAC;MAC9B,CAAC,MAAM;QACL9B,eAAe,CAAC,KAAK,CAAC;MACxB;IACF,CAAC;IACD+B,YAAY,EAAGX,KAAa,IAAK;MAC/BN,SAAS,CAACE,OAAO,EAAEe,YAAY,CAACX,KAAK,CAAC;IACxC;EACF,CAAC,CAAC,EACF,EACF,CAAC;;EAED;EACA,MAAMY,UAAU,GAAG5D,OAAO,CAAC6D,IAAI,CAAC,GAAG,CAAC;EACpC,MAAMC,aAAa,GAAGzF,OAAO,CAC3B,MAAM,CAAC,GAAG,IAAI0F,GAAG,CAACH,UAAU,CAACI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAACC,GAAG,CAAC/E,WAAW,CAAC,EAC1D,CAAC0E,UAAU,CACb,CAAC;;EAED;EACA,MAAMM,UAAU,GAAGxF,mBAAmB,CAAC,CAAC;EACxC,MAAMyF,WAAW,GAAGhC,IAAI,IAAI;IAC1BkB,KAAK,EAAExD,IAAI,KAAK,OAAO,GAAGqE,UAAU,CAACb,KAAK,GAAG,EAAE,GAAGa,UAAU,CAACb,KAAK;IAClEC,MAAM,EACJzD,IAAI,KAAK,OAAO,GAAGqE,UAAU,CAACZ,MAAM,GAAG,GAAG,GAAGY,UAAU,CAACZ,MAAM,GAAG;EACrE,CAAC;EACD,MAAMc,cAAyB,GAC7BvE,IAAI,KAAK,OAAO,GACZ;IAAEwE,QAAQ,EAAEF,WAAW,CAACd,KAAK;IAAEiB,SAAS,EAAEH,WAAW,CAACb;EAAO,CAAC,GAC9DQ,aAAa,CAACS,QAAQ,CAAC,YAAY,CAAC,GAClC;IAAElB,KAAK,EAAEc,WAAW,CAACd,KAAK;IAAEiB,SAAS,EAAEH,WAAW,CAACb;EAAO,CAAC,GAC3D;IAAED,KAAK,EAAEc,WAAW,CAACd,KAAK;IAAEC,MAAM,EAAEa,WAAW,CAACb;EAAO,CAAC;EAEhE,oBACEvE,IAAA,CAACF,cAAc;IACbkB,KAAK,EAAE2B,QAAQ,GAAG,CAACjD,UAAU,CAAC+F,YAAY,EAAEzE,KAAK,CAAC,GAAG0E,MAAM,CAACC,IAAK;IACjEC,aAAa,EAAEjD,QAAQ,GAAG,UAAU,GAAGjC,SAAU;IACjDG,MAAM,EAAEiC,IAAK;IACbhC,IAAI,EAAEA,IAAK;IACX6B,QAAQ,EAAEA,QAAS;IACnB1B,OAAO,EAAE8D,aAAc;IACvB7D,kBAAkB,EAAEA,kBAAmB;IACvCC,aAAa,EAAEX,aAAa,CAACW,aAAa,CAAC,IAAIjB,KAAM;IACrDkB,eAAe,EAAEA,eAAgB;IACjCyE,kBAAkB,EAAExE,YAAa;IACjCC,sBAAsB,EAAEA,sBAAuB;IAC/CC,cAAc,EAAEA,cAAe;IAC/BC,mBAAmB,EAAEA,mBAAoB;IACzCsE,cAAc,EAAErE,WAAY;IAC5BC,YAAY,EAAEA,YAAY,IAAI,CAAC,CAAE;IACjCqE,sBAAsB,EAAEvF,aAAa,CAACmB,eAAe,CAAE;IACvDC,gBAAgB,EAAEA,gBAAiB;IACnCC,cAAc,EAAEA,cAAe;IAC/BmE,oBAAoB,EAAE5D,eAAe,IAAI,IAAK;IAC9CC,UAAU,EAAEmB,mBAAmB,GAAGnB,UAAU,IAAIoB,WAAW,GAAG/C,SAAU;IACxE4B,qBAAqB,EAAE9B,aAAa,CAAC8B,qBAAqB,CAAE;IAAA,GACxDwB,eAAe;IAAAvB,QAAA,EAElBY,aAAa,gBACZnD,IAAA,CAACJ,IAAI;MACHqG,WAAW,EAAE,KAAM;MACnBzD,MAAM,EAAEA,MAAO;MACfxB,KAAK,EAAE,CACL0E,MAAM,CAACQ,SAAS,EAChBb,cAAc,EACdvD,qBAAqB,EACrBa,QAAQ,IAAIW,UAAU,GAAG,CAAC,GAAG;QAAEa,GAAG,EAAEb;MAAW,CAAC,GAAG,IAAI,CACvD;MAAAf,QAAA,EAEDA;IAAQ,CACL,CAAC,GACL;EAAI,CACM,CAAC;AAErB;AAEA,MAAMmD,MAAM,GAAGhG,UAAU,CAACyG,MAAM,CAAC;EAC/B;EACA;EACAR,IAAI,EAAE;IACJS,QAAQ,EAAE,UAAU;IACpBC,IAAI,EAAE,CAAC;IACPlC,GAAG,EAAE,CAAC;IACNG,KAAK,EAAE,CAAC;IACRC,MAAM,EAAE;EACV,CAAC;EACD;EACA;EACA2B,SAAS,EAAE;IACTE,QAAQ,EAAE,UAAU;IACpBC,IAAI,EAAE,CAAC;IACPlC,GAAG,EAAE;EACP;AACF,CAAC,CAAC","ignoreList":[]}
|