@thangph2146/lexical-editor 0.0.11 → 0.0.13

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 (46) hide show
  1. package/README.md +2 -1
  2. package/dist/editor-x/editor.cjs +280 -20
  3. package/dist/editor-x/editor.cjs.map +1 -1
  4. package/dist/editor-x/editor.css +27 -4
  5. package/dist/editor-x/editor.css.map +1 -1
  6. package/dist/editor-x/editor.js +281 -21
  7. package/dist/editor-x/editor.js.map +1 -1
  8. package/dist/index.cjs +292 -23
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.css +27 -4
  11. package/dist/index.css.map +1 -1
  12. package/dist/index.d.cts +26 -1
  13. package/dist/index.d.ts +26 -1
  14. package/dist/index.js +293 -24
  15. package/dist/index.js.map +1 -1
  16. package/package.json +1 -1
  17. package/src/components/lexical-editor.tsx +19 -6
  18. package/src/context/uploads-context.tsx +1 -0
  19. package/src/editor-ui/content-editable.tsx +18 -2
  20. package/src/editor-x/nodes.ts +2 -0
  21. package/src/nodes/download-link-node.tsx +118 -0
  22. package/src/plugins/floating-link-editor-plugin.tsx +338 -91
  23. package/src/themes/core/_tables.scss +0 -1
  24. package/src/themes/plugins/_floating-link-editor.scss +28 -2
  25. package/src/themes/ui-components/_button.scss +1 -1
  26. package/src/themes/ui-components/_flex.scss +1 -0
  27. package/src/ui/button-group.tsx +10 -10
  28. package/src/ui/button.tsx +38 -38
  29. package/src/ui/collapsible.tsx +67 -67
  30. package/src/ui/command.tsx +48 -48
  31. package/src/ui/dialog.tsx +146 -146
  32. package/src/ui/flex.tsx +45 -45
  33. package/src/ui/input.tsx +20 -20
  34. package/src/ui/label.tsx +20 -20
  35. package/src/ui/number-input.tsx +104 -104
  36. package/src/ui/popover.tsx +128 -128
  37. package/src/ui/scroll-area.tsx +17 -17
  38. package/src/ui/select.tsx +171 -171
  39. package/src/ui/separator.tsx +20 -20
  40. package/src/ui/slider.tsx +14 -14
  41. package/src/ui/slot.tsx +3 -3
  42. package/src/ui/tabs.tsx +87 -87
  43. package/src/ui/toggle-group.tsx +109 -109
  44. package/src/ui/toggle.tsx +28 -28
  45. package/src/ui/tooltip.tsx +28 -28
  46. package/src/ui/typography.tsx +44 -44
@@ -1,104 +1,104 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { Minus, Plus } from "lucide-react"
5
- import { cn } from "../lib/utils"
6
- import { Button } from "./button"
7
- import { Input } from "./input"
8
-
9
- export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
10
- value: number
11
- onValueChange: (value: number) => void
12
- min?: number
13
- max?: number
14
- step?: number
15
- unit?: string
16
- }
17
-
18
- export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
19
- ({ value, onValueChange, min = 0, max = 100, step = 1, unit = "px", className, ...props }, ref) => {
20
- const timerRef = React.useRef<NodeJS.Timeout | null>(null)
21
- const intervalRef = React.useRef<NodeJS.Timeout | null>(null)
22
- const valueRef = React.useRef(value)
23
-
24
- // Sync ref with current value for interval closure
25
- React.useEffect(() => {
26
- valueRef.current = value
27
- }, [value])
28
-
29
- const updateValue = React.useCallback((delta: number) => {
30
- onValueChange(Math.min(Math.max(valueRef.current + delta, min), max))
31
- }, [onValueChange, min, max])
32
-
33
- const startLongPress = (delta: number) => {
34
- updateValue(delta)
35
- timerRef.current = setTimeout(() => {
36
- intervalRef.current = setInterval(() => {
37
- updateValue(delta)
38
- }, 50)
39
- }, 300) // Reduced initial delay to 300ms
40
- }
41
-
42
- const stopLongPress = React.useCallback(() => {
43
- if (timerRef.current) clearTimeout(timerRef.current)
44
- if (intervalRef.current) clearInterval(intervalRef.current)
45
- }, [])
46
-
47
- const handleBlur = () => {
48
- onValueChange(Math.min(Math.max(value, min), max))
49
- }
50
-
51
- React.useEffect(() => {
52
- return () => stopLongPress()
53
- }, [stopLongPress])
54
-
55
- return (
56
- <div className={cn("editor-number-input-container", className)}>
57
- <Button
58
- variant="ghost"
59
- size="icon"
60
- className="editor-number-input-btn"
61
- onMouseDown={() => startLongPress(-step)}
62
- onMouseUp={stopLongPress}
63
- onMouseLeave={stopLongPress}
64
- onTouchStart={() => startLongPress(-step)}
65
- onTouchEnd={stopLongPress}
66
- tabIndex={-1} // Prevent tabbing into buttons
67
- >
68
- <Minus size={14} />
69
- </Button>
70
- <div className="editor-number-input-wrapper">
71
- <Input
72
- {...props}
73
- ref={ref}
74
- type="number"
75
- value={value}
76
- onBlur={handleBlur}
77
- onChange={(e) => {
78
- const val = parseInt(e.target.value, 10)
79
- if (!isNaN(val)) onValueChange(val)
80
- else onValueChange(0) // Default to 0 for invalid input while typing
81
- }}
82
- className="editor-number-input-field"
83
- />
84
- {unit && <span className="editor-number-input-unit">{unit}</span>}
85
- </div>
86
- <Button
87
- variant="ghost"
88
- size="icon"
89
- className="editor-number-input-btn"
90
- onMouseDown={() => startLongPress(step)}
91
- onMouseUp={stopLongPress}
92
- onMouseLeave={stopLongPress}
93
- onTouchStart={() => startLongPress(step)}
94
- onTouchEnd={stopLongPress}
95
- tabIndex={-1}
96
- >
97
- <Plus size={14} />
98
- </Button>
99
- </div>
100
- )
101
- }
102
- )
103
-
104
- NumberInput.displayName = "NumberInput"
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Minus, Plus } from "lucide-react"
5
+ import { cn } from "../lib/utils"
6
+ import { Button } from "./button"
7
+ import { Input } from "./input"
8
+
9
+ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
10
+ value: number
11
+ onValueChange: (value: number) => void
12
+ min?: number
13
+ max?: number
14
+ step?: number
15
+ unit?: string
16
+ }
17
+
18
+ export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
19
+ ({ value, onValueChange, min = 0, max = 100, step = 1, unit = "px", className, ...props }, ref) => {
20
+ const timerRef = React.useRef<NodeJS.Timeout | null>(null)
21
+ const intervalRef = React.useRef<NodeJS.Timeout | null>(null)
22
+ const valueRef = React.useRef(value)
23
+
24
+ // Sync ref with current value for interval closure
25
+ React.useEffect(() => {
26
+ valueRef.current = value
27
+ }, [value])
28
+
29
+ const updateValue = React.useCallback((delta: number) => {
30
+ onValueChange(Math.min(Math.max(valueRef.current + delta, min), max))
31
+ }, [onValueChange, min, max])
32
+
33
+ const startLongPress = (delta: number) => {
34
+ updateValue(delta)
35
+ timerRef.current = setTimeout(() => {
36
+ intervalRef.current = setInterval(() => {
37
+ updateValue(delta)
38
+ }, 50)
39
+ }, 300) // Reduced initial delay to 300ms
40
+ }
41
+
42
+ const stopLongPress = React.useCallback(() => {
43
+ if (timerRef.current) clearTimeout(timerRef.current)
44
+ if (intervalRef.current) clearInterval(intervalRef.current)
45
+ }, [])
46
+
47
+ const handleBlur = () => {
48
+ onValueChange(Math.min(Math.max(value, min), max))
49
+ }
50
+
51
+ React.useEffect(() => {
52
+ return () => stopLongPress()
53
+ }, [stopLongPress])
54
+
55
+ return (
56
+ <div className={cn("editor-number-input-container", className)}>
57
+ <Button
58
+ variant="ghost"
59
+ size="icon"
60
+ className="editor-number-input-btn"
61
+ onMouseDown={() => startLongPress(-step)}
62
+ onMouseUp={stopLongPress}
63
+ onMouseLeave={stopLongPress}
64
+ onTouchStart={() => startLongPress(-step)}
65
+ onTouchEnd={stopLongPress}
66
+ tabIndex={-1} // Prevent tabbing into buttons
67
+ >
68
+ <Minus size={14} />
69
+ </Button>
70
+ <div className="editor-number-input-wrapper">
71
+ <Input
72
+ {...props}
73
+ ref={ref}
74
+ type="number"
75
+ value={value}
76
+ onBlur={handleBlur}
77
+ onChange={(e) => {
78
+ const val = parseInt(e.target.value, 10)
79
+ if (!isNaN(val)) onValueChange(val)
80
+ else onValueChange(0) // Default to 0 for invalid input while typing
81
+ }}
82
+ className="editor-number-input-field"
83
+ />
84
+ {unit && <span className="editor-number-input-unit">{unit}</span>}
85
+ </div>
86
+ <Button
87
+ variant="ghost"
88
+ size="icon"
89
+ className="editor-number-input-btn"
90
+ onMouseDown={() => startLongPress(step)}
91
+ onMouseUp={stopLongPress}
92
+ onMouseLeave={stopLongPress}
93
+ onTouchStart={() => startLongPress(step)}
94
+ onTouchEnd={stopLongPress}
95
+ tabIndex={-1}
96
+ >
97
+ <Plus size={14} />
98
+ </Button>
99
+ </div>
100
+ )
101
+ }
102
+ )
103
+
104
+ NumberInput.displayName = "NumberInput"
@@ -1,128 +1,128 @@
1
- import * as React from "react"
2
- import { createPortal } from "react-dom"
3
- import { cn } from "../lib/utils"
4
-
5
- interface PopoverContextValue {
6
- open: boolean
7
- setOpen: (open: boolean) => void
8
- triggerRef: React.RefObject<HTMLButtonElement | null>
9
- modal?: boolean
10
- }
11
-
12
- const PopoverContext = React.createContext<PopoverContextValue | null>(null)
13
-
14
- export function Popover({ children, open: controlledOpen, defaultOpen = false, onOpenChange, modal = false }: {
15
- children: React.ReactNode
16
- open?: boolean
17
- defaultOpen?: boolean
18
- onOpenChange?: (open: boolean) => void
19
- modal?: boolean
20
- }) {
21
- const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
22
- const triggerRef = React.useRef<HTMLButtonElement>(null)
23
-
24
- const open = controlledOpen ?? uncontrolledOpen
25
- const setOpen = onOpenChange ?? setUncontrolledOpen
26
-
27
- const contextValue = React.useMemo(() => ({
28
- open, setOpen, triggerRef, modal
29
- }), [open, setOpen, modal])
30
-
31
- return (
32
- <PopoverContext.Provider value={contextValue}>
33
- {children}
34
- </PopoverContext.Provider>
35
- )
36
- }
37
-
38
- export function PopoverTrigger({ children, asChild, ...props }: React.HTMLAttributes<HTMLElement> & { asChild?: boolean, children: React.ReactNode, disabled?: boolean }) {
39
- const context = React.useContext(PopoverContext)
40
- if (!context) throw new Error("PopoverTrigger must be used within Popover")
41
-
42
- const { triggerRef, open, setOpen } = context
43
-
44
- const handleClick = (e: React.MouseEvent<HTMLElement>) => {
45
- setOpen(!open)
46
- if (React.isValidElement(children)) {
47
- (children as React.ReactElement<{ onClick?: React.MouseEventHandler<HTMLElement> }>).props.onClick?.(e)
48
- }
49
- }
50
-
51
- if (asChild && React.isValidElement(children)) {
52
- // eslint-disable-next-line react-hooks/refs
53
- return React.cloneElement(children as React.ReactElement<React.HTMLAttributes<HTMLElement> & { ref?: React.Ref<HTMLElement> }>, {
54
- ref: triggerRef,
55
- onClick: handleClick,
56
- ...props
57
- })
58
- }
59
-
60
- return (
61
- <button
62
- type="button"
63
- ref={triggerRef}
64
- onClick={handleClick}
65
- {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
66
- >
67
- {children}
68
- </button>
69
- )
70
- }
71
-
72
- export function PopoverContent({ children, className, align = "center", sideOffset = 4, ...props }: React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end", sideOffset?: number }) {
73
- const context = React.useContext(PopoverContext)
74
- const [position, setPosition] = React.useState({ top: 0, left: 0 })
75
- const contentRef = React.useRef<HTMLDivElement>(null)
76
-
77
- React.useEffect(() => {
78
- if (context?.open && context.triggerRef.current) {
79
- const rect = context.triggerRef.current.getBoundingClientRect()
80
- // Simple positioning logic (bottom-center or bottom-left)
81
- // Real popover needs collision detection, but this is "lite"
82
- let left = rect.left
83
- if (align === "center") {
84
- left = rect.left + rect.width / 2 // Will need to subtract half content width after render
85
- } else if (align === "end") {
86
- left = rect.right
87
- }
88
-
89
- setPosition({
90
- top: rect.bottom + window.scrollY + sideOffset,
91
- left: left + window.scrollX
92
- })
93
-
94
- const handleClickOutside = (e: MouseEvent) => {
95
- if (contentRef.current && !contentRef.current.contains(e.target as Node) &&
96
- !context.triggerRef.current?.contains(e.target as Node)) {
97
- context.setOpen(false)
98
- }
99
- }
100
- document.addEventListener("mousedown", handleClickOutside)
101
- return () => document.removeEventListener("mousedown", handleClickOutside)
102
- }
103
- }, [context, context?.open, align, sideOffset])
104
-
105
- if (!context?.open) return null
106
-
107
- return createPortal(
108
- <div
109
- ref={contentRef}
110
- className={cn("editor-popover-content", className)}
111
- style={{
112
- position: "absolute",
113
- top: position.top,
114
- left: position.left,
115
- zIndex: 9999
116
- }}
117
- {...props}
118
- >
119
- {children}
120
- </div>,
121
- document.body
122
- )
123
- }
124
-
125
- export function PopoverPortal({ children, container }: { children: React.ReactNode, container?: HTMLElement | null }) {
126
- if (!container) return <>{children}</>
127
- return createPortal(children, container)
128
- }
1
+ import * as React from "react"
2
+ import { createPortal } from "react-dom"
3
+ import { cn } from "../lib/utils"
4
+
5
+ interface PopoverContextValue {
6
+ open: boolean
7
+ setOpen: (open: boolean) => void
8
+ triggerRef: React.RefObject<HTMLButtonElement | null>
9
+ modal?: boolean
10
+ }
11
+
12
+ const PopoverContext = React.createContext<PopoverContextValue | null>(null)
13
+
14
+ export function Popover({ children, open: controlledOpen, defaultOpen = false, onOpenChange, modal = false }: {
15
+ children: React.ReactNode
16
+ open?: boolean
17
+ defaultOpen?: boolean
18
+ onOpenChange?: (open: boolean) => void
19
+ modal?: boolean
20
+ }) {
21
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
22
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
23
+
24
+ const open = controlledOpen ?? uncontrolledOpen
25
+ const setOpen = onOpenChange ?? setUncontrolledOpen
26
+
27
+ const contextValue = React.useMemo(() => ({
28
+ open, setOpen, triggerRef, modal
29
+ }), [open, setOpen, modal])
30
+
31
+ return (
32
+ <PopoverContext.Provider value={contextValue}>
33
+ {children}
34
+ </PopoverContext.Provider>
35
+ )
36
+ }
37
+
38
+ export function PopoverTrigger({ children, asChild, ...props }: React.HTMLAttributes<HTMLElement> & { asChild?: boolean, children: React.ReactNode, disabled?: boolean }) {
39
+ const context = React.useContext(PopoverContext)
40
+ if (!context) throw new Error("PopoverTrigger must be used within Popover")
41
+
42
+ const { triggerRef, open, setOpen } = context
43
+
44
+ const handleClick = (e: React.MouseEvent<HTMLElement>) => {
45
+ setOpen(!open)
46
+ if (React.isValidElement(children)) {
47
+ (children as React.ReactElement<{ onClick?: React.MouseEventHandler<HTMLElement> }>).props.onClick?.(e)
48
+ }
49
+ }
50
+
51
+ if (asChild && React.isValidElement(children)) {
52
+ // eslint-disable-next-line react-hooks/refs
53
+ return React.cloneElement(children as React.ReactElement<React.HTMLAttributes<HTMLElement> & { ref?: React.Ref<HTMLElement> }>, {
54
+ ref: triggerRef,
55
+ onClick: handleClick,
56
+ ...props
57
+ })
58
+ }
59
+
60
+ return (
61
+ <button
62
+ type="button"
63
+ ref={triggerRef}
64
+ onClick={handleClick}
65
+ {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
66
+ >
67
+ {children}
68
+ </button>
69
+ )
70
+ }
71
+
72
+ export function PopoverContent({ children, className, align = "center", sideOffset = 4, ...props }: React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end", sideOffset?: number }) {
73
+ const context = React.useContext(PopoverContext)
74
+ const [position, setPosition] = React.useState({ top: 0, left: 0 })
75
+ const contentRef = React.useRef<HTMLDivElement>(null)
76
+
77
+ React.useEffect(() => {
78
+ if (context?.open && context.triggerRef.current) {
79
+ const rect = context.triggerRef.current.getBoundingClientRect()
80
+ // Simple positioning logic (bottom-center or bottom-left)
81
+ // Real popover needs collision detection, but this is "lite"
82
+ let left = rect.left
83
+ if (align === "center") {
84
+ left = rect.left + rect.width / 2 // Will need to subtract half content width after render
85
+ } else if (align === "end") {
86
+ left = rect.right
87
+ }
88
+
89
+ setPosition({
90
+ top: rect.bottom + window.scrollY + sideOffset,
91
+ left: left + window.scrollX
92
+ })
93
+
94
+ const handleClickOutside = (e: MouseEvent) => {
95
+ if (contentRef.current && !contentRef.current.contains(e.target as Node) &&
96
+ !context.triggerRef.current?.contains(e.target as Node)) {
97
+ context.setOpen(false)
98
+ }
99
+ }
100
+ document.addEventListener("mousedown", handleClickOutside)
101
+ return () => document.removeEventListener("mousedown", handleClickOutside)
102
+ }
103
+ }, [context, context?.open, align, sideOffset])
104
+
105
+ if (!context?.open) return null
106
+
107
+ return createPortal(
108
+ <div
109
+ ref={contentRef}
110
+ className={cn("editor-popover-content", className)}
111
+ style={{
112
+ position: "absolute",
113
+ top: position.top,
114
+ left: position.left,
115
+ zIndex: 9999
116
+ }}
117
+ {...props}
118
+ >
119
+ {children}
120
+ </div>,
121
+ document.body
122
+ )
123
+ }
124
+
125
+ export function PopoverPortal({ children, container }: { children: React.ReactNode, container?: HTMLElement | null }) {
126
+ if (!container) return <>{children}</>
127
+ return createPortal(children, container)
128
+ }
@@ -1,17 +1,17 @@
1
- import * as React from "react"
2
- import { cn } from "../lib/utils"
3
-
4
- export const ScrollArea = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
- ({ className, children, ...props }, ref) => (
6
- <div
7
- ref={ref}
8
- className={cn("editor-scroll-area-viewport", className)}
9
- {...props}
10
- >
11
- {children}
12
- </div>
13
- )
14
- )
15
- ScrollArea.displayName = "ScrollArea"
16
-
17
- export const ScrollBar = () => null // No-op for now, or use custom scrollbar styles
1
+ import * as React from "react"
2
+ import { cn } from "../lib/utils"
3
+
4
+ export const ScrollArea = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, children, ...props }, ref) => (
6
+ <div
7
+ ref={ref}
8
+ className={cn("editor-scroll-area-viewport", className)}
9
+ {...props}
10
+ >
11
+ {children}
12
+ </div>
13
+ )
14
+ )
15
+ ScrollArea.displayName = "ScrollArea"
16
+
17
+ export const ScrollBar = () => null // No-op for now, or use custom scrollbar styles