tempest-react-sdk 0.26.1 → 0.28.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 (132) hide show
  1. package/README.md +25 -1
  2. package/bin/lib/css/analyze.mjs +149 -0
  3. package/bin/lib/css/analyze.test.mjs +136 -0
  4. package/bin/lib/css/collect.mjs +152 -0
  5. package/bin/lib/css/findings.mjs +79 -0
  6. package/bin/lib/css/fix.e2e.test.mjs +99 -0
  7. package/bin/lib/css/fix.mjs +138 -0
  8. package/bin/lib/css/fix.test.mjs +108 -0
  9. package/bin/lib/css/index.mjs +16 -0
  10. package/bin/lib/css/parse.mjs +398 -0
  11. package/bin/lib/css/parse.test.mjs +146 -0
  12. package/bin/lib/css/properties.mjs +399 -0
  13. package/bin/lib/css/repetition.mjs +213 -0
  14. package/bin/lib/css/repetition.test.mjs +166 -0
  15. package/bin/lib/css/semantic.mjs +327 -0
  16. package/bin/lib/css/semantic.test.mjs +201 -0
  17. package/bin/lib/css/tokens.mjs +119 -0
  18. package/bin/lib/doctor/doctor.e2e.test.mjs +246 -0
  19. package/bin/tempest.mjs +272 -36
  20. package/dist/charts/scales.cjs +2 -0
  21. package/dist/charts/scales.cjs.map +1 -0
  22. package/dist/charts/scales.js +29 -0
  23. package/dist/charts/scales.js.map +1 -0
  24. package/dist/charts.cjs +1 -1
  25. package/dist/charts.d.ts +91 -0
  26. package/dist/charts.js +9 -8
  27. package/dist/components/BottomNavigation/BottomNavigation.module.cjs.map +1 -1
  28. package/dist/components/BottomNavigation/BottomNavigation.module.js.map +1 -1
  29. package/dist/components/Carousel/Carousel.module.cjs.map +1 -1
  30. package/dist/components/Carousel/Carousel.module.js.map +1 -1
  31. package/dist/components/CodeBlock/CodeBlock.cjs +3 -0
  32. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -0
  33. package/dist/components/CodeBlock/CodeBlock.js +53 -0
  34. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  35. package/dist/components/CodeBlock/CodeBlock.module.cjs +2 -0
  36. package/dist/components/CodeBlock/CodeBlock.module.cjs.map +1 -0
  37. package/dist/components/CodeBlock/CodeBlock.module.js +29 -0
  38. package/dist/components/CodeBlock/CodeBlock.module.js.map +1 -0
  39. package/dist/components/CodeBlock/tokenize.cjs +3 -0
  40. package/dist/components/CodeBlock/tokenize.cjs.map +1 -0
  41. package/dist/components/CodeBlock/tokenize.js +267 -0
  42. package/dist/components/CodeBlock/tokenize.js.map +1 -0
  43. package/dist/components/ImageCropper/ImageCropper.cjs +2 -0
  44. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -0
  45. package/dist/components/ImageCropper/ImageCropper.js +240 -0
  46. package/dist/components/ImageCropper/ImageCropper.js.map +1 -0
  47. package/dist/components/ImageCropper/ImageCropper.module.cjs +2 -0
  48. package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -0
  49. package/dist/components/ImageCropper/ImageCropper.module.js +15 -0
  50. package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -0
  51. package/dist/components/ImageCropper/crop-geometry.cjs +2 -0
  52. package/dist/components/ImageCropper/crop-geometry.cjs.map +1 -0
  53. package/dist/components/ImageCropper/crop-geometry.js +52 -0
  54. package/dist/components/ImageCropper/crop-geometry.js.map +1 -0
  55. package/dist/components/Navbar/Navbar.module.cjs.map +1 -1
  56. package/dist/components/Navbar/Navbar.module.js.map +1 -1
  57. package/dist/components/QRCode/QRCode.cjs +2 -0
  58. package/dist/components/QRCode/QRCode.cjs.map +1 -0
  59. package/dist/components/QRCode/QRCode.js +49 -0
  60. package/dist/components/QRCode/QRCode.js.map +1 -0
  61. package/dist/components/QRCode/QRCode.module.cjs +2 -0
  62. package/dist/components/QRCode/QRCode.module.cjs.map +1 -0
  63. package/dist/components/QRCode/QRCode.module.js +9 -0
  64. package/dist/components/QRCode/QRCode.module.js.map +1 -0
  65. package/dist/components/QRCode/qr-encode.cjs +2 -0
  66. package/dist/components/QRCode/qr-encode.cjs.map +1 -0
  67. package/dist/components/QRCode/qr-encode.js +295 -0
  68. package/dist/components/QRCode/qr-encode.js.map +1 -0
  69. package/dist/components/QRCode/qr-tables.cjs +2 -0
  70. package/dist/components/QRCode/qr-tables.cjs.map +1 -0
  71. package/dist/components/QRCode/qr-tables.js +366 -0
  72. package/dist/components/QRCode/qr-tables.js.map +1 -0
  73. package/dist/components/Scheduler/Scheduler.cjs +2 -0
  74. package/dist/components/Scheduler/Scheduler.cjs.map +1 -0
  75. package/dist/components/Scheduler/Scheduler.js +134 -0
  76. package/dist/components/Scheduler/Scheduler.js.map +1 -0
  77. package/dist/components/Scheduler/Scheduler.module.cjs +2 -0
  78. package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -0
  79. package/dist/components/Scheduler/Scheduler.module.js +26 -0
  80. package/dist/components/Scheduler/Scheduler.module.js.map +1 -0
  81. package/dist/components/Scheduler/scheduler-layout.cjs +2 -0
  82. package/dist/components/Scheduler/scheduler-layout.cjs.map +1 -0
  83. package/dist/components/Scheduler/scheduler-layout.js +97 -0
  84. package/dist/components/Scheduler/scheduler-layout.js.map +1 -0
  85. package/dist/components/ScrollArea/ScrollArea.cjs +1 -1
  86. package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
  87. package/dist/components/ScrollArea/ScrollArea.js +22 -16
  88. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  89. package/dist/components/ScrollArea/ScrollArea.module.cjs.map +1 -1
  90. package/dist/components/ScrollArea/ScrollArea.module.js.map +1 -1
  91. package/dist/components/Sparkline/Sparkline.cjs +2 -0
  92. package/dist/components/Sparkline/Sparkline.cjs.map +1 -0
  93. package/dist/components/Sparkline/Sparkline.js +64 -0
  94. package/dist/components/Sparkline/Sparkline.js.map +1 -0
  95. package/dist/components/Sparkline/Sparkline.module.cjs +2 -0
  96. package/dist/components/Sparkline/Sparkline.module.cjs.map +1 -0
  97. package/dist/components/Sparkline/Sparkline.module.js +10 -0
  98. package/dist/components/Sparkline/Sparkline.module.js.map +1 -0
  99. package/dist/components/Sparkline/sparkline-geometry.cjs +2 -0
  100. package/dist/components/Sparkline/sparkline-geometry.cjs.map +1 -0
  101. package/dist/components/Sparkline/sparkline-geometry.js +56 -0
  102. package/dist/components/Sparkline/sparkline-geometry.js.map +1 -0
  103. package/dist/components/Table/Table.cjs +1 -1
  104. package/dist/components/Table/Table.cjs.map +1 -1
  105. package/dist/components/Table/Table.js +37 -30
  106. package/dist/components/Table/Table.js.map +1 -1
  107. package/dist/components/Table/Table.module.cjs.map +1 -1
  108. package/dist/components/Table/Table.module.js.map +1 -1
  109. package/dist/components/VirtualList/VirtualList.cjs +1 -1
  110. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  111. package/dist/components/VirtualList/VirtualList.js +13 -11
  112. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  113. package/dist/components/VirtualList/VirtualList.module.cjs.map +1 -1
  114. package/dist/components/VirtualList/VirtualList.module.js.map +1 -1
  115. package/dist/hooks/use-scroll-overflow.cjs +2 -0
  116. package/dist/hooks/use-scroll-overflow.cjs.map +1 -0
  117. package/dist/hooks/use-scroll-overflow.js +22 -0
  118. package/dist/hooks/use-scroll-overflow.js.map +1 -0
  119. package/dist/styles.css +1 -1
  120. package/dist/tempest-react-sdk.cjs +1 -1
  121. package/dist/tempest-react-sdk.d.ts +607 -2
  122. package/dist/tempest-react-sdk.js +197 -188
  123. package/dist/theme/create-theme.cjs +3 -3
  124. package/dist/theme/create-theme.cjs.map +1 -1
  125. package/dist/theme/create-theme.js +52 -28
  126. package/dist/theme/create-theme.js.map +1 -1
  127. package/dist/theme/data-viz-ramps.cjs +2 -0
  128. package/dist/theme/data-viz-ramps.cjs.map +1 -0
  129. package/dist/theme/data-viz-ramps.js +58 -0
  130. package/dist/theme/data-viz-ramps.js.map +1 -0
  131. package/package.json +2 -1
  132. package/template/package.json +1 -1
@@ -0,0 +1,267 @@
1
+ //#region src/components/CodeBlock/tokenize.ts
2
+ var e = {
3
+ ts: "typescript",
4
+ typescript: "typescript",
5
+ js: "javascript",
6
+ javascript: "javascript",
7
+ mjs: "javascript",
8
+ cjs: "javascript",
9
+ tsx: "tsx",
10
+ jsx: "jsx",
11
+ json: "json",
12
+ jsonc: "json",
13
+ css: "css",
14
+ scss: "css",
15
+ html: "html",
16
+ xml: "html",
17
+ svg: "html",
18
+ sh: "bash",
19
+ bash: "bash",
20
+ shell: "bash",
21
+ zsh: "bash",
22
+ console: "bash",
23
+ py: "python",
24
+ python: "python",
25
+ sql: "sql"
26
+ };
27
+ function t(t) {
28
+ return t ? e[t.toLowerCase()] ?? "plain" : "plain";
29
+ }
30
+ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield", r = "true|false|null|undefined|NaN|Infinity", i = {
31
+ typescript: a(!1),
32
+ javascript: a(!1),
33
+ tsx: a(!0),
34
+ jsx: a(!0),
35
+ json: [
36
+ {
37
+ kind: "property",
38
+ pattern: /"(?:[^"\\]|\\.)*"(?=\s*:)/y
39
+ },
40
+ {
41
+ kind: "string",
42
+ pattern: /"(?:[^"\\]|\\.)*"/y
43
+ },
44
+ {
45
+ kind: "number",
46
+ pattern: /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y
47
+ },
48
+ {
49
+ kind: "literal",
50
+ pattern: /\b(?:true|false|null)\b/y
51
+ },
52
+ {
53
+ kind: "punctuation",
54
+ pattern: /[{}[\],:]/y
55
+ }
56
+ ],
57
+ css: [
58
+ {
59
+ kind: "comment",
60
+ pattern: /\/\*[\s\S]*?\*\//y
61
+ },
62
+ {
63
+ kind: "string",
64
+ pattern: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y
65
+ },
66
+ {
67
+ kind: "keyword",
68
+ pattern: /@[a-zA-Z-]+/y
69
+ },
70
+ {
71
+ kind: "property",
72
+ pattern: /--?[a-zA-Z][\w-]*(?=\s*:)/y
73
+ },
74
+ {
75
+ kind: "function",
76
+ pattern: /[a-zA-Z-]+(?=\()/y
77
+ },
78
+ {
79
+ kind: "number",
80
+ pattern: /-?\d+(?:\.\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y
81
+ },
82
+ {
83
+ kind: "punctuation",
84
+ pattern: /[{}();:,>+~]/y
85
+ }
86
+ ],
87
+ html: [
88
+ {
89
+ kind: "comment",
90
+ pattern: /<!--[\s\S]*?-->/y
91
+ },
92
+ {
93
+ kind: "string",
94
+ pattern: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y
95
+ },
96
+ {
97
+ kind: "tag",
98
+ pattern: /<\/?[a-zA-Z][\w:-]*/y
99
+ },
100
+ {
101
+ kind: "attribute",
102
+ pattern: /[a-zA-Z][\w:-]*(?==)/y
103
+ },
104
+ {
105
+ kind: "punctuation",
106
+ pattern: /\/?>|=/y
107
+ }
108
+ ],
109
+ bash: [
110
+ {
111
+ kind: "comment",
112
+ pattern: /#[^\n]*/y
113
+ },
114
+ {
115
+ kind: "string",
116
+ pattern: /"(?:[^"\\]|\\.)*"|'[^']*'/y
117
+ },
118
+ {
119
+ kind: "keyword",
120
+ pattern: /\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\b/y
121
+ },
122
+ {
123
+ kind: "property",
124
+ pattern: /\$\{?[A-Za-z_][\w]*\}?/y
125
+ },
126
+ {
127
+ kind: "attribute",
128
+ pattern: /(?<=\s)--?[a-zA-Z][\w-]*/y
129
+ },
130
+ {
131
+ kind: "punctuation",
132
+ pattern: /[|&;()<>]/y
133
+ }
134
+ ],
135
+ python: [
136
+ {
137
+ kind: "comment",
138
+ pattern: /#[^\n]*/y
139
+ },
140
+ {
141
+ kind: "string",
142
+ pattern: /"""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y
143
+ },
144
+ {
145
+ kind: "keyword",
146
+ pattern: /\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/y
147
+ },
148
+ {
149
+ kind: "literal",
150
+ pattern: /\b(?:True|False|None|self)\b/y
151
+ },
152
+ {
153
+ kind: "number",
154
+ pattern: /\b\d+(?:\.\d+)?\b/y
155
+ },
156
+ {
157
+ kind: "function",
158
+ pattern: /[A-Za-z_]\w*(?=\()/y
159
+ },
160
+ {
161
+ kind: "punctuation",
162
+ pattern: /[{}[\]().,:;=+\-*/<>!]/y
163
+ }
164
+ ],
165
+ sql: [
166
+ {
167
+ kind: "comment",
168
+ pattern: /--[^\n]*|\/\*[\s\S]*?\*\//y
169
+ },
170
+ {
171
+ kind: "string",
172
+ pattern: /'(?:[^']|'')*'/y
173
+ },
174
+ {
175
+ kind: "keyword",
176
+ pattern: /\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\b/iy
177
+ },
178
+ {
179
+ kind: "number",
180
+ pattern: /\b\d+(?:\.\d+)?\b/y
181
+ },
182
+ {
183
+ kind: "punctuation",
184
+ pattern: /[(),;*=<>]/y
185
+ }
186
+ ],
187
+ plain: []
188
+ };
189
+ function a(e) {
190
+ let t = [
191
+ {
192
+ kind: "comment",
193
+ pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//y
194
+ },
195
+ {
196
+ kind: "string",
197
+ pattern: /`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y
198
+ },
199
+ {
200
+ kind: "keyword",
201
+ pattern: RegExp(`\\b(?:${n})\\b`, "y")
202
+ },
203
+ {
204
+ kind: "literal",
205
+ pattern: RegExp(`\\b(?:${r})\\b`, "y")
206
+ },
207
+ {
208
+ kind: "number",
209
+ pattern: /\b(?:0[xX][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/y
210
+ },
211
+ {
212
+ kind: "function",
213
+ pattern: /[A-Za-z_$][\w$]*(?=\s*\()/y
214
+ },
215
+ {
216
+ kind: "punctuation",
217
+ pattern: /[{}[\]().,;:?!=<>+\-*/%&|^~]/y
218
+ }
219
+ ];
220
+ return e && t.splice(2, 0, {
221
+ kind: "tag",
222
+ pattern: /<\/?[A-Za-z][\w.]*|\/>/y
223
+ }), t;
224
+ }
225
+ function o(e, n) {
226
+ let r = i[t(n)];
227
+ if (r.length === 0) return e === "" ? [] : [{
228
+ kind: "plain",
229
+ value: e
230
+ }];
231
+ let a = [], o = 0, s = 0, c = (t) => {
232
+ t > o && a.push({
233
+ kind: "plain",
234
+ value: e.slice(o, t)
235
+ });
236
+ };
237
+ for (; s < e.length;) {
238
+ let t = null;
239
+ for (let n of r) {
240
+ n.pattern.lastIndex = s;
241
+ let r = n.pattern.exec(e);
242
+ if (r && r[0].length > 0) {
243
+ t = {
244
+ kind: n.kind,
245
+ value: r[0]
246
+ };
247
+ break;
248
+ }
249
+ }
250
+ t ? (c(s), a.push(t), s += t.value.length, o = s) : s++;
251
+ }
252
+ return c(e.length), a;
253
+ }
254
+ function s(e, t) {
255
+ let n = [[]];
256
+ for (let r of o(e, t)) r.value.split("\n").forEach((e, t) => {
257
+ t > 0 && n.push([]), e !== "" && n[n.length - 1].push({
258
+ kind: r.kind,
259
+ value: e
260
+ });
261
+ });
262
+ return n;
263
+ }
264
+ //#endregion
265
+ export { t as resolveLanguage, o as tokenize, s as tokenizeLines };
266
+
267
+ //# sourceMappingURL=tokenize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenize.js","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /--?[a-zA-Z][\\w-]*(?=\\s*:)/y },\n { kind: \"function\", pattern: /[a-zA-Z-]+(?=\\()/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?-->/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern: /\"\"\"[\\s\\S]*?\"\"\"|'''[\\s\\S]*?'''|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n {\n kind: \"string\",\n pattern: /`(?:[^`\\\\]|\\\\.)*`|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})\\\\b`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})\\\\b`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)\\b/y,\n },\n { kind: \"function\", pattern: /[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n ];\n if (withJsx) {\n // Before the keyword rule, so `<Button` reads as a tag and not as `<`.\n rules.splice(2, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" x \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) tokens.push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n tokens.push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("./crop-geometry.cjs"),n=require("./ImageCropper.module.cjs");let r=require("react"),i=require("react/jsx-runtime");var a=12,o=.15;function s({src:s,aspect:c=1,maxZoom:l=4,maxSize:u,outputType:d=`image/png`,outputQuality:f=.92,shape:p=`rect`,onCropChange:m,label:h=`Área de recorte`,className:g,ref:_,...v}){let y=`${(0,r.useId)()}-hint`,b=(0,r.useRef)(null),x=(0,r.useRef)(null),S=(0,r.useRef)(null),[C,w]=(0,r.useState)(null),[T,E]=(0,r.useState)(null),[D,O]=(0,r.useState)({width:0,height:0}),[k,A]=(0,r.useState)(1),[j,M]=(0,r.useState)({x:0,y:0});(0,r.useEffect)(()=>{if(typeof s==`string`){w(s);return}let e=URL.createObjectURL(s);return w(e),()=>URL.revokeObjectURL(e)},[s]),(0,r.useEffect)(()=>{E(null),A(1),M({x:0,y:0})},[C]),(0,r.useEffect)(()=>{let e=b.current;if(!e)return;let t=()=>O({width:e.clientWidth,height:e.clientHeight});if(t(),typeof ResizeObserver>`u`)return;let n=new ResizeObserver(t);return n.observe(e),()=>n.disconnect()},[]);let N=T?t.coverScale(T,D)*k:0,P=(0,r.useMemo)(()=>T?{width:T.width*N,height:T.height*N}:{width:0,height:0},[T,N]),F=(0,r.useCallback)(e=>{M(n=>{let r=t.clampOffset(e,P,D);return r.x===n.x&&r.y===n.y?n:(m?.({zoom:k,offset:r}),r)})},[P,D,m,k]),I=(0,r.useCallback)(e=>{let n=Math.min(l,Math.max(1,e));if(A(n),!T)return;let r=t.coverScale(T,D)*n,i={width:T.width*r,height:T.height*r};M(e=>{let r=t.clampOffset(e,i,D);return m?.({zoom:n,offset:r}),r})},[D,l,T,m]),L=(0,r.useCallback)(()=>{A(1),M({x:0,y:0}),m?.({zoom:1,offset:{x:0,y:0}})},[m]),R=(0,r.useCallback)(async()=>{let e=x.current;if(!e||!T)return null;let n=t.computeCropRect({image:T,frame:D,zoom:k,offset:j});if(n.sWidth<=0||n.sHeight<=0)return null;let r=t.outputSize(n,u),i=document.createElement(`canvas`);i.width=r.width,i.height=r.height;let a=i.getContext(`2d`);return a?(a.drawImage(e,n.sx,n.sy,n.sWidth,n.sHeight,0,0,r.width,r.height),new Promise(e=>{i.toBlob(t=>e(t),d,f)})):null},[D,u,T,j,f,d,k]);(0,r.useImperativeHandle)(_,()=>({crop:R,reset:L}),[R,L]);let z=e=>{T&&(S.current={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,from:j},e.currentTarget.setPointerCapture?.(e.pointerId))},B=e=>{let t=S.current;!t||t.pointerId!==e.pointerId||F({x:t.from.x+(e.clientX-t.startX),y:t.from.y+(e.clientY-t.startY)})},V=e=>{S.current?.pointerId===e.pointerId&&(S.current=null)},H=e=>{let t=e.shiftKey?a*4:a,n={ArrowLeft:{x:-t,y:0},ArrowRight:{x:t,y:0},ArrowUp:{x:0,y:-t},ArrowDown:{x:0,y:t}}[e.key];if(n){e.preventDefault(),F({x:j.x+n.x,y:j.y+n.y});return}e.key===`+`||e.key===`=`?(e.preventDefault(),I(k+o)):e.key===`-`||e.key===`_`?(e.preventDefault(),I(k-o)):e.key===`0`&&(e.preventDefault(),L())};return(0,i.jsxs)(`div`,{className:e.cn(n.default.wrapper,g),...v,children:[(0,i.jsx)(`div`,{ref:b,className:e.cn(n.default.frame,p===`circle`&&n.default.circle),style:{aspectRatio:String(c)},role:`group`,"aria-label":h,"aria-describedby":y,tabIndex:0,onPointerDown:z,onPointerMove:B,onPointerUp:V,onPointerCancel:V,onKeyDown:H,onWheel:e=>{e.preventDefault(),I(k+(e.deltaY<0?o:-.15))},children:C&&(0,i.jsx)(`img`,{ref:x,src:C,alt:``,draggable:!1,className:n.default.image,style:{width:P.width||void 0,height:P.height||void 0,transform:`translate(${j.x}px, ${j.y}px)`},onLoad:e=>{let t=e.currentTarget;t.naturalWidth>0&&t.naturalHeight>0&&E({width:t.naturalWidth,height:t.naturalHeight})}})}),(0,i.jsxs)(`div`,{className:n.default.controls,children:[(0,i.jsx)(`input`,{type:`range`,className:n.default.zoom,min:1,max:l,step:.01,value:k,onChange:e=>I(Number(e.target.value)),"aria-label":`Zoom`,disabled:!T}),(0,i.jsx)(`button`,{type:`button`,className:n.default.reset,onClick:L,disabled:!T,children:`Centralizar`})]}),(0,i.jsxs)(`p`,{id:y,className:n.default.hint,children:[`Arraste para reposicionar. Setas movem, `,(0,i.jsx)(`kbd`,{children:`+`}),` e `,(0,i.jsx)(`kbd`,{children:`−`}),` dão zoom,`,` `,(0,i.jsx)(`kbd`,{children:`0`}),` centraliza.`]})]})}exports.ImageCropper=s;
2
+ //# sourceMappingURL=ImageCropper.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageCropper.cjs","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.tsx"],"sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport {\n clampOffset,\n computeCropRect,\n coverScale,\n type Offset,\n outputSize,\n type Size,\n} from \"./crop-geometry\";\nimport styles from \"./ImageCropper.module.css\";\n\n/** Imperative handle for exporting the current crop. */\nexport interface ImageCropperHandle {\n /**\n * Render the current crop and resolve with it.\n *\n * Resolves `null` when the image has not loaded yet or the browser refuses to\n * encode — never throws, so a submit handler does not need a try/catch.\n */\n crop: () => Promise<Blob | null>;\n /** Recentre at zoom 1. */\n reset: () => void;\n}\n\nexport interface ImageCropperProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The image to crop: a `File`/`Blob` from an input, or a URL. */\n src: File | Blob | string;\n /** Crop aspect ratio as `width / height`. Default `1` (square). */\n aspect?: number;\n /** Maximum zoom over the cover scale. Default `4`. */\n maxZoom?: number;\n /**\n * Cap on the longest edge of the exported image, in px.\n *\n * Without it the export keeps the source resolution, which is right for a\n * document scan and wasteful for an avatar.\n */\n maxSize?: number;\n /** Output MIME type. Default `\"image/png\"`. */\n outputType?: string;\n /** Output quality for lossy types, `0`–`1`. Default `0.92`. */\n outputQuality?: number;\n /** Overlay shape. `\"circle\"` for an avatar, `\"rect\"` for a document. Default `\"rect\"`. */\n shape?: \"rect\" | \"circle\";\n /** Called whenever the crop changes, e.g. to enable a submit button. */\n onCropChange?: (state: { zoom: number; offset: Offset }) => void;\n /** Accessible name for the crop area. */\n label?: string;\n ref?: React.Ref<ImageCropperHandle>;\n}\n\n/** Pixels moved per arrow-key press. */\nconst KEY_STEP = 12;\n\n/** Zoom added or removed per `+`/`-` press, and per wheel notch. */\nconst ZOOM_STEP = 0.15;\n\n/**\n * Crop an image to a fixed aspect ratio.\n *\n * The frame stays put and the image pans and zooms behind it — the model an avatar\n * or document-photo flow wants, where the output shape is decided by the app and\n * the user only chooses what lands inside it. (A free-form draggable rectangle is a\n * different component; this one cannot produce an off-ratio crop by construction.)\n *\n * The export reads the *natural* pixels through a canvas, so a 4000 px photo is not\n * silently downsampled to whatever the on-screen preview measured. The image is\n * also always clamped to cover the frame, so an export can never contain the empty\n * bands you get from panning past an edge.\n *\n * Works by pointer, wheel and keyboard: arrows pan, `+`/`-` zoom, `0` resets.\n *\n * @example\n * const cropper = useRef<ImageCropperHandle>(null);\n *\n * <ImageCropper ref={cropper} src={file} aspect={1} shape=\"circle\" maxSize={512} />\n * <Button onClick={async () => upload(await cropper.current?.crop())}>Salvar</Button>\n */\nexport function ImageCropper({\n src,\n aspect = 1,\n maxZoom = 4,\n maxSize,\n outputType = \"image/png\",\n outputQuality = 0.92,\n shape = \"rect\",\n onCropChange,\n label = \"Área de recorte\",\n className,\n ref,\n ...rest\n}: ImageCropperProps) {\n /**\n * Id for the shortcut hint.\n *\n * Generated rather than derived from `label`: a label with spaces would produce\n * an id with spaces, and `aria-describedby` splits on whitespace — so the\n * description would silently never associate with the frame.\n */\n const hintId = `${useId()}-hint`;\n\n const frameRef = useRef<HTMLDivElement>(null);\n const imageRef = useRef<HTMLImageElement | null>(null);\n const dragRef = useRef<{\n pointerId: number;\n startX: number;\n startY: number;\n from: Offset;\n } | null>(null);\n\n const [url, setUrl] = useState<string | null>(null);\n const [natural, setNatural] = useState<Size | null>(null);\n const [frame, setFrame] = useState<Size>({ width: 0, height: 0 });\n const [zoom, setZoom] = useState(1);\n const [offset, setOffset] = useState<Offset>({ x: 0, y: 0 });\n\n /**\n * Resolve `src` to a displayable URL.\n *\n * A `File`/`Blob` needs `createObjectURL`, and the URL must be revoked when the\n * source changes or the component unmounts — otherwise every re-pick of a photo\n * leaks the previous one for the lifetime of the document.\n */\n useEffect(() => {\n if (typeof src === \"string\") {\n setUrl(src);\n return;\n }\n const objectUrl = URL.createObjectURL(src);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [src]);\n\n // A new source invalidates the previous framing.\n useEffect(() => {\n setNatural(null);\n setZoom(1);\n setOffset({ x: 0, y: 0 });\n }, [url]);\n\n /** Track the frame's rendered size — the crop math is all relative to it. */\n useEffect(() => {\n const element = frameRef.current;\n if (!element) return;\n const measure = () =>\n setFrame({ width: element.clientWidth, height: element.clientHeight });\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n\n const scale = natural ? coverScale(natural, frame) * zoom : 0;\n\n /**\n * On-screen image size.\n *\n * Memoized because the pan callback depends on it: a fresh object every render\n * would rebuild that callback every render, and the pointer handlers close over\n * it during a drag.\n */\n const displayed = useMemo<Size>(\n () =>\n natural\n ? { width: natural.width * scale, height: natural.height * scale }\n : { width: 0, height: 0 },\n [natural, scale],\n );\n\n /** Apply a pan, clamped so the frame stays covered. */\n const pan = useCallback(\n (next: Offset) => {\n setOffset((current) => {\n const clamped = clampOffset(next, displayed, frame);\n if (clamped.x === current.x && clamped.y === current.y) return current;\n onCropChange?.({ zoom, offset: clamped });\n return clamped;\n });\n },\n [displayed, frame, onCropChange, zoom],\n );\n\n /**\n * Apply a zoom, then re-clamp the offset.\n *\n * Re-clamping is not optional: zooming *out* shrinks the image, so an offset\n * that was legal a moment ago can now expose background.\n */\n const applyZoom = useCallback(\n (next: number) => {\n const clampedZoom = Math.min(maxZoom, Math.max(1, next));\n setZoom(clampedZoom);\n if (!natural) return;\n const nextScale = coverScale(natural, frame) * clampedZoom;\n const nextDisplayed = {\n width: natural.width * nextScale,\n height: natural.height * nextScale,\n };\n setOffset((current) => {\n const clamped = clampOffset(current, nextDisplayed, frame);\n onCropChange?.({ zoom: clampedZoom, offset: clamped });\n return clamped;\n });\n },\n [frame, maxZoom, natural, onCropChange],\n );\n\n const reset = useCallback(() => {\n setZoom(1);\n setOffset({ x: 0, y: 0 });\n onCropChange?.({ zoom: 1, offset: { x: 0, y: 0 } });\n }, [onCropChange]);\n\n /**\n * Draw the current crop and encode it.\n *\n * Returns `null` rather than throwing on the paths a caller cannot do anything\n * about: no image yet, no 2D context, or an encoder that declined.\n */\n const crop = useCallback(async (): Promise<Blob | null> => {\n const image = imageRef.current;\n if (!image || !natural) return null;\n\n const rect = computeCropRect({ image: natural, frame, zoom, offset });\n if (rect.sWidth <= 0 || rect.sHeight <= 0) return null;\n\n const out = outputSize(rect, maxSize);\n const canvas = document.createElement(\"canvas\");\n canvas.width = out.width;\n canvas.height = out.height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n\n context.drawImage(\n image,\n rect.sx,\n rect.sy,\n rect.sWidth,\n rect.sHeight,\n 0,\n 0,\n out.width,\n out.height,\n );\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => resolve(blob), outputType, outputQuality);\n });\n }, [frame, maxSize, natural, offset, outputQuality, outputType, zoom]);\n\n useImperativeHandle(ref, () => ({ crop, reset }), [crop, reset]);\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (!natural) return;\n dragRef.current = {\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n from: offset,\n };\n event.currentTarget.setPointerCapture?.(event.pointerId);\n };\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>): void => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== event.pointerId) return;\n pan({\n x: drag.from.x + (event.clientX - drag.startX),\n y: drag.from.y + (event.clientY - drag.startY),\n });\n };\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (dragRef.current?.pointerId !== event.pointerId) return;\n dragRef.current = null;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n const step = event.shiftKey ? KEY_STEP * 4 : KEY_STEP;\n const moves: Record<string, Offset> = {\n ArrowLeft: { x: -step, y: 0 },\n ArrowRight: { x: step, y: 0 },\n ArrowUp: { x: 0, y: -step },\n ArrowDown: { x: 0, y: step },\n };\n const move = moves[event.key];\n if (move) {\n event.preventDefault();\n pan({ x: offset.x + move.x, y: offset.y + move.y });\n return;\n }\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n applyZoom(zoom + ZOOM_STEP);\n } else if (event.key === \"-\" || event.key === \"_\") {\n event.preventDefault();\n applyZoom(zoom - ZOOM_STEP);\n } else if (event.key === \"0\") {\n event.preventDefault();\n reset();\n }\n };\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n <div\n ref={frameRef}\n className={cn(styles.frame, shape === \"circle\" && styles.circle)}\n style={{ aspectRatio: String(aspect) }}\n role=\"group\"\n aria-label={label}\n aria-describedby={hintId}\n tabIndex={0}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n onWheel={(event) => {\n event.preventDefault();\n applyZoom(zoom + (event.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP));\n }}\n >\n {url && (\n <img\n ref={imageRef}\n src={url}\n alt=\"\"\n draggable={false}\n className={styles.image}\n style={{\n width: displayed.width || undefined,\n height: displayed.height || undefined,\n transform: `translate(${offset.x}px, ${offset.y}px)`,\n }}\n onLoad={(event) => {\n const element = event.currentTarget;\n // A decode can succeed and still report no intrinsic size —\n // an SVG without a viewBox is the common case. Accepting\n // that would enable the controls over an image the crop\n // maths can do nothing with.\n if (element.naturalWidth > 0 && element.naturalHeight > 0) {\n setNatural({\n width: element.naturalWidth,\n height: element.naturalHeight,\n });\n }\n }}\n />\n )}\n </div>\n\n <div className={styles.controls}>\n <input\n type=\"range\"\n className={styles.zoom}\n min={1}\n max={maxZoom}\n step={0.01}\n value={zoom}\n onChange={(event) => applyZoom(Number(event.target.value))}\n aria-label=\"Zoom\"\n disabled={!natural}\n />\n <button type=\"button\" className={styles.reset} onClick={reset} disabled={!natural}>\n Centralizar\n </button>\n </div>\n\n <p id={hintId} className={styles.hint}>\n Arraste para reposicionar. Setas movem, <kbd>+</kbd> e <kbd>−</kbd> dão zoom,{\" \"}\n <kbd>0</kbd> centraliza.\n </p>\n </div>\n );\n}\n"],"mappings":"oKAkEA,IAAM,EAAW,GAGX,EAAY,IAuBlB,SAAgB,EAAa,CACzB,MACA,SAAS,EACT,UAAU,EACV,UACA,aAAa,YACb,gBAAgB,IAChB,QAAQ,OACR,eACA,QAAQ,kBACR,YACA,MACA,GAAG,GACe,CAQlB,IAAM,EAAS,IAAA,EAAA,EAAA,MAAA,CAAS,EAAE,OAEpB,GAAA,EAAA,EAAA,OAAA,CAAkC,IAAI,EACtC,GAAA,EAAA,EAAA,OAAA,CAA2C,IAAI,EAC/C,GAAA,EAAA,EAAA,OAAA,CAKI,IAAI,EAER,CAAC,EAAK,IAAA,EAAA,EAAA,SAAA,CAAkC,IAAI,EAC5C,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAoC,IAAI,EAClD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA2B,CAAE,MAAO,EAAG,OAAQ,CAAE,CAAC,EAC1D,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAoB,CAAC,EAC5B,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA8B,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,GAS3D,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,GAAQ,SAAU,CACzB,EAAO,CAAG,EACV,MACJ,CACA,IAAM,EAAY,IAAI,gBAAgB,CAAG,EAEzC,OADA,EAAO,CAAS,MACH,IAAI,gBAAgB,CAAS,CAC9C,EAAG,CAAC,CAAG,CAAC,GAGR,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,IAAI,EACf,EAAQ,CAAC,EACT,EAAU,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,CAC5B,EAAG,CAAC,CAAG,CAAC,GAGR,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAU,EAAS,QACzB,GAAI,CAAC,EAAS,OACd,IAAM,MACF,EAAS,CAAE,MAAO,EAAQ,YAAa,OAAQ,EAAQ,YAAa,CAAC,EAEzE,GADA,EAAQ,EACJ,OAAO,eAAmB,IAAa,OAC3C,IAAM,EAAW,IAAI,eAAe,CAAO,EAE3C,OADA,EAAS,QAAQ,CAAO,MACX,EAAS,WAAW,CACrC,EAAG,CAAC,CAAC,EAEL,IAAM,EAAQ,EAAU,EAAA,WAAW,EAAS,CAAK,EAAI,EAAO,EAStD,GAAA,EAAA,EAAA,QAAA,KAEE,EACM,CAAE,MAAO,EAAQ,MAAQ,EAAO,OAAQ,EAAQ,OAAS,CAAM,EAC/D,CAAE,MAAO,EAAG,OAAQ,CAAE,EAChC,CAAC,EAAS,CAAK,CACnB,EAGM,GAAA,EAAA,EAAA,YAAA,CACD,GAAiB,CACd,EAAW,GAAY,CACnB,IAAM,EAAU,EAAA,YAAY,EAAM,EAAW,CAAK,EAGlD,OAFI,EAAQ,IAAM,EAAQ,GAAK,EAAQ,IAAM,EAAQ,EAAU,GAC/D,IAAe,CAAE,OAAM,OAAQ,CAAQ,CAAC,EACjC,EACX,CAAC,CACL,EACA,CAAC,EAAW,EAAO,EAAc,CAAI,CACzC,EAQM,GAAA,EAAA,EAAA,YAAA,CACD,GAAiB,CACd,IAAM,EAAc,KAAK,IAAI,EAAS,KAAK,IAAI,EAAG,CAAI,CAAC,EAEvD,GADA,EAAQ,CAAW,EACf,CAAC,EAAS,OACd,IAAM,EAAY,EAAA,WAAW,EAAS,CAAK,EAAI,EACzC,EAAgB,CAClB,MAAO,EAAQ,MAAQ,EACvB,OAAQ,EAAQ,OAAS,CAC7B,EACA,EAAW,GAAY,CACnB,IAAM,EAAU,EAAA,YAAY,EAAS,EAAe,CAAK,EAEzD,OADA,IAAe,CAAE,KAAM,EAAa,OAAQ,CAAQ,CAAC,EAC9C,CACX,CAAC,CACL,EACA,CAAC,EAAO,EAAS,EAAS,CAAY,CAC1C,EAEM,GAAA,EAAA,EAAA,YAAA,KAA0B,CAC5B,EAAQ,CAAC,EACT,EAAU,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,EACxB,IAAe,CAAE,KAAM,EAAG,OAAQ,CAAE,EAAG,EAAG,EAAG,CAAE,CAAE,CAAC,CACtD,EAAG,CAAC,CAAY,CAAC,EAQX,GAAA,EAAA,EAAA,YAAA,CAAmB,SAAkC,CACvD,IAAM,EAAQ,EAAS,QACvB,GAAI,CAAC,GAAS,CAAC,EAAS,OAAO,KAE/B,IAAM,EAAO,EAAA,gBAAgB,CAAE,MAAO,EAAS,QAAO,OAAM,QAAO,CAAC,EACpE,GAAI,EAAK,QAAU,GAAK,EAAK,SAAW,EAAG,OAAO,KAElD,IAAM,EAAM,EAAA,WAAW,EAAM,CAAO,EAC9B,EAAS,SAAS,cAAc,QAAQ,EAC9C,EAAO,MAAQ,EAAI,MACnB,EAAO,OAAS,EAAI,OACpB,IAAM,EAAU,EAAO,WAAW,IAAI,EAetC,OAdK,GAEL,EAAQ,UACJ,EACA,EAAK,GACL,EAAK,GACL,EAAK,OACL,EAAK,QACL,EACA,EACA,EAAI,MACJ,EAAI,MACR,EAEO,IAAI,QAAS,GAAY,CAC5B,EAAO,OAAQ,GAAS,EAAQ,CAAI,EAAG,EAAY,CAAa,CACpE,CAAC,GAhBoB,IAiBzB,EAAG,CAAC,EAAO,EAAS,EAAS,EAAQ,EAAe,EAAY,CAAI,CAAC,GAErE,EAAA,EAAA,oBAAA,CAAoB,OAAY,CAAE,OAAM,OAAM,GAAI,CAAC,EAAM,CAAK,CAAC,EAE/D,IAAM,EAAiB,GAAmD,CACjE,IACL,EAAQ,QAAU,CACd,UAAW,EAAM,UACjB,OAAQ,EAAM,QACd,OAAQ,EAAM,QACd,KAAM,CACV,EACA,EAAM,cAAc,oBAAoB,EAAM,SAAS,EAC3D,EAEM,EAAiB,GAAmD,CACtE,IAAM,EAAO,EAAQ,QACjB,CAAC,GAAQ,EAAK,YAAc,EAAM,WACtC,EAAI,CACA,EAAG,EAAK,KAAK,GAAK,EAAM,QAAU,EAAK,QACvC,EAAG,EAAK,KAAK,GAAK,EAAM,QAAU,EAAK,OAC3C,CAAC,CACL,EAEM,EAAW,GAAmD,CAC5D,EAAQ,SAAS,YAAc,EAAM,YACzC,EAAQ,QAAU,KACtB,EAEM,EAAa,GAA+C,CAC9D,IAAM,EAAO,EAAM,SAAW,EAAW,EAAI,EAOvC,EAAO,CALT,UAAW,CAAE,EAAG,CAAC,EAAM,EAAG,CAAE,EAC5B,WAAY,CAAE,EAAG,EAAM,EAAG,CAAE,EAC5B,QAAS,CAAE,EAAG,EAAG,EAAG,CAAC,CAAK,EAC1B,UAAW,CAAE,EAAG,EAAG,EAAG,CAAK,CAElB,EAAM,EAAM,KACzB,GAAI,EAAM,CACN,EAAM,eAAe,EACrB,EAAI,CAAE,EAAG,EAAO,EAAI,EAAK,EAAG,EAAG,EAAO,EAAI,EAAK,CAAE,CAAC,EAClD,MACJ,CACI,EAAM,MAAQ,KAAO,EAAM,MAAQ,KACnC,EAAM,eAAe,EACrB,EAAU,EAAO,CAAS,GACnB,EAAM,MAAQ,KAAO,EAAM,MAAQ,KAC1C,EAAM,eAAe,EACrB,EAAU,EAAO,CAAS,GACnB,EAAM,MAAQ,MACrB,EAAM,eAAe,EACrB,EAAM,EAEd,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EAAG,GAAI,WAAnD,EACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,IAAU,UAAY,EAAA,QAAO,MAAM,EAC/D,MAAO,CAAE,YAAa,OAAO,CAAM,CAAE,EACrC,KAAK,QACL,aAAY,EACZ,mBAAkB,EAClB,SAAU,EACK,gBACA,gBACf,YAAa,EACb,gBAAiB,EACN,YACX,QAAU,GAAU,CAChB,EAAM,eAAe,EACrB,EAAU,GAAQ,EAAM,OAAS,EAAI,EAAY,KAAW,CAChE,WAEC,IACG,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,IAAK,EACL,IAAI,GACJ,UAAW,GACX,UAAW,EAAA,QAAO,MAClB,MAAO,CACH,MAAO,EAAU,OAAS,IAAA,GAC1B,OAAQ,EAAU,QAAU,IAAA,GAC5B,UAAW,aAAa,EAAO,EAAE,MAAM,EAAO,EAAE,IACpD,EACA,OAAS,GAAU,CACf,IAAM,EAAU,EAAM,cAKlB,EAAQ,aAAe,GAAK,EAAQ,cAAgB,GACpD,EAAW,CACP,MAAO,EAAQ,aACf,OAAQ,EAAQ,aACpB,CAAC,CAET,CACH,CAAA,CAEJ,CAAA,GAEL,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,kBAAvB,EACI,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,KAAK,QACL,UAAW,EAAA,QAAO,KAClB,IAAK,EACL,IAAK,EACL,KAAM,IACN,MAAO,EACP,SAAW,GAAU,EAAU,OAAO,EAAM,OAAO,KAAK,CAAC,EACzD,aAAW,OACX,SAAU,CAAC,CACd,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,KAAK,SAAS,UAAW,EAAA,QAAO,MAAO,QAAS,EAAO,SAAU,CAAC,WAAS,aAE3E,CAAA,CACP,KAEL,EAAA,EAAA,KAAA,CAAC,IAAD,CAAG,GAAI,EAAQ,UAAW,EAAA,QAAO,cAAjC,CAAuC,4CACK,EAAA,EAAA,IAAA,CAAC,MAAD,CAAA,SAAK,GAAM,CAAA,EAAC,OAAG,EAAA,EAAA,IAAA,CAAC,MAAD,CAAA,SAAK,GAAM,CAAA,EAAC,aAAW,KAC9E,EAAA,EAAA,IAAA,CAAC,MAAD,CAAA,SAAK,GAAM,CAAA,EAAC,cACb,GACF,GAEb"}
@@ -0,0 +1,240 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { clampOffset as t, computeCropRect as n, coverScale as r, outputSize as i } from "./crop-geometry.js";
3
+ import a from "./ImageCropper.module.js";
4
+ import { useCallback as o, useEffect as s, useId as c, useImperativeHandle as l, useMemo as u, useRef as d, useState as f } from "react";
5
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
6
+ //#region src/components/ImageCropper/ImageCropper.tsx
7
+ var h = 12, g = .15;
8
+ function _({ src: _, aspect: v = 1, maxZoom: y = 4, maxSize: b, outputType: x = "image/png", outputQuality: S = .92, shape: C = "rect", onCropChange: w, label: T = "Área de recorte", className: E, ref: D, ...O }) {
9
+ let k = `${c()}-hint`, A = d(null), j = d(null), M = d(null), [N, P] = f(null), [F, I] = f(null), [L, R] = f({
10
+ width: 0,
11
+ height: 0
12
+ }), [z, B] = f(1), [V, H] = f({
13
+ x: 0,
14
+ y: 0
15
+ });
16
+ s(() => {
17
+ if (typeof _ == "string") {
18
+ P(_);
19
+ return;
20
+ }
21
+ let e = URL.createObjectURL(_);
22
+ return P(e), () => URL.revokeObjectURL(e);
23
+ }, [_]), s(() => {
24
+ I(null), B(1), H({
25
+ x: 0,
26
+ y: 0
27
+ });
28
+ }, [N]), s(() => {
29
+ let e = A.current;
30
+ if (!e) return;
31
+ let t = () => R({
32
+ width: e.clientWidth,
33
+ height: e.clientHeight
34
+ });
35
+ if (t(), typeof ResizeObserver > "u") return;
36
+ let n = new ResizeObserver(t);
37
+ return n.observe(e), () => n.disconnect();
38
+ }, []);
39
+ let U = F ? r(F, L) * z : 0, W = u(() => F ? {
40
+ width: F.width * U,
41
+ height: F.height * U
42
+ } : {
43
+ width: 0,
44
+ height: 0
45
+ }, [F, U]), G = o((e) => {
46
+ H((n) => {
47
+ let r = t(e, W, L);
48
+ return r.x === n.x && r.y === n.y ? n : (w?.({
49
+ zoom: z,
50
+ offset: r
51
+ }), r);
52
+ });
53
+ }, [
54
+ W,
55
+ L,
56
+ w,
57
+ z
58
+ ]), K = o((e) => {
59
+ let n = Math.min(y, Math.max(1, e));
60
+ if (B(n), !F) return;
61
+ let i = r(F, L) * n, a = {
62
+ width: F.width * i,
63
+ height: F.height * i
64
+ };
65
+ H((e) => {
66
+ let r = t(e, a, L);
67
+ return w?.({
68
+ zoom: n,
69
+ offset: r
70
+ }), r;
71
+ });
72
+ }, [
73
+ L,
74
+ y,
75
+ F,
76
+ w
77
+ ]), q = o(() => {
78
+ B(1), H({
79
+ x: 0,
80
+ y: 0
81
+ }), w?.({
82
+ zoom: 1,
83
+ offset: {
84
+ x: 0,
85
+ y: 0
86
+ }
87
+ });
88
+ }, [w]), J = o(async () => {
89
+ let e = j.current;
90
+ if (!e || !F) return null;
91
+ let t = n({
92
+ image: F,
93
+ frame: L,
94
+ zoom: z,
95
+ offset: V
96
+ });
97
+ if (t.sWidth <= 0 || t.sHeight <= 0) return null;
98
+ let r = i(t, b), a = document.createElement("canvas");
99
+ a.width = r.width, a.height = r.height;
100
+ let o = a.getContext("2d");
101
+ return o ? (o.drawImage(e, t.sx, t.sy, t.sWidth, t.sHeight, 0, 0, r.width, r.height), new Promise((e) => {
102
+ a.toBlob((t) => e(t), x, S);
103
+ })) : null;
104
+ }, [
105
+ L,
106
+ b,
107
+ F,
108
+ V,
109
+ S,
110
+ x,
111
+ z
112
+ ]);
113
+ l(D, () => ({
114
+ crop: J,
115
+ reset: q
116
+ }), [J, q]);
117
+ let Y = (e) => {
118
+ F && (M.current = {
119
+ pointerId: e.pointerId,
120
+ startX: e.clientX,
121
+ startY: e.clientY,
122
+ from: V
123
+ }, e.currentTarget.setPointerCapture?.(e.pointerId));
124
+ }, X = (e) => {
125
+ let t = M.current;
126
+ !t || t.pointerId !== e.pointerId || G({
127
+ x: t.from.x + (e.clientX - t.startX),
128
+ y: t.from.y + (e.clientY - t.startY)
129
+ });
130
+ }, Z = (e) => {
131
+ M.current?.pointerId === e.pointerId && (M.current = null);
132
+ }, Q = (e) => {
133
+ let t = e.shiftKey ? h * 4 : h, n = {
134
+ ArrowLeft: {
135
+ x: -t,
136
+ y: 0
137
+ },
138
+ ArrowRight: {
139
+ x: t,
140
+ y: 0
141
+ },
142
+ ArrowUp: {
143
+ x: 0,
144
+ y: -t
145
+ },
146
+ ArrowDown: {
147
+ x: 0,
148
+ y: t
149
+ }
150
+ }[e.key];
151
+ if (n) {
152
+ e.preventDefault(), G({
153
+ x: V.x + n.x,
154
+ y: V.y + n.y
155
+ });
156
+ return;
157
+ }
158
+ e.key === "+" || e.key === "=" ? (e.preventDefault(), K(z + g)) : e.key === "-" || e.key === "_" ? (e.preventDefault(), K(z - g)) : e.key === "0" && (e.preventDefault(), q());
159
+ };
160
+ return /* @__PURE__ */ m("div", {
161
+ className: e(a.wrapper, E),
162
+ ...O,
163
+ children: [
164
+ /* @__PURE__ */ p("div", {
165
+ ref: A,
166
+ className: e(a.frame, C === "circle" && a.circle),
167
+ style: { aspectRatio: String(v) },
168
+ role: "group",
169
+ "aria-label": T,
170
+ "aria-describedby": k,
171
+ tabIndex: 0,
172
+ onPointerDown: Y,
173
+ onPointerMove: X,
174
+ onPointerUp: Z,
175
+ onPointerCancel: Z,
176
+ onKeyDown: Q,
177
+ onWheel: (e) => {
178
+ e.preventDefault(), K(z + (e.deltaY < 0 ? g : -.15));
179
+ },
180
+ children: N && /* @__PURE__ */ p("img", {
181
+ ref: j,
182
+ src: N,
183
+ alt: "",
184
+ draggable: !1,
185
+ className: a.image,
186
+ style: {
187
+ width: W.width || void 0,
188
+ height: W.height || void 0,
189
+ transform: `translate(${V.x}px, ${V.y}px)`
190
+ },
191
+ onLoad: (e) => {
192
+ let t = e.currentTarget;
193
+ t.naturalWidth > 0 && t.naturalHeight > 0 && I({
194
+ width: t.naturalWidth,
195
+ height: t.naturalHeight
196
+ });
197
+ }
198
+ })
199
+ }),
200
+ /* @__PURE__ */ m("div", {
201
+ className: a.controls,
202
+ children: [/* @__PURE__ */ p("input", {
203
+ type: "range",
204
+ className: a.zoom,
205
+ min: 1,
206
+ max: y,
207
+ step: .01,
208
+ value: z,
209
+ onChange: (e) => K(Number(e.target.value)),
210
+ "aria-label": "Zoom",
211
+ disabled: !F
212
+ }), /* @__PURE__ */ p("button", {
213
+ type: "button",
214
+ className: a.reset,
215
+ onClick: q,
216
+ disabled: !F,
217
+ children: "Centralizar"
218
+ })]
219
+ }),
220
+ /* @__PURE__ */ m("p", {
221
+ id: k,
222
+ className: a.hint,
223
+ children: [
224
+ "Arraste para reposicionar. Setas movem, ",
225
+ /* @__PURE__ */ p("kbd", { children: "+" }),
226
+ " e ",
227
+ /* @__PURE__ */ p("kbd", { children: "−" }),
228
+ " dão zoom,",
229
+ " ",
230
+ /* @__PURE__ */ p("kbd", { children: "0" }),
231
+ " centraliza."
232
+ ]
233
+ })
234
+ ]
235
+ });
236
+ }
237
+ //#endregion
238
+ export { _ as ImageCropper };
239
+
240
+ //# sourceMappingURL=ImageCropper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageCropper.js","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.tsx"],"sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport {\n clampOffset,\n computeCropRect,\n coverScale,\n type Offset,\n outputSize,\n type Size,\n} from \"./crop-geometry\";\nimport styles from \"./ImageCropper.module.css\";\n\n/** Imperative handle for exporting the current crop. */\nexport interface ImageCropperHandle {\n /**\n * Render the current crop and resolve with it.\n *\n * Resolves `null` when the image has not loaded yet or the browser refuses to\n * encode — never throws, so a submit handler does not need a try/catch.\n */\n crop: () => Promise<Blob | null>;\n /** Recentre at zoom 1. */\n reset: () => void;\n}\n\nexport interface ImageCropperProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The image to crop: a `File`/`Blob` from an input, or a URL. */\n src: File | Blob | string;\n /** Crop aspect ratio as `width / height`. Default `1` (square). */\n aspect?: number;\n /** Maximum zoom over the cover scale. Default `4`. */\n maxZoom?: number;\n /**\n * Cap on the longest edge of the exported image, in px.\n *\n * Without it the export keeps the source resolution, which is right for a\n * document scan and wasteful for an avatar.\n */\n maxSize?: number;\n /** Output MIME type. Default `\"image/png\"`. */\n outputType?: string;\n /** Output quality for lossy types, `0`–`1`. Default `0.92`. */\n outputQuality?: number;\n /** Overlay shape. `\"circle\"` for an avatar, `\"rect\"` for a document. Default `\"rect\"`. */\n shape?: \"rect\" | \"circle\";\n /** Called whenever the crop changes, e.g. to enable a submit button. */\n onCropChange?: (state: { zoom: number; offset: Offset }) => void;\n /** Accessible name for the crop area. */\n label?: string;\n ref?: React.Ref<ImageCropperHandle>;\n}\n\n/** Pixels moved per arrow-key press. */\nconst KEY_STEP = 12;\n\n/** Zoom added or removed per `+`/`-` press, and per wheel notch. */\nconst ZOOM_STEP = 0.15;\n\n/**\n * Crop an image to a fixed aspect ratio.\n *\n * The frame stays put and the image pans and zooms behind it — the model an avatar\n * or document-photo flow wants, where the output shape is decided by the app and\n * the user only chooses what lands inside it. (A free-form draggable rectangle is a\n * different component; this one cannot produce an off-ratio crop by construction.)\n *\n * The export reads the *natural* pixels through a canvas, so a 4000 px photo is not\n * silently downsampled to whatever the on-screen preview measured. The image is\n * also always clamped to cover the frame, so an export can never contain the empty\n * bands you get from panning past an edge.\n *\n * Works by pointer, wheel and keyboard: arrows pan, `+`/`-` zoom, `0` resets.\n *\n * @example\n * const cropper = useRef<ImageCropperHandle>(null);\n *\n * <ImageCropper ref={cropper} src={file} aspect={1} shape=\"circle\" maxSize={512} />\n * <Button onClick={async () => upload(await cropper.current?.crop())}>Salvar</Button>\n */\nexport function ImageCropper({\n src,\n aspect = 1,\n maxZoom = 4,\n maxSize,\n outputType = \"image/png\",\n outputQuality = 0.92,\n shape = \"rect\",\n onCropChange,\n label = \"Área de recorte\",\n className,\n ref,\n ...rest\n}: ImageCropperProps) {\n /**\n * Id for the shortcut hint.\n *\n * Generated rather than derived from `label`: a label with spaces would produce\n * an id with spaces, and `aria-describedby` splits on whitespace — so the\n * description would silently never associate with the frame.\n */\n const hintId = `${useId()}-hint`;\n\n const frameRef = useRef<HTMLDivElement>(null);\n const imageRef = useRef<HTMLImageElement | null>(null);\n const dragRef = useRef<{\n pointerId: number;\n startX: number;\n startY: number;\n from: Offset;\n } | null>(null);\n\n const [url, setUrl] = useState<string | null>(null);\n const [natural, setNatural] = useState<Size | null>(null);\n const [frame, setFrame] = useState<Size>({ width: 0, height: 0 });\n const [zoom, setZoom] = useState(1);\n const [offset, setOffset] = useState<Offset>({ x: 0, y: 0 });\n\n /**\n * Resolve `src` to a displayable URL.\n *\n * A `File`/`Blob` needs `createObjectURL`, and the URL must be revoked when the\n * source changes or the component unmounts — otherwise every re-pick of a photo\n * leaks the previous one for the lifetime of the document.\n */\n useEffect(() => {\n if (typeof src === \"string\") {\n setUrl(src);\n return;\n }\n const objectUrl = URL.createObjectURL(src);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [src]);\n\n // A new source invalidates the previous framing.\n useEffect(() => {\n setNatural(null);\n setZoom(1);\n setOffset({ x: 0, y: 0 });\n }, [url]);\n\n /** Track the frame's rendered size — the crop math is all relative to it. */\n useEffect(() => {\n const element = frameRef.current;\n if (!element) return;\n const measure = () =>\n setFrame({ width: element.clientWidth, height: element.clientHeight });\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n\n const scale = natural ? coverScale(natural, frame) * zoom : 0;\n\n /**\n * On-screen image size.\n *\n * Memoized because the pan callback depends on it: a fresh object every render\n * would rebuild that callback every render, and the pointer handlers close over\n * it during a drag.\n */\n const displayed = useMemo<Size>(\n () =>\n natural\n ? { width: natural.width * scale, height: natural.height * scale }\n : { width: 0, height: 0 },\n [natural, scale],\n );\n\n /** Apply a pan, clamped so the frame stays covered. */\n const pan = useCallback(\n (next: Offset) => {\n setOffset((current) => {\n const clamped = clampOffset(next, displayed, frame);\n if (clamped.x === current.x && clamped.y === current.y) return current;\n onCropChange?.({ zoom, offset: clamped });\n return clamped;\n });\n },\n [displayed, frame, onCropChange, zoom],\n );\n\n /**\n * Apply a zoom, then re-clamp the offset.\n *\n * Re-clamping is not optional: zooming *out* shrinks the image, so an offset\n * that was legal a moment ago can now expose background.\n */\n const applyZoom = useCallback(\n (next: number) => {\n const clampedZoom = Math.min(maxZoom, Math.max(1, next));\n setZoom(clampedZoom);\n if (!natural) return;\n const nextScale = coverScale(natural, frame) * clampedZoom;\n const nextDisplayed = {\n width: natural.width * nextScale,\n height: natural.height * nextScale,\n };\n setOffset((current) => {\n const clamped = clampOffset(current, nextDisplayed, frame);\n onCropChange?.({ zoom: clampedZoom, offset: clamped });\n return clamped;\n });\n },\n [frame, maxZoom, natural, onCropChange],\n );\n\n const reset = useCallback(() => {\n setZoom(1);\n setOffset({ x: 0, y: 0 });\n onCropChange?.({ zoom: 1, offset: { x: 0, y: 0 } });\n }, [onCropChange]);\n\n /**\n * Draw the current crop and encode it.\n *\n * Returns `null` rather than throwing on the paths a caller cannot do anything\n * about: no image yet, no 2D context, or an encoder that declined.\n */\n const crop = useCallback(async (): Promise<Blob | null> => {\n const image = imageRef.current;\n if (!image || !natural) return null;\n\n const rect = computeCropRect({ image: natural, frame, zoom, offset });\n if (rect.sWidth <= 0 || rect.sHeight <= 0) return null;\n\n const out = outputSize(rect, maxSize);\n const canvas = document.createElement(\"canvas\");\n canvas.width = out.width;\n canvas.height = out.height;\n const context = canvas.getContext(\"2d\");\n if (!context) return null;\n\n context.drawImage(\n image,\n rect.sx,\n rect.sy,\n rect.sWidth,\n rect.sHeight,\n 0,\n 0,\n out.width,\n out.height,\n );\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => resolve(blob), outputType, outputQuality);\n });\n }, [frame, maxSize, natural, offset, outputQuality, outputType, zoom]);\n\n useImperativeHandle(ref, () => ({ crop, reset }), [crop, reset]);\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (!natural) return;\n dragRef.current = {\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n from: offset,\n };\n event.currentTarget.setPointerCapture?.(event.pointerId);\n };\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>): void => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== event.pointerId) return;\n pan({\n x: drag.from.x + (event.clientX - drag.startX),\n y: drag.from.y + (event.clientY - drag.startY),\n });\n };\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (dragRef.current?.pointerId !== event.pointerId) return;\n dragRef.current = null;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n const step = event.shiftKey ? KEY_STEP * 4 : KEY_STEP;\n const moves: Record<string, Offset> = {\n ArrowLeft: { x: -step, y: 0 },\n ArrowRight: { x: step, y: 0 },\n ArrowUp: { x: 0, y: -step },\n ArrowDown: { x: 0, y: step },\n };\n const move = moves[event.key];\n if (move) {\n event.preventDefault();\n pan({ x: offset.x + move.x, y: offset.y + move.y });\n return;\n }\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n applyZoom(zoom + ZOOM_STEP);\n } else if (event.key === \"-\" || event.key === \"_\") {\n event.preventDefault();\n applyZoom(zoom - ZOOM_STEP);\n } else if (event.key === \"0\") {\n event.preventDefault();\n reset();\n }\n };\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n <div\n ref={frameRef}\n className={cn(styles.frame, shape === \"circle\" && styles.circle)}\n style={{ aspectRatio: String(aspect) }}\n role=\"group\"\n aria-label={label}\n aria-describedby={hintId}\n tabIndex={0}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n onWheel={(event) => {\n event.preventDefault();\n applyZoom(zoom + (event.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP));\n }}\n >\n {url && (\n <img\n ref={imageRef}\n src={url}\n alt=\"\"\n draggable={false}\n className={styles.image}\n style={{\n width: displayed.width || undefined,\n height: displayed.height || undefined,\n transform: `translate(${offset.x}px, ${offset.y}px)`,\n }}\n onLoad={(event) => {\n const element = event.currentTarget;\n // A decode can succeed and still report no intrinsic size —\n // an SVG without a viewBox is the common case. Accepting\n // that would enable the controls over an image the crop\n // maths can do nothing with.\n if (element.naturalWidth > 0 && element.naturalHeight > 0) {\n setNatural({\n width: element.naturalWidth,\n height: element.naturalHeight,\n });\n }\n }}\n />\n )}\n </div>\n\n <div className={styles.controls}>\n <input\n type=\"range\"\n className={styles.zoom}\n min={1}\n max={maxZoom}\n step={0.01}\n value={zoom}\n onChange={(event) => applyZoom(Number(event.target.value))}\n aria-label=\"Zoom\"\n disabled={!natural}\n />\n <button type=\"button\" className={styles.reset} onClick={reset} disabled={!natural}>\n Centralizar\n </button>\n </div>\n\n <p id={hintId} className={styles.hint}>\n Arraste para reposicionar. Setas movem, <kbd>+</kbd> e <kbd>−</kbd> dão zoom,{\" \"}\n <kbd>0</kbd> centraliza.\n </p>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_wrapper_l83On`,t=`tempest_frame_AFLKa`,n=`tempest_circle_qI5b8`,r=`tempest_image_8RAkb`,i=`tempest_controls_Tbg70`,a=`tempest_zoom_hMOSD`,o=`tempest_reset_TK3vP`,s=`tempest_hint_rIrZ8`,c={wrapper:e,frame:t,circle:n,image:r,controls:i,zoom:a,reset:o,hint:s};exports.circle=n,exports.controls=i,exports.default=c,exports.frame=t,exports.hint=s,exports.image=r,exports.reset=o,exports.wrapper=e,exports.zoom=a;
2
+ //# sourceMappingURL=ImageCropper.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageCropper.module.cjs","names":[],"sources":["../../../src/components/ImageCropper/ImageCropper.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-3);\n max-width: 100%;\n font-family: var(--tempest-font-sans);\n}\n\n.frame {\n position: relative;\n width: 100%;\n overflow: hidden;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-surface);\n /*\n * `grab` rather than `move`: nothing is being moved to a destination, the\n * image is being dragged under a fixed frame.\n */\n cursor: grab;\n /*\n * The frame owns dragging on touch, so the browser must not also scroll the\n * page from the same gesture.\n */\n touch-action: none;\n user-select: none;\n}\n\n.frame:active {\n cursor: grabbing;\n}\n\n.frame:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.circle {\n border-radius: var(--tempest-radius-full);\n}\n\n.image {\n position: absolute;\n top: 50%;\n left: 50%;\n max-width: none;\n pointer-events: none;\n /*\n * Centring lives in `translate` and the pan in the inline `transform`, which\n * are separate CSS properties applied in that order. Keeping them apart means\n * a pan is just `transform: translate(x, y)` — it never has to re-derive the\n * `-50% -50%` centring, and the two cannot overwrite each other.\n */\n translate: -50% -50%;\n}\n\n.controls {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n}\n\n.zoom {\n flex: 1;\n min-width: 0;\n accent-color: var(--tempest-primary);\n}\n\n.zoom:disabled {\n opacity: 0.5;\n}\n\n.reset {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font: inherit;\n font-size: var(--tempest-text-sm);\n cursor: pointer;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .reset:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.reset:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.reset:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.hint {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n}\n\n.hint kbd {\n padding: 0 var(--tempest-space-1);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-surface);\n font-family: var(--tempest-font-mono, monospace);\n font-size: inherit;\n}\n"],"mappings":""}
@@ -0,0 +1,15 @@
1
+ //#region src/components/ImageCropper/ImageCropper.module.css
2
+ var e = "tempest_wrapper_l83On", t = "tempest_frame_AFLKa", n = "tempest_circle_qI5b8", r = "tempest_image_8RAkb", i = "tempest_controls_Tbg70", a = "tempest_zoom_hMOSD", o = "tempest_reset_TK3vP", s = "tempest_hint_rIrZ8", c = {
3
+ wrapper: e,
4
+ frame: t,
5
+ circle: n,
6
+ image: r,
7
+ controls: i,
8
+ zoom: a,
9
+ reset: o,
10
+ hint: s
11
+ };
12
+ //#endregion
13
+ export { n as circle, i as controls, c as default, t as frame, s as hint, r as image, o as reset, e as wrapper, a as zoom };
14
+
15
+ //# sourceMappingURL=ImageCropper.module.js.map