@buoy-gg/storage 2.1.14 → 2.1.16

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.
@@ -409,9 +409,7 @@ function StorageModalWithTabs({
409
409
  const END_REACHED_THRESHOLD = 0.8;
410
410
 
411
411
  // keyExtractor for raw event FlatList
412
- const eventKeyExtractor = (0, _react.useCallback)((item, index) => {
413
- return `${item.timestamp.getTime()}-${item.data?.key || index}`;
414
- }, []);
412
+ const eventKeyExtractor = (0, _react.useCallback)(item => item.id, []);
415
413
 
416
414
  // When a raw event card is pressed, open that key's conversation detail at the matching event
417
415
  const handleRawEventPress = (0, _react.useCallback)(item => {
@@ -474,8 +472,9 @@ function StorageModalWithTabs({
474
472
  const ascSortedEvents = [...selectedHistoryConversation.events].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
475
473
  const newestFirstEvents = [...ascSortedEvents].reverse();
476
474
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
475
+ style: styles.flexFill,
477
476
  data: newestFirstEvents,
478
- keyExtractor: (item, index) => `${item.timestamp.getTime()}-${index}`,
477
+ keyExtractor: item => item.id,
479
478
  renderItem: ({
480
479
  item
481
480
  }) => {
@@ -497,8 +496,7 @@ function StorageModalWithTabs({
497
496
  ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
498
497
  style: styles.separator
499
498
  }),
500
- initialNumToRender: 20,
501
- scrollEnabled: false
499
+ initialNumToRender: 20
502
500
  });
503
501
  }
504
502
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageBrowserMode.StorageBrowserMode, {
@@ -542,6 +540,7 @@ function StorageModalWithTabs({
542
540
  });
543
541
  }
544
542
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
543
+ style: styles.flexFill,
545
544
  data: visibleEvents,
546
545
  renderItem: renderEventItem,
547
546
  keyExtractor: eventKeyExtractor,
@@ -556,8 +555,7 @@ function StorageModalWithTabs({
556
555
  }) : null,
557
556
  initialNumToRender: 10,
558
557
  maxToRenderPerBatch: 10,
559
- windowSize: 10,
560
- scrollEnabled: false
558
+ windowSize: 10
561
559
  });
562
560
  };
563
561
  const footerNode = selectedConversation ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailFooter, {
@@ -686,6 +684,7 @@ function StorageModalWithTabs({
686
684
  styles: {},
687
685
  footer: footerNode,
688
686
  footerHeight: footerNode ? 68 : 0,
687
+ disableScrollWrapper: true,
689
688
  children: renderContent()
690
689
  })
691
690
  });
@@ -694,6 +693,9 @@ const styles = _reactNative.StyleSheet.create({
694
693
  headerSearchContainer: {
695
694
  flex: 1
696
695
  },
696
+ flexFill: {
697
+ flex: 1
698
+ },
697
699
  iconButton: {
698
700
  width: 32,
699
701
  height: 32,
@@ -30,9 +30,17 @@ var _MMKVListener = require("../utils/MMKVListener");
30
30
  */
31
31
 
32
32
  /**
33
- * Unified storage event type combining AsyncStorage and MMKV events
33
+ * Unified storage event type combining AsyncStorage and MMKV events.
34
+ * `id` is assigned by the store when the event enters — needed because raw
35
+ * events have no unique identifier (rapid same-key writes can share a
36
+ * millisecond timestamp), and React keys must be unique per render.
34
37
  */
35
38
 
39
+ let storageEventIdCounter = 0;
40
+ function nextStorageEventId() {
41
+ return `se-${Date.now()}-${++storageEventIdCounter}`;
42
+ }
43
+
36
44
  /**
37
45
  * Listener callback type for storage events array
38
46
  */
@@ -66,7 +74,8 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
66
74
  this.asyncStorageUnsubscribe = (0, _AsyncStorageListener.addListener)(event => {
67
75
  const storageEvent = {
68
76
  ...event,
69
- storageType: "async"
77
+ storageType: "async",
78
+ id: nextStorageEventId()
70
79
  };
71
80
  this.addEvent(storageEvent);
72
81
  });
@@ -77,7 +86,8 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
77
86
  this.mmkvUnsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
78
87
  const storageEvent = {
79
88
  ...event,
80
- storageType: "mmkv"
89
+ storageType: "mmkv",
90
+ id: nextStorageEventId()
81
91
  };
82
92
  this.addEvent(storageEvent);
83
93
  });
@@ -1,189 +1,95 @@
1
1
  "use strict";
2
2
 
3
- import { Fragment } from "react";
4
- import { View, Text, ScrollView, StyleSheet } from "react-native";
3
+ import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { View, Text, FlatList, StyleSheet, TouchableOpacity } from "react-native";
5
5
  import { computeLineDiff, DiffType } from "../../../utils/lineDiff";
6
6
  import { DiffSummary } from "../components/DiffSummary";
7
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const DEFAULT_OPTIONS = {
9
+ hideLineNumbers: false,
10
+ disableWordDiff: false,
11
+ showDiffOnly: false,
12
+ compareMethod: "words",
13
+ contextLines: 3,
14
+ lineOffset: 0
15
+ };
8
16
  export function ThemedSplitView({
9
17
  oldValue,
10
18
  newValue,
11
19
  theme,
12
- options = {
13
- hideLineNumbers: false,
14
- disableWordDiff: false,
15
- showDiffOnly: false,
16
- compareMethod: "words",
17
- contextLines: 3,
18
- lineOffset: 0
19
- },
20
+ options = DEFAULT_OPTIONS,
20
21
  showThemeName = false
21
22
  }) {
22
- // Compute line-by-line diff with options
23
- const diffComputeOptions = {
23
+ const dynamicStyles = useMemo(() => createDynamicStyles(theme), [theme]);
24
+ const diffComputeOptions = useMemo(() => ({
24
25
  compareMethod: options.compareMethod,
25
26
  disableWordDiff: options.disableWordDiff,
26
27
  showDiffOnly: options.showDiffOnly,
27
28
  contextLines: options.contextLines
28
- };
29
- const lineDiffs = computeLineDiff(oldValue, newValue, diffComputeOptions);
29
+ }), [options.compareMethod, options.disableWordDiff, options.showDiffOnly, options.contextLines]);
30
+ const lineDiffs = useMemo(() => computeLineDiff(oldValue, newValue, diffComputeOptions), [oldValue, newValue, diffComputeOptions]);
30
31
 
31
- // Create dynamic styles based on theme
32
- const dynamicStyles = createDynamicStyles(theme);
32
+ // One pass for the summary instead of three .filter() calls per render.
33
+ const summary = useMemo(() => {
34
+ let added = 0;
35
+ let removed = 0;
36
+ let modified = 0;
37
+ for (const d of lineDiffs) {
38
+ if (d.type === DiffType.ADDED) added++;else if (d.type === DiffType.REMOVED) removed++;else if (d.type === DiffType.MODIFIED) modified++;
39
+ }
40
+ return {
41
+ added,
42
+ removed,
43
+ modified
44
+ };
45
+ }, [lineDiffs]);
33
46
 
34
- // Render word diff content
35
- const renderWordDiff = wordDiffs => {
36
- return wordDiffs.map((word, idx) => {
37
- let backgroundColor = "transparent";
38
- if (word.type === DiffType.ADDED) {
39
- backgroundColor = theme.addedWordHighlight;
40
- } else if (word.type === DiffType.REMOVED) {
41
- backgroundColor = theme.removedWordHighlight;
42
- }
43
- return /*#__PURE__*/_jsx(Text, {
44
- style: [dynamicStyles.wordDiff, {
45
- backgroundColor
46
- }],
47
- children: word.value
48
- }, idx);
49
- });
50
- };
47
+ // Auto-collapse runs of unchanged lines that aren't near a change. Keeps
48
+ // CONTEXT_LINES on either side of every change visible (GitHub-style), folds
49
+ // the rest into a tappable header.
50
+ const [expandedFolds, setExpandedFolds] = useState(() => new Set());
51
51
 
52
- // Get colors for diff type
53
- const getDiffColors = (type, isModified = false) => {
54
- if (isModified) {
55
- return {
56
- background: theme.modifiedBackground,
57
- text: theme.modifiedText,
58
- markerBg: theme.markerModifiedBackground
59
- };
52
+ // Reset fold expansion when the user actually switches comparisons. Parent
53
+ // memoizes oldValue/newValue, so this only fires on a real change — not on
54
+ // every re-render. First run is a no-op (initial state is already empty).
55
+ const hasMountedRef = useRef(false);
56
+ useEffect(() => {
57
+ if (!hasMountedRef.current) {
58
+ hasMountedRef.current = true;
59
+ return;
60
60
  }
61
- switch (type) {
62
- case DiffType.ADDED:
63
- return {
64
- background: theme.addedBackground,
65
- text: theme.addedText,
66
- markerBg: theme.markerAddedBackground
67
- };
68
- case DiffType.REMOVED:
69
- return {
70
- background: theme.removedBackground,
71
- text: theme.removedText,
72
- markerBg: theme.markerRemovedBackground
73
- };
74
- case DiffType.DEFAULT:
75
- return {
76
- background: theme.unchangedBackground,
77
- text: theme.unchangedText,
78
- markerBg: "transparent"
79
- };
80
- default:
81
- return {
82
- background: theme.unchangedBackground,
83
- text: theme.unchangedText,
84
- markerBg: "transparent"
85
- };
86
- }
87
- };
88
-
89
- // Render a single line side (left or right)
90
- const renderLineSide = (lineNumber, content, type, marker, isEmpty = false) => {
91
- if (isEmpty) {
92
- return /*#__PURE__*/_jsxs(_Fragment, {
93
- children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
94
- style: [dynamicStyles.gutter, dynamicStyles.emptyGutter],
95
- children: /*#__PURE__*/_jsx(Text, {
96
- style: dynamicStyles.lineNumber,
97
- children: " "
98
- })
99
- }), /*#__PURE__*/_jsx(View, {
100
- style: [dynamicStyles.marker, dynamicStyles.emptyMarker],
101
- children: /*#__PURE__*/_jsx(Text, {
102
- style: dynamicStyles.markerText,
103
- children: " "
104
- })
105
- }), /*#__PURE__*/_jsx(View, {
106
- style: [dynamicStyles.contentCell, dynamicStyles.emptyContent],
107
- children: /*#__PURE__*/_jsx(Text, {
108
- style: dynamicStyles.content,
109
- children: " "
110
- })
111
- })]
61
+ setExpandedFolds(new Set());
62
+ }, [oldValue, newValue]);
63
+ const items = useMemo(() => buildSplitItems(lineDiffs, expandedFolds), [lineDiffs, expandedFolds]);
64
+ const toggleFold = useCallback(foldId => {
65
+ setExpandedFolds(prev => {
66
+ const next = new Set(prev);
67
+ if (next.has(foldId)) next.delete(foldId);else next.add(foldId);
68
+ return next;
69
+ });
70
+ }, []);
71
+ const keyExtractor = useCallback(item => item.kind === "row" ? `r:${item.diffIndex}` : `f:${item.foldId}`, []);
72
+ const renderItem = useCallback(({
73
+ item
74
+ }) => {
75
+ if (item.kind === "fold") {
76
+ return /*#__PURE__*/_jsx(FoldHeader, {
77
+ foldId: item.foldId,
78
+ count: item.count,
79
+ expanded: item.expanded,
80
+ theme: theme,
81
+ styles: dynamicStyles,
82
+ onToggle: toggleFold
112
83
  });
113
84
  }
114
- const colors = getDiffColors(type);
115
- return /*#__PURE__*/_jsxs(_Fragment, {
116
- children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
117
- style: [dynamicStyles.gutter, {
118
- backgroundColor: theme.lineNumberBackground
119
- }],
120
- children: /*#__PURE__*/_jsx(Text, {
121
- style: dynamicStyles.lineNumber,
122
- children: lineNumber || " "
123
- })
124
- }), /*#__PURE__*/_jsx(View, {
125
- style: [dynamicStyles.marker, {
126
- backgroundColor: colors.markerBg
127
- }],
128
- children: /*#__PURE__*/_jsx(Text, {
129
- style: [dynamicStyles.markerText, {
130
- color: theme.markerText
131
- }],
132
- children: marker
133
- })
134
- }), /*#__PURE__*/_jsx(View, {
135
- style: [dynamicStyles.contentCell, {
136
- backgroundColor: colors.background
137
- }],
138
- children: /*#__PURE__*/_jsx(Text, {
139
- style: [dynamicStyles.content, {
140
- color: colors.text
141
- }],
142
- children: Array.isArray(content) ? renderWordDiff(content) : content || " "
143
- })
144
- })]
85
+ return /*#__PURE__*/_jsx(DiffRow, {
86
+ diff: item.diff,
87
+ showSeparator: false,
88
+ theme: theme,
89
+ options: options,
90
+ styles: dynamicStyles
145
91
  });
146
- };
147
-
148
- // Check if we should show a separator (gap in line numbers)
149
- const shouldShowSeparator = (idx, diffs) => {
150
- if (!options.showDiffOnly || idx === 0) return false;
151
- const prevDiff = diffs[idx - 1];
152
- const currDiff = diffs[idx];
153
-
154
- // Check for gap in line numbers
155
- const leftGap = currDiff.leftLineNumber && prevDiff.leftLineNumber && currDiff.leftLineNumber - prevDiff.leftLineNumber > 1;
156
- const rightGap = currDiff.rightLineNumber && prevDiff.rightLineNumber && currDiff.rightLineNumber - prevDiff.rightLineNumber > 1;
157
- return leftGap || rightGap;
158
- };
159
-
160
- // Render a complete row with both left and right sides
161
- const renderDiffRow = (diff, idx, diffs) => {
162
- const isRemoved = diff.type === DiffType.REMOVED;
163
- const isAdded = diff.type === DiffType.ADDED;
164
- const isModified = diff.type === DiffType.MODIFIED;
165
- const isDefault = diff.type === DiffType.DEFAULT;
166
- return /*#__PURE__*/_jsxs(Fragment, {
167
- children: [shouldShowSeparator(idx, diffs) && /*#__PURE__*/_jsx(View, {
168
- style: dynamicStyles.separator,
169
- children: /*#__PURE__*/_jsx(Text, {
170
- style: dynamicStyles.separatorText,
171
- children: "\u2022 \u2022 \u2022"
172
- })
173
- }), /*#__PURE__*/_jsxs(View, {
174
- style: dynamicStyles.row,
175
- children: [/*#__PURE__*/_jsx(View, {
176
- style: dynamicStyles.leftSide,
177
- children: isRemoved || isModified || isDefault ? renderLineSide(diff.leftLineNumber, diff.leftContent, isModified ? DiffType.REMOVED : diff.type, isRemoved || isModified ? "-" : " ") : renderLineSide(undefined, undefined, DiffType.DEFAULT, " ", true)
178
- }), /*#__PURE__*/_jsx(View, {
179
- style: dynamicStyles.centerDivider
180
- }), /*#__PURE__*/_jsx(View, {
181
- style: dynamicStyles.rightSide,
182
- children: isAdded || isModified || isDefault ? renderLineSide(diff.rightLineNumber, diff.rightContent, isModified ? DiffType.ADDED : diff.type, isAdded || isModified ? "+" : " ") : renderLineSide(undefined, undefined, DiffType.DEFAULT, " ", true)
183
- })]
184
- })]
185
- }, idx);
186
- };
92
+ }, [theme, options, dynamicStyles, toggleFold]);
187
93
  return /*#__PURE__*/_jsxs(View, {
188
94
  style: [dynamicStyles.container, theme.glowColor && {
189
95
  shadowColor: theme.glowColor,
@@ -221,24 +127,276 @@ export function ThemedSplitView({
221
127
  })
222
128
  })]
223
129
  }), /*#__PURE__*/_jsx(DiffSummary, {
224
- added: lineDiffs.filter(d => d.type === DiffType.ADDED).length,
225
- removed: lineDiffs.filter(d => d.type === DiffType.REMOVED).length,
226
- modified: lineDiffs.filter(d => d.type === DiffType.MODIFIED).length,
130
+ added: summary.added,
131
+ removed: summary.removed,
132
+ modified: summary.modified,
227
133
  theme: theme
228
- }), /*#__PURE__*/_jsx(ScrollView, {
134
+ }), lineDiffs.length === 0 ? /*#__PURE__*/_jsx(View, {
135
+ style: dynamicStyles.emptyState,
136
+ children: /*#__PURE__*/_jsx(Text, {
137
+ style: dynamicStyles.emptyText,
138
+ children: options.showDiffOnly ? "No differences found" : "No content to display"
139
+ })
140
+ }) : /*#__PURE__*/_jsx(FlatList, {
229
141
  style: dynamicStyles.scrollView,
230
- showsVerticalScrollIndicator: false,
231
142
  contentContainerStyle: dynamicStyles.scrollContent,
232
- children: lineDiffs.length === 0 ? /*#__PURE__*/_jsx(View, {
233
- style: dynamicStyles.emptyState,
234
- children: /*#__PURE__*/_jsx(Text, {
235
- style: dynamicStyles.emptyText,
236
- children: options.showDiffOnly ? "No differences found" : "No content to display"
143
+ data: items,
144
+ renderItem: renderItem,
145
+ keyExtractor: keyExtractor,
146
+ showsVerticalScrollIndicator: false,
147
+ initialNumToRender: 20,
148
+ maxToRenderPerBatch: 20,
149
+ windowSize: 7,
150
+ removeClippedSubviews: true
151
+ })]
152
+ });
153
+ }
154
+
155
+ // Number of context lines kept visible on either side of every change.
156
+ const CONTEXT_LINES = 3;
157
+ // Don't bother folding tiny gaps — toggling them is more friction than scroll.
158
+ const MIN_FOLD_SIZE = 4;
159
+ function buildSplitItems(lineDiffs, expandedFolds) {
160
+ const n = lineDiffs.length;
161
+ if (n === 0) return [];
162
+
163
+ // Mark every line that should always be visible: any change, plus
164
+ // CONTEXT_LINES on either side of it.
165
+ const visible = new Array(n).fill(false);
166
+ for (let i = 0; i < n; i++) {
167
+ if (lineDiffs[i].type !== DiffType.DEFAULT) {
168
+ const start = Math.max(0, i - CONTEXT_LINES);
169
+ const end = Math.min(n - 1, i + CONTEXT_LINES);
170
+ for (let k = start; k <= end; k++) visible[k] = true;
171
+ }
172
+ }
173
+ const items = [];
174
+ let foldId = 0;
175
+ let i = 0;
176
+ while (i < n) {
177
+ if (visible[i]) {
178
+ items.push({
179
+ kind: "row",
180
+ diff: lineDiffs[i],
181
+ diffIndex: i
182
+ });
183
+ i++;
184
+ continue;
185
+ }
186
+ let j = i;
187
+ while (j < n && !visible[j]) j++;
188
+ const runLength = j - i;
189
+ if (runLength < MIN_FOLD_SIZE) {
190
+ for (let k = i; k < j; k++) {
191
+ items.push({
192
+ kind: "row",
193
+ diff: lineDiffs[k],
194
+ diffIndex: k
195
+ });
196
+ }
197
+ } else {
198
+ const id = foldId++;
199
+ const expanded = expandedFolds.has(id);
200
+ items.push({
201
+ kind: "fold",
202
+ foldId: id,
203
+ count: runLength,
204
+ expanded
205
+ });
206
+ if (expanded) {
207
+ for (let k = i; k < j; k++) {
208
+ items.push({
209
+ kind: "row",
210
+ diff: lineDiffs[k],
211
+ diffIndex: k
212
+ });
213
+ }
214
+ }
215
+ }
216
+ i = j;
217
+ }
218
+ return items;
219
+ }
220
+ const FoldHeader = /*#__PURE__*/memo(function FoldHeader({
221
+ foldId,
222
+ count,
223
+ expanded,
224
+ theme,
225
+ styles,
226
+ onToggle
227
+ }) {
228
+ return /*#__PURE__*/_jsx(TouchableOpacity, {
229
+ style: [styles.foldHeader, {
230
+ backgroundColor: theme.separatorBackground
231
+ }],
232
+ onPress: () => onToggle(foldId),
233
+ activeOpacity: 0.7,
234
+ children: /*#__PURE__*/_jsx(Text, {
235
+ style: [styles.foldHeaderText, {
236
+ color: theme.separatorText
237
+ }],
238
+ children: expanded ? `▼ ${count} unchanged line${count === 1 ? "" : "s"} — tap to hide` : `▶ ${count} unchanged line${count === 1 ? "" : "s"} — tap to expand`
239
+ })
240
+ });
241
+ });
242
+ const DiffRow = /*#__PURE__*/memo(function DiffRow({
243
+ diff,
244
+ showSeparator,
245
+ theme,
246
+ options,
247
+ styles
248
+ }) {
249
+ const isRemoved = diff.type === DiffType.REMOVED;
250
+ const isAdded = diff.type === DiffType.ADDED;
251
+ const isModified = diff.type === DiffType.MODIFIED;
252
+ const isDefault = diff.type === DiffType.DEFAULT;
253
+ return /*#__PURE__*/_jsxs(Fragment, {
254
+ children: [showSeparator && /*#__PURE__*/_jsx(View, {
255
+ style: styles.separator,
256
+ children: /*#__PURE__*/_jsx(Text, {
257
+ style: styles.separatorText,
258
+ children: "\u2022 \u2022 \u2022"
259
+ })
260
+ }), /*#__PURE__*/_jsxs(View, {
261
+ style: styles.row,
262
+ children: [/*#__PURE__*/_jsx(View, {
263
+ style: styles.leftSide,
264
+ children: isRemoved || isModified || isDefault ? /*#__PURE__*/_jsx(LineSide, {
265
+ lineNumber: diff.leftLineNumber,
266
+ content: diff.leftContent,
267
+ type: isModified ? DiffType.REMOVED : diff.type,
268
+ marker: isRemoved || isModified ? "-" : " ",
269
+ theme: theme,
270
+ options: options,
271
+ styles: styles
272
+ }) : /*#__PURE__*/_jsx(EmptyLineSide, {
273
+ options: options,
274
+ styles: styles
237
275
  })
238
- }) : lineDiffs.map((diff, idx) => renderDiffRow(diff, idx, lineDiffs))
276
+ }), /*#__PURE__*/_jsx(View, {
277
+ style: styles.centerDivider
278
+ }), /*#__PURE__*/_jsx(View, {
279
+ style: styles.rightSide,
280
+ children: isAdded || isModified || isDefault ? /*#__PURE__*/_jsx(LineSide, {
281
+ lineNumber: diff.rightLineNumber,
282
+ content: diff.rightContent,
283
+ type: isModified ? DiffType.ADDED : diff.type,
284
+ marker: isAdded || isModified ? "+" : " ",
285
+ theme: theme,
286
+ options: options,
287
+ styles: styles
288
+ }) : /*#__PURE__*/_jsx(EmptyLineSide, {
289
+ options: options,
290
+ styles: styles
291
+ })
292
+ })]
293
+ })]
294
+ });
295
+ });
296
+ function LineSide({
297
+ lineNumber,
298
+ content,
299
+ type,
300
+ marker,
301
+ theme,
302
+ options,
303
+ styles
304
+ }) {
305
+ const colors = getDiffColors(type, theme);
306
+ return /*#__PURE__*/_jsxs(Fragment, {
307
+ children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
308
+ style: [styles.gutter, {
309
+ backgroundColor: theme.lineNumberBackground
310
+ }],
311
+ children: /*#__PURE__*/_jsx(Text, {
312
+ style: styles.lineNumber,
313
+ children: lineNumber || " "
314
+ })
315
+ }), /*#__PURE__*/_jsx(View, {
316
+ style: [styles.marker, {
317
+ backgroundColor: colors.markerBg
318
+ }],
319
+ children: /*#__PURE__*/_jsx(Text, {
320
+ style: [styles.markerText, {
321
+ color: theme.markerText
322
+ }],
323
+ children: marker
324
+ })
325
+ }), /*#__PURE__*/_jsx(View, {
326
+ style: [styles.contentCell, {
327
+ backgroundColor: colors.background
328
+ }],
329
+ children: /*#__PURE__*/_jsx(Text, {
330
+ style: [styles.content, {
331
+ color: colors.text
332
+ }],
333
+ children: Array.isArray(content) ? content.map((word, idx) => {
334
+ let backgroundColor = "transparent";
335
+ if (word.type === DiffType.ADDED) {
336
+ backgroundColor = theme.addedWordHighlight;
337
+ } else if (word.type === DiffType.REMOVED) {
338
+ backgroundColor = theme.removedWordHighlight;
339
+ }
340
+ return /*#__PURE__*/_jsx(Text, {
341
+ style: [styles.wordDiff, {
342
+ backgroundColor
343
+ }],
344
+ children: word.value
345
+ }, idx);
346
+ }) : content || " "
347
+ })
348
+ })]
349
+ });
350
+ }
351
+ function EmptyLineSide({
352
+ options,
353
+ styles
354
+ }) {
355
+ return /*#__PURE__*/_jsxs(Fragment, {
356
+ children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
357
+ style: [styles.gutter, styles.emptyGutter],
358
+ children: /*#__PURE__*/_jsx(Text, {
359
+ style: styles.lineNumber,
360
+ children: " "
361
+ })
362
+ }), /*#__PURE__*/_jsx(View, {
363
+ style: [styles.marker, styles.emptyMarker],
364
+ children: /*#__PURE__*/_jsx(Text, {
365
+ style: styles.markerText,
366
+ children: " "
367
+ })
368
+ }), /*#__PURE__*/_jsx(View, {
369
+ style: [styles.contentCell, styles.emptyContent],
370
+ children: /*#__PURE__*/_jsx(Text, {
371
+ style: styles.content,
372
+ children: " "
373
+ })
239
374
  })]
240
375
  });
241
376
  }
377
+ function getDiffColors(type, theme) {
378
+ switch (type) {
379
+ case DiffType.ADDED:
380
+ return {
381
+ background: theme.addedBackground,
382
+ text: theme.addedText,
383
+ markerBg: theme.markerAddedBackground
384
+ };
385
+ case DiffType.REMOVED:
386
+ return {
387
+ background: theme.removedBackground,
388
+ text: theme.removedText,
389
+ markerBg: theme.markerRemovedBackground
390
+ };
391
+ case DiffType.DEFAULT:
392
+ default:
393
+ return {
394
+ background: theme.unchangedBackground,
395
+ text: theme.unchangedText,
396
+ markerBg: "transparent"
397
+ };
398
+ }
399
+ }
242
400
 
243
401
  // Create dynamic styles based on theme
244
402
  function createDynamicStyles(theme) {
@@ -400,6 +558,20 @@ function createDynamicStyles(theme) {
400
558
  fontFamily: "monospace",
401
559
  letterSpacing: 2
402
560
  },
561
+ foldHeader: {
562
+ paddingVertical: 6,
563
+ paddingHorizontal: 12,
564
+ alignItems: "center",
565
+ justifyContent: "center",
566
+ borderTopWidth: 1,
567
+ borderBottomWidth: 1,
568
+ borderColor: theme.borderColor
569
+ },
570
+ foldHeaderText: {
571
+ fontSize: 10,
572
+ fontFamily: "monospace",
573
+ letterSpacing: 0.5
574
+ },
403
575
  emptyState: {
404
576
  padding: 40,
405
577
  alignItems: "center",