@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
@@ -120,3 +120,162 @@
120
120
  outline: 2px solid var(--ed-accent);
121
121
  outline-offset: 1px;
122
122
  }
123
+
124
+ /* 2026-09: 新しいエディタとそのPortalだけに適用。利用側のレールは対象外。
125
+ 層の名前を base にしない: 利用側が Tailwind v3(PostCSS)だと dist/editor.css の
126
+ `@layer base` を自分の指令と誤認して「@tailwind base が無い」とビルドを止める。
127
+ 独自名の層なら素通しされ、層なしの utilities が上書きする関係も保てる。 */
128
+ @layer gg-editor-defaults {
129
+ .gg-editor-ui { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", Meiryo, sans-serif; font-size: 13px; line-height: 1.5; text-align: left; }
130
+ .gg-editor-ui, .gg-editor-ui :where(*, *::before, *::after) { box-sizing: border-box; border-width: 0; border-style: solid; }
131
+ .gg-editor-ui :where(button, input, textarea, select) { font: inherit; color: inherit; margin: 0; }
132
+ .gg-editor-ui :where(button) { background: transparent; cursor: pointer; }
133
+ .gg-editor-ui :where(button:disabled) { cursor: default; }
134
+ .gg-editor-ui :where(p, h1, h2, h3, h4, ul, ol) { margin: 0; padding: 0; }
135
+ .gg-editor-ui :where(svg) { display: block; flex-shrink: 0; }
136
+ .gg-editor-ui :where(input, textarea) { min-width: 0; }
137
+ }
138
+
139
+ .gg-editor-ui {
140
+ color-scheme: light;
141
+ --ed-bg: #eef1f5;
142
+ --ed-panel: #ffffff;
143
+ --ed-panel-raised: #ffffff;
144
+ --ed-surface: #f3f5f8;
145
+ --ed-surface-hover: #e8edf4;
146
+ --ed-text: #202938;
147
+ --ed-muted: #566477;
148
+ --ed-line: #dce2ea;
149
+ --ed-line-strong: #7b889a;
150
+ --ed-accent: #2459c4;
151
+ --ed-accent-strong: #2459c4;
152
+ --ed-accent-hover: #1c48a2;
153
+ --ed-accent-soft: #eaf0ff;
154
+ --ed-success: #216346;
155
+ --ed-warning: #885500;
156
+ --ed-danger: #b42332;
157
+ --ed-shadow: 0 8px 32px #20293814, 0 2px 8px #2029380a;
158
+ --ed-radius: 8px;
159
+ --ed-focus: 0 0 0 2px var(--ed-panel), 0 0 0 4px var(--ed-accent);
160
+ --color-background: var(--ed-panel);
161
+ --color-foreground: var(--ed-text);
162
+ --color-card: var(--ed-panel);
163
+ --color-card-foreground: var(--ed-text);
164
+ --color-popover: var(--ed-panel-raised);
165
+ --color-popover-foreground: var(--ed-text);
166
+ --color-primary: var(--ed-accent-strong);
167
+ --color-primary-foreground: #fff;
168
+ --color-secondary: var(--ed-surface);
169
+ --color-secondary-foreground: var(--ed-text);
170
+ --color-muted: var(--ed-surface);
171
+ --color-muted-foreground: var(--ed-muted);
172
+ --color-accent: var(--ed-surface-hover);
173
+ --color-accent-foreground: var(--ed-text);
174
+ --color-border: var(--ed-line);
175
+ --color-input: var(--ed-line-strong);
176
+ --color-ring: var(--ed-accent);
177
+ --color-destructive: var(--ed-danger);
178
+ color: var(--ed-text);
179
+ accent-color: var(--ed-accent-strong);
180
+ scrollbar-color: var(--ed-line-strong) transparent;
181
+ }
182
+ .gg-editor-ui[data-editor-theme="dark"] {
183
+ color-scheme: dark;
184
+ --ed-bg: #191d24;
185
+ --ed-panel: #242932;
186
+ --ed-panel-raised: #2b313c;
187
+ --ed-surface: #303744;
188
+ --ed-surface-hover: #3c4554;
189
+ --ed-text: #f0f3f8;
190
+ --ed-muted: #b4bfce;
191
+ --ed-line: #3b4554;
192
+ --ed-line-strong: #7e8ca0;
193
+ --ed-accent: #9bbcff;
194
+ --ed-accent-strong: #3168d5;
195
+ --ed-accent-hover: #2859bc;
196
+ --ed-accent-soft: #303e59;
197
+ --ed-success: #8bddb0;
198
+ --ed-warning: #f3cc80;
199
+ --ed-danger: #ffa6ad;
200
+ --ed-shadow: 0 8px 32px #00000040, 0 2px 8px #00000026;
201
+ }
202
+ /* 古い固定色の部品も同じトークンへ移行。塗り見本・紙面のコンテンツは対象外。 */
203
+ .gg-editor-ui :is(.text-white, .text-gray-100, .text-gray-200, .text-gray-300):not([data-editor-preview] *) { color: var(--ed-text); }
204
+ .gg-editor-ui :is(.text-gray-400, .text-gray-500, .text-gray-600):not([data-editor-preview] *) { color: var(--ed-muted); }
205
+ .gg-editor-ui :is(.hover\:text-white:hover, .hover\:text-gray-200:hover):not([data-editor-preview] *) { color: var(--ed-text); }
206
+ .gg-editor-ui :is(.hover\:bg-\[\#444444\]:hover, .hover\:bg-\[\#383838\]:hover, .hover\:bg-white\/10:hover):not([data-editor-preview] *) { background-color: var(--ed-surface-hover); }
207
+ .gg-editor-ui :is(.text-sky-400, .text-\[\#7cc4ff\]):not([data-editor-preview] *) { color: var(--ed-accent); }
208
+ .gg-editor-ui :is(.text-red-400, .text-red-500):not([data-editor-preview] *) { color: var(--ed-danger); }
209
+ .gg-editor-ui :is(.bg-blue-500, .bg-blue-600, .bg-\[\#0d99ff\], .bg-green-600, .bg-purple-600, .bg-red-500, .bg-black\/90).text-white,
210
+ .gg-editor-ui :is(.bg-blue-500, .bg-blue-600, .bg-\[\#0d99ff\], .bg-green-600, .bg-purple-600, .bg-red-500, .bg-black\/90) .text-white:not([data-editor-preview] *) { color: #fff; }
211
+ .gg-editor-ui :is(.bg-\[\#0d99ff\]\/20, .bg-\[\#0d99ff\]\/15, .bg-\[\#0d99ff\]\/30):not([data-editor-preview] *) { background-color: var(--ed-accent-soft); }
212
+ .gg-editor-ui :is(.bg-white\/15, .bg-white\/10):not([data-editor-preview] *) { background-color: var(--ed-line); }
213
+ .gg-editor-ui :is(.shadow-lg, .shadow-xl, .shadow-2xl):not([data-editor-preview] *) { box-shadow: var(--ed-shadow); }
214
+ .gg-editor-ui :is([class*="text-[6px]"], [class*="text-[7px]"], [class*="text-[8px]"], [class*="text-[9px]"], [class*="text-[9.5px]"], [class*="text-[10px]"], [class*="text-[10.5px]"], [class*="text-[11px]"], [class*="text-[11.5px]"]):not([data-editor-preview] *) { font-size: 12px; }
215
+ .gg-editor-ui :is(button, input, select, textarea, a, [tabindex]):focus-visible {
216
+ outline: 2px solid var(--ed-accent); outline-offset: 2px; box-shadow: none;
217
+ }
218
+ .gg-editor-ui :is(input, textarea):not([data-editor-preview] *)::placeholder { color: var(--ed-muted); opacity: 1; }
219
+ .gg-editor-ui .ed-icon-button, .gg-editor-ui .ed-button {
220
+ display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; gap: 8px;
221
+ height: 36px; padding: 0 12px; border: 1px solid var(--ed-line); border-radius: var(--ed-radius);
222
+ color: var(--ed-text); background: var(--ed-panel); font-size: 13px; font-weight: 500; white-space: nowrap;
223
+ }
224
+ .gg-editor-ui .ed-icon-button { width: 32px; height: 32px; padding: 0; border-color: transparent; background: transparent; }
225
+ .gg-editor-ui :is(.ed-button, .ed-icon-button):hover:not([data-editor-preview] *) { background: var(--ed-surface-hover); }
226
+ .gg-editor-ui :is(.ed-button, .ed-icon-button)[aria-pressed="true"]:not([data-editor-preview] *) { background: var(--ed-accent-soft); color: var(--ed-accent); border-color: var(--ed-accent); }
227
+ .gg-editor-ui .ed-button-primary { background: var(--ed-accent-strong); color: #fff; border-color: transparent; }
228
+ .gg-editor-ui .ed-button-primary:hover { background: var(--ed-accent-hover); }
229
+ .gg-editor-ui button:disabled { opacity: .45; }
230
+ .gg-editor-ui [data-editor-topbar] { height: 56px; gap: 12px; padding-inline: 16px; }
231
+ .gg-editor-ui [data-topbar="title"] { font-size: 14px; }
232
+ .gg-editor-ui .ed-save-status { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; white-space: nowrap; color: var(--ed-muted); }
233
+ .gg-editor-ui .ed-save-status[data-save-status="dirty"] { color: var(--ed-warning); }
234
+ .gg-editor-ui .ed-save-status[data-save-status="error"] { color: var(--ed-danger); }
235
+ .gg-editor-ui .ed-save-status[data-save-status="saved"] svg { color: var(--ed-success); }
236
+ .gg-editor-ui .ed-panel-heading { height: 48px; display: flex; align-items: center; gap: 8px; padding: 0 16px; border-bottom: 1px solid var(--ed-line); flex-shrink: 0; font-size: 14px; font-weight: 600; }
237
+ .gg-editor-ui .ed-toolbar { border: 1px solid var(--ed-line); border-radius: 12px; padding: 8px; background: var(--ed-panel-raised); color: var(--ed-text); box-shadow: var(--ed-shadow); }
238
+ .gg-editor-ui .ed-footer { height: 36px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 16px; border-top: 1px solid var(--ed-line); background: var(--ed-panel); color: var(--ed-muted); font-size: 12px; flex-shrink: 0; }
239
+ .gg-editor-ui .ed-footer-hint { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
240
+ .gg-editor-ui .ed-empty { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; padding: 24px; color: var(--ed-muted); font-size: 13px; line-height: 1.7; }
241
+ .gg-editor-ui .ed-empty strong { color: var(--ed-text); font-size: 14px; }
242
+ @media (prefers-reduced-motion: reduce) {
243
+ .gg-editor-ui *, .gg-editor-ui *::before, .gg-editor-ui *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
244
+ }
245
+ .gg-editor-ui .ed-panel-tabs { display: flex; height: 48px; width: 100%; flex-shrink: 0; gap: 4px; padding: 8px; border-bottom: 1px solid var(--ed-line); border-radius: 0; background: var(--ed-panel); }
246
+ .gg-editor-ui .ed-panel-tabs > button { flex: 1; font-size: 12px; height: 32px; }
247
+ .gg-editor-ui .ed-panel-tabs > [data-state="active"] { background: var(--ed-accent-soft); color: var(--ed-accent); box-shadow: none; }
248
+ .gg-editor-ui .ed-panel-tab-content { margin: 0; min-height: 0; flex: 1; display: flex; }
249
+ .gg-editor-ui .ed-panel-tab-content[data-state="inactive"] { display: none; }
250
+ .gg-editor-ui .ed-resize-handle { width: 6px; background-color: transparent; }
251
+ .gg-editor-ui .ed-resize-handle:hover, .gg-editor-ui .ed-resize-handle:focus-visible { background-color: var(--ed-accent); }
252
+ .gg-editor-ui .ed-layer-selected { box-shadow: inset 3px 0 var(--ed-accent); }
253
+ .gg-editor-ui .ed-layer-selected :is(.text-white, .text-blue-100, .text-blue-200) { color: var(--ed-accent); }
254
+ .gg-editor-ui [data-property-panel] input:not([type="color"]), .gg-editor-ui [data-property-panel] select { min-height: 28px; }
255
+ .gg-editor-ui [data-property-panel] button[data-state] { min-height: 32px; }
256
+ .gg-editor-ui [data-property-panel] .ed-panel-heading { position: sticky; top: 0; z-index: 1; }
257
+ /* ポータル自身も配色スコープ。利用側にToasterがある場合もその設定を奪わない。 */
258
+ .gg-editor-ui[data-editor-surface="popup"], .gg-editor-ui[data-editor-surface="dialog"], .gg-editor-ui[data-editor-surface="tooltip"] {
259
+ 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;
260
+ }
261
+ .gg-editor-ui[data-editor-surface="popup"] { max-width: calc(100vw - 24px); }
262
+ .gg-editor-ui[data-editor-surface="dialog"] { max-width: min(var(--container-7xl, 80rem), calc(100vw - 48px)); max-height: calc(100dvh - 48px); overflow-y: auto; border-radius: 16px; }
263
+ .gg-editor-ui[data-editor-surface="dialog"].max-w-lg { max-width: min(32rem, calc(100vw - 48px)); }
264
+ .gg-editor-ui[data-editor-surface="overlay"] { background: #10182766; z-index: 9999; }
265
+ .gg-editor-ui[data-editor-surface="tooltip"] { border: 1px solid var(--ed-line); font-size: 12px; }
266
+ .gg-editor-ui [role="menuitem"], .gg-editor-ui [role="menuitemcheckbox"], .gg-editor-ui [role="menuitemradio"] { min-height: 32px; font-size: 13px; }
267
+ .gg-editor-ui :is([role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"])[data-highlighted]:not([data-editor-preview] *) { background-color: var(--ed-accent-soft); color: var(--ed-accent); }
268
+ .gg-editor-ui[data-editor-surface="dialog"] > button:last-child { min-width: 28px; min-height: 28px; display: flex; align-items: center; justify-content: center; }
269
+ .gg-editor-ui .ed-comments-panel { background: var(--ed-surface); }
270
+ .gg-editor-ui .ed-comment-thread p { overflow-wrap: anywhere; }
271
+ .gg-editor-ui .ed-comment-thread button { min-height: 28px; }
272
+ .gg-editor-ui .ed-comments-panel textarea { padding: 12px; border-radius: 8px; min-height: 88px; line-height: 1.6; }
273
+ .gg-editor-ui .ed-comment-thread > div:first-child > button { min-width: 28px; }
274
+ .gg-editor-ui .ed-ribbon [role="tab"] { height: 40px; font-size: 13px; padding-inline: 12px; }
275
+ .gg-editor-ui .ed-ribbon [role="tabpanel"] { padding-inline: 16px; gap: 4px; }
276
+ .gg-editor-ui .ed-ribbon [role="tabpanel"] button { min-height: 28px; }
277
+ .gg-editor-ui .ed-ribbon [role="tabpanel"] input:not([type="color"]), .gg-editor-ui .ed-ribbon [role="tabpanel"] select { min-height: 28px; }
278
+ .gg-editor-ui .ed-ribbon [role="tab"][data-state="active"] { color: var(--ed-accent); }
279
+
280
+ /* 入力の識別に必要な境界は、レイアウトの薄い区切り線とは分ける。 */
281
+ .gg-editor-ui :is(input:not([type="color"]), textarea, select):not([data-editor-preview] *) { border-color: var(--ed-line-strong) !important; }
@@ -8,6 +8,8 @@
8
8
  * Firestore Timestamps are converted on read/write operations.
9
9
  */
10
10
 
11
+ import type { EditorPartDef } from '../io';
12
+
11
13
  // =============================================================================
12
14
  // 1. Overridable Properties
13
15
  // =============================================================================
@@ -928,11 +930,31 @@ export interface EditorComponentsActions {
928
930
  clearNavigationRequest: () => void;
929
931
  }
930
932
 
933
+ /**
934
+ * 部品(HTML の template)モードの操作。io.loadParts が渡されているときだけ意味を持つ。
935
+ * 詳細は src/editor/parts.ts。
936
+ */
937
+ export interface EditorPartsActions {
938
+ /** io.loadParts が渡されているか。true のとき部品パネルは HTML 部品を使い、挿入は実体化になる */
939
+ partsMode: boolean;
940
+ getPartDef: (id: string) => EditorPartDef | null;
941
+ /** 定義を実体化した要素(まだページには入っていない)。定義が無ければ null */
942
+ materializePartInstance: (id: string, doc: Document) => HTMLElement | null;
943
+ /** 選択要素を新しい部品として保存し、その要素をその場でインスタンスにする */
944
+ savePartFromElement: (
945
+ el: HTMLElement,
946
+ meta: { name: string; category?: string; description?: string }
947
+ ) => Promise<EditorPartDef>;
948
+ /** インスタンスの今の姿で定義を更新する(版 +1)。インスタンスでなければ null */
949
+ updatePartFromElement: (el: HTMLElement) => Promise<EditorPartDef | null>;
950
+ }
951
+
931
952
  /**
932
953
  * Combined context value type.
933
954
  */
934
955
  export type EditorComponentsContextValue = EditorComponentsState &
935
- EditorComponentsActions;
956
+ EditorComponentsActions &
957
+ EditorPartsActions;
936
958
 
937
959
  // =============================================================================
938
960
  // Alias for backward compatibility