@seed-design/lynx-react 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Field/Field.d.ts +47 -0
- package/lib/components/Field/Field.d.ts.map +1 -0
- package/lib/components/Field/Field.jsx +121 -0
- package/lib/components/Field/Field.namespace.d.ts +2 -0
- package/lib/components/Field/Field.namespace.d.ts.map +1 -0
- package/lib/components/Field/Field.namespace.js +16 -0
- package/lib/components/Field/context.d.ts +19 -0
- package/lib/components/Field/context.d.ts.map +1 -0
- package/lib/components/Field/context.js +10 -0
- package/lib/components/Field/index.d.ts +3 -0
- package/lib/components/Field/index.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts +42 -0
- package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.jsx +140 -0
- package/lib/components/KeyboardAvoidingScrollView/context.d.ts +17 -0
- package/lib/components/KeyboardAvoidingScrollView/context.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/context.js +12 -0
- package/lib/components/KeyboardAvoidingScrollView/engine.d.ts +50 -0
- package/lib/components/KeyboardAvoidingScrollView/engine.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/engine.js +259 -0
- package/lib/components/KeyboardAvoidingScrollView/geometry.d.ts +50 -0
- package/lib/components/KeyboardAvoidingScrollView/geometry.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/geometry.js +90 -0
- package/lib/components/KeyboardAvoidingScrollView/index.d.ts +2 -0
- package/lib/components/KeyboardAvoidingScrollView/index.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.d.ts +11 -0
- package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.js +43 -0
- package/lib/components/KeyboardAvoidingScrollView/native-driver.d.ts +34 -0
- package/lib/components/KeyboardAvoidingScrollView/native-driver.d.ts.map +1 -0
- package/lib/components/KeyboardAvoidingScrollView/native-driver.js +133 -0
- package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
- package/lib/components/ProgressCircle/ProgressCircle.jsx +36 -5
- package/lib/components/Stack/Stack.d.ts.map +1 -1
- package/lib/components/Stack/Stack.jsx +10 -1
- package/lib/components/TagGroup/TagGroup.d.ts +2 -1
- package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
- package/lib/components/TagGroup/TagGroup.jsx +2 -1
- package/lib/components/TextField/TextField.d.ts +131 -0
- package/lib/components/TextField/TextField.d.ts.map +1 -0
- package/lib/components/TextField/TextField.jsx +434 -0
- package/lib/components/TextField/TextField.namespace.d.ts +2 -0
- package/lib/components/TextField/TextField.namespace.d.ts.map +1 -0
- package/lib/components/TextField/TextField.namespace.js +14 -0
- package/lib/components/TextField/context.d.ts +22 -0
- package/lib/components/TextField/context.d.ts.map +1 -0
- package/lib/components/TextField/context.js +7 -0
- package/lib/components/TextField/index.d.ts +4 -0
- package/lib/components/TextField/index.d.ts.map +1 -0
- package/lib/components/TextField/useTextFieldWithGraphemes.d.ts +24 -0
- package/lib/components/TextField/useTextFieldWithGraphemes.d.ts.map +1 -0
- package/lib/components/TextField/useTextFieldWithGraphemes.js +47 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/index.js +7 -1
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/_grapheme_data.js +8 -0
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/_incb_data.js +14 -0
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/core.js +68 -0
- package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/grapheme.js +175 -0
- package/package.json +7 -5
- package/src/components/Field/Field.namespace.ts +19 -0
- package/src/components/Field/Field.test.tsx +81 -0
- package/src/components/Field/Field.tsx +252 -0
- package/src/components/Field/context.ts +26 -0
- package/src/components/Field/index.ts +21 -0
- package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.test.tsx +267 -0
- package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.tsx +299 -0
- package/src/components/KeyboardAvoidingScrollView/context.ts +24 -0
- package/src/components/KeyboardAvoidingScrollView/engine.test.ts +451 -0
- package/src/components/KeyboardAvoidingScrollView/engine.ts +417 -0
- package/src/components/KeyboardAvoidingScrollView/geometry.test.ts +315 -0
- package/src/components/KeyboardAvoidingScrollView/geometry.ts +194 -0
- package/src/components/KeyboardAvoidingScrollView/index.ts +5 -0
- package/src/components/KeyboardAvoidingScrollView/keyboard-event-source.test.ts +122 -0
- package/src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts +75 -0
- package/src/components/KeyboardAvoidingScrollView/native-driver.test.ts +223 -0
- package/src/components/KeyboardAvoidingScrollView/native-driver.ts +248 -0
- package/src/components/ProgressCircle/ProgressCircle.test.tsx +58 -0
- package/src/components/ProgressCircle/ProgressCircle.tsx +42 -5
- package/src/components/Stack/Stack.test.tsx +67 -0
- package/src/components/Stack/Stack.tsx +15 -1
- package/src/components/TagGroup/TagGroup.test.tsx +73 -0
- package/src/components/TagGroup/TagGroup.tsx +4 -2
- package/src/components/TextField/TextField.namespace.ts +16 -0
- package/src/components/TextField/TextField.test.tsx +1052 -0
- package/src/components/TextField/TextField.tsx +954 -0
- package/src/components/TextField/context.ts +33 -0
- package/src/components/TextField/index.ts +23 -0
- package/src/components/TextField/useTextFieldWithGraphemes.test.tsx +80 -0
- package/src/components/TextField/useTextFieldWithGraphemes.ts +78 -0
- package/src/components/index.ts +3 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { calculateSafeArea, calculateSignedScrollDelta, clampScrollOffset, createFieldBodyRect, hasMeaningfulGeometryChange, selectLargestFittingTarget } from "./geometry.js";
|
|
2
|
+
//#region src/components/KeyboardAvoidingScrollView/engine.ts
|
|
3
|
+
var BLUR_HANDOFF_DELAY_MS = 30;
|
|
4
|
+
function normalizeKeyboardState(state) {
|
|
5
|
+
const height = Number.isFinite(state.height) ? Math.max(0, state.height) : 0;
|
|
6
|
+
const visible = state.visible && height > 0;
|
|
7
|
+
return {
|
|
8
|
+
visible,
|
|
9
|
+
height: visible ? height : 0
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function targetHeight(rect) {
|
|
13
|
+
return Math.max(0, rect.bottom - rect.top);
|
|
14
|
+
}
|
|
15
|
+
var KeyboardAvoidingEngineImpl = class {
|
|
16
|
+
#options;
|
|
17
|
+
#pendingReasons = /* @__PURE__ */ new Set();
|
|
18
|
+
#ownerStates = /* @__PURE__ */ new Map();
|
|
19
|
+
#activeRegistration = null;
|
|
20
|
+
#keyboard = {
|
|
21
|
+
visible: false,
|
|
22
|
+
height: 0
|
|
23
|
+
};
|
|
24
|
+
#revision = 0;
|
|
25
|
+
#session = 0;
|
|
26
|
+
#appliedSpacerHeight = 0;
|
|
27
|
+
#needsCloseClamp = false;
|
|
28
|
+
#cancelFrame = null;
|
|
29
|
+
#cancelBlur = null;
|
|
30
|
+
#blurOwner = null;
|
|
31
|
+
#userScrolling = false;
|
|
32
|
+
#disposed = false;
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.#options = options;
|
|
35
|
+
}
|
|
36
|
+
focus(registration) {
|
|
37
|
+
if (this.#disposed) return;
|
|
38
|
+
this.#cancelPendingBlur();
|
|
39
|
+
if (registration.enabled === false) {
|
|
40
|
+
if (this.#activeRegistration === null) return;
|
|
41
|
+
this.#activeRegistration = null;
|
|
42
|
+
this.#invalidate("focus");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.#activeRegistration = registration;
|
|
46
|
+
this.#invalidate("focus");
|
|
47
|
+
}
|
|
48
|
+
blur(owner) {
|
|
49
|
+
if (this.#disposed || this.#activeRegistration?.owner !== owner) return;
|
|
50
|
+
this.#cancelPendingBlur();
|
|
51
|
+
this.#blurOwner = owner;
|
|
52
|
+
this.#cancelBlur = this.#options.scheduler.scheduleTimer(() => {
|
|
53
|
+
this.#cancelBlur = null;
|
|
54
|
+
const blurOwner = this.#blurOwner;
|
|
55
|
+
this.#blurOwner = null;
|
|
56
|
+
if (this.#activeRegistration?.owner !== blurOwner) return;
|
|
57
|
+
this.#activeRegistration = null;
|
|
58
|
+
this.#invalidate("focus");
|
|
59
|
+
}, BLUR_HANDOFF_DELAY_MS);
|
|
60
|
+
}
|
|
61
|
+
layoutChanged(owner) {
|
|
62
|
+
if (this.#disposed || this.#activeRegistration?.owner !== owner) return;
|
|
63
|
+
this.#invalidate("layout");
|
|
64
|
+
}
|
|
65
|
+
unregister(owner) {
|
|
66
|
+
if (this.#disposed) return;
|
|
67
|
+
this.#ownerStates.delete(owner);
|
|
68
|
+
if (this.#blurOwner === owner) this.#cancelPendingBlur();
|
|
69
|
+
if (this.#activeRegistration?.owner !== owner) return;
|
|
70
|
+
this.#activeRegistration = null;
|
|
71
|
+
this.#invalidate("focus");
|
|
72
|
+
}
|
|
73
|
+
keyboardChanged(state) {
|
|
74
|
+
if (this.#disposed) return;
|
|
75
|
+
const next = normalizeKeyboardState(state);
|
|
76
|
+
if (next.visible === this.#keyboard.visible && !hasMeaningfulGeometryChange(this.#keyboard.height, next.height)) return;
|
|
77
|
+
if (next.visible !== this.#keyboard.visible) {
|
|
78
|
+
this.#session += 1;
|
|
79
|
+
this.#ownerStates.clear();
|
|
80
|
+
}
|
|
81
|
+
this.#keyboard = next;
|
|
82
|
+
this.#invalidate("keyboard");
|
|
83
|
+
}
|
|
84
|
+
viewportChanged() {
|
|
85
|
+
if (this.#disposed) return;
|
|
86
|
+
this.#invalidate("viewport");
|
|
87
|
+
}
|
|
88
|
+
userScrollStarted() {
|
|
89
|
+
if (this.#disposed || this.#userScrolling) return;
|
|
90
|
+
this.#userScrolling = true;
|
|
91
|
+
this.#revision += 1;
|
|
92
|
+
this.#pendingReasons.add("scroll");
|
|
93
|
+
this.#cancelFrame?.();
|
|
94
|
+
this.#cancelFrame = null;
|
|
95
|
+
}
|
|
96
|
+
userScrollEnded() {
|
|
97
|
+
if (this.#disposed || !this.#userScrolling) return;
|
|
98
|
+
this.#userScrolling = false;
|
|
99
|
+
this.#invalidate("scroll");
|
|
100
|
+
}
|
|
101
|
+
dispose() {
|
|
102
|
+
if (this.#disposed) return;
|
|
103
|
+
this.#disposed = true;
|
|
104
|
+
this.#revision += 1;
|
|
105
|
+
this.#cancelFrame?.();
|
|
106
|
+
this.#cancelFrame = null;
|
|
107
|
+
this.#cancelPendingBlur();
|
|
108
|
+
this.#pendingReasons.clear();
|
|
109
|
+
this.#ownerStates.clear();
|
|
110
|
+
this.#activeRegistration = null;
|
|
111
|
+
}
|
|
112
|
+
#cancelPendingBlur() {
|
|
113
|
+
this.#cancelBlur?.();
|
|
114
|
+
this.#cancelBlur = null;
|
|
115
|
+
this.#blurOwner = null;
|
|
116
|
+
}
|
|
117
|
+
#invalidate(reason) {
|
|
118
|
+
this.#revision += 1;
|
|
119
|
+
this.#pendingReasons.add(reason);
|
|
120
|
+
if (this.#userScrolling || this.#cancelFrame !== null) return;
|
|
121
|
+
this.#cancelFrame = this.#options.scheduler.scheduleFrame(async () => {
|
|
122
|
+
this.#cancelFrame = null;
|
|
123
|
+
if (this.#disposed || this.#userScrolling) return;
|
|
124
|
+
const reasons = new Set(this.#pendingReasons);
|
|
125
|
+
this.#pendingReasons.clear();
|
|
126
|
+
const transaction = {
|
|
127
|
+
revision: this.#revision,
|
|
128
|
+
session: this.#session,
|
|
129
|
+
registration: this.#activeRegistration,
|
|
130
|
+
keyboard: this.#keyboard,
|
|
131
|
+
layoutOnly: reasons.size === 1 && reasons.has("layout")
|
|
132
|
+
};
|
|
133
|
+
await this.#evaluate(transaction);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
#isCurrent(transaction) {
|
|
137
|
+
return !this.#disposed && transaction.revision === this.#revision && transaction.session === this.#session && transaction.registration === this.#activeRegistration && !this.#userScrolling;
|
|
138
|
+
}
|
|
139
|
+
async #evaluate(transaction) {
|
|
140
|
+
try {
|
|
141
|
+
if (!transaction.keyboard.visible || transaction.registration === null) {
|
|
142
|
+
await this.#close(transaction);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
await this.#open(transaction, transaction.registration);
|
|
146
|
+
} catch {}
|
|
147
|
+
}
|
|
148
|
+
async #close(transaction) {
|
|
149
|
+
if (this.#appliedSpacerHeight === 0 && !this.#needsCloseClamp) return;
|
|
150
|
+
const scrollNode = this.#options.getScrollNode();
|
|
151
|
+
if (this.#appliedSpacerHeight !== 0) {
|
|
152
|
+
const spacerNode = this.#options.getSpacerNode();
|
|
153
|
+
if (spacerNode === null) return;
|
|
154
|
+
this.#options.driver.setSpacerHeight(spacerNode, 0);
|
|
155
|
+
this.#appliedSpacerHeight = 0;
|
|
156
|
+
this.#needsCloseClamp = true;
|
|
157
|
+
}
|
|
158
|
+
if (scrollNode === null) return;
|
|
159
|
+
await this.#options.driver.waitForLayout();
|
|
160
|
+
if (!this.#isCurrent(transaction)) return;
|
|
161
|
+
const viewport = await this.#options.driver.measure(scrollNode);
|
|
162
|
+
if (!this.#isCurrent(transaction) || viewport === null) return;
|
|
163
|
+
const viewportHeight = Math.max(0, viewport.bottom - viewport.top);
|
|
164
|
+
const metrics = await this.#options.driver.getScrollMetrics(scrollNode, viewportHeight);
|
|
165
|
+
if (!this.#isCurrent(transaction) || metrics === null) return;
|
|
166
|
+
const clampedOffset = clampScrollOffset(metrics.offsetY, metrics.maxOffsetY);
|
|
167
|
+
if (clampedOffset === metrics.offsetY) {
|
|
168
|
+
this.#needsCloseClamp = false;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.#options.driver.scrollTo(scrollNode, clampedOffset, this.#options.getSmooth());
|
|
172
|
+
this.#needsCloseClamp = false;
|
|
173
|
+
}
|
|
174
|
+
async #open(transaction, registration) {
|
|
175
|
+
const scrollNode = this.#options.getScrollNode();
|
|
176
|
+
const spacerNode = this.#options.getSpacerNode();
|
|
177
|
+
if (scrollNode === null || spacerNode === null) return;
|
|
178
|
+
const [viewport, occlusion] = await Promise.all([this.#options.driver.measure(scrollNode), this.#options.driver.resolveKeyboardOcclusion(transaction.keyboard)]);
|
|
179
|
+
if (!this.#isCurrent(transaction) || viewport === null || occlusion === null) return;
|
|
180
|
+
const safeArea = calculateSafeArea({
|
|
181
|
+
viewport,
|
|
182
|
+
keyboardOcclusionTop: occlusion.topInScreenPx,
|
|
183
|
+
toolbarHeight: this.#options.getToolbarHeight(),
|
|
184
|
+
keyboardGap: this.#options.getKeyboardGap()
|
|
185
|
+
});
|
|
186
|
+
this.#needsCloseClamp = false;
|
|
187
|
+
if (hasMeaningfulGeometryChange(this.#appliedSpacerHeight, safeArea.spacerHeight)) {
|
|
188
|
+
this.#options.driver.setSpacerHeight(spacerNode, safeArea.spacerHeight);
|
|
189
|
+
this.#appliedSpacerHeight = safeArea.spacerHeight;
|
|
190
|
+
}
|
|
191
|
+
await this.#options.driver.waitForLayout();
|
|
192
|
+
if (!this.#isCurrent(transaction)) return;
|
|
193
|
+
const state = this.#getOwnerState(registration.owner, transaction.session);
|
|
194
|
+
const measure = (ref) => {
|
|
195
|
+
const node = ref?.current;
|
|
196
|
+
if (node === null || node === void 0) return Promise.resolve(null);
|
|
197
|
+
return Promise.resolve().then(() => this.#options.driver.measure(node)).catch(() => null);
|
|
198
|
+
};
|
|
199
|
+
const [field, control, native, anchor] = await Promise.all([
|
|
200
|
+
state.fieldDowngraded ? Promise.resolve(null) : measure(registration.fieldRef),
|
|
201
|
+
measure(registration.controlRef),
|
|
202
|
+
measure(registration.nativeRef),
|
|
203
|
+
measure(registration.anchorRef)
|
|
204
|
+
]);
|
|
205
|
+
if (!this.#isCurrent(transaction)) return;
|
|
206
|
+
const fieldBody = createFieldBodyRect(field, control);
|
|
207
|
+
const selected = selectLargestFittingTarget({
|
|
208
|
+
field,
|
|
209
|
+
fieldBody,
|
|
210
|
+
control,
|
|
211
|
+
native,
|
|
212
|
+
anchor
|
|
213
|
+
}, safeArea);
|
|
214
|
+
const nextState = {
|
|
215
|
+
...state,
|
|
216
|
+
fieldDowngraded: state.fieldDowngraded || fieldBody === null && field !== null && selected !== null && selected.kind !== "field",
|
|
217
|
+
lastTargetKind: selected?.kind ?? null,
|
|
218
|
+
lastTargetHeight: selected ? targetHeight(selected.rect) : null
|
|
219
|
+
};
|
|
220
|
+
if (selected === null) {
|
|
221
|
+
this.#ownerStates.set(registration.owner, nextState);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (transaction.layoutOnly && (state.lastTargetKind === selected.kind && state.lastTargetHeight !== null && targetHeight(selected.rect) < state.lastTargetHeight - 1 || state.lastTargetKind === "fieldBottom" && selected.kind !== "fieldBottom")) {
|
|
225
|
+
this.#ownerStates.set(registration.owner, nextState);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const delta = calculateSignedScrollDelta(selected.rect, safeArea);
|
|
229
|
+
if (delta === 0) {
|
|
230
|
+
this.#ownerStates.set(registration.owner, nextState);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const viewportHeight = Math.max(0, viewport.bottom - viewport.top);
|
|
234
|
+
const metrics = await this.#options.driver.getScrollMetrics(scrollNode, viewportHeight);
|
|
235
|
+
if (!this.#isCurrent(transaction) || metrics === null) return;
|
|
236
|
+
const nextOffset = clampScrollOffset(metrics.offsetY + delta, metrics.maxOffsetY);
|
|
237
|
+
if (!hasMeaningfulGeometryChange(metrics.offsetY, nextOffset)) {
|
|
238
|
+
this.#ownerStates.set(registration.owner, nextState);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
this.#options.driver.scrollTo(scrollNode, nextOffset, this.#options.getSmooth());
|
|
242
|
+
this.#ownerStates.set(registration.owner, nextState);
|
|
243
|
+
}
|
|
244
|
+
#getOwnerState(owner, session) {
|
|
245
|
+
const current = this.#ownerStates.get(owner);
|
|
246
|
+
if (current?.session === session) return current;
|
|
247
|
+
return {
|
|
248
|
+
session,
|
|
249
|
+
fieldDowngraded: false,
|
|
250
|
+
lastTargetKind: null,
|
|
251
|
+
lastTargetHeight: null
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
function createKeyboardAvoidingEngine(options) {
|
|
256
|
+
return new KeyboardAvoidingEngineImpl(options);
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { createKeyboardAvoidingEngine };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const KEYBOARD_AVOIDANCE_EPSILON_PX = 1;
|
|
2
|
+
export interface VerticalRect {
|
|
3
|
+
top: number;
|
|
4
|
+
bottom: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SafeArea {
|
|
7
|
+
safeTop: number;
|
|
8
|
+
safeBottom: number;
|
|
9
|
+
spacerHeight: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CalculateSafeAreaOptions {
|
|
12
|
+
viewport: VerticalRect;
|
|
13
|
+
keyboardOcclusionTop: number;
|
|
14
|
+
toolbarHeight: number;
|
|
15
|
+
keyboardGap: number;
|
|
16
|
+
}
|
|
17
|
+
export type AvoidanceTargetKind = "field" | "fieldBody" | "control" | "native" | "anchor" | "fieldBottom";
|
|
18
|
+
export interface AvoidanceTarget {
|
|
19
|
+
kind: AvoidanceTargetKind;
|
|
20
|
+
rect: VerticalRect;
|
|
21
|
+
}
|
|
22
|
+
export interface AvoidanceTargetCandidates {
|
|
23
|
+
field?: VerticalRect | null;
|
|
24
|
+
fieldBody?: VerticalRect | null;
|
|
25
|
+
control?: VerticalRect | null;
|
|
26
|
+
native?: VerticalRect | null;
|
|
27
|
+
anchor?: VerticalRect | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* screen 좌표계에서 키보드와 툴바가 가리지 않는 세로 영역을 계산한다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function calculateSafeArea({ viewport, keyboardOcclusionTop, toolbarHeight, keyboardGap, }: CalculateSafeAreaOptions): SafeArea;
|
|
33
|
+
/**
|
|
34
|
+
* Field의 header를 제외하고 control부터 footer까지 포함하는 영역을 만든다.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createFieldBodyRect(field: VerticalRect | null, control: VerticalRect | null, epsilon?: number): VerticalRect | null;
|
|
37
|
+
/**
|
|
38
|
+
* 화면에 온전히 들어오는 가장 큰 의미 단위를 선택한다.
|
|
39
|
+
* 우선순위는 Field, Field body, Field bottom, control, native control, 정밀 anchor 순이다.
|
|
40
|
+
* Field body가 너무 크면 autoresize Field의 footer를 추적할 수 있도록
|
|
41
|
+
* control보다 먼저 Field body의 하단점을 사용한다.
|
|
42
|
+
*/
|
|
43
|
+
export declare function selectLargestFittingTarget(candidates: AvoidanceTargetCandidates, safeArea: Pick<SafeArea, "safeTop" | "safeBottom">, epsilon?: number): AvoidanceTarget | null;
|
|
44
|
+
/**
|
|
45
|
+
* 양수는 아래로, 음수는 위로 이동해야 하는 scroll offset 변화량이다.
|
|
46
|
+
*/
|
|
47
|
+
export declare function calculateSignedScrollDelta(target: VerticalRect, safeArea: Pick<SafeArea, "safeTop" | "safeBottom">, epsilon?: number): number;
|
|
48
|
+
export declare function clampScrollOffset(offset: number, maxOffset: number): number;
|
|
49
|
+
export declare function hasMeaningfulGeometryChange(previous: number, next: number, epsilon?: number): boolean;
|
|
50
|
+
//# sourceMappingURL=geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/geometry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,YAAY,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,WAAW,GACX,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,aAAa,CAAC;AAElB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9B;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,EAAE,wBAAwB,GAAG,QAAQ,CAcrC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,GAAG,IAAI,EAC1B,OAAO,EAAE,YAAY,GAAG,IAAI,EAC5B,OAAO,SAAgC,GACtC,YAAY,GAAG,IAAI,CAgBrB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,yBAAyB,EACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,YAAY,CAAC,EAClD,OAAO,SAAgC,GACtC,eAAe,GAAG,IAAI,CAwDxB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,YAAY,CAAC,EAClD,OAAO,SAAgC,GACtC,MAAM,CAYR;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,SAAgC,GACtC,OAAO,CAET"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
function normalizeNonNegativeFinite(value) {
|
|
2
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* screen 좌표계에서 키보드와 툴바가 가리지 않는 세로 영역을 계산한다.
|
|
6
|
+
*/
|
|
7
|
+
function calculateSafeArea({ viewport, keyboardOcclusionTop, toolbarHeight, keyboardGap }) {
|
|
8
|
+
const normalizedToolbarHeight = normalizeNonNegativeFinite(toolbarHeight);
|
|
9
|
+
const normalizedKeyboardGap = normalizeNonNegativeFinite(keyboardGap);
|
|
10
|
+
const normalizedViewportBottom = Math.max(viewport.top, viewport.bottom);
|
|
11
|
+
const safeBottom = Math.min(normalizedViewportBottom, Math.max(viewport.top, keyboardOcclusionTop - normalizedToolbarHeight - normalizedKeyboardGap));
|
|
12
|
+
return {
|
|
13
|
+
safeTop: viewport.top,
|
|
14
|
+
safeBottom,
|
|
15
|
+
spacerHeight: normalizedViewportBottom - safeBottom
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Field의 header를 제외하고 control부터 footer까지 포함하는 영역을 만든다.
|
|
20
|
+
*/
|
|
21
|
+
function createFieldBodyRect(field, control, epsilon = 1) {
|
|
22
|
+
if (field === null || control === null) return null;
|
|
23
|
+
const normalizedEpsilon = Math.max(0, epsilon);
|
|
24
|
+
const fieldHeight = field.bottom - field.top;
|
|
25
|
+
const controlHeight = control.bottom - control.top;
|
|
26
|
+
if (fieldHeight < 0 || controlHeight < 0 || control.top < field.top - normalizedEpsilon || control.bottom > field.bottom + normalizedEpsilon) return null;
|
|
27
|
+
return {
|
|
28
|
+
top: control.top,
|
|
29
|
+
bottom: Math.max(field.bottom, control.bottom)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 화면에 온전히 들어오는 가장 큰 의미 단위를 선택한다.
|
|
34
|
+
* 우선순위는 Field, Field body, Field bottom, control, native control, 정밀 anchor 순이다.
|
|
35
|
+
* Field body가 너무 크면 autoresize Field의 footer를 추적할 수 있도록
|
|
36
|
+
* control보다 먼저 Field body의 하단점을 사용한다.
|
|
37
|
+
*/
|
|
38
|
+
function selectLargestFittingTarget(candidates, safeArea, epsilon = 1) {
|
|
39
|
+
const availableHeight = Math.max(0, safeArea.safeBottom - safeArea.safeTop);
|
|
40
|
+
const normalizedEpsilon = Math.max(0, epsilon);
|
|
41
|
+
const fieldCandidates = [["field", candidates.field], ["fieldBody", candidates.fieldBody]];
|
|
42
|
+
const fallbackCandidates = [
|
|
43
|
+
["control", candidates.control],
|
|
44
|
+
["native", candidates.native],
|
|
45
|
+
["anchor", candidates.anchor]
|
|
46
|
+
];
|
|
47
|
+
for (const [kind, rect] of fieldCandidates) {
|
|
48
|
+
if (!rect) continue;
|
|
49
|
+
const targetHeight = rect.bottom - rect.top;
|
|
50
|
+
if (targetHeight >= 0 && targetHeight <= availableHeight + normalizedEpsilon) return {
|
|
51
|
+
kind,
|
|
52
|
+
rect
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const fieldBody = candidates.fieldBody;
|
|
56
|
+
const fieldBodyHeight = fieldBody ? fieldBody.bottom - fieldBody.top : -1;
|
|
57
|
+
if (fieldBody && Number.isFinite(fieldBody.top) && Number.isFinite(fieldBody.bottom) && fieldBodyHeight > availableHeight + normalizedEpsilon) return {
|
|
58
|
+
kind: "fieldBottom",
|
|
59
|
+
rect: {
|
|
60
|
+
top: fieldBody.bottom,
|
|
61
|
+
bottom: fieldBody.bottom
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
for (const [kind, rect] of fallbackCandidates) {
|
|
65
|
+
if (!rect) continue;
|
|
66
|
+
const targetHeight = rect.bottom - rect.top;
|
|
67
|
+
if (targetHeight >= 0 && targetHeight <= availableHeight + normalizedEpsilon) return {
|
|
68
|
+
kind,
|
|
69
|
+
rect
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 양수는 아래로, 음수는 위로 이동해야 하는 scroll offset 변화량이다.
|
|
76
|
+
*/
|
|
77
|
+
function calculateSignedScrollDelta(target, safeArea, epsilon = 1) {
|
|
78
|
+
const normalizedEpsilon = Math.max(0, epsilon);
|
|
79
|
+
if (target.top < safeArea.safeTop - normalizedEpsilon) return target.top - safeArea.safeTop;
|
|
80
|
+
if (target.bottom > safeArea.safeBottom + normalizedEpsilon) return target.bottom - safeArea.safeBottom;
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
function clampScrollOffset(offset, maxOffset) {
|
|
84
|
+
return Math.min(Math.max(0, offset), Math.max(0, maxOffset));
|
|
85
|
+
}
|
|
86
|
+
function hasMeaningfulGeometryChange(previous, next, epsilon = 1) {
|
|
87
|
+
return Math.abs(next - previous) > Math.max(0, epsilon);
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { calculateSafeArea, calculateSignedScrollDelta, clampScrollOffset, createFieldBodyRect, hasMeaningfulGeometryChange, selectLargestFittingTarget };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,GACrC,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GlobalEventEmitter } from '@lynx-js/types';
|
|
2
|
+
import { RawKeyboardState } from './native-driver';
|
|
3
|
+
export type KeyboardEventListener = (state: RawKeyboardState) => void;
|
|
4
|
+
export interface KeyboardEventSource {
|
|
5
|
+
subscribe(listener: KeyboardEventListener): () => void;
|
|
6
|
+
}
|
|
7
|
+
type GetGlobalEventEmitter = () => GlobalEventEmitter;
|
|
8
|
+
export declare function createKeyboardEventSource(getEmitter: GetGlobalEventEmitter): KeyboardEventSource;
|
|
9
|
+
export declare const lynxKeyboardEventSource: KeyboardEventSource;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=keyboard-event-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-event-source.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;CACxD;AAED,KAAK,qBAAqB,GAAG,MAAM,kBAAkB,CAAC;AAEtD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,qBAAqB,GAAG,mBAAmB,CAwDhG;AAED,eAAO,MAAM,uBAAuB,qBAIlC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts
|
|
2
|
+
function createKeyboardEventSource(getEmitter) {
|
|
3
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
4
|
+
let emitter = null;
|
|
5
|
+
let currentState = {
|
|
6
|
+
visible: false,
|
|
7
|
+
height: 0
|
|
8
|
+
};
|
|
9
|
+
const handleNativeKeyboardEvent = (...args) => {
|
|
10
|
+
"background only";
|
|
11
|
+
const status = args[0];
|
|
12
|
+
const height = args[1];
|
|
13
|
+
currentState = {
|
|
14
|
+
visible: status === "on",
|
|
15
|
+
height: status === "on" && typeof height === "number" && Number.isFinite(height) ? Math.max(0, height) : 0
|
|
16
|
+
};
|
|
17
|
+
for (const subscription of subscriptions) subscription.listener(currentState);
|
|
18
|
+
};
|
|
19
|
+
return { subscribe(listener) {
|
|
20
|
+
"background only";
|
|
21
|
+
const subscription = { listener };
|
|
22
|
+
subscriptions.add(subscription);
|
|
23
|
+
if (emitter === null) try {
|
|
24
|
+
const nextEmitter = getEmitter();
|
|
25
|
+
nextEmitter.addListener("keyboardstatuschanged", handleNativeKeyboardEvent);
|
|
26
|
+
emitter = nextEmitter;
|
|
27
|
+
} catch {}
|
|
28
|
+
listener(currentState);
|
|
29
|
+
let subscribed = true;
|
|
30
|
+
return () => {
|
|
31
|
+
"background only";
|
|
32
|
+
if (!subscribed) return;
|
|
33
|
+
subscribed = false;
|
|
34
|
+
subscriptions.delete(subscription);
|
|
35
|
+
};
|
|
36
|
+
} };
|
|
37
|
+
}
|
|
38
|
+
var lynxKeyboardEventSource = createKeyboardEventSource(() => {
|
|
39
|
+
"background only";
|
|
40
|
+
return lynx.getJSModule("GlobalEventEmitter");
|
|
41
|
+
});
|
|
42
|
+
//#endregion
|
|
43
|
+
export { lynxKeyboardEventSource };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NodesRef, PlatformType } from '@lynx-js/types';
|
|
2
|
+
import { VerticalRect } from './geometry';
|
|
3
|
+
export type KeyboardAvoidingPlatform = Extract<PlatformType, "Android" | "iOS">;
|
|
4
|
+
export interface RawKeyboardState {
|
|
5
|
+
visible: boolean;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface KeyboardOcclusion {
|
|
9
|
+
visible: boolean;
|
|
10
|
+
topInScreenPx: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ScrollMetrics {
|
|
13
|
+
offsetY: number;
|
|
14
|
+
maxOffsetY: number;
|
|
15
|
+
}
|
|
16
|
+
export interface RawScrollInfo {
|
|
17
|
+
scrollY?: number;
|
|
18
|
+
scrollRange?: number;
|
|
19
|
+
maxScrollOffset?: number;
|
|
20
|
+
scrollTop?: number;
|
|
21
|
+
scrollHeight?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface KeyboardAvoidingNativeDriver {
|
|
24
|
+
measure(node: NodesRef): Promise<VerticalRect | null>;
|
|
25
|
+
resolveKeyboardOcclusion(state: RawKeyboardState): Promise<KeyboardOcclusion | null>;
|
|
26
|
+
setSpacerHeight(node: NodesRef, height: number): void;
|
|
27
|
+
waitForLayout(): Promise<void>;
|
|
28
|
+
getScrollMetrics(node: NodesRef, viewportHeight: number): Promise<ScrollMetrics | null>;
|
|
29
|
+
scrollTo(node: NodesRef, offset: number, smooth: boolean): void;
|
|
30
|
+
}
|
|
31
|
+
export declare function normalizeScrollMetrics(raw: RawScrollInfo, platform: KeyboardAvoidingPlatform, viewportHeight: number): ScrollMetrics | null;
|
|
32
|
+
export declare function resolveKeyboardOcclusionTop(state: RawKeyboardState, platform: KeyboardAvoidingPlatform, screenHeight: number, rootRect: VerticalRect | null): number | null;
|
|
33
|
+
export declare const lynxKeyboardAvoidingNativeDriver: KeyboardAvoidingNativeDriver;
|
|
34
|
+
//# sourceMappingURL=native-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-driver.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/native-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAQD,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACtD,wBAAwB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACrF,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACxF,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACjE;AAsBD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,wBAAwB,EAClC,cAAc,EAAE,MAAM,GACrB,aAAa,GAAG,IAAI,CA2BtB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,wBAAwB,EAClC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,YAAY,GAAG,IAAI,GAC5B,MAAM,GAAG,IAAI,CAQf;AA8ED,eAAO,MAAM,gCAAgC,EAAE,4BA2D9C,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/components/KeyboardAvoidingScrollView/native-driver.ts
|
|
2
|
+
function isFiniteNumber(value) {
|
|
3
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
4
|
+
}
|
|
5
|
+
function normalizeNonNegative(value) {
|
|
6
|
+
return Math.max(0, value);
|
|
7
|
+
}
|
|
8
|
+
function normalizeRect(value) {
|
|
9
|
+
if (typeof value !== "object" || value === null) return null;
|
|
10
|
+
const rect = value;
|
|
11
|
+
if (!isFiniteNumber(rect.top) || !isFiniteNumber(rect.bottom)) return null;
|
|
12
|
+
return {
|
|
13
|
+
top: rect.top,
|
|
14
|
+
bottom: Math.max(rect.top, rect.bottom)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function normalizeScrollMetrics(raw, platform, viewportHeight) {
|
|
18
|
+
const offsetY = isFiniteNumber(raw.scrollY) ? raw.scrollY : isFiniteNumber(raw.scrollTop) ? raw.scrollTop : null;
|
|
19
|
+
if (offsetY === null) return null;
|
|
20
|
+
const normalizedViewportHeight = normalizeNonNegative(viewportHeight);
|
|
21
|
+
let maxOffsetY = null;
|
|
22
|
+
if (isFiniteNumber(raw.maxScrollOffset)) maxOffsetY = raw.maxScrollOffset;
|
|
23
|
+
else if (isFiniteNumber(raw.scrollRange)) maxOffsetY = platform === "iOS" ? raw.scrollRange - normalizedViewportHeight : raw.scrollRange;
|
|
24
|
+
else if (isFiniteNumber(raw.scrollHeight)) maxOffsetY = platform === "iOS" ? raw.scrollHeight - normalizedViewportHeight : raw.scrollHeight;
|
|
25
|
+
if (maxOffsetY === null) return null;
|
|
26
|
+
return {
|
|
27
|
+
offsetY: normalizeNonNegative(offsetY),
|
|
28
|
+
maxOffsetY: normalizeNonNegative(maxOffsetY)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function resolveKeyboardOcclusionTop(state, platform, screenHeight, rootRect) {
|
|
32
|
+
if (!state.visible || !isFiniteNumber(state.height) || state.height <= 0) return null;
|
|
33
|
+
if (platform === "Android") return rootRect ? rootRect.bottom - state.height : null;
|
|
34
|
+
return isFiniteNumber(screenHeight) && screenHeight > 0 ? screenHeight - state.height : null;
|
|
35
|
+
}
|
|
36
|
+
function getSystemInfo() {
|
|
37
|
+
const value = globalThis.SystemInfo;
|
|
38
|
+
if (value?.platform !== "Android" && value?.platform !== "iOS") return null;
|
|
39
|
+
if (!isFiniteNumber(value.pixelHeight) || !isFiniteNumber(value.pixelRatio)) return null;
|
|
40
|
+
if (value.pixelHeight <= 0 || value.pixelRatio <= 0) return null;
|
|
41
|
+
return {
|
|
42
|
+
platform: value.platform,
|
|
43
|
+
screenHeight: value.pixelHeight / value.pixelRatio
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function measureNode(node) {
|
|
47
|
+
"background only";
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
try {
|
|
50
|
+
node.invoke({
|
|
51
|
+
method: "boundingClientRect",
|
|
52
|
+
params: {
|
|
53
|
+
relativeTo: "screen",
|
|
54
|
+
androidEnableTransformProps: true
|
|
55
|
+
},
|
|
56
|
+
success: (value) => resolve(normalizeRect(value)),
|
|
57
|
+
fail: () => resolve(null)
|
|
58
|
+
}).exec();
|
|
59
|
+
} catch {
|
|
60
|
+
resolve(null);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function measureRoot() {
|
|
65
|
+
"background only";
|
|
66
|
+
try {
|
|
67
|
+
return measureNode(lynx.createSelectorQuery().selectRoot());
|
|
68
|
+
} catch {
|
|
69
|
+
return Promise.resolve(null);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function getScrollMetrics(node, platform, viewportHeight) {
|
|
73
|
+
"background only";
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
try {
|
|
76
|
+
node.invoke({
|
|
77
|
+
method: "getScrollInfo",
|
|
78
|
+
success: (value) => {
|
|
79
|
+
if (typeof value !== "object" || value === null) {
|
|
80
|
+
resolve(null);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolve(normalizeScrollMetrics(value, platform, viewportHeight));
|
|
84
|
+
},
|
|
85
|
+
fail: () => resolve(null)
|
|
86
|
+
}).exec();
|
|
87
|
+
} catch {
|
|
88
|
+
resolve(null);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
var lynxKeyboardAvoidingNativeDriver = {
|
|
93
|
+
measure: measureNode,
|
|
94
|
+
async resolveKeyboardOcclusion(state) {
|
|
95
|
+
"background only";
|
|
96
|
+
const systemInfo = getSystemInfo();
|
|
97
|
+
if (!systemInfo || !state.visible) return null;
|
|
98
|
+
const rootRect = systemInfo.platform === "Android" ? await measureRoot() : null;
|
|
99
|
+
const topInScreenPx = resolveKeyboardOcclusionTop(state, systemInfo.platform, systemInfo.screenHeight, rootRect);
|
|
100
|
+
return topInScreenPx === null ? null : {
|
|
101
|
+
visible: true,
|
|
102
|
+
topInScreenPx
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
setSpacerHeight(node, height) {
|
|
106
|
+
"background only";
|
|
107
|
+
node.setNativeProps({ height: `${normalizeNonNegative(height)}px` }).exec();
|
|
108
|
+
},
|
|
109
|
+
waitForLayout() {
|
|
110
|
+
"background only";
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
requestAnimationFrame(() => resolve());
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
async getScrollMetrics(node, viewportHeight) {
|
|
116
|
+
"background only";
|
|
117
|
+
const systemInfo = getSystemInfo();
|
|
118
|
+
if (!systemInfo) return null;
|
|
119
|
+
return getScrollMetrics(node, systemInfo.platform, viewportHeight);
|
|
120
|
+
},
|
|
121
|
+
scrollTo(node, offset, smooth) {
|
|
122
|
+
"background only";
|
|
123
|
+
node.invoke({
|
|
124
|
+
method: "scrollTo",
|
|
125
|
+
params: {
|
|
126
|
+
offset: normalizeNonNegative(offset),
|
|
127
|
+
smooth
|
|
128
|
+
}
|
|
129
|
+
}).exec();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { lynxKeyboardAvoidingNativeDriver };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgressCircle.d.ts","sourceRoot":"","sources":["../../../src/components/ProgressCircle/ProgressCircle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAa7C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ProgressCircle.d.ts","sourceRoot":"","sources":["../../../src/components/ProgressCircle/ProgressCircle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAa7C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,aAAa,CAAC;AA2IvE,MAAM,WAAW,uBACf,SAAQ,0BAA0B,EAChC,sBAAsB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAIhD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,yFA8B7B,CAAC;AAIH,eAAO,MAAM,mBAAmB;;;CAQ/B,CAAC"}
|