@unif/react-native-chat 3.2.4 → 3.3.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/module/components/Attachments/AttachmentItem.js +8 -4
- package/lib/module/components/Attachments/AttachmentItem.js.map +1 -1
- package/lib/module/components/Attachments/Attachments.js +1 -0
- package/lib/module/components/Attachments/Attachments.js.map +1 -1
- package/lib/module/components/Attachments/constants.js +1 -0
- package/lib/module/components/Attachments/constants.js.map +1 -1
- package/lib/module/components/Attachments/styles.js +10 -5
- package/lib/module/components/Attachments/styles.js.map +1 -1
- package/lib/module/components/Composer/Composer.js +13 -9
- package/lib/module/components/Composer/Composer.js.map +1 -1
- package/lib/module/components/Composer/styles.js +1 -0
- package/lib/module/components/Composer/styles.js.map +1 -1
- package/lib/module/components/Confirmation/Confirmation.js +29 -13
- package/lib/module/components/Confirmation/Confirmation.js.map +1 -1
- package/lib/module/components/Confirmation/styles.js +17 -6
- package/lib/module/components/Confirmation/styles.js.map +1 -1
- package/lib/module/components/Message/MarkdownContent/styles.js +29 -33
- package/lib/module/components/Message/MarkdownContent/styles.js.map +1 -1
- package/lib/module/components/Message/Message.js +37 -24
- package/lib/module/components/Message/Message.js.map +1 -1
- package/lib/module/components/Message/MessageWaiting.js +53 -0
- package/lib/module/components/Message/MessageWaiting.js.map +1 -0
- package/lib/module/components/Message/constants.js +3 -0
- package/lib/module/components/Message/constants.js.map +1 -1
- package/lib/module/components/Message/styles.js +41 -3
- package/lib/module/components/Message/styles.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +60 -31
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/MessageListEndButton.js +60 -0
- package/lib/module/components/MessageList/MessageListEndButton.js.map +1 -0
- package/lib/module/components/MessageList/MessageListRow.js +9 -2
- package/lib/module/components/MessageList/MessageListRow.js.map +1 -1
- package/lib/module/components/MessageList/styles.js +6 -0
- package/lib/module/components/MessageList/styles.js.map +1 -1
- package/lib/module/components/MessageList/useMessageListScroll.js +218 -13
- package/lib/module/components/MessageList/useMessageListScroll.js.map +1 -1
- package/lib/module/components/Process/Process.js +15 -5
- package/lib/module/components/Process/Process.js.map +1 -1
- package/lib/module/components/Process/ProcessCompactStep.js +21 -12
- package/lib/module/components/Process/ProcessCompactStep.js.map +1 -1
- package/lib/module/components/Process/ProcessTimelineStep.js +79 -0
- package/lib/module/components/Process/ProcessTimelineStep.js.map +1 -0
- package/lib/module/components/Process/styles.js +63 -3
- package/lib/module/components/Process/styles.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/Attachments/AttachmentItem.d.ts +1 -1
- package/lib/typescript/src/components/Attachments/AttachmentItem.d.ts.map +1 -1
- package/lib/typescript/src/components/Attachments/Attachments.d.ts.map +1 -1
- package/lib/typescript/src/components/Attachments/constants.d.ts +1 -0
- package/lib/typescript/src/components/Attachments/constants.d.ts.map +1 -1
- package/lib/typescript/src/components/Attachments/styles.d.ts +5 -0
- package/lib/typescript/src/components/Attachments/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Attachments/types.d.ts +2 -1
- package/lib/typescript/src/components/Attachments/types.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/Composer.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/styles.d.ts +1 -0
- package/lib/typescript/src/components/Composer/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Confirmation/Confirmation.d.ts.map +1 -1
- package/lib/typescript/src/components/Confirmation/styles.d.ts +13 -2
- package/lib/typescript/src/components/Confirmation/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/MarkdownContent/styles.d.ts +32 -66
- package/lib/typescript/src/components/Message/MarkdownContent/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/Message.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/MessageWaiting.d.ts +3 -0
- package/lib/typescript/src/components/Message/MessageWaiting.d.ts.map +1 -0
- package/lib/typescript/src/components/Message/constants.d.ts +3 -0
- package/lib/typescript/src/components/Message/constants.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/styles.d.ts +40 -2
- package/lib/typescript/src/components/Message/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/types.d.ts +5 -0
- package/lib/typescript/src/components/Message/types.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/MessageListEndButton.d.ts +3 -0
- package/lib/typescript/src/components/MessageList/MessageListEndButton.d.ts.map +1 -0
- package/lib/typescript/src/components/MessageList/MessageListRow.d.ts +2 -8
- package/lib/typescript/src/components/MessageList/MessageListRow.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/index.d.ts +1 -1
- package/lib/typescript/src/components/MessageList/index.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/styles.d.ts +6 -0
- package/lib/typescript/src/components/MessageList/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/types.d.ts +41 -2
- package/lib/typescript/src/components/MessageList/types.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/useMessageListScroll.d.ts +14 -3
- package/lib/typescript/src/components/MessageList/useMessageListScroll.d.ts.map +1 -1
- package/lib/typescript/src/components/Process/Process.d.ts.map +1 -1
- package/lib/typescript/src/components/Process/ProcessCompactStep.d.ts +1 -1
- package/lib/typescript/src/components/Process/ProcessCompactStep.d.ts.map +1 -1
- package/lib/typescript/src/components/Process/ProcessTimelineStep.d.ts +5 -0
- package/lib/typescript/src/components/Process/ProcessTimelineStep.d.ts.map +1 -0
- package/lib/typescript/src/components/Process/styles.d.ts +62 -2
- package/lib/typescript/src/components/Process/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Process/types.d.ts +7 -2
- package/lib/typescript/src/components/Process/types.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Attachments/AttachmentItem.tsx +16 -5
- package/src/components/Attachments/Attachments.tsx +1 -0
- package/src/components/Attachments/constants.ts +1 -0
- package/src/components/Attachments/styles.ts +7 -4
- package/src/components/Attachments/types.ts +2 -1
- package/src/components/Composer/Composer.tsx +34 -8
- package/src/components/Composer/styles.ts +1 -0
- package/src/components/Confirmation/Confirmation.tsx +50 -14
- package/src/components/Confirmation/styles.ts +13 -6
- package/src/components/Message/MarkdownContent/styles.ts +18 -44
- package/src/components/Message/Message.tsx +53 -33
- package/src/components/Message/MessageWaiting.tsx +68 -0
- package/src/components/Message/constants.ts +4 -0
- package/src/components/Message/styles.ts +25 -2
- package/src/components/Message/types.ts +6 -0
- package/src/components/MessageList/MessageList.tsx +107 -35
- package/src/components/MessageList/MessageListEndButton.tsx +61 -0
- package/src/components/MessageList/MessageListRow.tsx +12 -12
- package/src/components/MessageList/index.ts +1 -0
- package/src/components/MessageList/styles.ts +6 -0
- package/src/components/MessageList/types.ts +52 -2
- package/src/components/MessageList/useMessageListScroll.ts +296 -21
- package/src/components/Process/Process.tsx +38 -5
- package/src/components/Process/ProcessCompactStep.tsx +32 -16
- package/src/components/Process/ProcessTimelineStep.tsx +109 -0
- package/src/components/Process/styles.ts +48 -3
- package/src/components/Process/types.ts +7 -2
- package/src/index.tsx +1 -0
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import type { RefObject } from 'react';
|
|
3
3
|
import type {
|
|
4
|
+
DimensionValue,
|
|
4
5
|
FlatList,
|
|
6
|
+
FlatListProps,
|
|
5
7
|
LayoutChangeEvent,
|
|
8
|
+
LayoutRectangle,
|
|
6
9
|
NativeScrollEvent,
|
|
7
10
|
NativeSyntheticEvent,
|
|
8
11
|
} from 'react-native';
|
|
9
12
|
import { DEFAULT_END_THRESHOLD } from './constants';
|
|
10
13
|
import type {
|
|
11
14
|
ListMeasurements,
|
|
15
|
+
MessageListAnchorOptions,
|
|
16
|
+
MessageListAnchorRequest,
|
|
17
|
+
MessageListItemLayout,
|
|
18
|
+
MessageListPendingScroll,
|
|
12
19
|
MessageListProps,
|
|
13
20
|
MessageListScrollOptions,
|
|
14
21
|
} from './types';
|
|
@@ -19,10 +26,24 @@ interface MessageListScrollInput<T> extends Pick<
|
|
|
19
26
|
> {
|
|
20
27
|
listRef: RefObject<FlatList<T> | null>;
|
|
21
28
|
keys: readonly string[];
|
|
29
|
+
contentMinimumHeight?: DimensionValue;
|
|
30
|
+
contentBottomPadding?: DimensionValue;
|
|
31
|
+
hasFooter?: boolean;
|
|
22
32
|
preservingPrependRef: RefObject<boolean>;
|
|
23
33
|
cancelPrependPreservation(): void;
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
function layoutValue(value: DimensionValue | undefined, extent: number) {
|
|
37
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
38
|
+
return Math.max(0, value);
|
|
39
|
+
if (typeof value === 'string' && value.endsWith('%')) {
|
|
40
|
+
const percentage = Number.parseFloat(value);
|
|
41
|
+
if (Number.isFinite(percentage))
|
|
42
|
+
return Math.max(0, (extent * percentage) / 100);
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
26
47
|
export function useMessageListScroll<T>({
|
|
27
48
|
listRef,
|
|
28
49
|
keys,
|
|
@@ -30,10 +51,14 @@ export function useMessageListScroll<T>({
|
|
|
30
51
|
followOutput,
|
|
31
52
|
endThreshold = DEFAULT_END_THRESHOLD,
|
|
32
53
|
onAtEndChange,
|
|
54
|
+
contentMinimumHeight,
|
|
55
|
+
contentBottomPadding,
|
|
56
|
+
hasFooter,
|
|
33
57
|
preservingPrependRef,
|
|
34
58
|
cancelPrependPreservation,
|
|
35
59
|
}: MessageListScrollInput<T>) {
|
|
36
60
|
const measurements = useRef<ListMeasurements>({
|
|
61
|
+
width: 0,
|
|
37
62
|
viewport: 0,
|
|
38
63
|
content: 0,
|
|
39
64
|
offset: 0,
|
|
@@ -42,29 +67,27 @@ export function useMessageListScroll<T>({
|
|
|
42
67
|
const initial = useRef(initialPosition);
|
|
43
68
|
const initialized = useRef(false);
|
|
44
69
|
const previousKeys = useRef<readonly string[]>([]);
|
|
70
|
+
const currentKeys = useRef(keys);
|
|
71
|
+
currentKeys.current = keys;
|
|
72
|
+
const contentStyle = useRef({ contentMinimumHeight, contentBottomPadding });
|
|
73
|
+
contentStyle.current = { contentMinimumHeight, contentBottomPadding };
|
|
45
74
|
const prependPending = useRef(false);
|
|
46
|
-
const pendingScroll = useRef<
|
|
75
|
+
const pendingScroll = useRef<MessageListPendingScroll | undefined>(undefined);
|
|
47
76
|
const userScrolling = useRef(false);
|
|
48
77
|
const atEndRef = useRef<boolean | undefined>(undefined);
|
|
49
78
|
const [atEnd, setAtEnd] = useState(true);
|
|
79
|
+
const anchor = useRef<MessageListAnchorRequest | undefined>(undefined);
|
|
80
|
+
const layouts = useRef(new Map<string, MessageListItemLayout>());
|
|
81
|
+
const layoutRevision = useRef(0);
|
|
82
|
+
const footerHeight = useRef(0);
|
|
83
|
+
if (!hasFooter) footerHeight.current = 0;
|
|
84
|
+
const layoutChanged = useRef<() => void>(() => {});
|
|
85
|
+
const [anchorMinimumHeight, setAnchorMinimumHeight] = useState<number>();
|
|
86
|
+
const minimumRef = useRef<number | undefined>(undefined);
|
|
50
87
|
const threshold = Number.isFinite(endThreshold)
|
|
51
88
|
? Math.max(0, endThreshold)
|
|
52
89
|
: DEFAULT_END_THRESHOLD;
|
|
53
90
|
|
|
54
|
-
useLayoutEffect(() => {
|
|
55
|
-
const previous = previousKeys.current;
|
|
56
|
-
const added = keys.length - previous.length;
|
|
57
|
-
const onlyPrepended =
|
|
58
|
-
previous.length > 0 &&
|
|
59
|
-
added > 0 &&
|
|
60
|
-
previous.every((key, index) => keys[index + added] === key);
|
|
61
|
-
const changed =
|
|
62
|
-
keys.length !== previous.length ||
|
|
63
|
-
keys.some((key, index) => key !== previous[index]);
|
|
64
|
-
if (changed) prependPending.current = onlyPrepended;
|
|
65
|
-
previousKeys.current = keys;
|
|
66
|
-
}, [keys]);
|
|
67
|
-
|
|
68
91
|
const ready = useCallback(() => {
|
|
69
92
|
const current = measurements.current;
|
|
70
93
|
return current.viewport > 0 && current.hasContentSize;
|
|
@@ -81,11 +104,192 @@ export function useMessageListScroll<T>({
|
|
|
81
104
|
}
|
|
82
105
|
}, [onAtEndChange, ready, threshold]);
|
|
83
106
|
|
|
107
|
+
const cancelAnchor = useCallback(() => {
|
|
108
|
+
anchor.current = undefined;
|
|
109
|
+
minimumRef.current = undefined;
|
|
110
|
+
setAnchorMinimumHeight(undefined);
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
const naturalContentHeight = useCallback(() => {
|
|
114
|
+
const last = currentKeys.current.at(-1);
|
|
115
|
+
const cell =
|
|
116
|
+
last === undefined ? undefined : layouts.current.get(last)?.cell;
|
|
117
|
+
if (!cell) return undefined;
|
|
118
|
+
const { width, viewport } = measurements.current;
|
|
119
|
+
return Math.max(
|
|
120
|
+
cell.y +
|
|
121
|
+
cell.height +
|
|
122
|
+
footerHeight.current +
|
|
123
|
+
layoutValue(contentStyle.current.contentBottomPadding, width),
|
|
124
|
+
layoutValue(contentStyle.current.contentMinimumHeight, viewport)
|
|
125
|
+
);
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
const applyAnchor = useCallback(() => {
|
|
129
|
+
const request = anchor.current;
|
|
130
|
+
if (!request || !ready()) return;
|
|
131
|
+
const index = currentKeys.current.indexOf(request.key);
|
|
132
|
+
if (index < 0) return;
|
|
133
|
+
request.seen = true;
|
|
134
|
+
const position = layouts.current.get(request.key);
|
|
135
|
+
if (!position?.cell || position.itemY === undefined) {
|
|
136
|
+
// An offscreen row first needs to enter FlatList's render window. This
|
|
137
|
+
// estimate only reveals it; success still requires its actual layouts.
|
|
138
|
+
if (request.locatedAt !== layoutRevision.current) {
|
|
139
|
+
request.locatedAt = layoutRevision.current;
|
|
140
|
+
listRef.current?.scrollToIndex({
|
|
141
|
+
index,
|
|
142
|
+
animated: false,
|
|
143
|
+
viewPosition: 0,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const { viewport, content } = measurements.current;
|
|
149
|
+
const offset = Math.max(
|
|
150
|
+
0,
|
|
151
|
+
position.cell.y + position.itemY - request.topOffset
|
|
152
|
+
);
|
|
153
|
+
const minimum = Math.max(
|
|
154
|
+
offset + viewport,
|
|
155
|
+
layoutValue(contentStyle.current.contentMinimumHeight, viewport)
|
|
156
|
+
);
|
|
157
|
+
minimumRef.current = minimum;
|
|
158
|
+
setAnchorMinimumHeight(minimum);
|
|
159
|
+
// Yoga's requested minimum is not yet native scroll space. Wait for the
|
|
160
|
+
// content-size event; otherwise native scrolling can clamp the target.
|
|
161
|
+
if (content + 0.5 < minimum) return;
|
|
162
|
+
if (request.offset === offset && request.viewport === viewport) return;
|
|
163
|
+
request.offset = offset;
|
|
164
|
+
request.viewport = viewport;
|
|
165
|
+
request.settled = false;
|
|
166
|
+
initialized.current = true;
|
|
167
|
+
listRef.current?.scrollToOffset({ offset, animated: request.animated });
|
|
168
|
+
measurements.current.offset = offset;
|
|
169
|
+
publishPosition();
|
|
170
|
+
}, [listRef, publishPosition, ready]);
|
|
171
|
+
|
|
172
|
+
const onCellLayout = useCallback(
|
|
173
|
+
(key: string, index: number, cell: LayoutRectangle) => {
|
|
174
|
+
if (
|
|
175
|
+
currentKeys.current[index] !== key ||
|
|
176
|
+
!Number.isFinite(cell.y) ||
|
|
177
|
+
!Number.isFinite(cell.height)
|
|
178
|
+
)
|
|
179
|
+
return;
|
|
180
|
+
const previous = layouts.current.get(key);
|
|
181
|
+
if (
|
|
182
|
+
previous?.index !== index ||
|
|
183
|
+
previous.cell?.x !== cell.x ||
|
|
184
|
+
previous.cell.y !== cell.y ||
|
|
185
|
+
previous.cell.width !== cell.width ||
|
|
186
|
+
previous.cell.height !== cell.height
|
|
187
|
+
)
|
|
188
|
+
layoutRevision.current++;
|
|
189
|
+
layouts.current.set(key, { ...previous, index, cell });
|
|
190
|
+
layoutChanged.current();
|
|
191
|
+
},
|
|
192
|
+
[]
|
|
193
|
+
);
|
|
194
|
+
const onItemLayout = useCallback(
|
|
195
|
+
(key: string, index: number, itemY: number) => {
|
|
196
|
+
if (currentKeys.current[index] !== key || !Number.isFinite(itemY)) return;
|
|
197
|
+
const previous = layouts.current.get(key);
|
|
198
|
+
if (previous?.index !== index || previous.itemY !== itemY)
|
|
199
|
+
layoutRevision.current++;
|
|
200
|
+
layouts.current.set(key, { ...previous, index, itemY });
|
|
201
|
+
layoutChanged.current();
|
|
202
|
+
},
|
|
203
|
+
[]
|
|
204
|
+
);
|
|
205
|
+
const onCellUnmount = useCallback((key: string, index: number) => {
|
|
206
|
+
if (layouts.current.get(key)?.index === index) {
|
|
207
|
+
layouts.current.delete(key);
|
|
208
|
+
layoutRevision.current++;
|
|
209
|
+
}
|
|
210
|
+
}, []);
|
|
211
|
+
const onFooterLayout = useCallback((event: LayoutChangeEvent) => {
|
|
212
|
+
footerHeight.current = event.nativeEvent.layout.height;
|
|
213
|
+
layoutChanged.current();
|
|
214
|
+
}, []);
|
|
215
|
+
|
|
216
|
+
const anchorToItem = useCallback(
|
|
217
|
+
(key: string, options: MessageListAnchorOptions = {}) => {
|
|
218
|
+
pendingScroll.current = undefined;
|
|
219
|
+
cancelPrependPreservation();
|
|
220
|
+
userScrolling.current = false;
|
|
221
|
+
anchor.current = {
|
|
222
|
+
key,
|
|
223
|
+
topOffset: Number.isFinite(options.topOffset)
|
|
224
|
+
? Math.max(0, options.topOffset!)
|
|
225
|
+
: 0,
|
|
226
|
+
animated: options.animated ?? true,
|
|
227
|
+
seen: currentKeys.current.includes(key),
|
|
228
|
+
};
|
|
229
|
+
minimumRef.current = 0;
|
|
230
|
+
setAnchorMinimumHeight(0);
|
|
231
|
+
applyAnchor();
|
|
232
|
+
},
|
|
233
|
+
[applyAnchor, cancelPrependPreservation]
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
useLayoutEffect(() => {
|
|
237
|
+
const previous = previousKeys.current;
|
|
238
|
+
const added = keys.length - previous.length;
|
|
239
|
+
const onlyPrepended =
|
|
240
|
+
previous.length > 0 &&
|
|
241
|
+
added > 0 &&
|
|
242
|
+
previous.every((key, index) => keys[index + added] === key);
|
|
243
|
+
const changed =
|
|
244
|
+
keys.length !== previous.length ||
|
|
245
|
+
keys.some((key, index) => key !== previous[index]);
|
|
246
|
+
if (changed) {
|
|
247
|
+
layoutRevision.current++;
|
|
248
|
+
prependPending.current = onlyPrepended;
|
|
249
|
+
const indices = new Map(keys.map((key, index) => [key, index]));
|
|
250
|
+
for (const [key, layout] of layouts.current) {
|
|
251
|
+
const index = indices.get(key);
|
|
252
|
+
if (index === undefined) layouts.current.delete(key);
|
|
253
|
+
else if (index !== layout.index) {
|
|
254
|
+
// A moved cell gets a new natural y; the row's local separator
|
|
255
|
+
// offset remains valid until an actual layout change replaces it.
|
|
256
|
+
layouts.current.set(key, { index, itemY: layout.itemY });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
previousKeys.current = keys;
|
|
261
|
+
const request = anchor.current;
|
|
262
|
+
if (request) {
|
|
263
|
+
if (keys.includes(request.key)) request.seen = true;
|
|
264
|
+
else if (request.seen) {
|
|
265
|
+
cancelAnchor();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Explicit positioning and native/Web prepend preservation must never
|
|
269
|
+
// compensate the same layout twice.
|
|
270
|
+
cancelPrependPreservation();
|
|
271
|
+
applyAnchor();
|
|
272
|
+
}
|
|
273
|
+
}, [applyAnchor, cancelAnchor, cancelPrependPreservation, keys]);
|
|
274
|
+
|
|
84
275
|
const scrollToEnd = useCallback(
|
|
85
276
|
(options: MessageListScrollOptions = {}) => {
|
|
86
277
|
cancelPrependPreservation();
|
|
87
|
-
const request = {
|
|
88
|
-
|
|
278
|
+
const request: MessageListPendingScroll = {
|
|
279
|
+
animated: options.animated ?? true,
|
|
280
|
+
};
|
|
281
|
+
const natural = naturalContentHeight();
|
|
282
|
+
const minimum = minimumRef.current;
|
|
283
|
+
if (
|
|
284
|
+
minimum !== undefined &&
|
|
285
|
+
natural !== undefined &&
|
|
286
|
+
measurements.current.content <= minimum + 0.5 &&
|
|
287
|
+
natural + 0.5 < measurements.current.content
|
|
288
|
+
) {
|
|
289
|
+
request.contentBeforeRelease = measurements.current.content;
|
|
290
|
+
}
|
|
291
|
+
cancelAnchor();
|
|
292
|
+
if (!ready() || request.contentBeforeRelease !== undefined) {
|
|
89
293
|
pendingScroll.current = request;
|
|
90
294
|
return;
|
|
91
295
|
}
|
|
@@ -100,14 +304,31 @@ export function useMessageListScroll<T>({
|
|
|
100
304
|
if (measurements.current.content <= measurements.current.viewport)
|
|
101
305
|
publishPosition();
|
|
102
306
|
},
|
|
103
|
-
[
|
|
307
|
+
[
|
|
308
|
+
cancelAnchor,
|
|
309
|
+
cancelPrependPreservation,
|
|
310
|
+
listRef,
|
|
311
|
+
naturalContentHeight,
|
|
312
|
+
publishPosition,
|
|
313
|
+
ready,
|
|
314
|
+
]
|
|
104
315
|
);
|
|
105
316
|
|
|
106
317
|
const itemCount = keys.length;
|
|
107
318
|
const reconcileLayout = useCallback(
|
|
108
319
|
(contentChanged: boolean) => {
|
|
109
320
|
if (!ready()) return;
|
|
321
|
+
if (anchor.current) {
|
|
322
|
+
applyAnchor();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
110
325
|
const request = pendingScroll.current;
|
|
326
|
+
if (
|
|
327
|
+
request?.contentBeforeRelease !== undefined &&
|
|
328
|
+
measurements.current.content === request.contentBeforeRelease &&
|
|
329
|
+
(naturalContentHeight() ?? 0) < request.contentBeforeRelease
|
|
330
|
+
)
|
|
331
|
+
return;
|
|
111
332
|
if (itemCount > 0 && !initialized.current) {
|
|
112
333
|
initialized.current = true;
|
|
113
334
|
if (request || initial.current === 'end') {
|
|
@@ -134,8 +355,10 @@ export function useMessageListScroll<T>({
|
|
|
134
355
|
else publishPosition();
|
|
135
356
|
},
|
|
136
357
|
[
|
|
358
|
+
applyAnchor,
|
|
137
359
|
followOutput,
|
|
138
360
|
itemCount,
|
|
361
|
+
naturalContentHeight,
|
|
139
362
|
preservingPrependRef,
|
|
140
363
|
publishPosition,
|
|
141
364
|
ready,
|
|
@@ -143,19 +366,36 @@ export function useMessageListScroll<T>({
|
|
|
143
366
|
]
|
|
144
367
|
);
|
|
145
368
|
|
|
369
|
+
layoutChanged.current = () => {
|
|
370
|
+
if (anchor.current) applyAnchor();
|
|
371
|
+
else if (pendingScroll.current) reconcileLayout(false);
|
|
372
|
+
};
|
|
373
|
+
|
|
146
374
|
const onScroll = useCallback(
|
|
147
375
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
148
376
|
const { contentOffset, contentSize, layoutMeasurement } =
|
|
149
377
|
event.nativeEvent;
|
|
150
378
|
const movedUp = contentOffset.y < measurements.current.offset;
|
|
379
|
+
const geometryChanged =
|
|
380
|
+
measurements.current.content !== contentSize.height ||
|
|
381
|
+
measurements.current.viewport !== layoutMeasurement.height;
|
|
382
|
+
const request = anchor.current;
|
|
383
|
+
if (request?.offset !== undefined) {
|
|
384
|
+
if (Math.abs(contentOffset.y - request.offset) <= 0.5)
|
|
385
|
+
request.settled = true;
|
|
386
|
+
// RN Web wheel scrolling need not dispatch scrollBeginDrag. Once a
|
|
387
|
+
// requested scroll has settled, a user move at unchanged geometry wins.
|
|
388
|
+
else if (request.settled && !geometryChanged) cancelAnchor();
|
|
389
|
+
}
|
|
151
390
|
const resizingAtEnd =
|
|
391
|
+
!anchor.current &&
|
|
152
392
|
atEndRef.current &&
|
|
153
393
|
!movedUp &&
|
|
154
394
|
!userScrolling.current &&
|
|
155
395
|
followOutput === 'whenAtEnd' &&
|
|
156
|
-
|
|
157
|
-
measurements.current.viewport !== layoutMeasurement.height);
|
|
396
|
+
geometryChanged;
|
|
158
397
|
measurements.current = {
|
|
398
|
+
width: layoutMeasurement.width,
|
|
159
399
|
offset: contentOffset.y,
|
|
160
400
|
content: contentSize.height,
|
|
161
401
|
viewport: layoutMeasurement.height,
|
|
@@ -163,12 +403,13 @@ export function useMessageListScroll<T>({
|
|
|
163
403
|
};
|
|
164
404
|
if (!resizingAtEnd) publishPosition();
|
|
165
405
|
},
|
|
166
|
-
[followOutput, publishPosition]
|
|
406
|
+
[cancelAnchor, followOutput, publishPosition]
|
|
167
407
|
);
|
|
168
408
|
|
|
169
409
|
const onLayout = useCallback(
|
|
170
410
|
({ nativeEvent: { layout } }: LayoutChangeEvent) => {
|
|
171
411
|
measurements.current.viewport = layout.height;
|
|
412
|
+
measurements.current.width = layout.width;
|
|
172
413
|
reconcileLayout(false);
|
|
173
414
|
},
|
|
174
415
|
[reconcileLayout]
|
|
@@ -183,6 +424,11 @@ export function useMessageListScroll<T>({
|
|
|
183
424
|
);
|
|
184
425
|
const onUserScrollBegin = useCallback(() => {
|
|
185
426
|
userScrolling.current = true;
|
|
427
|
+
pendingScroll.current = undefined;
|
|
428
|
+
cancelAnchor();
|
|
429
|
+
}, [cancelAnchor]);
|
|
430
|
+
const onMomentumScrollBegin = useCallback(() => {
|
|
431
|
+
userScrolling.current = true;
|
|
186
432
|
}, []);
|
|
187
433
|
const onScrollEndDrag = useCallback(
|
|
188
434
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
@@ -202,14 +448,43 @@ export function useMessageListScroll<T>({
|
|
|
202
448
|
},
|
|
203
449
|
[onScroll, publishPosition]
|
|
204
450
|
);
|
|
451
|
+
const onScrollToIndexFailed = useCallback(
|
|
452
|
+
(
|
|
453
|
+
failure: Parameters<
|
|
454
|
+
NonNullable<FlatListProps<T>['onScrollToIndexFailed']>
|
|
455
|
+
>[0]
|
|
456
|
+
) => {
|
|
457
|
+
const request = anchor.current;
|
|
458
|
+
if (
|
|
459
|
+
!request ||
|
|
460
|
+
currentKeys.current.indexOf(request.key) !== failure.index ||
|
|
461
|
+
!Number.isFinite(failure.averageItemLength) ||
|
|
462
|
+
failure.averageItemLength <= 0
|
|
463
|
+
)
|
|
464
|
+
return;
|
|
465
|
+
listRef.current?.scrollToOffset({
|
|
466
|
+
offset: failure.index * failure.averageItemLength,
|
|
467
|
+
animated: false,
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
[listRef]
|
|
471
|
+
);
|
|
205
472
|
|
|
206
473
|
return {
|
|
207
474
|
atEnd,
|
|
475
|
+
anchorMinimumHeight,
|
|
476
|
+
anchorToItem,
|
|
477
|
+
onCellLayout,
|
|
478
|
+
onItemLayout,
|
|
479
|
+
onCellUnmount,
|
|
480
|
+
onFooterLayout,
|
|
481
|
+
onScrollToIndexFailed,
|
|
208
482
|
scrollToEnd,
|
|
209
483
|
onLayout,
|
|
210
484
|
onContentSizeChange,
|
|
211
485
|
onScroll,
|
|
212
486
|
onUserScrollBegin,
|
|
487
|
+
onMomentumScrollBegin,
|
|
213
488
|
onScrollEndDrag,
|
|
214
489
|
onMomentumScrollEnd,
|
|
215
490
|
};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { PROCESS_STATUS_ICONS, PROCESS_STATUS_LABELS } from './constants';
|
|
10
10
|
import { createStyles } from './styles';
|
|
11
11
|
import { ProcessCompactStep } from './ProcessCompactStep';
|
|
12
|
+
import { ProcessTimelineStep } from './ProcessTimelineStep';
|
|
12
13
|
import type { ProcessProps, ProcessStep } from './types';
|
|
13
14
|
|
|
14
15
|
function idsWithDetails(
|
|
@@ -45,6 +46,10 @@ function formatElapsedMs(elapsedMs: number | undefined): string | undefined {
|
|
|
45
46
|
|
|
46
47
|
export function Process(props: ProcessProps): React.JSX.Element {
|
|
47
48
|
const { steps, title, identity, style, testID, variant = 'card' } = props;
|
|
49
|
+
const inlineIdentity =
|
|
50
|
+
variant === 'compact' &&
|
|
51
|
+
steps.length === 1 &&
|
|
52
|
+
props.identityPlacement === 'inline';
|
|
48
53
|
const styles = useThemedStyles(createStyles);
|
|
49
54
|
const colors = useColors();
|
|
50
55
|
const isControlled = props.expandedIds !== undefined;
|
|
@@ -73,25 +78,53 @@ export function Process(props: ProcessProps): React.JSX.Element {
|
|
|
73
78
|
|
|
74
79
|
return (
|
|
75
80
|
<View
|
|
76
|
-
style={[
|
|
81
|
+
style={[
|
|
82
|
+
variant === 'compact'
|
|
83
|
+
? styles.compactRoot
|
|
84
|
+
: variant === 'timeline'
|
|
85
|
+
? styles.timelineRoot
|
|
86
|
+
: styles.root,
|
|
87
|
+
style,
|
|
88
|
+
]}
|
|
77
89
|
testID={testID}
|
|
78
90
|
>
|
|
79
|
-
{title || identity ? (
|
|
91
|
+
{title || (identity && !inlineIdentity) ? (
|
|
80
92
|
<View style={styles.heading}>
|
|
81
|
-
{identity}
|
|
93
|
+
{!inlineIdentity ? identity : null}
|
|
82
94
|
{title ? <Text style={styles.headingTitle}>{title}</Text> : null}
|
|
83
95
|
</View>
|
|
84
96
|
) : null}
|
|
85
|
-
<View
|
|
97
|
+
<View
|
|
98
|
+
style={
|
|
99
|
+
variant === 'compact'
|
|
100
|
+
? styles.compactSteps
|
|
101
|
+
: variant === 'timeline'
|
|
102
|
+
? undefined
|
|
103
|
+
: styles.steps
|
|
104
|
+
}
|
|
105
|
+
>
|
|
86
106
|
{steps.map((step, index) => {
|
|
87
|
-
const statusLabel =
|
|
107
|
+
const statusLabel =
|
|
108
|
+
step.statusText ?? PROCESS_STATUS_LABELS[step.status];
|
|
88
109
|
const elapsed = formatElapsedMs(step.elapsedMs);
|
|
89
110
|
const isExpanded = expandedIds.includes(step.id);
|
|
111
|
+
if (variant === 'timeline')
|
|
112
|
+
return (
|
|
113
|
+
<ProcessTimelineStep
|
|
114
|
+
key={step.id}
|
|
115
|
+
step={step}
|
|
116
|
+
last={index === steps.length - 1}
|
|
117
|
+
expanded={isExpanded}
|
|
118
|
+
elapsed={elapsed}
|
|
119
|
+
onToggle={() => toggleDetails(step.id)}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
90
122
|
if (variant === 'compact')
|
|
91
123
|
return (
|
|
92
124
|
<ProcessCompactStep
|
|
93
125
|
key={step.id}
|
|
94
126
|
step={step}
|
|
127
|
+
identity={inlineIdentity ? identity : undefined}
|
|
95
128
|
expanded={isExpanded}
|
|
96
129
|
elapsed={elapsed}
|
|
97
130
|
onToggle={() => toggleDetails(step.id)}
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
Icon,
|
|
5
5
|
Pulse,
|
|
6
6
|
Reveal,
|
|
7
|
+
motion,
|
|
7
8
|
pressedOpacity,
|
|
8
|
-
r,
|
|
9
9
|
useColors,
|
|
10
10
|
useThemedStyles,
|
|
11
11
|
} from '@unif/react-native-design';
|
|
@@ -15,6 +15,7 @@ import type { ProcessCompactStepProps } from './types';
|
|
|
15
15
|
|
|
16
16
|
export function ProcessCompactStep({
|
|
17
17
|
step,
|
|
18
|
+
identity,
|
|
18
19
|
expanded,
|
|
19
20
|
elapsed,
|
|
20
21
|
onToggle,
|
|
@@ -23,6 +24,7 @@ export function ProcessCompactStep({
|
|
|
23
24
|
const colors = useColors();
|
|
24
25
|
const hasDetails = step.details != null;
|
|
25
26
|
const running = step.status === 'running';
|
|
27
|
+
const statusText = step.statusText ?? PROCESS_STATUS_LABELS[step.status];
|
|
26
28
|
const color = running
|
|
27
29
|
? colors.primary
|
|
28
30
|
: step.status === 'completed'
|
|
@@ -37,7 +39,7 @@ export function ProcessCompactStep({
|
|
|
37
39
|
? 'spark'
|
|
38
40
|
: PROCESS_STATUS_ICONS[step.status]
|
|
39
41
|
}
|
|
40
|
-
size={
|
|
42
|
+
size={14}
|
|
41
43
|
color={color}
|
|
42
44
|
/>
|
|
43
45
|
);
|
|
@@ -47,31 +49,44 @@ export function ProcessCompactStep({
|
|
|
47
49
|
testID={`process-step-${step.id}-toggle`}
|
|
48
50
|
accessible
|
|
49
51
|
accessibilityRole={hasDetails ? 'button' : undefined}
|
|
50
|
-
accessibilityLabel={`${step.title},${
|
|
52
|
+
accessibilityLabel={`${step.title},${statusText}${elapsed ? `,${elapsed}` : ''}`}
|
|
51
53
|
accessibilityState={hasDetails ? { expanded } : undefined}
|
|
52
54
|
disabled={!hasDetails}
|
|
55
|
+
hitSlop={10}
|
|
53
56
|
onPress={onToggle}
|
|
54
57
|
style={({ pressed }) => [
|
|
55
58
|
styles.compactHeader,
|
|
56
59
|
pressed && { opacity: pressedOpacity },
|
|
57
60
|
]}
|
|
58
61
|
>
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
{identity ? (
|
|
63
|
+
<>
|
|
64
|
+
{identity}
|
|
65
|
+
<View style={styles.spacer} />
|
|
66
|
+
</>
|
|
67
|
+
) : null}
|
|
68
|
+
{running ? (
|
|
69
|
+
<Pulse from={0.4} duration={motion.pulse / 2}>
|
|
70
|
+
{icon}
|
|
71
|
+
</Pulse>
|
|
72
|
+
) : (
|
|
73
|
+
icon
|
|
74
|
+
)}
|
|
75
|
+
{!identity ? (
|
|
76
|
+
<Text
|
|
77
|
+
style={styles.compactTitle}
|
|
78
|
+
testID={`process-step-title-${step.id}`}
|
|
79
|
+
numberOfLines={1}
|
|
80
|
+
>
|
|
81
|
+
{step.title}
|
|
82
|
+
</Text>
|
|
83
|
+
) : null}
|
|
84
|
+
<Text style={styles.compactStatus}>{statusText}</Text>
|
|
70
85
|
{elapsed ? <Text style={styles.elapsed}>{elapsed}</Text> : null}
|
|
71
86
|
{hasDetails ? (
|
|
72
87
|
<Icon
|
|
73
88
|
name={expanded ? 'chevron-up' : 'chevron-down'}
|
|
74
|
-
size={
|
|
89
|
+
size={14}
|
|
75
90
|
color={colors.foregroundSubtle}
|
|
76
91
|
/>
|
|
77
92
|
) : null}
|
|
@@ -80,11 +95,12 @@ export function ProcessCompactStep({
|
|
|
80
95
|
<Text style={styles.compactDescription}>{step.description}</Text>
|
|
81
96
|
) : null}
|
|
82
97
|
{hasDetails && expanded ? (
|
|
83
|
-
<Reveal>
|
|
98
|
+
<Reveal style={styles.compactDetailsContainer}>
|
|
84
99
|
<ScrollView
|
|
85
100
|
testID={`process-step-${step.id}-details`}
|
|
86
101
|
style={styles.compactDetails}
|
|
87
102
|
nestedScrollEnabled
|
|
103
|
+
showsVerticalScrollIndicator={false}
|
|
88
104
|
keyboardShouldPersistTaps="handled"
|
|
89
105
|
>
|
|
90
106
|
{step.details}
|