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

keplergl / kepler.gl / 25884645943

14 May 2026 08:43PM UTC coverage: 57.684% (-1.0%) from 58.684%
25884645943

push

github

web-flow
feat: basic annotations (#3434)

* feat: basic annotations

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

* fixes and improvements

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix annotations lag

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* tests, lint, fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* formatting/prettier

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* update icon from target to letters

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

* fix dragging

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* follow up

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes; follow ups

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

---------

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>
Co-authored-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

7158 of 14867 branches covered (48.15%)

Branch coverage included in aggregate %.

217 of 737 new or added lines in 25 files covered. (29.44%)

70 existing lines in 2 files now uncovered.

14556 of 22776 relevant lines covered (63.91%)

77.67 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