@thangph2146/lexical-editor 0.0.5 → 0.0.7
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.
- package/README.md +47 -0
- package/dist/editor-x/editor.cjs +610 -493
- package/dist/editor-x/editor.cjs.map +1 -1
- package/dist/editor-x/editor.css +157 -69
- package/dist/editor-x/editor.css.map +1 -1
- package/dist/editor-x/editor.d.cts +2 -1
- package/dist/editor-x/editor.d.ts +2 -1
- package/dist/editor-x/editor.js +350 -233
- package/dist/editor-x/editor.js.map +1 -1
- package/dist/index.cjs +620 -501
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +157 -69
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +354 -235
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/components/lexical-editor.tsx +2 -0
- package/src/editor-x/editor.tsx +4 -2
- package/src/editor-x/plugins.tsx +7 -6
- package/src/plugins/images-plugin.tsx +3 -2
- package/src/plugins/layout-plugin.tsx +96 -61
- package/src/themes/_mixins.scss +12 -7
- package/src/themes/_variables.scss +2 -1
- package/src/themes/core/_reset.scss +10 -6
- package/src/themes/plugins/_color-picker.scss +1 -0
- package/src/themes/plugins/_layout.scss +3 -7
- package/src/themes/plugins/_list-color.scss +2 -0
- package/src/themes/plugins/_toolbar.scss +7 -7
- package/src/themes/ui-components/_button.scss +3 -3
- package/src/themes/ui-components/_flex.scss +2 -0
- package/src/themes/ui-components/_number-input.scss +81 -0
- package/src/themes/ui-components/_text-utilities.scss +1 -1
- package/src/themes/ui-components.scss +1 -0
- package/src/ui/flex.tsx +9 -2
- package/src/ui/number-input.tsx +104 -0
- package/src/themes/editor-theme copy.scss +0 -763
- package/src/themes/plugins copy.scss +0 -656
- package/src/themes/ui-components copy.scss +0 -1335
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
@use "../variables" as *;
|
|
2
|
+
@use "../mixins" as *;
|
|
3
|
+
|
|
4
|
+
.editor-number-input-container {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
border: $editor-border-width solid var(--border);
|
|
8
|
+
border-radius: $editor-border-radius;
|
|
9
|
+
background-color: var(--background);
|
|
10
|
+
height: $editor-control-size-xl; // h-11 to match color picker trigger
|
|
11
|
+
transition: border-color $editor-transition-normal;
|
|
12
|
+
padding: 2px; // Small padding to make inner buttons look nested
|
|
13
|
+
gap: 2px;
|
|
14
|
+
|
|
15
|
+
&:focus-within {
|
|
16
|
+
border-color: var(--ring);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.editor-number-input-btn {
|
|
21
|
+
height: 100% !important;
|
|
22
|
+
width: $editor-control-size-lg !important;
|
|
23
|
+
@include rounded-md; // Increase rounding to match container more closely
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
color: var(--muted-foreground);
|
|
26
|
+
border: none !important;
|
|
27
|
+
box-shadow: none !important;
|
|
28
|
+
|
|
29
|
+
&:hover:not(:disabled) {
|
|
30
|
+
background-color: var(--accent) !important;
|
|
31
|
+
color: var(--accent-foreground) !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:active:not(:disabled) {
|
|
35
|
+
background-color: var(--accent-muted) !important;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.editor-number-input-wrapper {
|
|
40
|
+
position: relative;
|
|
41
|
+
flex: 1;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
height: 100%;
|
|
46
|
+
@include rounded-md;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.editor-number-input-field {
|
|
51
|
+
border: none !important;
|
|
52
|
+
border-radius: 0 !important;
|
|
53
|
+
height: 100% !important;
|
|
54
|
+
text-align: center;
|
|
55
|
+
padding-right: 28px !important; // space for unit
|
|
56
|
+
box-shadow: none !important;
|
|
57
|
+
background: transparent !important;
|
|
58
|
+
|
|
59
|
+
&:focus {
|
|
60
|
+
box-shadow: none !important; // Don't show inner focus ring, container handles it
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&::-webkit-inner-spin-button,
|
|
64
|
+
&::-webkit-outer-spin-button {
|
|
65
|
+
-webkit-appearance: none;
|
|
66
|
+
margin: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
-moz-appearance: textfield;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.editor-number-input-unit {
|
|
73
|
+
position: absolute;
|
|
74
|
+
right: 10px;
|
|
75
|
+
top: 50%;
|
|
76
|
+
transform: translateY(-50%);
|
|
77
|
+
font-size: $editor-font-size-xs;
|
|
78
|
+
color: var(--muted-foreground);
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
font-family: $editor-font-family-mono;
|
|
81
|
+
}
|
package/src/ui/flex.tsx
CHANGED
|
@@ -11,10 +11,16 @@ export interface FlexProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
11
11
|
|
|
12
12
|
export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
|
|
13
13
|
({ className, align, justify, direction, wrap, gap, style, ...props }, ref) => {
|
|
14
|
+
const isStandardGap =
|
|
15
|
+
typeof gap === "number" && [0.5, 1, 1.5, 2, 3, 4, 5].includes(gap)
|
|
16
|
+
const gapClass = isStandardGap
|
|
17
|
+
? `editor-gap-${gap.toString().replace(".", "-")}`
|
|
18
|
+
: undefined
|
|
19
|
+
|
|
14
20
|
const gapStyle =
|
|
15
|
-
typeof gap === "number"
|
|
21
|
+
!isStandardGap && typeof gap === "number"
|
|
16
22
|
? { gap: `${gap * 0.25}rem` }
|
|
17
|
-
: typeof gap !== "undefined"
|
|
23
|
+
: !isStandardGap && typeof gap !== "undefined"
|
|
18
24
|
? { gap }
|
|
19
25
|
: undefined
|
|
20
26
|
|
|
@@ -27,6 +33,7 @@ export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
|
|
|
27
33
|
justify && `editor-justify-${justify}`,
|
|
28
34
|
direction && `editor-flex-${direction}`,
|
|
29
35
|
wrap && `editor-flex-${wrap}`,
|
|
36
|
+
gapClass,
|
|
30
37
|
className
|
|
31
38
|
)}
|
|
32
39
|
style={{ ...gapStyle, ...style }}
|
|
@@ -0,0 +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"
|