dyelight 1.0.0
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/LICENSE.md +9 -0
- package/README.md +280 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ragaeeb Haq
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# DyeLight
|
|
2
|
+
|
|
3
|
+
A lightweight TypeScript React component for highlighting characters in textareas with powerful text annotation capabilities.
|
|
4
|
+
|
|
5
|
+
[](https://wakatime.com/badge/user/a0b906ce-b8e7-4463-8bce-383238df6d4b/project/897368ef-62b9-48be-bba9-7c530f10e3da)
|
|
6
|
+

|
|
7
|
+
[](https://github.com/ragaeeb/dyelight/actions/workflows/build.yml)
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
[](https://bundlejs.com/?q=dyelight%40latest)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+

|
|
15
|
+
[](https://www.npmjs.com/package/dyelight)
|
|
16
|
+
[](https://opensource.org/licenses/MIT)
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Absolute Position Highlighting**: Highlight text using simple start/end positions across the entire text
|
|
21
|
+
- **Multi-line Support**: Highlights automatically span across line breaks
|
|
22
|
+
- **Line-level Highlighting**: Optional background highlighting for entire lines
|
|
23
|
+
- **Pattern Matching**: Built-in support for regex and keyword highlighting
|
|
24
|
+
- **Auto-resize**: Automatic textarea height adjustment based on content
|
|
25
|
+
- **TypeScript**: Full TypeScript support with comprehensive type definitions
|
|
26
|
+
- **Lightweight**: Minimal dependencies, optimized for performance
|
|
27
|
+
- **Flexible Styling**: Support for CSS classes and inline styles
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install dyelight
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
yarn add dyelight
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add dyelight
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Basic Usage
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import React, { useState } from 'react';
|
|
47
|
+
import { DyeLight, HighlightBuilder } from 'dyelight';
|
|
48
|
+
|
|
49
|
+
function MyEditor() {
|
|
50
|
+
const [text, setText] = useState('Hello world\nThis is line 2');
|
|
51
|
+
|
|
52
|
+
// Highlight characters 0-5 (absolute positions)
|
|
53
|
+
const highlights = HighlightBuilder.ranges([
|
|
54
|
+
{ start: 0, end: 5, className: 'bg-yellow-200' },
|
|
55
|
+
{ start: 12, end: 24, className: 'bg-blue-200' },
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<DyeLight
|
|
60
|
+
value={text}
|
|
61
|
+
onChange={setText}
|
|
62
|
+
highlights={highlights}
|
|
63
|
+
className="w-full p-2 border rounded"
|
|
64
|
+
rows={4}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Advanced Usage
|
|
71
|
+
|
|
72
|
+
### Pattern-based Highlighting
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { DyeLight, HighlightBuilder } from 'dyelight';
|
|
76
|
+
|
|
77
|
+
function CodeEditor() {
|
|
78
|
+
const [code, setCode] = useState(`
|
|
79
|
+
function hello() {
|
|
80
|
+
const message = "Hello World";
|
|
81
|
+
console.log(message);
|
|
82
|
+
}
|
|
83
|
+
`);
|
|
84
|
+
|
|
85
|
+
// Highlight JavaScript keywords
|
|
86
|
+
const keywordHighlights = HighlightBuilder.pattern(
|
|
87
|
+
code,
|
|
88
|
+
/\b(function|const|let|var|if|else|for|while)\b/g,
|
|
89
|
+
'text-blue-600 font-semibold',
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Highlight strings
|
|
93
|
+
const stringHighlights = HighlightBuilder.pattern(code, /"[^"]*"/g, 'text-green-600');
|
|
94
|
+
|
|
95
|
+
// Highlight specific words
|
|
96
|
+
const wordHighlights = HighlightBuilder.words(code, ['console', 'log'], 'text-purple-600');
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<DyeLight
|
|
100
|
+
value={code}
|
|
101
|
+
onChange={setCode}
|
|
102
|
+
highlights={[...keywordHighlights, ...stringHighlights, ...wordHighlights]}
|
|
103
|
+
className="font-mono text-sm"
|
|
104
|
+
enableAutoResize
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Line Highlighting
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
function ErrorHighlighter() {
|
|
114
|
+
const [text, setText] = useState('Line 1\nLine 2 with error\nLine 3');
|
|
115
|
+
|
|
116
|
+
// Highlight line 1 (0-indexed) with error styling
|
|
117
|
+
const lineHighlights = HighlightBuilder.lines([
|
|
118
|
+
{ line: 1, className: 'bg-red-100' },
|
|
119
|
+
{ line: 2, color: '#e8f5e8' }, // Or use direct color
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
return <DyeLight value={text} onChange={setText} lineHighlights={lineHighlights} />;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Character-level Highlighting
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
function CharacterHighlighter() {
|
|
130
|
+
const [text, setText] = useState('Hello World');
|
|
131
|
+
|
|
132
|
+
// Highlight individual characters
|
|
133
|
+
const characterHighlights = HighlightBuilder.characters([
|
|
134
|
+
{ index: 0, className: 'bg-red-200' }, // 'H'
|
|
135
|
+
{ index: 6, className: 'bg-blue-200' }, // 'W'
|
|
136
|
+
{ index: 10, style: { backgroundColor: 'yellow' } }, // 'd'
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
return <DyeLight value={text} onChange={setText} highlights={characterHighlights} />;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Using Refs
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
function RefExample() {
|
|
147
|
+
const dyeLightRef = useRef<DyeLightRef>(null);
|
|
148
|
+
const [text, setText] = useState('');
|
|
149
|
+
|
|
150
|
+
const handleFocus = () => {
|
|
151
|
+
dyeLightRef.current?.focus();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleSelectAll = () => {
|
|
155
|
+
dyeLightRef.current?.select();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const handleGetValue = () => {
|
|
159
|
+
const value = dyeLightRef.current?.getValue();
|
|
160
|
+
console.log('Current value:', value);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div>
|
|
165
|
+
<DyeLight ref={dyeLightRef} value={text} onChange={setText} />
|
|
166
|
+
<button onClick={handleFocus}>Focus</button>
|
|
167
|
+
<button onClick={handleSelectAll}>Select All</button>
|
|
168
|
+
<button onClick={handleGetValue}>Get Value</button>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## API Reference
|
|
175
|
+
|
|
176
|
+
### DyeLight Props
|
|
177
|
+
|
|
178
|
+
| Prop | Type | Default | Description |
|
|
179
|
+
| ------------------ | ---------------------------------- | ----------- | ------------------------------------ |
|
|
180
|
+
| `value` | `string` | `undefined` | Controlled value |
|
|
181
|
+
| `defaultValue` | `string` | `''` | Default value for uncontrolled usage |
|
|
182
|
+
| `onChange` | `(value: string) => void` | `undefined` | Callback when value changes |
|
|
183
|
+
| `highlights` | `CharacterRange[]` | `[]` | Character range highlights |
|
|
184
|
+
| `lineHighlights` | `{ [lineNumber: number]: string }` | `{}` | Line-level highlights |
|
|
185
|
+
| `enableAutoResize` | `boolean` | `true` | Enable auto-height adjustment |
|
|
186
|
+
| `className` | `string` | `''` | CSS class name |
|
|
187
|
+
| `dir` | `'ltr' \| 'rtl'` | `'ltr'` | Text direction |
|
|
188
|
+
| `rows` | `number` | `4` | Number of visible rows |
|
|
189
|
+
|
|
190
|
+
All standard textarea HTML attributes are also supported.
|
|
191
|
+
|
|
192
|
+
### CharacterRange
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
type CharacterRange = {
|
|
196
|
+
start: number; // Start position (inclusive)
|
|
197
|
+
end: number; // End position (exclusive)
|
|
198
|
+
className?: string; // CSS class name
|
|
199
|
+
style?: React.CSSProperties; // Inline styles
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### DyeLightRef Methods
|
|
204
|
+
|
|
205
|
+
| Method | Description |
|
|
206
|
+
| ------------------------------- | -------------------------- |
|
|
207
|
+
| `focus()` | Focus the textarea |
|
|
208
|
+
| `blur()` | Blur the textarea |
|
|
209
|
+
| `select()` | Select all text |
|
|
210
|
+
| `setSelectionRange(start, end)` | Set text selection |
|
|
211
|
+
| `getValue()` | Get current value |
|
|
212
|
+
| `setValue(value)` | Set value programmatically |
|
|
213
|
+
|
|
214
|
+
## HighlightBuilder Utilities
|
|
215
|
+
|
|
216
|
+
### `HighlightBuilder.ranges(ranges)`
|
|
217
|
+
|
|
218
|
+
Create highlights from character ranges.
|
|
219
|
+
|
|
220
|
+
### `HighlightBuilder.pattern(text, pattern, className?, style?)`
|
|
221
|
+
|
|
222
|
+
Highlight text matching a regex pattern.
|
|
223
|
+
|
|
224
|
+
### `HighlightBuilder.words(text, words, className?, style?)`
|
|
225
|
+
|
|
226
|
+
Highlight specific words.
|
|
227
|
+
|
|
228
|
+
### `HighlightBuilder.characters(chars)`
|
|
229
|
+
|
|
230
|
+
Highlight individual characters by index.
|
|
231
|
+
|
|
232
|
+
### `HighlightBuilder.selection(start, end, className?, style?)`
|
|
233
|
+
|
|
234
|
+
Create a single selection highlight.
|
|
235
|
+
|
|
236
|
+
### `HighlightBuilder.lines(lines)`
|
|
237
|
+
|
|
238
|
+
Create line-level highlights.
|
|
239
|
+
|
|
240
|
+
## Styling
|
|
241
|
+
|
|
242
|
+
DyeLight uses CSS-in-JS for core functionality but allows complete customization through CSS classes and inline styles.
|
|
243
|
+
|
|
244
|
+
### Example CSS Classes
|
|
245
|
+
|
|
246
|
+
```css
|
|
247
|
+
.highlight-keyword {
|
|
248
|
+
background-color: #e3f2fd;
|
|
249
|
+
color: #1976d2;
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.highlight-error {
|
|
254
|
+
background-color: #ffebee;
|
|
255
|
+
color: #c62828;
|
|
256
|
+
text-decoration: underline wavy red;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.highlight-string {
|
|
260
|
+
background-color: #e8f5e8;
|
|
261
|
+
color: #2e7d32;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Browser Support
|
|
266
|
+
|
|
267
|
+
DyeLight supports all modern browsers including:
|
|
268
|
+
|
|
269
|
+
- Chrome 60+
|
|
270
|
+
- Firefox 60+
|
|
271
|
+
- Safari 12+
|
|
272
|
+
- Edge 79+
|
|
273
|
+
|
|
274
|
+
## Contributing
|
|
275
|
+
|
|
276
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT © [Ragaeeb Haq](https://github.com/ragaeeb)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a character range to highlight in the entire text
|
|
5
|
+
*/
|
|
6
|
+
type CharacterRange = {
|
|
7
|
+
/** Optional CSS class name to apply to the highlighted range */
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Zero-based end index in the entire text (exclusive) */
|
|
10
|
+
end: number;
|
|
11
|
+
/** Zero-based start index in the entire text (inclusive) */
|
|
12
|
+
start: number;
|
|
13
|
+
/** Optional inline styles to apply to the highlighted range */
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Methods exposed by the HighlightableTextarea component through its ref
|
|
18
|
+
*/
|
|
19
|
+
type DyeLightRef = {
|
|
20
|
+
blur: () => void;
|
|
21
|
+
focus: () => void;
|
|
22
|
+
getValue: () => string;
|
|
23
|
+
select: () => void;
|
|
24
|
+
setSelectionRange: (start: number, end: number) => void;
|
|
25
|
+
setValue: (value: string) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Props for the DyeLight component
|
|
29
|
+
*/
|
|
30
|
+
interface DyeLightProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'> {
|
|
31
|
+
/** CSS class name for the component */
|
|
32
|
+
className?: string;
|
|
33
|
+
/** Default value for uncontrolled usage */
|
|
34
|
+
defaultValue?: string;
|
|
35
|
+
/** Text direction */
|
|
36
|
+
dir?: 'ltr' | 'rtl';
|
|
37
|
+
/** Enable automatic height adjustment based on content */
|
|
38
|
+
enableAutoResize?: boolean;
|
|
39
|
+
/** Character range highlights using absolute positions in the entire text */
|
|
40
|
+
highlights?: CharacterRange[];
|
|
41
|
+
/** Line-level highlights mapped by line number (0-based) to CSS color/class */
|
|
42
|
+
lineHighlights?: {
|
|
43
|
+
[lineNumber: number]: string;
|
|
44
|
+
};
|
|
45
|
+
/** Callback fired when the textarea value changes */
|
|
46
|
+
onChange?: (value: string) => void;
|
|
47
|
+
/** Number of visible text lines */
|
|
48
|
+
rows?: number;
|
|
49
|
+
/** Controlled value */
|
|
50
|
+
value?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Automatically resizes a textarea element to fit its content
|
|
55
|
+
* Sets the height to 'auto' first, then adjusts to the scroll height
|
|
56
|
+
* Prevents textarea from having scrollbars when content fits
|
|
57
|
+
*
|
|
58
|
+
* @param textArea - The HTML textarea element to resize
|
|
59
|
+
*/
|
|
60
|
+
declare const autoResize: (textArea: HTMLTextAreaElement) => void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Utility functions for building highlight objects
|
|
64
|
+
*/
|
|
65
|
+
declare const HighlightBuilder: {
|
|
66
|
+
/**
|
|
67
|
+
* Creates highlights for individual characters using absolute positions
|
|
68
|
+
*/
|
|
69
|
+
characters: (chars: Array<{
|
|
70
|
+
className?: string;
|
|
71
|
+
index: number;
|
|
72
|
+
style?: React.CSSProperties;
|
|
73
|
+
}>) => {
|
|
74
|
+
className: string | undefined;
|
|
75
|
+
end: number;
|
|
76
|
+
start: number;
|
|
77
|
+
style: react.CSSProperties | undefined;
|
|
78
|
+
}[];
|
|
79
|
+
/**
|
|
80
|
+
* Creates line highlights
|
|
81
|
+
*/
|
|
82
|
+
lines: (lines: Array<{
|
|
83
|
+
className?: string;
|
|
84
|
+
color?: string;
|
|
85
|
+
line: number;
|
|
86
|
+
}>) => {
|
|
87
|
+
[lineNumber: number]: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Highlights text matching a pattern using absolute positions
|
|
91
|
+
*/
|
|
92
|
+
pattern: (text: string, pattern: RegExp | string, className?: string, style?: React.CSSProperties) => {
|
|
93
|
+
className: string | undefined;
|
|
94
|
+
end: number;
|
|
95
|
+
start: number;
|
|
96
|
+
style: react.CSSProperties | undefined;
|
|
97
|
+
}[];
|
|
98
|
+
/**
|
|
99
|
+
* Creates character range highlights using absolute positions
|
|
100
|
+
*/
|
|
101
|
+
ranges: (ranges: Array<{
|
|
102
|
+
className?: string;
|
|
103
|
+
end: number;
|
|
104
|
+
start: number;
|
|
105
|
+
style?: React.CSSProperties;
|
|
106
|
+
}>) => {
|
|
107
|
+
className: string | undefined;
|
|
108
|
+
end: number;
|
|
109
|
+
start: number;
|
|
110
|
+
style: react.CSSProperties | undefined;
|
|
111
|
+
}[];
|
|
112
|
+
/**
|
|
113
|
+
* Highlights text between specific start and end positions
|
|
114
|
+
*/
|
|
115
|
+
selection: (start: number, end: number, className?: string, style?: React.CSSProperties) => {
|
|
116
|
+
className: string | undefined;
|
|
117
|
+
end: number;
|
|
118
|
+
start: number;
|
|
119
|
+
style: react.CSSProperties | undefined;
|
|
120
|
+
}[];
|
|
121
|
+
/**
|
|
122
|
+
* Highlights entire words that match a pattern
|
|
123
|
+
*/
|
|
124
|
+
words: (text: string, words: string[], className?: string, style?: React.CSSProperties) => {
|
|
125
|
+
className: string | undefined;
|
|
126
|
+
end: number;
|
|
127
|
+
start: number;
|
|
128
|
+
style: react.CSSProperties | undefined;
|
|
129
|
+
}[];
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export { type CharacterRange, type DyeLightProps, type DyeLightRef, HighlightBuilder, autoResize };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{forwardRef as J,useCallback as T,useEffect as K,useImperativeHandle as Q,useMemo as X,useRef as w,useState as $}from"react";var I=e=>{e.style.height="auto",e.style.height=`${e.scrollHeight}px`};var F=e=>{let r=e.split(`
|
|
2
|
+
`),t=[],n=0;return r.forEach((a,l)=>{t.push(n),n+=a.length+(l<r.length-1?1:0)}),{lines:r,lineStarts:t}},P=(e,r)=>{for(let t=r.length-1;t>=0;t--)if(e>=r[t])return{char:e-r[t],line:t};return{char:0,line:0}},k=e=>/^(#|rgb|hsl|var\(--.*?\)|transparent|currentColor|inherit|initial|unset)/i.test(e)||/^[a-z]+$/i.test(e);var z={display:"block",position:"relative",width:"100%"},Y={background:"transparent",boxSizing:"border-box",caretColor:"#000000",color:"transparent",minHeight:"auto",position:"relative",width:"100%",zIndex:2},V={border:"transparent solid 1px",bottom:0,boxSizing:"border-box",color:"inherit",fontFamily:"inherit",fontSize:"inherit",left:0,lineHeight:"inherit",margin:0,overflow:"hidden",pointerEvents:"none",position:"absolute",right:0,top:0,whiteSpace:"pre-wrap",wordWrap:"break-word",zIndex:1};import{jsx as L,jsxs as te}from"react/jsx-runtime";var U=(e,r,t)=>{if(!t)return L("div",{children:e},r);let n=k(t);return L("div",{className:n?void 0:t,style:n?{backgroundColor:t}:void 0,children:e},r)},Z=(e,r,t,n)=>{if(t.length===0)return U(e||"\xA0",r,n);let a=t.toSorted((p,u)=>p.start-u.start),l=[],c=0;if(a.forEach((p,u)=>{let{className:A,end:H,start:o,style:g}=p,f=Math.max(0,Math.min(o,e.length)),x=Math.max(f,Math.min(H,e.length));if(f>c){let h=e.slice(c,f);h&&l.push(h)}if(x>f){let h=e.slice(f,x);l.push(L("span",{className:A,style:g,children:h},`highlight-${r}-${u}`))}c=Math.max(c,x)}),c<e.length){let p=e.slice(c);p&&l.push(p)}let C=l.length===0?"\xA0":l;return U(C,r,n)},ee=J(({className:e="",defaultValue:r="",dir:t="ltr",enableAutoResize:n=!0,highlights:a=[],lineHighlights:l={},onChange:c,rows:C=4,style:p,value:u,...A},H)=>{let o=w(null),g=w(null),f=w(null),[x,h]=$(u??r),[b,B]=$(),S=u!==void 0?u:x,y=T(s=>{n&&(I(s),B(s.scrollHeight))},[n]),G=T(s=>{let i=s.target.value;u===void 0&&h(i),c?.(i),y(s.target)},[u,c,y]),O=T(()=>{if(o.current&&g.current){let{scrollLeft:s,scrollTop:i}=o.current;g.current.scrollTop=i,g.current.scrollLeft=s}},[]),v=T(()=>{if(!o.current||!g.current)return;let s=getComputedStyle(o.current),i=g.current;i.style.padding=s.padding,i.style.fontSize=s.fontSize,i.style.fontFamily=s.fontFamily,i.style.lineHeight=s.lineHeight,i.style.letterSpacing=s.letterSpacing,i.style.wordSpacing=s.wordSpacing,i.style.textIndent=s.textIndent},[]);Q(H,()=>({blur:()=>o.current?.blur(),focus:()=>o.current?.focus(),getValue:()=>S,select:()=>o.current?.select(),setSelectionRange:(s,i)=>o.current?.setSelectionRange(s,i),setValue:s=>{u===void 0&&h(s),o.current&&(o.current.value=s,y(o.current))}}),[S,u,y]),K(()=>{o.current&&n&&y(o.current),v()},[S,y,n,v]);let W=X(()=>{let{lines:s,lineStarts:i}=F(S),R={};return a.forEach(m=>{let E=P(m.start,i),N=P(m.end-1,i);for(let d=E.line;d<=N.line;d++){R[d]||(R[d]=[]);let D=i[d],M=Math.max(m.start-D,0),_=Math.min(m.end-D,s[d].length);_>M&&R[d].push({className:m.className,end:_,start:M,style:m.style})}}),s.map((m,E)=>{let N=l[E],d=R[E]||[];return Z(m,E,d,N)})},[S,a,l]),j={...Y,height:b?`${b}px`:void 0,resize:n?"none":"vertical"},q={...V,direction:t,height:b?`${b}px`:void 0,padding:o.current?getComputedStyle(o.current).padding:"8px 12px"};return te("div",{ref:f,style:{...z,...p},children:[L("div",{"aria-hidden":"true",ref:g,style:q,children:W}),L("textarea",{className:e,dir:t,onChange:G,onScroll:O,ref:o,rows:C,style:j,value:S,...A})]})});ee.displayName="DyeLight";var re={characters:e=>e.map(({className:r,index:t,style:n})=>({className:r,end:t+1,start:t,style:n})),lines:e=>{let r={};return e.forEach(({className:t,color:n,line:a})=>{r[a]=t||n||""}),r},pattern:(e,r,t,n)=>{let a=typeof r=="string"?new RegExp(r,"g"):new RegExp(r.source,"g");return Array.from(e.matchAll(a)).map(c=>({className:t,end:c.index+c[0].length,start:c.index,style:n}))},ranges:e=>e.map(({className:r,end:t,start:n,style:a})=>({className:r,end:t,start:n,style:a})),selection:(e,r,t,n)=>[{className:t,end:r,start:e,style:n}],words:(e,r,t,n)=>{let a=new RegExp(`\\b(${r.join("|")})\\b`,"g");return re.pattern(e,a,t,n)}};export{re as HighlightBuilder,I as autoResize};
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/DyeLight.tsx","../src/domUtils.ts","../src/textUtils.ts","../src/styles.ts","../src/builder.ts"],"sourcesContent":["import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\n\nimport { autoResize } from './domUtils';\nimport type { DyeLightProps, DyeLightRef } from './types';\nimport { absoluteToLinePos, getLinePositions, isColorValue } from './textUtils';\nimport { DEFAULT_BASE_STYLE, DEFAULT_CONTAINER_STYLE, DEFAULT_HIGHLIGHT_LAYER_STYLE } from './styles';\n\nconst createLineElement = (content: React.ReactNode, lineIndex: number, lineHighlight?: string): React.ReactElement => {\n if (!lineHighlight) {\n return <div key={lineIndex}>{content}</div>;\n }\n\n const isColor = isColorValue(lineHighlight);\n return (\n <div\n className={isColor ? undefined : lineHighlight}\n key={lineIndex}\n style={isColor ? { backgroundColor: lineHighlight } : undefined}\n >\n {content}\n </div>\n );\n};\n\nconst renderHighlightedLine = (\n line: string,\n lineIndex: number,\n ranges: Array<{ className?: string; end: number; start: number; style?: React.CSSProperties }>,\n lineHighlight?: string,\n): React.ReactElement => {\n if (ranges.length === 0) {\n const content = line || '\\u00A0';\n return createLineElement(content, lineIndex, lineHighlight);\n }\n\n // Sort ranges by start position\n const sortedRanges = ranges.toSorted((a, b) => a.start - b.start);\n\n const result: React.ReactNode[] = [];\n let lastIndex = 0;\n\n sortedRanges.forEach((range, idx) => {\n const { className, end, start, style: rangeStyle } = range;\n\n // Clamp range to line bounds\n const clampedStart = Math.max(0, Math.min(start, line.length));\n const clampedEnd = Math.max(clampedStart, Math.min(end, line.length));\n\n // Add text before highlight\n if (clampedStart > lastIndex) {\n const textBefore = line.slice(lastIndex, clampedStart);\n if (textBefore) {\n result.push(textBefore);\n }\n }\n\n // Add highlighted text\n if (clampedEnd > clampedStart) {\n const highlightedText = line.slice(clampedStart, clampedEnd);\n result.push(\n <span className={className} key={`highlight-${lineIndex}-${idx}`} style={rangeStyle}>\n {highlightedText}\n </span>,\n );\n }\n\n lastIndex = Math.max(lastIndex, clampedEnd);\n });\n\n // Add remaining text\n if (lastIndex < line.length) {\n const textAfter = line.slice(lastIndex);\n if (textAfter) {\n result.push(textAfter);\n }\n }\n\n const content = result.length === 0 ? '\\u00A0' : result;\n return createLineElement(content, lineIndex, lineHighlight);\n};\n\n/**\n * A textarea component with support for highlighting character ranges using absolute positions\n * and optional line-level highlighting. Perfect for syntax highlighting, error indication,\n * and text annotation without the complexity of line-based positioning.\n *\n * @example\n * ```tsx\n * const MyComponent = () => {\n * const [text, setText] = useState('Hello world\\nSecond line');\n *\n * // Highlight characters 0-5 (absolute positions)\n * const highlights = HighlightBuilder.ranges([\n * { start: 0, end: 5, className: 'bg-yellow-200' },\n * { start: 12, end: 18, className: 'bg-blue-200' }\n * ]);\n *\n * // Highlight keywords automatically\n * const keywordHighlights = HighlightBuilder.pattern(\n * text,\n * /\\b(function|const|let)\\b/g,\n * 'text-blue-600'\n * );\n *\n * // Optional line highlights\n * const lineHighlights = HighlightBuilder.lines([\n * { line: 1, className: 'bg-red-100' }\n * ]);\n *\n * return (\n * <DyeLight\n * value={text}\n * onChange={setText}\n * highlights={[...highlights, ...keywordHighlights]}\n * lineHighlights={lineHighlights}\n * />\n * );\n * };\n * ```\n */\nconst DyeLight = forwardRef<DyeLightRef, DyeLightProps>(\n (\n {\n className = '',\n defaultValue = '',\n dir = 'ltr',\n enableAutoResize = true,\n highlights = [],\n lineHighlights = {},\n onChange,\n rows = 4,\n style,\n value,\n ...props\n },\n ref,\n ) => {\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const highlightLayerRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const [internalValue, setInternalValue] = useState(value ?? defaultValue);\n const [textareaHeight, setTextareaHeight] = useState<number | undefined>();\n\n const currentValue = value !== undefined ? value : internalValue;\n\n const handleAutoResize = useCallback(\n (element: HTMLTextAreaElement) => {\n if (!enableAutoResize) return;\n\n autoResize(element);\n setTextareaHeight(element.scrollHeight);\n },\n [enableAutoResize],\n );\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n\n if (value === undefined) {\n setInternalValue(newValue);\n }\n\n onChange?.(newValue);\n handleAutoResize(e.target);\n },\n [value, onChange, handleAutoResize],\n );\n\n const syncScroll = useCallback(() => {\n if (textareaRef.current && highlightLayerRef.current) {\n const { scrollLeft, scrollTop } = textareaRef.current;\n highlightLayerRef.current.scrollTop = scrollTop;\n highlightLayerRef.current.scrollLeft = scrollLeft;\n }\n }, []);\n\n const syncStyles = useCallback(() => {\n if (!textareaRef.current || !highlightLayerRef.current) return;\n\n const computedStyle = getComputedStyle(textareaRef.current);\n const highlightLayer = highlightLayerRef.current;\n\n highlightLayer.style.padding = computedStyle.padding;\n highlightLayer.style.fontSize = computedStyle.fontSize;\n highlightLayer.style.fontFamily = computedStyle.fontFamily;\n highlightLayer.style.lineHeight = computedStyle.lineHeight;\n highlightLayer.style.letterSpacing = computedStyle.letterSpacing;\n highlightLayer.style.wordSpacing = computedStyle.wordSpacing;\n highlightLayer.style.textIndent = computedStyle.textIndent;\n }, []);\n\n useImperativeHandle(\n ref,\n () => ({\n blur: () => textareaRef.current?.blur(),\n focus: () => textareaRef.current?.focus(),\n getValue: () => currentValue,\n select: () => textareaRef.current?.select(),\n setSelectionRange: (start: number, end: number) => textareaRef.current?.setSelectionRange(start, end),\n setValue: (newValue: string) => {\n if (value === undefined) {\n setInternalValue(newValue);\n }\n if (textareaRef.current) {\n textareaRef.current.value = newValue;\n handleAutoResize(textareaRef.current);\n }\n },\n }),\n [currentValue, value, handleAutoResize],\n );\n\n useEffect(() => {\n if (textareaRef.current && enableAutoResize) {\n handleAutoResize(textareaRef.current);\n }\n syncStyles();\n }, [currentValue, handleAutoResize, enableAutoResize, syncStyles]);\n\n const highlightedContent = useMemo(() => {\n const { lines, lineStarts } = getLinePositions(currentValue);\n\n // Group highlights by line\n const highlightsByLine: {\n [lineIndex: number]: Array<{\n className?: string;\n end: number;\n start: number;\n style?: React.CSSProperties;\n }>;\n } = {};\n\n highlights.forEach((highlight) => {\n const startPos = absoluteToLinePos(highlight.start, lineStarts);\n const endPos = absoluteToLinePos(highlight.end - 1, lineStarts);\n\n // Handle highlights that span multiple lines\n for (let lineIndex = startPos.line; lineIndex <= endPos.line; lineIndex++) {\n if (!highlightsByLine[lineIndex]) {\n highlightsByLine[lineIndex] = [];\n }\n\n const lineStart = lineStarts[lineIndex];\n\n // Calculate highlight range within this line\n const rangeStart = Math.max(highlight.start - lineStart, 0);\n const rangeEnd = Math.min(highlight.end - lineStart, lines[lineIndex].length);\n\n if (rangeEnd > rangeStart) {\n highlightsByLine[lineIndex].push({\n className: highlight.className,\n end: rangeEnd,\n start: rangeStart,\n style: highlight.style,\n });\n }\n }\n });\n\n return lines.map((line, lineIndex) => {\n const lineHighlight = lineHighlights[lineIndex];\n const ranges = highlightsByLine[lineIndex] || [];\n\n return renderHighlightedLine(line, lineIndex, ranges, lineHighlight);\n });\n }, [currentValue, highlights, lineHighlights]);\n\n const baseTextareaStyle: React.CSSProperties = {\n ...DEFAULT_BASE_STYLE,\n height: textareaHeight ? `${textareaHeight}px` : undefined,\n resize: enableAutoResize ? 'none' : 'vertical',\n };\n\n const highlightLayerStyle: React.CSSProperties = {\n ...DEFAULT_HIGHLIGHT_LAYER_STYLE,\n direction: dir,\n height: textareaHeight ? `${textareaHeight}px` : undefined,\n padding: textareaRef.current ? getComputedStyle(textareaRef.current).padding : '8px 12px',\n };\n\n return (\n <div ref={containerRef} style={{ ...DEFAULT_CONTAINER_STYLE, ...style }}>\n <div aria-hidden=\"true\" ref={highlightLayerRef} style={highlightLayerStyle}>\n {highlightedContent}\n </div>\n\n <textarea\n className={className}\n dir={dir}\n onChange={handleChange}\n onScroll={syncScroll}\n ref={textareaRef}\n rows={rows}\n style={baseTextareaStyle}\n value={currentValue}\n {...props}\n />\n </div>\n );\n },\n);\n\nDyeLight.displayName = 'DyeLight';\n\nexport default DyeLight;\n","/**\n * Automatically resizes a textarea element to fit its content\n * Sets the height to 'auto' first, then adjusts to the scroll height\n * Prevents textarea from having scrollbars when content fits\n *\n * @param textArea - The HTML textarea element to resize\n */\nexport const autoResize = (textArea: HTMLTextAreaElement) => {\n textArea.style.height = 'auto';\n textArea.style.height = `${textArea.scrollHeight}px`;\n};\n","/**\n * Converts absolute text positions to line-relative positions\n */\nexport const getLinePositions = (text: string) => {\n const lines = text.split('\\n');\n const lineStarts: number[] = [];\n let position = 0;\n\n lines.forEach((line, index) => {\n lineStarts.push(position);\n position += line.length + (index < lines.length - 1 ? 1 : 0); // +1 for \\n except last line\n });\n\n return { lines, lineStarts };\n};\n\n/**\n * Converts absolute position to line and character index\n */\nexport const absoluteToLinePos = (absolutePos: number, lineStarts: number[]) => {\n for (let i = lineStarts.length - 1; i >= 0; i--) {\n if (absolutePos >= lineStarts[i]) {\n return {\n char: absolutePos - lineStarts[i],\n line: i,\n };\n }\n }\n return { char: 0, line: 0 };\n};\n\nexport const isColorValue = (value: string): boolean => {\n return (\n /^(#|rgb|hsl|var\\(--.*?\\)|transparent|currentColor|inherit|initial|unset)/i.test(value) ||\n /^[a-z]+$/i.test(value)\n );\n};\n","export const DEFAULT_CONTAINER_STYLE: React.CSSProperties = {\n display: 'block',\n position: 'relative',\n width: '100%',\n};\n\nexport const DEFAULT_BASE_STYLE: React.CSSProperties = {\n background: 'transparent',\n boxSizing: 'border-box',\n caretColor: '#000000',\n color: 'transparent',\n minHeight: 'auto',\n position: 'relative',\n width: '100%',\n zIndex: 2,\n};\n\nexport const DEFAULT_HIGHLIGHT_LAYER_STYLE: React.CSSProperties = {\n border: 'transparent solid 1px',\n bottom: 0,\n boxSizing: 'border-box',\n color: 'inherit',\n fontFamily: 'inherit',\n fontSize: 'inherit',\n left: 0,\n lineHeight: 'inherit',\n margin: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n position: 'absolute',\n right: 0,\n top: 0,\n whiteSpace: 'pre-wrap',\n wordWrap: 'break-word',\n zIndex: 1,\n};\n","/**\n * Utility functions for building highlight objects\n */\nexport const HighlightBuilder = {\n /**\n * Creates highlights for individual characters using absolute positions\n */\n characters: (chars: Array<{ className?: string; index: number; style?: React.CSSProperties }>) => {\n return chars.map(({ className, index, style }) => ({\n className,\n end: index + 1,\n start: index,\n style,\n }));\n },\n\n /**\n * Creates line highlights\n */\n lines: (lines: Array<{ className?: string; color?: string; line: number }>) => {\n const result: { [lineNumber: number]: string } = {};\n lines.forEach(({ className, color, line }) => {\n result[line] = className || color || '';\n });\n return result;\n },\n\n /**\n * Highlights text matching a pattern using absolute positions\n */\n pattern: (text: string, pattern: RegExp | string, className?: string, style?: React.CSSProperties) => {\n const regex = typeof pattern === 'string' ? new RegExp(pattern, 'g') : new RegExp(pattern.source, 'g');\n const matches = Array.from(text.matchAll(regex));\n\n return matches.map((match) => ({\n className,\n end: match.index! + match[0].length,\n start: match.index!,\n style,\n }));\n },\n\n /**\n * Creates character range highlights using absolute positions\n */\n ranges: (ranges: Array<{ className?: string; end: number; start: number; style?: React.CSSProperties }>) => {\n return ranges.map(({ className, end, start, style }) => ({\n className,\n end,\n start,\n style,\n }));\n },\n\n /**\n * Highlights text between specific start and end positions\n */\n selection: (start: number, end: number, className?: string, style?: React.CSSProperties) => {\n return [{ className, end, start, style }];\n },\n\n /**\n * Highlights entire words that match a pattern\n */\n words: (text: string, words: string[], className?: string, style?: React.CSSProperties) => {\n const pattern = new RegExp(`\\\\b(${words.join('|')})\\\\b`, 'g');\n return HighlightBuilder.pattern(text, pattern, className, style);\n },\n};\n"],"mappings":"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","names":["forwardRef","useCallback","useEffect","useImperativeHandle","useMemo","useRef","useState","autoResize","textArea","getLinePositions","text","lines","lineStarts","position","line","index","absoluteToLinePos","absolutePos","i","isColorValue","value","DEFAULT_CONTAINER_STYLE","DEFAULT_BASE_STYLE","DEFAULT_HIGHLIGHT_LAYER_STYLE","jsx","jsxs","createLineElement","content","lineIndex","lineHighlight","isColor","isColorValue","renderHighlightedLine","line","ranges","sortedRanges","a","b","result","lastIndex","range","idx","className","end","start","rangeStyle","clampedStart","clampedEnd","textBefore","highlightedText","textAfter","DyeLight","forwardRef","defaultValue","dir","enableAutoResize","highlights","lineHighlights","onChange","rows","style","value","props","ref","textareaRef","useRef","highlightLayerRef","containerRef","internalValue","setInternalValue","useState","textareaHeight","setTextareaHeight","currentValue","handleAutoResize","useCallback","element","autoResize","handleChange","e","newValue","syncScroll","scrollLeft","scrollTop","syncStyles","computedStyle","highlightLayer","useImperativeHandle","useEffect","highlightedContent","useMemo","lines","lineStarts","getLinePositions","highlightsByLine","highlight","startPos","absoluteToLinePos","endPos","lineStart","rangeStart","rangeEnd","baseTextareaStyle","DEFAULT_BASE_STYLE","highlightLayerStyle","DEFAULT_HIGHLIGHT_LAYER_STYLE","DEFAULT_CONTAINER_STYLE","HighlightBuilder","chars","className","index","style","lines","result","color","line","text","pattern","regex","match","ranges","end","start","words"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dyelight",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "https://github.com/ragaeeb/dyelight.git"
|
|
6
|
+
},
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@eslint/js": "^9.30.1",
|
|
21
|
+
"@types/bun": "^1.2.17",
|
|
22
|
+
"@types/react": "^19.1.8",
|
|
23
|
+
"@types/react-dom": "^19.1.6",
|
|
24
|
+
"eslint": "^9.30.1",
|
|
25
|
+
"eslint-config-prettier": "^10.1.5",
|
|
26
|
+
"eslint-plugin-perfectionist": "^4.15.0",
|
|
27
|
+
"eslint-plugin-prettier": "^5.5.1",
|
|
28
|
+
"eslint-plugin-react": "^7.37.5",
|
|
29
|
+
"globals": "^16.3.0",
|
|
30
|
+
"prettier": "^3.6.2",
|
|
31
|
+
"semantic-release": "^24.2.6",
|
|
32
|
+
"tsup": "^8.5.0",
|
|
33
|
+
"typescript-eslint": "^8.35.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": "^19.1.0",
|
|
37
|
+
"react-dom": "^19.1.0"
|
|
38
|
+
},
|
|
39
|
+
"type": "module",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/ragaeeb/dyelight/issues"
|
|
42
|
+
},
|
|
43
|
+
"description": "A lightweight TypeScript React component to allow highlighting characters in textareas.",
|
|
44
|
+
"homepage": "https://github.com/ragaeeb/dyelight",
|
|
45
|
+
"keywords": [
|
|
46
|
+
"highlight",
|
|
47
|
+
"textarea",
|
|
48
|
+
"multiline",
|
|
49
|
+
"input",
|
|
50
|
+
"forms",
|
|
51
|
+
"styling"
|
|
52
|
+
],
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup"
|
|
56
|
+
}
|
|
57
|
+
}
|