@leviosa-ai/detail-page-editor 0.17.0 → 0.17.2

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.
@@ -39,7 +39,7 @@ import {
39
39
  detailPageEditorProfile,
40
40
  type DetailPageEditorFormat,
41
41
  } from "../../lib/detail-page/editor-profile";
42
- import { MAX_CANVAS_PIXELS } from "../../lib/detail-page/reference-image";
42
+ import { exceededCanvasLimit } from "../../lib/detail-page/canvas-limits";
43
43
 
44
44
  /**
45
45
  * hookable-style 다운로드 팝오버 — 다운로드 버튼 바로 아래에 떠서 **등록 플랫폼을
@@ -332,12 +332,13 @@ export const DetailPageDownloadDialog = observer(function DetailPageDownloadDial
332
332
  const maxBytes = chosen?.maxBytes ?? null;
333
333
  // 상세페이지는 세로로 길어서, 병합하면 아트보드 한계를 넘길 수 있다.
334
334
  const aiOverflow = isPdfFormat(format) && single && totalHeight > AI_MAX_ARTBOARD;
335
- // 병합 PNG/JPG 는 한 캔버스에 쌓는다. 넓이가 브라우저 한계를 넘으면 Safari 는 **빈 그림**을
336
- // 내놓으므로 병합을 끄고 페이지별 ZIP 으로 내린다.
337
- const mergeTooLarge =
338
- isRasterFormat(format) &&
339
- selectedPages.length > 1 &&
340
- outWidth * outHeight > MAX_CANVAS_PIXELS;
335
+ // 병합 PNG/JPG 는 한 캔버스에 쌓는다. 이 브라우저의 캔버스 한계를 넘으면 **빈 그림**이
336
+ // 나오므로 병합을 끄고 페이지별 ZIP 으로 내린다.
337
+ const mergeLimit =
338
+ isRasterFormat(format) && selectedPages.length > 1
339
+ ? exceededCanvasLimit(outWidth, outHeight)
340
+ : null;
341
+ const mergeTooLarge = mergeLimit !== null;
341
342
  const merge = single && !mergeTooLarge;
342
343
 
343
344
  // 내려받기가 끝난 뒤. 말할 것이 없으면 창을 닫고, 있으면 열어 둔 채 알린다 —
@@ -638,7 +639,7 @@ export const DetailPageDownloadDialog = observer(function DetailPageDownloadDial
638
639
  {mergeTooLarge ? (
639
640
  <p className="text-[10px] leading-relaxed text-le-warn-700">
640
641
  {t("editor.mergeTooLargeHint", {
641
- limit: MAX_CANVAS_PIXELS.toLocaleString(),
642
+ limit: mergeLimit?.toLocaleString(),
642
643
  })}
643
644
  </p>
644
645
  ) : null}
@@ -0,0 +1,43 @@
1
+ // Copyright © 2026 주식회사레비오사에이아이. All rights reserved. See LICENSE.
2
+ /**
3
+ * 지금 브라우저가 한 캔버스에 그릴 수 있는 크기. 넘기면 예외 없이 **빈 그림**이 나온다.
4
+ *
5
+ * 병합 PNG/JPG 는 모든 페이지를 한 캔버스에 쌓으므로 이 한계를 넘으면 페이지별로
6
+ * 내려야 한다. 한계는 브라우저마다 크게 다르다 — iOS 기준(1,670만 px)을 데스크톱에
7
+ * 걸면 860×19,000 남짓한 상세페이지부터 전부 쪼개진다.
8
+ *
9
+ * - iOS·iPadOS(모든 브라우저가 WebKit): 넓이 16,777,216
10
+ * - Firefox: 한 변 32,767, 넓이 268,435,456
11
+ * - 그 외(Chrome·Edge·데스크톱 Safari): 한 변 65,535, 넓이 268,435,456
12
+ *
13
+ * 수치는 jhildenbiddle/canvas-size 의 실측표를 따른다.
14
+ */
15
+ export interface CanvasLimits {
16
+ /** 한 변의 최대 픽셀. */
17
+ side: number;
18
+ /** 넓이(가로×세로)의 최대 픽셀. */
19
+ area: number;
20
+ }
21
+
22
+ export function canvasLimits(
23
+ userAgent = typeof navigator === "undefined" ? "" : navigator.userAgent,
24
+ maxTouchPoints = typeof navigator === "undefined" ? 0 : navigator.maxTouchPoints,
25
+ ): CanvasLimits {
26
+ // iPadOS 는 데스크톱 Safari 로 자신을 소개한다 — 터치 지점으로 가린다.
27
+ const ios =
28
+ /iPad|iPhone|iPod/.test(userAgent) || (/Macintosh/.test(userAgent) && maxTouchPoints > 1);
29
+ if (ios) return { side: 16_777_216, area: 16_777_216 };
30
+ if (/Firefox\//.test(userAgent)) return { side: 32_767, area: 268_435_456 };
31
+ return { side: 65_535, area: 268_435_456 };
32
+ }
33
+
34
+ /** 이 크기의 캔버스를 그릴 수 없으면 넘긴 한계(px)를, 그릴 수 있으면 null 을 준다. */
35
+ export function exceededCanvasLimit(
36
+ width: number,
37
+ height: number,
38
+ limits: CanvasLimits = canvasLimits(),
39
+ ): number | null {
40
+ if (Math.max(width, height) > limits.side) return limits.side;
41
+ if (width * height > limits.area) return limits.area;
42
+ return null;
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leviosa-ai/detail-page-editor",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface, and every URL it fetches comes from configureDetailPageEditor.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -68,7 +68,7 @@
68
68
  "woff2-encoder": "2.0.0"
69
69
  },
70
70
  "peerDependencies": {
71
- "@leviosa-ai/canvas": "0.9.0",
71
+ "@leviosa-ai/canvas": "0.9.1",
72
72
  "@tanstack/react-query": "^5.100.3",
73
73
  "konva": "^10.0.0",
74
74
  "lucide-react": "^0.563.0 || ^1.0.0",