Skip to content

Commit b36be22

Browse files
committed
Improve logic of createRegexRenderer
1 parent e900f9d commit b36be22

File tree

6 files changed

+233
-145
lines changed

6 files changed

+233
-145
lines changed

src/__snapshots__/textarea.spec.tsx.snap

+120-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,85 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`match double matchers 1`] = `
4+
<DocumentFragment>
5+
<div
6+
style="display: inline-block; position: relative; width: 0px; height: 0px;"
7+
>
8+
<div
9+
style="position: absolute; overflow: hidden; top: 0px; left: 0px; width: 0px; height: 0px;"
10+
>
11+
<div
12+
aria-hidden="true"
13+
style="width: 0px; transform: translate(0px, 0px); pointer-events: none; user-select: none; box-sizing: content-box; padding: 2px 2px 2px 2px; margin: 0px 0px 0px 0px; border: 1px solid; border-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-right-width: 1px; border-style: solid; border-top-style: solid; border-bottom-style: solid; border-left-style: solid; border-right-style: solid; font-family: -webkit-small-control; text-align: start; text-transform: none; text-indent: 0; letter-spacing: normal; word-spacing: normal; line-height: normal; white-space: pre-wrap; border-color: transparent;"
14+
>
15+
L
16+
<span
17+
style="color: blue; border: 1px solid blue;"
18+
>
19+
<span
20+
style="color: red; background: red;"
21+
>
22+
o
23+
</span>
24+
</span>
25+
<span
26+
style="color: red; background: red;"
27+
>
28+
r
29+
</span>
30+
<span
31+
style="color: blue; border: 1px solid blue;"
32+
>
33+
e
34+
</span>
35+
m ipsum d
36+
<span
37+
style="color: blue; border: 1px solid blue;"
38+
>
39+
<span
40+
style="color: red; background: red;"
41+
>
42+
o
43+
</span>
44+
</span>
45+
l
46+
<span
47+
style="color: blue; border: 1px solid blue;"
48+
>
49+
<span
50+
style="color: red; background: red;"
51+
>
52+
o
53+
</span>
54+
</span>
55+
<span
56+
style="color: red; background: red;"
57+
>
58+
r
59+
</span>
60+
sit am
61+
<span
62+
style="color: blue; border: 1px solid blue;"
63+
>
64+
e
65+
</span>
66+
t
67+
<span
68+
style="color: transparent;"
69+
>
70+
71+
</span>
72+
</div>
73+
</div>
74+
<textarea
75+
style="background: transparent; margin: 0px; vertical-align: top; color: transparent;"
76+
>
77+
Lorem ipsum dolor sit amet
78+
</textarea>
79+
</div>
80+
</DocumentFragment>
81+
`;
82+
383
exports[`match emoji 1`] = `
484
<DocumentFragment>
585
<div
@@ -403,7 +483,7 @@ exports[`match match one 1`] = `
403483
</DocumentFragment>
404484
`;
405485

406-
exports[`match multiple matchers 1`] = `
486+
exports[`match triple matchers 1`] = `
407487
<DocumentFragment>
408488
<div
409489
style="display: inline-block; position: relative; width: 0px; height: 0px;"
@@ -415,54 +495,70 @@ exports[`match multiple matchers 1`] = `
415495
aria-hidden="true"
416496
style="width: 0px; transform: translate(0px, 0px); pointer-events: none; user-select: none; box-sizing: content-box; padding: 2px 2px 2px 2px; margin: 0px 0px 0px 0px; border: 1px solid; border-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-right-width: 1px; border-style: solid; border-top-style: solid; border-bottom-style: solid; border-left-style: solid; border-right-style: solid; font-family: -webkit-small-control; text-align: start; text-transform: none; text-indent: 0; letter-spacing: normal; word-spacing: normal; line-height: normal; white-space: pre-wrap; border-color: transparent;"
417497
>
418-
L
419498
<span
420-
style="color: red; background: red;"
499+
style="background-color: lightgray;"
421500
>
422-
<span
423-
style="color: blue; border: 1px solid blue;"
424-
>
425-
o
426-
</span>
501+
Lor
427502
</span>
428503
<span
429-
style="color: red; background: red;"
504+
style="color: red; font-weight: bold;"
430505
>
431-
r
506+
<span
507+
style="background-color: lightgray;"
508+
>
509+
e
510+
</span>
432511
</span>
433512
<span
434-
style="color: blue; border: 1px solid blue;"
513+
style="background-color: lightgray;"
435514
>
436-
e
515+
m
437516
</span>
438-
m ipsum d
517+
439518
<span
440-
style="color: red; background: red;"
519+
style="color: blue; text-decoration: underline wavy;"
441520
>
442521
<span
443-
style="color: blue; border: 1px solid blue;"
522+
style="color: red; font-weight: bold;"
444523
>
445-
o
524+
i
446525
</span>
447526
</span>
448-
l
449527
<span
450-
style="color: red; background: red;"
528+
style="color: blue; text-decoration: underline wavy;"
451529
>
452530
<span
453-
style="color: blue; border: 1px solid blue;"
531+
style="color: red; font-weight: bold;"
454532
>
455-
o
533+
p
456534
</span>
457535
</span>
458536
<span
459-
style="color: red; background: red;"
537+
style="color: blue; text-decoration: underline wavy;"
460538
>
461-
r
539+
sum
462540
</span>
463-
sit am
541+
464542
<span
465-
style="color: blue; border: 1px solid blue;"
543+
style="color: red; font-weight: bold;"
544+
>
545+
d
546+
</span>
547+
olor s
548+
<span
549+
style="color: red; font-weight: bold;"
550+
>
551+
i
552+
</span>
553+
t
554+
<span
555+
style="color: red; font-weight: bold;"
556+
>
557+
a
558+
</span>
559+
m
560+
<span
561+
style="color: red; font-weight: bold;"
466562
>
467563
e
468564
</span>

src/renderers.tsx

+79-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { execReg } from "./regex";
22
import type { Renderer } from "./types";
3-
import { RangeChunk, mergeRanges } from "./utils";
43

54
export type StyleOrRender =
65
| React.CSSProperties
@@ -10,38 +9,85 @@ export type StyleOrRender =
109
key?: string | undefined;
1110
}) => React.ReactNode);
1211

12+
type RangeChunk = [start: number, end: number];
13+
1314
/**
1415
* An utility to create renderer function with regex.
16+
*
17+
* The priority is descending order.
1518
*/
1619
export const createRegexRenderer = (
1720
matchers: [RegExp, StyleOrRender][]
1821
): Renderer => {
1922
return (value) => {
20-
const styles: StyleOrRender[] = [];
21-
const ranges: RangeChunk<number>[] = [];
22-
matchers.forEach(([matcher, style], i) => {
23-
ranges.push(
24-
...execReg(matcher, value).map((m): RangeChunk<number> => {
25-
const start = m.index;
26-
const end = m.index + m[0]!.length;
27-
return [start, end, i];
28-
})
29-
);
30-
styles.push(style);
23+
const matches = matchers.map(
24+
([matcher, style]): [RangeChunk[], StyleOrRender] => {
25+
return [
26+
execReg(matcher, value).map((m): RangeChunk => {
27+
return [m.index, m.index + m[0]!.length];
28+
}),
29+
style,
30+
];
31+
}
32+
);
33+
const [indexSet, startToStyleMap, endToStyleMap] = matches.reduce(
34+
(acc, [ranges, style]) => {
35+
ranges.forEach(([start, end]) => {
36+
acc[0].add(start).add(end);
37+
let startStyles = acc[1].get(start);
38+
let endStyles = acc[2].get(end);
39+
if (!startStyles) {
40+
acc[1].set(start, (startStyles = []));
41+
}
42+
if (!endStyles) {
43+
acc[2].set(end, (endStyles = []));
44+
}
45+
startStyles.push(style);
46+
endStyles.push(style);
47+
});
48+
return acc;
49+
},
50+
[
51+
new Set<number>(),
52+
new Map<number, StyleOrRender[]>(),
53+
new Map<number, StyleOrRender[]>(),
54+
] as const
55+
);
56+
const indexes = Array.from(indexSet);
57+
indexes.sort((a, b) => {
58+
return a - b;
3159
});
3260

33-
const chunks = mergeRanges(ranges);
34-
const res: React.ReactNode[] = [];
3561
let prevEnd = 0;
36-
for (let i = 0; i < chunks.length; i++) {
37-
const [start, end, styleIds] = chunks[i]!;
38-
res.push(value.slice(prevEnd, start));
62+
const activeStyles = new Set<StyleOrRender>();
63+
const res: React.ReactNode[] = [];
64+
for (let i = 0; i < indexes.length; i++) {
65+
const start = indexes[i]!;
66+
const end = indexes[i + 1] ?? value.length;
67+
if (start === end) continue;
68+
const headValue = value.slice(prevEnd, start);
69+
if (headValue) {
70+
res.push(headValue);
71+
}
72+
const startStyles = startToStyleMap.get(start);
73+
const endStyles = endToStyleMap.get(end);
74+
if (startStyles) {
75+
startStyles.forEach((s) => {
76+
activeStyles.add(s);
77+
});
78+
}
3979

4080
const v = value.slice(start, end);
81+
const sortedStyles = Array.from(activeStyles).sort((a, b) => {
82+
return (
83+
matchers.findIndex(([, s]) => s === b) -
84+
matchers.findIndex(([, s]) => s === a)
85+
);
86+
});
87+
4188
res.push(
42-
Array.from(styleIds).reduceRight((acc, si, index) => {
43-
const styleOrRender = styles[si];
44-
const key = index === 0 ? String(start) : undefined;
89+
sortedStyles.reduceRight((acc, styleOrRender, j) => {
90+
const key = j === 0 ? String(start) : undefined;
4591
if (typeof styleOrRender === "function") {
4692
return styleOrRender({ children: acc, value: v, key });
4793
} else {
@@ -53,9 +99,21 @@ export const createRegexRenderer = (
5399
}
54100
}, v as React.ReactNode)
55101
);
102+
103+
if (endStyles) {
104+
endStyles.forEach((s) => {
105+
activeStyles.delete(s);
106+
});
107+
}
108+
56109
prevEnd = end;
57110
}
58-
res.push(value.slice(prevEnd));
111+
112+
const tailValue = value.slice(prevEnd);
113+
if (tailValue) {
114+
res.push(tailValue);
115+
}
116+
59117
return res;
60118
};
61119
};

src/textarea.spec.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ describe("match", () => {
168168
expect(asFragment()).toMatchSnapshot();
169169
});
170170

171-
it("multiple matchers", () => {
171+
it("double matchers", () => {
172172
const { asFragment } = render(
173173
<RichTextarea value={"Lorem ipsum dolor sit amet"} onChange={NOP}>
174174
{createRegexRenderer([
@@ -180,6 +180,19 @@ describe("match", () => {
180180
expect(asFragment()).toMatchSnapshot();
181181
});
182182

183+
it("triple matchers", () => {
184+
const { asFragment } = render(
185+
<RichTextarea value={"Lorem ipsum dolor sit amet"} onChange={NOP}>
186+
{createRegexRenderer([
187+
[/[A-Z][a-z]+/g, { backgroundColor: "lightgray" }],
188+
[/[abcdeip]/g, { color: "red", fontWeight: "bold" }],
189+
[/ipsum/g, { color: "blue", textDecoration: "underline wavy" }],
190+
])}
191+
</RichTextarea>
192+
);
193+
expect(asFragment()).toMatchSnapshot();
194+
});
195+
183196
it("japanese", () => {
184197
const { asFragment } = render(
185198
<RichTextarea

src/utils.spec.ts

-33
This file was deleted.

0 commit comments

Comments
 (0)