@growgroup/visual-editor 0.3.0 → 0.4.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 (65) hide show
  1. package/README.md +10 -1
  2. package/dist/editor.css +1 -1
  3. package/package.json +3 -2
  4. package/src/editor/EditorContext.tsx +11 -2
  5. package/src/editor/EditorToolbar.tsx +8 -2
  6. package/src/editor/FrontendVisualEditor.tsx +128 -45
  7. package/src/editor/components/EditorCanvas.tsx +87 -15
  8. package/src/editor/components/EditorContextMenu.tsx +12 -3
  9. package/src/editor/components/EditorFooter.tsx +2 -0
  10. package/src/editor/components/EditorHeader.tsx +3 -2
  11. package/src/editor/components/EditorLayerPanel.tsx +28 -2
  12. package/src/editor/components/EditorPropertyPanel.tsx +54 -25
  13. package/src/editor/components/FigmaColorPicker.tsx +2 -1
  14. package/src/editor/components/JsEditorDialog.tsx +1 -0
  15. package/src/editor/components/PageSettingsDialog.tsx +1 -0
  16. package/src/editor/components/comments/CommentBoard.tsx +9 -6
  17. package/src/editor/components/multi-page/CanvasFrameDecor.tsx +172 -0
  18. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +204 -58
  19. package/src/editor/components/multi-page/PageFramePreview.tsx +131 -22
  20. package/src/editor/components/multi-page/index.ts +1 -0
  21. package/src/editor/components/ppt/PptChrome.tsx +6 -2
  22. package/src/editor/components/ppt/PptComments.tsx +411 -71
  23. package/src/editor/constants.ts +44 -1
  24. package/src/editor/contexts/EditorArtboardContext.tsx +60 -1
  25. package/src/editor/contexts/EditorComponentsContext.tsx +48 -31
  26. package/src/editor/contexts/EditorUIStateContext.tsx +4 -3
  27. package/src/editor/contexts/EditorVariablesContext.tsx +5 -4
  28. package/src/editor/contexts/MultiPageCanvasContext.tsx +211 -34
  29. package/src/editor/hooks/useAiReplace.ts +4 -3
  30. package/src/editor/hooks/useBrowserZoomPrevention.ts +2 -1
  31. package/src/editor/hooks/useComponentEditMode.ts +27 -26
  32. package/src/editor/hooks/useEditorMessages.ts +3 -2
  33. package/src/editor/hooks/useElementActions.ts +27 -26
  34. package/src/editor/hooks/useElementSelection.ts +19 -16
  35. package/src/editor/hooks/useFocusManagement.ts +7 -6
  36. package/src/editor/hooks/useGoogleFonts.ts +3 -2
  37. package/src/editor/hooks/useIframeInitializer.ts +6 -3
  38. package/src/editor/hooks/useIframeSetup.ts +4 -3
  39. package/src/editor/hooks/useImageUpload.ts +4 -3
  40. package/src/editor/hooks/useMarqueeSelection.ts +2 -1
  41. package/src/editor/hooks/usePageSettingsManager.ts +24 -23
  42. package/src/editor/hooks/useRichPaste.ts +11 -10
  43. package/src/editor/parts.ts +7 -25
  44. package/src/editor/utils/component-renderer.ts +8 -7
  45. package/src/editor/utils/component-sync.ts +16 -15
  46. package/src/editor/utils/debug.ts +52 -0
  47. package/src/editor/utils/dom-utils.ts +27 -10
  48. package/src/editor/utils/drop-target.ts +135 -0
  49. package/src/editor/utils/eyedropper.ts +3 -1
  50. package/src/editor/utils/figma-export.ts +6 -5
  51. package/src/editor/utils/figma-kiwi-decoder.ts +28 -27
  52. package/src/editor/utils/figma-kiwi-encoder.ts +9 -8
  53. package/src/editor/utils/figma-paste.ts +57 -56
  54. package/src/editor/utils/flex-utils.ts +12 -10
  55. package/src/editor/utils/html-utils.ts +19 -6
  56. package/src/editor/utils/override-apply.ts +3 -2
  57. package/src/editor/utils/override-detection.ts +11 -10
  58. package/src/editor/utils/tailwind-utils.ts +4 -3
  59. package/src/index.ts +5 -0
  60. package/src/io.ts +22 -1
  61. package/src/lib/firebase/css-variables.ts +4 -3
  62. package/src/lib/firebase/storage.ts +3 -2
  63. package/src/styles/skin.css +63 -4
  64. package/src/types/editor.ts +6 -2
  65. package/src/version.ts +9 -0
@@ -14,6 +14,7 @@ import type {
14
14
  ComponentInstance,
15
15
  OverridableProperties,
16
16
  } from '../../types/editor-components';
17
+ import { debugLog } from './debug';
17
18
 
18
19
  /**
19
20
  * Find an element within the ComponentElement tree by ID
@@ -148,7 +149,7 @@ function detectSinglePropertyOverride(
148
149
  switch (type) {
149
150
  case 'text': {
150
151
  // Debug: Log text comparison attempt
151
- console.log('[detectOverrides] Text comparison for element:', {
152
+ debugLog('[detectOverrides] Text comparison for element:', {
152
153
  elementId,
153
154
  masterHasInnerHTML: masterElement.innerHTML !== undefined,
154
155
  masterChildrenCount: masterElement.children.length,
@@ -160,7 +161,7 @@ function detectSinglePropertyOverride(
160
161
  // Skip text comparison for elements that use innerHTML (mixed content)
161
162
  // because the text is embedded in the HTML structure
162
163
  if (masterElement.innerHTML !== undefined) {
163
- console.log('[detectOverrides] Skipping: element uses innerHTML');
164
+ debugLog('[detectOverrides] Skipping: element uses innerHTML');
164
165
  return null;
165
166
  }
166
167
 
@@ -171,7 +172,7 @@ function detectSinglePropertyOverride(
171
172
  if (masterElement.children.length === 0) {
172
173
  const masterText = masterElement.textContent || '';
173
174
 
174
- console.log('[detectOverrides] Comparing text (leaf element):', {
175
+ debugLog('[detectOverrides] Comparing text (leaf element):', {
175
176
  elementId,
176
177
  instanceText,
177
178
  masterText,
@@ -179,7 +180,7 @@ function detectSinglePropertyOverride(
179
180
  });
180
181
 
181
182
  if (instanceText !== masterText) {
182
- console.log('[detectOverrides] Text override detected:', {
183
+ debugLog('[detectOverrides] Text override detected:', {
183
184
  elementId,
184
185
  instanceText: instanceText.substring(0, 50),
185
186
  masterText: masterText.substring(0, 50),
@@ -196,7 +197,7 @@ function detectSinglePropertyOverride(
196
197
  // Text overrides should only be created at leaf elements (no children)
197
198
  // Creating a parent-level text override would destroy the child structure
198
199
  // when the override is applied (textContent replaces all children)
199
- console.log('[detectOverrides] Skipping text override for element with children:', {
200
+ debugLog('[detectOverrides] Skipping text override for element with children:', {
200
201
  elementId,
201
202
  childrenCount: masterElement.children.length,
202
203
  reason: 'Text overrides at parent level would destroy child structure',
@@ -351,7 +352,7 @@ export function detectOverrides(
351
352
 
352
353
  // Debug: Log child matching
353
354
  if (componentElement.children.length > 0 || domChildren.length > 0) {
354
- console.log('[detectOverrides] Child matching:', {
355
+ debugLog('[detectOverrides] Child matching:', {
355
356
  parentId: componentElement.id,
356
357
  domChildrenCount: domChildren.length,
357
358
  domChildrenIds: domChildren.map(dc => dc.getAttribute('data-element-id')),
@@ -375,7 +376,7 @@ export function detectOverrides(
375
376
  if (matchingDomChild) {
376
377
  processElement(matchingDomChild, componentChild);
377
378
  } else {
378
- console.log('[detectOverrides] No matching DOM child for component child:', {
379
+ debugLog('[detectOverrides] No matching DOM child for component child:', {
379
380
  componentChildId: componentChild.id,
380
381
  componentChildTagName: componentChild.tagName,
381
382
  availableDomIds: domChildren.map(dc => dc.getAttribute('data-element-id')),
@@ -387,10 +388,10 @@ export function detectOverrides(
387
388
 
388
389
  processElement(instanceElement, masterElement);
389
390
 
390
- console.log('[detectOverrides] ========== FINAL RESULT ==========');
391
- console.log('[detectOverrides] Total overrides detected:', overrides.length);
391
+ debugLog('[detectOverrides] ========== FINAL RESULT ==========');
392
+ debugLog('[detectOverrides] Total overrides detected:', overrides.length);
392
393
  if (overrides.length > 0) {
393
- console.log('[detectOverrides] Detected overrides:', overrides.map(o => ({
394
+ debugLog('[detectOverrides] Detected overrides:', overrides.map(o => ({
394
395
  type: o.type,
395
396
  targetElementId: o.targetElementId,
396
397
  value: typeof o.value === 'string' ? o.value.substring(0, 50) : o.value,
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { twMerge } from 'tailwind-merge';
8
+ import { debugLog } from './debug';
8
9
  import {
9
10
  TAILWIND_CONFLICT_GROUPS,
10
11
  ENUM_MAPPINGS,
@@ -63,7 +64,7 @@ export function cssToTailwindClass(
63
64
  // CSS変数参照はstyle属性に直接設定
64
65
  if (value.startsWith('var(')) {
65
66
  // DEBUG: var() を検出した時のログ
66
- console.log('[DEBUG tailwind-utils] cssToTailwindClass detected var():', {
67
+ debugLog('[DEBUG tailwind-utils] cssToTailwindClass detected var():', {
67
68
  property,
68
69
  value,
69
70
  result: { className: null, styleValue: value },
@@ -421,7 +422,7 @@ export function applyTailwindStyles(
421
422
  styles: Record<string, string>
422
423
  ): void {
423
424
  // DEBUG: applyTailwindStyles 開始時のログ
424
- console.log('[DEBUG tailwind-utils] applyTailwindStyles called:', {
425
+ debugLog('[DEBUG tailwind-utils] applyTailwindStyles called:', {
425
426
  elementId: element.id,
426
427
  elementTagName: element.tagName,
427
428
  inputStyles: styles,
@@ -492,7 +493,7 @@ export function applyTailwindStyles(
492
493
  });
493
494
 
494
495
  // DEBUG: applyTailwindStyles 最終適用結果のログ
495
- console.log('[DEBUG tailwind-utils] applyTailwindStyles completed:', {
496
+ debugLog('[DEBUG tailwind-utils] applyTailwindStyles completed:', {
496
497
  elementId: element.id,
497
498
  classesToAdd,
498
499
  stylesToApply,
package/src/index.ts CHANGED
@@ -22,11 +22,16 @@ export type { FrontendVisualEditorProps } from "./editor/FrontendVisualEditor";
22
22
 
23
23
  export { EditorProvider, useEditorContext } from "./editor/EditorContext";
24
24
  export type { ContentListItem, EditorMode } from "./editor/EditorContext";
25
+ export type { DocumentAttributes } from "./editor/contexts/EditorArtboardContext";
26
+ export { computeContentDepths } from "./editor/contexts/EditorArtboardContext";
25
27
 
26
28
  export { WEBPAGE_WIDTH, WEBPAGE_MIN_HEIGHT, SLIDE_WIDTH, SLIDE_HEIGHT, BREAKPOINT_PRESETS } from "./editor/constants";
27
29
 
28
30
  export { registerAutoSaveFlush, flushAutoSave } from "./editor/autosave";
29
31
 
32
+ /** パッケージの版。利用側が dev サーバーの束ねたコードと node_modules の食い違い(再起動漏れ)を検出するのに使う */
33
+ export { EDITOR_VERSION } from "./version";
34
+
30
35
  /**
31
36
  * コメントの一覧(カンバン)。エディタとは別の画面に単独で置ける。
32
37
  *
package/src/io.ts CHANGED
@@ -36,15 +36,36 @@ export type EditorContent = {
36
36
  comments?: EditorComment[];
37
37
  };
38
38
 
39
+ /**
40
+ * 範囲アンカー。紙面(`#artboard`)の左上を原点にした px。
41
+ * `ref` は投稿したときの紙面の大きさ。紙面の幅が変わったら(ノート欄の有無など)
42
+ * 横方向だけ幅の比で補正する。縦は内容が増減すると動くので補正しない。
43
+ * width / height が 0 のときは「点」(クリックで置いたピン)。
44
+ */
45
+ export type CommentRect = {
46
+ x: number;
47
+ y: number;
48
+ width: number;
49
+ height: number;
50
+ ref?: { width: number; height: number };
51
+ };
52
+
39
53
  export type EditorComment = {
40
54
  id: string;
41
55
  author: string;
42
56
  text: string;
43
57
  createdAt: string;
44
58
  resolved?: boolean;
59
+ /**
60
+ * ページ内の通し番号(投稿順)。削除しても振り直さないので、返信で「#3 の件」と
61
+ * 書いても指す先が変わらない。ホストが採番する(無いホストでは付かない)
62
+ */
63
+ seq?: number;
45
64
  /** 要素アンカー。無ければ全体へのコメント */
46
65
  anchorSrc?: string;
47
66
  anchorLabel?: string;
67
+ /** 範囲アンカー(紙面の px)。要素アンカーより優先して表示する */
68
+ anchorRect?: CommentRect;
48
69
  replies?: { id: string; author: string; text: string; createdAt: string }[];
49
70
  };
50
71
 
@@ -55,7 +76,7 @@ export type EditorDeck = {
55
76
  };
56
77
 
57
78
  export type CommentAction =
58
- | { action: "add"; author: string; text: string; anchorSrc?: string; anchorLabel?: string }
79
+ | { action: "add"; author: string; text: string; anchorSrc?: string; anchorLabel?: string; anchorRect?: CommentRect }
59
80
  | { action: "reply"; commentId: string; author: string; text: string }
60
81
  | { action: "resolve"; commentId: string; resolved: boolean }
61
82
  | { action: "delete"; commentId: string };
@@ -17,6 +17,7 @@ import type {
17
17
  CSSVariableDefinition,
18
18
  CSSVariableCollection,
19
19
  } from '../../types/css-variables';
20
+ import { debugLog } from '../../editor/utils/debug';
20
21
 
21
22
  // ============================================
22
23
  // スコープタイプ定義
@@ -184,7 +185,7 @@ export async function getCSSVariables(
184
185
  const data = readDoc(key);
185
186
 
186
187
  if (!data) {
187
- console.log(`[CSSVariables] No variables found for ${scope}:`, resourceId);
188
+ debugLog(`[CSSVariables] No variables found for ${scope}:`, resourceId);
188
189
  return null;
189
190
  }
190
191
 
@@ -240,7 +241,7 @@ export async function saveCSSVariables(
240
241
  };
241
242
 
242
243
  writeDoc(key, data);
243
- console.log(`[CSSVariables] Saved variables for ${scope}:`, resourceId, 'count:', variables.length);
244
+ debugLog(`[CSSVariables] Saved variables for ${scope}:`, resourceId, 'count:', variables.length);
244
245
  } catch (error) {
245
246
  console.error('[CSSVariables] Error saving variables:', error);
246
247
  throw error;
@@ -298,7 +299,7 @@ export async function deleteCSSVariablesCollection(websiteId: string): Promise<v
298
299
  const key = getCSSVariablesKey(websiteId);
299
300
  const store = getStore();
300
301
  if (store) store.removeItem(key);
301
- console.log('[CSSVariables] Deleted variables for website:', websiteId);
302
+ debugLog('[CSSVariables] Deleted variables for website:', websiteId);
302
303
  } catch (error) {
303
304
  console.error('[CSSVariables] Error deleting variables:', error);
304
305
  throw error;
@@ -42,6 +42,7 @@ async function persistDataUrl(dataUrl: string, fileName: string): Promise<string
42
42
  }
43
43
 
44
44
  import type { PageResourceUpload } from '../../types/page';
45
+ import { debugLog } from '../../editor/utils/debug';
45
46
 
46
47
  // ============================================
47
48
  // 内部ヘルパー
@@ -161,7 +162,7 @@ export async function uploadPageResource(
161
162
 
162
163
  // ファイル削除(ローカルではリモート実体が無いため no-op)
163
164
  export async function deletePageResource(storagePath: string): Promise<void> {
164
- console.log('[storage] deletePageResource is a no-op in local mode:', storagePath);
165
+ debugLog('[storage] deletePageResource is a no-op in local mode:', storagePath);
165
166
  }
166
167
 
167
168
  // ファイル情報取得(ローカルではメタデータを保持しないためスタブを返す)
@@ -264,7 +265,7 @@ export async function uploadFigmaImage(
264
265
  // コンポーネントの画像ファイルを削除(ローカルでは no-op)
265
266
  export async function deleteFigmaComponentImages(componentId: string): Promise<void> {
266
267
  const basePath = `components/${componentId}/figma-images/`;
267
- console.log(`[storage] deleteFigmaComponentImages is a no-op in local mode: ${basePath}`);
268
+ debugLog(`[storage] deleteFigmaComponentImages is a no-op in local mode: ${basePath}`);
268
269
  }
269
270
 
270
271
  // 画像ファイルサイズの推定(Base64から)
@@ -258,9 +258,46 @@
258
258
  .gg-editor-ui .ed-resize-handle:hover, .gg-editor-ui .ed-resize-handle:focus-visible { background-color: var(--ed-accent); }
259
259
  .gg-editor-ui .ed-layer-selected { box-shadow: inset 3px 0 var(--ed-accent); }
260
260
  .gg-editor-ui .ed-layer-selected :is(.text-white, .text-blue-100, .text-blue-200) { color: var(--ed-accent); }
261
- .gg-editor-ui [data-property-panel] input:not([type="color"]), .gg-editor-ui [data-property-panel] select { min-height: 28px; }
262
- .gg-editor-ui [data-property-panel] button[data-state] { min-height: 32px; }
261
+ /* ── 右のプロパティパネル(Figma UI3 の寸法に寄せる) ──────────────
262
+ 節は 1px の罫線で区切り、見出しは 13px/600・高さ 40px、中身は左右 16px。
263
+ 入力は高さ 32px・角丸 6px・面の色(枠線は無し。ホバーで枠、フォーカスでアクセント)。
264
+ ラベルは 11px の補助色。数値の小さな入力(十字のパディング等)は 28px */
263
265
  .gg-editor-ui [data-property-panel] .ed-panel-heading { position: sticky; top: 0; z-index: 1; }
266
+ .gg-editor-ui [data-property-panel] .p-3 { padding: 0; }
267
+ .gg-editor-ui [data-property-panel] .p-3 > * { padding-left: 16px; padding-right: 16px; }
268
+ .gg-editor-ui [data-property-panel] .p-3 > .mb-3 { min-height: 44px; margin-bottom: 0; }
269
+ .gg-editor-ui [data-property-panel] .p-3 > [data-state] { border-top: 1px solid var(--ed-line); }
270
+ .gg-editor-ui [data-property-panel] .p-3 > .ed-part-card { margin: 0 16px 12px; padding: 10px 12px; }
271
+ .gg-editor-ui [data-property-panel] [data-state] > button[data-state] {
272
+ min-height: 40px; padding: 0; border-top: 0; font-size: 13px; font-weight: 600; color: var(--ed-text); border-radius: 0;
273
+ }
274
+ .gg-editor-ui [data-property-panel] [data-state] > button[data-state]:hover { background: transparent; color: var(--ed-text); }
275
+ .gg-editor-ui [data-property-panel] [data-state] > button[data-state] > span:first-child > svg { width: 14px; height: 14px; color: var(--ed-muted); }
276
+ .gg-editor-ui [data-property-panel] [data-state] > button[data-state] > svg:last-child { width: 14px; height: 14px; color: var(--ed-muted); }
277
+ .gg-editor-ui [data-property-panel] [data-state] > div[data-state="open"] { padding-bottom: 16px; }
278
+ .gg-editor-ui [data-property-panel] label, .gg-editor-ui [data-property-panel] .ed-prop-label { font-size: 11px; line-height: 1.4; color: var(--ed-muted); font-weight: 500; }
279
+ .gg-editor-ui [data-property-panel] :is(input:not([type="color"]):not([type="checkbox"]):not([type="range"]), select, [role="combobox"]):not(.group *) {
280
+ min-height: 32px; height: 32px; border-radius: 6px; background: var(--ed-surface); border: 1px solid transparent !important;
281
+ color: var(--ed-text); font-size: 12px; padding-left: 8px; padding-right: 8px; box-shadow: none;
282
+ }
283
+ .gg-editor-ui [data-property-panel] :is(input:not([type="color"]):not([type="checkbox"]):not([type="range"]), select, [role="combobox"]):not(.group *):hover { border-color: var(--ed-line-strong) !important; }
284
+ .gg-editor-ui [data-property-panel] :is(input:not([type="color"]):not([type="checkbox"]):not([type="range"]), select, [role="combobox"]):not(.group *):focus-visible,
285
+ .gg-editor-ui [data-property-panel] :is(input:not([type="color"]):not([type="checkbox"]):not([type="range"]), select, [role="combobox"]):not(.group *):focus-within { border-color: var(--ed-accent) !important; outline: none; box-shadow: none; }
286
+ /* 数値のスクラブ入力(CompactNumberInput。器 .group が枠) */
287
+ .gg-editor-ui [data-property-panel] .group.h-5 { height: 28px; min-width: 40px; border-radius: 6px; background: var(--ed-surface); border-color: transparent; }
288
+ .gg-editor-ui [data-property-panel] .group.h-5:hover { border-color: var(--ed-line-strong); }
289
+ .gg-editor-ui [data-property-panel] .group.h-5:focus-within { border-color: var(--ed-accent); }
290
+ .gg-editor-ui [data-property-panel] .group.h-5 input { font-size: 12px; color: var(--ed-text); }
291
+ .gg-editor-ui [data-property-panel] .group.h-5 input:focus { box-shadow: none; }
292
+ /* 配置・書式のトグル(h-7 w-7)は 32px の角丸 6px。押下は面の色で示す */
293
+ .gg-editor-ui [data-property-panel] button.h-7.w-7, .gg-editor-ui [data-property-panel] button.h-7 { height: 32px; min-width: 32px; border-radius: 6px; }
294
+ .gg-editor-ui [data-property-panel] button.h-7.w-7 svg { width: 14px; height: 14px; }
295
+ .gg-editor-ui [data-property-panel] button[data-state]:not([role="checkbox"]):not([role="switch"]) { min-height: 32px; }
296
+ .gg-editor-ui [data-property-panel] button[role="checkbox"] { width: 16px; height: 16px; min-height: 16px; border-radius: 4px; border: 1px solid var(--ed-line-strong); background: var(--ed-surface); }
297
+ .gg-editor-ui [data-property-panel] button[role="checkbox"][data-state="checked"] { background: var(--ed-accent-strong); border-color: var(--ed-accent-strong); color: #fff; }
298
+ .gg-editor-ui [data-property-panel] input[type="color"] { width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--ed-line-strong); padding: 0; }
299
+ .gg-editor-ui [data-property-panel] .grid.grid-cols-2 { gap: 8px; }
300
+ .gg-editor-ui [data-property-panel] .space-y-2 > * + *, .gg-editor-ui [data-property-panel] .space-y-3 > * + * { margin-top: 10px; }
264
301
  /* ポータル自身も配色スコープ。利用側にToasterがある場合もその設定を奪わない。 */
265
302
  .gg-editor-ui[data-editor-surface="popup"], .gg-editor-ui[data-editor-surface="dialog"], .gg-editor-ui[data-editor-surface="tooltip"] {
266
303
  color: var(--ed-text); background-color: var(--ed-panel-raised); border-color: var(--ed-line); border-radius: 12px; box-shadow: var(--ed-shadow); z-index: 10000;
@@ -288,13 +325,31 @@
288
325
  .gg-editor-ui :is(input:not([type="color"]), textarea, select):not([data-editor-preview] *) { border-color: var(--ed-line-strong) !important; }
289
326
 
290
327
  /* ── マルチフレームのキャンバス(Figma 風) ──────────────
291
- フレーム名とリングは画面座標に置く(倍率に関係なく同じ太さ・同じ文字サイズ)。
328
+ フレーム名とリングはオーバーレイの中で scale(1/倍率) を掛けて画面上の大きさに戻す
329
+ (倍率に関係なく同じ太さ・同じ文字サイズ)。
292
330
  見るだけの紙面の骨組み(スケルトン)だけは転写層の中にあるので、フレームの寸法に対する % で描く */
331
+ /* フレームの白地だけ。枠線と階層の線は画面の座標の canvas(CanvasFrameDecor)が描く。
332
+ 転写層の中に枠線を持たせると、倍率が変わるたびに転写層まるごとが塗り直される(実測) */
333
+ .gg-editor-ui .ed-frame-shell { background: #fff; }
334
+ .gg-editor-ui .ed-frame-label-row { position: absolute; left: 0; height: 20px; display: inline-flex; align-items: center; gap: 2px; max-width: 100%; }
293
335
  .gg-editor-ui .ed-frame-label {
294
- position: absolute; left: 0; height: 20px; display: inline-flex; align-items: center; gap: 6px;
336
+ height: 20px; min-width: 0; display: inline-flex; align-items: center; gap: 6px;
295
337
  padding: 0 6px 0 4px; border-radius: 4px; font-size: 11px; line-height: 1; white-space: nowrap;
296
338
  color: var(--ed-muted); pointer-events: auto; cursor: pointer; overflow: hidden; text-overflow: ellipsis;
297
339
  }
340
+ /* 別タブで開く(編集しない表示)。名前の右。ホバー・編集中のときだけ見せる */
341
+ .gg-editor-ui .ed-frame-label-link {
342
+ display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 18px; height: 18px;
343
+ border-radius: 4px; color: var(--ed-muted); pointer-events: auto; opacity: 0; transition: opacity .12s;
344
+ }
345
+ .gg-editor-ui .ed-frame-label-link svg { width: 12px; height: 12px; }
346
+ .gg-editor-ui .ed-frame-label-row:hover .ed-frame-label-link, .gg-editor-ui .ed-frame-label-active + .ed-frame-label-link, .gg-editor-ui .ed-frame-label-link:focus-visible { opacity: 1; }
347
+ .gg-editor-ui .ed-frame-label-link:hover { color: var(--ed-text); background: var(--ed-panel); box-shadow: 0 0 0 1px var(--ed-line); }
348
+ /* 部品(data-part)のカード。紙面の紫の選択枠と同じ色 */
349
+ .gg-editor-ui .ed-part-card { border: 1px solid color-mix(in srgb, #7b61ff 45%, var(--ed-line)); background: color-mix(in srgb, #7b61ff 8%, var(--ed-panel)); color: var(--ed-text); }
350
+ .gg-editor-ui .ed-part-accent { color: #7b61ff; }
351
+ .gg-editor-ui[data-editor-theme="dark"] .ed-part-accent { color: #a99cff; }
352
+ .gg-editor-ui .ed-part-note { color: var(--ed-muted); }
298
353
  .gg-editor-ui .ed-frame-label:hover { color: var(--ed-text); background: var(--ed-panel); box-shadow: 0 0 0 1px var(--ed-line); }
299
354
  .gg-editor-ui .ed-frame-label-active, .gg-editor-ui .ed-frame-label-active:hover { color: var(--ed-accent); font-weight: 600; }
300
355
  .gg-editor-ui .ed-frame-label-index { font-variant-numeric: tabular-nums; opacity: 0.7; }
@@ -306,6 +361,10 @@
306
361
  .gg-editor-ui .ed-canvas-marquee { position: absolute; z-index: 20; pointer-events: none; border: 1px solid var(--ed-accent); background: color-mix(in srgb, var(--ed-accent) 12%, transparent); }
307
362
  .gg-editor-ui .ed-frame-ring-active { box-shadow: 0 0 0 1.5px var(--ed-accent-strong); }
308
363
  .gg-editor-ui .ed-frame-ring-hover { box-shadow: 0 0 0 1px var(--ed-accent-strong); opacity: 0.55; }
364
+ /* 縮小の途中(倍率 0.4 未満)は、画像が読めている紙面の iframe を止めて、下に敷いた画像を見せる。
365
+ iframe を外して(メモリを返して)しまうのは操作が止まってから ── その間もラスタは止まっている。
366
+ 属性 1 つ(キャンバスの容器の data-canvas-preview-image)の書き換えで 26 枚がいっぺんに切り替わる */
367
+ .gg-editor-ui [data-canvas-preview-image="1"] [data-page-preview][data-image-ready="1"] iframe { visibility: hidden; }
309
368
  .gg-editor-ui .ed-frame-skeleton { position: absolute; inset: 0; padding: 6% 8%; display: flex; flex-direction: column; gap: 3%; background: #fff; }
310
369
  .gg-editor-ui .ed-frame-skeleton-bar { display: block; height: 2.5%; min-height: 8px; border-radius: 999px; background: #eceff3; }
311
370
  .gg-editor-ui .ed-frame-skeleton-note { font-size: 40px; color: #9aa3b2; }
@@ -16,7 +16,8 @@ export type EditorTool =
16
16
  | 'text' // T - Text tool
17
17
  | 'scale' // K - Scale tool
18
18
  | 'eraser' // E - Eraser (ペン/鉛筆ストロークを消す)
19
- | 'shape'; // 図形ギャラリーで選んだ図形(ドラッグでサイズ指定)
19
+ | 'shape' // 図形ギャラリーで選んだ図形(ドラッグでサイズ指定)
20
+ | 'comment'; // C - コメント(紙面をドラッグして範囲を指定。クリックで点)
20
21
 
21
22
  // Shape types that can be drawn
22
23
  export type ShapeType = 'rectangle' | 'ellipse' | 'line' | 'arrow' | 'text' | 'pen' | 'pencil' | 'frame';
@@ -101,6 +102,7 @@ export type ShortcutAction =
101
102
  | 'text'
102
103
  | 'frame'
103
104
  | 'scale'
105
+ | 'comment'
104
106
  // Edit operations
105
107
  | 'undo'
106
108
  | 'redo'
@@ -189,7 +191,7 @@ export interface ToolConfig {
189
191
  label: string;
190
192
  shortcut: string;
191
193
  icon: string; // Lucide icon name
192
- group: 'selection' | 'shapes' | 'drawing' | 'text' | 'frame';
194
+ group: 'selection' | 'shapes' | 'drawing' | 'text' | 'frame' | 'comment';
193
195
  }
194
196
 
195
197
  // Default tool configurations
@@ -205,6 +207,7 @@ export const TOOL_CONFIGS: ToolConfig[] = [
205
207
  { id: 'pen', label: 'ペン', shortcut: 'P', icon: 'Pen', group: 'drawing' },
206
208
  { id: 'pencil', label: 'フリーハンド', shortcut: '⇧P', icon: 'Pencil', group: 'drawing' },
207
209
  { id: 'text', label: 'テキスト', shortcut: 'T', icon: 'Type', group: 'text' },
210
+ { id: 'comment', label: 'コメント(範囲を指定)', shortcut: 'C', icon: 'MessageSquare', group: 'comment' },
208
211
  ];
209
212
 
210
213
  /**
@@ -232,6 +235,7 @@ export const KEYBOARD_SHORTCUTS: Record<string, ShortcutAction> = {
232
235
  'shift+p': 'pencil',
233
236
  't': 'text',
234
237
  'f': 'frame',
238
+ 'c': 'comment',
235
239
 
236
240
  // Edit operations (Cmd/Ctrl + key)
237
241
  'meta+z': 'undo',
package/src/version.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * パッケージの版(package.json の version と同じ値。scripts/check-version.mjs が一致を検査する)。
3
+ *
4
+ * 利用側が「いま動いているエディタの版」を知るために使う。dev サーバー(Vite)は起動時に
5
+ * node_modules を束ねるので、パッケージを更新しても再起動するまで古いコードが動く。
6
+ * 利用側はこの値と node_modules の package.json を見比べて、食い違っていれば
7
+ * 「再起動してください」と出せる(構成ラフのテンプレートが行っている)
8
+ */
9
+ export const EDITOR_VERSION = '0.4.0';