@transferwise/components 46.155.0 → 46.156.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.
Files changed (235) hide show
  1. package/build/AvatarLayout/AvatarLayout.js.map +1 -1
  2. package/build/AvatarLayout/AvatarLayout.mjs.map +1 -1
  3. package/build/AvatarView/AvatarView.js.map +1 -1
  4. package/build/AvatarView/AvatarView.mjs.map +1 -1
  5. package/build/AvatarView/Dot/Dot.js.map +1 -1
  6. package/build/AvatarView/Dot/Dot.mjs.map +1 -1
  7. package/build/Body/Body.js.map +1 -1
  8. package/build/Body/Body.mjs.map +1 -1
  9. package/build/Chips/Chip/Chip.js.map +1 -1
  10. package/build/Chips/Chip/Chip.mjs.map +1 -1
  11. package/build/DefinitionList/DefinitionList.js +2 -1
  12. package/build/DefinitionList/DefinitionList.js.map +1 -1
  13. package/build/DefinitionList/DefinitionList.mjs +2 -1
  14. package/build/DefinitionList/DefinitionList.mjs.map +1 -1
  15. package/build/Display/Display.js.map +1 -1
  16. package/build/Display/Display.mjs.map +1 -1
  17. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js +2 -2
  18. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js.map +1 -1
  19. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs +1 -1
  20. package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs.map +1 -1
  21. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js +4 -4
  22. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js.map +1 -1
  23. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs +1 -1
  24. package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs.map +1 -1
  25. package/build/ExpressiveMoneyInput/Chevron/Chevron.js +2 -2
  26. package/build/ExpressiveMoneyInput/Chevron/Chevron.js.map +1 -1
  27. package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs +1 -1
  28. package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs.map +1 -1
  29. package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.js.map +1 -1
  30. package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.mjs.map +1 -1
  31. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js +3 -3
  32. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -1
  33. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs +1 -1
  34. package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -1
  35. package/build/ExpressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
  36. package/build/ExpressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
  37. package/build/IconButton/IconButton.js.map +1 -1
  38. package/build/IconButton/IconButton.mjs.map +1 -1
  39. package/build/Link/Link.js.map +1 -1
  40. package/build/Link/Link.mjs.map +1 -1
  41. package/build/Markup/Markup.js +138 -0
  42. package/build/Markup/Markup.js.map +1 -0
  43. package/build/Markup/Markup.mjs +133 -0
  44. package/build/Markup/Markup.mjs.map +1 -0
  45. package/build/Markup/constants.js +21 -0
  46. package/build/Markup/constants.js.map +1 -0
  47. package/build/Markup/constants.mjs +15 -0
  48. package/build/Markup/constants.mjs.map +1 -0
  49. package/build/Markup/utils/parseMarkup.js +263 -0
  50. package/build/Markup/utils/parseMarkup.js.map +1 -0
  51. package/build/Markup/utils/parseMarkup.mjs +258 -0
  52. package/build/Markup/utils/parseMarkup.mjs.map +1 -0
  53. package/build/Markup/utils/sanitise.js +111 -0
  54. package/build/Markup/utils/sanitise.js.map +1 -0
  55. package/build/Markup/utils/sanitise.mjs +108 -0
  56. package/build/Markup/utils/sanitise.mjs.map +1 -0
  57. package/build/Nudge/Nudge.js +11 -2
  58. package/build/Nudge/Nudge.js.map +1 -1
  59. package/build/Nudge/Nudge.mjs +12 -3
  60. package/build/Nudge/Nudge.mjs.map +1 -1
  61. package/build/Sticky/Sticky.js +1 -0
  62. package/build/Sticky/Sticky.js.map +1 -1
  63. package/build/Sticky/Sticky.mjs +1 -0
  64. package/build/Sticky/Sticky.mjs.map +1 -1
  65. package/build/Title/Title.js +1 -0
  66. package/build/Title/Title.js.map +1 -1
  67. package/build/Title/Title.mjs +1 -0
  68. package/build/Title/Title.mjs.map +1 -1
  69. package/build/_deprecated/Select/Option/Option.js.map +1 -1
  70. package/build/_deprecated/Select/Option/Option.mjs.map +1 -1
  71. package/build/_deprecated/Select/SearchBox/SearchBox.js.map +1 -1
  72. package/build/_deprecated/Select/SearchBox/SearchBox.mjs.map +1 -1
  73. package/build/common/Circle/Circle.js.map +1 -1
  74. package/build/common/Circle/Circle.mjs.map +1 -1
  75. package/build/i18n/hu.json +1 -1
  76. package/build/i18n/hu.json.js +1 -1
  77. package/build/i18n/hu.json.mjs +1 -1
  78. package/build/index.js +2 -0
  79. package/build/index.js.map +1 -1
  80. package/build/index.mjs +1 -0
  81. package/build/index.mjs.map +1 -1
  82. package/build/main.css +71 -1
  83. package/build/styles/DefinitionList/DefinitionList.css +30 -0
  84. package/build/styles/Markup/Markup.css +28 -0
  85. package/build/styles/Markup/_stories/Vulnerability/Vulnerability.css +69 -0
  86. package/build/styles/Nudge/Nudge.css +1 -1
  87. package/build/styles/Sticky/Sticky.css +4 -0
  88. package/build/styles/main.css +71 -1
  89. package/build/types/AvatarLayout/AvatarLayout.d.ts +2 -2
  90. package/build/types/AvatarLayout/AvatarLayout.d.ts.map +1 -1
  91. package/build/types/AvatarLayout/index.d.ts +1 -1
  92. package/build/types/AvatarLayout/index.d.ts.map +1 -1
  93. package/build/types/AvatarView/AvatarView.d.ts +2 -2
  94. package/build/types/AvatarView/AvatarView.d.ts.map +1 -1
  95. package/build/types/AvatarView/Dot/Dot.d.ts +1 -1
  96. package/build/types/AvatarView/Dot/Dot.d.ts.map +1 -1
  97. package/build/types/AvatarView/index.d.ts +1 -1
  98. package/build/types/AvatarView/index.d.ts.map +1 -1
  99. package/build/types/Body/Body.d.ts +13 -2
  100. package/build/types/Body/Body.d.ts.map +1 -1
  101. package/build/types/Body/index.d.ts +1 -0
  102. package/build/types/Body/index.d.ts.map +1 -1
  103. package/build/types/Chips/Chip/Chip.d.ts +2 -2
  104. package/build/types/Chips/Chip/Chip.d.ts.map +1 -1
  105. package/build/types/Chips/index.d.ts +1 -1
  106. package/build/types/Chips/index.d.ts.map +1 -1
  107. package/build/types/DefinitionList/DefinitionList.d.ts.map +1 -1
  108. package/build/types/Display/Display.d.ts +2 -2
  109. package/build/types/Display/Display.d.ts.map +1 -1
  110. package/build/types/Display/index.d.ts +1 -0
  111. package/build/types/Display/index.d.ts.map +1 -1
  112. package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts +3 -3
  113. package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts.map +1 -1
  114. package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts +3 -3
  115. package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts.map +1 -1
  116. package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts +4 -4
  117. package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -1
  118. package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts +1 -1
  119. package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
  120. package/build/types/ExpressiveMoneyInput/index.d.ts +1 -1
  121. package/build/types/ExpressiveMoneyInput/index.d.ts.map +1 -1
  122. package/build/types/IconButton/IconButton.d.ts +2 -2
  123. package/build/types/IconButton/IconButton.d.ts.map +1 -1
  124. package/build/types/IconButton/index.d.ts +1 -1
  125. package/build/types/IconButton/index.d.ts.map +1 -1
  126. package/build/types/Link/Link.d.ts +2 -2
  127. package/build/types/Link/Link.d.ts.map +1 -1
  128. package/build/types/Link/index.d.ts +1 -1
  129. package/build/types/Link/index.d.ts.map +1 -1
  130. package/build/types/Markup/Markup.d.ts +18 -0
  131. package/build/types/Markup/Markup.d.ts.map +1 -0
  132. package/build/types/Markup/Markup.types.d.ts +77 -0
  133. package/build/types/Markup/Markup.types.d.ts.map +1 -0
  134. package/build/types/Markup/_stories/Vulnerability/Vulnerability.d.ts +12 -0
  135. package/build/types/Markup/_stories/Vulnerability/Vulnerability.d.ts.map +1 -0
  136. package/build/types/Markup/constants.d.ts +6 -0
  137. package/build/types/Markup/constants.d.ts.map +1 -0
  138. package/build/types/Markup/index.d.ts +3 -0
  139. package/build/types/Markup/index.d.ts.map +1 -0
  140. package/build/types/Markup/utils/parseMarkup.d.ts +8 -0
  141. package/build/types/Markup/utils/parseMarkup.d.ts.map +1 -0
  142. package/build/types/Markup/utils/sanitise.d.ts +4 -0
  143. package/build/types/Markup/utils/sanitise.d.ts.map +1 -0
  144. package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
  145. package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
  146. package/build/types/Nudge/Nudge.d.ts +28 -5
  147. package/build/types/Nudge/Nudge.d.ts.map +1 -1
  148. package/build/types/Nudge/index.d.ts +1 -1
  149. package/build/types/Nudge/index.d.ts.map +1 -1
  150. package/build/types/Sticky/Sticky.d.ts.map +1 -1
  151. package/build/types/Title/Title.d.ts +13 -3
  152. package/build/types/Title/Title.d.ts.map +1 -1
  153. package/build/types/Title/index.d.ts +1 -0
  154. package/build/types/Title/index.d.ts.map +1 -1
  155. package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
  156. package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
  157. package/build/types/_deprecated/Select/Option/Option.d.ts +2 -2
  158. package/build/types/_deprecated/Select/Option/Option.d.ts.map +1 -1
  159. package/build/types/common/Circle/Circle.d.ts +1 -1
  160. package/build/types/common/Circle/Circle.d.ts.map +1 -1
  161. package/build/types/common/Circle/index.d.ts +1 -1
  162. package/build/types/common/Circle/index.d.ts.map +1 -1
  163. package/build/types/index.d.ts +6 -0
  164. package/build/types/index.d.ts.map +1 -1
  165. package/package.json +3 -3
  166. package/src/AvatarLayout/AvatarLayout.tsx +3 -3
  167. package/src/AvatarLayout/index.ts +1 -1
  168. package/src/AvatarView/AvatarView.tsx +4 -4
  169. package/src/AvatarView/Dot/Dot.tsx +1 -1
  170. package/src/AvatarView/index.ts +1 -1
  171. package/src/Body/Body.tsx +2 -2
  172. package/src/Body/index.ts +1 -0
  173. package/src/Chips/Chip/Chip.tsx +2 -2
  174. package/src/Chips/index.ts +1 -1
  175. package/src/DefinitionList/DefinitionList.css +30 -0
  176. package/src/DefinitionList/DefinitionList.less +24 -0
  177. package/src/DefinitionList/DefinitionList.tsx +1 -0
  178. package/src/DefinitionList/_stories/DefinitionList.test.story.tsx +168 -0
  179. package/src/Display/Display.tsx +2 -2
  180. package/src/Display/index.ts +1 -0
  181. package/src/Drawer/Drawer.test.tsx +2 -3
  182. package/src/ExpressiveMoneyInput/AmountInput/AmountInput.tsx +4 -4
  183. package/src/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.tsx +1 -1
  184. package/src/ExpressiveMoneyInput/Chevron/Chevron.tsx +1 -1
  185. package/src/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.tsx +3 -3
  186. package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.test.tsx +1 -1
  187. package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.tsx +5 -8
  188. package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.story.tsx +1 -1
  189. package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.test.story.tsx +1 -1
  190. package/src/ExpressiveMoneyInput/hooks/useInputStyle.ts +1 -1
  191. package/src/ExpressiveMoneyInput/index.ts +1 -1
  192. package/src/IconButton/IconButton.tsx +2 -2
  193. package/src/IconButton/_stories/IconButton.story.tsx +8 -8
  194. package/src/IconButton/index.ts +1 -1
  195. package/src/Link/Link.tsx +2 -2
  196. package/src/Link/index.ts +1 -1
  197. package/src/Markup/Markup.css +28 -0
  198. package/src/Markup/Markup.injection.test.tsx +163 -0
  199. package/src/Markup/Markup.less +29 -0
  200. package/src/Markup/Markup.test.tsx +502 -0
  201. package/src/Markup/Markup.tsx +175 -0
  202. package/src/Markup/Markup.types.ts +62 -0
  203. package/src/Markup/_stories/Markup.docs.mdx +75 -0
  204. package/src/Markup/_stories/Markup.security.docs.mdx +343 -0
  205. package/src/Markup/_stories/Markup.story.tsx +266 -0
  206. package/src/Markup/_stories/Vulnerability/Vulnerability.css +69 -0
  207. package/src/Markup/_stories/Vulnerability/Vulnerability.tsx +30 -0
  208. package/src/Markup/constants.ts +23 -0
  209. package/src/Markup/index.ts +2 -0
  210. package/src/Markup/utils/parseMarkup.test.ts +706 -0
  211. package/src/Markup/utils/parseMarkup.ts +265 -0
  212. package/src/Markup/utils/sanitise.test.ts +499 -0
  213. package/src/Markup/utils/sanitise.ts +129 -0
  214. package/src/Nudge/Nudge.css +1 -1
  215. package/src/Nudge/Nudge.less +1 -1
  216. package/src/Nudge/Nudge.test.tsx +43 -0
  217. package/src/Nudge/Nudge.tsx +43 -8
  218. package/src/Nudge/index.ts +1 -1
  219. package/src/Sticky/Sticky.css +4 -0
  220. package/src/Sticky/Sticky.less +6 -0
  221. package/src/Sticky/Sticky.test.tsx +1 -0
  222. package/src/Sticky/Sticky.tsx +1 -0
  223. package/src/Sticky/_stories/Sticky.test.story.tsx +177 -0
  224. package/src/Title/Title.tsx +25 -20
  225. package/src/Title/index.ts +1 -0
  226. package/src/_deprecated/LegacyListItem/_stories/LegacyListItem.story.tsx +2 -3
  227. package/src/_deprecated/Select/Option/Option.test.tsx +1 -1
  228. package/src/_deprecated/Select/Option/Option.tsx +2 -2
  229. package/src/_deprecated/Select/SearchBox/SearchBox.tsx +2 -2
  230. package/src/common/Circle/Circle.tsx +2 -2
  231. package/src/common/Circle/index.ts +1 -1
  232. package/src/i18n/hu.json +1 -1
  233. package/src/index.ts +6 -0
  234. package/src/main.css +71 -1
  235. package/src/main.less +2 -0
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ var constants = require('../constants.js');
4
+
5
+ const MAX_INPUT_LENGTH = 10_000;
6
+ const MAX_ACCESSIBILITY_LABEL_LENGTH = 250;
7
+ const OBJECT_PROTOTYPE_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
8
+ // eslint-disable-next-line regexp/no-control-character
9
+ const CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
10
+ const HTML_METACHAR = /[<>"']/g;
11
+ /**
12
+ * Matches opening and closing tags from the ALLOWED_TAGS allow-list.
13
+ *
14
+ * Capture groups:
15
+ * 1. `/` if this is a closing tag, undefined otherwise
16
+ * 2. Tag name
17
+ * 3. Raw attribute string between the tag name and `>` (empty for closing tags)
18
+ *
19
+ * The lookahead `(?=[\s>/])` after the tag name ensures we don't match partial
20
+ * words — e.g. `<stronger>` won't be matched as `<strong>`.
21
+ *
22
+ * Global flag (`g`) allows iterative matching via `exec()`.
23
+ */
24
+ const TAG_REGEX = new RegExp(`<(\\/)?(${constants.ALLOWED_TAGS.join('|')})(?=[\\s>/])((?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)>`, 'g');
25
+ /**
26
+ * Splits the input string into a flat list of tokens by scanning for known tags.
27
+ * Text between tags is emitted as text/newline tokens. Unrecognised tags are never
28
+ * tokenised — they remain part of the surrounding text content.
29
+ */
30
+ function tokenise(input) {
31
+ const tokens = [];
32
+ let lastIndex = 0;
33
+ TAG_REGEX.lastIndex = 0;
34
+ let match = TAG_REGEX.exec(input);
35
+ while (match !== null) {
36
+ if (match.index > lastIndex) {
37
+ pushTextWithNewlines(tokens, input.slice(lastIndex, match.index));
38
+ }
39
+ const isClose = match[1] === '/';
40
+ const tag = match[2];
41
+ const attrs = match[3] || '';
42
+ if (isClose) {
43
+ tokens.push({
44
+ type: 'close',
45
+ tag
46
+ });
47
+ } else {
48
+ tokens.push({
49
+ type: 'open',
50
+ tag,
51
+ attrs
52
+ });
53
+ }
54
+ lastIndex = TAG_REGEX.lastIndex;
55
+ match = TAG_REGEX.exec(input);
56
+ }
57
+ if (lastIndex < input.length) {
58
+ pushTextWithNewlines(tokens, input.slice(lastIndex));
59
+ }
60
+ return tokens;
61
+ }
62
+ /** Splits a text fragment on newline characters, interleaving newline tokens between parts. */
63
+ function pushTextWithNewlines(tokens, text) {
64
+ const parts = text.split('\n');
65
+ for (let index = 0; index < parts.length; index += 1) {
66
+ if (index > 0) {
67
+ tokens.push({
68
+ type: 'newline'
69
+ });
70
+ }
71
+ if (parts[index]) {
72
+ tokens.push({
73
+ type: 'text',
74
+ content: parts[index]
75
+ });
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Recursive descent parser that consumes tokens and builds a MarkupNode tree.
81
+ *
82
+ * When `untilTag` is provided, the function collects nodes until it finds a matching
83
+ * close token — returning `{ closed: true }`. If it exhausts all tokens without finding
84
+ * the close, it returns `{ closed: false }` so the caller can fall back to plain text
85
+ * (graceful degradation for malformed input).
86
+ *
87
+ * The shared `position` object acts as a cursor across recursive calls, avoiding the
88
+ * need to slice or copy the token array.
89
+ */
90
+ function parse(tokens, position, untilTag, depth = 0) {
91
+ const nodes = [];
92
+ while (position.index < tokens.length) {
93
+ const token = tokens[position.index];
94
+ if (token.type === 'text') {
95
+ nodes.push({
96
+ type: 'text',
97
+ content: token.content
98
+ });
99
+ position.index += 1;
100
+ } else if (token.type === 'newline') {
101
+ nodes.push({
102
+ type: 'newline'
103
+ });
104
+ position.index += 1;
105
+ } else if (token.type === 'close') {
106
+ if (token.tag === untilTag) {
107
+ position.index += 1;
108
+ return {
109
+ closed: true,
110
+ nodes
111
+ };
112
+ }
113
+ // Mismatched close tag — render as literal text
114
+ nodes.push({
115
+ type: 'text',
116
+ content: `</${token.tag}>`
117
+ });
118
+ position.index += 1;
119
+ } else if (token.type === 'open') {
120
+ if (depth >= constants.MAX_NESTING_DEPTH) {
121
+ nodes.push({
122
+ type: 'text',
123
+ content: `<${token.tag}${token.attrs}>`
124
+ });
125
+ position.index += 1;
126
+ } else {
127
+ position.index += 1;
128
+ const result = parse(tokens, position, token.tag, depth + 1);
129
+ if (result.closed) {
130
+ const node = buildNode(token.tag, token.attrs, result.nodes);
131
+ nodes.push(node);
132
+ } else {
133
+ nodes.push({
134
+ type: 'text',
135
+ content: `<${token.tag}${token.attrs}>`
136
+ });
137
+ nodes.push(...result.nodes);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ if (untilTag) {
143
+ return {
144
+ closed: false,
145
+ nodes
146
+ };
147
+ }
148
+ return {
149
+ closed: true,
150
+ nodes
151
+ };
152
+ }
153
+ /**
154
+ * Maps a raw tag name to a typed MarkupNode. Resolves aliases first (p→paragraph, a→link),
155
+ * then builds the appropriate node shape. Link nodes carry full attributes; other element
156
+ * nodes extract only accessibilityLabel.
157
+ */
158
+ function buildNode(rawTag, attrs, children) {
159
+ const tag = constants.TAG_ALIASES[rawTag] || rawTag;
160
+ if (tag === 'link') {
161
+ return {
162
+ type: 'link',
163
+ children,
164
+ ...parseAttributes(attrs)
165
+ };
166
+ }
167
+ if (tag === 'paragraph') {
168
+ return {
169
+ type: 'paragraph',
170
+ children
171
+ };
172
+ }
173
+ const {
174
+ accessibilityLabel
175
+ } = parseAttributes(attrs);
176
+ return {
177
+ type: tag,
178
+ children,
179
+ ...(accessibilityLabel && {
180
+ accessibilityLabel
181
+ })
182
+ };
183
+ }
184
+ /**
185
+ * Matches allow-listed attributes from the raw attribute string captured by TAG_REGEX.
186
+ * Requires whitespace before each attribute name to prevent matching inside quoted values.
187
+ *
188
+ * Example: for input ` href="https://wise.com" target="_blank"`
189
+ * - Match 1: groups = ['href', 'https://wise.com', undefined]
190
+ * - Match 2: groups = ['target', '_blank', undefined]
191
+ *
192
+ * Capture groups:
193
+ * 1. Attribute name (href, action, target, or accessibilityLabel)
194
+ * 2. Double-quoted value (if present)
195
+ * 3. Single-quoted value (if present)
196
+ */
197
+ const ATTR_REGEX = /(?<=\s)(href|action|target|accessibilityLabel)=(?:"([^"]*)"|'([^']*)')/g;
198
+ /**
199
+ * Extracts allowed attributes from a raw attribute string.
200
+ * Supports both double- and single-quoted values. Any attribute not in the
201
+ * allow-list (href, action, target, accessibilityLabel) is silently discarded.
202
+ * First occurrence wins — duplicates are ignored.
203
+ */
204
+ function parseAttributes(attrs) {
205
+ const result = {};
206
+ ATTR_REGEX.lastIndex = 0;
207
+ let match = ATTR_REGEX.exec(attrs);
208
+ while (match !== null) {
209
+ const key = match[1];
210
+ const value = match[2] ?? match[3];
211
+ if (!(key in result)) {
212
+ const sanitized = sanitizeAttributeValue(key, value);
213
+ if (sanitized !== undefined) {
214
+ result[key] = sanitized;
215
+ }
216
+ }
217
+ match = ATTR_REGEX.exec(attrs);
218
+ }
219
+ return result;
220
+ }
221
+ function sanitizeAttributeValue(key, value) {
222
+ switch (key) {
223
+ case 'href':
224
+ return value.replace(CONTROL_CHARS, '');
225
+ case 'action':
226
+ if (OBJECT_PROTOTYPE_KEYS.has(value)) {
227
+ return undefined;
228
+ }
229
+ return value;
230
+ case 'target':
231
+ if (!constants.ALLOWED_TARGETS.includes(value)) {
232
+ return undefined;
233
+ }
234
+ return value;
235
+ case 'accessibilityLabel':
236
+ return value.replace(HTML_METACHAR, '').slice(0, MAX_ACCESSIBILITY_LABEL_LENGTH);
237
+ default:
238
+ return undefined;
239
+ }
240
+ }
241
+ /** Entry point: tokenises the input string, then parses the token stream into a MarkupNode tree. */
242
+ function parseMarkup(input) {
243
+ if (!input) {
244
+ return [];
245
+ }
246
+ if (input.length > MAX_INPUT_LENGTH) {
247
+ return [{
248
+ type: 'text',
249
+ content: input
250
+ }];
251
+ }
252
+ const tokens = tokenise(input);
253
+ const position = {
254
+ index: 0
255
+ };
256
+ return parse(tokens, position).nodes;
257
+ }
258
+
259
+ exports.MAX_NESTING_DEPTH = constants.MAX_NESTING_DEPTH;
260
+ exports.MAX_ACCESSIBILITY_LABEL_LENGTH = MAX_ACCESSIBILITY_LABEL_LENGTH;
261
+ exports.MAX_INPUT_LENGTH = MAX_INPUT_LENGTH;
262
+ exports.parseMarkup = parseMarkup;
263
+ //# sourceMappingURL=parseMarkup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseMarkup.js","sources":["../../../src/Markup/utils/parseMarkup.ts"],"sourcesContent":["import { ALLOWED_TAGS, ALLOWED_TARGETS, MAX_NESTING_DEPTH, TAG_ALIASES } from '../constants';\nimport { MarkupNode } from '../Markup.types';\n\nexport { MAX_NESTING_DEPTH };\nexport const MAX_INPUT_LENGTH = 10_000;\nexport const MAX_ACCESSIBILITY_LABEL_LENGTH = 250;\n\nconst OBJECT_PROTOTYPE_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));\n\n// eslint-disable-next-line regexp/no-control-character\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f-\\x9f]/g;\nconst HTML_METACHAR = /[<>\"']/g;\n\n/**\n * Matches opening and closing tags from the ALLOWED_TAGS allow-list.\n *\n * Capture groups:\n * 1. `/` if this is a closing tag, undefined otherwise\n * 2. Tag name\n * 3. Raw attribute string between the tag name and `>` (empty for closing tags)\n *\n * The lookahead `(?=[\\s>/])` after the tag name ensures we don't match partial\n * words — e.g. `<stronger>` won't be matched as `<strong>`.\n *\n * Global flag (`g`) allows iterative matching via `exec()`.\n */\nconst TAG_REGEX = new RegExp(\n `<(\\\\/)?(${ALLOWED_TAGS.join('|')})(?=[\\\\s>/])((?:[^>\"']*(?:\"[^\"]*\"|'[^']*'))*[^>\"']*)>`,\n 'g',\n);\n\ntype Token =\n | { type: 'text'; content: string }\n | { type: 'newline' }\n | { type: 'open'; tag: string; attrs: string }\n | { type: 'close'; tag: string };\n\n/**\n * Splits the input string into a flat list of tokens by scanning for known tags.\n * Text between tags is emitted as text/newline tokens. Unrecognised tags are never\n * tokenised — they remain part of the surrounding text content.\n */\nfunction tokenise(input: string): Token[] {\n const tokens: Token[] = [];\n let lastIndex = 0;\n\n TAG_REGEX.lastIndex = 0;\n let match = TAG_REGEX.exec(input);\n\n while (match !== null) {\n if (match.index > lastIndex) {\n pushTextWithNewlines(tokens, input.slice(lastIndex, match.index));\n }\n\n const isClose = match[1] === '/';\n const tag = match[2];\n const attrs = match[3] || '';\n\n if (isClose) {\n tokens.push({ type: 'close', tag });\n } else {\n tokens.push({ type: 'open', tag, attrs });\n }\n\n lastIndex = TAG_REGEX.lastIndex;\n match = TAG_REGEX.exec(input);\n }\n\n if (lastIndex < input.length) {\n pushTextWithNewlines(tokens, input.slice(lastIndex));\n }\n\n return tokens;\n}\n\n/** Splits a text fragment on newline characters, interleaving newline tokens between parts. */\nfunction pushTextWithNewlines(tokens: Token[], text: string): void {\n const parts = text.split('\\n');\n for (let index = 0; index < parts.length; index += 1) {\n if (index > 0) {\n tokens.push({ type: 'newline' });\n }\n if (parts[index]) {\n tokens.push({ type: 'text', content: parts[index] });\n }\n }\n}\n\ntype ParseResult = { closed: true; nodes: MarkupNode[] } | { closed: false; nodes: MarkupNode[] };\n\n/**\n * Recursive descent parser that consumes tokens and builds a MarkupNode tree.\n *\n * When `untilTag` is provided, the function collects nodes until it finds a matching\n * close token — returning `{ closed: true }`. If it exhausts all tokens without finding\n * the close, it returns `{ closed: false }` so the caller can fall back to plain text\n * (graceful degradation for malformed input).\n *\n * The shared `position` object acts as a cursor across recursive calls, avoiding the\n * need to slice or copy the token array.\n */\nfunction parse(\n tokens: Token[],\n position: { index: number },\n untilTag?: string,\n depth = 0,\n): ParseResult {\n const nodes: MarkupNode[] = [];\n\n while (position.index < tokens.length) {\n const token = tokens[position.index];\n\n if (token.type === 'text') {\n nodes.push({ type: 'text', content: token.content });\n position.index += 1;\n } else if (token.type === 'newline') {\n nodes.push({ type: 'newline' });\n position.index += 1;\n } else if (token.type === 'close') {\n if (token.tag === untilTag) {\n position.index += 1;\n return { closed: true, nodes };\n }\n // Mismatched close tag — render as literal text\n nodes.push({ type: 'text', content: `</${token.tag}>` });\n position.index += 1;\n } else if (token.type === 'open') {\n if (depth >= MAX_NESTING_DEPTH) {\n nodes.push({ type: 'text', content: `<${token.tag}${token.attrs}>` });\n position.index += 1;\n } else {\n position.index += 1;\n const result = parse(tokens, position, token.tag, depth + 1);\n\n if (result.closed) {\n const node = buildNode(token.tag, token.attrs, result.nodes);\n nodes.push(node);\n } else {\n nodes.push({ type: 'text', content: `<${token.tag}${token.attrs}>` });\n nodes.push(...result.nodes);\n }\n }\n }\n }\n\n if (untilTag) {\n return { closed: false, nodes };\n }\n\n return { closed: true, nodes };\n}\n\n/**\n * Maps a raw tag name to a typed MarkupNode. Resolves aliases first (p→paragraph, a→link),\n * then builds the appropriate node shape. Link nodes carry full attributes; other element\n * nodes extract only accessibilityLabel.\n */\nfunction buildNode(rawTag: string, attrs: string, children: MarkupNode[]): MarkupNode {\n const tag = TAG_ALIASES[rawTag] || rawTag;\n\n if (tag === 'link') {\n return { type: 'link', children, ...parseAttributes(attrs) };\n }\n\n if (tag === 'paragraph') {\n return { type: 'paragraph', children };\n }\n\n const { accessibilityLabel } = parseAttributes(attrs);\n\n return {\n type: tag as MarkupNode['type'],\n children,\n ...(accessibilityLabel && { accessibilityLabel }),\n } as MarkupNode;\n}\n\n/**\n * Matches allow-listed attributes from the raw attribute string captured by TAG_REGEX.\n * Requires whitespace before each attribute name to prevent matching inside quoted values.\n *\n * Example: for input ` href=\"https://wise.com\" target=\"_blank\"`\n * - Match 1: groups = ['href', 'https://wise.com', undefined]\n * - Match 2: groups = ['target', '_blank', undefined]\n *\n * Capture groups:\n * 1. Attribute name (href, action, target, or accessibilityLabel)\n * 2. Double-quoted value (if present)\n * 3. Single-quoted value (if present)\n */\nconst ATTR_REGEX = /(?<=\\s)(href|action|target|accessibilityLabel)=(?:\"([^\"]*)\"|'([^']*)')/g;\n\ntype ParsedAttributes = {\n href?: string;\n action?: string;\n target?: string;\n accessibilityLabel?: string;\n};\n\n/**\n * Extracts allowed attributes from a raw attribute string.\n * Supports both double- and single-quoted values. Any attribute not in the\n * allow-list (href, action, target, accessibilityLabel) is silently discarded.\n * First occurrence wins — duplicates are ignored.\n */\nfunction parseAttributes(attrs: string): ParsedAttributes {\n const result: ParsedAttributes = {};\n ATTR_REGEX.lastIndex = 0;\n let match = ATTR_REGEX.exec(attrs);\n\n while (match !== null) {\n const key = match[1] as 'href' | 'action' | 'target' | 'accessibilityLabel';\n const value = match[2] ?? match[3];\n\n if (!(key in result)) {\n const sanitized = sanitizeAttributeValue(key, value);\n if (sanitized !== undefined) {\n result[key] = sanitized;\n }\n }\n\n match = ATTR_REGEX.exec(attrs);\n }\n\n return result;\n}\n\nfunction sanitizeAttributeValue(\n key: 'href' | 'action' | 'target' | 'accessibilityLabel',\n value: string,\n): string | undefined {\n switch (key) {\n case 'href':\n return value.replace(CONTROL_CHARS, '');\n case 'action':\n if (OBJECT_PROTOTYPE_KEYS.has(value)) {\n return undefined;\n }\n return value;\n case 'target':\n if (!ALLOWED_TARGETS.includes(value)) {\n return undefined;\n }\n return value;\n case 'accessibilityLabel':\n return value.replace(HTML_METACHAR, '').slice(0, MAX_ACCESSIBILITY_LABEL_LENGTH);\n default:\n return undefined;\n }\n}\n\n/** Entry point: tokenises the input string, then parses the token stream into a MarkupNode tree. */\nexport function parseMarkup(input: string): MarkupNode[] {\n if (!input) {\n return [];\n }\n\n if (input.length > MAX_INPUT_LENGTH) {\n return [{ type: 'text', content: input }];\n }\n\n const tokens = tokenise(input);\n const position = { index: 0 };\n return parse(tokens, position).nodes;\n}\n"],"names":["MAX_INPUT_LENGTH","MAX_ACCESSIBILITY_LABEL_LENGTH","OBJECT_PROTOTYPE_KEYS","Set","Object","getOwnPropertyNames","prototype","CONTROL_CHARS","HTML_METACHAR","TAG_REGEX","RegExp","ALLOWED_TAGS","join","tokenise","input","tokens","lastIndex","match","exec","index","pushTextWithNewlines","slice","isClose","tag","attrs","push","type","length","text","parts","split","content","parse","position","untilTag","depth","nodes","token","closed","MAX_NESTING_DEPTH","result","node","buildNode","rawTag","children","TAG_ALIASES","parseAttributes","accessibilityLabel","ATTR_REGEX","key","value","sanitized","sanitizeAttributeValue","undefined","replace","has","ALLOWED_TARGETS","includes","parseMarkup"],"mappings":"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"}
@@ -0,0 +1,258 @@
1
+ import { MAX_NESTING_DEPTH, ALLOWED_TAGS, TAG_ALIASES, ALLOWED_TARGETS } from '../constants.mjs';
2
+
3
+ const MAX_INPUT_LENGTH = 10_000;
4
+ const MAX_ACCESSIBILITY_LABEL_LENGTH = 250;
5
+ const OBJECT_PROTOTYPE_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));
6
+ // eslint-disable-next-line regexp/no-control-character
7
+ const CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
8
+ const HTML_METACHAR = /[<>"']/g;
9
+ /**
10
+ * Matches opening and closing tags from the ALLOWED_TAGS allow-list.
11
+ *
12
+ * Capture groups:
13
+ * 1. `/` if this is a closing tag, undefined otherwise
14
+ * 2. Tag name
15
+ * 3. Raw attribute string between the tag name and `>` (empty for closing tags)
16
+ *
17
+ * The lookahead `(?=[\s>/])` after the tag name ensures we don't match partial
18
+ * words — e.g. `<stronger>` won't be matched as `<strong>`.
19
+ *
20
+ * Global flag (`g`) allows iterative matching via `exec()`.
21
+ */
22
+ const TAG_REGEX = new RegExp(`<(\\/)?(${ALLOWED_TAGS.join('|')})(?=[\\s>/])((?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)>`, 'g');
23
+ /**
24
+ * Splits the input string into a flat list of tokens by scanning for known tags.
25
+ * Text between tags is emitted as text/newline tokens. Unrecognised tags are never
26
+ * tokenised — they remain part of the surrounding text content.
27
+ */
28
+ function tokenise(input) {
29
+ const tokens = [];
30
+ let lastIndex = 0;
31
+ TAG_REGEX.lastIndex = 0;
32
+ let match = TAG_REGEX.exec(input);
33
+ while (match !== null) {
34
+ if (match.index > lastIndex) {
35
+ pushTextWithNewlines(tokens, input.slice(lastIndex, match.index));
36
+ }
37
+ const isClose = match[1] === '/';
38
+ const tag = match[2];
39
+ const attrs = match[3] || '';
40
+ if (isClose) {
41
+ tokens.push({
42
+ type: 'close',
43
+ tag
44
+ });
45
+ } else {
46
+ tokens.push({
47
+ type: 'open',
48
+ tag,
49
+ attrs
50
+ });
51
+ }
52
+ lastIndex = TAG_REGEX.lastIndex;
53
+ match = TAG_REGEX.exec(input);
54
+ }
55
+ if (lastIndex < input.length) {
56
+ pushTextWithNewlines(tokens, input.slice(lastIndex));
57
+ }
58
+ return tokens;
59
+ }
60
+ /** Splits a text fragment on newline characters, interleaving newline tokens between parts. */
61
+ function pushTextWithNewlines(tokens, text) {
62
+ const parts = text.split('\n');
63
+ for (let index = 0; index < parts.length; index += 1) {
64
+ if (index > 0) {
65
+ tokens.push({
66
+ type: 'newline'
67
+ });
68
+ }
69
+ if (parts[index]) {
70
+ tokens.push({
71
+ type: 'text',
72
+ content: parts[index]
73
+ });
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Recursive descent parser that consumes tokens and builds a MarkupNode tree.
79
+ *
80
+ * When `untilTag` is provided, the function collects nodes until it finds a matching
81
+ * close token — returning `{ closed: true }`. If it exhausts all tokens without finding
82
+ * the close, it returns `{ closed: false }` so the caller can fall back to plain text
83
+ * (graceful degradation for malformed input).
84
+ *
85
+ * The shared `position` object acts as a cursor across recursive calls, avoiding the
86
+ * need to slice or copy the token array.
87
+ */
88
+ function parse(tokens, position, untilTag, depth = 0) {
89
+ const nodes = [];
90
+ while (position.index < tokens.length) {
91
+ const token = tokens[position.index];
92
+ if (token.type === 'text') {
93
+ nodes.push({
94
+ type: 'text',
95
+ content: token.content
96
+ });
97
+ position.index += 1;
98
+ } else if (token.type === 'newline') {
99
+ nodes.push({
100
+ type: 'newline'
101
+ });
102
+ position.index += 1;
103
+ } else if (token.type === 'close') {
104
+ if (token.tag === untilTag) {
105
+ position.index += 1;
106
+ return {
107
+ closed: true,
108
+ nodes
109
+ };
110
+ }
111
+ // Mismatched close tag — render as literal text
112
+ nodes.push({
113
+ type: 'text',
114
+ content: `</${token.tag}>`
115
+ });
116
+ position.index += 1;
117
+ } else if (token.type === 'open') {
118
+ if (depth >= MAX_NESTING_DEPTH) {
119
+ nodes.push({
120
+ type: 'text',
121
+ content: `<${token.tag}${token.attrs}>`
122
+ });
123
+ position.index += 1;
124
+ } else {
125
+ position.index += 1;
126
+ const result = parse(tokens, position, token.tag, depth + 1);
127
+ if (result.closed) {
128
+ const node = buildNode(token.tag, token.attrs, result.nodes);
129
+ nodes.push(node);
130
+ } else {
131
+ nodes.push({
132
+ type: 'text',
133
+ content: `<${token.tag}${token.attrs}>`
134
+ });
135
+ nodes.push(...result.nodes);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ if (untilTag) {
141
+ return {
142
+ closed: false,
143
+ nodes
144
+ };
145
+ }
146
+ return {
147
+ closed: true,
148
+ nodes
149
+ };
150
+ }
151
+ /**
152
+ * Maps a raw tag name to a typed MarkupNode. Resolves aliases first (p→paragraph, a→link),
153
+ * then builds the appropriate node shape. Link nodes carry full attributes; other element
154
+ * nodes extract only accessibilityLabel.
155
+ */
156
+ function buildNode(rawTag, attrs, children) {
157
+ const tag = TAG_ALIASES[rawTag] || rawTag;
158
+ if (tag === 'link') {
159
+ return {
160
+ type: 'link',
161
+ children,
162
+ ...parseAttributes(attrs)
163
+ };
164
+ }
165
+ if (tag === 'paragraph') {
166
+ return {
167
+ type: 'paragraph',
168
+ children
169
+ };
170
+ }
171
+ const {
172
+ accessibilityLabel
173
+ } = parseAttributes(attrs);
174
+ return {
175
+ type: tag,
176
+ children,
177
+ ...(accessibilityLabel && {
178
+ accessibilityLabel
179
+ })
180
+ };
181
+ }
182
+ /**
183
+ * Matches allow-listed attributes from the raw attribute string captured by TAG_REGEX.
184
+ * Requires whitespace before each attribute name to prevent matching inside quoted values.
185
+ *
186
+ * Example: for input ` href="https://wise.com" target="_blank"`
187
+ * - Match 1: groups = ['href', 'https://wise.com', undefined]
188
+ * - Match 2: groups = ['target', '_blank', undefined]
189
+ *
190
+ * Capture groups:
191
+ * 1. Attribute name (href, action, target, or accessibilityLabel)
192
+ * 2. Double-quoted value (if present)
193
+ * 3. Single-quoted value (if present)
194
+ */
195
+ const ATTR_REGEX = /(?<=\s)(href|action|target|accessibilityLabel)=(?:"([^"]*)"|'([^']*)')/g;
196
+ /**
197
+ * Extracts allowed attributes from a raw attribute string.
198
+ * Supports both double- and single-quoted values. Any attribute not in the
199
+ * allow-list (href, action, target, accessibilityLabel) is silently discarded.
200
+ * First occurrence wins — duplicates are ignored.
201
+ */
202
+ function parseAttributes(attrs) {
203
+ const result = {};
204
+ ATTR_REGEX.lastIndex = 0;
205
+ let match = ATTR_REGEX.exec(attrs);
206
+ while (match !== null) {
207
+ const key = match[1];
208
+ const value = match[2] ?? match[3];
209
+ if (!(key in result)) {
210
+ const sanitized = sanitizeAttributeValue(key, value);
211
+ if (sanitized !== undefined) {
212
+ result[key] = sanitized;
213
+ }
214
+ }
215
+ match = ATTR_REGEX.exec(attrs);
216
+ }
217
+ return result;
218
+ }
219
+ function sanitizeAttributeValue(key, value) {
220
+ switch (key) {
221
+ case 'href':
222
+ return value.replace(CONTROL_CHARS, '');
223
+ case 'action':
224
+ if (OBJECT_PROTOTYPE_KEYS.has(value)) {
225
+ return undefined;
226
+ }
227
+ return value;
228
+ case 'target':
229
+ if (!ALLOWED_TARGETS.includes(value)) {
230
+ return undefined;
231
+ }
232
+ return value;
233
+ case 'accessibilityLabel':
234
+ return value.replace(HTML_METACHAR, '').slice(0, MAX_ACCESSIBILITY_LABEL_LENGTH);
235
+ default:
236
+ return undefined;
237
+ }
238
+ }
239
+ /** Entry point: tokenises the input string, then parses the token stream into a MarkupNode tree. */
240
+ function parseMarkup(input) {
241
+ if (!input) {
242
+ return [];
243
+ }
244
+ if (input.length > MAX_INPUT_LENGTH) {
245
+ return [{
246
+ type: 'text',
247
+ content: input
248
+ }];
249
+ }
250
+ const tokens = tokenise(input);
251
+ const position = {
252
+ index: 0
253
+ };
254
+ return parse(tokens, position).nodes;
255
+ }
256
+
257
+ export { MAX_ACCESSIBILITY_LABEL_LENGTH, MAX_INPUT_LENGTH, MAX_NESTING_DEPTH, parseMarkup };
258
+ //# sourceMappingURL=parseMarkup.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseMarkup.mjs","sources":["../../../src/Markup/utils/parseMarkup.ts"],"sourcesContent":["import { ALLOWED_TAGS, ALLOWED_TARGETS, MAX_NESTING_DEPTH, TAG_ALIASES } from '../constants';\nimport { MarkupNode } from '../Markup.types';\n\nexport { MAX_NESTING_DEPTH };\nexport const MAX_INPUT_LENGTH = 10_000;\nexport const MAX_ACCESSIBILITY_LABEL_LENGTH = 250;\n\nconst OBJECT_PROTOTYPE_KEYS = new Set(Object.getOwnPropertyNames(Object.prototype));\n\n// eslint-disable-next-line regexp/no-control-character\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f-\\x9f]/g;\nconst HTML_METACHAR = /[<>\"']/g;\n\n/**\n * Matches opening and closing tags from the ALLOWED_TAGS allow-list.\n *\n * Capture groups:\n * 1. `/` if this is a closing tag, undefined otherwise\n * 2. Tag name\n * 3. Raw attribute string between the tag name and `>` (empty for closing tags)\n *\n * The lookahead `(?=[\\s>/])` after the tag name ensures we don't match partial\n * words — e.g. `<stronger>` won't be matched as `<strong>`.\n *\n * Global flag (`g`) allows iterative matching via `exec()`.\n */\nconst TAG_REGEX = new RegExp(\n `<(\\\\/)?(${ALLOWED_TAGS.join('|')})(?=[\\\\s>/])((?:[^>\"']*(?:\"[^\"]*\"|'[^']*'))*[^>\"']*)>`,\n 'g',\n);\n\ntype Token =\n | { type: 'text'; content: string }\n | { type: 'newline' }\n | { type: 'open'; tag: string; attrs: string }\n | { type: 'close'; tag: string };\n\n/**\n * Splits the input string into a flat list of tokens by scanning for known tags.\n * Text between tags is emitted as text/newline tokens. Unrecognised tags are never\n * tokenised — they remain part of the surrounding text content.\n */\nfunction tokenise(input: string): Token[] {\n const tokens: Token[] = [];\n let lastIndex = 0;\n\n TAG_REGEX.lastIndex = 0;\n let match = TAG_REGEX.exec(input);\n\n while (match !== null) {\n if (match.index > lastIndex) {\n pushTextWithNewlines(tokens, input.slice(lastIndex, match.index));\n }\n\n const isClose = match[1] === '/';\n const tag = match[2];\n const attrs = match[3] || '';\n\n if (isClose) {\n tokens.push({ type: 'close', tag });\n } else {\n tokens.push({ type: 'open', tag, attrs });\n }\n\n lastIndex = TAG_REGEX.lastIndex;\n match = TAG_REGEX.exec(input);\n }\n\n if (lastIndex < input.length) {\n pushTextWithNewlines(tokens, input.slice(lastIndex));\n }\n\n return tokens;\n}\n\n/** Splits a text fragment on newline characters, interleaving newline tokens between parts. */\nfunction pushTextWithNewlines(tokens: Token[], text: string): void {\n const parts = text.split('\\n');\n for (let index = 0; index < parts.length; index += 1) {\n if (index > 0) {\n tokens.push({ type: 'newline' });\n }\n if (parts[index]) {\n tokens.push({ type: 'text', content: parts[index] });\n }\n }\n}\n\ntype ParseResult = { closed: true; nodes: MarkupNode[] } | { closed: false; nodes: MarkupNode[] };\n\n/**\n * Recursive descent parser that consumes tokens and builds a MarkupNode tree.\n *\n * When `untilTag` is provided, the function collects nodes until it finds a matching\n * close token — returning `{ closed: true }`. If it exhausts all tokens without finding\n * the close, it returns `{ closed: false }` so the caller can fall back to plain text\n * (graceful degradation for malformed input).\n *\n * The shared `position` object acts as a cursor across recursive calls, avoiding the\n * need to slice or copy the token array.\n */\nfunction parse(\n tokens: Token[],\n position: { index: number },\n untilTag?: string,\n depth = 0,\n): ParseResult {\n const nodes: MarkupNode[] = [];\n\n while (position.index < tokens.length) {\n const token = tokens[position.index];\n\n if (token.type === 'text') {\n nodes.push({ type: 'text', content: token.content });\n position.index += 1;\n } else if (token.type === 'newline') {\n nodes.push({ type: 'newline' });\n position.index += 1;\n } else if (token.type === 'close') {\n if (token.tag === untilTag) {\n position.index += 1;\n return { closed: true, nodes };\n }\n // Mismatched close tag — render as literal text\n nodes.push({ type: 'text', content: `</${token.tag}>` });\n position.index += 1;\n } else if (token.type === 'open') {\n if (depth >= MAX_NESTING_DEPTH) {\n nodes.push({ type: 'text', content: `<${token.tag}${token.attrs}>` });\n position.index += 1;\n } else {\n position.index += 1;\n const result = parse(tokens, position, token.tag, depth + 1);\n\n if (result.closed) {\n const node = buildNode(token.tag, token.attrs, result.nodes);\n nodes.push(node);\n } else {\n nodes.push({ type: 'text', content: `<${token.tag}${token.attrs}>` });\n nodes.push(...result.nodes);\n }\n }\n }\n }\n\n if (untilTag) {\n return { closed: false, nodes };\n }\n\n return { closed: true, nodes };\n}\n\n/**\n * Maps a raw tag name to a typed MarkupNode. Resolves aliases first (p→paragraph, a→link),\n * then builds the appropriate node shape. Link nodes carry full attributes; other element\n * nodes extract only accessibilityLabel.\n */\nfunction buildNode(rawTag: string, attrs: string, children: MarkupNode[]): MarkupNode {\n const tag = TAG_ALIASES[rawTag] || rawTag;\n\n if (tag === 'link') {\n return { type: 'link', children, ...parseAttributes(attrs) };\n }\n\n if (tag === 'paragraph') {\n return { type: 'paragraph', children };\n }\n\n const { accessibilityLabel } = parseAttributes(attrs);\n\n return {\n type: tag as MarkupNode['type'],\n children,\n ...(accessibilityLabel && { accessibilityLabel }),\n } as MarkupNode;\n}\n\n/**\n * Matches allow-listed attributes from the raw attribute string captured by TAG_REGEX.\n * Requires whitespace before each attribute name to prevent matching inside quoted values.\n *\n * Example: for input ` href=\"https://wise.com\" target=\"_blank\"`\n * - Match 1: groups = ['href', 'https://wise.com', undefined]\n * - Match 2: groups = ['target', '_blank', undefined]\n *\n * Capture groups:\n * 1. Attribute name (href, action, target, or accessibilityLabel)\n * 2. Double-quoted value (if present)\n * 3. Single-quoted value (if present)\n */\nconst ATTR_REGEX = /(?<=\\s)(href|action|target|accessibilityLabel)=(?:\"([^\"]*)\"|'([^']*)')/g;\n\ntype ParsedAttributes = {\n href?: string;\n action?: string;\n target?: string;\n accessibilityLabel?: string;\n};\n\n/**\n * Extracts allowed attributes from a raw attribute string.\n * Supports both double- and single-quoted values. Any attribute not in the\n * allow-list (href, action, target, accessibilityLabel) is silently discarded.\n * First occurrence wins — duplicates are ignored.\n */\nfunction parseAttributes(attrs: string): ParsedAttributes {\n const result: ParsedAttributes = {};\n ATTR_REGEX.lastIndex = 0;\n let match = ATTR_REGEX.exec(attrs);\n\n while (match !== null) {\n const key = match[1] as 'href' | 'action' | 'target' | 'accessibilityLabel';\n const value = match[2] ?? match[3];\n\n if (!(key in result)) {\n const sanitized = sanitizeAttributeValue(key, value);\n if (sanitized !== undefined) {\n result[key] = sanitized;\n }\n }\n\n match = ATTR_REGEX.exec(attrs);\n }\n\n return result;\n}\n\nfunction sanitizeAttributeValue(\n key: 'href' | 'action' | 'target' | 'accessibilityLabel',\n value: string,\n): string | undefined {\n switch (key) {\n case 'href':\n return value.replace(CONTROL_CHARS, '');\n case 'action':\n if (OBJECT_PROTOTYPE_KEYS.has(value)) {\n return undefined;\n }\n return value;\n case 'target':\n if (!ALLOWED_TARGETS.includes(value)) {\n return undefined;\n }\n return value;\n case 'accessibilityLabel':\n return value.replace(HTML_METACHAR, '').slice(0, MAX_ACCESSIBILITY_LABEL_LENGTH);\n default:\n return undefined;\n }\n}\n\n/** Entry point: tokenises the input string, then parses the token stream into a MarkupNode tree. */\nexport function parseMarkup(input: string): MarkupNode[] {\n if (!input) {\n return [];\n }\n\n if (input.length > MAX_INPUT_LENGTH) {\n return [{ type: 'text', content: input }];\n }\n\n const tokens = tokenise(input);\n const position = { index: 0 };\n return parse(tokens, position).nodes;\n}\n"],"names":["MAX_INPUT_LENGTH","MAX_ACCESSIBILITY_LABEL_LENGTH","OBJECT_PROTOTYPE_KEYS","Set","Object","getOwnPropertyNames","prototype","CONTROL_CHARS","HTML_METACHAR","TAG_REGEX","RegExp","ALLOWED_TAGS","join","tokenise","input","tokens","lastIndex","match","exec","index","pushTextWithNewlines","slice","isClose","tag","attrs","push","type","length","text","parts","split","content","parse","position","untilTag","depth","nodes","token","closed","MAX_NESTING_DEPTH","result","node","buildNode","rawTag","children","TAG_ALIASES","parseAttributes","accessibilityLabel","ATTR_REGEX","key","value","sanitized","sanitizeAttributeValue","undefined","replace","has","ALLOWED_TARGETS","includes","parseMarkup"],"mappings":"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"}