@thangph2146/lexical-editor 0.0.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.
Files changed (183) hide show
  1. package/dist/editor-x/editor.cjs +33121 -0
  2. package/dist/editor-x/editor.cjs.map +1 -0
  3. package/dist/editor-x/editor.css +2854 -0
  4. package/dist/editor-x/editor.css.map +1 -0
  5. package/dist/editor-x/editor.d.cts +12 -0
  6. package/dist/editor-x/editor.d.ts +12 -0
  7. package/dist/editor-x/editor.js +33095 -0
  8. package/dist/editor-x/editor.js.map +1 -0
  9. package/dist/index.cjs +33210 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +2854 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.cts +15 -0
  14. package/dist/index.d.ts +15 -0
  15. package/dist/index.js +33183 -0
  16. package/dist/index.js.map +1 -0
  17. package/package.json +84 -0
  18. package/src/components/lexical-editor.tsx +123 -0
  19. package/src/context/editor-container-context.tsx +29 -0
  20. package/src/context/priority-image-context.tsx +7 -0
  21. package/src/context/toolbar-context.tsx +60 -0
  22. package/src/context/uploads-context.tsx +53 -0
  23. package/src/editor-hooks/use-debounce.ts +80 -0
  24. package/src/editor-hooks/use-modal.tsx +64 -0
  25. package/src/editor-hooks/use-report.ts +57 -0
  26. package/src/editor-hooks/use-update-toolbar.ts +41 -0
  27. package/src/editor-ui/broken-image.tsx +18 -0
  28. package/src/editor-ui/caption-composer.tsx +45 -0
  29. package/src/editor-ui/code-button.tsx +75 -0
  30. package/src/editor-ui/color-picker.tsx +2010 -0
  31. package/src/editor-ui/content-editable.tsx +37 -0
  32. package/src/editor-ui/hooks/use-image-caption-controls.ts +118 -0
  33. package/src/editor-ui/hooks/use-image-node-interactions.ts +245 -0
  34. package/src/editor-ui/hooks/use-responsive-image-dimensions.ts +202 -0
  35. package/src/editor-ui/image-component.tsx +321 -0
  36. package/src/editor-ui/image-placeholder.tsx +57 -0
  37. package/src/editor-ui/image-resizer.tsx +499 -0
  38. package/src/editor-ui/image-sizing.ts +120 -0
  39. package/src/editor-ui/lazy-image.tsx +136 -0
  40. package/src/editor-x/editor.tsx +117 -0
  41. package/src/editor-x/nodes.ts +79 -0
  42. package/src/editor-x/plugins.tsx +380 -0
  43. package/src/hooks/use-click-outside.ts +27 -0
  44. package/src/hooks/use-element-size.ts +54 -0
  45. package/src/hooks/use-header-height.ts +95 -0
  46. package/src/hooks/use-isomorphic-layout-effect.ts +4 -0
  47. package/src/index.ts +4 -0
  48. package/src/lib/logger.ts +6 -0
  49. package/src/lib/utils.ts +19 -0
  50. package/src/nodes/autocomplete-node.tsx +94 -0
  51. package/src/nodes/embeds/tweet-node.tsx +224 -0
  52. package/src/nodes/embeds/youtube-node.tsx +519 -0
  53. package/src/nodes/emoji-node.tsx +83 -0
  54. package/src/nodes/image-node.tsx +328 -0
  55. package/src/nodes/keyword-node.tsx +58 -0
  56. package/src/nodes/layout-container-node.tsx +128 -0
  57. package/src/nodes/layout-item-node.tsx +118 -0
  58. package/src/nodes/list-with-color-node.tsx +160 -0
  59. package/src/nodes/mention-node.ts +122 -0
  60. package/src/plugins/actions/actions-plugin.tsx +3 -0
  61. package/src/plugins/actions/character-limit-plugin.tsx +27 -0
  62. package/src/plugins/actions/clear-editor-plugin.tsx +70 -0
  63. package/src/plugins/actions/counter-character-plugin.tsx +80 -0
  64. package/src/plugins/actions/edit-mode-toggle-plugin.tsx +49 -0
  65. package/src/plugins/actions/import-export-plugin.tsx +61 -0
  66. package/src/plugins/actions/markdown-toggle-plugin.tsx +78 -0
  67. package/src/plugins/actions/max-length-plugin.tsx +59 -0
  68. package/src/plugins/actions/share-content-plugin.tsx +72 -0
  69. package/src/plugins/actions/speech-to-text-plugin.tsx +159 -0
  70. package/src/plugins/actions/tree-view-plugin.tsx +63 -0
  71. package/src/plugins/align-plugin.tsx +86 -0
  72. package/src/plugins/auto-link-plugin.tsx +34 -0
  73. package/src/plugins/autocomplete-plugin.tsx +2574 -0
  74. package/src/plugins/code-action-menu-plugin.tsx +240 -0
  75. package/src/plugins/code-highlight-plugin.tsx +22 -0
  76. package/src/plugins/component-picker-menu-plugin.tsx +427 -0
  77. package/src/plugins/context-menu-plugin.tsx +311 -0
  78. package/src/plugins/drag-drop-paste-plugin.tsx +52 -0
  79. package/src/plugins/draggable-block-plugin.tsx +50 -0
  80. package/src/plugins/embeds/auto-embed-plugin.tsx +324 -0
  81. package/src/plugins/embeds/twitter-plugin.tsx +45 -0
  82. package/src/plugins/embeds/youtube-plugin.tsx +84 -0
  83. package/src/plugins/emoji-picker-plugin.tsx +206 -0
  84. package/src/plugins/emojis-plugin.tsx +84 -0
  85. package/src/plugins/floating-link-editor-plugin.tsx +791 -0
  86. package/src/plugins/floating-text-format-plugin.tsx +710 -0
  87. package/src/plugins/images-plugin.tsx +671 -0
  88. package/src/plugins/keywords-plugin.tsx +59 -0
  89. package/src/plugins/layout-plugin.tsx +658 -0
  90. package/src/plugins/link-plugin.tsx +18 -0
  91. package/src/plugins/list-color-plugin.tsx +178 -0
  92. package/src/plugins/list-max-indent-level-plugin.tsx +85 -0
  93. package/src/plugins/mentions-plugin.tsx +714 -0
  94. package/src/plugins/picker/alignment-picker-plugin.tsx +40 -0
  95. package/src/plugins/picker/bulleted-list-picker-plugin.tsx +14 -0
  96. package/src/plugins/picker/check-list-picker-plugin.tsx +14 -0
  97. package/src/plugins/picker/code-picker-plugin.tsx +30 -0
  98. package/src/plugins/picker/columns-layout-picker-plugin.tsx +16 -0
  99. package/src/plugins/picker/component-picker-option.tsx +47 -0
  100. package/src/plugins/picker/divider-picker-plugin.tsx +14 -0
  101. package/src/plugins/picker/embeds-picker-plugin.tsx +24 -0
  102. package/src/plugins/picker/heading-picker-plugin.tsx +32 -0
  103. package/src/plugins/picker/image-picker-plugin.tsx +16 -0
  104. package/src/plugins/picker/numbered-list-picker-plugin.tsx +14 -0
  105. package/src/plugins/picker/paragraph-picker-plugin.tsx +20 -0
  106. package/src/plugins/picker/quote-picker-plugin.tsx +21 -0
  107. package/src/plugins/picker/table-picker-plugin.tsx +56 -0
  108. package/src/plugins/tab-focus-plugin.tsx +66 -0
  109. package/src/plugins/table-column-resizer-plugin.tsx +309 -0
  110. package/src/plugins/table-plugin.tsx +299 -0
  111. package/src/plugins/toolbar/block-format/block-format-data.tsx +69 -0
  112. package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -0
  113. package/src/plugins/toolbar/block-format/format-check-list.tsx +40 -0
  114. package/src/plugins/toolbar/block-format/format-code-block.tsx +45 -0
  115. package/src/plugins/toolbar/block-format/format-heading.tsx +34 -0
  116. package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -0
  117. package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -0
  118. package/src/plugins/toolbar/block-format/format-paragraph.tsx +31 -0
  119. package/src/plugins/toolbar/block-format/format-quote.tsx +32 -0
  120. package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +117 -0
  121. package/src/plugins/toolbar/block-insert/insert-columns-layout.tsx +32 -0
  122. package/src/plugins/toolbar/block-insert/insert-embeds.tsx +31 -0
  123. package/src/plugins/toolbar/block-insert/insert-horizontal-rule.tsx +30 -0
  124. package/src/plugins/toolbar/block-insert/insert-image.tsx +32 -0
  125. package/src/plugins/toolbar/block-insert/insert-table.tsx +32 -0
  126. package/src/plugins/toolbar/block-insert-plugin.tsx +30 -0
  127. package/src/plugins/toolbar/clear-formatting-toolbar-plugin.tsx +92 -0
  128. package/src/plugins/toolbar/code-language-toolbar-plugin.tsx +121 -0
  129. package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +251 -0
  130. package/src/plugins/toolbar/font-background-toolbar-plugin.tsx +179 -0
  131. package/src/plugins/toolbar/font-color-toolbar-plugin.tsx +101 -0
  132. package/src/plugins/toolbar/font-family-toolbar-plugin.tsx +91 -0
  133. package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +85 -0
  134. package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +177 -0
  135. package/src/plugins/toolbar/history-toolbar-plugin.tsx +87 -0
  136. package/src/plugins/toolbar/link-toolbar-plugin.tsx +90 -0
  137. package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +69 -0
  138. package/src/plugins/toolbar/toolbar-plugin.tsx +66 -0
  139. package/src/plugins/typing-pref-plugin.tsx +118 -0
  140. package/src/shared/can-use-dom.ts +4 -0
  141. package/src/shared/environment.ts +47 -0
  142. package/src/shared/invariant.ts +16 -0
  143. package/src/shared/use-layout-effect.ts +12 -0
  144. package/src/themes/_mixins.scss +107 -0
  145. package/src/themes/_variables.scss +33 -0
  146. package/src/themes/editor-theme.scss +622 -0
  147. package/src/themes/editor-theme.ts +118 -0
  148. package/src/themes/plugins.scss +1180 -0
  149. package/src/themes/ui-components.scss +936 -0
  150. package/src/transformers/markdown-emoji-transformer.ts +20 -0
  151. package/src/transformers/markdown-hr-transformer.ts +28 -0
  152. package/src/transformers/markdown-image-transformer.ts +31 -0
  153. package/src/transformers/markdown-list-transformer.ts +51 -0
  154. package/src/transformers/markdown-table-transformer.ts +200 -0
  155. package/src/transformers/markdown-tweet-transformer.ts +26 -0
  156. package/src/ui/button-group.tsx +10 -0
  157. package/src/ui/button.tsx +29 -0
  158. package/src/ui/collapsible.tsx +67 -0
  159. package/src/ui/command.tsx +48 -0
  160. package/src/ui/dialog.tsx +146 -0
  161. package/src/ui/flex.tsx +38 -0
  162. package/src/ui/input.tsx +20 -0
  163. package/src/ui/label.tsx +20 -0
  164. package/src/ui/popover.tsx +128 -0
  165. package/src/ui/scroll-area.tsx +17 -0
  166. package/src/ui/select.tsx +171 -0
  167. package/src/ui/separator.tsx +20 -0
  168. package/src/ui/slider.tsx +14 -0
  169. package/src/ui/slot.tsx +3 -0
  170. package/src/ui/tabs.tsx +87 -0
  171. package/src/ui/toggle-group.tsx +109 -0
  172. package/src/ui/toggle.tsx +28 -0
  173. package/src/ui/tooltip.tsx +28 -0
  174. package/src/ui/typography.tsx +44 -0
  175. package/src/utils/doc-serialization.ts +68 -0
  176. package/src/utils/emoji-list.ts +16604 -0
  177. package/src/utils/get-dom-range-rect.ts +20 -0
  178. package/src/utils/get-selected-node.ts +20 -0
  179. package/src/utils/is-mobile-width.ts +0 -0
  180. package/src/utils/set-floating-elem-position-for-link-editor.ts +39 -0
  181. package/src/utils/set-floating-elem-position.ts +44 -0
  182. package/src/utils/swipe.ts +119 -0
  183. package/src/utils/url.ts +32 -0
@@ -0,0 +1,20 @@
1
+ export function getDOMRangeRect(
2
+ nativeSelection: Selection,
3
+ rootElement: HTMLElement
4
+ ): DOMRect {
5
+ const domRange = nativeSelection.getRangeAt(0)
6
+
7
+ let rect
8
+
9
+ if (nativeSelection.anchorNode === rootElement) {
10
+ let inner = rootElement
11
+ while (inner.firstElementChild != null) {
12
+ inner = inner.firstElementChild as HTMLElement
13
+ }
14
+ rect = inner.getBoundingClientRect()
15
+ } else {
16
+ rect = domRange.getBoundingClientRect()
17
+ }
18
+
19
+ return rect
20
+ }
@@ -0,0 +1,20 @@
1
+ import { $isAtNodeEnd } from "@lexical/selection"
2
+ import { ElementNode, RangeSelection, TextNode } from "lexical"
3
+
4
+ export function getSelectedNode(
5
+ selection: RangeSelection
6
+ ): TextNode | ElementNode {
7
+ const anchor = selection.anchor
8
+ const focus = selection.focus
9
+ const anchorNode = selection.anchor.getNode()
10
+ const focusNode = selection.focus.getNode()
11
+ if (anchorNode === focusNode) {
12
+ return anchorNode
13
+ }
14
+ const isBackward = selection.isBackward()
15
+ if (isBackward) {
16
+ return $isAtNodeEnd(focus) ? anchorNode : focusNode
17
+ } else {
18
+ return $isAtNodeEnd(anchor) ? anchorNode : focusNode
19
+ }
20
+ }
File without changes
@@ -0,0 +1,39 @@
1
+ const VERTICAL_GAP = 10
2
+ const HORIZONTAL_OFFSET = 5
3
+
4
+ export function setFloatingElemPositionForLinkEditor(
5
+ targetRect: DOMRect | null,
6
+ floatingElem: HTMLElement,
7
+ anchorElem: HTMLElement,
8
+ verticalGap: number = VERTICAL_GAP,
9
+ horizontalOffset: number = HORIZONTAL_OFFSET
10
+ ): void {
11
+ const scrollerElem = anchorElem.parentElement
12
+
13
+ if (targetRect === null || !scrollerElem) {
14
+ floatingElem.style.opacity = "0"
15
+ floatingElem.style.transform = "translate(-10000px, -10000px)"
16
+ return
17
+ }
18
+
19
+ const floatingElemRect = floatingElem.getBoundingClientRect()
20
+ const anchorElementRect = anchorElem.getBoundingClientRect()
21
+ const editorScrollerRect = scrollerElem.getBoundingClientRect()
22
+
23
+ let top = targetRect.top - verticalGap
24
+ let left = targetRect.left - horizontalOffset
25
+
26
+ if (top < editorScrollerRect.top) {
27
+ top += floatingElemRect.height + targetRect.height + verticalGap * 2
28
+ }
29
+
30
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
31
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset
32
+ }
33
+
34
+ top -= anchorElementRect.top
35
+ left -= anchorElementRect.left
36
+
37
+ floatingElem.style.opacity = "1"
38
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`
39
+ }
@@ -0,0 +1,44 @@
1
+ const VERTICAL_GAP = 10
2
+ const HORIZONTAL_OFFSET = 5
3
+
4
+ export function setFloatingElemPosition(
5
+ targetRect: DOMRect | null,
6
+ floatingElem: HTMLElement,
7
+ anchorElem: HTMLElement,
8
+ isLink: boolean = false,
9
+ verticalGap: number = VERTICAL_GAP,
10
+ horizontalOffset: number = HORIZONTAL_OFFSET
11
+ ): void {
12
+ const scrollerElem = anchorElem.parentElement
13
+
14
+ if (targetRect === null || !scrollerElem) {
15
+ floatingElem.style.opacity = "0"
16
+ floatingElem.style.transform = "translate(-10000px, -10000px)"
17
+ return
18
+ }
19
+
20
+ const floatingElemRect = floatingElem.getBoundingClientRect()
21
+ const anchorElementRect = anchorElem.getBoundingClientRect()
22
+ const editorScrollerRect = scrollerElem.getBoundingClientRect()
23
+
24
+ let top = targetRect.top - floatingElemRect.height - verticalGap
25
+ let left = targetRect.left - horizontalOffset
26
+
27
+ if (top < editorScrollerRect.top) {
28
+ // adjusted height for link element if the element is at top
29
+ top +=
30
+ floatingElemRect.height +
31
+ targetRect.height +
32
+ verticalGap * (isLink ? 9 : 2)
33
+ }
34
+
35
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
36
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset
37
+ }
38
+
39
+ top -= anchorElementRect.top
40
+ left -= anchorElementRect.left
41
+
42
+ floatingElem.style.opacity = "1"
43
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`
44
+ }
@@ -0,0 +1,119 @@
1
+ type Force = [number, number]
2
+ type Listener = (force: Force, e: TouchEvent) => void
3
+ type ElementValues = {
4
+ start: null | Force
5
+ listeners: Set<Listener>
6
+ handleTouchstart: (e: TouchEvent) => void
7
+ handleTouchend: (e: TouchEvent) => void
8
+ }
9
+
10
+ const elements = new WeakMap<HTMLElement, ElementValues>()
11
+
12
+ function readTouch(e: TouchEvent): [number, number] | null {
13
+ const touch = e.changedTouches[0]
14
+ if (touch === undefined) {
15
+ return null
16
+ }
17
+ return [touch.clientX, touch.clientY]
18
+ }
19
+
20
+ function addListener(element: HTMLElement, cb: Listener): () => void {
21
+ let elementValues = elements.get(element)
22
+ if (elementValues === undefined) {
23
+ const listeners = new Set<Listener>()
24
+ const handleTouchstart = (e: TouchEvent) => {
25
+ if (elementValues !== undefined) {
26
+ elementValues.start = readTouch(e)
27
+ }
28
+ }
29
+ const handleTouchend = (e: TouchEvent) => {
30
+ if (elementValues === undefined) {
31
+ return
32
+ }
33
+ const start = elementValues.start
34
+ if (start === null) {
35
+ return
36
+ }
37
+ const end = readTouch(e)
38
+ for (const listener of Array.from(listeners)) {
39
+ if (end !== null) {
40
+ listener([end[0] - start[0], end[1] - start[1]], e)
41
+ }
42
+ }
43
+ }
44
+ element.addEventListener("touchstart", handleTouchstart, { passive: true })
45
+ element.addEventListener("touchend", handleTouchend, { passive: true })
46
+
47
+ elementValues = {
48
+ handleTouchend,
49
+ handleTouchstart,
50
+ listeners,
51
+ start: null,
52
+ }
53
+ elements.set(element, elementValues)
54
+ }
55
+ elementValues.listeners.add(cb)
56
+ return () => deleteListener(element, cb)
57
+ }
58
+
59
+ function deleteListener(element: HTMLElement, cb: Listener): void {
60
+ const elementValues = elements.get(element)
61
+ if (elementValues === undefined) {
62
+ return
63
+ }
64
+ const listeners = elementValues.listeners
65
+ listeners.delete(cb)
66
+ if (listeners.size === 0) {
67
+ elements.delete(element)
68
+ element.removeEventListener("touchstart", elementValues.handleTouchstart)
69
+ element.removeEventListener("touchend", elementValues.handleTouchend)
70
+ }
71
+ }
72
+
73
+ export function addSwipeLeftListener(
74
+ element: HTMLElement,
75
+ cb: (_force: number, e: TouchEvent) => void
76
+ ) {
77
+ return addListener(element, (force, e) => {
78
+ const [x, y] = force
79
+ if (x < 0 && -x > Math.abs(y)) {
80
+ cb(x, e)
81
+ }
82
+ })
83
+ }
84
+
85
+ export function addSwipeRightListener(
86
+ element: HTMLElement,
87
+ cb: (_force: number, e: TouchEvent) => void
88
+ ) {
89
+ return addListener(element, (force, e) => {
90
+ const [x, y] = force
91
+ if (x > 0 && x > Math.abs(y)) {
92
+ cb(x, e)
93
+ }
94
+ })
95
+ }
96
+
97
+ export function addSwipeUpListener(
98
+ element: HTMLElement,
99
+ cb: (_force: number, e: TouchEvent) => void
100
+ ) {
101
+ return addListener(element, (force, e) => {
102
+ const [x, y] = force
103
+ if (y < 0 && -y > Math.abs(x)) {
104
+ cb(x, e)
105
+ }
106
+ })
107
+ }
108
+
109
+ export function addSwipeDownListener(
110
+ element: HTMLElement,
111
+ cb: (_force: number, e: TouchEvent) => void
112
+ ) {
113
+ return addListener(element, (force, e) => {
114
+ const [x, y] = force
115
+ if (y > 0 && y > Math.abs(x)) {
116
+ cb(x, e)
117
+ }
118
+ })
119
+ }
@@ -0,0 +1,32 @@
1
+ const SUPPORTED_URL_PROTOCOLS = new Set([
2
+ "http:",
3
+ "https:",
4
+ "mailto:",
5
+ "sms:",
6
+ "tel:",
7
+ ])
8
+
9
+ export function sanitizeUrl(url: string): string {
10
+ try {
11
+ const parsedUrl = new URL(url)
12
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {
13
+ return "about:blank"
14
+ }
15
+ } catch {
16
+ return url
17
+ }
18
+ return url
19
+ }
20
+
21
+ // Source: https://stackoverflow.com/a/8234912/2013580
22
+ const urlRegExp = new RegExp(
23
+ /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
24
+ )
25
+ export function validateUrl(url: string): boolean {
26
+ // Reject invalid URLs like "https://" (incomplete URLs)
27
+ if (!url || url === "https://" || url === "http://") {
28
+ return false
29
+ }
30
+ // Validate using regex pattern
31
+ return urlRegExp.test(url)
32
+ }