react-native-nitro-markdown 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +66 -4
  2. package/ios/HybridMarkdownSession.swift +11 -4
  3. package/lib/commonjs/MarkdownContext.js +1 -3
  4. package/lib/commonjs/MarkdownContext.js.map +1 -1
  5. package/lib/commonjs/index.js +6 -0
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/markdown-stream.js +80 -29
  8. package/lib/commonjs/markdown-stream.js.map +1 -1
  9. package/lib/commonjs/markdown.js +94 -37
  10. package/lib/commonjs/markdown.js.map +1 -1
  11. package/lib/commonjs/renderers/image.js +2 -2
  12. package/lib/commonjs/renderers/image.js.map +1 -1
  13. package/lib/commonjs/renderers/math.js +6 -2
  14. package/lib/commonjs/renderers/math.js.map +1 -1
  15. package/lib/commonjs/renderers/table/index.js +4 -4
  16. package/lib/commonjs/renderers/table/index.js.map +1 -1
  17. package/lib/commonjs/renderers/table/table-utils.js +1 -1
  18. package/lib/commonjs/renderers/table/table-utils.js.map +1 -1
  19. package/lib/commonjs/utils/code-highlight.js +1 -1
  20. package/lib/commonjs/utils/code-highlight.js.map +1 -1
  21. package/lib/commonjs/utils/incremental-ast.js +24 -11
  22. package/lib/commonjs/utils/incremental-ast.js.map +1 -1
  23. package/lib/commonjs/utils/link-security.js +2 -2
  24. package/lib/commonjs/utils/link-security.js.map +1 -1
  25. package/lib/commonjs/utils/stream-timeline.js +13 -7
  26. package/lib/commonjs/utils/stream-timeline.js.map +1 -1
  27. package/lib/module/MarkdownContext.js +1 -3
  28. package/lib/module/MarkdownContext.js.map +1 -1
  29. package/lib/module/index.js +1 -1
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/markdown-stream.js +79 -29
  32. package/lib/module/markdown-stream.js.map +1 -1
  33. package/lib/module/markdown.js +94 -37
  34. package/lib/module/markdown.js.map +1 -1
  35. package/lib/module/renderers/image.js +2 -2
  36. package/lib/module/renderers/image.js.map +1 -1
  37. package/lib/module/renderers/math.js +6 -2
  38. package/lib/module/renderers/math.js.map +1 -1
  39. package/lib/module/renderers/table/index.js +4 -4
  40. package/lib/module/renderers/table/index.js.map +1 -1
  41. package/lib/module/renderers/table/table-utils.js +1 -1
  42. package/lib/module/renderers/table/table-utils.js.map +1 -1
  43. package/lib/module/utils/code-highlight.js +1 -1
  44. package/lib/module/utils/code-highlight.js.map +1 -1
  45. package/lib/module/utils/incremental-ast.js +24 -11
  46. package/lib/module/utils/incremental-ast.js.map +1 -1
  47. package/lib/module/utils/link-security.js +2 -2
  48. package/lib/module/utils/link-security.js.map +1 -1
  49. package/lib/module/utils/stream-timeline.js +13 -7
  50. package/lib/module/utils/stream-timeline.js.map +1 -1
  51. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/index.d.ts +2 -2
  53. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/markdown-stream.d.ts +28 -7
  55. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/utils/incremental-ast.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/utils/stream-timeline.d.ts.map +1 -1
  59. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
  60. package/lib/typescript/module/index.d.ts +2 -2
  61. package/lib/typescript/module/index.d.ts.map +1 -1
  62. package/lib/typescript/module/markdown-stream.d.ts +28 -7
  63. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  64. package/lib/typescript/module/markdown.d.ts.map +1 -1
  65. package/lib/typescript/module/utils/incremental-ast.d.ts.map +1 -1
  66. package/lib/typescript/module/utils/stream-timeline.d.ts.map +1 -1
  67. package/package.json +1 -1
  68. package/src/MarkdownContext.ts +0 -2
  69. package/src/index.ts +9 -2
  70. package/src/markdown-stream.tsx +107 -31
  71. package/src/markdown.tsx +75 -41
  72. package/src/renderers/image.tsx +2 -2
  73. package/src/renderers/math.tsx +2 -2
  74. package/src/renderers/table/index.tsx +4 -4
  75. package/src/renderers/table/table-utils.ts +1 -1
  76. package/src/utils/code-highlight.ts +1 -1
  77. package/src/utils/incremental-ast.ts +25 -9
  78. package/src/utils/link-security.ts +3 -3
  79. package/src/utils/stream-timeline.ts +10 -7
package/src/markdown.tsx CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  type CustomRenderer,
35
35
  type CustomRenderers,
36
36
  type LinkPressHandler,
37
+ type MarkdownContextValue,
37
38
  type NodeRendererProps,
38
39
  type TableOptions,
39
40
  } from "./MarkdownContext";
@@ -174,10 +175,8 @@ const warnInDev = (message: string, error?: unknown): void => {
174
175
  };
175
176
 
176
177
  const cloneMarkdownNode = (node: MarkdownNode): MarkdownNode => {
177
- return {
178
- ...node,
179
- children: node.children?.map(cloneMarkdownNode),
180
- };
178
+ const children = node.children?.map(cloneMarkdownNode);
179
+ return children ? { ...node, children } : { ...node };
181
180
  };
182
181
 
183
182
  const getParserOptionsKey = (options?: ParserOptions): string => {
@@ -204,11 +203,11 @@ const normalizeParserOptions = (
204
203
  return undefined;
205
204
  }
206
205
 
207
- return {
208
- gfm,
209
- math,
210
- html,
211
- };
206
+ const normalized: ParserOptions = {};
207
+ if (gfm !== undefined) normalized.gfm = gfm;
208
+ if (math !== undefined) normalized.math = math;
209
+ if (html !== undefined) normalized.html = html;
210
+ return normalized;
212
211
  };
213
212
 
214
213
  const parseWithNativeParser = (
@@ -478,11 +477,14 @@ export const Markdown: FC<MarkdownProps> = ({
478
477
  const markdownToParse = sourceAst
479
478
  ? children
480
479
  : applyBeforeParsePlugins(children, sortedPlugins, onErrorRef.current);
481
- const parserOptions = normalizeParserOptions({
482
- gfm: parserOptionGfm,
483
- math: parserOptionMath,
484
- html: parserOptionHtml,
485
- });
480
+ const parserOptions = normalizeParserOptions(
481
+ Object.assign(
482
+ {},
483
+ parserOptionGfm === undefined ? null : { gfm: parserOptionGfm },
484
+ parserOptionMath === undefined ? null : { math: parserOptionMath },
485
+ parserOptionHtml === undefined ? null : { html: parserOptionHtml },
486
+ ),
487
+ );
486
488
  const shouldCloneSourceAst =
487
489
  sourceAst &&
488
490
  (Boolean(astTransform) ||
@@ -566,16 +568,16 @@ export const Markdown: FC<MarkdownProps> = ({
566
568
  }, [userTheme, stylingStrategy]);
567
569
 
568
570
  const baseStyles = getBaseStyles(theme);
569
- const contextValue = useMemo(
571
+ const contextValue = useMemo<MarkdownContextValue>(
570
572
  () => ({
571
573
  renderers,
572
574
  theme,
573
- styles: nodeStyles,
574
575
  stylingStrategy,
575
- onLinkPress,
576
- tableOptions,
577
- imageOptions,
578
- highlightCode,
576
+ ...(nodeStyles ? { styles: nodeStyles } : {}),
577
+ ...(onLinkPress ? { onLinkPress } : {}),
578
+ ...(tableOptions ? { tableOptions } : {}),
579
+ ...(imageOptions ? { imageOptions } : {}),
580
+ ...(highlightCode === undefined ? {} : { highlightCode }),
579
581
  }),
580
582
  [
581
583
  renderers,
@@ -779,15 +781,18 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
779
781
  ...(node.type === "heading" && {
780
782
  level: (node.level ?? 1) as 1 | 2 | 3 | 4 | 5 | 6,
781
783
  }),
782
- ...(node.type === "link" && { href: node.href ?? "", title: node.title }),
784
+ ...(node.type === "link" && {
785
+ href: node.href ?? "",
786
+ ...(node.title ? { title: node.title } : {}),
787
+ }),
783
788
  ...(node.type === "image" && {
784
789
  url: node.href ?? "",
785
- alt: node.alt,
786
- title: node.title,
790
+ ...(node.alt ? { alt: node.alt } : {}),
791
+ ...(node.title ? { title: node.title } : {}),
787
792
  }),
788
793
  ...(node.type === "code_block" && {
789
794
  content: getTextContent(node),
790
- language: node.language,
795
+ ...(node.language ? { language: node.language } : {}),
791
796
  }),
792
797
  ...(node.type === "code_inline" && { content: node.content ?? "" }),
793
798
  ...((node.type === "math_inline" || node.type === "math_block") && {
@@ -795,7 +800,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
795
800
  }),
796
801
  ...(node.type === "list" && {
797
802
  ordered: node.ordered ?? false,
798
- start: node.start,
803
+ ...(node.start === undefined ? {} : { start: node.start }),
799
804
  }),
800
805
  ...(node.type === "task_list_item" && { checked: node.checked ?? false }),
801
806
  };
@@ -816,7 +821,10 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
816
821
 
817
822
  case "heading":
818
823
  return (
819
- <Heading level={node.level ?? 1} style={nodeStyles?.heading}>
824
+ <Heading
825
+ level={node.level ?? 1}
826
+ {...(nodeStyles?.heading ? { style: nodeStyles.heading } : {})}
827
+ >
820
828
  {renderChildren(node.children, inListItem, true)}
821
829
  </Heading>
822
830
  );
@@ -859,7 +867,10 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
859
867
 
860
868
  case "link":
861
869
  return (
862
- <Link href={node.href ?? ""} style={nodeStyles?.link}>
870
+ <Link
871
+ href={node.href ?? ""}
872
+ {...(nodeStyles?.link ? { style: nodeStyles.link } : {})}
873
+ >
863
874
  {renderChildren(node.children, inListItem, true)}
864
875
  </Link>
865
876
  );
@@ -868,36 +879,52 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
868
879
  return (
869
880
  <Image
870
881
  url={node.href ?? ""}
871
- title={node.title}
872
- alt={node.alt}
873
882
  Renderer={NodeRenderer}
874
- style={nodeStyles?.image}
883
+ {...(node.title ? { title: node.title } : {})}
884
+ {...(node.alt ? { alt: node.alt } : {})}
885
+ {...(nodeStyles?.image ? { style: nodeStyles.image } : {})}
875
886
  />
876
887
  );
877
888
 
878
889
  case "code_inline":
879
890
  return (
880
- <InlineCode style={nodeStyles?.code_inline}>{node.content}</InlineCode>
891
+ <InlineCode
892
+ {...(nodeStyles?.code_inline
893
+ ? { style: nodeStyles.code_inline }
894
+ : {})}
895
+ >
896
+ {node.content}
897
+ </InlineCode>
881
898
  );
882
899
 
883
900
  case "code_block":
884
901
  return (
885
902
  <CodeBlock
886
- language={node.language}
887
903
  content={getTextContent(node)}
888
- style={nodeStyles?.code_block}
904
+ {...(node.language ? { language: node.language } : {})}
905
+ {...(nodeStyles?.code_block ? { style: nodeStyles.code_block } : {})}
889
906
  />
890
907
  );
891
908
 
892
909
  case "blockquote":
893
910
  return (
894
- <Blockquote style={nodeStyles?.blockquote}>
911
+ <Blockquote
912
+ {...(nodeStyles?.blockquote
913
+ ? { style: nodeStyles.blockquote }
914
+ : {})}
915
+ >
895
916
  {renderChildren(node.children, inListItem, false)}
896
917
  </Blockquote>
897
918
  );
898
919
 
899
920
  case "horizontal_rule":
900
- return <HorizontalRule style={nodeStyles?.horizontal_rule} />;
921
+ return (
922
+ <HorizontalRule
923
+ {...(nodeStyles?.horizontal_rule
924
+ ? { style: nodeStyles.horizontal_rule }
925
+ : {})}
926
+ />
927
+ );
901
928
 
902
929
  case "line_break":
903
930
  return <Text>{"\n"}</Text>;
@@ -910,7 +937,12 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
910
937
  if (!mathContent) return null;
911
938
  mathContent = mathContent.replace(/^\$+|\$+$/g, "").trim();
912
939
  return (
913
- <MathInline content={mathContent} style={nodeStyles?.math_inline} />
940
+ <MathInline
941
+ content={mathContent}
942
+ {...(nodeStyles?.math_inline
943
+ ? { style: nodeStyles.math_inline }
944
+ : {})}
945
+ />
914
946
  );
915
947
  }
916
948
 
@@ -918,7 +950,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
918
950
  return (
919
951
  <MathBlock
920
952
  content={getTextContent(node)}
921
- style={nodeStyles?.math_block}
953
+ {...(nodeStyles?.math_block ? { style: nodeStyles.math_block } : {})}
922
954
  />
923
955
  );
924
956
 
@@ -926,9 +958,9 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
926
958
  return (
927
959
  <List
928
960
  ordered={node.ordered ?? false}
929
- start={node.start}
930
961
  depth={depth}
931
- style={nodeStyles?.list}
962
+ {...(node.start === undefined ? {} : { start: node.start })}
963
+ {...(nodeStyles?.list ? { style: nodeStyles.list } : {})}
932
964
  >
933
965
  {node.children?.map((child, index) => {
934
966
  if (child.type === "task_list_item") {
@@ -968,7 +1000,9 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
968
1000
  return (
969
1001
  <TaskListItem
970
1002
  checked={node.checked ?? false}
971
- style={nodeStyles?.task_list_item}
1003
+ {...(nodeStyles?.task_list_item
1004
+ ? { style: nodeStyles.task_list_item }
1005
+ : {})}
972
1006
  >
973
1007
  {renderChildren(node.children, true, false)}
974
1008
  </TaskListItem>
@@ -979,7 +1013,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
979
1013
  <TableRenderer
980
1014
  node={node}
981
1015
  Renderer={NodeRenderer}
982
- style={nodeStyles?.table}
1016
+ {...(nodeStyles?.table ? { style: nodeStyles.table } : {})}
983
1017
  />
984
1018
  );
985
1019
 
@@ -140,8 +140,8 @@ export const Image: FC<ImageProps> = ({ url, title, alt, Renderer, style }) => {
140
140
  /picsum\.photos\/.*\/(\d+)\/(\d+)/,
141
141
  );
142
142
  if (picsumMatch) {
143
- const w = parseInt(picsumMatch[1], 10);
144
- const h = parseInt(picsumMatch[2], 10);
143
+ const w = parseInt(picsumMatch[1] ?? "", 10);
144
+ const h = parseInt(picsumMatch[2] ?? "", 10);
145
145
  if (!isNaN(w) && !isNaN(h) && h !== 0) {
146
146
  setAspectRatio(w / h);
147
147
  return () => {
@@ -265,8 +265,8 @@ export const MathInline: FC<MathInlineProps> = ({ content, style }) => {
265
265
  latex={content}
266
266
  fontSize={getInlineMathFontSize(content, theme)}
267
267
  displayMode={false}
268
- color={theme.colors.text}
269
268
  style={styles.ratexInline}
269
+ {...(theme.colors.text ? { color: theme.colors.text } : {})}
270
270
  onError={() => {
271
271
  if (!mountedRef.current) return;
272
272
  setHasRenderError(true);
@@ -314,8 +314,8 @@ export const MathBlock: FC<MathBlockProps> = ({ content, style }) => {
314
314
  latex={content}
315
315
  fontSize={theme.fontSizes.xl}
316
316
  displayMode
317
- color={theme.colors.text}
318
317
  style={styles.ratexBlock}
318
+ {...(theme.colors.text ? { color: theme.colors.text } : {})}
319
319
  onError={() => {
320
320
  if (!mountedRef.current) return;
321
321
  setHasRenderError(true);
@@ -181,19 +181,19 @@ export const TableRenderer: FC<TableRendererProps> = ({
181
181
  for (let col = 0; col < columnCount; col++) {
182
182
  const headerWidth = measuredWidths.current.get(`header-${col}`);
183
183
  if (headerWidth && headerWidth > 0) {
184
- maxWidths[col] = Math.max(maxWidths[col], headerWidth);
184
+ maxWidths[col] = Math.max(maxWidths[col] ?? 0, headerWidth);
185
185
  }
186
186
 
187
187
  for (let row = 0; row < rows.length; row++) {
188
- if (col >= rows[row].length) continue;
188
+ if (col >= (rows[row]?.length ?? 0)) continue;
189
189
  const cellWidth = measuredWidths.current.get(`cell-${row}-${col}`);
190
190
  if (cellWidth && cellWidth > 0) {
191
- maxWidths[col] = Math.max(maxWidths[col], cellWidth);
191
+ maxWidths[col] = Math.max(maxWidths[col] ?? 0, cellWidth);
192
192
  }
193
193
  }
194
194
 
195
195
  maxWidths[col] = Math.max(
196
- maxWidths[col] + COLUMN_MEASUREMENT_PADDING,
196
+ (maxWidths[col] ?? 0) + COLUMN_MEASUREMENT_PADDING,
197
197
  minColumnWidth,
198
198
  );
199
199
  }
@@ -59,7 +59,7 @@ export const estimateColumnWidths = (
59
59
  let maxChars = headerChars;
60
60
 
61
61
  for (let row = 0; row < rows.length; row++) {
62
- const cell = rows[row][col];
62
+ const cell = rows[row]?.[col];
63
63
  if (!cell) continue;
64
64
  const cellChars = Math.min(
65
65
  getTextContent(cell).trim().length,
@@ -182,7 +182,7 @@ export function defaultHighlighter(
182
182
  const lines = code.split("\n");
183
183
  const result: HighlightedToken[] = [];
184
184
  for (let i = 0; i < lines.length; i++) {
185
- result.push(...tokenizeLine(lines[i], language));
185
+ result.push(...tokenizeLine(lines[i] ?? "", language));
186
186
  if (i < lines.length - 1) {
187
187
  result.push({ text: "\n", type: "default" });
188
188
  }
@@ -24,7 +24,7 @@ const isInsideFencedCodeBlock = (text: string): boolean => {
24
24
  const fenceMatch = line.match(FENCE_LINE_PATTERN);
25
25
  if (!fenceMatch) continue;
26
26
 
27
- const marker = fenceMatch[1];
27
+ const marker = fenceMatch[1] ?? "";
28
28
  const markerChar = marker[0] as "`" | "~";
29
29
  const markerLength = marker.length;
30
30
 
@@ -94,7 +94,11 @@ const findTrailingLeafPath = (
94
94
  }
95
95
 
96
96
  const lastIndex = children.length - 1;
97
- return findTrailingLeafPath(children[lastIndex], [...path, lastIndex]);
97
+ const lastChild = children[lastIndex];
98
+ if (lastChild === undefined) {
99
+ return path;
100
+ }
101
+ return findTrailingLeafPath(lastChild, [...path, lastIndex]);
98
102
  };
99
103
 
100
104
  const getNodeAtPath = (
@@ -126,11 +130,14 @@ const appendPlainTextToAst = (
126
130
  const delta = appendedChunk.length;
127
131
  const update = (node: MarkdownNode, depth: number): MarkdownNode => {
128
132
  if (depth === path.length) {
129
- return {
133
+ const updatedNode: MarkdownNode = {
130
134
  ...node,
131
135
  content: (node.content ?? "") + appendedChunk,
132
- end: typeof node.end === "number" ? node.end + delta : node.end,
133
136
  };
137
+ if (typeof node.end === "number") {
138
+ updatedNode.end = node.end + delta;
139
+ }
140
+ return updatedNode;
134
141
  }
135
142
 
136
143
  const childIndex = path[depth];
@@ -138,11 +145,16 @@ const appendPlainTextToAst = (
138
145
  index === childIndex ? update(child, depth + 1) : child,
139
146
  );
140
147
 
141
- return {
148
+ const updatedNode: MarkdownNode = {
142
149
  ...node,
143
- end: typeof node.end === "number" ? node.end + delta : node.end,
144
- children,
145
150
  };
151
+ if (typeof node.end === "number") {
152
+ updatedNode.end = node.end + delta;
153
+ }
154
+ if (children) {
155
+ updatedNode.children = children;
156
+ }
157
+ return updatedNode;
146
158
  };
147
159
 
148
160
  return update(ast, 0);
@@ -206,8 +218,12 @@ export const reuseStableAstNodes = (
206
218
 
207
219
  let hasChildChange = false;
208
220
  const children = nextChildren.map((nextChild, index) => {
209
- const child = reuseStableAstNodes(previousChildren[index], nextChild);
210
- hasChildChange ||= child !== previousChildren[index];
221
+ const previousChild = previousChildren[index];
222
+ const child =
223
+ previousChild === undefined
224
+ ? nextChild
225
+ : reuseStableAstNodes(previousChild, nextChild);
226
+ hasChildChange ||= child !== previousChild;
211
227
  return child;
212
228
  });
213
229
 
@@ -26,14 +26,14 @@ const parseAbsoluteHref = (
26
26
  const protocolMatch = href.match(/^([a-z][a-z0-9+.-]*):/i);
27
27
  if (!protocolMatch) return null;
28
28
 
29
- const protocol = normalizeProtocol(protocolMatch[1]);
29
+ const protocol = normalizeProtocol(protocolMatch[1] ?? "");
30
30
  const rest = href.slice(protocolMatch[0].length);
31
31
  const authorityMatch = rest.match(/^\/\/([^/?#]*)/);
32
32
  const rawHost = authorityMatch?.[1] ?? "";
33
- const hostname = rawHost
33
+ const hostname = (rawHost
34
34
  .replace(/^[^@]*@/, "")
35
35
  .replace(/^\[|\]$/g, "")
36
- .split(":")[0]
36
+ .split(":")[0] ?? "")
37
37
  .toLowerCase();
38
38
 
39
39
  return { protocol, hostname };
@@ -19,15 +19,17 @@ export const createTimestampTimeline = (
19
19
  .map(Number)
20
20
  .filter((index) => Number.isFinite(index))
21
21
  .sort((a, b) => a - b)
22
- .map((index) => ({
23
- index,
24
- time: timestamps[index],
25
- }))
22
+ .flatMap((index) => {
23
+ const time = timestamps[index];
24
+ return time === undefined ? [] : [{ index, time }];
25
+ })
26
26
  .filter((entry) => Number.isFinite(entry.time));
27
27
 
28
28
  let monotonic = true;
29
29
  for (let i = 1; i < entries.length; i += 1) {
30
- if (entries[i].time < entries[i - 1].time) {
30
+ const current = entries[i];
31
+ const previous = entries[i - 1];
32
+ if (current && previous && current.time < previous.time) {
31
33
  monotonic = false;
32
34
  break;
33
35
  }
@@ -59,7 +61,8 @@ export const resolveHighlightPosition = (
59
61
 
60
62
  while (left <= right) {
61
63
  const mid = (left + right) >> 1;
62
- if (entries[mid].time <= currentTimeMs) {
64
+ const entry = entries[mid];
65
+ if (entry !== undefined && entry.time <= currentTimeMs) {
63
66
  resolvedIndex = mid;
64
67
  left = mid + 1;
65
68
  } else {
@@ -68,5 +71,5 @@ export const resolveHighlightPosition = (
68
71
  }
69
72
 
70
73
  if (resolvedIndex === -1) return 0;
71
- return entries[resolvedIndex].index + 1;
74
+ return (entries[resolvedIndex]?.index ?? 0) + 1;
72
75
  };