tempest-react-sdk 0.29.0 → 0.30.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 (68) hide show
  1. package/bin/lib/alias/index.mjs +9 -3
  2. package/bin/lib/alias/tsconfig.mjs +48 -5
  3. package/bin/lib/alias/typescript.mjs +72 -2
  4. package/bin/lib/alias/typescript.test.mjs +120 -0
  5. package/bin/lib/css/extract.mjs +2 -3
  6. package/bin/lib/doctor/doctor.e2e.test.mjs +44 -0
  7. package/bin/tempest.mjs +17 -2
  8. package/dist/components/Chat/Chat.cjs +2 -0
  9. package/dist/components/Chat/Chat.cjs.map +1 -0
  10. package/dist/components/Chat/Chat.js +126 -0
  11. package/dist/components/Chat/Chat.js.map +1 -0
  12. package/dist/components/Chat/Chat.module.cjs +2 -0
  13. package/dist/components/Chat/Chat.module.cjs.map +1 -0
  14. package/dist/components/Chat/Chat.module.js +32 -0
  15. package/dist/components/Chat/Chat.module.js.map +1 -0
  16. package/dist/components/Chat/ChatComposer.cjs +2 -0
  17. package/dist/components/Chat/ChatComposer.cjs.map +1 -0
  18. package/dist/components/Chat/ChatComposer.js +67 -0
  19. package/dist/components/Chat/ChatComposer.js.map +1 -0
  20. package/dist/components/Chat/chat-groups.cjs +2 -0
  21. package/dist/components/Chat/chat-groups.cjs.map +1 -0
  22. package/dist/components/Chat/chat-groups.js +89 -0
  23. package/dist/components/Chat/chat-groups.js.map +1 -0
  24. package/dist/components/Markdown/Markdown.cjs +2 -0
  25. package/dist/components/Markdown/Markdown.cjs.map +1 -0
  26. package/dist/components/Markdown/Markdown.js +124 -0
  27. package/dist/components/Markdown/Markdown.js.map +1 -0
  28. package/dist/components/Markdown/Markdown.module.cjs +2 -0
  29. package/dist/components/Markdown/Markdown.module.cjs.map +1 -0
  30. package/dist/components/Markdown/Markdown.module.js +23 -0
  31. package/dist/components/Markdown/Markdown.module.js.map +1 -0
  32. package/dist/components/Markdown/markdown-parse.cjs +7 -0
  33. package/dist/components/Markdown/markdown-parse.cjs.map +1 -0
  34. package/dist/components/Markdown/markdown-parse.js +269 -0
  35. package/dist/components/Markdown/markdown-parse.js.map +1 -0
  36. package/dist/components/Markdown/markdown-url.cjs +2 -0
  37. package/dist/components/Markdown/markdown-url.cjs.map +1 -0
  38. package/dist/components/Markdown/markdown-url.js +28 -0
  39. package/dist/components/Markdown/markdown-url.js.map +1 -0
  40. package/dist/components/Masonry/Masonry.cjs +2 -0
  41. package/dist/components/Masonry/Masonry.cjs.map +1 -0
  42. package/dist/components/Masonry/Masonry.js +70 -0
  43. package/dist/components/Masonry/Masonry.js.map +1 -0
  44. package/dist/components/Masonry/Masonry.module.cjs +2 -0
  45. package/dist/components/Masonry/Masonry.module.cjs.map +1 -0
  46. package/dist/components/Masonry/Masonry.module.js +10 -0
  47. package/dist/components/Masonry/Masonry.module.js.map +1 -0
  48. package/dist/components/Masonry/masonry-layout.cjs +2 -0
  49. package/dist/components/Masonry/masonry-layout.cjs.map +1 -0
  50. package/dist/components/Masonry/masonry-layout.js +20 -0
  51. package/dist/components/Masonry/masonry-layout.js.map +1 -0
  52. package/dist/components/Transfer/Transfer.cjs +2 -0
  53. package/dist/components/Transfer/Transfer.cjs.map +1 -0
  54. package/dist/components/Transfer/Transfer.js +163 -0
  55. package/dist/components/Transfer/Transfer.js.map +1 -0
  56. package/dist/components/Transfer/Transfer.module.cjs +2 -0
  57. package/dist/components/Transfer/Transfer.module.cjs.map +1 -0
  58. package/dist/components/Transfer/Transfer.module.js +19 -0
  59. package/dist/components/Transfer/Transfer.module.js.map +1 -0
  60. package/dist/components/Transfer/transfer-state.cjs +2 -0
  61. package/dist/components/Transfer/transfer-state.cjs.map +1 -0
  62. package/dist/components/Transfer/transfer-state.js +59 -0
  63. package/dist/components/Transfer/transfer-state.js.map +1 -0
  64. package/dist/styles.css +1 -1
  65. package/dist/tempest-react-sdk.cjs +1 -1
  66. package/dist/tempest-react-sdk.d.ts +493 -0
  67. package/dist/tempest-react-sdk.js +114 -105
  68. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-parse.cjs","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"./markdown-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)\\s*$/;\nconst HEADING = /^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\n/**\n * A table's delimiter row.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two.\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?\\s*$/;\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines);\n}\n\n/** Parse a run of lines into blocks. */\nfunction parseBlocks(lines: string[]): MarkdownBlock[] {\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line);\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = HEADING.exec(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading[1].length,\n children: parseInline(heading[2]),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (i + 1 < lines.length && line.includes(\"|\") && TABLE_DELIMITER.test(lines[i + 1])) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n HEADING.test(current) ||\n RULE.test(current) ||\n FENCE.test(current) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({ type: \"paragraph\", children: parseInline(paragraph.join(\"\\n\")) });\n }\n }\n\n return blocks;\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n pattern: RegExp;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^ {2,}\\n/, build: () => ({ type: \"break\" }) },\n { pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n const match = rule.pattern.exec(rest);\n if (!match) continue;\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
@@ -0,0 +1,269 @@
1
+ import { safeImageUrl as e, safeLinkUrl as t } from "./markdown-url.js";
2
+ //#region src/components/Markdown/markdown-parse.ts
3
+ var n = /^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)\s*$/, r = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/, i = /^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/, a = /^\s{0,3}>\s?(.*)$/, o = /^(\s*)([-*+])\s+(.*)$/, s = /^(\s*)(\d{1,9})[.)]\s+(.*)$/, c = /^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?\s*$/;
4
+ function l(e) {
5
+ return u(e.replace(/\r\n?/g, "\n").split("\n"));
6
+ }
7
+ function u(e) {
8
+ let t = [], l = 0;
9
+ for (; l < e.length;) {
10
+ let f = e[l];
11
+ if (f.trim() === "") {
12
+ l += 1;
13
+ continue;
14
+ }
15
+ let m = n.exec(f);
16
+ if (m) {
17
+ let n = m[1][0], r = [];
18
+ for (l += 1; l < e.length && !RegExp(`^\\s{0,3}${n}{3,}\\s*$`).test(e[l]);) r.push(e[l]), l += 1;
19
+ l += 1, t.push({
20
+ type: "code",
21
+ language: m[2] || null,
22
+ value: r.join("\n")
23
+ });
24
+ continue;
25
+ }
26
+ let g = r.exec(f);
27
+ if (g) {
28
+ t.push({
29
+ type: "heading",
30
+ level: g[1].length,
31
+ children: h(g[2])
32
+ }), l += 1;
33
+ continue;
34
+ }
35
+ if (i.test(f)) {
36
+ t.push({ type: "rule" }), l += 1;
37
+ continue;
38
+ }
39
+ if (a.test(f)) {
40
+ let n = [];
41
+ for (; l < e.length;) {
42
+ let t = a.exec(e[l]);
43
+ if (t) {
44
+ n.push(t[1]), l += 1;
45
+ continue;
46
+ }
47
+ if (e[l].trim() === "") break;
48
+ n.push(e[l]), l += 1;
49
+ }
50
+ t.push({
51
+ type: "quote",
52
+ children: u(n)
53
+ });
54
+ continue;
55
+ }
56
+ if (o.test(f) || s.test(f)) {
57
+ let [n, r] = d(e, l);
58
+ t.push(n), l = r;
59
+ continue;
60
+ }
61
+ if (l + 1 < e.length && f.includes("|") && c.test(e[l + 1])) {
62
+ let [n, r] = p(e, l);
63
+ t.push(n), l = r;
64
+ continue;
65
+ }
66
+ let _ = [];
67
+ for (; l < e.length && e[l].trim() !== "";) {
68
+ let t = e[l];
69
+ if (r.test(t) || i.test(t) || n.test(t) || a.test(t) || o.test(t) || s.test(t)) break;
70
+ _.push(t.trim()), l += 1;
71
+ }
72
+ _.length > 0 && t.push({
73
+ type: "paragraph",
74
+ children: h(_.join("\n"))
75
+ });
76
+ }
77
+ return t;
78
+ }
79
+ function d(e, t) {
80
+ let n = o.exec(e[t]), r = s.exec(e[t]), i = r !== null, a = ((r ?? n)?.[1] ?? "").length, c = i ? Number(r?.[2] ?? 1) : 1, l = [], d = [], f = t, p = () => {
81
+ d.length !== 0 && (l.push(u(d)), d = []);
82
+ };
83
+ for (; f < e.length;) {
84
+ let t = e[f];
85
+ if (t.trim() === "") {
86
+ if (f + 1 < e.length && e[f + 1].trim() !== "") {
87
+ d.push(""), f += 1;
88
+ continue;
89
+ }
90
+ break;
91
+ }
92
+ let n = o.exec(t), r = s.exec(t), c = n ?? r;
93
+ if (c && c[1].length <= a) {
94
+ if (!(i ? r : n)) break;
95
+ p(), d.push(c[3]), f += 1;
96
+ continue;
97
+ }
98
+ if (c) {
99
+ d.push(t.slice(a)), f += 1;
100
+ continue;
101
+ }
102
+ if (t.search(/\S/) > a) {
103
+ d.push(t.trim()), f += 1;
104
+ continue;
105
+ }
106
+ break;
107
+ }
108
+ return p(), [{
109
+ type: "list",
110
+ ordered: i,
111
+ start: c,
112
+ items: l
113
+ }, f];
114
+ }
115
+ function f(e) {
116
+ let t = e.trim().replace(/^\|/, "").replace(/\|$/, ""), n = [], r = "";
117
+ for (let e = 0; e < t.length; e += 1) {
118
+ if (t[e] === "\\" && t[e + 1] === "|") {
119
+ r += "|", e += 1;
120
+ continue;
121
+ }
122
+ if (t[e] === "|") {
123
+ n.push(r), r = "";
124
+ continue;
125
+ }
126
+ r += t[e];
127
+ }
128
+ return n.push(r), n.map((e) => e.trim());
129
+ }
130
+ function p(e, t) {
131
+ let n = f(e[t]), r = f(e[t + 1]).map((e) => {
132
+ let t = e.startsWith(":"), n = e.endsWith(":");
133
+ return t && n ? "center" : n ? "right" : t ? "left" : null;
134
+ }), i = [], a = t + 2;
135
+ for (; a < e.length && e[a].trim() !== "" && e[a].includes("|");) i.push(f(e[a]).map(h)), a += 1;
136
+ return [{
137
+ type: "table",
138
+ align: r,
139
+ head: n.map(h),
140
+ rows: i
141
+ }, a];
142
+ }
143
+ var m = [
144
+ {
145
+ pattern: /^!\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,
146
+ build: (t) => {
147
+ let n = e(t[2]);
148
+ return n ? {
149
+ type: "image",
150
+ src: n,
151
+ alt: t[1]
152
+ } : {
153
+ type: "text",
154
+ value: t[1]
155
+ };
156
+ }
157
+ },
158
+ {
159
+ pattern: /^\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,
160
+ build: (e) => {
161
+ let n = t(e[2]), r = h(e[1]);
162
+ return n ? {
163
+ type: "link",
164
+ href: n,
165
+ children: r
166
+ } : {
167
+ type: "text",
168
+ value: e[1]
169
+ };
170
+ }
171
+ },
172
+ {
173
+ pattern: /^`([^`]+)`/,
174
+ build: (e) => ({
175
+ type: "code",
176
+ value: e[1]
177
+ })
178
+ },
179
+ {
180
+ pattern: /^\*\*([\s\S]+?)\*\*/,
181
+ build: (e) => ({
182
+ type: "strong",
183
+ children: h(e[1])
184
+ })
185
+ },
186
+ {
187
+ pattern: /^__([\s\S]+?)__/,
188
+ build: (e) => ({
189
+ type: "strong",
190
+ children: h(e[1])
191
+ })
192
+ },
193
+ {
194
+ pattern: /^~~([\s\S]+?)~~/,
195
+ build: (e) => ({
196
+ type: "del",
197
+ children: h(e[1])
198
+ })
199
+ },
200
+ {
201
+ pattern: /^\*([^*\n]+)\*/,
202
+ build: (e) => ({
203
+ type: "em",
204
+ children: h(e[1])
205
+ })
206
+ },
207
+ {
208
+ pattern: /^_([^_\n]+)_/,
209
+ build: (e) => ({
210
+ type: "em",
211
+ children: h(e[1])
212
+ })
213
+ },
214
+ {
215
+ pattern: /^<((?:https?:\/\/|mailto:)[^>\s]+)>/,
216
+ build: (e) => {
217
+ let n = t(e[1]);
218
+ return n ? {
219
+ type: "link",
220
+ href: n,
221
+ children: [{
222
+ type: "text",
223
+ value: e[1]
224
+ }]
225
+ } : {
226
+ type: "text",
227
+ value: e[1]
228
+ };
229
+ }
230
+ },
231
+ {
232
+ pattern: /^ {2,}\n/,
233
+ build: () => ({ type: "break" })
234
+ },
235
+ {
236
+ pattern: /^\\\n/,
237
+ build: () => ({ type: "break" })
238
+ }
239
+ ];
240
+ function h(e) {
241
+ let t = [], n = "", r = 0, i = () => {
242
+ n && t.push({
243
+ type: "text",
244
+ value: n
245
+ }), n = "";
246
+ };
247
+ for (; r < e.length;) {
248
+ if (e[r] === "\\" && r + 1 < e.length && e[r + 1] !== "\n") {
249
+ n += e[r + 1], r += 2;
250
+ continue;
251
+ }
252
+ let a = e.slice(r), o = !1;
253
+ for (let e of m) {
254
+ let n = e.pattern.exec(a);
255
+ if (!n) continue;
256
+ let s = e.build(n);
257
+ if (s) {
258
+ i(), t.push(s), r += n[0].length, o = !0;
259
+ break;
260
+ }
261
+ }
262
+ o || (n += e[r], r += 1);
263
+ }
264
+ return i(), t;
265
+ }
266
+ //#endregion
267
+ export { h as parseInline, l as parseMarkdown };
268
+
269
+ //# sourceMappingURL=markdown-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-parse.js","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"./markdown-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)\\s*$/;\nconst HEADING = /^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\n/**\n * A table's delimiter row.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two.\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?\\s*$/;\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines);\n}\n\n/** Parse a run of lines into blocks. */\nfunction parseBlocks(lines: string[]): MarkdownBlock[] {\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line);\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = HEADING.exec(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading[1].length,\n children: parseInline(heading[2]),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (i + 1 < lines.length && line.includes(\"|\") && TABLE_DELIMITER.test(lines[i + 1])) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n HEADING.test(current) ||\n RULE.test(current) ||\n FENCE.test(current) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({ type: \"paragraph\", children: parseInline(paragraph.join(\"\\n\")) });\n }\n }\n\n return blocks;\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n pattern: RegExp;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^ {2,}\\n/, build: () => ({ type: \"break\" }) },\n { pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n const match = rule.pattern.exec(rest);\n if (!match) continue;\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=new Set([`http:`,`https:`,`mailto:`,`tel:`,`sms:`]),t=/^data:image\/(png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\s]+$/i;function n(e){return e.replace(/[\u0000-\u0020\u007F]/g,``).trim()}function r(t){let r=n(t);if(!r)return null;if(/^[a-z][a-z0-9+.-]*:/i.test(r)){let t=r.slice(0,r.indexOf(`:`)+1).toLowerCase();return e.has(t)?r:null}return r}function i(e){let i=n(e);return i?t.test(i)?i:/^data:/i.test(i)?null:r(i):null}exports.safeImageUrl=i,exports.safeLinkUrl=r;
2
+ //# sourceMappingURL=markdown-url.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-url.cjs","names":[],"sources":["../../../src/components/Markdown/markdown-url.ts"],"sourcesContent":["/**\n * URL vetting for links and images in rendered Markdown.\n *\n * This is the security boundary of the whole component. Markdown that arrives from\n * a user — a comment, a ticket description, a review — reaches the DOM as a real\n * `href`, and `javascript:alert(1)` in a link is script execution on click. So the\n * scheme is checked against an allowlist rather than a blocklist: a blocklist has\n * to enumerate `javascript:`, `JaVaScRiPt:`, `java\\tscript:`, `&#106;avascript:`\n * and whatever comes next, and misses the one nobody thought of.\n */\n\n/** Schemes a link may use. Anything else is dropped. */\nconst LINK_SCHEMES = new Set([\"http:\", \"https:\", \"mailto:\", \"tel:\", \"sms:\"]);\n\n/**\n * Image `data:` types that are safe to inline.\n *\n * `svg+xml` is deliberately absent: an SVG is a document, it can carry `<script>`\n * and event handlers, and browsers execute them when it loads from a `data:` URL in\n * some contexts. The raster formats cannot carry code.\n */\nconst IMAGE_DATA_TYPES = /^data:image\\/(png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\\s]+$/i;\n\n/**\n * Strip the characters a scheme can be smuggled through.\n *\n * `java\\tscript:` and `java\\nscript:` are both parsed as `javascript:` by browsers,\n * and a leading control character defeats a naive `startsWith` check.\n */\nfunction normalize(raw: string): string {\n // eslint-disable-next-line no-control-regex -- stripping control characters is the point\n return raw.replace(/[\\u0000-\\u0020\\u007F]/g, \"\").trim();\n}\n\n/**\n * The href to render, or `null` when the URL must not be linked.\n *\n * Relative URLs (`/docs`, `./x`, `#anchor`, `foo/bar`) are kept: they cannot carry\n * a scheme, so they cannot execute anything, and dropping them would break every\n * in-app link.\n *\n * @param raw - URL as written in the Markdown.\n * @returns A safe href, or `null`.\n */\nexport function safeLinkUrl(raw: string): string | null {\n const url = normalize(raw);\n if (!url) return null;\n if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {\n const scheme = url.slice(0, url.indexOf(\":\") + 1).toLowerCase();\n return LINK_SCHEMES.has(scheme) ? url : null;\n }\n // Protocol-relative (`//host/path`) inherits the page scheme — safe, and real.\n return url;\n}\n\n/**\n * The `src` to render for an image, or `null`.\n *\n * @param raw - URL as written in the Markdown.\n * @returns A safe src, or `null`.\n */\nexport function safeImageUrl(raw: string): string | null {\n const url = normalize(raw);\n if (!url) return null;\n if (IMAGE_DATA_TYPES.test(url)) return url;\n if (/^data:/i.test(url)) return null;\n return safeLinkUrl(url);\n}\n"],"mappings":"AAYA,IAAM,EAAe,IAAI,IAAI,CAAC,QAAS,SAAU,UAAW,OAAQ,MAAM,CAAC,EASrE,EAAmB,qEAQzB,SAAS,EAAU,EAAqB,CAEpC,OAAO,EAAI,QAAQ,yBAA0B,EAAE,CAAC,CAAC,KAAK,CAC1D,CAYA,SAAgB,EAAY,EAA4B,CACpD,IAAM,EAAM,EAAU,CAAG,EACzB,GAAI,CAAC,EAAK,OAAO,KACjB,GAAI,uBAAuB,KAAK,CAAG,EAAG,CAClC,IAAM,EAAS,EAAI,MAAM,EAAG,EAAI,QAAQ,GAAG,EAAI,CAAC,CAAC,CAAC,YAAY,EAC9D,OAAO,EAAa,IAAI,CAAM,EAAI,EAAM,IAC5C,CAEA,OAAO,CACX,CAQA,SAAgB,EAAa,EAA4B,CACrD,IAAM,EAAM,EAAU,CAAG,EAIzB,OAHK,EACD,EAAiB,KAAK,CAAG,EAAU,EACnC,UAAU,KAAK,CAAG,EAAU,KACzB,EAAY,CAAG,EAHL,IAIrB"}
@@ -0,0 +1,28 @@
1
+ //#region src/components/Markdown/markdown-url.ts
2
+ var e = /* @__PURE__ */ new Set([
3
+ "http:",
4
+ "https:",
5
+ "mailto:",
6
+ "tel:",
7
+ "sms:"
8
+ ]), t = /^data:image\/(png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\s]+$/i;
9
+ function n(e) {
10
+ return e.replace(/[\u0000-\u0020\u007F]/g, "").trim();
11
+ }
12
+ function r(t) {
13
+ let r = n(t);
14
+ if (!r) return null;
15
+ if (/^[a-z][a-z0-9+.-]*:/i.test(r)) {
16
+ let t = r.slice(0, r.indexOf(":") + 1).toLowerCase();
17
+ return e.has(t) ? r : null;
18
+ }
19
+ return r;
20
+ }
21
+ function i(e) {
22
+ let i = n(e);
23
+ return i ? t.test(i) ? i : /^data:/i.test(i) ? null : r(i) : null;
24
+ }
25
+ //#endregion
26
+ export { i as safeImageUrl, r as safeLinkUrl };
27
+
28
+ //# sourceMappingURL=markdown-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-url.js","names":[],"sources":["../../../src/components/Markdown/markdown-url.ts"],"sourcesContent":["/**\n * URL vetting for links and images in rendered Markdown.\n *\n * This is the security boundary of the whole component. Markdown that arrives from\n * a user — a comment, a ticket description, a review — reaches the DOM as a real\n * `href`, and `javascript:alert(1)` in a link is script execution on click. So the\n * scheme is checked against an allowlist rather than a blocklist: a blocklist has\n * to enumerate `javascript:`, `JaVaScRiPt:`, `java\\tscript:`, `&#106;avascript:`\n * and whatever comes next, and misses the one nobody thought of.\n */\n\n/** Schemes a link may use. Anything else is dropped. */\nconst LINK_SCHEMES = new Set([\"http:\", \"https:\", \"mailto:\", \"tel:\", \"sms:\"]);\n\n/**\n * Image `data:` types that are safe to inline.\n *\n * `svg+xml` is deliberately absent: an SVG is a document, it can carry `<script>`\n * and event handlers, and browsers execute them when it loads from a `data:` URL in\n * some contexts. The raster formats cannot carry code.\n */\nconst IMAGE_DATA_TYPES = /^data:image\\/(png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\\s]+$/i;\n\n/**\n * Strip the characters a scheme can be smuggled through.\n *\n * `java\\tscript:` and `java\\nscript:` are both parsed as `javascript:` by browsers,\n * and a leading control character defeats a naive `startsWith` check.\n */\nfunction normalize(raw: string): string {\n // eslint-disable-next-line no-control-regex -- stripping control characters is the point\n return raw.replace(/[\\u0000-\\u0020\\u007F]/g, \"\").trim();\n}\n\n/**\n * The href to render, or `null` when the URL must not be linked.\n *\n * Relative URLs (`/docs`, `./x`, `#anchor`, `foo/bar`) are kept: they cannot carry\n * a scheme, so they cannot execute anything, and dropping them would break every\n * in-app link.\n *\n * @param raw - URL as written in the Markdown.\n * @returns A safe href, or `null`.\n */\nexport function safeLinkUrl(raw: string): string | null {\n const url = normalize(raw);\n if (!url) return null;\n if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {\n const scheme = url.slice(0, url.indexOf(\":\") + 1).toLowerCase();\n return LINK_SCHEMES.has(scheme) ? url : null;\n }\n // Protocol-relative (`//host/path`) inherits the page scheme — safe, and real.\n return url;\n}\n\n/**\n * The `src` to render for an image, or `null`.\n *\n * @param raw - URL as written in the Markdown.\n * @returns A safe src, or `null`.\n */\nexport function safeImageUrl(raw: string): string | null {\n const url = normalize(raw);\n if (!url) return null;\n if (IMAGE_DATA_TYPES.test(url)) return url;\n if (/^data:/i.test(url)) return null;\n return safeLinkUrl(url);\n}\n"],"mappings":";AAYA,IAAM,oBAAe,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;CAAQ;AAAM,CAAC,GASrE,IAAmB;AAQzB,SAAS,EAAU,GAAqB;CAEpC,OAAO,EAAI,QAAQ,0BAA0B,EAAE,CAAC,CAAC,KAAK;AAC1D;AAYA,SAAgB,EAAY,GAA4B;CACpD,IAAM,IAAM,EAAU,CAAG;CACzB,IAAI,CAAC,GAAK,OAAO;CACjB,IAAI,uBAAuB,KAAK,CAAG,GAAG;EAClC,IAAM,IAAS,EAAI,MAAM,GAAG,EAAI,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY;EAC9D,OAAO,EAAa,IAAI,CAAM,IAAI,IAAM;CAC5C;CAEA,OAAO;AACX;AAQA,SAAgB,EAAa,GAA4B;CACrD,IAAM,IAAM,EAAU,CAAG;CAIzB,OAHK,IACD,EAAiB,KAAK,CAAG,IAAU,IACnC,UAAU,KAAK,CAAG,IAAU,OACzB,EAAY,CAAG,IAHL;AAIrB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("./masonry-layout.cjs"),n=require("./Masonry.module.cjs");let r=require("react"),i=require("react/jsx-runtime");var a={0:1,640:2,1024:3};function o({items:o,children:s,itemKey:c,columns:l=a,gap:u,className:d,style:f,...p}){let m=(0,r.useRef)(null),h=(0,r.useRef)(new Map),[g,_]=(0,r.useState)(0),[v,y]=(0,r.useState)([]),b=t.columnsFor(g,l);(0,r.useEffect)(()=>{let e=m.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(([e])=>{_(e.contentRect.width)});return t.observe(e),_(e.getBoundingClientRect().width),()=>t.disconnect()},[]);let x=(0,r.useCallback)(()=>{let e=o.map((e,t)=>{let n=h.current.get(t);return n?n.getBoundingClientRect().height:1});y(t=>t.length===e.length&&t.every((t,n)=>t===e[n])?t:e)},[o]);(0,r.useLayoutEffect)(x,[x,b,g]),(0,r.useEffect)(()=>{if(typeof ResizeObserver>`u`)return;let e=new ResizeObserver(x);for(let t of h.current.values())e.observe(t);return()=>e.disconnect()},[x,b]);let S=(0,r.useMemo)(()=>t.distribute(o.map((e,t)=>v[t]??1),b),[o,v,b]);return(0,i.jsx)(`div`,{ref:m,className:e.cn(n.default.wrapper,d),style:{...f,...u?{"--tempest-masonry-gap":u}:{}},...p,children:S.map((e,t)=>(0,i.jsx)(`div`,{className:n.default.column,children:e.map(e=>{let t=o[e];return(0,i.jsx)(`div`,{className:n.default.item,ref:t=>{t?h.current.set(e,t):h.current.delete(e)},children:s(t,e)},c?c(t,e):e)})},t))})}exports.Masonry=o;
2
+ //# sourceMappingURL=Masonry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** What to lay out. */\n items: readonly T[];\n /** Render one card. */\n children: (item: T, index: number) => ReactNode;\n /** Stable key per item. Index is a fallback nobody should rely on. */\n itemKey?: (item: T, index: number) => string | number;\n /**\n * Column count: a fixed number, or a `width → columns` map read as\n * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n */\n columns?: number | Record<number, number>;\n /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n * {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n items,\n children,\n itemKey,\n columns = DEFAULT_COLUMNS,\n gap,\n className,\n style,\n ...rest\n}: MasonryProps<T>) {\n const wrapper = useRef<HTMLDivElement | null>(null);\n const cells = useRef<Map<number, HTMLElement>>(new Map());\n const [width, setWidth] = useState(0);\n const [heights, setHeights] = useState<number[]>([]);\n\n const columnCount = columnsFor(width, columns);\n\n /**\n * Track the container width, not the viewport.\n *\n * A masonry inside a drawer or a two-column page is narrower than the window,\n * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n * is what makes the breakpoint map mean \"this container\", which is the only\n * useful reading.\n */\n useEffect(() => {\n const node = wrapper.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n setWidth(entry.contentRect.width);\n });\n observer.observe(node);\n setWidth(node.getBoundingClientRect().width);\n return () => observer.disconnect();\n }, []);\n\n /**\n * Re-read every card's height, and store it only when something moved.\n *\n * The equality check is what stops the loop: writing a new array on every pass\n * would re-render, re-measure and write again. The first render lays out in\n * source order with weight 1 each — never blank — and this pass re-deals with\n * real numbers.\n */\n const measure = useCallback(() => {\n const next = items.map((_, index) => {\n const node = cells.current.get(index);\n return node ? node.getBoundingClientRect().height : 1;\n });\n setHeights((current) =>\n current.length === next.length && current.every((value, i) => value === next[i])\n ? current\n : next,\n );\n }, [items]);\n\n useLayoutEffect(measure, [measure, columnCount, width]);\n\n /**\n * Re-measure when a card changes size on its own — an image finishing its\n * download is the common one, and it is exactly the case a height measured at\n * mount gets wrong.\n */\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const node of cells.current.values()) observer.observe(node);\n return () => observer.disconnect();\n }, [measure, columnCount]);\n\n const layout = useMemo(\n () =>\n distribute(\n items.map((_, index) => heights[index] ?? 1),\n columnCount,\n ),\n [items, heights, columnCount],\n );\n\n return (\n <div\n ref={wrapper}\n className={cn(styles.wrapper, className)}\n style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n {...rest}\n >\n {layout.map((columnItems, column) => (\n <div key={column} className={styles.column}>\n {columnItems.map((index) => {\n const item = items[index];\n return (\n <div\n key={itemKey ? itemKey(item, index) : index}\n className={styles.item}\n ref={(node) => {\n if (node) cells.current.set(index, node);\n else cells.current.delete(index);\n }}\n >\n {children(item, index)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"gKAoCA,IAAM,EAA0C,CAAE,EAAG,EAAG,IAAK,EAAG,KAAM,CAAE,EAiBxE,SAAgB,EAAW,CACvB,QACA,WACA,UACA,UAAU,EACV,MACA,YACA,QACA,GAAG,GACa,CAChB,IAAM,GAAA,EAAA,EAAA,OAAA,CAAwC,IAAI,EAC5C,GAAA,EAAA,EAAA,OAAA,CAAyC,IAAI,GAAK,EAClD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAqB,CAAC,EAC9B,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAiC,CAAC,CAAC,EAE7C,EAAc,EAAA,WAAW,EAAO,CAAO,GAU7C,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAQ,QACrB,GAAI,CAAC,GAAQ,OAAO,eAAmB,IAAa,OACpD,IAAM,EAAW,IAAI,gBAAgB,CAAC,KAAW,CAC7C,EAAS,EAAM,YAAY,KAAK,CACpC,CAAC,EAGD,OAFA,EAAS,QAAQ,CAAI,EACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,MAC9B,EAAS,WAAW,CACrC,EAAG,CAAC,CAAC,EAUL,IAAM,GAAA,EAAA,EAAA,YAAA,KAA4B,CAC9B,IAAM,EAAO,EAAM,KAAK,EAAG,IAAU,CACjC,IAAM,EAAO,EAAM,QAAQ,IAAI,CAAK,EACpC,OAAO,EAAO,EAAK,sBAAsB,CAAC,CAAC,OAAS,CACxD,CAAC,EACD,EAAY,GACR,EAAQ,SAAW,EAAK,QAAU,EAAQ,OAAO,EAAO,IAAM,IAAU,EAAK,EAAE,EACzE,EACA,CACV,CACJ,EAAG,CAAC,CAAK,CAAC,GAEV,EAAA,EAAA,gBAAA,CAAgB,EAAS,CAAC,EAAS,EAAa,CAAK,CAAC,GAOtD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,eAAmB,IAAa,OAC3C,IAAM,EAAW,IAAI,eAAe,CAAO,EAC3C,IAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,EAAG,EAAS,QAAQ,CAAI,EAChE,UAAa,EAAS,WAAW,CACrC,EAAG,CAAC,EAAS,CAAW,CAAC,EAEzB,IAAM,GAAA,EAAA,EAAA,QAAA,KAEE,EAAA,WACI,EAAM,KAAK,EAAG,IAAU,EAAQ,IAAU,CAAC,EAC3C,CACJ,EACJ,CAAC,EAAO,EAAS,CAAW,CAChC,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EACvC,MAAO,CAAE,GAAG,EAAO,GAAI,EAAM,CAAE,wBAAyB,CAAI,EAAI,CAAC,CAAG,EACpE,GAAI,WAEH,EAAO,KAAK,EAAa,KACtB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAkB,UAAW,EAAA,QAAO,gBAC/B,EAAY,IAAK,GAAU,CACxB,IAAM,EAAO,EAAM,GACnB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAEI,UAAW,EAAA,QAAO,KAClB,IAAM,GAAS,CACP,EAAM,EAAM,QAAQ,IAAI,EAAO,CAAI,EAClC,EAAM,QAAQ,OAAO,CAAK,CACnC,WAEC,EAAS,EAAM,CAAK,CACpB,EARI,EAAU,EAAQ,EAAM,CAAK,EAAI,CAQrC,CAEb,CAAC,CACA,EAhBK,CAgBL,CACR,CACA,CAAA,CAEb"}
@@ -0,0 +1,70 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { columnsFor as t, distribute as n } from "./masonry-layout.js";
3
+ import r from "./Masonry.module.js";
4
+ import { useCallback as i, useEffect as a, useLayoutEffect as o, useMemo as s, useRef as c, useState as l } from "react";
5
+ import { jsx as u } from "react/jsx-runtime";
6
+ //#region src/components/Masonry/Masonry.tsx
7
+ var d = {
8
+ 0: 1,
9
+ 640: 2,
10
+ 1024: 3
11
+ };
12
+ function f({ items: f, children: p, itemKey: m, columns: h = d, gap: g, className: _, style: v, ...y }) {
13
+ let b = c(null), x = c(/* @__PURE__ */ new Map()), [S, C] = l(0), [w, T] = l([]), E = t(S, h);
14
+ a(() => {
15
+ let e = b.current;
16
+ if (!e || typeof ResizeObserver > "u") return;
17
+ let t = new ResizeObserver(([e]) => {
18
+ C(e.contentRect.width);
19
+ });
20
+ return t.observe(e), C(e.getBoundingClientRect().width), () => t.disconnect();
21
+ }, []);
22
+ let D = i(() => {
23
+ let e = f.map((e, t) => {
24
+ let n = x.current.get(t);
25
+ return n ? n.getBoundingClientRect().height : 1;
26
+ });
27
+ T((t) => t.length === e.length && t.every((t, n) => t === e[n]) ? t : e);
28
+ }, [f]);
29
+ o(D, [
30
+ D,
31
+ E,
32
+ S
33
+ ]), a(() => {
34
+ if (typeof ResizeObserver > "u") return;
35
+ let e = new ResizeObserver(D);
36
+ for (let t of x.current.values()) e.observe(t);
37
+ return () => e.disconnect();
38
+ }, [D, E]);
39
+ let O = s(() => n(f.map((e, t) => w[t] ?? 1), E), [
40
+ f,
41
+ w,
42
+ E
43
+ ]);
44
+ return /* @__PURE__ */ u("div", {
45
+ ref: b,
46
+ className: e(r.wrapper, _),
47
+ style: {
48
+ ...v,
49
+ ...g ? { "--tempest-masonry-gap": g } : {}
50
+ },
51
+ ...y,
52
+ children: O.map((e, t) => /* @__PURE__ */ u("div", {
53
+ className: r.column,
54
+ children: e.map((e) => {
55
+ let t = f[e];
56
+ return /* @__PURE__ */ u("div", {
57
+ className: r.item,
58
+ ref: (t) => {
59
+ t ? x.current.set(e, t) : x.current.delete(e);
60
+ },
61
+ children: p(t, e)
62
+ }, m ? m(t, e) : e);
63
+ })
64
+ }, t))
65
+ });
66
+ }
67
+ //#endregion
68
+ export { f as Masonry };
69
+
70
+ //# sourceMappingURL=Masonry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.js","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** What to lay out. */\n items: readonly T[];\n /** Render one card. */\n children: (item: T, index: number) => ReactNode;\n /** Stable key per item. Index is a fallback nobody should rely on. */\n itemKey?: (item: T, index: number) => string | number;\n /**\n * Column count: a fixed number, or a `width → columns` map read as\n * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n */\n columns?: number | Record<number, number>;\n /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n * {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n items,\n children,\n itemKey,\n columns = DEFAULT_COLUMNS,\n gap,\n className,\n style,\n ...rest\n}: MasonryProps<T>) {\n const wrapper = useRef<HTMLDivElement | null>(null);\n const cells = useRef<Map<number, HTMLElement>>(new Map());\n const [width, setWidth] = useState(0);\n const [heights, setHeights] = useState<number[]>([]);\n\n const columnCount = columnsFor(width, columns);\n\n /**\n * Track the container width, not the viewport.\n *\n * A masonry inside a drawer or a two-column page is narrower than the window,\n * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n * is what makes the breakpoint map mean \"this container\", which is the only\n * useful reading.\n */\n useEffect(() => {\n const node = wrapper.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n setWidth(entry.contentRect.width);\n });\n observer.observe(node);\n setWidth(node.getBoundingClientRect().width);\n return () => observer.disconnect();\n }, []);\n\n /**\n * Re-read every card's height, and store it only when something moved.\n *\n * The equality check is what stops the loop: writing a new array on every pass\n * would re-render, re-measure and write again. The first render lays out in\n * source order with weight 1 each — never blank — and this pass re-deals with\n * real numbers.\n */\n const measure = useCallback(() => {\n const next = items.map((_, index) => {\n const node = cells.current.get(index);\n return node ? node.getBoundingClientRect().height : 1;\n });\n setHeights((current) =>\n current.length === next.length && current.every((value, i) => value === next[i])\n ? current\n : next,\n );\n }, [items]);\n\n useLayoutEffect(measure, [measure, columnCount, width]);\n\n /**\n * Re-measure when a card changes size on its own — an image finishing its\n * download is the common one, and it is exactly the case a height measured at\n * mount gets wrong.\n */\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const node of cells.current.values()) observer.observe(node);\n return () => observer.disconnect();\n }, [measure, columnCount]);\n\n const layout = useMemo(\n () =>\n distribute(\n items.map((_, index) => heights[index] ?? 1),\n columnCount,\n ),\n [items, heights, columnCount],\n );\n\n return (\n <div\n ref={wrapper}\n className={cn(styles.wrapper, className)}\n style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n {...rest}\n >\n {layout.map((columnItems, column) => (\n <div key={column} className={styles.column}>\n {columnItems.map((index) => {\n const item = items[index];\n return (\n <div\n key={itemKey ? itemKey(item, index) : index}\n className={styles.item}\n ref={(node) => {\n if (node) cells.current.set(index, node);\n else cells.current.delete(index);\n }}\n >\n {children(item, index)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;AAoCA,IAAM,IAA0C;CAAE,GAAG;CAAG,KAAK;CAAG,MAAM;AAAE;AAiBxE,SAAgB,EAAW,EACvB,UACA,aACA,YACA,aAAU,GACV,QACA,cACA,UACA,GAAG,KACa;CAChB,IAAM,IAAU,EAA8B,IAAI,GAC5C,IAAQ,kBAAiC,IAAI,IAAI,CAAC,GAClD,CAAC,GAAO,KAAY,EAAS,CAAC,GAC9B,CAAC,GAAS,KAAc,EAAmB,CAAC,CAAC,GAE7C,IAAc,EAAW,GAAO,CAAO;CAU7C,QAAgB;EACZ,IAAM,IAAO,EAAQ;EACrB,IAAI,CAAC,KAAQ,OAAO,iBAAmB,KAAa;EACpD,IAAM,IAAW,IAAI,gBAAgB,CAAC,OAAW;GAC7C,EAAS,EAAM,YAAY,KAAK;EACpC,CAAC;EAGD,OAFA,EAAS,QAAQ,CAAI,GACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,SAC9B,EAAS,WAAW;CACrC,GAAG,CAAC,CAAC;CAUL,IAAM,IAAU,QAAkB;EAC9B,IAAM,IAAO,EAAM,KAAK,GAAG,MAAU;GACjC,IAAM,IAAO,EAAM,QAAQ,IAAI,CAAK;GACpC,OAAO,IAAO,EAAK,sBAAsB,CAAC,CAAC,SAAS;EACxD,CAAC;EACD,GAAY,MACR,EAAQ,WAAW,EAAK,UAAU,EAAQ,OAAO,GAAO,MAAM,MAAU,EAAK,EAAE,IACzE,IACA,CACV;CACJ,GAAG,CAAC,CAAK,CAAC;CASV,AAPA,EAAgB,GAAS;EAAC;EAAS;EAAa;CAAK,CAAC,GAOtD,QAAgB;EACZ,IAAI,OAAO,iBAAmB,KAAa;EAC3C,IAAM,IAAW,IAAI,eAAe,CAAO;EAC3C,KAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,GAAG,EAAS,QAAQ,CAAI;EAChE,aAAa,EAAS,WAAW;CACrC,GAAG,CAAC,GAAS,CAAW,CAAC;CAEzB,IAAM,IAAS,QAEP,EACI,EAAM,KAAK,GAAG,MAAU,EAAQ,MAAU,CAAC,GAC3C,CACJ,GACJ;EAAC;EAAO;EAAS;CAAW,CAChC;CAEA,OACI,kBAAC,OAAD;EACI,KAAK;EACL,WAAW,EAAG,EAAO,SAAS,CAAS;EACvC,OAAO;GAAE,GAAG;GAAO,GAAI,IAAM,EAAE,yBAAyB,EAAI,IAAI,CAAC;EAAG;EACpE,GAAI;YAEH,EAAO,KAAK,GAAa,MACtB,kBAAC,OAAD;GAAkB,WAAW,EAAO;aAC/B,EAAY,KAAK,MAAU;IACxB,IAAM,IAAO,EAAM;IACnB,OACI,kBAAC,OAAD;KAEI,WAAW,EAAO;KAClB,MAAM,MAAS;MACX,AAAI,IAAM,EAAM,QAAQ,IAAI,GAAO,CAAI,IAClC,EAAM,QAAQ,OAAO,CAAK;KACnC;eAEC,EAAS,GAAM,CAAK;IACpB,GARI,IAAU,EAAQ,GAAM,CAAK,IAAI,CAQrC;GAEb,CAAC;EACA,GAhBK,CAgBL,CACR;CACA,CAAA;AAEb"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_wrapper_xO0g4`,t=`tempest_column_FX2W1`,n=`tempest_item_m7Agj`,r={wrapper:e,column:t,item:n};exports.column=t,exports.default=r,exports.item=n,exports.wrapper=e;
2
+ //# sourceMappingURL=Masonry.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.module.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n width: 100%;\n}\n\n.column {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n min-width: 0;\n}\n\n.item {\n min-width: 0;\n}\n"],"mappings":""}
@@ -0,0 +1,10 @@
1
+ //#region src/components/Masonry/Masonry.module.css
2
+ var e = "tempest_wrapper_xO0g4", t = "tempest_column_FX2W1", n = "tempest_item_m7Agj", r = {
3
+ wrapper: e,
4
+ column: t,
5
+ item: n
6
+ };
7
+ //#endregion
8
+ export { t as column, r as default, n as item, e as wrapper };
9
+
10
+ //# sourceMappingURL=Masonry.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.module.js","names":[],"sources":["../../../src/components/Masonry/Masonry.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n width: 100%;\n}\n\n.column {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n min-width: 0;\n}\n\n.item {\n min-width: 0;\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ function e(e,t){if(typeof t==`number`)return Math.max(1,Math.floor(t));let n=Object.keys(t).map(Number).filter(e=>Number.isFinite(e)).sort((e,t)=>e-t),r=1;for(let i of n)e>=i&&(r=t[i]);return Math.max(1,Math.floor(r))}function t(e,t){let n=Math.max(1,Math.floor(t)),r=Array.from({length:n},()=>[]),i=Array(n).fill(0);for(let t=0;t<e.length;t+=1){let a=0;for(let e=1;e<n;e+=1)i[e]<i[a]&&(a=e);r[a].push(t),i[a]+=Math.max(1,e[t]??1)}return r}exports.columnsFor=e,exports.distribute=t;
2
+ //# sourceMappingURL=masonry-layout.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masonry-layout.cjs","names":[],"sources":["../../../src/components/Masonry/masonry-layout.ts"],"sourcesContent":["/**\n * Column count for a width, from a breakpoint map.\n *\n * The map is read as \"from this width up\", so `{ 0: 1, 640: 2, 1024: 3 }` means one\n * column on a phone and three on a desktop. Keys are sorted here rather than\n * trusted in insertion order — an object literal written out of order would\n * otherwise pick the wrong column count, silently.\n *\n * @param width - Container width in pixels.\n * @param columns - Breakpoint → column count, or a fixed number.\n * @returns At least 1, always.\n */\nexport function columnsFor(width: number, columns: number | Record<number, number>): number {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n const steps = Object.keys(columns)\n .map(Number)\n .filter((key) => Number.isFinite(key))\n .sort((a, b) => a - b);\n let count = 1;\n for (const step of steps) {\n if (width >= step) count = columns[step];\n }\n return Math.max(1, Math.floor(count));\n}\n\n/**\n * Deal items into columns, shortest column first.\n *\n * Round-robin (`index % columns`) is the obvious approach and produces ragged\n * columns the moment items differ in height — which is the only reason to reach\n * for a masonry layout at all. Feeding the shortest column keeps the bottom edge\n * as even as the content allows.\n *\n * Reading order is the cost, and it is why this is a layout for **independent**\n * cards: down a column rather than across the row. A list where item 2 must follow\n * item 1 wants a grid, not this.\n *\n * @param heights - Estimated or measured height per item, in the items' order.\n * @param columnCount - How many columns to fill.\n * @returns Item indexes per column.\n */\nexport function distribute(heights: readonly number[], columnCount: number): number[][] {\n const count = Math.max(1, Math.floor(columnCount));\n const columns: number[][] = Array.from({ length: count }, () => []);\n const totals = new Array<number>(count).fill(0);\n\n for (let index = 0; index < heights.length; index += 1) {\n let shortest = 0;\n for (let column = 1; column < count; column += 1) {\n if (totals[column] < totals[shortest]) shortest = column;\n }\n columns[shortest].push(index);\n totals[shortest] += Math.max(1, heights[index] ?? 1);\n }\n\n return columns;\n}\n"],"mappings":"AAYA,SAAgB,EAAW,EAAe,EAAkD,CACxF,GAAI,OAAO,GAAY,SAAU,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,CAAO,CAAC,EACvE,IAAM,EAAQ,OAAO,KAAK,CAAO,CAAC,CAC7B,IAAI,MAAM,CAAC,CACX,OAAQ,GAAQ,OAAO,SAAS,CAAG,CAAC,CAAC,CACrC,MAAM,EAAG,IAAM,EAAI,CAAC,EACrB,EAAQ,EACZ,IAAK,IAAM,KAAQ,EACX,GAAS,IAAM,EAAQ,EAAQ,IAEvC,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,CAAK,CAAC,CACxC,CAkBA,SAAgB,EAAW,EAA4B,EAAiC,CACpF,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,MAAM,CAAW,CAAC,EAC3C,EAAsB,MAAM,KAAK,CAAE,OAAQ,CAAM,MAAS,CAAC,CAAC,EAC5D,EAAa,MAAc,CAAK,CAAC,CAAC,KAAK,CAAC,EAE9C,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAQ,OAAQ,GAAS,EAAG,CACpD,IAAI,EAAW,EACf,IAAK,IAAI,EAAS,EAAG,EAAS,EAAO,GAAU,EACvC,EAAO,GAAU,EAAO,KAAW,EAAW,GAEtD,EAAQ,EAAS,CAAC,KAAK,CAAK,EAC5B,EAAO,IAAa,KAAK,IAAI,EAAG,EAAQ,IAAU,CAAC,CACvD,CAEA,OAAO,CACX"}
@@ -0,0 +1,20 @@
1
+ //#region src/components/Masonry/masonry-layout.ts
2
+ function e(e, t) {
3
+ if (typeof t == "number") return Math.max(1, Math.floor(t));
4
+ let n = Object.keys(t).map(Number).filter((e) => Number.isFinite(e)).sort((e, t) => e - t), r = 1;
5
+ for (let i of n) e >= i && (r = t[i]);
6
+ return Math.max(1, Math.floor(r));
7
+ }
8
+ function t(e, t) {
9
+ let n = Math.max(1, Math.floor(t)), r = Array.from({ length: n }, () => []), i = Array(n).fill(0);
10
+ for (let t = 0; t < e.length; t += 1) {
11
+ let a = 0;
12
+ for (let e = 1; e < n; e += 1) i[e] < i[a] && (a = e);
13
+ r[a].push(t), i[a] += Math.max(1, e[t] ?? 1);
14
+ }
15
+ return r;
16
+ }
17
+ //#endregion
18
+ export { e as columnsFor, t as distribute };
19
+
20
+ //# sourceMappingURL=masonry-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masonry-layout.js","names":[],"sources":["../../../src/components/Masonry/masonry-layout.ts"],"sourcesContent":["/**\n * Column count for a width, from a breakpoint map.\n *\n * The map is read as \"from this width up\", so `{ 0: 1, 640: 2, 1024: 3 }` means one\n * column on a phone and three on a desktop. Keys are sorted here rather than\n * trusted in insertion order — an object literal written out of order would\n * otherwise pick the wrong column count, silently.\n *\n * @param width - Container width in pixels.\n * @param columns - Breakpoint → column count, or a fixed number.\n * @returns At least 1, always.\n */\nexport function columnsFor(width: number, columns: number | Record<number, number>): number {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n const steps = Object.keys(columns)\n .map(Number)\n .filter((key) => Number.isFinite(key))\n .sort((a, b) => a - b);\n let count = 1;\n for (const step of steps) {\n if (width >= step) count = columns[step];\n }\n return Math.max(1, Math.floor(count));\n}\n\n/**\n * Deal items into columns, shortest column first.\n *\n * Round-robin (`index % columns`) is the obvious approach and produces ragged\n * columns the moment items differ in height — which is the only reason to reach\n * for a masonry layout at all. Feeding the shortest column keeps the bottom edge\n * as even as the content allows.\n *\n * Reading order is the cost, and it is why this is a layout for **independent**\n * cards: down a column rather than across the row. A list where item 2 must follow\n * item 1 wants a grid, not this.\n *\n * @param heights - Estimated or measured height per item, in the items' order.\n * @param columnCount - How many columns to fill.\n * @returns Item indexes per column.\n */\nexport function distribute(heights: readonly number[], columnCount: number): number[][] {\n const count = Math.max(1, Math.floor(columnCount));\n const columns: number[][] = Array.from({ length: count }, () => []);\n const totals = new Array<number>(count).fill(0);\n\n for (let index = 0; index < heights.length; index += 1) {\n let shortest = 0;\n for (let column = 1; column < count; column += 1) {\n if (totals[column] < totals[shortest]) shortest = column;\n }\n columns[shortest].push(index);\n totals[shortest] += Math.max(1, heights[index] ?? 1);\n }\n\n return columns;\n}\n"],"mappings":";AAYA,SAAgB,EAAW,GAAe,GAAkD;CACxF,IAAI,OAAO,KAAY,UAAU,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,CAAO,CAAC;CACvE,IAAM,IAAQ,OAAO,KAAK,CAAO,CAAC,CAC7B,IAAI,MAAM,CAAC,CACX,QAAQ,MAAQ,OAAO,SAAS,CAAG,CAAC,CAAC,CACrC,MAAM,GAAG,MAAM,IAAI,CAAC,GACrB,IAAQ;CACZ,KAAK,IAAM,KAAQ,GACf,AAAI,KAAS,MAAM,IAAQ,EAAQ;CAEvC,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,CAAK,CAAC;AACxC;AAkBA,SAAgB,EAAW,GAA4B,GAAiC;CACpF,IAAM,IAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,CAAW,CAAC,GAC3C,IAAsB,MAAM,KAAK,EAAE,QAAQ,EAAM,SAAS,CAAC,CAAC,GAC5D,IAAa,MAAc,CAAK,CAAC,CAAC,KAAK,CAAC;CAE9C,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAQ,QAAQ,KAAS,GAAG;EACpD,IAAI,IAAW;EACf,KAAK,IAAI,IAAS,GAAG,IAAS,GAAO,KAAU,GAC3C,AAAI,EAAO,KAAU,EAAO,OAAW,IAAW;EAGtD,AADA,EAAQ,EAAS,CAAC,KAAK,CAAK,GAC5B,EAAO,MAAa,KAAK,IAAI,GAAG,EAAQ,MAAU,CAAC;CACvD;CAEA,OAAO;AACX"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("../Checkbox/Checkbox.cjs"),n=require("../VisuallyHidden/VisuallyHidden.cjs"),r=require("./transfer-state.cjs"),i=require("./Transfer.module.cjs");let a=require("react"),o=require("react/jsx-runtime");function s({items:s,value:c,onChange:l,sourceTitle:u,targetTitle:d,searchable:f,renderItem:p,locale:m=`pt-BR`,height:h=`16rem`,disabled:g=!1,className:_,...v}){let y=r.transferStrings(m),b=(0,a.useId)(),[x,S]=(0,a.useState)(new Set),[C,w]=(0,a.useState)({source:``,target:``}),[T,E]=(0,a.useState)(``),{source:D,target:O}=(0,a.useMemo)(()=>r.splitSides(s,c),[s,c]),k=f??s.length>8,A={source:k?r.filterItems(D,C.source):[...D],target:k?r.filterItems(O,C.target):[...O]},j=[{side:`source`,title:u??y.sourceTitle,items:D},{side:`target`,title:d??y.targetTitle,items:O}],M=(e,t)=>{if(g||e.length===0)return;let n=r.applyMove({value:c,moving:e,to:t,items:s}),i=t===`target`?n.length-c.length:c.length-n.length;if(l(n),S(t=>{let n=new Set(t);for(let t of e)n.delete(t);return n}),i>0){let e=t===`target`?y.targetTitle:y.sourceTitle;E(y.moved(i,String(e).toLowerCase()))}},N=e=>{S(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},P=e=>r.movableIds(A[e]).filter(e=>x.has(e)),F=P(`source`),I=P(`target`);return(0,o.jsxs)(`div`,{className:e.cn(i.default.wrapper,_),...v,children:[j.map(e=>{let n=A[e.side],a=r.movableIds(n),s=a.filter(e=>x.has(e)),c=a.length>0&&s.length===a.length,l=`${b}-${e.side}-title`;return(0,o.jsxs)(`section`,{className:i.default.pane,"aria-labelledby":l,children:[(0,o.jsxs)(`div`,{className:i.default.paneHeader,children:[(0,o.jsxs)(`div`,{className:i.default.paneTitle,children:[(0,o.jsx)(t.Checkbox,{checked:c,indeterminate:s.length>0&&!c,disabled:g||a.length===0,"aria-label":typeof e.title==`string`?e.title:String(e.side),onChange:()=>S(e=>{let t=new Set(e);for(let e of a)c?t.delete(e):t.add(e);return t})}),(0,o.jsx)(`h3`,{className:i.default.title,id:l,children:e.title})]}),(0,o.jsx)(`span`,{className:i.default.count,children:y.selected(s.length,e.items.length)})]}),k&&(0,o.jsx)(`input`,{type:`search`,className:i.default.search,value:C[e.side],placeholder:y.search,"aria-label":`${y.search}: ${typeof e.title==`string`?e.title:e.side}`,onChange:t=>w(n=>({...n,[e.side]:t.target.value}))}),(0,o.jsx)(`ul`,{className:i.default.list,style:{height:h},children:n.length===0?(0,o.jsx)(`li`,{className:i.default.empty,children:C[e.side]?y.noMatches:y.empty}):n.map(n=>(0,o.jsx)(`li`,{className:i.default.row,children:(0,o.jsx)(t.Checkbox,{checked:x.has(n.id),disabled:g||n.disabled,label:p?p(n,e.side):n.label,onChange:()=>N(n.id),onDoubleClick:()=>M([n.id],e.side===`source`?`target`:`source`)})},n.id))})]},e.side)}),(0,o.jsxs)(`div`,{className:i.default.controls,children:[(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||r.movableIds(A.source).length===0,"aria-label":y.allToTarget,title:y.allToTarget,onClick:()=>M(r.movableIds(A.source),`target`),children:`»`}),(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||F.length===0,"aria-label":y.toTarget,title:y.toTarget,onClick:()=>M(F,`target`),children:`›`}),(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||I.length===0,"aria-label":y.toSource,title:y.toSource,onClick:()=>M(I,`source`),children:`‹`}),(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||r.movableIds(A.target).length===0,"aria-label":y.allToSource,title:y.allToSource,onClick:()=>M(r.movableIds(A.target),`source`),children:`«`})]}),(0,o.jsx)(n.VisuallyHidden,{"aria-live":`polite`,role:`status`,children:T})]})}exports.Transfer=s;
2
+ //# sourceMappingURL=Transfer.cjs.map