@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,205 @@
|
|
|
1
|
+
#import "JoltReorderSession.h"
|
|
2
|
+
|
|
3
|
+
@implementation JoltReorderHandle
|
|
4
|
+
- (BOOL)canBecomeFirstResponder { return YES; }
|
|
5
|
+
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
|
|
6
|
+
[self becomeFirstResponder]; return [super beginTrackingWithTouch:touch withEvent:event];
|
|
7
|
+
}
|
|
8
|
+
- (NSArray<UIKeyCommand *> *)keyCommands {
|
|
9
|
+
if (!self.onMove) return [super keyCommands];
|
|
10
|
+
UIKeyCommand *up = [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierAlternate action:@selector(moveUp:)];
|
|
11
|
+
UIKeyCommand *down = [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierAlternate action:@selector(moveDown:)];
|
|
12
|
+
if (@available(iOS 15.0, *)) { up.wantsPriorityOverSystemBehavior = YES; down.wantsPriorityOverSystemBehavior = YES; }
|
|
13
|
+
return @[up, down];
|
|
14
|
+
}
|
|
15
|
+
- (void)moveUp:(UIKeyCommand *)command { if (self.onMove) self.onMove(-1); }
|
|
16
|
+
- (void)moveDown:(UIKeyCommand *)command { if (self.onMove) self.onMove(1); }
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
@interface JoltReorderCellState : NSObject
|
|
20
|
+
@property(nonatomic) CATransform3D transform;
|
|
21
|
+
@property(nonatomic) BOOL cellClips, contentClips;
|
|
22
|
+
@property(nonatomic) CGFloat alpha, offset;
|
|
23
|
+
@property(nonatomic) NSInteger index;
|
|
24
|
+
@end
|
|
25
|
+
@implementation JoltReorderCellState
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
@interface JoltReorderSession () <UIGestureRecognizerDelegate>
|
|
29
|
+
@property(nonatomic, weak) UITableView *list;
|
|
30
|
+
@property(nonatomic) UILongPressGestureRecognizer *press;
|
|
31
|
+
@property(nonatomic) NSHashTable<UIView *> *handles;
|
|
32
|
+
@property(nonatomic) NSMapTable<UITableViewCell *, JoltReorderCellState *> *cells;
|
|
33
|
+
@property(nonatomic) UIView *overlay;
|
|
34
|
+
@property(nonatomic) CADisplayLink *clock;
|
|
35
|
+
@property(nonatomic, copy) void (^onState)(NSInteger, NSInteger, NSInteger);
|
|
36
|
+
@property(nonatomic) NSInteger from, target, count, generation;
|
|
37
|
+
@property(nonatomic) CGFloat edgeSize, edgeSpeed, height, grabOffset, pointerY, pointerX;
|
|
38
|
+
@property(nonatomic) CGSize viewport;
|
|
39
|
+
@property(nonatomic) double previousTime;
|
|
40
|
+
@property(nonatomic) BOOL enabled, disposed, handlesOnly, reduced, settling, scrollEnabled, cancelling;
|
|
41
|
+
@property(nonatomic, readwrite) BOOL active;
|
|
42
|
+
@end
|
|
43
|
+
|
|
44
|
+
@implementation JoltReorderSession
|
|
45
|
+
+ (instancetype)attach:(UITableView *)list options:(NSString *)options onState:(void (^)(NSInteger, NSInteger, NSInteger))onState {
|
|
46
|
+
if (![list isKindOfClass:UITableView.class]) @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Reorder requires a UITableView" userInfo:nil];
|
|
47
|
+
NSDictionary *config = [NSJSONSerialization JSONObjectWithData:[options dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
|
|
48
|
+
JoltReorderSession *s = [self new]; s.list = list; s.onState = onState; s.enabled = YES;
|
|
49
|
+
s.edgeSize = [config[@"edgeSize"] doubleValue]; s.edgeSpeed = [config[@"edgeSpeed"] doubleValue];
|
|
50
|
+
s.reduced = [config[@"reducedMotion"] boolValue]; s.handlesOnly = [config[@"handles"] boolValue];
|
|
51
|
+
s.handles = [NSHashTable weakObjectsHashTable]; s.cells = [NSMapTable weakToStrongObjectsMapTable];
|
|
52
|
+
s.press = [[UILongPressGestureRecognizer alloc] initWithTarget:s action:@selector(pressed:)];
|
|
53
|
+
s.press.minimumPressDuration = [config[@"longPress"] doubleValue] / 1000.; s.press.numberOfTouchesRequired = 1;
|
|
54
|
+
s.press.allowableMovement = 10; s.press.delegate = s;
|
|
55
|
+
[list addGestureRecognizer:s.press];
|
|
56
|
+
return s;
|
|
57
|
+
}
|
|
58
|
+
- (void)registerHandle:(UIView *)view { if (!self.disposed && view) [self.handles addObject:view]; }
|
|
59
|
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)recognizer shouldReceiveTouch:(UITouch *)touch {
|
|
60
|
+
if (self.active) { [self cancel]; return NO; }
|
|
61
|
+
if (!self.enabled || self.disposed || self.active || !self.list.window) return NO;
|
|
62
|
+
if (!self.handlesOnly) return YES;
|
|
63
|
+
for (UIView *view = touch.view; view && view != self.list; view = view.superview)
|
|
64
|
+
if ([self.handles containsObject:view]) return YES;
|
|
65
|
+
return NO;
|
|
66
|
+
}
|
|
67
|
+
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recognizer {
|
|
68
|
+
NSIndexPath *path = [self.list indexPathForRowAtPoint:[recognizer locationInView:self.list]];
|
|
69
|
+
return path && path.section == 0 && self.list.numberOfSections == 1 && !self.list.dragging && !self.list.decelerating;
|
|
70
|
+
}
|
|
71
|
+
- (void)emit:(NSInteger)state detail:(NSInteger)detail { if (!self.disposed && self.onState) self.onState(state, self.from, detail); }
|
|
72
|
+
- (void)pressed:(UILongPressGestureRecognizer *)press {
|
|
73
|
+
if (press.state == UIGestureRecognizerStateBegan) [self begin];
|
|
74
|
+
else if (press.state == UIGestureRecognizerStateChanged && self.active && !self.settling) {
|
|
75
|
+
CGPoint p = [press locationInView:self.list.window]; self.pointerY = p.y; self.pointerX = p.x; [self update];
|
|
76
|
+
} else if (press.state == UIGestureRecognizerStateEnded && self.active) [self drop];
|
|
77
|
+
else if (press.state == UIGestureRecognizerStateCancelled || press.state == UIGestureRecognizerStateFailed) [self cancel];
|
|
78
|
+
}
|
|
79
|
+
- (void)begin {
|
|
80
|
+
UITableView *list = self.list;
|
|
81
|
+
NSIndexPath *path = [list indexPathForRowAtPoint:[self.press locationInView:list]];
|
|
82
|
+
UITableViewCell *cell = path ? [list cellForRowAtIndexPath:path] : nil;
|
|
83
|
+
if (!cell || path.section != 0 || !list.window) return;
|
|
84
|
+
UIView *snapshot = [cell snapshotViewAfterScreenUpdates:NO];
|
|
85
|
+
if (!snapshot) return;
|
|
86
|
+
CGRect frame = [list convertRect:[list rectForRowAtIndexPath:path] toView:list.window];
|
|
87
|
+
self.overlay = snapshot; snapshot.frame = frame; snapshot.userInteractionEnabled = NO;
|
|
88
|
+
snapshot.accessibilityElementsHidden = YES;
|
|
89
|
+
snapshot.layer.shadowColor = UIColor.blackColor.CGColor; snapshot.layer.shadowRadius = 12;
|
|
90
|
+
snapshot.layer.shadowOpacity = .18; snapshot.layer.shadowOffset = CGSizeMake(0, 6);
|
|
91
|
+
[list.window addSubview:snapshot];
|
|
92
|
+
CGPoint p = [self.press locationInView:list.window];
|
|
93
|
+
self.pointerY = p.y; self.pointerX = p.x; self.grabOffset = p.y - CGRectGetMidY(frame);
|
|
94
|
+
self.height = frame.size.height; self.viewport = list.bounds.size;
|
|
95
|
+
self.count = [list numberOfRowsInSection:0]; self.from = self.target = path.row;
|
|
96
|
+
self.scrollEnabled = list.scrollEnabled; list.scrollEnabled = NO;
|
|
97
|
+
self.active = YES; self.settling = NO; self.previousTime = CACurrentMediaTime(); self.generation++;
|
|
98
|
+
[self emit:1 detail:self.from];
|
|
99
|
+
if (!self.active || self.disposed) return;
|
|
100
|
+
self.clock = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
|
|
101
|
+
[self.clock addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
102
|
+
[self update];
|
|
103
|
+
}
|
|
104
|
+
- (void)tick:(CADisplayLink *)clock {
|
|
105
|
+
if (!self.active || self.settling) return;
|
|
106
|
+
UITableView *list = self.list;
|
|
107
|
+
if (!list.window || !CGSizeEqualToSize(self.viewport, list.bounds.size) || self.count != [list numberOfRowsInSection:0]) {
|
|
108
|
+
[self finish:NO detail:1]; return;
|
|
109
|
+
}
|
|
110
|
+
double now = CACurrentMediaTime(), dt = MIN(.04, MAX(0, now - self.previousTime)); self.previousTime = now;
|
|
111
|
+
CGRect viewport = [list convertRect:list.bounds toView:list.window];
|
|
112
|
+
CGFloat inset = MIN(self.edgeSize, viewport.size.height / 3), distance = 0;
|
|
113
|
+
if (self.pointerY < CGRectGetMinY(viewport) + inset) distance = -MIN(1, (CGRectGetMinY(viewport) + inset - self.pointerY) / inset);
|
|
114
|
+
else if (self.pointerY > CGRectGetMaxY(viewport) - inset) distance = MIN(1, (self.pointerY - CGRectGetMaxY(viewport) + inset) / inset);
|
|
115
|
+
CGFloat top = -list.adjustedContentInset.top, bottom = MAX(top, list.contentSize.height - list.bounds.size.height + list.adjustedContentInset.bottom);
|
|
116
|
+
CGFloat next = MAX(top, MIN(bottom, list.contentOffset.y + distance * self.edgeSpeed * dt));
|
|
117
|
+
if (fabs(next - list.contentOffset.y) > .01) { [list setContentOffset:CGPointMake(list.contentOffset.x, next) animated:NO]; [list layoutIfNeeded]; }
|
|
118
|
+
[self update];
|
|
119
|
+
}
|
|
120
|
+
- (void)restore:(UITableViewCell *)cell state:(JoltReorderCellState *)state {
|
|
121
|
+
[cell.contentView.layer removeAnimationForKey:@"jolt.reorder"];
|
|
122
|
+
cell.contentView.layer.sublayerTransform = state.transform; cell.alpha = state.alpha; state.offset = 0;
|
|
123
|
+
cell.clipsToBounds = state.cellClips; cell.contentView.clipsToBounds = state.contentClips;
|
|
124
|
+
}
|
|
125
|
+
- (void)update {
|
|
126
|
+
if (!self.active || self.settling) return;
|
|
127
|
+
UITableView *list = self.list;
|
|
128
|
+
CGFloat center = self.pointerY - self.grabOffset;
|
|
129
|
+
CGPoint inList = [list convertPoint:CGPointMake(self.pointerX, center) fromView:list.window];
|
|
130
|
+
CGFloat draggedTop = inList.y - self.height / 2;
|
|
131
|
+
self.overlay.center = CGPointMake(self.overlay.center.x, center);
|
|
132
|
+
NSArray<NSIndexPath *> *paths = [[list indexPathsForVisibleRows] sortedArrayUsingSelector:@selector(compare:)];
|
|
133
|
+
NSInteger destination = self.from;
|
|
134
|
+
for (NSIndexPath *path in paths) {
|
|
135
|
+
if (path.section || path.row == self.from) continue;
|
|
136
|
+
CGRect frame = [list rectForRowAtIndexPath:path];
|
|
137
|
+
CGFloat midpoint = CGRectGetMidY(frame) - (path.row > self.from ? self.height : 0);
|
|
138
|
+
NSInteger insertion = path.row - (path.row > self.from ? 1 : 0);
|
|
139
|
+
destination = insertion;
|
|
140
|
+
if (draggedTop < midpoint) break;
|
|
141
|
+
destination = insertion + 1;
|
|
142
|
+
}
|
|
143
|
+
self.target = MAX(0, MIN(self.count - 1, destination));
|
|
144
|
+
NSSet *visible = [NSSet setWithArray:list.visibleCells];
|
|
145
|
+
for (UITableViewCell *cell in self.cells.keyEnumerator.allObjects)
|
|
146
|
+
if (![visible containsObject:cell]) { [self restore:cell state:[self.cells objectForKey:cell]]; [self.cells removeObjectForKey:cell]; }
|
|
147
|
+
for (NSIndexPath *path in paths) {
|
|
148
|
+
UITableViewCell *cell = [list cellForRowAtIndexPath:path]; if (!cell) continue;
|
|
149
|
+
JoltReorderCellState *state = [self.cells objectForKey:cell];
|
|
150
|
+
if (state && state.index != path.row) { [self restore:cell state:state]; [self.cells removeObjectForKey:cell]; state = nil; }
|
|
151
|
+
if (!state) { state = [JoltReorderCellState new]; state.transform = cell.contentView.layer.sublayerTransform; state.cellClips = cell.clipsToBounds; state.contentClips = cell.contentView.clipsToBounds; state.alpha = cell.alpha; state.index = path.row; [self.cells setObject:state forKey:cell]; }
|
|
152
|
+
cell.clipsToBounds = NO; cell.contentView.clipsToBounds = NO;
|
|
153
|
+
cell.alpha = path.row == self.from ? 0 : state.alpha;
|
|
154
|
+
CGFloat offset = path.row > self.from && path.row <= self.target ? -self.height : path.row < self.from && path.row >= self.target ? self.height : 0;
|
|
155
|
+
if (fabs(offset - state.offset) < .01) continue;
|
|
156
|
+
// UITableView owns cell/contentView frames. Move only the contents of
|
|
157
|
+
// the layer so height estimation and recycling cannot corrupt geometry.
|
|
158
|
+
CALayer *layer = cell.contentView.layer;
|
|
159
|
+
CATransform3D previous = ((CALayer *)layer.presentationLayer ?: layer).sublayerTransform;
|
|
160
|
+
state.offset = offset; layer.sublayerTransform = CATransform3DTranslate(state.transform, 0, offset, 0);
|
|
161
|
+
[layer removeAnimationForKey:@"jolt.reorder"];
|
|
162
|
+
if (!self.reduced) {
|
|
163
|
+
CASpringAnimation *spring = [CASpringAnimation animationWithKeyPath:@"sublayerTransform"];
|
|
164
|
+
spring.mass = 1; spring.stiffness = 360; spring.damping = 34;
|
|
165
|
+
spring.fromValue = [NSValue valueWithCATransform3D:previous]; spring.toValue = [NSValue valueWithCATransform3D:layer.sublayerTransform]; spring.duration = spring.settlingDuration;
|
|
166
|
+
[layer addAnimation:spring forKey:@"jolt.reorder"];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
- (void)drop {
|
|
171
|
+
if (!self.active || self.settling) return;
|
|
172
|
+
self.settling = YES; [self.clock invalidate]; self.clock = nil;
|
|
173
|
+
NSInteger generation = self.generation;
|
|
174
|
+
CGRect row = [self.list rectForRowAtIndexPath:[NSIndexPath indexPathForRow:self.target inSection:0]];
|
|
175
|
+
CGFloat top = self.target > self.from ? CGRectGetMaxY(row) - self.height : row.origin.y;
|
|
176
|
+
CGPoint end = [self.list convertPoint:CGPointMake(row.origin.x + row.size.width / 2, top + self.height / 2) toView:self.list.window];
|
|
177
|
+
if (self.reduced) { [self finish:YES detail:0]; return; }
|
|
178
|
+
[UIView animateWithDuration:.18 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:^{ self.overlay.center = end; } completion:^(BOOL finished) {
|
|
179
|
+
if (self.active && generation == self.generation) [self finish:YES detail:0];
|
|
180
|
+
}];
|
|
181
|
+
}
|
|
182
|
+
- (void)finish:(BOOL)commit detail:(NSInteger)detail {
|
|
183
|
+
if (!self.active) return;
|
|
184
|
+
self.active = NO; self.settling = NO; self.generation++;
|
|
185
|
+
[self.clock invalidate]; self.clock = nil;
|
|
186
|
+
[self.overlay.layer removeAllAnimations]; [self.overlay removeFromSuperview]; self.overlay = nil;
|
|
187
|
+
for (UITableViewCell *cell in self.cells.keyEnumerator.allObjects) [self restore:cell state:[self.cells objectForKey:cell]];
|
|
188
|
+
[self.cells removeAllObjects]; self.list.scrollEnabled = self.scrollEnabled;
|
|
189
|
+
[self emit:commit ? 2 : 3 detail:commit ? self.target : detail];
|
|
190
|
+
}
|
|
191
|
+
- (void)cancel {
|
|
192
|
+
if (self.cancelling) return;
|
|
193
|
+
self.cancelling = YES;
|
|
194
|
+
[self finish:NO detail:0];
|
|
195
|
+
if (self.press.enabled) { self.press.enabled = NO; self.press.enabled = YES; }
|
|
196
|
+
self.cancelling = NO;
|
|
197
|
+
}
|
|
198
|
+
- (void)setEnabled:(BOOL)value { _enabled = value; if (!value) [self cancel]; }
|
|
199
|
+
- (void)setReducedMotion:(BOOL)value { self.reduced = value; }
|
|
200
|
+
- (void)dispose {
|
|
201
|
+
if (self.disposed) return;
|
|
202
|
+
[self cancel]; self.disposed = YES; [self.list removeGestureRecognizer:self.press]; self.onState = nil;
|
|
203
|
+
[self.handles removeAllObjects];
|
|
204
|
+
}
|
|
205
|
+
@end
|
package/src/android-plugin.d.ts
CHANGED
|
@@ -1,8 +1,42 @@
|
|
|
1
1
|
declare namespace org.rafaelaugusto.jolt {
|
|
2
|
+
class ReorderSurface extends org.nativescript.widgets.GridLayout {
|
|
3
|
+
constructor(context: android.content.Context)
|
|
4
|
+
}
|
|
5
|
+
class ReorderHandle extends android.widget.Button {
|
|
6
|
+
constructor(context: android.content.Context)
|
|
7
|
+
setMoveListener(listener: ReorderHandle.MoveListener | null): void
|
|
8
|
+
}
|
|
9
|
+
namespace ReorderHandle {
|
|
10
|
+
class MoveListener {
|
|
11
|
+
constructor(implementation: { move(direction: number): void })
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
class ReorderSession {
|
|
15
|
+
constructor(
|
|
16
|
+
surface: ReorderSurface,
|
|
17
|
+
list: android.widget.ListView,
|
|
18
|
+
options: string,
|
|
19
|
+
listener: ReorderSession.Listener,
|
|
20
|
+
)
|
|
21
|
+
isActive(): boolean
|
|
22
|
+
registerHandle(view: android.view.View): void
|
|
23
|
+
setEnabled(enabled: boolean): void
|
|
24
|
+
setReducedMotion(reduced: boolean): void
|
|
25
|
+
cancel(): void
|
|
26
|
+
dispose(): void
|
|
27
|
+
}
|
|
28
|
+
namespace ReorderSession {
|
|
29
|
+
class Listener {
|
|
30
|
+
constructor(implementation: {
|
|
31
|
+
onState(state: number, index: number, toIndex: number): void
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
2
35
|
class GestureSurface extends org.nativescript.widgets.GridLayout {
|
|
3
36
|
constructor(context: android.content.Context)
|
|
4
37
|
}
|
|
5
38
|
class GestureSession {
|
|
39
|
+
static supportsContactEvents(): boolean
|
|
6
40
|
constructor(
|
|
7
41
|
surface: GestureSurface,
|
|
8
42
|
scroll: android.view.View | null,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type SheetSnap = number | `${number}%` | 'content'
|
|
2
|
+
|
|
3
|
+
export interface SheetStop {
|
|
4
|
+
point: SheetSnap
|
|
5
|
+
height: number
|
|
6
|
+
y: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SheetGeometry {
|
|
10
|
+
height: number
|
|
11
|
+
closedY: number
|
|
12
|
+
top: number
|
|
13
|
+
stops: SheetStop[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function validateSnaps(points: readonly SheetSnap[]): void {
|
|
17
|
+
if (!Array.isArray(points) || !points.length)
|
|
18
|
+
throw new TypeError('bottomSheet() needs at least one snap point.')
|
|
19
|
+
for (const point of points) {
|
|
20
|
+
if (point === 'content') continue
|
|
21
|
+
if (typeof point === 'number' && Number.isFinite(point) && point > 0) continue
|
|
22
|
+
if (typeof point === 'string' && /^(?:\d+(?:\.\d+)?|\.\d+)%$/.test(point)) {
|
|
23
|
+
const percent = Number.parseFloat(point)
|
|
24
|
+
if (percent > 0 && percent <= 100) continue
|
|
25
|
+
}
|
|
26
|
+
throw new RangeError(
|
|
27
|
+
'Sheet snaps must be positive DIPs, 0–100% (exclusive of 0), or content.',
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
if (new Set(points).size !== points.length)
|
|
31
|
+
throw new RangeError('Sheet snap points must not repeat.')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Keyboard overlap is relative to the host, so adjustResize is not subtracted twice. */
|
|
35
|
+
export function sheetGeometry(
|
|
36
|
+
points: readonly SheetSnap[],
|
|
37
|
+
hostHeight: number,
|
|
38
|
+
hostBottom: number,
|
|
39
|
+
windowHeight: number,
|
|
40
|
+
keyboardHeight: number,
|
|
41
|
+
safeTop: number,
|
|
42
|
+
safeBottom: number,
|
|
43
|
+
contentHeight: number,
|
|
44
|
+
): SheetGeometry {
|
|
45
|
+
const top = Math.max(0, safeTop)
|
|
46
|
+
const overlap = Math.max(
|
|
47
|
+
0,
|
|
48
|
+
Math.min(hostHeight, hostBottom - (windowHeight - keyboardHeight)),
|
|
49
|
+
)
|
|
50
|
+
const bottom = Math.max(Math.max(0, safeBottom), overlap)
|
|
51
|
+
const height = Math.max(1, hostHeight - top - bottom)
|
|
52
|
+
const closedY = Math.max(height, hostHeight - top)
|
|
53
|
+
const stops = points.map((point) => {
|
|
54
|
+
const desired =
|
|
55
|
+
point === 'content'
|
|
56
|
+
? contentHeight
|
|
57
|
+
: typeof point === 'number'
|
|
58
|
+
? point
|
|
59
|
+
: (Number.parseFloat(point) / 100) * height
|
|
60
|
+
const visible = Math.max(1, Math.min(height, desired))
|
|
61
|
+
return { point, height: visible, y: height - visible }
|
|
62
|
+
})
|
|
63
|
+
return { height, closedY, top, stops }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Projection selects a stop; the native spring retains the actual release velocity. */
|
|
67
|
+
export function releaseStop(
|
|
68
|
+
geometry: SheetGeometry,
|
|
69
|
+
y: number,
|
|
70
|
+
velocity: number,
|
|
71
|
+
dismissible: boolean,
|
|
72
|
+
): SheetStop | undefined {
|
|
73
|
+
const projected = y + Math.max(-4000, Math.min(4000, velocity)) * 0.2
|
|
74
|
+
let distance = dismissible ? Math.abs(projected - geometry.closedY) : Infinity
|
|
75
|
+
let result: SheetStop | undefined
|
|
76
|
+
for (const stop of geometry.stops) {
|
|
77
|
+
const next = Math.abs(projected - stop.y)
|
|
78
|
+
if (next <= distance) {
|
|
79
|
+
distance = next
|
|
80
|
+
result = stop
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result
|
|
84
|
+
}
|