@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,231 @@
1
+ /**
2
+ * フレックス内ドラッグ = 同一親の中での並べ替え。
3
+ *
4
+ * オートレイアウト(display:flex)の子を掴んだとき、従来は問答無用で絶対配置へ
5
+ * 倒していたため、フレックスの設定が触った瞬間に無意味になっていた。
6
+ * ここでは「親の中での順番を変える」だけを行い、**要素を親の外へ出さない**。
7
+ * 以前あった「ゴースト並べ替え」は任意の親へ再挿入できたため、離した場所に
8
+ * よっては要素が画面の外へ消えた。同一親に限ることでその事故を構造的に防ぐ。
9
+ *
10
+ * 座標はクライアント座標(getBoundingClientRect)のまま扱う。ここで必要なのは
11
+ * 「カーソルが兄弟の前半か後半か」だけで、アートボード座標へ割り戻す必要がない。
12
+ */
13
+
14
+ import { isAutoLayoutContainer, restoreChildrenToFlow } from './restore-flow';
15
+
16
+ const INDICATOR_ID = 'gg-reorder-indicator';
17
+ const BLUE = '#0d99ff';
18
+
19
+ /** 並べ替えの対象にしない、エディタが描いている飾り */
20
+ const OVERLAY_SELECTOR =
21
+ '.selection-box,.marquee-selection-box,.resize-handle,.rotation-handle,' +
22
+ '.size-label,.element-breadcrumb,.gg-comment-layer,.gg-crop-ui,' +
23
+ '.flex-drop-indicator,#gg-measure-layer,#gg-smart-guides,#' + INDICATOR_ID;
24
+
25
+ /** 並べ替え中の状態。ドラッグ開始時に1回だけ作る */
26
+ export interface ReorderSession {
27
+ element: HTMLElement;
28
+ parent: HTMLElement;
29
+ /** 動かしている要素を除いた兄弟(DOM順) */
30
+ siblings: HTMLElement[];
31
+ /** 主軸が縦か */
32
+ isColumn: boolean;
33
+ /** 開始時の位置(取り消し・変化判定用) */
34
+ originalIndex: number;
35
+ /** 直前に描いた挿入位置。同じなら描き直さない */
36
+ lastIndex: number;
37
+ }
38
+
39
+ /** 並べ替えの相手になる子だけを取り出す */
40
+ function realChildren(parent: HTMLElement): HTMLElement[] {
41
+ const out: HTMLElement[] = [];
42
+ for (const node of Array.from(parent.children)) {
43
+ // [注意] `node instanceof HTMLElement` は使えない。この関数は親ウィンドウで
44
+ // 動くが要素は iframe の realm に属するため、常に false になる
45
+ const el = node as HTMLElement;
46
+ if (typeof el.matches !== 'function') return out.length ? out : [];
47
+ if (el.matches(OVERLAY_SELECTOR)) continue;
48
+ if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE') continue;
49
+ out.push(el);
50
+ }
51
+ return out;
52
+ }
53
+
54
+ /**
55
+ * この要素を「同一親の並べ替え」で動かすべきか判定する。
56
+ *
57
+ * 【既定は現状維持】computed が flex というだけでは並べ替えにしない。
58
+ * テンプレート由来の flex はスライド中に山ほどあり、それを全部器として扱うと
59
+ * 既定のドラッグ(絶対配置化して自由移動)が勝手に変わってしまう。
60
+ *
61
+ * 条件はすべて満たす必要がある:
62
+ * - 単一選択(群ドラッグは従来どおり座標移動。順番の概念が無い)
63
+ * - 親が **明示的にオートレイアウトON** (gg-autolayout クラスを持つ)
64
+ * - 親が display:flex / inline-flex(gridは対象外。2次元の並びは順番だけでは決まらない)
65
+ * - 自身がフローの中にいる(ONにした時点で子はフロー化済み。
66
+ * その後ユーザーが自分で絶対配置にした子は座標移動のまま)
67
+ * - 並べ替える相手が2つ以上いる
68
+ */
69
+ export function shouldReorder(
70
+ element: HTMLElement,
71
+ doc: Document,
72
+ selectedCount: number,
73
+ ): boolean {
74
+ if (selectedCount > 1) return false;
75
+ const win = doc.defaultView;
76
+ const parent = element.parentElement;
77
+ if (!win || !parent) return false;
78
+
79
+ // 明示的にONにした器の中だけが並べ替えの対象
80
+ if (!isAutoLayoutContainer(parent)) return false;
81
+
82
+ const display = win.getComputedStyle(parent).display;
83
+ if (display !== 'flex' && display !== 'inline-flex') return false;
84
+
85
+ const own = win.getComputedStyle(element).position;
86
+ if (own === 'absolute' || own === 'fixed') return false;
87
+
88
+ return realChildren(parent).length >= 2;
89
+ }
90
+
91
+ /** 並べ替えの下ごしらえ(ドラッグが動き出した最初の1回だけ呼ぶ) */
92
+ export function startReorder(element: HTMLElement, doc: Document): ReorderSession | null {
93
+ const win = doc.defaultView;
94
+ const parent = element.parentElement;
95
+ if (!win || !parent) return null;
96
+
97
+ // 子はONにした時点でフロー化済み。ただし後から追加された子や、
98
+ // 別経路で絶対配置が焼き込まれた子が混じると並びが崩れるので保険で通す
99
+ restoreChildrenToFlow(parent);
100
+
101
+ const children = realChildren(parent);
102
+ const originalIndex = children.indexOf(element);
103
+ if (originalIndex < 0) return null;
104
+
105
+ const dir = win.getComputedStyle(parent).flexDirection || 'row';
106
+
107
+ // 掴んでいる要素は「持ち上がっている」ことを見せる。
108
+ // 既存の .dragging(opacity .7 + grabbing)をそのまま使う。インラインstyleで
109
+ // 当てると保存HTMLに漏れうるが、このクラスは getCleanHtml が必ず剥がす
110
+
111
+ element.classList.add('dragging');
112
+
113
+ return {
114
+ element,
115
+ parent,
116
+ siblings: children.filter((c) => c !== element),
117
+ isColumn: dir.startsWith('column'),
118
+ originalIndex,
119
+ lastIndex: -1,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * カーソル位置から「兄弟の何番目の前に入るか」を決める
125
+ *
126
+ * 返すのは siblings(動かしている要素を除いた配列)に対する挿入位置。
127
+ * 各兄弟の中点と比べるだけなので、要素が可変幅でも破綻しない。
128
+ * flex-direction:*-reverse は画面上の並びとDOM順が逆なので、最後に反転する。
129
+ */
130
+ export function computeInsertIndex(
131
+ session: ReorderSession,
132
+ clientX: number,
133
+ clientY: number,
134
+ doc: Document,
135
+ ): number {
136
+ const { siblings, isColumn } = session;
137
+ const dir = doc.defaultView?.getComputedStyle(session.parent).flexDirection || 'row';
138
+ const isReverse = dir.endsWith('-reverse');
139
+
140
+ let index = siblings.length;
141
+ for (let i = 0; i < siblings.length; i++) {
142
+ const r = siblings[i].getBoundingClientRect();
143
+ const mid = isColumn ? r.top + r.height / 2 : r.left + r.width / 2;
144
+ const cursor = isColumn ? clientY : clientX;
145
+ const before = isReverse ? cursor > mid : cursor < mid;
146
+ if (before) {
147
+ index = i;
148
+ break;
149
+ }
150
+ }
151
+ return index;
152
+ }
153
+
154
+ /**
155
+ * 挿入位置を示す青い線を描く
156
+ *
157
+ * 線は #artboard 直下ではなく **親のすぐ内側** に置きたいが、親に
158
+ * position が無いと座標の基準にできない。親の style を書き換えると版面が動くので、
159
+ * body直下に fixed で置く(画面座標そのまま)。ドラッグ中しか出ないので
160
+ * スクロール追従は不要。
161
+ */
162
+ export function drawInsertIndicator(
163
+ session: ReorderSession,
164
+ index: number,
165
+ doc: Document,
166
+ ): void {
167
+ clearInsertIndicator(doc);
168
+
169
+ const { siblings, isColumn, parent } = session;
170
+ const parentRect = parent.getBoundingClientRect();
171
+ const dir = doc.defaultView?.getComputedStyle(parent).flexDirection || 'row';
172
+ const isReverse = dir.endsWith('-reverse');
173
+
174
+ // 挿入位置の座標。末尾なら最後の兄弟の後ろ、それ以外は index 番目の手前
175
+ let pos: number;
176
+ if (siblings.length === 0) {
177
+ pos = isColumn ? parentRect.top : parentRect.left;
178
+ } else if (index >= siblings.length) {
179
+ const r = siblings[siblings.length - 1].getBoundingClientRect();
180
+ pos = isColumn ? (isReverse ? r.top : r.bottom) : isReverse ? r.left : r.right;
181
+ } else {
182
+ const r = siblings[index].getBoundingClientRect();
183
+ pos = isColumn ? (isReverse ? r.bottom : r.top) : isReverse ? r.right : r.left;
184
+ }
185
+
186
+ const line = doc.createElement('div');
187
+ line.id = INDICATOR_ID;
188
+ const base = 'position:fixed;z-index:10001;pointer-events:none;' +
189
+ `background:${BLUE};border-radius:1.5px;box-shadow:0 0 6px rgba(13,153,255,0.5);`;
190
+ line.style.cssText = isColumn
191
+ ? base + `left:${parentRect.left}px;width:${parentRect.width}px;top:${pos - 1.5}px;height:3px;`
192
+ : base + `top:${parentRect.top}px;height:${parentRect.height}px;left:${pos - 1.5}px;width:3px;`;
193
+
194
+ doc.body.appendChild(line);
195
+ }
196
+
197
+ /** 挿入位置の線を消す */
198
+ export function clearInsertIndicator(doc: Document): void {
199
+ doc.getElementById(INDICATOR_ID)?.remove();
200
+ }
201
+
202
+ /**
203
+ * 並べ替えを確定する。順番が変わったら true
204
+ *
205
+ * insertBefore しか使わないので、要素は必ず同じ親の中に留まる。
206
+ */
207
+ export function commitReorder(
208
+ session: ReorderSession,
209
+ index: number,
210
+ doc: Document,
211
+ ): boolean {
212
+ const { element, parent, siblings } = session;
213
+
214
+ // 見た目の持ち上げを戻してから並べ替える(保存HTMLに opacity を残さない)
215
+ endReorder(session, doc);
216
+
217
+ const target = index >= siblings.length ? null : siblings[index];
218
+ // すでにその位置にいるなら DOM を触らない(履歴を無駄に汚さない)
219
+ if (target === element.nextElementSibling) return false;
220
+ if (target === null && element === parent.lastElementChild) return false;
221
+
222
+ parent.insertBefore(element, target);
223
+ return true;
224
+ }
225
+
226
+ /** 見た目の持ち上げと線を片付ける(取り消し・中断からも呼ぶ) */
227
+ export function endReorder(session: ReorderSession | null, doc: Document): void {
228
+ clearInsertIndicator(doc);
229
+ if (!session) return;
230
+ session.element.classList.remove('dragging');
231
+ }