• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 25830234009

13 May 2026 10:31PM UTC coverage: 57.668% (-1.0%) from 58.644%
25830234009

Pull #3434

github

web-flow
Merge b10a56eba into b4790f0f5
Pull Request #3434: feat: basic annotations

7143 of 14848 branches covered (48.11%)

Branch coverage included in aggregate %.

216 of 732 new or added lines in 25 files covered. (29.51%)

74 existing lines in 3 files now uncovered.

14551 of 22771 relevant lines covered (63.9%)

77.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/src/annotations/lexical-editor/clickable-link-plugin.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
5
import {$getSelection, $isRangeSelection} from 'lexical';
6
import {useEffect} from 'react';
7

8
import type {LexicalEditor} from 'lexical';
9

10
export default function ClickableLinkPlugin({
11
  newTab = true
×
12
}: {
13
  newTab?: boolean;
14
}): JSX.Element | null {
NEW
15
  const [editor] = useLexicalComposerContext();
×
NEW
16
  useEffect(() => {
×
17
    function onClick(e: Event) {
NEW
18
      if (editor.isEditable()) {
×
NEW
19
        return;
×
20
      }
21

NEW
22
      const evt = e as MouseEvent;
×
NEW
23
      const linkDomNode = getLinkDomNode(evt, editor);
×
NEW
24
      if (!linkDomNode) {
×
NEW
25
        return;
×
26
      }
NEW
27
      const href = linkDomNode.getAttribute('href');
×
NEW
28
      if (linkDomNode.getAttribute('contenteditable') === 'false' || href == null) {
×
NEW
29
        return;
×
30
      }
NEW
31
      const selection = editor.getEditorState().read($getSelection);
×
NEW
32
      if ($isRangeSelection(selection) && !selection.isCollapsed()) {
×
NEW
33
        return;
×
34
      }
35

NEW
36
      if (href && newTab) {
×
NEW
37
        window.open(href, '_blank', 'noopener,noreferrer');
×
NEW
38
      } else if (href) {
×
NEW
39
        window.location.href = href;
×
40
      }
41
    }
NEW
42
    return editor.registerRootListener(
×
43
      (rootElement: null | HTMLElement, prevRootElement: null | HTMLElement) => {
NEW
44
        if (prevRootElement !== null) {
×
NEW
45
          prevRootElement.removeEventListener('click', onClick);
×
46
        }
NEW
47
        if (rootElement !== null) {
×
NEW
48
          rootElement.addEventListener('click', onClick);
×
49
        }
50
      }
51
    );
52
  }, [editor, newTab]);
NEW
53
  return null;
×
54
}
55

56
function isLinkDomNode(domNode: Node): boolean {
NEW
57
  return domNode.nodeName.toLowerCase() === 'a';
×
58
}
59

60
function getLinkDomNode(event: MouseEvent, editor: LexicalEditor): HTMLAnchorElement | null {
NEW
61
  return editor.getEditorState().read(() => {
×
NEW
62
    const domNode = event.target as Node;
×
NEW
63
    if (isLinkDomNode(domNode)) {
×
NEW
64
      return domNode as HTMLAnchorElement;
×
65
    }
NEW
66
    if (domNode.parentNode && isLinkDomNode(domNode.parentNode)) {
×
NEW
67
      return domNode.parentNode as HTMLAnchorElement;
×
68
    }
NEW
69
    return null;
×
70
  });
71
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc