@rafaelaugusto/jolt 0.1.0-beta.16 → 0.1.0-beta.17
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 +7 -2
- package/dist/bottom-sheet/geometry.d.ts +18 -0
- package/dist/bottom-sheet/geometry.d.ts.map +1 -0
- package/dist/bottom-sheet/geometry.js +51 -0
- package/dist/bottom-sheet/geometry.js.map +1 -0
- package/dist/bottom-sheet/index.d.ts +56 -0
- package/dist/bottom-sheet/index.d.ts.map +1 -0
- package/dist/bottom-sheet/index.js +462 -0
- package/dist/bottom-sheet/index.js.map +1 -0
- package/dist/bottom-sheet/native.d.ts +11 -0
- package/dist/bottom-sheet/native.d.ts.map +1 -0
- package/dist/bottom-sheet/native.js +173 -0
- package/dist/bottom-sheet/native.js.map +1 -0
- package/dist/gesture-native.d.ts +1 -0
- package/dist/gesture-native.d.ts.map +1 -1
- package/dist/gesture-native.js +6 -0
- package/dist/gesture-native.js.map +1 -1
- package/dist/gestures/index.d.ts +4 -0
- package/dist/gestures/index.d.ts.map +1 -1
- package/dist/gestures/index.js +12 -1
- package/dist/gestures/index.js.map +1 -1
- package/dist/native/index.d.ts +2 -0
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +1 -0
- package/dist/native/index.js.map +1 -1
- package/dist/native-property.d.ts +2 -0
- package/dist/native-property.d.ts.map +1 -1
- package/dist/native-property.js +12 -4
- package/dist/native-property.js.map +1 -1
- package/dist/reorder/handle.d.ts +6 -0
- package/dist/reorder/handle.d.ts.map +1 -0
- package/dist/reorder/handle.js +10 -0
- package/dist/reorder/handle.js.map +1 -0
- package/dist/reorder/index.d.ts +10 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +233 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/list-view.d.ts +5 -0
- package/dist/reorder/list-view.d.ts.map +1 -0
- package/dist/reorder/list-view.js +238 -0
- package/dist/reorder/list-view.js.map +1 -0
- package/dist/reorder/model.d.ts +6 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +30 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/reorder/surface.d.ts +6 -0
- package/dist/reorder/surface.d.ts.map +1 -0
- package/dist/reorder/surface.js +10 -0
- package/dist/reorder/surface.js.map +1 -0
- package/dist/reorder/types.d.ts +92 -0
- package/dist/reorder/types.d.ts.map +1 -0
- package/dist/reorder/types.js +2 -0
- package/dist/reorder/types.js.map +1 -0
- package/dist/scene-shader.d.ts +18 -0
- package/dist/scene-shader.d.ts.map +1 -0
- package/dist/scene-shader.js +76 -0
- package/dist/scene-shader.js.map +1 -0
- package/package.json +9 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +7 -2
- package/platforms/android/java/org/rafaelaugusto/jolt/ReorderHandle.java +25 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSession.java +213 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSurface.java +27 -0
- package/platforms/android/proguard-rules.pro +5 -0
- package/platforms/ios/src/JoltGestureSession.h +1 -0
- package/platforms/ios/src/JoltGestureSession.m +7 -2
- package/platforms/ios/src/JoltReorderSession.h +15 -0
- package/platforms/ios/src/JoltReorderSession.m +205 -0
- package/platforms/ios/src/module.modulemap +1 -0
- package/src/android-plugin.d.ts +34 -0
- package/src/bottom-sheet/geometry.ts +84 -0
- package/src/bottom-sheet/index.ts +536 -0
- package/src/bottom-sheet/native.ts +214 -0
- package/src/gesture-native.ts +15 -0
- package/src/gestures/index.ts +16 -1
- package/src/ios-plugin.d.ts +32 -0
- package/src/native/index.ts +6 -0
- package/src/native-property.ts +17 -4
- package/src/reorder/handle.ts +9 -0
- package/src/reorder/index.ts +243 -0
- package/src/reorder/list-view.ts +262 -0
- package/src/reorder/model.ts +47 -0
- package/src/reorder/surface.ts +10 -0
- package/src/reorder/types.ts +87 -0
- package/src/scene-shader.ts +139 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Application,
|
|
3
|
+
GridLayout,
|
|
4
|
+
ScrollView,
|
|
5
|
+
Trace,
|
|
6
|
+
View,
|
|
7
|
+
isAndroid,
|
|
8
|
+
} from '@nativescript/core'
|
|
9
|
+
import { bind, type SpringValueOptions } from '../motion-value.js'
|
|
10
|
+
import { GestureSurface, gestures, type Gestures } from '../gestures/index.js'
|
|
11
|
+
import { currentKeyboard, onKeyboard } from '../keyboard.js'
|
|
12
|
+
import {
|
|
13
|
+
releaseStop,
|
|
14
|
+
sheetGeometry,
|
|
15
|
+
validateSnaps,
|
|
16
|
+
type SheetGeometry,
|
|
17
|
+
type SheetSnap,
|
|
18
|
+
} from './geometry.js'
|
|
19
|
+
import {
|
|
20
|
+
captureFocus,
|
|
21
|
+
dismissKeyboard,
|
|
22
|
+
revealFocusedField,
|
|
23
|
+
sheetActions,
|
|
24
|
+
windowHeight,
|
|
25
|
+
} from './native.js'
|
|
26
|
+
|
|
27
|
+
export { GestureSurface }
|
|
28
|
+
export type { SheetSnap }
|
|
29
|
+
|
|
30
|
+
export interface BottomSheetOptions {
|
|
31
|
+
/** A separate, focusable drag handle inside the panel; receives native accessibility actions. */
|
|
32
|
+
handle: View
|
|
33
|
+
snap: readonly SheetSnap[]
|
|
34
|
+
/** A descendant vertical ScrollView. Native list plugins need their own integration. */
|
|
35
|
+
scrollView?: ScrollView
|
|
36
|
+
/** Measured content for the content snap; defaults to scrollView.content. */
|
|
37
|
+
content?: View
|
|
38
|
+
/** A sibling behind the panel, filling the same one-cell GridLayout. */
|
|
39
|
+
backdrop?: View
|
|
40
|
+
backdropOpacity?: number
|
|
41
|
+
/** Follow temporarily expands above the keyboard, then restores the selected snap. */
|
|
42
|
+
keyboard?: 'follow' | 'none'
|
|
43
|
+
/** Whether dragging, the backdrop, back and accessibility can dismiss. Defaults to true. */
|
|
44
|
+
dismissible?: boolean
|
|
45
|
+
safeArea?: { top?: number; bottom?: number }
|
|
46
|
+
returnFocus?: View
|
|
47
|
+
spring?: Pick<SpringValueOptions, 'stiffness' | 'damping' | 'mass'>
|
|
48
|
+
accessibility?: { label?: string; expand?: string; collapse?: string; dismiss?: string }
|
|
49
|
+
onChange?: (point: SheetSnap | null) => void
|
|
50
|
+
/** Called after a close settles; interrupted closes do not dismiss. */
|
|
51
|
+
onDismiss?: () => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BottomSheet {
|
|
55
|
+
readonly isOpen: boolean
|
|
56
|
+
readonly snapPoint: SheetSnap | null
|
|
57
|
+
readonly visibleHeight: number
|
|
58
|
+
readonly paused: boolean
|
|
59
|
+
readonly disposed: boolean
|
|
60
|
+
open(point?: SheetSnap): void
|
|
61
|
+
snapTo(point: SheetSnap): void
|
|
62
|
+
expand(): void
|
|
63
|
+
collapse(): void
|
|
64
|
+
dismiss(): void
|
|
65
|
+
remeasure(): void
|
|
66
|
+
pause(): void
|
|
67
|
+
play(): void
|
|
68
|
+
dispose(): void
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const owners = new WeakMap<GestureSurface, BottomSheet>()
|
|
72
|
+
const descendant = (child: View, parent: View) => {
|
|
73
|
+
for (let current = child.parent; current; current = current.parent)
|
|
74
|
+
if (current === parent) return true
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
const report = (callback: (() => void) | undefined) => {
|
|
78
|
+
try {
|
|
79
|
+
callback?.()
|
|
80
|
+
} catch (error) {
|
|
81
|
+
Trace.write(
|
|
82
|
+
`Jolt bottom sheet callback failed: ${String(error)}`,
|
|
83
|
+
Trace.categories.Error,
|
|
84
|
+
Trace.messageType.error,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** An in-page sheet, built from native gestures and live spring bindings. */
|
|
90
|
+
export function bottomSheet(
|
|
91
|
+
panel: GestureSurface,
|
|
92
|
+
options: BottomSheetOptions,
|
|
93
|
+
): BottomSheet {
|
|
94
|
+
if (!(panel instanceof GestureSurface) || !(panel.parent instanceof GridLayout))
|
|
95
|
+
throw new TypeError(
|
|
96
|
+
'bottomSheet() needs a GestureSurface inside an overlay GridLayout.',
|
|
97
|
+
)
|
|
98
|
+
if (!options || typeof options !== 'object')
|
|
99
|
+
throw new TypeError('Sheet options are required.')
|
|
100
|
+
validateSnaps(options.snap)
|
|
101
|
+
const points = [...options.snap],
|
|
102
|
+
host = panel.parent
|
|
103
|
+
if (!(options.handle instanceof View) || !descendant(options.handle, panel))
|
|
104
|
+
throw new TypeError('Provide a separate accessibility handle inside the sheet.')
|
|
105
|
+
const handle = options.handle,
|
|
106
|
+
scroll = options.scrollView
|
|
107
|
+
if (
|
|
108
|
+
scroll &&
|
|
109
|
+
(!(scroll instanceof ScrollView) ||
|
|
110
|
+
scroll.orientation !== 'vertical' ||
|
|
111
|
+
!descendant(scroll, panel))
|
|
112
|
+
)
|
|
113
|
+
throw new TypeError('Sheet scrolling needs a descendant vertical ScrollView.')
|
|
114
|
+
const content = options.content ?? scroll?.content
|
|
115
|
+
if (content && (!(content instanceof View) || !descendant(content, panel)))
|
|
116
|
+
throw new TypeError('Sheet content must be a descendant of its panel.')
|
|
117
|
+
if (points.includes('content') && !content)
|
|
118
|
+
throw new TypeError(
|
|
119
|
+
'A content snap needs scrollView.content or an explicit content view.',
|
|
120
|
+
)
|
|
121
|
+
const backdrop = options.backdrop
|
|
122
|
+
if (
|
|
123
|
+
backdrop &&
|
|
124
|
+
(!(backdrop instanceof View) || backdrop.parent !== host || backdrop === panel)
|
|
125
|
+
)
|
|
126
|
+
throw new TypeError('The backdrop must be a sibling behind the sheet.')
|
|
127
|
+
const opacity = options.backdropOpacity ?? 0.45
|
|
128
|
+
if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1)
|
|
129
|
+
throw new RangeError('Backdrop opacity must be between zero and one.')
|
|
130
|
+
if (options.keyboard !== undefined && !['follow', 'none'].includes(options.keyboard))
|
|
131
|
+
throw new TypeError('Sheet keyboard behavior must be follow or none.')
|
|
132
|
+
for (const inset of Object.values(options.safeArea ?? {}))
|
|
133
|
+
if (!Number.isFinite(inset) || inset < 0)
|
|
134
|
+
throw new RangeError('Safe-area insets must be finite and nonnegative.')
|
|
135
|
+
if (options.dismissible !== undefined && typeof options.dismissible !== 'boolean')
|
|
136
|
+
throw new TypeError('dismissible must be boolean.')
|
|
137
|
+
for (const callback of [options.onChange, options.onDismiss])
|
|
138
|
+
if (callback !== undefined && typeof callback !== 'function')
|
|
139
|
+
throw new TypeError('Sheet callbacks must be functions.')
|
|
140
|
+
|
|
141
|
+
owners.get(panel)?.dispose()
|
|
142
|
+
const original = {
|
|
143
|
+
contentAlignment: content?.verticalAlignment,
|
|
144
|
+
height: panel.height,
|
|
145
|
+
marginTop: panel.marginTop,
|
|
146
|
+
marginBottom: panel.marginBottom,
|
|
147
|
+
alignment: panel.verticalAlignment,
|
|
148
|
+
visibility: panel.visibility,
|
|
149
|
+
y: panel.translateY,
|
|
150
|
+
clipping: host.clipToBounds,
|
|
151
|
+
backdropOpacity: backdrop?.opacity,
|
|
152
|
+
backdropVisibility: backdrop?.visibility,
|
|
153
|
+
accessible: handle.accessible,
|
|
154
|
+
label: handle.accessibilityLabel,
|
|
155
|
+
value: handle.accessibilityValue,
|
|
156
|
+
}
|
|
157
|
+
let geometry: SheetGeometry | undefined, input: Gestures | undefined
|
|
158
|
+
let opened = false,
|
|
159
|
+
point = points[0]!,
|
|
160
|
+
ended = false,
|
|
161
|
+
held = false,
|
|
162
|
+
changing = false,
|
|
163
|
+
pendingDismiss = false,
|
|
164
|
+
contactSettled = false,
|
|
165
|
+
focusCaptured = false
|
|
166
|
+
let keyboard = currentKeyboard(),
|
|
167
|
+
timer: ReturnType<typeof setTimeout> | undefined
|
|
168
|
+
let releasePanel = () => {},
|
|
169
|
+
releaseBackdrop = () => {},
|
|
170
|
+
releaseActions = () => {},
|
|
171
|
+
releaseKeyboard = () => {}
|
|
172
|
+
let releaseClose = () => {},
|
|
173
|
+
restoreFocus = () => {}
|
|
174
|
+
const dismissible = options.dismissible !== false
|
|
175
|
+
const followKeyboard = options.keyboard !== 'none'
|
|
176
|
+
const keyboardOpen = () => followKeyboard && keyboard.visible
|
|
177
|
+
const targetY = () =>
|
|
178
|
+
!opened
|
|
179
|
+
? geometry!.closedY
|
|
180
|
+
: keyboardOpen()
|
|
181
|
+
? 0
|
|
182
|
+
: geometry!.stops.find((stop) => stop.point === point)!.y
|
|
183
|
+
const minimum = () =>
|
|
184
|
+
keyboardOpen() ? 0 : Math.min(...geometry!.stops.map((stop) => stop.y))
|
|
185
|
+
|
|
186
|
+
function finishClose() {
|
|
187
|
+
releaseClose()
|
|
188
|
+
releaseClose = () => {}
|
|
189
|
+
if (ended || opened) return
|
|
190
|
+
panel.visibility = 'hidden'
|
|
191
|
+
if (backdrop) backdrop.visibility = 'hidden'
|
|
192
|
+
input?.pause()
|
|
193
|
+
if (pendingDismiss) {
|
|
194
|
+
pendingDismiss = false
|
|
195
|
+
const restore = restoreFocus
|
|
196
|
+
restoreFocus = () => {}
|
|
197
|
+
focusCaptured = false
|
|
198
|
+
restore()
|
|
199
|
+
report(options.onDismiss)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function move() {
|
|
203
|
+
if (!input || !geometry || held || ended) return
|
|
204
|
+
changing = true
|
|
205
|
+
try {
|
|
206
|
+
input.updateBounds({
|
|
207
|
+
top: minimum(),
|
|
208
|
+
bottom:
|
|
209
|
+
!opened || dismissible
|
|
210
|
+
? geometry.closedY
|
|
211
|
+
: Math.max(...geometry.stops.map((stop) => stop.y)),
|
|
212
|
+
})
|
|
213
|
+
input.settleTo({ y: targetY() })
|
|
214
|
+
} finally {
|
|
215
|
+
changing = false
|
|
216
|
+
}
|
|
217
|
+
if (!opened) {
|
|
218
|
+
releaseClose()
|
|
219
|
+
const check = () => {
|
|
220
|
+
if (
|
|
221
|
+
!opened &&
|
|
222
|
+
geometry &&
|
|
223
|
+
input &&
|
|
224
|
+
Math.abs(input.y.get() - geometry.closedY) < 0.01
|
|
225
|
+
)
|
|
226
|
+
finishClose()
|
|
227
|
+
}
|
|
228
|
+
releaseClose = input.y.subscribe(check, { immediate: false })
|
|
229
|
+
check()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function setTarget(next: SheetSnap | null) {
|
|
233
|
+
if (ended) return
|
|
234
|
+
if (next !== null && !points.includes(next))
|
|
235
|
+
throw new RangeError('Choose one of the configured sheet snap points.')
|
|
236
|
+
const wasOpen = opened,
|
|
237
|
+
changed = (next !== null) !== opened || (next !== null && next !== point)
|
|
238
|
+
releaseClose()
|
|
239
|
+
releaseClose = () => {}
|
|
240
|
+
if (next !== null) {
|
|
241
|
+
if (!opened && !pendingDismiss && panel.isLoaded) {
|
|
242
|
+
restoreFocus = captureFocus(panel, options.returnFocus)
|
|
243
|
+
focusCaptured = true
|
|
244
|
+
}
|
|
245
|
+
opened = true
|
|
246
|
+
pendingDismiss = false
|
|
247
|
+
point = next
|
|
248
|
+
panel.visibility = 'visible'
|
|
249
|
+
if (backdrop) backdrop.visibility = 'visible'
|
|
250
|
+
if (!held) input?.play()
|
|
251
|
+
} else {
|
|
252
|
+
opened = false
|
|
253
|
+
pendingDismiss ||= wasOpen
|
|
254
|
+
dismissKeyboard(panel)
|
|
255
|
+
}
|
|
256
|
+
handle.accessibilityValue = opened ? String(point) : 'Closed'
|
|
257
|
+
move()
|
|
258
|
+
if (changed) report(() => options.onChange?.(next))
|
|
259
|
+
}
|
|
260
|
+
function adjacent(expand: boolean) {
|
|
261
|
+
if (ended) return
|
|
262
|
+
if (!opened) {
|
|
263
|
+
if (expand) setTarget(point)
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
if (!geometry) return
|
|
267
|
+
const current = geometry.stops.find((stop) => stop.point === point)!
|
|
268
|
+
const candidates = geometry.stops
|
|
269
|
+
.filter((stop) =>
|
|
270
|
+
expand
|
|
271
|
+
? stop.height > current.height + 0.01
|
|
272
|
+
: stop.height < current.height - 0.01,
|
|
273
|
+
)
|
|
274
|
+
.sort((a, b) => (expand ? a.height - b.height : b.height - a.height))
|
|
275
|
+
if (candidates[0]) setTarget(candidates[0].point)
|
|
276
|
+
else if (!expand && dismissible) setTarget(null)
|
|
277
|
+
}
|
|
278
|
+
function remeasure() {
|
|
279
|
+
if (ended || !panel.isLoaded || !handle.isLoaded || (scroll && !scroll.isLoaded))
|
|
280
|
+
return
|
|
281
|
+
const size = host.getActualSize()
|
|
282
|
+
if (!(size.height > 0)) return
|
|
283
|
+
const safe = host.getSafeAreaInsets()
|
|
284
|
+
const top = options.safeArea?.top ?? safe.top
|
|
285
|
+
const bottom = options.safeArea?.bottom ?? safe.bottom
|
|
286
|
+
const chrome = scroll
|
|
287
|
+
? Math.max(0, panel.getActualSize().height - scroll.getActualSize().height)
|
|
288
|
+
: 0
|
|
289
|
+
const contentHeight = (content?.getActualSize().height ?? 0) + chrome
|
|
290
|
+
const next = sheetGeometry(
|
|
291
|
+
points,
|
|
292
|
+
size.height,
|
|
293
|
+
(host.getLocationInWindow()?.y ?? 0) + size.height,
|
|
294
|
+
windowHeight(host),
|
|
295
|
+
followKeyboard ? keyboard.height : 0,
|
|
296
|
+
top,
|
|
297
|
+
bottom,
|
|
298
|
+
contentHeight,
|
|
299
|
+
)
|
|
300
|
+
const previous = geometry
|
|
301
|
+
geometry = next
|
|
302
|
+
const panelHeight = keyboardOpen()
|
|
303
|
+
? next.height
|
|
304
|
+
: Math.max(...next.stops.map((stop) => stop.height))
|
|
305
|
+
if (panel.height !== panelHeight) panel.height = panelHeight
|
|
306
|
+
if (panel.marginTop !== next.top) panel.marginTop = next.top
|
|
307
|
+
if (!input) {
|
|
308
|
+
input = gestures(panel, {
|
|
309
|
+
pan: { axis: 'y', bounds: { top: minimum(), bottom: next.closedY } },
|
|
310
|
+
initial: { y: next.closedY },
|
|
311
|
+
scrollView: scroll,
|
|
312
|
+
spring: options.spring,
|
|
313
|
+
onContactStart() {
|
|
314
|
+
const wasOpen = opened
|
|
315
|
+
contactSettled = false
|
|
316
|
+
releaseClose()
|
|
317
|
+
releaseClose = () => {}
|
|
318
|
+
opened = true
|
|
319
|
+
pendingDismiss = false
|
|
320
|
+
handle.accessibilityValue = String(point)
|
|
321
|
+
if (!wasOpen) report(() => options.onChange?.(point))
|
|
322
|
+
},
|
|
323
|
+
onContactEnd(event) {
|
|
324
|
+
if (contactSettled || changing || held || ended || !geometry) return
|
|
325
|
+
const stop = releaseStop(geometry, event.y, 0, dismissible)
|
|
326
|
+
setTarget(stop?.point ?? null)
|
|
327
|
+
},
|
|
328
|
+
onStart() {
|
|
329
|
+
releaseClose()
|
|
330
|
+
releaseClose = () => {}
|
|
331
|
+
opened = true
|
|
332
|
+
pendingDismiss = false
|
|
333
|
+
},
|
|
334
|
+
onEnd(event) {
|
|
335
|
+
contactSettled = true
|
|
336
|
+
if (!geometry) return
|
|
337
|
+
const stop = releaseStop(geometry, event.y, event.velocityY, dismissible)
|
|
338
|
+
setTarget(stop?.point ?? null)
|
|
339
|
+
},
|
|
340
|
+
onCancel() {
|
|
341
|
+
contactSettled = true
|
|
342
|
+
if (!changing && !held && !ended) move()
|
|
343
|
+
},
|
|
344
|
+
})
|
|
345
|
+
releasePanel = bind(panel, { y: input.y })
|
|
346
|
+
if (held || !opened) input.pause()
|
|
347
|
+
}
|
|
348
|
+
if (
|
|
349
|
+
!previous ||
|
|
350
|
+
previous.closedY !== next.closedY ||
|
|
351
|
+
previous.height !== next.height ||
|
|
352
|
+
previous.stops.some((stop, i) => stop.y !== next.stops[i]!.y)
|
|
353
|
+
) {
|
|
354
|
+
releaseBackdrop()
|
|
355
|
+
releaseBackdrop = backdrop
|
|
356
|
+
? bind(backdrop, { opacity: input.y.to([minimum(), next.closedY], [opacity, 0]) })
|
|
357
|
+
: () => {}
|
|
358
|
+
if (!input.active) move()
|
|
359
|
+
else
|
|
360
|
+
input.updateBounds({
|
|
361
|
+
top: minimum(),
|
|
362
|
+
bottom: dismissible
|
|
363
|
+
? next.closedY
|
|
364
|
+
: Math.max(...next.stops.map((stop) => stop.y)),
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
if (opened && keyboardOpen()) revealFocusedField(panel, scroll)
|
|
368
|
+
}
|
|
369
|
+
function queueMeasure() {
|
|
370
|
+
if (ended || timer !== undefined) return
|
|
371
|
+
timer = setTimeout(() => {
|
|
372
|
+
timer = undefined
|
|
373
|
+
remeasure()
|
|
374
|
+
}, 0)
|
|
375
|
+
}
|
|
376
|
+
function back(event: { cancel: boolean }) {
|
|
377
|
+
if (opened && panel.isLoaded && dismissible) {
|
|
378
|
+
event.cancel = true
|
|
379
|
+
setTarget(null)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function backdropTap() {
|
|
383
|
+
if (opened && dismissible) setTarget(null)
|
|
384
|
+
}
|
|
385
|
+
function attach() {
|
|
386
|
+
if (ended || !panel.isLoaded || !handle.isLoaded || (scroll && !scroll.isLoaded))
|
|
387
|
+
return
|
|
388
|
+
if (opened) {
|
|
389
|
+
panel.visibility = 'visible'
|
|
390
|
+
if (backdrop) backdrop.visibility = 'visible'
|
|
391
|
+
}
|
|
392
|
+
if (opened && !focusCaptured) {
|
|
393
|
+
restoreFocus = captureFocus(panel, options.returnFocus)
|
|
394
|
+
focusCaptured = true
|
|
395
|
+
}
|
|
396
|
+
releaseActions()
|
|
397
|
+
const labels = options.accessibility
|
|
398
|
+
releaseActions = sheetActions(handle, [
|
|
399
|
+
{ label: labels?.expand ?? 'Expand', run: () => adjacent(true) },
|
|
400
|
+
{ label: labels?.collapse ?? 'Collapse', run: () => adjacent(false) },
|
|
401
|
+
...(dismissible
|
|
402
|
+
? [{ label: labels?.dismiss ?? 'Dismiss', run: () => setTarget(null) }]
|
|
403
|
+
: []),
|
|
404
|
+
])
|
|
405
|
+
releaseKeyboard()
|
|
406
|
+
releaseKeyboard = onKeyboard((state) => {
|
|
407
|
+
keyboard = state
|
|
408
|
+
remeasure()
|
|
409
|
+
if (opened) move()
|
|
410
|
+
queueMeasure()
|
|
411
|
+
})
|
|
412
|
+
keyboard = currentKeyboard()
|
|
413
|
+
remeasure()
|
|
414
|
+
queueMeasure()
|
|
415
|
+
}
|
|
416
|
+
function detach() {
|
|
417
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
418
|
+
timer = undefined
|
|
419
|
+
releaseClose()
|
|
420
|
+
releaseClose = () => {}
|
|
421
|
+
if (backdrop) backdrop.visibility = 'hidden'
|
|
422
|
+
releaseActions()
|
|
423
|
+
releaseActions = () => {}
|
|
424
|
+
releaseKeyboard()
|
|
425
|
+
releaseKeyboard = () => {}
|
|
426
|
+
releasePanel()
|
|
427
|
+
releasePanel = () => {}
|
|
428
|
+
releaseBackdrop()
|
|
429
|
+
releaseBackdrop = () => {}
|
|
430
|
+
changing = true
|
|
431
|
+
input?.dispose()
|
|
432
|
+
input = undefined
|
|
433
|
+
geometry = undefined
|
|
434
|
+
changing = false
|
|
435
|
+
}
|
|
436
|
+
const control: BottomSheet = {
|
|
437
|
+
get isOpen() {
|
|
438
|
+
return opened
|
|
439
|
+
},
|
|
440
|
+
get snapPoint() {
|
|
441
|
+
return opened ? point : null
|
|
442
|
+
},
|
|
443
|
+
get visibleHeight() {
|
|
444
|
+
return geometry && input
|
|
445
|
+
? Math.max(0, Math.min(geometry.height, geometry.height - input.y.get()))
|
|
446
|
+
: 0
|
|
447
|
+
},
|
|
448
|
+
get paused() {
|
|
449
|
+
return held
|
|
450
|
+
},
|
|
451
|
+
get disposed() {
|
|
452
|
+
return ended
|
|
453
|
+
},
|
|
454
|
+
open: (next = point) => setTarget(next),
|
|
455
|
+
snapTo: (next) => setTarget(next),
|
|
456
|
+
expand: () => adjacent(true),
|
|
457
|
+
collapse: () => adjacent(false),
|
|
458
|
+
dismiss: () => setTarget(null),
|
|
459
|
+
remeasure,
|
|
460
|
+
pause() {
|
|
461
|
+
held = true
|
|
462
|
+
input?.pause()
|
|
463
|
+
},
|
|
464
|
+
play() {
|
|
465
|
+
if (ended) return
|
|
466
|
+
held = false
|
|
467
|
+
input?.play()
|
|
468
|
+
move()
|
|
469
|
+
},
|
|
470
|
+
dispose() {
|
|
471
|
+
if (ended) return
|
|
472
|
+
ended = true
|
|
473
|
+
detach()
|
|
474
|
+
for (const view of [panel, handle, ...(scroll ? [scroll] : [])])
|
|
475
|
+
view.off('loaded', attach)
|
|
476
|
+
panel.off('unloaded', detach)
|
|
477
|
+
for (const view of new Set([
|
|
478
|
+
host,
|
|
479
|
+
panel,
|
|
480
|
+
...(content ? [content] : []),
|
|
481
|
+
...(scroll ? [scroll] : []),
|
|
482
|
+
]))
|
|
483
|
+
view.off('layoutChanged', queueMeasure)
|
|
484
|
+
backdrop?.off('tap', backdropTap)
|
|
485
|
+
if (isAndroid)
|
|
486
|
+
Application.android.off(Application.android.activityBackPressedEvent, back)
|
|
487
|
+
if (content) content.verticalAlignment = original.contentAlignment!
|
|
488
|
+
panel.height = original.height
|
|
489
|
+
panel.marginTop = original.marginTop
|
|
490
|
+
panel.marginBottom = original.marginBottom
|
|
491
|
+
panel.verticalAlignment = original.alignment
|
|
492
|
+
panel.visibility = original.visibility
|
|
493
|
+
panel.translateY = original.y
|
|
494
|
+
host.clipToBounds = original.clipping
|
|
495
|
+
handle.accessible = original.accessible
|
|
496
|
+
handle.accessibilityLabel = original.label
|
|
497
|
+
handle.accessibilityValue = original.value
|
|
498
|
+
if (backdrop) {
|
|
499
|
+
backdrop.opacity = original.backdropOpacity!
|
|
500
|
+
backdrop.visibility = original.backdropVisibility!
|
|
501
|
+
}
|
|
502
|
+
if (owners.get(panel) === control) owners.delete(panel)
|
|
503
|
+
},
|
|
504
|
+
}
|
|
505
|
+
owners.set(panel, control)
|
|
506
|
+
if (content && points.includes('content')) content.verticalAlignment = 'top'
|
|
507
|
+
panel.verticalAlignment = 'top'
|
|
508
|
+
panel.marginBottom = 0
|
|
509
|
+
panel.visibility = 'hidden'
|
|
510
|
+
host.clipToBounds = true
|
|
511
|
+
handle.accessible = true
|
|
512
|
+
handle.accessibilityLabel =
|
|
513
|
+
options.accessibility?.label ?? (handle.accessibilityLabel || 'Bottom sheet')
|
|
514
|
+
handle.accessibilityValue = 'Closed'
|
|
515
|
+
if (backdrop) backdrop.visibility = 'hidden'
|
|
516
|
+
for (const view of [panel, handle, ...(scroll ? [scroll] : [])])
|
|
517
|
+
view.on('loaded', attach)
|
|
518
|
+
panel.on('unloaded', detach)
|
|
519
|
+
for (const view of new Set([
|
|
520
|
+
host,
|
|
521
|
+
panel,
|
|
522
|
+
...(content ? [content] : []),
|
|
523
|
+
...(scroll ? [scroll] : []),
|
|
524
|
+
]))
|
|
525
|
+
view.on('layoutChanged', queueMeasure)
|
|
526
|
+
backdrop?.on('tap', backdropTap)
|
|
527
|
+
if (isAndroid)
|
|
528
|
+
Application.android.on(Application.android.activityBackPressedEvent, back)
|
|
529
|
+
try {
|
|
530
|
+
attach()
|
|
531
|
+
} catch (error) {
|
|
532
|
+
control.dispose()
|
|
533
|
+
throw error
|
|
534
|
+
}
|
|
535
|
+
return control
|
|
536
|
+
}
|