@growgroup/visual-editor 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +52 -0
  3. package/README.md +143 -0
  4. package/dist/editor.css +2 -0
  5. package/package.json +94 -0
  6. package/src/components/DeckSlideRender.tsx +21 -0
  7. package/src/components/SaveNote.tsx +21 -0
  8. package/src/components/auth/AuthProvider.tsx +44 -0
  9. package/src/components/ui/accordion.tsx +57 -0
  10. package/src/components/ui/alert-dialog.tsx +141 -0
  11. package/src/components/ui/badge.tsx +36 -0
  12. package/src/components/ui/button.tsx +56 -0
  13. package/src/components/ui/card.tsx +76 -0
  14. package/src/components/ui/checkbox.tsx +30 -0
  15. package/src/components/ui/collapsible.tsx +11 -0
  16. package/src/components/ui/context-menu.tsx +200 -0
  17. package/src/components/ui/dialog.tsx +122 -0
  18. package/src/components/ui/dropdown-menu.tsx +201 -0
  19. package/src/components/ui/input.tsx +22 -0
  20. package/src/components/ui/label.tsx +26 -0
  21. package/src/components/ui/popover.tsx +33 -0
  22. package/src/components/ui/scroll-area.tsx +48 -0
  23. package/src/components/ui/scrubbable-label.tsx +95 -0
  24. package/src/components/ui/select.tsx +159 -0
  25. package/src/components/ui/separator.tsx +31 -0
  26. package/src/components/ui/slider.tsx +28 -0
  27. package/src/components/ui/switch.tsx +29 -0
  28. package/src/components/ui/tabs.tsx +55 -0
  29. package/src/components/ui/textarea.tsx +22 -0
  30. package/src/components/ui/tooltip.tsx +32 -0
  31. package/src/components/viewer/useDeck.ts +58 -0
  32. package/src/editor/EditorContext.tsx +632 -0
  33. package/src/editor/EditorToolbar.tsx +446 -0
  34. package/src/editor/FrontendVisualEditor.tsx +2595 -0
  35. package/src/editor/autosave.ts +34 -0
  36. package/src/editor/components/AiPromptPopover.tsx +378 -0
  37. package/src/editor/components/BreakpointGuides.tsx +323 -0
  38. package/src/editor/components/BreakpointSelector.tsx +180 -0
  39. package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
  40. package/src/editor/components/CssEditorDialog.tsx +168 -0
  41. package/src/editor/components/EditorCanvas.tsx +987 -0
  42. package/src/editor/components/EditorContextMenu.tsx +515 -0
  43. package/src/editor/components/EditorFooter.tsx +81 -0
  44. package/src/editor/components/EditorHeader.tsx +524 -0
  45. package/src/editor/components/EditorLayerPanel.tsx +1197 -0
  46. package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
  47. package/src/editor/components/EditorSidebar.tsx +226 -0
  48. package/src/editor/components/FigmaColorPicker.tsx +1577 -0
  49. package/src/editor/components/HtmlEditorDialog.tsx +71 -0
  50. package/src/editor/components/HtmlImportDialog.tsx +346 -0
  51. package/src/editor/components/JsEditorDialog.tsx +175 -0
  52. package/src/editor/components/LayerTreeItem.tsx +494 -0
  53. package/src/editor/components/MasterComponentEditor.tsx +846 -0
  54. package/src/editor/components/MediaLibraryDialog.tsx +447 -0
  55. package/src/editor/components/PageSettingsDialog.tsx +776 -0
  56. package/src/editor/components/VariablePicker.tsx +355 -0
  57. package/src/editor/components/VariablesDialog.tsx +564 -0
  58. package/src/editor/components/VariantEditor.tsx +687 -0
  59. package/src/editor/components/VariantMatrix.tsx +507 -0
  60. package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
  61. package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
  62. package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
  63. package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
  64. package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
  65. package/src/editor/components/component-panel/VariantList.tsx +260 -0
  66. package/src/editor/components/component-panel/index.ts +12 -0
  67. package/src/editor/components/index.ts +48 -0
  68. package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
  69. package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
  70. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
  71. package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
  72. package/src/editor/components/multi-page/PageLabel.tsx +34 -0
  73. package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
  74. package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
  75. package/src/editor/components/multi-page/index.ts +7 -0
  76. package/src/editor/components/ppt/PptChrome.tsx +2508 -0
  77. package/src/editor/components/ppt/PptComments.tsx +589 -0
  78. package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
  79. package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
  80. package/src/editor/components/ppt/PptNotes.tsx +246 -0
  81. package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
  82. package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
  83. package/src/editor/components/property-panel/BorderSection.tsx +104 -0
  84. package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
  85. package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
  86. package/src/editor/components/property-panel/EffectSection.tsx +157 -0
  87. package/src/editor/components/property-panel/FillSection.tsx +69 -0
  88. package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
  89. package/src/editor/components/property-panel/ImageSection.tsx +206 -0
  90. package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
  91. package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
  92. package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
  93. package/src/editor/components/property-panel/LinkSection.tsx +103 -0
  94. package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
  95. package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
  96. package/src/editor/components/property-panel/TypographySection.tsx +230 -0
  97. package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
  98. package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
  99. package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
  100. package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
  101. package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
  102. package/src/editor/components/property-panel/index.ts +24 -0
  103. package/src/editor/components/property-panel/unit-utils.ts +261 -0
  104. package/src/editor/components/shell/EditorTopBar.tsx +392 -0
  105. package/src/editor/components/shell/LeftPanel.tsx +47 -0
  106. package/src/editor/components/shell/PagesPanel.tsx +118 -0
  107. package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
  108. package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
  109. package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
  110. package/src/editor/components/variables-panel/index.ts +7 -0
  111. package/src/editor/constants.ts +468 -0
  112. package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
  113. package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
  114. package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
  115. package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
  116. package/src/editor/contexts/EditorRefsContext.tsx +204 -0
  117. package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
  118. package/src/editor/contexts/EditorToolContext.tsx +58 -0
  119. package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
  120. package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
  121. package/src/editor/contexts/EditorViewContext.tsx +101 -0
  122. package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
  123. package/src/editor/contexts/index.ts +17 -0
  124. package/src/editor/editor-skin.css +122 -0
  125. package/src/editor/hooks/index.ts +38 -0
  126. package/src/editor/hooks/useAiReplace.ts +397 -0
  127. package/src/editor/hooks/useAltMeasure.ts +67 -0
  128. package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
  129. package/src/editor/hooks/useCanvasControls.ts +1222 -0
  130. package/src/editor/hooks/useComponentEditMode.ts +658 -0
  131. package/src/editor/hooks/useComponentInstances.ts +515 -0
  132. package/src/editor/hooks/useContextMenuHandler.ts +157 -0
  133. package/src/editor/hooks/useCoordinateTransform.ts +207 -0
  134. package/src/editor/hooks/useDragResize.ts +1317 -0
  135. package/src/editor/hooks/useDrawingMode.ts +472 -0
  136. package/src/editor/hooks/useEditorColors.ts +144 -0
  137. package/src/editor/hooks/useEditorMessages.ts +108 -0
  138. package/src/editor/hooks/useElementActions.ts +1754 -0
  139. package/src/editor/hooks/useElementSelection.ts +1573 -0
  140. package/src/editor/hooks/useFocusManagement.ts +249 -0
  141. package/src/editor/hooks/useGoogleFonts.ts +305 -0
  142. package/src/editor/hooks/useIframeInitializer.ts +584 -0
  143. package/src/editor/hooks/useIframeSetup.ts +360 -0
  144. package/src/editor/hooks/useImageUpload.ts +270 -0
  145. package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
  146. package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
  147. package/src/editor/hooks/useMarqueeSelection.ts +388 -0
  148. package/src/editor/hooks/useMediaLibrary.ts +129 -0
  149. package/src/editor/hooks/usePageSettingsManager.ts +739 -0
  150. package/src/editor/hooks/useResizablePanel.ts +163 -0
  151. package/src/editor/hooks/useRichPaste.ts +255 -0
  152. package/src/editor/hooks/useTouchGestures.ts +197 -0
  153. package/src/editor/index.ts +27 -0
  154. package/src/editor/types/page-settings.ts +513 -0
  155. package/src/editor/types.ts +382 -0
  156. package/src/editor/utils/align-elements.ts +169 -0
  157. package/src/editor/utils/api-json.ts +21 -0
  158. package/src/editor/utils/aspect-lock.ts +50 -0
  159. package/src/editor/utils/component-renderer.ts +702 -0
  160. package/src/editor/utils/component-sync.ts +845 -0
  161. package/src/editor/utils/crop-mode.ts +265 -0
  162. package/src/editor/utils/dom-utils.ts +2238 -0
  163. package/src/editor/utils/element-effects.ts +218 -0
  164. package/src/editor/utils/eyedropper.ts +35 -0
  165. package/src/editor/utils/figma-export.ts +560 -0
  166. package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
  167. package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
  168. package/src/editor/utils/figma-paste.ts +2262 -0
  169. package/src/editor/utils/flex-reorder.ts +231 -0
  170. package/src/editor/utils/flex-utils.ts +1230 -0
  171. package/src/editor/utils/geometry.ts +232 -0
  172. package/src/editor/utils/grid-layout.ts +210 -0
  173. package/src/editor/utils/html-utils.ts +470 -0
  174. package/src/editor/utils/index.ts +11 -0
  175. package/src/editor/utils/ink-style.ts +57 -0
  176. package/src/editor/utils/inline-format.ts +236 -0
  177. package/src/editor/utils/measure-distance.ts +120 -0
  178. package/src/editor/utils/override-apply.ts +526 -0
  179. package/src/editor/utils/override-detection.ts +613 -0
  180. package/src/editor/utils/paste-processors.ts +609 -0
  181. package/src/editor/utils/paste-sanitizer.ts +244 -0
  182. package/src/editor/utils/restore-flow.ts +188 -0
  183. package/src/editor/utils/shape-library.ts +171 -0
  184. package/src/editor/utils/slide-root.ts +29 -0
  185. package/src/editor/utils/smart-guides.ts +218 -0
  186. package/src/editor/utils/style-utils.ts +685 -0
  187. package/src/editor/utils/table-edit.ts +151 -0
  188. package/src/editor/utils/tailwind-mappings.ts +778 -0
  189. package/src/editor/utils/tailwind-utils.ts +804 -0
  190. package/src/editor/utils/text-highlight.ts +216 -0
  191. package/src/editor/utils/theme-to-variables.ts +341 -0
  192. package/src/editor/utils/variant-resolver.ts +456 -0
  193. package/src/editor/utils/viewport-utils.ts +295 -0
  194. package/src/hooks/useEditorHistory.ts +195 -0
  195. package/src/hooks/useEditorShortcuts.ts +260 -0
  196. package/src/index.ts +40 -0
  197. package/src/io.ts +134 -0
  198. package/src/lib/agent/slide-agent/types.ts +547 -0
  199. package/src/lib/agent/website-agent/types.ts +805 -0
  200. package/src/lib/api/auth-fetch.ts +58 -0
  201. package/src/lib/deck.ts +69 -0
  202. package/src/lib/export.ts +47 -0
  203. package/src/lib/firebase/config.ts +29 -0
  204. package/src/lib/firebase/css-variables.ts +378 -0
  205. package/src/lib/firebase/editor-components.ts +677 -0
  206. package/src/lib/firebase/storage.ts +469 -0
  207. package/src/lib/utils.ts +6 -0
  208. package/src/styles/editor.css +52 -0
  209. package/src/styles/skin.css +122 -0
  210. package/src/types/css-variables.ts +645 -0
  211. package/src/types/editor-components.ts +944 -0
  212. package/src/types/editor.ts +420 -0
  213. package/src/types/page-master.ts +197 -0
  214. package/src/types/page.ts +419 -0
  215. package/src/types/slide.ts +402 -0
  216. package/src/types/website-theme.ts +788 -0
  217. package/src/types/website.ts +374 -0
  218. package/src/vendor/firebase-firestore.ts +13 -0
  219. package/src/vendor/firebase-functions.ts +17 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * エディタの幾何計算ユーティリティ
3
+ *
4
+ * [なぜ独立させたか]
5
+ * ドラッグ移動・リサイズ・キーボード移動は「同じ座標をどう丸めるか」「最小サイズをいくつにするか」
6
+ * という規則を共有していないと、操作のたびに 0.3px 単位の誤差が積み上がって
7
+ * 「群で動かすと要素ごとに位置がズレる」「同じ操作なのに結果が違う」が起きる。
8
+ * 丸め規則・最小サイズ・軸拘束・リサイズのアンカー計算をここに一本化し、
9
+ * 各フックは import して使うだけにする。
10
+ */
11
+
12
+ /**
13
+ * 要素に書き込む最小サイズ(px)
14
+ *
15
+ * 従来は 10px でクランプしていたため、罫線(h-px)や 1〜9px の細い装飾要素を
16
+ * リサイズすると勝手に 10px まで太らされて元に戻せなかった。
17
+ * CSS 上 1px 未満の指定は実質意味を持たないので下限は 1px とする。
18
+ */
19
+ export const MIN_ELEMENT_SIZE = 1;
20
+
21
+ /**
22
+ * クリックとドラッグを区別する移動量(px, アートボード座標)
23
+ * これを超えて初めて「ドラッグが始まった」とみなす。
24
+ */
25
+ export const DRAG_START_THRESHOLD = 2;
26
+
27
+ /** px値の丸め規則(座標・サイズを書き込む直前に必ず通す) */
28
+ export function roundPx(value: number): number {
29
+ return Math.round(value);
30
+ }
31
+
32
+ /** 丸めた px 文字列を返す */
33
+ export function pxValue(value: number): string {
34
+ return `${roundPx(value)}px`;
35
+ }
36
+
37
+ /**
38
+ * computed position が「フローから外れている」か
39
+ * left/top を書いて自由に動かせるのは absolute / fixed だけ。
40
+ * static / relative / sticky は書いても意味が違う(relative は流し込み位置からの
41
+ * オフセットなので offsetLeft を書き戻すと飛ぶ)ため、自由移動の対象にしない。
42
+ */
43
+ export function isOutOfFlowPosition(position: string | null | undefined): boolean {
44
+ return position === 'absolute' || position === 'fixed';
45
+ }
46
+
47
+ /** 要素がフローから外れているか(computed style を見る) */
48
+ export function isElementOutOfFlow(element: HTMLElement, doc: Document): boolean {
49
+ const cs = doc.defaultView?.getComputedStyle(element);
50
+ return isOutOfFlowPosition(cs?.position);
51
+ }
52
+
53
+ /**
54
+ * Shift による軸拘束
55
+ *
56
+ * 「Shiftを押した瞬間の軸に固定」ではなく、毎フレーム
57
+ * ドラッグ開始点からの |dx| と |dy| を比べて支配軸を決める。
58
+ * こうすると Shift を押しっぱなしのまま方向を変えても素直に追従する(Figma と同じ)。
59
+ */
60
+ export function constrainAxis(
61
+ dx: number,
62
+ dy: number,
63
+ enabled: boolean | undefined
64
+ ): { dx: number; dy: number } {
65
+ if (!enabled) return { dx, dy };
66
+ return Math.abs(dx) >= Math.abs(dy) ? { dx, dy: 0 } : { dx: 0, dy };
67
+ }
68
+
69
+ /** リサイズハンドルの向き(-1: 左/上, 0: 動かさない, 1: 右/下) */
70
+ export function handleDirection(handle: string): { x: -1 | 0 | 1; y: -1 | 0 | 1 } {
71
+ const x: -1 | 0 | 1 = handle.includes('e') ? 1 : handle.includes('w') ? -1 : 0;
72
+ const y: -1 | 0 | 1 = handle.includes('s') ? 1 : handle.includes('n') ? -1 : 0;
73
+ return { x, y };
74
+ }
75
+
76
+ /** リサイズの基準値(掴んだ時点の値。すべてスケール前の CSS px) */
77
+ export interface ResizeOrigin {
78
+ left: number;
79
+ top: number;
80
+ width: number;
81
+ height: number;
82
+ }
83
+
84
+ /** リサイズの修飾キー */
85
+ export interface ResizeModifiers {
86
+ /** Shift: 縦横比維持 */
87
+ shiftKey?: boolean;
88
+ /** Alt: 中心固定(両側に伸びる) */
89
+ altKey?: boolean;
90
+ /** 最小サイズ(既定 MIN_ELEMENT_SIZE) */
91
+ minSize?: number;
92
+ }
93
+
94
+ /**
95
+ * リサイズ後の矩形を計算する
96
+ *
97
+ * [計算順序]
98
+ * 1. 掴んだハンドルの向きから「幅・高さの増分」に読み替える
99
+ * 2. Alt(中心固定)なら増分を2倍にする(掴んだ辺と対辺の両方が動くため)
100
+ * 3. Shift(縦横比維持)なら支配辺から従属辺を決める
101
+ * 4. 最小サイズでクランプする
102
+ * 5. **クランプ後の実増分**からアンカー(対辺)を決める
103
+ * → 先に left/top を生の delta から決めると、クランプや比率維持が効いたときに
104
+ * 「対辺が固定される」という不変条件が静かに壊れる
105
+ *
106
+ * [丸め] 端(left/right, top/bottom)をそれぞれ丸めてから幅を引き算で出す。
107
+ * 幅と位置を独立に丸めると、対辺が 1px ずれることがあるため。
108
+ */
109
+ export function computeResizeGeometry(
110
+ orig: ResizeOrigin,
111
+ handle: string,
112
+ deltaX: number,
113
+ deltaY: number,
114
+ mods: ResizeModifiers = {}
115
+ ): ResizeOrigin {
116
+ const minSize = mods.minSize ?? MIN_ELEMENT_SIZE;
117
+ const dir = handleDirection(handle);
118
+
119
+ // 1. ハンドルの向き → 幅・高さの増分
120
+ let dw = dir.x * deltaX;
121
+ let dh = dir.y * deltaY;
122
+
123
+ // 2. Alt: 中心固定(掴んだ辺と対辺が同時に動くので、増分は2倍)
124
+ if (mods.altKey) {
125
+ dw *= 2;
126
+ dh *= 2;
127
+ }
128
+
129
+ // 3. Shift: 縦横比維持
130
+ if (mods.shiftKey && orig.width > 0 && orig.height > 0) {
131
+ const aspect = orig.width / orig.height;
132
+ if (dir.x !== 0 && dir.y !== 0) {
133
+ // 角ハンドル: 相対変化の大きいほうを支配辺にする
134
+ if (Math.abs(dw) * orig.height >= Math.abs(dh) * orig.width) {
135
+ dh = dw / aspect;
136
+ } else {
137
+ dw = dh * aspect;
138
+ }
139
+ } else if (dir.x !== 0) {
140
+ dh = dw / aspect;
141
+ } else if (dir.y !== 0) {
142
+ dw = dh * aspect;
143
+ }
144
+ }
145
+
146
+ // 4. クランプ
147
+ const rawWidth = Math.max(minSize, orig.width + dw);
148
+ const rawHeight = Math.max(minSize, orig.height + dh);
149
+
150
+ // 5. クランプ後の実増分でアンカーを決める
151
+ const appliedDw = rawWidth - orig.width;
152
+ const appliedDh = rawHeight - orig.height;
153
+
154
+ let rawLeft = orig.left;
155
+ let rawTop = orig.top;
156
+ if (mods.altKey) {
157
+ // 中心固定: 両側に半分ずつ広がる
158
+ rawLeft = orig.left - appliedDw / 2;
159
+ rawTop = orig.top - appliedDh / 2;
160
+ } else {
161
+ // 対辺固定: 左/上を掴んだときだけ位置が動く
162
+ if (dir.x < 0) rawLeft = orig.left - appliedDw;
163
+ if (dir.y < 0) rawTop = orig.top - appliedDh;
164
+ }
165
+
166
+ // 丸め: 端を丸めてから幅・高さを引き算で出す(対辺の位置を1px単位で保つ)
167
+ const left = roundPx(rawLeft);
168
+ const top = roundPx(rawTop);
169
+ const right = roundPx(rawLeft + rawWidth);
170
+ const bottom = roundPx(rawTop + rawHeight);
171
+
172
+ return {
173
+ left,
174
+ top,
175
+ width: Math.max(minSize, right - left),
176
+ height: Math.max(minSize, bottom - top),
177
+ };
178
+ }
179
+
180
+ /**
181
+ * style.width / style.height に書くべき現在サイズを実測する
182
+ *
183
+ * offsetWidth/offsetHeight はスケール前の CSS px(border-box)を返す。
184
+ * getBoundingClientRect().width / zoom と違い、artboard のスケールや
185
+ * 要素自身の transform の影響を受けないので基準として素直。
186
+ * box-sizing: content-box の要素だけ padding/border を差し引いて
187
+ * 「style.width に書いた値 = 実際に効く値」を一致させる。
188
+ */
189
+ export function measureStyleSize(
190
+ element: HTMLElement,
191
+ doc: Document
192
+ ): { width: number; height: number } {
193
+ const cs = doc.defaultView?.getComputedStyle(element);
194
+ let width = element.offsetWidth;
195
+ let height = element.offsetHeight;
196
+
197
+ // インライン要素などで offsetWidth が 0 のときは矩形から拾う(保険)
198
+ if (width === 0 && height === 0) {
199
+ const rect = element.getBoundingClientRect();
200
+ const scale = getArtboardRenderScale(doc);
201
+ width = rect.width / scale;
202
+ height = rect.height / scale;
203
+ return { width, height };
204
+ }
205
+
206
+ if (cs && cs.boxSizing === 'content-box') {
207
+ const num = (v: string) => parseFloat(v) || 0;
208
+ width -=
209
+ num(cs.paddingLeft) +
210
+ num(cs.paddingRight) +
211
+ num(cs.borderLeftWidth) +
212
+ num(cs.borderRightWidth);
213
+ height -=
214
+ num(cs.paddingTop) +
215
+ num(cs.paddingBottom) +
216
+ num(cs.borderTopWidth) +
217
+ num(cs.borderBottomWidth);
218
+ }
219
+
220
+ return { width: Math.max(0, width), height: Math.max(0, height) };
221
+ }
222
+
223
+ /** アートボードが実際に描画されている倍率(保険用。取れなければ 1) */
224
+ function getArtboardRenderScale(doc: Document): number {
225
+ const wrapper = doc.getElementById('artboard-wrapper');
226
+ if (!wrapper || !doc.defaultView) return 1;
227
+ const transform = doc.defaultView.getComputedStyle(wrapper).transform;
228
+ if (!transform || transform === 'none') return 1;
229
+ const match = transform.match(/matrix\(([^,]+),/);
230
+ const scale = match ? parseFloat(match[1]) : 1;
231
+ return scale > 0 ? scale : 1;
232
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * マルチアートボードのグリッドレイアウト計算ユーティリティ
3
+ */
4
+
5
+ import { SLIDE_WIDTH, SLIDE_HEIGHT } from '../constants';
6
+
7
+ export interface ArtboardPosition {
8
+ id: string;
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ row: number;
14
+ col: number;
15
+ }
16
+
17
+ export interface GridLayoutConfig {
18
+ /** アートボードの幅 */
19
+ artboardWidth: number;
20
+ /** アートボードの高さ */
21
+ artboardHeight: number;
22
+ /** アートボード間の水平方向の間隔 */
23
+ horizontalGap: number;
24
+ /** アートボード間の垂直方向の間隔 */
25
+ verticalGap: number;
26
+ /** 1行あたりの最大アートボード数 */
27
+ columnsPerRow: number;
28
+ /** キャンバスの左パディング */
29
+ paddingLeft: number;
30
+ /** キャンバスの上パディング */
31
+ paddingTop: number;
32
+ }
33
+
34
+ const DEFAULT_GRID_CONFIG: GridLayoutConfig = {
35
+ artboardWidth: SLIDE_WIDTH,
36
+ artboardHeight: SLIDE_HEIGHT,
37
+ horizontalGap: 100,
38
+ verticalGap: 100,
39
+ columnsPerRow: 3,
40
+ paddingLeft: 200,
41
+ paddingTop: 200,
42
+ };
43
+
44
+ /**
45
+ * アートボードIDの配列からグリッド位置を計算
46
+ */
47
+ export function calculateGridPositions(
48
+ artboardIds: string[],
49
+ config: Partial<GridLayoutConfig> = {}
50
+ ): ArtboardPosition[] {
51
+ const cfg = { ...DEFAULT_GRID_CONFIG, ...config };
52
+
53
+ return artboardIds.map((id, index) => {
54
+ const row = Math.floor(index / cfg.columnsPerRow);
55
+ const col = index % cfg.columnsPerRow;
56
+
57
+ const x = cfg.paddingLeft + col * (cfg.artboardWidth + cfg.horizontalGap);
58
+ const y = cfg.paddingTop + row * (cfg.artboardHeight + cfg.verticalGap);
59
+
60
+ return {
61
+ id,
62
+ x,
63
+ y,
64
+ width: cfg.artboardWidth,
65
+ height: cfg.artboardHeight,
66
+ row,
67
+ col,
68
+ };
69
+ });
70
+ }
71
+
72
+ /**
73
+ * すべてのアートボードを含む境界ボックスを計算
74
+ */
75
+ export function getContentBounds(
76
+ positions: ArtboardPosition[],
77
+ padding: number = 200
78
+ ): { width: number; height: number; minX: number; minY: number; maxX: number; maxY: number } {
79
+ if (positions.length === 0) {
80
+ return { width: 0, height: 0, minX: 0, minY: 0, maxX: 0, maxY: 0 };
81
+ }
82
+
83
+ let minX = Infinity;
84
+ let minY = Infinity;
85
+ let maxX = -Infinity;
86
+ let maxY = -Infinity;
87
+
88
+ positions.forEach(pos => {
89
+ minX = Math.min(minX, pos.x);
90
+ minY = Math.min(minY, pos.y);
91
+ maxX = Math.max(maxX, pos.x + pos.width);
92
+ maxY = Math.max(maxY, pos.y + pos.height);
93
+ });
94
+
95
+ return {
96
+ minX: minX - padding,
97
+ minY: minY - padding,
98
+ maxX: maxX + padding,
99
+ maxY: maxY + padding,
100
+ width: maxX - minX + padding * 2,
101
+ height: maxY - minY + padding * 2,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * 指定した座標がどのアートボード上にあるかを判定
107
+ */
108
+ export function getArtboardAtPosition(
109
+ positions: ArtboardPosition[],
110
+ x: number,
111
+ y: number
112
+ ): ArtboardPosition | null {
113
+ for (const pos of positions) {
114
+ if (
115
+ x >= pos.x &&
116
+ x <= pos.x + pos.width &&
117
+ y >= pos.y &&
118
+ y <= pos.y + pos.height
119
+ ) {
120
+ return pos;
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+
126
+ /**
127
+ * ズームレベルに基づいてスクロール可能な領域サイズを計算
128
+ */
129
+ export function calculateScrollableArea(
130
+ bounds: ReturnType<typeof getContentBounds>,
131
+ zoom: number,
132
+ viewportWidth: number,
133
+ viewportHeight: number
134
+ ): { width: number; height: number } {
135
+ const scale = zoom / 100;
136
+ const scaledWidth = bounds.width * scale;
137
+ const scaledHeight = bounds.height * scale;
138
+
139
+ return {
140
+ width: Math.max(scaledWidth, viewportWidth),
141
+ height: Math.max(scaledHeight, viewportHeight),
142
+ };
143
+ }
144
+
145
+ /**
146
+ * 特定のアートボードを中央に表示するためのスクロール位置を計算
147
+ */
148
+ export function calculateScrollToArtboard(
149
+ position: ArtboardPosition,
150
+ zoom: number,
151
+ viewportWidth: number,
152
+ viewportHeight: number
153
+ ): { scrollX: number; scrollY: number } {
154
+ const scale = zoom / 100;
155
+
156
+ // アートボードの中心座標
157
+ const centerX = (position.x + position.width / 2) * scale;
158
+ const centerY = (position.y + position.height / 2) * scale;
159
+
160
+ // ビューポートの中央にアートボードの中心が来るようにスクロール
161
+ return {
162
+ scrollX: centerX - viewportWidth / 2,
163
+ scrollY: centerY - viewportHeight / 2,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * フィットズームを計算(全アートボードがビューポート内に収まるように)
169
+ */
170
+ export function calculateFitAllZoom(
171
+ bounds: ReturnType<typeof getContentBounds>,
172
+ viewportWidth: number,
173
+ viewportHeight: number,
174
+ minZoom: number = 10,
175
+ maxZoom: number = 200
176
+ ): number {
177
+ if (bounds.width === 0 || bounds.height === 0) {
178
+ return 100;
179
+ }
180
+
181
+ const scaleX = viewportWidth / bounds.width;
182
+ const scaleY = viewportHeight / bounds.height;
183
+ const scale = Math.min(scaleX, scaleY);
184
+
185
+ const zoom = Math.floor(scale * 100);
186
+ return Math.max(minZoom, Math.min(maxZoom, zoom));
187
+ }
188
+
189
+ /**
190
+ * 単一アートボードにフィットするズームを計算
191
+ */
192
+ export function calculateFitSingleZoom(
193
+ artboardWidth: number,
194
+ artboardHeight: number,
195
+ viewportWidth: number,
196
+ viewportHeight: number,
197
+ padding: number = 64,
198
+ minZoom: number = 10,
199
+ maxZoom: number = 200
200
+ ): number {
201
+ const availableWidth = viewportWidth - padding;
202
+ const availableHeight = viewportHeight - padding;
203
+
204
+ const scaleX = availableWidth / artboardWidth;
205
+ const scaleY = availableHeight / artboardHeight;
206
+ const scale = Math.min(scaleX, scaleY);
207
+
208
+ const zoom = Math.floor(scale * 100);
209
+ return Math.max(minZoom, Math.min(maxZoom, zoom));
210
+ }