dyelight 1.0.0 → 1.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.
- package/dist/index.d.ts +266 -64
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,70 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
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
|
|
4
|
+
* @fileoverview HighlightBuilder - Utility functions for creating highlight objects
|
|
57
5
|
*
|
|
58
|
-
*
|
|
6
|
+
* This module provides a convenient API for building highlight configurations
|
|
7
|
+
* for the DyeLight component. It includes methods for creating character-level
|
|
8
|
+
* highlights, line-level highlights, pattern-based highlights, and more.
|
|
59
9
|
*/
|
|
60
|
-
declare const autoResize: (textArea: HTMLTextAreaElement) => void;
|
|
61
10
|
|
|
62
11
|
/**
|
|
63
|
-
* Utility functions for building highlight objects
|
|
12
|
+
* Utility functions for building highlight objects for the DyeLight component
|
|
13
|
+
* Provides a fluent API for creating various types of text highlights
|
|
64
14
|
*/
|
|
65
15
|
declare const HighlightBuilder: {
|
|
66
16
|
/**
|
|
67
17
|
* Creates highlights for individual characters using absolute positions
|
|
18
|
+
* @param chars - Array of character highlight configurations
|
|
19
|
+
* @param chars[].index - Zero-based character index in the text
|
|
20
|
+
* @param chars[].className - Optional CSS class name to apply
|
|
21
|
+
* @param chars[].style - Optional inline styles to apply
|
|
22
|
+
* @returns Array of character range highlights
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // Highlight characters at positions 5, 10, and 15
|
|
26
|
+
* const highlights = HighlightBuilder.characters([
|
|
27
|
+
* { index: 5, className: 'highlight-error' },
|
|
28
|
+
* { index: 10, className: 'highlight-warning' },
|
|
29
|
+
* { index: 15, style: { backgroundColor: 'yellow' } }
|
|
30
|
+
* ]);
|
|
31
|
+
* ```
|
|
68
32
|
*/
|
|
69
33
|
characters: (chars: Array<{
|
|
70
34
|
className?: string;
|
|
@@ -74,10 +38,23 @@ declare const HighlightBuilder: {
|
|
|
74
38
|
className: string | undefined;
|
|
75
39
|
end: number;
|
|
76
40
|
start: number;
|
|
77
|
-
style:
|
|
41
|
+
style: React.CSSProperties | undefined;
|
|
78
42
|
}[];
|
|
79
43
|
/**
|
|
80
|
-
* Creates line highlights
|
|
44
|
+
* Creates line highlights for entire lines
|
|
45
|
+
* @param lines - Array of line highlight configurations
|
|
46
|
+
* @param lines[].line - Zero-based line number
|
|
47
|
+
* @param lines[].className - Optional CSS class name to apply to the line
|
|
48
|
+
* @param lines[].color - Optional color value (CSS color, hex, rgb, etc.)
|
|
49
|
+
* @returns Object mapping line numbers to highlight values
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* // Highlight lines 0 and 2 with different styles
|
|
53
|
+
* const lineHighlights = HighlightBuilder.lines([
|
|
54
|
+
* { line: 0, className: 'error-line' },
|
|
55
|
+
* { line: 2, color: '#ffff00' }
|
|
56
|
+
* ]);
|
|
57
|
+
* ```
|
|
81
58
|
*/
|
|
82
59
|
lines: (lines: Array<{
|
|
83
60
|
className?: string;
|
|
@@ -88,15 +65,51 @@ declare const HighlightBuilder: {
|
|
|
88
65
|
};
|
|
89
66
|
/**
|
|
90
67
|
* Highlights text matching a pattern using absolute positions
|
|
68
|
+
* @param text - The text to search within
|
|
69
|
+
* @param pattern - Regular expression or string pattern to match
|
|
70
|
+
* @param className - Optional CSS class name to apply to matches
|
|
71
|
+
* @param style - Optional inline styles to apply to matches
|
|
72
|
+
* @returns Array of character range highlights for all matches
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* // Highlight all JavaScript keywords
|
|
76
|
+
* const highlights = HighlightBuilder.pattern(
|
|
77
|
+
* code,
|
|
78
|
+
* /\b(function|const|let|var|if|else|for|while)\b/g,
|
|
79
|
+
* 'keyword-highlight'
|
|
80
|
+
* );
|
|
81
|
+
*
|
|
82
|
+
* // Highlight all email addresses
|
|
83
|
+
* const emailHighlights = HighlightBuilder.pattern(
|
|
84
|
+
* text,
|
|
85
|
+
* /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
86
|
+
* 'email-highlight'
|
|
87
|
+
* );
|
|
88
|
+
* ```
|
|
91
89
|
*/
|
|
92
90
|
pattern: (text: string, pattern: RegExp | string, className?: string, style?: React.CSSProperties) => {
|
|
93
91
|
className: string | undefined;
|
|
94
92
|
end: number;
|
|
95
93
|
start: number;
|
|
96
|
-
style:
|
|
94
|
+
style: React.CSSProperties | undefined;
|
|
97
95
|
}[];
|
|
98
96
|
/**
|
|
99
97
|
* Creates character range highlights using absolute positions
|
|
98
|
+
* @param ranges - Array of character range configurations
|
|
99
|
+
* @param ranges[].start - Zero-based start index (inclusive)
|
|
100
|
+
* @param ranges[].end - Zero-based end index (exclusive)
|
|
101
|
+
* @param ranges[].className - Optional CSS class name to apply
|
|
102
|
+
* @param ranges[].style - Optional inline styles to apply
|
|
103
|
+
* @returns Array of character range highlights
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* // Highlight specific ranges in the text
|
|
107
|
+
* const highlights = HighlightBuilder.ranges([
|
|
108
|
+
* { start: 0, end: 5, className: 'title-highlight' },
|
|
109
|
+
* { start: 10, end: 20, style: { backgroundColor: 'yellow' } },
|
|
110
|
+
* { start: 25, end: 30, className: 'error-highlight' }
|
|
111
|
+
* ]);
|
|
112
|
+
* ```
|
|
100
113
|
*/
|
|
101
114
|
ranges: (ranges: Array<{
|
|
102
115
|
className?: string;
|
|
@@ -107,26 +120,215 @@ declare const HighlightBuilder: {
|
|
|
107
120
|
className: string | undefined;
|
|
108
121
|
end: number;
|
|
109
122
|
start: number;
|
|
110
|
-
style:
|
|
123
|
+
style: React.CSSProperties | undefined;
|
|
111
124
|
}[];
|
|
112
125
|
/**
|
|
113
126
|
* Highlights text between specific start and end positions
|
|
127
|
+
* Convenience method for highlighting a single selection range
|
|
128
|
+
* @param start - Zero-based start index (inclusive)
|
|
129
|
+
* @param end - Zero-based end index (exclusive)
|
|
130
|
+
* @param className - Optional CSS class name to apply
|
|
131
|
+
* @param style - Optional inline styles to apply
|
|
132
|
+
* @returns Array containing a single character range highlight
|
|
133
|
+
* @example
|
|
134
|
+
* ```tsx
|
|
135
|
+
* // Highlight a selection from position 10 to 25
|
|
136
|
+
* const selectionHighlight = HighlightBuilder.selection(
|
|
137
|
+
* 10,
|
|
138
|
+
* 25,
|
|
139
|
+
* 'selection-highlight'
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
114
142
|
*/
|
|
115
143
|
selection: (start: number, end: number, className?: string, style?: React.CSSProperties) => {
|
|
116
144
|
className: string | undefined;
|
|
117
145
|
end: number;
|
|
118
146
|
start: number;
|
|
119
|
-
style:
|
|
147
|
+
style: React.CSSProperties | undefined;
|
|
120
148
|
}[];
|
|
121
149
|
/**
|
|
122
|
-
* Highlights entire words that match
|
|
150
|
+
* Highlights entire words that match specific terms
|
|
151
|
+
* @param text - The text to search within
|
|
152
|
+
* @param words - Array of words to highlight
|
|
153
|
+
* @param className - Optional CSS class name to apply to matched words
|
|
154
|
+
* @param style - Optional inline styles to apply to matched words
|
|
155
|
+
* @returns Array of character range highlights for all matched words
|
|
156
|
+
* @example
|
|
157
|
+
* ```tsx
|
|
158
|
+
* // Highlight specific programming keywords
|
|
159
|
+
* const highlights = HighlightBuilder.words(
|
|
160
|
+
* sourceCode,
|
|
161
|
+
* ['function', 'const', 'let', 'var', 'return'],
|
|
162
|
+
* 'keyword'
|
|
163
|
+
* );
|
|
164
|
+
*
|
|
165
|
+
* // Highlight important terms with custom styling
|
|
166
|
+
* const termHighlights = HighlightBuilder.words(
|
|
167
|
+
* document,
|
|
168
|
+
* ['TODO', 'FIXME', 'NOTE'],
|
|
169
|
+
* undefined,
|
|
170
|
+
* { backgroundColor: 'orange', fontWeight: 'bold' }
|
|
171
|
+
* );
|
|
172
|
+
* ```
|
|
123
173
|
*/
|
|
124
174
|
words: (text: string, words: string[], className?: string, style?: React.CSSProperties) => {
|
|
125
175
|
className: string | undefined;
|
|
126
176
|
end: number;
|
|
127
177
|
start: number;
|
|
128
|
-
style:
|
|
178
|
+
style: React.CSSProperties | undefined;
|
|
129
179
|
}[];
|
|
130
180
|
};
|
|
131
181
|
|
|
182
|
+
/**
|
|
183
|
+
* @fileoverview DOM utility functions for textarea manipulation
|
|
184
|
+
*
|
|
185
|
+
* This module provides utility functions for working with DOM elements,
|
|
186
|
+
* specifically focused on textarea auto-resizing functionality used by
|
|
187
|
+
* the DyeLight component.
|
|
188
|
+
*/
|
|
189
|
+
/**
|
|
190
|
+
* Automatically resizes a textarea element to fit its content
|
|
191
|
+
*
|
|
192
|
+
* This function adjusts the textarea height to match its scroll height,
|
|
193
|
+
* effectively removing scrollbars when the content fits and expanding
|
|
194
|
+
* the textarea as content is added. The height is first set to 'auto'
|
|
195
|
+
* to allow the element to shrink if content is removed.
|
|
196
|
+
*
|
|
197
|
+
* @param textArea - The HTML textarea element to resize
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const textarea = document.querySelector('textarea');
|
|
201
|
+
* if (textarea) {
|
|
202
|
+
* autoResize(textarea);
|
|
203
|
+
* }
|
|
204
|
+
*
|
|
205
|
+
* // Or in an event handler:
|
|
206
|
+
* const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
207
|
+
* autoResize(e.target);
|
|
208
|
+
* };
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
declare const autoResize: (textArea: HTMLTextAreaElement) => void;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @fileoverview Type definitions for the DyeLight component
|
|
215
|
+
*
|
|
216
|
+
* This module contains all TypeScript type definitions used by the DyeLight
|
|
217
|
+
* component, including props interfaces, ref methods, and highlight configurations.
|
|
218
|
+
* These types provide comprehensive type safety and IntelliSense support.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Represents a character range to highlight in the entire text using absolute positions
|
|
223
|
+
* @example
|
|
224
|
+
* ```tsx
|
|
225
|
+
* const highlight: CharacterRange = {
|
|
226
|
+
* start: 0,
|
|
227
|
+
* end: 5,
|
|
228
|
+
* className: 'highlight-keyword',
|
|
229
|
+
* style: { backgroundColor: 'yellow', fontWeight: 'bold' }
|
|
230
|
+
* };
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
type CharacterRange = {
|
|
234
|
+
/** Optional CSS class name to apply to the highlighted range */
|
|
235
|
+
className?: string;
|
|
236
|
+
/** Zero-based end index in the entire text (exclusive) */
|
|
237
|
+
end: number;
|
|
238
|
+
/** Zero-based start index in the entire text (inclusive) */
|
|
239
|
+
start: number;
|
|
240
|
+
/** Optional inline styles to apply to the highlighted range */
|
|
241
|
+
style?: React.CSSProperties;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Props for the DyeLight component
|
|
245
|
+
* Extends standard textarea props while replacing onChange with a simplified version
|
|
246
|
+
* @example
|
|
247
|
+
* ```tsx
|
|
248
|
+
* const MyEditor = () => {
|
|
249
|
+
* const [code, setCode] = useState('const x = 1;');
|
|
250
|
+
*
|
|
251
|
+
* const highlights = HighlightBuilder.pattern(
|
|
252
|
+
* code,
|
|
253
|
+
* /\b(const|let|var)\b/g,
|
|
254
|
+
* 'keyword-highlight'
|
|
255
|
+
* );
|
|
256
|
+
*
|
|
257
|
+
* const lineHighlights = HighlightBuilder.lines([
|
|
258
|
+
* { line: 0, className: 'current-line' }
|
|
259
|
+
* ]);
|
|
260
|
+
*
|
|
261
|
+
* return (
|
|
262
|
+
* <DyeLight
|
|
263
|
+
* value={code}
|
|
264
|
+
* onChange={setCode}
|
|
265
|
+
* highlights={highlights}
|
|
266
|
+
* lineHighlights={lineHighlights}
|
|
267
|
+
* enableAutoResize={true}
|
|
268
|
+
* rows={10}
|
|
269
|
+
* className="my-editor"
|
|
270
|
+
* placeholder="Enter your code here..."
|
|
271
|
+
* />
|
|
272
|
+
* );
|
|
273
|
+
* };
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
interface DyeLightProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'> {
|
|
277
|
+
/** CSS class name for the textarea element */
|
|
278
|
+
className?: string;
|
|
279
|
+
/** Default value for uncontrolled usage */
|
|
280
|
+
defaultValue?: string;
|
|
281
|
+
/** Text direction - supports left-to-right and right-to-left */
|
|
282
|
+
dir?: 'ltr' | 'rtl';
|
|
283
|
+
/** Enable automatic height adjustment based on content */
|
|
284
|
+
enableAutoResize?: boolean;
|
|
285
|
+
/** Character range highlights using absolute positions in the entire text */
|
|
286
|
+
highlights?: CharacterRange[];
|
|
287
|
+
/** Line-level highlights mapped by line number (0-based) to CSS color/class */
|
|
288
|
+
lineHighlights?: {
|
|
289
|
+
[lineNumber: number]: string;
|
|
290
|
+
};
|
|
291
|
+
/** Callback fired when the textarea value changes */
|
|
292
|
+
onChange?: (value: string) => void;
|
|
293
|
+
/** Number of visible text lines */
|
|
294
|
+
rows?: number;
|
|
295
|
+
/** Controlled value */
|
|
296
|
+
value?: string;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Methods exposed by the DyeLight component through its ref
|
|
300
|
+
* Provides programmatic access to common textarea operations
|
|
301
|
+
* @example
|
|
302
|
+
* ```tsx
|
|
303
|
+
* const MyComponent = () => {
|
|
304
|
+
* const dyeLightRef = useRef<DyeLightRef>(null);
|
|
305
|
+
*
|
|
306
|
+
* const handleFocus = () => {
|
|
307
|
+
* dyeLightRef.current?.focus();
|
|
308
|
+
* };
|
|
309
|
+
*
|
|
310
|
+
* const handleGetValue = () => {
|
|
311
|
+
* const value = dyeLightRef.current?.getValue();
|
|
312
|
+
* console.log('Current value:', value);
|
|
313
|
+
* };
|
|
314
|
+
*
|
|
315
|
+
* return <DyeLight ref={dyeLightRef} />;
|
|
316
|
+
* };
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
type DyeLightRef = {
|
|
320
|
+
/** Removes focus from the textarea */
|
|
321
|
+
blur: () => void;
|
|
322
|
+
/** Sets focus to the textarea */
|
|
323
|
+
focus: () => void;
|
|
324
|
+
/** Gets the current value of the textarea */
|
|
325
|
+
getValue: () => string;
|
|
326
|
+
/** Selects all text in the textarea */
|
|
327
|
+
select: () => void;
|
|
328
|
+
/** Sets the selection range in the textarea */
|
|
329
|
+
setSelectionRange: (start: number, end: number) => void;
|
|
330
|
+
/** Sets the value of the textarea programmatically */
|
|
331
|
+
setValue: (value: string) => void;
|
|
332
|
+
};
|
|
333
|
+
|
|
132
334
|
export { type CharacterRange, type DyeLightProps, type DyeLightRef, HighlightBuilder, autoResize };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{forwardRef as
|
|
2
|
-
`),t=[],n=0;return r.forEach((
|
|
1
|
+
import"react";var J={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:i})=>{r[i]=t||n||""}),r},pattern:(e,r,t,n)=>{let i=typeof r=="string"?new RegExp(r,"g"):new RegExp(r.source,"g");return Array.from(e.matchAll(i)).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:i})=>({className:r,end:t,start:n,style:i})),selection:(e,r,t,n)=>[{className:t,end:r,start:e,style:n}],words:(e,r,t,n)=>{let i=new RegExp(`\\b(${r.join("|")})\\b`,"g");return J.pattern(e,i,t,n)}};var I=e=>{e.style.height="auto",e.style.height=`${e.scrollHeight}px`};import{forwardRef as K,useCallback as T,useEffect as Q,useImperativeHandle as X,useMemo as Z,useRef as P,useState as $}from"react";import"react";var V={display:"block",position:"relative",width:"100%"},z={background:"transparent",boxSizing:"border-box",caretColor:"#000000",color:"transparent",minHeight:"auto",position:"relative",width:"100%",zIndex:2},F={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};var k=e=>{let r=e.split(`
|
|
2
|
+
`),t=[],n=0;return r.forEach((i,l)=>{t.push(n),n+=i.length+(l<r.length-1?1:0)}),{lines:r,lineStarts:t}},N=(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}},Y=e=>/^(#|rgb|hsl|var\(--.*?\)|transparent|currentColor|inherit|initial|unset)/i.test(e)||/^[a-z]+$/i.test(e);import{jsx as R,jsxs as re}from"react/jsx-runtime";var U=(e,r,t)=>{if(!t)return R("div",{children:e},r);let n=Y(t);return R("div",{className:n?void 0:t,style:n?{backgroundColor:t}:void 0,children:e},r)},ee=(e,r,t,n)=>{if(t.length===0)return U(e||"\xA0",r,n);let i=t.toSorted((g,u)=>g.start-u.start),l=[],c=0;if(i.forEach((g,u)=>{let{className:A,end:v,start:o,style:d}=g,h=Math.max(0,Math.min(o,e.length)),x=Math.max(h,Math.min(v,e.length));if(h>c){let f=e.slice(c,h);f&&l.push(f)}if(x>h){let f=e.slice(h,x);l.push(R("span",{className:A,style:d,children:f},`highlight-${r}-${u}`))}c=Math.max(c,x)}),c<e.length){let g=e.slice(c);g&&l.push(g)}let C=l.length===0?"\xA0":l;return U(C,r,n)},te=K(({className:e="",defaultValue:r="",dir:t="ltr",enableAutoResize:n=!0,highlights:i=[],lineHighlights:l={},onChange:c,rows:C=4,style:g,value:u,...A},v)=>{let o=P(null),d=P(null),h=P(null),[x,f]=$(u??r),[L,B]=$(),y=u!==void 0?u:x,S=T(s=>{n&&(I(s),B(s.scrollHeight))},[n]),G=T(s=>{let a=s.target.value;u===void 0&&f(a),c?.(a),S(s.target)},[u,c,S]),O=T(()=>{if(o.current&&d.current){let{scrollLeft:s,scrollTop:a}=o.current;d.current.scrollTop=a,d.current.scrollLeft=s}},[]),w=T(()=>{if(!o.current||!d.current)return;let s=getComputedStyle(o.current),a=d.current;a.style.padding=s.padding,a.style.fontSize=s.fontSize,a.style.fontFamily=s.fontFamily,a.style.lineHeight=s.lineHeight,a.style.letterSpacing=s.letterSpacing,a.style.wordSpacing=s.wordSpacing,a.style.textIndent=s.textIndent},[]);X(v,()=>({blur:()=>o.current?.blur(),focus:()=>o.current?.focus(),getValue:()=>y,select:()=>o.current?.select(),setSelectionRange:(s,a)=>o.current?.setSelectionRange(s,a),setValue:s=>{u===void 0&&f(s),o.current&&(o.current.value=s,S(o.current))}}),[y,u,S]),Q(()=>{o.current&&n&&S(o.current),w()},[y,S,n,w]);let W=Z(()=>{let{lines:s,lineStarts:a}=k(y),E={};return i.forEach(m=>{let b=N(m.start,a),H=N(m.end-1,a);for(let p=b.line;p<=H.line;p++){E[p]||(E[p]=[]);let D=a[p],M=Math.max(m.start-D,0),_=Math.min(m.end-D,s[p].length);_>M&&E[p].push({className:m.className,end:_,start:M,style:m.style})}}),s.map((m,b)=>{let H=l[b],p=E[b]||[];return ee(m,b,p,H)})},[y,i,l]),j={...z,height:L?`${L}px`:void 0,resize:n?"none":"vertical"},q={...F,direction:t,height:L?`${L}px`:void 0,padding:o.current?getComputedStyle(o.current).padding:"8px 12px"};return re("div",{ref:h,style:{...V,...g},children:[R("div",{"aria-hidden":"true",ref:d,style:q,children:W}),R("textarea",{className:e,dir:t,onChange:G,onScroll:O,ref:o,rows:C,style:j,value:y,...A})]})});te.displayName="DyeLight";import"react";export{J as HighlightBuilder,I as autoResize};
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/builder.ts","../src/domUtils.ts","../src/DyeLight.tsx","../src/styles.ts","../src/textUtils.ts","../src/types.ts"],"sourcesContent":["/**\n * @fileoverview HighlightBuilder - Utility functions for creating highlight objects\n *\n * This module provides a convenient API for building highlight configurations\n * for the DyeLight component. It includes methods for creating character-level\n * highlights, line-level highlights, pattern-based highlights, and more.\n */\n\nimport React from 'react';\n\n/**\n * Utility functions for building highlight objects for the DyeLight component\n * Provides a fluent API for creating various types of text highlights\n */\nexport const HighlightBuilder = {\n /**\n * Creates highlights for individual characters using absolute positions\n * @param chars - Array of character highlight configurations\n * @param chars[].index - Zero-based character index in the text\n * @param chars[].className - Optional CSS class name to apply\n * @param chars[].style - Optional inline styles to apply\n * @returns Array of character range highlights\n * @example\n * ```tsx\n * // Highlight characters at positions 5, 10, and 15\n * const highlights = HighlightBuilder.characters([\n * { index: 5, className: 'highlight-error' },\n * { index: 10, className: 'highlight-warning' },\n * { index: 15, style: { backgroundColor: 'yellow' } }\n * ]);\n * ```\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 for entire lines\n * @param lines - Array of line highlight configurations\n * @param lines[].line - Zero-based line number\n * @param lines[].className - Optional CSS class name to apply to the line\n * @param lines[].color - Optional color value (CSS color, hex, rgb, etc.)\n * @returns Object mapping line numbers to highlight values\n * @example\n * ```tsx\n * // Highlight lines 0 and 2 with different styles\n * const lineHighlights = HighlightBuilder.lines([\n * { line: 0, className: 'error-line' },\n * { line: 2, color: '#ffff00' }\n * ]);\n * ```\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 * @param text - The text to search within\n * @param pattern - Regular expression or string pattern to match\n * @param className - Optional CSS class name to apply to matches\n * @param style - Optional inline styles to apply to matches\n * @returns Array of character range highlights for all matches\n * @example\n * ```tsx\n * // Highlight all JavaScript keywords\n * const highlights = HighlightBuilder.pattern(\n * code,\n * /\\b(function|const|let|var|if|else|for|while)\\b/g,\n * 'keyword-highlight'\n * );\n *\n * // Highlight all email addresses\n * const emailHighlights = HighlightBuilder.pattern(\n * text,\n * /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/g,\n * 'email-highlight'\n * );\n * ```\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 * @param ranges - Array of character range configurations\n * @param ranges[].start - Zero-based start index (inclusive)\n * @param ranges[].end - Zero-based end index (exclusive)\n * @param ranges[].className - Optional CSS class name to apply\n * @param ranges[].style - Optional inline styles to apply\n * @returns Array of character range highlights\n * @example\n * ```tsx\n * // Highlight specific ranges in the text\n * const highlights = HighlightBuilder.ranges([\n * { start: 0, end: 5, className: 'title-highlight' },\n * { start: 10, end: 20, style: { backgroundColor: 'yellow' } },\n * { start: 25, end: 30, className: 'error-highlight' }\n * ]);\n * ```\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 * Convenience method for highlighting a single selection range\n * @param start - Zero-based start index (inclusive)\n * @param end - Zero-based end index (exclusive)\n * @param className - Optional CSS class name to apply\n * @param style - Optional inline styles to apply\n * @returns Array containing a single character range highlight\n * @example\n * ```tsx\n * // Highlight a selection from position 10 to 25\n * const selectionHighlight = HighlightBuilder.selection(\n * 10,\n * 25,\n * 'selection-highlight'\n * );\n * ```\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 specific terms\n * @param text - The text to search within\n * @param words - Array of words to highlight\n * @param className - Optional CSS class name to apply to matched words\n * @param style - Optional inline styles to apply to matched words\n * @returns Array of character range highlights for all matched words\n * @example\n * ```tsx\n * // Highlight specific programming keywords\n * const highlights = HighlightBuilder.words(\n * sourceCode,\n * ['function', 'const', 'let', 'var', 'return'],\n * 'keyword'\n * );\n *\n * // Highlight important terms with custom styling\n * const termHighlights = HighlightBuilder.words(\n * document,\n * ['TODO', 'FIXME', 'NOTE'],\n * undefined,\n * { backgroundColor: 'orange', fontWeight: 'bold' }\n * );\n * ```\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","/**\n * @fileoverview DOM utility functions for textarea manipulation\n *\n * This module provides utility functions for working with DOM elements,\n * specifically focused on textarea auto-resizing functionality used by\n * the DyeLight component.\n */\n\n/**\n * Automatically resizes a textarea element to fit its content\n *\n * This function adjusts the textarea height to match its scroll height,\n * effectively removing scrollbars when the content fits and expanding\n * the textarea as content is added. The height is first set to 'auto'\n * to allow the element to shrink if content is removed.\n *\n * @param textArea - The HTML textarea element to resize\n * @example\n * ```ts\n * const textarea = document.querySelector('textarea');\n * if (textarea) {\n * autoResize(textarea);\n * }\n *\n * // Or in an event handler:\n * const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n * autoResize(e.target);\n * };\n * ```\n */\nexport const autoResize = (textArea: HTMLTextAreaElement) => {\n // Reset height to auto to allow shrinking\n textArea.style.height = 'auto';\n // Set height to scroll height to fit content\n textArea.style.height = `${textArea.scrollHeight}px`;\n};\n","import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\n\nimport type { DyeLightProps, DyeLightRef } from './types';\n\nimport { autoResize } from './domUtils';\nimport { DEFAULT_BASE_STYLE, DEFAULT_CONTAINER_STYLE, DEFAULT_HIGHLIGHT_LAYER_STYLE } from './styles';\nimport { absoluteToLinePos, getLinePositions, isColorValue } from './textUtils';\n\n/**\n * @fileoverview DyeLight - A React textarea component with advanced text highlighting capabilities\n *\n * This component provides a textarea with overlay-based text highlighting that supports:\n * - Character-level highlighting using absolute text positions\n * - Line-level highlighting with CSS classes or color values\n * - Automatic height adjustment based on content\n * - Synchronized scrolling between textarea and highlight layer\n * - Both controlled and uncontrolled usage patterns\n * - RTL text direction support\n */\n\n/**\n * Creates a line element with optional highlighting\n * @param content - The content to render inside the line element\n * @param lineIndex - The index of the line for React key prop\n * @param lineHighlight - Optional CSS class name or color value for line highlighting\n * @returns A React div element with the line content and optional highlighting\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\n/**\n * Renders a single line with character-level highlights and optional line-level highlighting\n * @param line - The text content of the line\n * @param lineIndex - The index of the line for React key prop\n * @param ranges - Array of character ranges to highlight within the line\n * @param lineHighlight - Optional CSS class name or color value for line highlighting\n * @returns A React element representing the highlighted line\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 * Features:\n * - Character-level highlighting using absolute text positions\n * - Line-level highlighting with CSS classes or color values\n * - Automatic height adjustment based on content\n * - Synchronized scrolling between textarea and highlight layer\n * - Support for both controlled and uncontrolled usage\n * - Accessibility-friendly with proper ARIA attributes\n * - RTL text direction support\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 /**\n * Handles automatic resizing of the textarea based on content\n * @param element - The textarea element to resize\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 /**\n * Handles textarea value changes and triggers auto-resize if enabled\n * @param e - The change event from the textarea\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 /**\n * Synchronizes scroll position between textarea and highlight layer\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 /**\n * Synchronizes computed styles from textarea to highlight layer\n * Ensures font, padding, and spacing properties match exactly\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 /**\n * Computes the highlighted content by processing text and highlight ranges\n * Groups highlights by line and renders each line with appropriate highlighting\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 * @fileoverview Default styles for DyeLight component elements\n *\n * This module contains the default CSS-in-JS styles used by the DyeLight component\n * for its container, textarea, and highlight layer elements. These styles ensure\n * proper layering, positioning, and visual alignment between the textarea and\n * its highlight overlay.\n */\n\nimport React from 'react';\n\n/**\n * Default styles for the main container element that wraps the entire component\n * Creates a positioned container that serves as the reference for absolutely positioned children\n */\nexport const DEFAULT_CONTAINER_STYLE: React.CSSProperties = {\n /** Ensures the container behaves as a block-level element */\n display: 'block',\n /** Enables absolute positioning for child elements */\n position: 'relative',\n /** Takes full width of parent container */\n width: '100%',\n};\n\n/**\n * Default styles for the textarea element\n * Makes the textarea transparent while maintaining its functionality and positioning\n */\nexport const DEFAULT_BASE_STYLE: React.CSSProperties = {\n /** Transparent background to show highlights underneath */\n background: 'transparent',\n /** Ensures consistent box model calculations */\n boxSizing: 'border-box',\n /** Maintains visible text cursor */\n caretColor: '#000000',\n /** Transparent text to reveal highlights while keeping cursor and selection */\n color: 'transparent',\n /** Prevents unwanted height constraints */\n minHeight: 'auto',\n /** Positioned above the highlight layer */\n position: 'relative',\n /** Takes full width of container */\n width: '100%',\n /** Ensures textarea appears above highlight layer */\n zIndex: 2,\n};\n\n/**\n * Default styles for the highlight layer that renders behind the textarea\n * Positioned absolutely to overlay perfectly with the textarea content\n */\nexport const DEFAULT_HIGHLIGHT_LAYER_STYLE: React.CSSProperties = {\n /** Transparent border to match textarea default border */\n border: 'transparent solid 1px',\n /** Stretch to fill container bottom */\n bottom: 0,\n /** Consistent box model with textarea */\n boxSizing: 'border-box',\n /** Inherit text color from parent */\n color: 'inherit',\n /** Match textarea font family */\n fontFamily: 'inherit',\n /** Match textarea font size */\n fontSize: 'inherit',\n /** Stretch to fill container left */\n left: 0,\n /** Match textarea line height */\n lineHeight: 'inherit',\n /** Remove default margins */\n margin: 0,\n /** Hide scrollbars on highlight layer */\n overflow: 'hidden',\n /** Prevent highlight layer from capturing mouse events */\n pointerEvents: 'none',\n /** Positioned absolutely within container */\n position: 'absolute',\n /** Stretch to fill container right */\n right: 0,\n /** Stretch to fill container top */\n top: 0,\n /** Preserve whitespace and line breaks like textarea */\n whiteSpace: 'pre-wrap',\n /** Break long words like textarea */\n wordWrap: 'break-word',\n /** Ensure highlight layer appears behind textarea */\n zIndex: 1,\n};\n","/**\n * @fileoverview Text utility functions for position calculations and text processing\n *\n * This module provides utilities for converting between absolute text positions\n * and line-relative positions, as well as other text processing functions used\n * by the DyeLight component for highlight positioning.\n */\n\n/**\n * Analyzes text content and returns line information with position mappings\n * @param text - The text content to analyze\n * @returns Object containing lines array and line start positions\n * @returns returns.lines - Array of individual lines (without newline characters)\n * @returns returns.lineStarts - Array of absolute positions where each line starts\n * @example\n * ```ts\n * const text = \"Hello\\nWorld\\nTest\";\n * const { lines, lineStarts } = getLinePositions(text);\n * // lines: [\"Hello\", \"World\", \"Test\"]\n * // lineStarts: [0, 6, 12]\n * ```\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 an absolute text position to line-relative coordinates\n * @param absolutePos - Zero-based absolute position in the entire text\n * @param lineStarts - Array of line start positions (from getLinePositions)\n * @returns Object containing line number and character position within that line\n * @returns returns.line - Zero-based line number\n * @returns returns.char - Zero-based character position within the line\n * @example\n * ```ts\n * const text = \"Hello\\nWorld\\nTest\";\n * const { lineStarts } = getLinePositions(text);\n * const pos = absoluteToLinePos(8, lineStarts);\n * // pos: { line: 1, char: 2 } (the 'r' in \"World\")\n * ```\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\n/**\n * Determines if a string value represents a CSS color value\n * @param value - The string value to test\n * @returns True if the value appears to be a color value, false otherwise\n * @example\n * ```ts\n * isColorValue('#ff0000'); // true\n * isColorValue('rgb(255, 0, 0)'); // true\n * isColorValue('red'); // true\n * isColorValue('my-css-class'); // false (contains hyphens)\n * isColorValue('transparent'); // true\n * isColorValue('var(--primary-color)'); // true\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","/**\n * @fileoverview Type definitions for the DyeLight component\n *\n * This module contains all TypeScript type definitions used by the DyeLight\n * component, including props interfaces, ref methods, and highlight configurations.\n * These types provide comprehensive type safety and IntelliSense support.\n */\n\nimport React from 'react';\n\n/**\n * Represents a character range to highlight in the entire text using absolute positions\n * @example\n * ```tsx\n * const highlight: CharacterRange = {\n * start: 0,\n * end: 5,\n * className: 'highlight-keyword',\n * style: { backgroundColor: 'yellow', fontWeight: 'bold' }\n * };\n * ```\n */\nexport type CharacterRange = {\n /** Optional CSS class name to apply to the highlighted range */\n className?: string;\n /** Zero-based end index in the entire text (exclusive) */\n end: number;\n /** Zero-based start index in the entire text (inclusive) */\n start: number;\n /** Optional inline styles to apply to the highlighted range */\n style?: React.CSSProperties;\n};\n\n/**\n * Props for the DyeLight component\n * Extends standard textarea props while replacing onChange with a simplified version\n * @example\n * ```tsx\n * const MyEditor = () => {\n * const [code, setCode] = useState('const x = 1;');\n *\n * const highlights = HighlightBuilder.pattern(\n * code,\n * /\\b(const|let|var)\\b/g,\n * 'keyword-highlight'\n * );\n *\n * const lineHighlights = HighlightBuilder.lines([\n * { line: 0, className: 'current-line' }\n * ]);\n *\n * return (\n * <DyeLight\n * value={code}\n * onChange={setCode}\n * highlights={highlights}\n * lineHighlights={lineHighlights}\n * enableAutoResize={true}\n * rows={10}\n * className=\"my-editor\"\n * placeholder=\"Enter your code here...\"\n * />\n * );\n * };\n * ```\n */\nexport interface DyeLightProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'> {\n /** CSS class name for the textarea element */\n className?: string;\n /** Default value for uncontrolled usage */\n defaultValue?: string;\n /** Text direction - supports left-to-right and right-to-left */\n dir?: 'ltr' | 'rtl';\n /** Enable automatic height adjustment based on content */\n enableAutoResize?: boolean;\n /** Character range highlights using absolute positions in the entire text */\n highlights?: CharacterRange[];\n /** Line-level highlights mapped by line number (0-based) to CSS color/class */\n lineHighlights?: { [lineNumber: number]: string };\n /** Callback fired when the textarea value changes */\n onChange?: (value: string) => void;\n /** Number of visible text lines */\n rows?: number;\n /** Controlled value */\n value?: string;\n}\n\n/**\n * Methods exposed by the DyeLight component through its ref\n * Provides programmatic access to common textarea operations\n * @example\n * ```tsx\n * const MyComponent = () => {\n * const dyeLightRef = useRef<DyeLightRef>(null);\n *\n * const handleFocus = () => {\n * dyeLightRef.current?.focus();\n * };\n *\n * const handleGetValue = () => {\n * const value = dyeLightRef.current?.getValue();\n * console.log('Current value:', value);\n * };\n *\n * return <DyeLight ref={dyeLightRef} />;\n * };\n * ```\n */\nexport type DyeLightRef = {\n /** Removes focus from the textarea */\n blur: () => void;\n /** Sets focus to the textarea */\n focus: () => void;\n /** Gets the current value of the textarea */\n getValue: () => string;\n /** Selects all text in the textarea */\n select: () => void;\n /** Sets the selection range in the textarea */\n setSelectionRange: (start: number, end: number) => void;\n /** Sets the value of the textarea programmatically */\n setValue: (value: string) => void;\n};\n"],"mappings":"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","names":["HighlightBuilder","chars","className","index","style","lines","result","color","line","text","pattern","regex","match","ranges","end","start","words","autoResize","textArea","forwardRef","useCallback","useEffect","useImperativeHandle","useMemo","useRef","useState","DEFAULT_CONTAINER_STYLE","DEFAULT_BASE_STYLE","DEFAULT_HIGHLIGHT_LAYER_STYLE","getLinePositions","text","lines","lineStarts","position","line","index","absoluteToLinePos","absolutePos","i","isColorValue","value","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"]}
|