Skip to content

Commit fa4fcb8

Browse files
authored
feat(intl): support self-closing tags in rich-text formatter (#185)
1 parent 25cd26d commit fa4fcb8

3 files changed

Lines changed: 53 additions & 4 deletions

File tree

packages/intl/.size-limit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "All publics",
44
"path": "dist/index.js",
55
"import": "*",
6-
"limit": "1.8 kB"
6+
"limit": "1.85 kB"
77
},
88
{
99
"name": "Minimal set",

packages/intl/src/format/rich.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ const tags = {
2424
strong: (chunks: (string | Node)[]) => ({
2525
type: 'strong',
2626
children: chunks
27+
}),
28+
br: () => ({
29+
type: 'br',
30+
children: []
2731
})
2832
}
2933

@@ -93,6 +97,48 @@ describe('intl', () => {
9397
'guidelines.'
9498
])
9599
})
100+
101+
it('should map self-closing tags', () => {
102+
expect(mapTags('First line<br/>second line', tags)).toEqual([
103+
'First line',
104+
{
105+
type: 'br',
106+
children: []
107+
},
108+
'second line'
109+
])
110+
expect(mapTags('First line<br />second line', tags)).toEqual([
111+
'First line',
112+
{
113+
type: 'br',
114+
children: []
115+
},
116+
'second line'
117+
])
118+
})
119+
120+
it('should map self-closing tags inside other tags', () => {
121+
expect(mapTags('<strong>First<br/>second</strong>', tags)).toEqual([
122+
{
123+
type: 'strong',
124+
children: [
125+
'First',
126+
{
127+
type: 'br',
128+
children: []
129+
},
130+
'second'
131+
]
132+
}
133+
])
134+
})
135+
136+
it('should ignore unknown self-closing tags', () => {
137+
expect(mapTags('First<hr/>second', tags)).toEqual([
138+
'First',
139+
'second'
140+
])
141+
})
96142
})
97143
})
98144
})

packages/intl/src/format/rich.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function rich<T>(
6363
}
6464
}
6565

66-
const tagRegex = /<\/?([A-Za-z][\w.-]*)>/g
66+
const tagRegex = /<(\/?)([A-Za-z][\w.-]*)\s*(\/?)>/g
6767

6868
interface RichFrame<T> {
6969
tag: string
@@ -72,6 +72,7 @@ interface RichFrame<T> {
7272

7373
/**
7474
* Maps rich-text tags in a string to chunks using provided tag handlers.
75+
* Self-closing tags are mapped with empty chunks.
7576
* Unknown or malformed tags are ignored as markup and only their text content is kept.
7677
* @param input - Rich-text input string.
7778
* @param tags - Tag handlers used to map known tags.
@@ -91,21 +92,23 @@ export function mapTags<T>(
9192
let match: RegExpExecArray | null
9293

9394
while (match = tagRegex.exec(input)) {
94-
const [rawTag, tag] = match
95+
const [, closing, tag, selfClosing] = match
9596
const current = stack[stack.length - 1]
9697

9798
if (match.index > lastIndex) {
9899
current.chunks.push(input.slice(lastIndex, match.index))
99100
}
100101

101102
if (tag in tags) {
102-
if (rawTag[1] === '/') {
103+
if (closing) {
103104
if (current.tag === tag) {
104105
stack.pop()
105106
stack[stack.length - 1].chunks.push(
106107
tags[tag](current.chunks)
107108
)
108109
}
110+
} else if (selfClosing) {
111+
current.chunks.push(tags[tag]([]))
109112
} else {
110113
stack.push({
111114
tag,

0 commit comments

Comments
 (0)