@rafaelaugusto/jolt 0.1.0-beta.15 → 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.
Files changed (132) hide show
  1. package/README.md +9 -2
  2. package/dist/bottom-sheet/geometry.d.ts +18 -0
  3. package/dist/bottom-sheet/geometry.d.ts.map +1 -0
  4. package/dist/bottom-sheet/geometry.js +51 -0
  5. package/dist/bottom-sheet/geometry.js.map +1 -0
  6. package/dist/bottom-sheet/index.d.ts +56 -0
  7. package/dist/bottom-sheet/index.d.ts.map +1 -0
  8. package/dist/bottom-sheet/index.js +462 -0
  9. package/dist/bottom-sheet/index.js.map +1 -0
  10. package/dist/bottom-sheet/native.d.ts +11 -0
  11. package/dist/bottom-sheet/native.d.ts.map +1 -0
  12. package/dist/bottom-sheet/native.js +173 -0
  13. package/dist/bottom-sheet/native.js.map +1 -0
  14. package/dist/drag.js +1 -1
  15. package/dist/drag.js.map +1 -1
  16. package/dist/driver/android.d.ts.map +1 -1
  17. package/dist/driver/android.js +2 -0
  18. package/dist/driver/android.js.map +1 -1
  19. package/dist/driver/ios-scroll-delegate.d.ts +4 -0
  20. package/dist/driver/ios-scroll-delegate.d.ts.map +1 -0
  21. package/dist/driver/ios-scroll-delegate.js +26 -0
  22. package/dist/driver/ios-scroll-delegate.js.map +1 -0
  23. package/dist/driver/ios.d.ts.map +1 -1
  24. package/dist/driver/ios.js +2 -0
  25. package/dist/driver/ios.js.map +1 -1
  26. package/dist/gesture-native.d.ts +28 -0
  27. package/dist/gesture-native.d.ts.map +1 -0
  28. package/dist/gesture-native.js +61 -0
  29. package/dist/gesture-native.js.map +1 -0
  30. package/dist/gestures/index.d.ts +69 -0
  31. package/dist/gestures/index.d.ts.map +1 -0
  32. package/dist/gestures/index.js +262 -0
  33. package/dist/gestures/index.js.map +1 -0
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/live-spring-driver.d.ts +52 -0
  39. package/dist/live-spring-driver.d.ts.map +1 -0
  40. package/dist/live-spring-driver.js +104 -0
  41. package/dist/live-spring-driver.js.map +1 -0
  42. package/dist/mixer.d.ts +6 -0
  43. package/dist/mixer.d.ts.map +1 -1
  44. package/dist/mixer.js +41 -1
  45. package/dist/mixer.js.map +1 -1
  46. package/dist/motion-value.d.ts +34 -2
  47. package/dist/motion-value.d.ts.map +1 -1
  48. package/dist/motion-value.js +277 -10
  49. package/dist/motion-value.js.map +1 -1
  50. package/dist/native/index.d.ts +2 -0
  51. package/dist/native/index.d.ts.map +1 -1
  52. package/dist/native/index.js +1 -0
  53. package/dist/native/index.js.map +1 -1
  54. package/dist/native-property.d.ts +2 -0
  55. package/dist/native-property.d.ts.map +1 -1
  56. package/dist/native-property.js +12 -4
  57. package/dist/native-property.js.map +1 -1
  58. package/dist/reorder/handle.d.ts +6 -0
  59. package/dist/reorder/handle.d.ts.map +1 -0
  60. package/dist/reorder/handle.js +10 -0
  61. package/dist/reorder/handle.js.map +1 -0
  62. package/dist/reorder/index.d.ts +10 -0
  63. package/dist/reorder/index.d.ts.map +1 -0
  64. package/dist/reorder/index.js +233 -0
  65. package/dist/reorder/index.js.map +1 -0
  66. package/dist/reorder/list-view.d.ts +5 -0
  67. package/dist/reorder/list-view.d.ts.map +1 -0
  68. package/dist/reorder/list-view.js +238 -0
  69. package/dist/reorder/list-view.js.map +1 -0
  70. package/dist/reorder/model.d.ts +6 -0
  71. package/dist/reorder/model.d.ts.map +1 -0
  72. package/dist/reorder/model.js +30 -0
  73. package/dist/reorder/model.js.map +1 -0
  74. package/dist/reorder/surface.d.ts +6 -0
  75. package/dist/reorder/surface.d.ts.map +1 -0
  76. package/dist/reorder/surface.js +10 -0
  77. package/dist/reorder/surface.js.map +1 -0
  78. package/dist/reorder/types.d.ts +92 -0
  79. package/dist/reorder/types.d.ts.map +1 -0
  80. package/dist/reorder/types.js +2 -0
  81. package/dist/reorder/types.js.map +1 -0
  82. package/dist/scene-shader.d.ts +18 -0
  83. package/dist/scene-shader.d.ts.map +1 -0
  84. package/dist/scene-shader.js +76 -0
  85. package/dist/scene-shader.js.map +1 -0
  86. package/dist/value-observers.d.ts +3 -1
  87. package/dist/value-observers.d.ts.map +1 -1
  88. package/dist/value-observers.js +10 -2
  89. package/dist/value-observers.js.map +1 -1
  90. package/package.json +13 -1
  91. package/platforms/android/include.gradle +9 -0
  92. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +198 -0
  93. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSurface.java +53 -0
  94. package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +270 -0
  95. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderHandle.java +25 -0
  96. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSession.java +213 -0
  97. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSurface.java +27 -0
  98. package/platforms/android/java/org/rafaelaugusto/jolt/SpringState.java +39 -0
  99. package/platforms/android/proguard-rules.pro +5 -0
  100. package/platforms/ios/src/JoltGestureSession.h +18 -0
  101. package/platforms/ios/src/JoltGestureSession.m +222 -0
  102. package/platforms/ios/src/JoltLiveSpring.h +28 -0
  103. package/platforms/ios/src/JoltLiveSpring.m +236 -0
  104. package/platforms/ios/src/JoltReorderSession.h +15 -0
  105. package/platforms/ios/src/JoltReorderSession.m +205 -0
  106. package/platforms/ios/src/JoltSpringState.h +35 -0
  107. package/platforms/ios/src/module.modulemap +3 -0
  108. package/src/android-plugin.d.ts +93 -0
  109. package/src/bottom-sheet/geometry.ts +84 -0
  110. package/src/bottom-sheet/index.ts +536 -0
  111. package/src/bottom-sheet/native.ts +214 -0
  112. package/src/drag.ts +1 -1
  113. package/src/driver/android.ts +2 -0
  114. package/src/driver/ios-scroll-delegate.ts +23 -0
  115. package/src/driver/ios.ts +2 -0
  116. package/src/gesture-native.ts +139 -0
  117. package/src/gestures/index.ts +365 -0
  118. package/src/index.ts +3 -1
  119. package/src/ios-plugin.d.ts +90 -0
  120. package/src/live-spring-driver.ts +181 -0
  121. package/src/mixer.ts +43 -1
  122. package/src/motion-value.ts +318 -11
  123. package/src/native/index.ts +6 -0
  124. package/src/native-property.ts +17 -4
  125. package/src/reorder/handle.ts +9 -0
  126. package/src/reorder/index.ts +243 -0
  127. package/src/reorder/list-view.ts +262 -0
  128. package/src/reorder/model.ts +47 -0
  129. package/src/reorder/surface.ts +10 -0
  130. package/src/reorder/types.ts +87 -0
  131. package/src/scene-shader.ts +139 -0
  132. package/src/value-observers.ts +12 -2
package/README.md CHANGED
@@ -31,7 +31,7 @@ NativeScript's `view.animate()` covers the basics and stops there: one curve for
31
31
 
32
32
  Jolt computes the motion once, in JavaScript, when the animation starts, and hands it to the platform as keyframes: Core Animation on iOS, `ObjectAnimator` on Android. Those default view tracks play without per-frame JavaScript. Real-size layout, opt-in subscribers, gesture input and timeline step activation retain their documented [execution limits](https://jolt.rafaelaugusto.dev/guide/how-it-works). Springs follow real physics, and an animation that interrupts another one starts from where the view is, at the speed it was going.
33
33
 
34
- The [beta.15 release](https://github.com/rafaelaugustos/jolt/releases/tag/v0.1.0-beta.15) adds real width/height/auto animation and SceneKit camera field of view. Install `@rafaelaugusto/jolt@0.1.0-beta.15`; for external native properties, also install matching `@rafaelaugusto/jolt-native@0.1.0-beta.15` and rebuild the native app.
34
+ The [beta.17 release](https://github.com/rafaelaugustos/jolt/releases/tag/v0.1.0-beta.17) adds a bottom sheet with native scroll handoff, native list reordering, and SceneKit environment/shader inputs. The redesigned preview includes 55 examples, English/Portuguese and light/dark themes. Install `@rafaelaugusto/jolt@0.1.0-beta.17` and rebuild the native app. For external native properties, also install matching `@rafaelaugusto/jolt-native@0.1.0-beta.17`.
35
35
 
36
36
  ## Features
37
37
 
@@ -41,6 +41,11 @@ The [beta.15 release](https://github.com/rafaelaugustos/jolt/releases/tag/v0.1.0
41
41
  - [Motion paths](#motion-along-a-path) with optional tangent orientation on iOS and Android.
42
42
  - [Colors](#colors) for backgrounds, borders and text, interpolated in OKLab on iOS and Android.
43
43
  - [Motion values](#motion-values): one value driving several properties and views, mapped like `interpolate`.
44
+ - [Live springs](https://jolt.rafaelaugusto.dev/guide/live-springs): retained native followers with position and velocity that continue through target changes.
45
+ - [Native gestures](https://jolt.rafaelaugusto.dev/guide/gestures): simultaneous pan/pinch/rotation, tap arbitration, axis competition and vertical Core ScrollView handoff.
46
+ - [Bottom sheet](https://jolt.rafaelaugusto.dev/guide/bottom-sheet): native drag/scroll handoff, content and percentage stops, keyboard following and dismissal.
47
+ - [List reordering](https://jolt.rafaelaugusto.dev/guide/reorder): stable keys, native drag snapshots, neighbour springs and edge scrolling through recycled cells.
48
+ - [SceneKit environment and shaders](https://jolt.rafaelaugusto.dev/guide/scene-shaders): native environment intensity and typed scalar/vector shader inputs on iOS.
44
49
  - [Layered motion](#layered-motion): several animations on one property, added or multiplied.
45
50
  - [Gradients](#gradients): native linear, radial and conic gradients with animated colors, angle, center and radius.
46
51
  - [Motion scopes and states](https://jolt.rafaelaugusto.dev/guide/motion-controllers) for component cleanup and coordinated visual states, with a transition per target.
@@ -674,7 +679,9 @@ Each running property remembers its curve and start time. On Android, interrupti
674
679
 
675
680
  ## Demo
676
681
 
677
- [`demo/`](./demo) opens an interactive assistant on iOS and Android: a fan of document tools, a voice screen with letter-by-letter text, and a staged conversation with document previews and a rolling countdown, with the screens cross-fading through the Vue entry. Open **Help → Watch demo** to replay the flow. The orb is an animated illustration; speech, documents and audio are sample data.
682
+ [`demo/`](./demo) now opens **Jolt Motion Studio**: a branded animated splash, a moving home, and 55 searchable experiments with category filters and native page navigation through Vue Router NS. The iOS preview includes the Jolt name and icon; launching normally always opens the collection. See the [native walkthrough](./docs/guide/demo.md).
683
+
684
+ The assistant remains available through `--screen=home` on iOS or `--es screen home` on Android: a fan of document tools, a voice screen with letter-by-letter text, and a staged conversation with document previews and a rolling countdown, with the screens cross-fading through the Vue entry. Open **Help → Watch demo** to replay the flow. The orb is an animated illustration; speech, documents and audio are sample data.
678
685
 
679
686
  | Assistant | Presets | Springs | Stagger |
680
687
  | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------: |
@@ -0,0 +1,18 @@
1
+ export type SheetSnap = number | `${number}%` | 'content';
2
+ export interface SheetStop {
3
+ point: SheetSnap;
4
+ height: number;
5
+ y: number;
6
+ }
7
+ export interface SheetGeometry {
8
+ height: number;
9
+ closedY: number;
10
+ top: number;
11
+ stops: SheetStop[];
12
+ }
13
+ export declare function validateSnaps(points: readonly SheetSnap[]): void;
14
+ /** Keyboard overlap is relative to the host, so adjustResize is not subtracted twice. */
15
+ export declare function sheetGeometry(points: readonly SheetSnap[], hostHeight: number, hostBottom: number, windowHeight: number, keyboardHeight: number, safeTop: number, safeBottom: number, contentHeight: number): SheetGeometry;
16
+ /** Projection selects a stop; the native spring retains the actual release velocity. */
17
+ export declare function releaseStop(geometry: SheetGeometry, y: number, velocity: number, dismissible: boolean): SheetStop | undefined;
18
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/bottom-sheet/geometry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,GAAG,SAAS,CAAA;AAEzD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAgBhE;AAED,yFAAyF;AACzF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACpB,aAAa,CAoBf;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,aAAa,EACvB,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,OAAO,GACnB,SAAS,GAAG,SAAS,CAYvB"}
@@ -0,0 +1,51 @@
1
+ export function validateSnaps(points) {
2
+ if (!Array.isArray(points) || !points.length)
3
+ throw new TypeError('bottomSheet() needs at least one snap point.');
4
+ for (const point of points) {
5
+ if (point === 'content')
6
+ continue;
7
+ if (typeof point === 'number' && Number.isFinite(point) && point > 0)
8
+ continue;
9
+ if (typeof point === 'string' && /^(?:\d+(?:\.\d+)?|\.\d+)%$/.test(point)) {
10
+ const percent = Number.parseFloat(point);
11
+ if (percent > 0 && percent <= 100)
12
+ continue;
13
+ }
14
+ throw new RangeError('Sheet snaps must be positive DIPs, 0–100% (exclusive of 0), or content.');
15
+ }
16
+ if (new Set(points).size !== points.length)
17
+ throw new RangeError('Sheet snap points must not repeat.');
18
+ }
19
+ /** Keyboard overlap is relative to the host, so adjustResize is not subtracted twice. */
20
+ export function sheetGeometry(points, hostHeight, hostBottom, windowHeight, keyboardHeight, safeTop, safeBottom, contentHeight) {
21
+ const top = Math.max(0, safeTop);
22
+ const overlap = Math.max(0, Math.min(hostHeight, hostBottom - (windowHeight - keyboardHeight)));
23
+ const bottom = Math.max(Math.max(0, safeBottom), overlap);
24
+ const height = Math.max(1, hostHeight - top - bottom);
25
+ const closedY = Math.max(height, hostHeight - top);
26
+ const stops = points.map((point) => {
27
+ const desired = point === 'content'
28
+ ? contentHeight
29
+ : typeof point === 'number'
30
+ ? point
31
+ : (Number.parseFloat(point) / 100) * height;
32
+ const visible = Math.max(1, Math.min(height, desired));
33
+ return { point, height: visible, y: height - visible };
34
+ });
35
+ return { height, closedY, top, stops };
36
+ }
37
+ /** Projection selects a stop; the native spring retains the actual release velocity. */
38
+ export function releaseStop(geometry, y, velocity, dismissible) {
39
+ const projected = y + Math.max(-4000, Math.min(4000, velocity)) * 0.2;
40
+ let distance = dismissible ? Math.abs(projected - geometry.closedY) : Infinity;
41
+ let result;
42
+ for (const stop of geometry.stops) {
43
+ const next = Math.abs(projected - stop.y);
44
+ if (next <= distance) {
45
+ distance = next;
46
+ result = stop;
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","sourceRoot":"","sources":["../../src/bottom-sheet/geometry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { ScrollView, View } from '@nativescript/core';
2
+ import { type SpringValueOptions } from '../motion-value.js';
3
+ import { GestureSurface } from '../gestures/index.js';
4
+ import { type SheetSnap } from './geometry.js';
5
+ export { GestureSurface };
6
+ export type { SheetSnap };
7
+ export interface BottomSheetOptions {
8
+ /** A separate, focusable drag handle inside the panel; receives native accessibility actions. */
9
+ handle: View;
10
+ snap: readonly SheetSnap[];
11
+ /** A descendant vertical ScrollView. Native list plugins need their own integration. */
12
+ scrollView?: ScrollView;
13
+ /** Measured content for the content snap; defaults to scrollView.content. */
14
+ content?: View;
15
+ /** A sibling behind the panel, filling the same one-cell GridLayout. */
16
+ backdrop?: View;
17
+ backdropOpacity?: number;
18
+ /** Follow temporarily expands above the keyboard, then restores the selected snap. */
19
+ keyboard?: 'follow' | 'none';
20
+ /** Whether dragging, the backdrop, back and accessibility can dismiss. Defaults to true. */
21
+ dismissible?: boolean;
22
+ safeArea?: {
23
+ top?: number;
24
+ bottom?: number;
25
+ };
26
+ returnFocus?: View;
27
+ spring?: Pick<SpringValueOptions, 'stiffness' | 'damping' | 'mass'>;
28
+ accessibility?: {
29
+ label?: string;
30
+ expand?: string;
31
+ collapse?: string;
32
+ dismiss?: string;
33
+ };
34
+ onChange?: (point: SheetSnap | null) => void;
35
+ /** Called after a close settles; interrupted closes do not dismiss. */
36
+ onDismiss?: () => void;
37
+ }
38
+ export interface BottomSheet {
39
+ readonly isOpen: boolean;
40
+ readonly snapPoint: SheetSnap | null;
41
+ readonly visibleHeight: number;
42
+ readonly paused: boolean;
43
+ readonly disposed: boolean;
44
+ open(point?: SheetSnap): void;
45
+ snapTo(point: SheetSnap): void;
46
+ expand(): void;
47
+ collapse(): void;
48
+ dismiss(): void;
49
+ remeasure(): void;
50
+ pause(): void;
51
+ play(): void;
52
+ dispose(): void;
53
+ }
54
+ /** An in-page sheet, built from native gestures and live spring bindings. */
55
+ export declare function bottomSheet(panel: GestureSurface, options: BottomSheetOptions): BottomSheet;
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bottom-sheet/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAEV,IAAI,EAEL,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAQ,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAClE,OAAO,EAAE,cAAc,EAA2B,MAAM,sBAAsB,CAAA;AAE9E,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,eAAe,CAAA;AAStB,OAAO,EAAE,cAAc,EAAE,CAAA;AACzB,YAAY,EAAE,SAAS,EAAE,CAAA;AAEzB,MAAM,WAAW,kBAAkB;IACjC,iGAAiG;IACjG,MAAM,EAAE,IAAI,CAAA;IACZ,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;IAC1B,wFAAwF;IACxF,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,wEAAwE;IACxE,QAAQ,CAAC,EAAE,IAAI,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC5B,4FAA4F;IAC5F,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC,CAAA;IACnE,aAAa,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAA;IAC5C,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;IAC7B,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAC9B,MAAM,IAAI,IAAI,CAAA;IACd,QAAQ,IAAI,IAAI,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,SAAS,IAAI,IAAI,CAAA;IACjB,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;IACZ,OAAO,IAAI,IAAI,CAAA;CAChB;AAoBD,6EAA6E;AAC7E,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,kBAAkB,GAC1B,WAAW,CA2bb"}
@@ -0,0 +1,462 @@
1
+ import { Application, GridLayout, ScrollView, Trace, View, isAndroid, } from '@nativescript/core';
2
+ import { bind } from '../motion-value.js';
3
+ import { GestureSurface, gestures } from '../gestures/index.js';
4
+ import { currentKeyboard, onKeyboard } from '../keyboard.js';
5
+ import { releaseStop, sheetGeometry, validateSnaps, } from './geometry.js';
6
+ import { captureFocus, dismissKeyboard, revealFocusedField, sheetActions, windowHeight, } from './native.js';
7
+ export { GestureSurface };
8
+ const owners = new WeakMap();
9
+ const descendant = (child, parent) => {
10
+ for (let current = child.parent; current; current = current.parent)
11
+ if (current === parent)
12
+ return true;
13
+ return false;
14
+ };
15
+ const report = (callback) => {
16
+ try {
17
+ callback?.();
18
+ }
19
+ catch (error) {
20
+ Trace.write(`Jolt bottom sheet callback failed: ${String(error)}`, Trace.categories.Error, Trace.messageType.error);
21
+ }
22
+ };
23
+ /** An in-page sheet, built from native gestures and live spring bindings. */
24
+ export function bottomSheet(panel, options) {
25
+ if (!(panel instanceof GestureSurface) || !(panel.parent instanceof GridLayout))
26
+ throw new TypeError('bottomSheet() needs a GestureSurface inside an overlay GridLayout.');
27
+ if (!options || typeof options !== 'object')
28
+ throw new TypeError('Sheet options are required.');
29
+ validateSnaps(options.snap);
30
+ const points = [...options.snap], host = panel.parent;
31
+ if (!(options.handle instanceof View) || !descendant(options.handle, panel))
32
+ throw new TypeError('Provide a separate accessibility handle inside the sheet.');
33
+ const handle = options.handle, scroll = options.scrollView;
34
+ if (scroll &&
35
+ (!(scroll instanceof ScrollView) ||
36
+ scroll.orientation !== 'vertical' ||
37
+ !descendant(scroll, panel)))
38
+ throw new TypeError('Sheet scrolling needs a descendant vertical ScrollView.');
39
+ const content = options.content ?? scroll?.content;
40
+ if (content && (!(content instanceof View) || !descendant(content, panel)))
41
+ throw new TypeError('Sheet content must be a descendant of its panel.');
42
+ if (points.includes('content') && !content)
43
+ throw new TypeError('A content snap needs scrollView.content or an explicit content view.');
44
+ const backdrop = options.backdrop;
45
+ if (backdrop &&
46
+ (!(backdrop instanceof View) || backdrop.parent !== host || backdrop === panel))
47
+ throw new TypeError('The backdrop must be a sibling behind the sheet.');
48
+ const opacity = options.backdropOpacity ?? 0.45;
49
+ if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1)
50
+ throw new RangeError('Backdrop opacity must be between zero and one.');
51
+ if (options.keyboard !== undefined && !['follow', 'none'].includes(options.keyboard))
52
+ throw new TypeError('Sheet keyboard behavior must be follow or none.');
53
+ for (const inset of Object.values(options.safeArea ?? {}))
54
+ if (!Number.isFinite(inset) || inset < 0)
55
+ throw new RangeError('Safe-area insets must be finite and nonnegative.');
56
+ if (options.dismissible !== undefined && typeof options.dismissible !== 'boolean')
57
+ throw new TypeError('dismissible must be boolean.');
58
+ for (const callback of [options.onChange, options.onDismiss])
59
+ if (callback !== undefined && typeof callback !== 'function')
60
+ throw new TypeError('Sheet callbacks must be functions.');
61
+ owners.get(panel)?.dispose();
62
+ const original = {
63
+ contentAlignment: content?.verticalAlignment,
64
+ height: panel.height,
65
+ marginTop: panel.marginTop,
66
+ marginBottom: panel.marginBottom,
67
+ alignment: panel.verticalAlignment,
68
+ visibility: panel.visibility,
69
+ y: panel.translateY,
70
+ clipping: host.clipToBounds,
71
+ backdropOpacity: backdrop?.opacity,
72
+ backdropVisibility: backdrop?.visibility,
73
+ accessible: handle.accessible,
74
+ label: handle.accessibilityLabel,
75
+ value: handle.accessibilityValue,
76
+ };
77
+ let geometry, input;
78
+ let opened = false, point = points[0], ended = false, held = false, changing = false, pendingDismiss = false, contactSettled = false, focusCaptured = false;
79
+ let keyboard = currentKeyboard(), timer;
80
+ let releasePanel = () => { }, releaseBackdrop = () => { }, releaseActions = () => { }, releaseKeyboard = () => { };
81
+ let releaseClose = () => { }, restoreFocus = () => { };
82
+ const dismissible = options.dismissible !== false;
83
+ const followKeyboard = options.keyboard !== 'none';
84
+ const keyboardOpen = () => followKeyboard && keyboard.visible;
85
+ const targetY = () => !opened
86
+ ? geometry.closedY
87
+ : keyboardOpen()
88
+ ? 0
89
+ : geometry.stops.find((stop) => stop.point === point).y;
90
+ const minimum = () => keyboardOpen() ? 0 : Math.min(...geometry.stops.map((stop) => stop.y));
91
+ function finishClose() {
92
+ releaseClose();
93
+ releaseClose = () => { };
94
+ if (ended || opened)
95
+ return;
96
+ panel.visibility = 'hidden';
97
+ if (backdrop)
98
+ backdrop.visibility = 'hidden';
99
+ input?.pause();
100
+ if (pendingDismiss) {
101
+ pendingDismiss = false;
102
+ const restore = restoreFocus;
103
+ restoreFocus = () => { };
104
+ focusCaptured = false;
105
+ restore();
106
+ report(options.onDismiss);
107
+ }
108
+ }
109
+ function move() {
110
+ if (!input || !geometry || held || ended)
111
+ return;
112
+ changing = true;
113
+ try {
114
+ input.updateBounds({
115
+ top: minimum(),
116
+ bottom: !opened || dismissible
117
+ ? geometry.closedY
118
+ : Math.max(...geometry.stops.map((stop) => stop.y)),
119
+ });
120
+ input.settleTo({ y: targetY() });
121
+ }
122
+ finally {
123
+ changing = false;
124
+ }
125
+ if (!opened) {
126
+ releaseClose();
127
+ const check = () => {
128
+ if (!opened &&
129
+ geometry &&
130
+ input &&
131
+ Math.abs(input.y.get() - geometry.closedY) < 0.01)
132
+ finishClose();
133
+ };
134
+ releaseClose = input.y.subscribe(check, { immediate: false });
135
+ check();
136
+ }
137
+ }
138
+ function setTarget(next) {
139
+ if (ended)
140
+ return;
141
+ if (next !== null && !points.includes(next))
142
+ throw new RangeError('Choose one of the configured sheet snap points.');
143
+ const wasOpen = opened, changed = (next !== null) !== opened || (next !== null && next !== point);
144
+ releaseClose();
145
+ releaseClose = () => { };
146
+ if (next !== null) {
147
+ if (!opened && !pendingDismiss && panel.isLoaded) {
148
+ restoreFocus = captureFocus(panel, options.returnFocus);
149
+ focusCaptured = true;
150
+ }
151
+ opened = true;
152
+ pendingDismiss = false;
153
+ point = next;
154
+ panel.visibility = 'visible';
155
+ if (backdrop)
156
+ backdrop.visibility = 'visible';
157
+ if (!held)
158
+ input?.play();
159
+ }
160
+ else {
161
+ opened = false;
162
+ pendingDismiss ||= wasOpen;
163
+ dismissKeyboard(panel);
164
+ }
165
+ handle.accessibilityValue = opened ? String(point) : 'Closed';
166
+ move();
167
+ if (changed)
168
+ report(() => options.onChange?.(next));
169
+ }
170
+ function adjacent(expand) {
171
+ if (ended)
172
+ return;
173
+ if (!opened) {
174
+ if (expand)
175
+ setTarget(point);
176
+ return;
177
+ }
178
+ if (!geometry)
179
+ return;
180
+ const current = geometry.stops.find((stop) => stop.point === point);
181
+ const candidates = geometry.stops
182
+ .filter((stop) => expand
183
+ ? stop.height > current.height + 0.01
184
+ : stop.height < current.height - 0.01)
185
+ .sort((a, b) => (expand ? a.height - b.height : b.height - a.height));
186
+ if (candidates[0])
187
+ setTarget(candidates[0].point);
188
+ else if (!expand && dismissible)
189
+ setTarget(null);
190
+ }
191
+ function remeasure() {
192
+ if (ended || !panel.isLoaded || !handle.isLoaded || (scroll && !scroll.isLoaded))
193
+ return;
194
+ const size = host.getActualSize();
195
+ if (!(size.height > 0))
196
+ return;
197
+ const safe = host.getSafeAreaInsets();
198
+ const top = options.safeArea?.top ?? safe.top;
199
+ const bottom = options.safeArea?.bottom ?? safe.bottom;
200
+ const chrome = scroll
201
+ ? Math.max(0, panel.getActualSize().height - scroll.getActualSize().height)
202
+ : 0;
203
+ const contentHeight = (content?.getActualSize().height ?? 0) + chrome;
204
+ const next = sheetGeometry(points, size.height, (host.getLocationInWindow()?.y ?? 0) + size.height, windowHeight(host), followKeyboard ? keyboard.height : 0, top, bottom, contentHeight);
205
+ const previous = geometry;
206
+ geometry = next;
207
+ const panelHeight = keyboardOpen()
208
+ ? next.height
209
+ : Math.max(...next.stops.map((stop) => stop.height));
210
+ if (panel.height !== panelHeight)
211
+ panel.height = panelHeight;
212
+ if (panel.marginTop !== next.top)
213
+ panel.marginTop = next.top;
214
+ if (!input) {
215
+ input = gestures(panel, {
216
+ pan: { axis: 'y', bounds: { top: minimum(), bottom: next.closedY } },
217
+ initial: { y: next.closedY },
218
+ scrollView: scroll,
219
+ spring: options.spring,
220
+ onContactStart() {
221
+ const wasOpen = opened;
222
+ contactSettled = false;
223
+ releaseClose();
224
+ releaseClose = () => { };
225
+ opened = true;
226
+ pendingDismiss = false;
227
+ handle.accessibilityValue = String(point);
228
+ if (!wasOpen)
229
+ report(() => options.onChange?.(point));
230
+ },
231
+ onContactEnd(event) {
232
+ if (contactSettled || changing || held || ended || !geometry)
233
+ return;
234
+ const stop = releaseStop(geometry, event.y, 0, dismissible);
235
+ setTarget(stop?.point ?? null);
236
+ },
237
+ onStart() {
238
+ releaseClose();
239
+ releaseClose = () => { };
240
+ opened = true;
241
+ pendingDismiss = false;
242
+ },
243
+ onEnd(event) {
244
+ contactSettled = true;
245
+ if (!geometry)
246
+ return;
247
+ const stop = releaseStop(geometry, event.y, event.velocityY, dismissible);
248
+ setTarget(stop?.point ?? null);
249
+ },
250
+ onCancel() {
251
+ contactSettled = true;
252
+ if (!changing && !held && !ended)
253
+ move();
254
+ },
255
+ });
256
+ releasePanel = bind(panel, { y: input.y });
257
+ if (held || !opened)
258
+ input.pause();
259
+ }
260
+ if (!previous ||
261
+ previous.closedY !== next.closedY ||
262
+ previous.height !== next.height ||
263
+ previous.stops.some((stop, i) => stop.y !== next.stops[i].y)) {
264
+ releaseBackdrop();
265
+ releaseBackdrop = backdrop
266
+ ? bind(backdrop, { opacity: input.y.to([minimum(), next.closedY], [opacity, 0]) })
267
+ : () => { };
268
+ if (!input.active)
269
+ move();
270
+ else
271
+ input.updateBounds({
272
+ top: minimum(),
273
+ bottom: dismissible
274
+ ? next.closedY
275
+ : Math.max(...next.stops.map((stop) => stop.y)),
276
+ });
277
+ }
278
+ if (opened && keyboardOpen())
279
+ revealFocusedField(panel, scroll);
280
+ }
281
+ function queueMeasure() {
282
+ if (ended || timer !== undefined)
283
+ return;
284
+ timer = setTimeout(() => {
285
+ timer = undefined;
286
+ remeasure();
287
+ }, 0);
288
+ }
289
+ function back(event) {
290
+ if (opened && panel.isLoaded && dismissible) {
291
+ event.cancel = true;
292
+ setTarget(null);
293
+ }
294
+ }
295
+ function backdropTap() {
296
+ if (opened && dismissible)
297
+ setTarget(null);
298
+ }
299
+ function attach() {
300
+ if (ended || !panel.isLoaded || !handle.isLoaded || (scroll && !scroll.isLoaded))
301
+ return;
302
+ if (opened) {
303
+ panel.visibility = 'visible';
304
+ if (backdrop)
305
+ backdrop.visibility = 'visible';
306
+ }
307
+ if (opened && !focusCaptured) {
308
+ restoreFocus = captureFocus(panel, options.returnFocus);
309
+ focusCaptured = true;
310
+ }
311
+ releaseActions();
312
+ const labels = options.accessibility;
313
+ releaseActions = sheetActions(handle, [
314
+ { label: labels?.expand ?? 'Expand', run: () => adjacent(true) },
315
+ { label: labels?.collapse ?? 'Collapse', run: () => adjacent(false) },
316
+ ...(dismissible
317
+ ? [{ label: labels?.dismiss ?? 'Dismiss', run: () => setTarget(null) }]
318
+ : []),
319
+ ]);
320
+ releaseKeyboard();
321
+ releaseKeyboard = onKeyboard((state) => {
322
+ keyboard = state;
323
+ remeasure();
324
+ if (opened)
325
+ move();
326
+ queueMeasure();
327
+ });
328
+ keyboard = currentKeyboard();
329
+ remeasure();
330
+ queueMeasure();
331
+ }
332
+ function detach() {
333
+ if (timer !== undefined)
334
+ clearTimeout(timer);
335
+ timer = undefined;
336
+ releaseClose();
337
+ releaseClose = () => { };
338
+ if (backdrop)
339
+ backdrop.visibility = 'hidden';
340
+ releaseActions();
341
+ releaseActions = () => { };
342
+ releaseKeyboard();
343
+ releaseKeyboard = () => { };
344
+ releasePanel();
345
+ releasePanel = () => { };
346
+ releaseBackdrop();
347
+ releaseBackdrop = () => { };
348
+ changing = true;
349
+ input?.dispose();
350
+ input = undefined;
351
+ geometry = undefined;
352
+ changing = false;
353
+ }
354
+ const control = {
355
+ get isOpen() {
356
+ return opened;
357
+ },
358
+ get snapPoint() {
359
+ return opened ? point : null;
360
+ },
361
+ get visibleHeight() {
362
+ return geometry && input
363
+ ? Math.max(0, Math.min(geometry.height, geometry.height - input.y.get()))
364
+ : 0;
365
+ },
366
+ get paused() {
367
+ return held;
368
+ },
369
+ get disposed() {
370
+ return ended;
371
+ },
372
+ open: (next = point) => setTarget(next),
373
+ snapTo: (next) => setTarget(next),
374
+ expand: () => adjacent(true),
375
+ collapse: () => adjacent(false),
376
+ dismiss: () => setTarget(null),
377
+ remeasure,
378
+ pause() {
379
+ held = true;
380
+ input?.pause();
381
+ },
382
+ play() {
383
+ if (ended)
384
+ return;
385
+ held = false;
386
+ input?.play();
387
+ move();
388
+ },
389
+ dispose() {
390
+ if (ended)
391
+ return;
392
+ ended = true;
393
+ detach();
394
+ for (const view of [panel, handle, ...(scroll ? [scroll] : [])])
395
+ view.off('loaded', attach);
396
+ panel.off('unloaded', detach);
397
+ for (const view of new Set([
398
+ host,
399
+ panel,
400
+ ...(content ? [content] : []),
401
+ ...(scroll ? [scroll] : []),
402
+ ]))
403
+ view.off('layoutChanged', queueMeasure);
404
+ backdrop?.off('tap', backdropTap);
405
+ if (isAndroid)
406
+ Application.android.off(Application.android.activityBackPressedEvent, back);
407
+ if (content)
408
+ content.verticalAlignment = original.contentAlignment;
409
+ panel.height = original.height;
410
+ panel.marginTop = original.marginTop;
411
+ panel.marginBottom = original.marginBottom;
412
+ panel.verticalAlignment = original.alignment;
413
+ panel.visibility = original.visibility;
414
+ panel.translateY = original.y;
415
+ host.clipToBounds = original.clipping;
416
+ handle.accessible = original.accessible;
417
+ handle.accessibilityLabel = original.label;
418
+ handle.accessibilityValue = original.value;
419
+ if (backdrop) {
420
+ backdrop.opacity = original.backdropOpacity;
421
+ backdrop.visibility = original.backdropVisibility;
422
+ }
423
+ if (owners.get(panel) === control)
424
+ owners.delete(panel);
425
+ },
426
+ };
427
+ owners.set(panel, control);
428
+ if (content && points.includes('content'))
429
+ content.verticalAlignment = 'top';
430
+ panel.verticalAlignment = 'top';
431
+ panel.marginBottom = 0;
432
+ panel.visibility = 'hidden';
433
+ host.clipToBounds = true;
434
+ handle.accessible = true;
435
+ handle.accessibilityLabel =
436
+ options.accessibility?.label ?? (handle.accessibilityLabel || 'Bottom sheet');
437
+ handle.accessibilityValue = 'Closed';
438
+ if (backdrop)
439
+ backdrop.visibility = 'hidden';
440
+ for (const view of [panel, handle, ...(scroll ? [scroll] : [])])
441
+ view.on('loaded', attach);
442
+ panel.on('unloaded', detach);
443
+ for (const view of new Set([
444
+ host,
445
+ panel,
446
+ ...(content ? [content] : []),
447
+ ...(scroll ? [scroll] : []),
448
+ ]))
449
+ view.on('layoutChanged', queueMeasure);
450
+ backdrop?.on('tap', backdropTap);
451
+ if (isAndroid)
452
+ Application.android.on(Application.android.activityBackPressedEvent, back);
453
+ try {
454
+ attach();
455
+ }
456
+ catch (error) {
457
+ control.dispose();
458
+ throw error;
459
+ }
460
+ return control;
461
+ }
462
+ //# sourceMappingURL=index.js.map