@growgroup/visual-editor 0.1.6 → 0.2.1

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 (42) hide show
  1. package/README.md +59 -2
  2. package/dist/editor.css +1 -1
  3. package/package.json +7 -3
  4. package/src/components/ui/alert-dialog.tsx +18 -4
  5. package/src/components/ui/context-menu.tsx +18 -4
  6. package/src/components/ui/dialog.tsx +19 -5
  7. package/src/components/ui/dropdown-menu.tsx +18 -4
  8. package/src/components/ui/popover.tsx +10 -2
  9. package/src/components/ui/select.tsx +10 -2
  10. package/src/components/ui/tooltip.tsx +10 -2
  11. package/src/editor/EditorToolbar.tsx +10 -20
  12. package/src/editor/FrontendVisualEditor.tsx +157 -43
  13. package/src/editor/components/EditorCanvas.tsx +1 -1
  14. package/src/editor/components/EditorContextMenu.tsx +53 -71
  15. package/src/editor/components/EditorFooter.tsx +36 -70
  16. package/src/editor/components/EditorHeader.tsx +37 -20
  17. package/src/editor/components/EditorLayerPanel.tsx +7 -9
  18. package/src/editor/components/EditorPropertyPanel.tsx +84 -8
  19. package/src/editor/components/LayerTreeItem.tsx +28 -5
  20. package/src/editor/components/component-panel/ComponentPanel.tsx +4 -3
  21. package/src/editor/components/ppt/PptChrome.tsx +156 -344
  22. package/src/editor/components/ppt/PptComments.tsx +135 -87
  23. package/src/editor/components/ppt/PptDesignProposals.tsx +21 -26
  24. package/src/editor/components/ppt/PptNotes.tsx +15 -15
  25. package/src/editor/components/shell/CanvasAppearance.tsx +19 -0
  26. package/src/editor/components/shell/ConfirmDialog.tsx +15 -0
  27. package/src/editor/components/shell/EditorTopBar.tsx +29 -68
  28. package/src/editor/components/shell/LeftPanel.tsx +20 -94
  29. package/src/editor/components/shell/PagesPanel.tsx +4 -2
  30. package/src/editor/contexts/EditorAppearanceContext.tsx +44 -0
  31. package/src/editor/contexts/EditorComponentsContext.tsx +278 -16
  32. package/src/editor/editor-skin.css +2 -122
  33. package/src/editor/hooks/useComponentEditMode.ts +93 -1
  34. package/src/editor/hooks/useIframeSetup.ts +10 -0
  35. package/src/editor/hooks/useMediaLibrary.ts +4 -5
  36. package/src/editor/hooks/useResizablePanel.ts +40 -11
  37. package/src/editor/parts.ts +321 -0
  38. package/src/editor/utils/dom-utils.ts +5 -4
  39. package/src/index.ts +25 -0
  40. package/src/io.ts +54 -0
  41. package/src/styles/skin.css +159 -0
  42. package/src/types/editor-components.ts +23 -1
@@ -5,7 +5,8 @@
5
5
  * 右クリックで要素操作メニューを表示
6
6
  */
7
7
 
8
- import { useCallback, useEffect, useRef } from 'react';
8
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../components/ui/dropdown-menu';
9
+ import { useEditorContext } from '../EditorContext';
9
10
  import {
10
11
  Copy,
11
12
  Scissors,
@@ -56,6 +57,12 @@ export interface ContextMenuProps {
56
57
  // コンポーネント状態
57
58
  isComponentInstance?: boolean;
58
59
  hasOverrides?: boolean;
60
+ // 部品(HTML template)状態
61
+ /** io.loadParts が渡されている(「コンポーネントを作成」が「部品として保存」になる) */
62
+ partsMode?: boolean;
63
+ isPartInstance?: boolean;
64
+ /** 「名前 vN」 */
65
+ partLabel?: string;
59
66
  // アクション
60
67
  /** 画像の変更(選択が<img>のときのみ渡される) */
61
68
  onReplaceImageFromFile?: () => void;
@@ -93,6 +100,9 @@ export interface ContextMenuProps {
93
100
  onDetachInstance?: () => void;
94
101
  onResetOverrides?: () => void;
95
102
  onPushOverridesToMain?: () => void;
103
+ // 部品操作
104
+ onDetachPart?: () => void;
105
+ onUpdatePart?: () => void;
96
106
  }
97
107
 
98
108
  interface MenuItemProps {
@@ -106,7 +116,7 @@ interface MenuItemProps {
106
116
 
107
117
  function MenuItem({ icon, label, shortcut, onClick, disabled, danger }: MenuItemProps) {
108
118
  return (
109
- <button
119
+ <DropdownMenuItem
110
120
  className={`
111
121
  w-full flex items-center gap-2 px-3 py-1.5 text-xs
112
122
  ${disabled
@@ -117,7 +127,7 @@ function MenuItem({ icon, label, shortcut, onClick, disabled, danger }: MenuItem
117
127
  }
118
128
  transition-colors
119
129
  `}
120
- onClick={(e) => {
130
+ onSelect={(e) => {
121
131
  e.stopPropagation();
122
132
  if (!disabled && onClick) onClick();
123
133
  }}
@@ -128,7 +138,7 @@ function MenuItem({ icon, label, shortcut, onClick, disabled, danger }: MenuItem
128
138
  {shortcut && (
129
139
  <span className="text-[10px] text-gray-500">{shortcut}</span>
130
140
  )}
131
- </button>
141
+ </DropdownMenuItem>
132
142
  );
133
143
  }
134
144
 
@@ -145,6 +155,11 @@ export function EditorContextMenu({
145
155
  hasStyleInClipboard,
146
156
  isComponentInstance,
147
157
  hasOverrides,
158
+ partsMode,
159
+ isPartInstance,
160
+ partLabel,
161
+ onDetachPart,
162
+ onUpdatePart,
148
163
  onCopy,
149
164
  onCut,
150
165
  onPaste,
@@ -176,63 +191,7 @@ export function EditorContextMenu({
176
191
  onResetOverrides,
177
192
  onPushOverridesToMain,
178
193
  }: ContextMenuProps) {
179
- const menuRef = useRef<HTMLDivElement>(null);
180
-
181
- // メニュー外クリックで閉じる
182
- useEffect(() => {
183
- if (!position) return;
184
-
185
- const handleClickOutside = (e: MouseEvent) => {
186
- if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
187
- onClose();
188
- }
189
- };
190
-
191
- const handleEscape = (e: KeyboardEvent) => {
192
- if (e.key === 'Escape') {
193
- onClose();
194
- }
195
- };
196
-
197
- // 次のフレームでリスナーを追加(右クリックイベントの完了を待つ)
198
- requestAnimationFrame(() => {
199
- document.addEventListener('mousedown', handleClickOutside);
200
- document.addEventListener('keydown', handleEscape);
201
- });
202
-
203
- return () => {
204
- document.removeEventListener('mousedown', handleClickOutside);
205
- document.removeEventListener('keydown', handleEscape);
206
- };
207
- }, [position, onClose]);
208
-
209
- // メニュー位置の調整(画面外にはみ出さないように)
210
- const getMenuStyle = useCallback(() => {
211
- if (!position) return {};
212
-
213
- const menuWidth = 200;
214
- const menuHeight = 320;
215
- const padding = 8;
216
-
217
- let x = position.x;
218
- let y = position.y;
219
-
220
- // 右端チェック
221
- if (x + menuWidth > window.innerWidth - padding) {
222
- x = window.innerWidth - menuWidth - padding;
223
- }
224
-
225
- // 下端チェック
226
- if (y + menuHeight > window.innerHeight - padding) {
227
- y = window.innerHeight - menuHeight - padding;
228
- }
229
-
230
- return {
231
- left: `${x}px`,
232
- top: `${y}px`,
233
- };
234
- }, [position]);
235
-
194
+ const { restoreFocus } = useEditorContext();
236
195
  if (!position) return null;
237
196
 
238
197
  const handleAction = (action?: () => void) => {
@@ -243,12 +202,12 @@ export function EditorContextMenu({
243
202
  };
244
203
 
245
204
  return (
246
- <div
247
- ref={menuRef}
248
- className="fixed z-[60] bg-[#2c2c2c] border border-[#444444] rounded-lg shadow-xl py-1 min-w-[180px]"
249
- style={getMenuStyle()}
250
- onClick={(e) => e.stopPropagation()}
251
- >
205
+ <DropdownMenu open onOpenChange={(open) => { if (!open) onClose(); }}>
206
+ <DropdownMenuTrigger asChild>
207
+ <button tabIndex={-1} aria-label="要素の操作" style={{ position: 'fixed', left: position.x, top: position.y, width: 1, height: 1, opacity: 0, pointerEvents: 'none' }} />
208
+ </DropdownMenuTrigger>
209
+ <DropdownMenuContent align="start" sideOffset={0} collisionPadding={8} className="w-60"
210
+ onCloseAutoFocus={(e) => { e.preventDefault(); restoreFocus(); }}>
252
211
  {/* クリップボード操作 */}
253
212
  <MenuItem
254
213
  icon={<Copy className="w-3.5 h-3.5" />}
@@ -471,11 +430,33 @@ export function EditorContextMenu({
471
430
  {hasSelection && selectionCount === 1 && (
472
431
  <>
473
432
  <MenuDivider />
474
- {/* 通常要素の場合: コンポーネント作成 */}
475
- {!isComponentInstance && (
433
+ {/* 部品(data-part)のインスタンス: 更新・切り離し */}
434
+ {isPartInstance && (
435
+ <>
436
+ <MenuItem
437
+ icon={<Component className="w-3.5 h-3.5 text-purple-400" />}
438
+ label={`部品: ${partLabel ?? ''}`}
439
+ disabled
440
+ />
441
+ {onUpdatePart && (
442
+ <MenuItem
443
+ icon={<ArrowUpFromLine className="w-3.5 h-3.5" />}
444
+ label="この姿で部品を更新"
445
+ onClick={() => handleAction(onUpdatePart)}
446
+ />
447
+ )}
448
+ <MenuItem
449
+ icon={<Unlink className="w-3.5 h-3.5" />}
450
+ label="部品から切り離す"
451
+ onClick={() => handleAction(onDetachPart)}
452
+ />
453
+ </>
454
+ )}
455
+ {/* 通常要素の場合: コンポーネント作成(部品モードでは「部品として保存」) */}
456
+ {!isComponentInstance && !isPartInstance && (
476
457
  <MenuItem
477
458
  icon={<Component className="w-3.5 h-3.5 text-purple-400" />}
478
- label="コンポーネントを作成"
459
+ label={partsMode ? '部品として保存' : 'コンポーネントを作成'}
479
460
  onClick={() => handleAction(onCreateComponent)}
480
461
  />
481
462
  )}
@@ -510,6 +491,7 @@ export function EditorContextMenu({
510
491
  )}
511
492
  </>
512
493
  )}
513
- </div>
494
+ </DropdownMenuContent>
495
+ </DropdownMenu>
514
496
  );
515
497
  }
@@ -1,80 +1,46 @@
1
1
  'use client';
2
2
 
3
+ import { Keyboard } from 'lucide-react';
3
4
  import { useEditorContext } from '../EditorContext';
4
5
  import { BreakpointSelector } from './BreakpointSelector';
6
+ import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
5
7
 
6
- /**
7
- * エディタのフッター(ショートカットヘルプ表示 + ブレイクポイントセレクター)
8
- */
9
8
  export function EditorFooter() {
10
- const { activeTool, editorMode } = useEditorContext();
11
-
9
+ const { activeTool, editorMode, selectedElement, selectedElementIds } = useEditorContext();
10
+ const cmd = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform) ? '⌘' : 'Ctrl+';
11
+ const hint = activeTool === 'move' ? 'ドラッグで紙面を移動 · V で選択に戻る'
12
+ : activeTool === 'scale' ? 'ドラッグで拡大・縮小 · V で選択に戻る'
13
+ : activeTool !== 'select' ? 'ドラッグで追加 · Esc でキャンセル'
14
+ : selectedElement || selectedElementIds.length ? 'ダブルクリックで文字を編集 · Esc で選択を解除'
15
+ : 'クリックで選択 · Space+ドラッグで紙面を移動';
16
+ const shortcuts = [
17
+ ['選択 / 移動 / 拡大・縮小', 'V / H / K'],
18
+ ['元に戻す / やり直し', `${cmd}Z / ${cmd}⇧Z`],
19
+ ['保存', `${cmd}S`], ['複製', `${cmd}D`],
20
+ ['全体表示 / 100% / 選択範囲', `${cmd}0 / ${cmd}1 / ${cmd}2`],
21
+ ['複数選択', 'Shift+クリック'], ['最下層の要素を選択', `${cmd}クリック`],
22
+ ['選択・入力を終了', 'Esc'],
23
+ ];
12
24
  return (
13
- <div className="px-4 py-2 bg-[#2c2c2c] border-t border-[#444444]">
14
- <div className="flex items-center justify-between text-xs text-gray-500">
15
- {/* 左側: ショートカットヘルプ */}
16
- <div className="flex items-center gap-4">
17
- {activeTool === 'select' ? (
18
- <>
19
- <span>
20
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
21
- クリック
22
- </kbd>{' '}
23
- 選択 & 編集
24
- </span>
25
- <span>
26
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
27
- Enter
28
- </kbd>{' '}
29
- 確定
30
- </span>
31
- <span>
32
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
33
- Esc
34
- </kbd>{' '}
35
- キャンセル
36
- </span>
37
- </>
38
- ) : (
39
- <>
40
- <span>
41
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
42
- ドラッグ
43
- </kbd>{' '}
44
- 描画
45
- </span>
46
- <span>
47
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
48
- V
49
- </kbd>{' '}
50
- 選択ツールに戻る
51
- </span>
52
- </>
53
- )}
54
- </div>
55
-
56
- {/* 中央: ブレイクポイントセレクター(webpageモードのみ) */}
57
- {editorMode === 'webpage' && (
58
- <div className="flex items-center">
59
- <BreakpointSelector />
60
- </div>
61
- )}
62
-
63
- {/* 右側: Undo/Redoショートカット */}
64
- <div className="flex items-center gap-4">
65
- <span>
66
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
67
- ⌘Z
68
- </kbd>{' '}
69
- 元に戻す
70
- </span>
71
- <span>
72
- <kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
73
- ⌘⇧Z
74
- </kbd>{' '}
75
- やり直し
76
- </span>
77
- </div>
25
+ <div className="ed-footer">
26
+ <span className="ed-footer-hint">{hint}</span>
27
+ <div className="flex shrink-0 items-center gap-4">
28
+ {editorMode === 'webpage' && <BreakpointSelector />}
29
+ <Popover>
30
+ <PopoverTrigger asChild>
31
+ <button className="flex items-center gap-2 rounded px-2 py-1" aria-label="キーボードショートカット">
32
+ <Keyboard className="h-4 w-4" />ショートカット
33
+ </button>
34
+ </PopoverTrigger>
35
+ <PopoverContent side="top" align="end" className="w-[400px]">
36
+ <h2 className="mb-4 text-sm font-semibold">キーボードショートカット</h2>
37
+ <dl className="space-y-3">
38
+ {shortcuts.map(([label, key]) => <div key={label} className="flex items-center justify-between gap-4 text-xs">
39
+ <dt>{label}</dt><dd className="m-0"><kbd className="rounded bg-muted px-2 py-1">{key}</kbd></dd>
40
+ </div>)}
41
+ </dl>
42
+ </PopoverContent>
43
+ </Popover>
78
44
  </div>
79
45
  </div>
80
46
  );
@@ -22,9 +22,10 @@ import {
22
22
  FolderArchive,
23
23
  ChevronDown,
24
24
  MoreHorizontal,
25
- MessageSquare,
25
+ MessageSquare, PanelLeft, Sun, Moon, Ruler,
26
26
  } from "lucide-react";
27
27
  import { useEditorContext } from "../EditorContext";
28
+ import { ConfirmDialog } from "./shell/ConfirmDialog";
28
29
  import { EditorTopBar } from "./shell/EditorTopBar";
29
30
  import { MIN_ZOOM, MAX_ZOOM, editorZoomApiRef } from "../hooks/useCanvasControls";
30
31
  import { generateEditableHtml } from "../utils/html-utils";
@@ -38,6 +39,12 @@ import {
38
39
  import { can } from "../../io";
39
40
 
40
41
  interface EditorHeaderProps {
42
+ theme?: "light" | "dark";
43
+ onToggleTheme?: () => void;
44
+ layersOpen?: boolean;
45
+ onToggleLayers?: () => void;
46
+ guidesOpen?: boolean;
47
+ onToggleGuides?: () => void;
41
48
  /** コメントパネルの開閉(Figma風にもPowerPoint同等のコメントを出す) */
42
49
  comments?: { open: boolean; toggle: () => void; page: number };
43
50
  /** PowerPoint風UIへの切り替え(任意) */
@@ -69,6 +76,7 @@ interface EditorHeaderProps {
69
76
  }
70
77
 
71
78
  export function EditorHeader({
79
+ theme, onToggleTheme, layersOpen, onToggleLayers, guidesOpen, onToggleGuides,
72
80
  onSwitchUi,
73
81
  onSave,
74
82
  onSaveSettings,
@@ -108,6 +116,8 @@ export function EditorHeader({
108
116
  editorMode,
109
117
  } = useEditorContext();
110
118
 
119
+ const [resetOpen, setResetOpen] = useState(false);
120
+
111
121
  // 保存・共有・プレビュー・閉じるは共通トップバー(EditorTopBar)が持つ。
112
122
  // ここに実装を残すと、PowerPoint風UIと2本の保存経路ができてしまう
113
123
 
@@ -289,6 +299,7 @@ export function EditorHeader({
289
299
  };
290
300
 
291
301
  return (
302
+ <>
292
303
  <EditorTopBar
293
304
  variant="figma"
294
305
  /* 一覧・PPT風UIはスライドのデッキが前提。Webページでは出さない */
@@ -302,7 +313,11 @@ export function EditorHeader({
302
313
  isCanvasEditing={isCanvasEditing}
303
314
  onSwitchUi={editorMode === "webpage" ? undefined : onSwitchUi}
304
315
  /* 利用側の導線。一覧へ戻る/UI切替が出ない使い方では左端が空くので、そこに並ぶ */
305
- leftExtra={headerExtra}
316
+ leftExtra={<>{headerExtra}{onToggleLayers && (
317
+ <button className="ed-button" onClick={onToggleLayers} aria-pressed={layersOpen} title="左パネルの表示を切り替え">
318
+ <PanelLeft className="h-4 w-4" />レイヤー
319
+ </button>
320
+ )}</>}
306
321
  /* Figma風の固有機能: ズームコントロール */
307
322
  rightExtra={
308
323
  <div className="flex items-center gap-1">
@@ -319,7 +334,7 @@ export function EditorHeader({
319
334
  onClick={handleZoomOut}
320
335
  disabled={!canZoomOut}
321
336
  title={canZoomOut ? "縮小" : `これ以上縮小できません(下限 ${MIN_ZOOM}%)`}
322
- className="h-7 w-7 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
337
+ className="h-8 w-8 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
323
338
  >
324
339
  <ZoomOut className="h-4 w-4" />
325
340
  </Button>
@@ -411,7 +426,7 @@ export function EditorHeader({
411
426
  onClick={handleZoomIn}
412
427
  disabled={!canZoomIn}
413
428
  title={canZoomIn ? "拡大" : `これ以上拡大できません(上限 ${MAX_ZOOM}%)`}
414
- className="h-7 w-7 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
429
+ className="h-8 w-8 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
415
430
  >
416
431
  <ZoomIn className="h-4 w-4" />
417
432
  </Button>
@@ -420,7 +435,7 @@ export function EditorHeader({
420
435
  size="icon"
421
436
  data-zoom-fit
422
437
  onClick={handleFitZoom}
423
- className="h-7 w-7 text-gray-400 hover:text-white hover:bg-[#444444]"
438
+ className="h-8 w-8 text-gray-400 hover:text-white hover:bg-[#444444]"
424
439
  title="全体を表示(⌘0)"
425
440
  >
426
441
  <Maximize className="h-4 w-4" />
@@ -440,14 +455,22 @@ export function EditorHeader({
440
455
  variant="ghost"
441
456
  size="icon"
442
457
  data-header-more
443
- className="h-7 w-7 text-gray-400 hover:bg-[#444444] hover:text-white"
458
+ className="h-8 w-8 text-gray-400 hover:bg-[#444444] hover:text-white"
444
459
  title="その他の操作"
445
460
  >
446
461
  <MoreHorizontal className="h-4 w-4" />
447
462
  </Button>
448
463
  </DropdownMenuTrigger>
449
464
  <DropdownMenuContent align="end" className="w-52 bg-[#2c2c2c] border-[#444444]">
450
- <DropdownMenuItem onClick={handleReset} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
465
+ {onToggleTheme && <DropdownMenuItem onSelect={onToggleTheme} className="gap-2">
466
+ {theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
467
+ {theme === 'dark' ? 'ライト表示にする' : 'ダーク表示にする'}
468
+ </DropdownMenuItem>}
469
+ {editorMode === 'webpage' && onToggleGuides && <DropdownMenuItem onSelect={onToggleGuides} className="gap-2">
470
+ <Ruler className="h-4 w-4" />{guidesOpen ? '幅のガイドを隠す' : '幅のガイドを表示'}
471
+ </DropdownMenuItem>}
472
+ <DropdownMenuSeparator />
473
+ <DropdownMenuItem onSelect={() => setResetOpen(true)} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
451
474
  <RotateCcw className="h-4 w-4" />
452
475
  変更をリセット
453
476
  </DropdownMenuItem>
@@ -493,6 +516,8 @@ export function EditorHeader({
493
516
  </DropdownMenu>
494
517
  }
495
518
  />
519
+ <ConfirmDialog open={resetOpen} onOpenChange={setResetOpen} title="変更をリセットしますか?" description="最後に保存した内容に戻ります。未保存の変更は失われます。" confirmLabel="リセットする" onConfirm={handleReset} />
520
+ </>
496
521
  );
497
522
  }
498
523
 
@@ -511,19 +536,11 @@ function CommentToggleButton({
511
536
  const deck = useDeck();
512
537
  const count = unresolvedCount(deck.slides[page - 1]?.comments);
513
538
  return (
514
- <Button
515
- variant="ghost"
516
- size="icon"
517
- onClick={onToggle}
518
- title="コメントパネルの表示/非表示"
519
- className={`relative h-8 w-8 hover:bg-[#444444] ${open ? "text-[#4fb8ff]" : "text-gray-400 hover:text-white"}`}
520
- >
539
+ <button className="ed-button" data-comment-toggle onClick={onToggle} aria-pressed={open}
540
+ aria-expanded={open} title="このページのコメントを確認・追加">
521
541
  <MessageSquare className="h-4 w-4" />
522
- {count > 0 && (
523
- <span className="absolute -right-0.5 -top-0.5 rounded-full bg-[#0d99ff] px-1 text-[9px] font-bold leading-[14px] text-white">
524
- {count}
525
- </span>
526
- )}
527
- </Button>
542
+ コメント
543
+ {count > 0 && <span className="tabular-nums" aria-label={`未解決${count}件`}>{count}</span>}
544
+ </button>
528
545
  );
529
546
  }
@@ -610,7 +610,7 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
610
610
  }, [getIframeDoc]);
611
611
 
612
612
  // ノードクリックハンドラ
613
- const handleNodeClick = useCallback((e: React.MouseEvent, nodeId: string) => {
613
+ const handleNodeClick = useCallback((e: React.MouseEvent | React.KeyboardEvent, nodeId: string) => {
614
614
  e.stopPropagation(); // バブリング防止
615
615
 
616
616
  let newSelectedIds: string[] = [];
@@ -1096,7 +1096,7 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
1096
1096
 
1097
1097
  return (
1098
1098
  <div
1099
- className="bg-[#2c2c2c] border-r border-[#444444] flex flex-col relative flex-shrink-0"
1099
+ className="ed-layer-panel min-h-0 flex-1 bg-[#2c2c2c] flex flex-col relative"
1100
1100
  // 2段構成(ページ+レイヤー)に埋め込まれているときは幅を親(LeftPanel)が持つ。
1101
1101
  // ここで固定幅を持つと上下の段で幅が食い違い、リサイズも二重になる
1102
1102
  style={hideSlideList ? { width: '100%' } : { width: `${width}px` }}
@@ -1119,8 +1119,9 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
1119
1119
  <Input
1120
1120
  value={searchQuery}
1121
1121
  onChange={(e) => setSearchQuery(e.target.value)}
1122
- placeholder="要素を検索..."
1123
- className="h-7 text-xs pl-7 bg-[#383838] border-[#444444] text-white placeholder:text-gray-500"
1122
+ placeholder="文字や要素名で検索"
1123
+ aria-label="レイヤーを検索"
1124
+ className="h-8 text-xs pl-7 bg-[#383838] border-[#444444] text-white placeholder:text-gray-500"
1124
1125
  />
1125
1126
  </div>
1126
1127
  </div>
@@ -1194,15 +1195,12 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
1194
1195
  ) : (
1195
1196
  // スライドがない場合は従来のレイヤー表示
1196
1197
  <>
1197
- <div className="flex items-center gap-2 text-xs text-gray-400 font-medium p-2">
1198
- <Layers className="w-3.5 h-3.5" />
1199
- レイヤー
1200
- </div>
1198
+ <p className="px-3 py-2 text-xs text-gray-400">クリックで選択・ドラッグで並べ替え</p>
1201
1199
  {filteredDomTree.length > 0 ? (
1202
1200
  filteredDomTree.map((node) => renderTreeNode(node))
1203
1201
  ) : (
1204
1202
  <div className="text-xs text-gray-500 text-center py-4">
1205
- {searchQuery ? '検索結果がありません' : 'レイヤーがありません'}
1203
+ {searchQuery ? '一致する要素がありません。別の言葉でお試しください。' : 'このページには編集できる要素がありません。'}
1206
1204
  </div>
1207
1205
  )}
1208
1206
  </>