@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.
- package/LICENSE +21 -0
- package/NOTICE +52 -0
- package/README.md +143 -0
- package/dist/editor.css +2 -0
- package/package.json +94 -0
- package/src/components/DeckSlideRender.tsx +21 -0
- package/src/components/SaveNote.tsx +21 -0
- package/src/components/auth/AuthProvider.tsx +44 -0
- package/src/components/ui/accordion.tsx +57 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +11 -0
- package/src/components/ui/context-menu.tsx +200 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/scrubbable-label.tsx +95 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/viewer/useDeck.ts +58 -0
- package/src/editor/EditorContext.tsx +632 -0
- package/src/editor/EditorToolbar.tsx +446 -0
- package/src/editor/FrontendVisualEditor.tsx +2595 -0
- package/src/editor/autosave.ts +34 -0
- package/src/editor/components/AiPromptPopover.tsx +378 -0
- package/src/editor/components/BreakpointGuides.tsx +323 -0
- package/src/editor/components/BreakpointSelector.tsx +180 -0
- package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
- package/src/editor/components/CssEditorDialog.tsx +168 -0
- package/src/editor/components/EditorCanvas.tsx +987 -0
- package/src/editor/components/EditorContextMenu.tsx +515 -0
- package/src/editor/components/EditorFooter.tsx +81 -0
- package/src/editor/components/EditorHeader.tsx +524 -0
- package/src/editor/components/EditorLayerPanel.tsx +1197 -0
- package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
- package/src/editor/components/EditorSidebar.tsx +226 -0
- package/src/editor/components/FigmaColorPicker.tsx +1577 -0
- package/src/editor/components/HtmlEditorDialog.tsx +71 -0
- package/src/editor/components/HtmlImportDialog.tsx +346 -0
- package/src/editor/components/JsEditorDialog.tsx +175 -0
- package/src/editor/components/LayerTreeItem.tsx +494 -0
- package/src/editor/components/MasterComponentEditor.tsx +846 -0
- package/src/editor/components/MediaLibraryDialog.tsx +447 -0
- package/src/editor/components/PageSettingsDialog.tsx +776 -0
- package/src/editor/components/VariablePicker.tsx +355 -0
- package/src/editor/components/VariablesDialog.tsx +564 -0
- package/src/editor/components/VariantEditor.tsx +687 -0
- package/src/editor/components/VariantMatrix.tsx +507 -0
- package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
- package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
- package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
- package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
- package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
- package/src/editor/components/component-panel/VariantList.tsx +260 -0
- package/src/editor/components/component-panel/index.ts +12 -0
- package/src/editor/components/index.ts +48 -0
- package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
- package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
- package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
- package/src/editor/components/multi-page/PageLabel.tsx +34 -0
- package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
- package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
- package/src/editor/components/multi-page/index.ts +7 -0
- package/src/editor/components/ppt/PptChrome.tsx +2508 -0
- package/src/editor/components/ppt/PptComments.tsx +589 -0
- package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
- package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
- package/src/editor/components/ppt/PptNotes.tsx +246 -0
- package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
- package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
- package/src/editor/components/property-panel/BorderSection.tsx +104 -0
- package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
- package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
- package/src/editor/components/property-panel/EffectSection.tsx +157 -0
- package/src/editor/components/property-panel/FillSection.tsx +69 -0
- package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
- package/src/editor/components/property-panel/ImageSection.tsx +206 -0
- package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
- package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
- package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
- package/src/editor/components/property-panel/LinkSection.tsx +103 -0
- package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
- package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
- package/src/editor/components/property-panel/TypographySection.tsx +230 -0
- package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
- package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
- package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
- package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
- package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
- package/src/editor/components/property-panel/index.ts +24 -0
- package/src/editor/components/property-panel/unit-utils.ts +261 -0
- package/src/editor/components/shell/EditorTopBar.tsx +392 -0
- package/src/editor/components/shell/LeftPanel.tsx +47 -0
- package/src/editor/components/shell/PagesPanel.tsx +118 -0
- package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
- package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
- package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
- package/src/editor/components/variables-panel/index.ts +7 -0
- package/src/editor/constants.ts +468 -0
- package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
- package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
- package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
- package/src/editor/contexts/EditorRefsContext.tsx +204 -0
- package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
- package/src/editor/contexts/EditorToolContext.tsx +58 -0
- package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
- package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
- package/src/editor/contexts/EditorViewContext.tsx +101 -0
- package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
- package/src/editor/contexts/index.ts +17 -0
- package/src/editor/editor-skin.css +122 -0
- package/src/editor/hooks/index.ts +38 -0
- package/src/editor/hooks/useAiReplace.ts +397 -0
- package/src/editor/hooks/useAltMeasure.ts +67 -0
- package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
- package/src/editor/hooks/useCanvasControls.ts +1222 -0
- package/src/editor/hooks/useComponentEditMode.ts +658 -0
- package/src/editor/hooks/useComponentInstances.ts +515 -0
- package/src/editor/hooks/useContextMenuHandler.ts +157 -0
- package/src/editor/hooks/useCoordinateTransform.ts +207 -0
- package/src/editor/hooks/useDragResize.ts +1317 -0
- package/src/editor/hooks/useDrawingMode.ts +472 -0
- package/src/editor/hooks/useEditorColors.ts +144 -0
- package/src/editor/hooks/useEditorMessages.ts +108 -0
- package/src/editor/hooks/useElementActions.ts +1754 -0
- package/src/editor/hooks/useElementSelection.ts +1573 -0
- package/src/editor/hooks/useFocusManagement.ts +249 -0
- package/src/editor/hooks/useGoogleFonts.ts +305 -0
- package/src/editor/hooks/useIframeInitializer.ts +584 -0
- package/src/editor/hooks/useIframeSetup.ts +360 -0
- package/src/editor/hooks/useImageUpload.ts +270 -0
- package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
- package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
- package/src/editor/hooks/useMarqueeSelection.ts +388 -0
- package/src/editor/hooks/useMediaLibrary.ts +129 -0
- package/src/editor/hooks/usePageSettingsManager.ts +739 -0
- package/src/editor/hooks/useResizablePanel.ts +163 -0
- package/src/editor/hooks/useRichPaste.ts +255 -0
- package/src/editor/hooks/useTouchGestures.ts +197 -0
- package/src/editor/index.ts +27 -0
- package/src/editor/types/page-settings.ts +513 -0
- package/src/editor/types.ts +382 -0
- package/src/editor/utils/align-elements.ts +169 -0
- package/src/editor/utils/api-json.ts +21 -0
- package/src/editor/utils/aspect-lock.ts +50 -0
- package/src/editor/utils/component-renderer.ts +702 -0
- package/src/editor/utils/component-sync.ts +845 -0
- package/src/editor/utils/crop-mode.ts +265 -0
- package/src/editor/utils/dom-utils.ts +2238 -0
- package/src/editor/utils/element-effects.ts +218 -0
- package/src/editor/utils/eyedropper.ts +35 -0
- package/src/editor/utils/figma-export.ts +560 -0
- package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
- package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
- package/src/editor/utils/figma-paste.ts +2262 -0
- package/src/editor/utils/flex-reorder.ts +231 -0
- package/src/editor/utils/flex-utils.ts +1230 -0
- package/src/editor/utils/geometry.ts +232 -0
- package/src/editor/utils/grid-layout.ts +210 -0
- package/src/editor/utils/html-utils.ts +470 -0
- package/src/editor/utils/index.ts +11 -0
- package/src/editor/utils/ink-style.ts +57 -0
- package/src/editor/utils/inline-format.ts +236 -0
- package/src/editor/utils/measure-distance.ts +120 -0
- package/src/editor/utils/override-apply.ts +526 -0
- package/src/editor/utils/override-detection.ts +613 -0
- package/src/editor/utils/paste-processors.ts +609 -0
- package/src/editor/utils/paste-sanitizer.ts +244 -0
- package/src/editor/utils/restore-flow.ts +188 -0
- package/src/editor/utils/shape-library.ts +171 -0
- package/src/editor/utils/slide-root.ts +29 -0
- package/src/editor/utils/smart-guides.ts +218 -0
- package/src/editor/utils/style-utils.ts +685 -0
- package/src/editor/utils/table-edit.ts +151 -0
- package/src/editor/utils/tailwind-mappings.ts +778 -0
- package/src/editor/utils/tailwind-utils.ts +804 -0
- package/src/editor/utils/text-highlight.ts +216 -0
- package/src/editor/utils/theme-to-variables.ts +341 -0
- package/src/editor/utils/variant-resolver.ts +456 -0
- package/src/editor/utils/viewport-utils.ts +295 -0
- package/src/hooks/useEditorHistory.ts +195 -0
- package/src/hooks/useEditorShortcuts.ts +260 -0
- package/src/index.ts +40 -0
- package/src/io.ts +134 -0
- package/src/lib/agent/slide-agent/types.ts +547 -0
- package/src/lib/agent/website-agent/types.ts +805 -0
- package/src/lib/api/auth-fetch.ts +58 -0
- package/src/lib/deck.ts +69 -0
- package/src/lib/export.ts +47 -0
- package/src/lib/firebase/config.ts +29 -0
- package/src/lib/firebase/css-variables.ts +378 -0
- package/src/lib/firebase/editor-components.ts +677 -0
- package/src/lib/firebase/storage.ts +469 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/editor.css +52 -0
- package/src/styles/skin.css +122 -0
- package/src/types/css-variables.ts +645 -0
- package/src/types/editor-components.ts +944 -0
- package/src/types/editor.ts +420 -0
- package/src/types/page-master.ts +197 -0
- package/src/types/page.ts +419 -0
- package/src/types/slide.ts +402 -0
- package/src/types/website-theme.ts +788 -0
- package/src/types/website.ts +374 -0
- package/src/vendor/firebase-firestore.ts +13 -0
- package/src/vendor/firebase-functions.ts +17 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* フロントエンドエディタの型定義
|
|
3
|
+
* (スライド、ページ、コンポーネント等で共用)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 再エクスポート(既存の型)
|
|
7
|
+
export type {
|
|
8
|
+
EditorTool,
|
|
9
|
+
SerializedElement,
|
|
10
|
+
ClipboardData,
|
|
11
|
+
DrawingState,
|
|
12
|
+
BoundingBox,
|
|
13
|
+
EditorToolbarProps,
|
|
14
|
+
} from '../types/editor';
|
|
15
|
+
|
|
16
|
+
export { DEFAULT_SHAPE_STYLES, MOVEMENT_AMOUNTS, TOOL_CONFIGS } from '../types/editor';
|
|
17
|
+
|
|
18
|
+
// DOMツリーノード
|
|
19
|
+
export interface DOMTreeNode {
|
|
20
|
+
id: string;
|
|
21
|
+
tagName: string;
|
|
22
|
+
className: string;
|
|
23
|
+
text: string;
|
|
24
|
+
children: DOMTreeNode[];
|
|
25
|
+
visible: boolean;
|
|
26
|
+
depth: number;
|
|
27
|
+
/** コンポーネントインスタンスID (該当する場合) */
|
|
28
|
+
componentInstanceId?: string;
|
|
29
|
+
/** マスターコンポーネントID (該当する場合) */
|
|
30
|
+
masterComponentId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 選択された要素の情報
|
|
34
|
+
export interface SelectedElementInfo {
|
|
35
|
+
id: string;
|
|
36
|
+
tagName: string;
|
|
37
|
+
text: string;
|
|
38
|
+
// Typography
|
|
39
|
+
fontSize: number;
|
|
40
|
+
fontFamily: string;
|
|
41
|
+
fontWeight: string;
|
|
42
|
+
lineHeight: string;
|
|
43
|
+
letterSpacing: string;
|
|
44
|
+
textAlign: string;
|
|
45
|
+
textDecoration: string;
|
|
46
|
+
fontStyle: string;
|
|
47
|
+
color: string;
|
|
48
|
+
// Layout
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
widthAuto: boolean; // width: auto かどうか
|
|
54
|
+
heightAuto: boolean; // height: auto かどうか
|
|
55
|
+
rotation: number;
|
|
56
|
+
// Transform (flip)
|
|
57
|
+
scaleX: number; // 1 or -1 (horizontal flip)
|
|
58
|
+
scaleY: number; // 1 or -1 (vertical flip)
|
|
59
|
+
// Padding
|
|
60
|
+
paddingTop: number;
|
|
61
|
+
paddingRight: number;
|
|
62
|
+
paddingBottom: number;
|
|
63
|
+
paddingLeft: number;
|
|
64
|
+
paddingLinked: boolean; // すべてのpaddingが同じかどうか
|
|
65
|
+
// Margin
|
|
66
|
+
marginTop: number;
|
|
67
|
+
marginRight: number;
|
|
68
|
+
marginBottom: number;
|
|
69
|
+
marginLeft: number;
|
|
70
|
+
// Background
|
|
71
|
+
backgroundColor: string;
|
|
72
|
+
backgroundImage: string;
|
|
73
|
+
backgroundSize: string;
|
|
74
|
+
backgroundPosition: string;
|
|
75
|
+
backgroundRepeat: string;
|
|
76
|
+
opacity: number;
|
|
77
|
+
// Image fill settings
|
|
78
|
+
hasBackgroundImage: boolean;
|
|
79
|
+
imageFillMode: ImageFillMode;
|
|
80
|
+
imagePositionX: number;
|
|
81
|
+
imagePositionY: number;
|
|
82
|
+
imageScale: number;
|
|
83
|
+
// Border
|
|
84
|
+
borderWidth: number;
|
|
85
|
+
borderColor: string;
|
|
86
|
+
borderStyle: string;
|
|
87
|
+
borderRadius: number;
|
|
88
|
+
borderRadiusTopLeft: number;
|
|
89
|
+
borderRadiusTopRight: number;
|
|
90
|
+
borderRadiusBottomRight: number;
|
|
91
|
+
borderRadiusBottomLeft: number;
|
|
92
|
+
// Shadow
|
|
93
|
+
boxShadow: string;
|
|
94
|
+
hasShadow: boolean;
|
|
95
|
+
shadowX: number;
|
|
96
|
+
shadowY: number;
|
|
97
|
+
shadowBlur: number;
|
|
98
|
+
shadowSpread: number;
|
|
99
|
+
shadowColor: string;
|
|
100
|
+
// Filter effects
|
|
101
|
+
filter: string;
|
|
102
|
+
filterBlur: number; // blur(Xpx)
|
|
103
|
+
filterBrightness: number; // brightness(X)
|
|
104
|
+
filterContrast: number; // contrast(X)
|
|
105
|
+
filterGrayscale: number; // grayscale(X)
|
|
106
|
+
filterSaturate: number; // saturate(X)
|
|
107
|
+
filterSepia: number; // sepia(X)
|
|
108
|
+
filterHueRotate: number; // hue-rotate(Xdeg)
|
|
109
|
+
filterInvert: number; // invert(X)
|
|
110
|
+
mixBlendMode: string;
|
|
111
|
+
// Backdrop filter
|
|
112
|
+
backdropFilter: string;
|
|
113
|
+
backdropBlur: number; // backdrop-filter: blur(Xpx)
|
|
114
|
+
// Overflow
|
|
115
|
+
overflow: string; // visible, hidden, scroll, auto
|
|
116
|
+
// Transform settings
|
|
117
|
+
transformOrigin: string; // transform-origin
|
|
118
|
+
// Auto Layout (Flexbox) settings
|
|
119
|
+
display: string; // block, flex, grid
|
|
120
|
+
flexDirection: string; // row, column, row-reverse, column-reverse
|
|
121
|
+
flexWrap: string; // nowrap, wrap, wrap-reverse
|
|
122
|
+
justifyContent: string; // flex-start, center, flex-end, space-between, space-around, space-evenly
|
|
123
|
+
alignItems: string; // flex-start, center, flex-end, stretch, baseline
|
|
124
|
+
gap: number; // gap between items
|
|
125
|
+
// Child flex properties
|
|
126
|
+
flexGrow: number; // flex-grow
|
|
127
|
+
flexShrink: number; // flex-shrink
|
|
128
|
+
flexBasis: string; // flex-basis (auto, px, %)
|
|
129
|
+
alignSelf: string; // auto, flex-start, center, flex-end, stretch
|
|
130
|
+
// CSS Grid properties
|
|
131
|
+
gridTemplateColumns: string; // grid-template-columns
|
|
132
|
+
gridTemplateRows: string; // grid-template-rows
|
|
133
|
+
gridGap: number; // gap (for grid)
|
|
134
|
+
gridRowGap: number; // row-gap
|
|
135
|
+
gridColumnGap: number; // column-gap
|
|
136
|
+
gridAutoFlow: string; // grid-auto-flow: row, column, dense
|
|
137
|
+
gridAutoRows: string; // grid-auto-rows
|
|
138
|
+
gridAutoColumns: string; // grid-auto-columns
|
|
139
|
+
justifyItems: string; // justify-items: start, center, end, stretch
|
|
140
|
+
alignContent: string; // align-content: start, center, end, stretch, space-between, space-around
|
|
141
|
+
// Child grid properties
|
|
142
|
+
gridColumn: string; // grid-column (e.g., "1 / 3", "span 2")
|
|
143
|
+
gridRow: string; // grid-row (e.g., "1 / 2", "span 2")
|
|
144
|
+
gridColumnStart: number; // grid-column-start
|
|
145
|
+
gridColumnEnd: number; // grid-column-end
|
|
146
|
+
gridRowStart: number; // grid-row-start
|
|
147
|
+
gridRowEnd: number; // grid-row-end
|
|
148
|
+
justifySelf: string; // justify-self: start, center, end, stretch
|
|
149
|
+
// Parent Layout context
|
|
150
|
+
parentDisplay: string; // 親要素のdisplayプロパティ
|
|
151
|
+
parentFlexDirection: string; // 親要素のflex-direction
|
|
152
|
+
// Link properties (for <a> tags)
|
|
153
|
+
isLink: boolean;
|
|
154
|
+
/** [移植時の追加] <img>要素のsrc(画像差し替えUI用) */
|
|
155
|
+
imageSrc?: string; // この要素またはその親が<a>タグかどうか
|
|
156
|
+
linkHref: string; // href属性値
|
|
157
|
+
linkTarget: string; // target属性値 (_self, _blank, _parent, _top)
|
|
158
|
+
linkTitle: string; // title属性値
|
|
159
|
+
// CSS Variable references (raw inline style values if they contain var())
|
|
160
|
+
rawWidth?: string; // width (e.g., "var(--spacing-lg)")
|
|
161
|
+
rawHeight?: string; // height
|
|
162
|
+
rawPaddingTop?: string; // padding-top
|
|
163
|
+
rawPaddingRight?: string; // padding-right
|
|
164
|
+
rawPaddingBottom?: string; // padding-bottom
|
|
165
|
+
rawPaddingLeft?: string; // padding-left
|
|
166
|
+
rawMarginTop?: string; // margin-top
|
|
167
|
+
rawMarginRight?: string; // margin-right
|
|
168
|
+
rawMarginBottom?: string; // margin-bottom
|
|
169
|
+
rawMarginLeft?: string; // margin-left
|
|
170
|
+
rawBackgroundColor?: string; // background-color (e.g., "var(--color-primary)")
|
|
171
|
+
rawColor?: string; // color (text color)
|
|
172
|
+
rawBorderColor?: string; // border-color
|
|
173
|
+
// Additional CSS Variable references
|
|
174
|
+
rawBorderRadius?: string; // border-radius
|
|
175
|
+
rawBorderTopLeftRadius?: string;
|
|
176
|
+
rawBorderTopRightRadius?: string;
|
|
177
|
+
rawBorderBottomLeftRadius?: string;
|
|
178
|
+
rawBorderBottomRightRadius?: string;
|
|
179
|
+
rawOpacity?: string; // opacity
|
|
180
|
+
rawFontSize?: string; // font-size
|
|
181
|
+
rawLineHeight?: string; // line-height
|
|
182
|
+
rawLetterSpacing?: string; // letter-spacing
|
|
183
|
+
rawBorderWidth?: string; // border-width
|
|
184
|
+
rawGap?: string; // gap (flexbox/grid)
|
|
185
|
+
rawLeft?: string; // left (position)
|
|
186
|
+
rawTop?: string; // top (position)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 画像フィルモード
|
|
190
|
+
export type ImageFillMode = 'fill' | 'fit' | 'crop' | 'tile';
|
|
191
|
+
|
|
192
|
+
// プロパティパネルのセクション開閉状態
|
|
193
|
+
export interface PanelSections {
|
|
194
|
+
position: boolean; // 位置: 配置、XY座標、回転
|
|
195
|
+
layout: boolean; // レイアウト: オートレイアウト、サイズ、クリッピング
|
|
196
|
+
appearance: boolean; // 外見: 不透明度、角丸
|
|
197
|
+
image: boolean;
|
|
198
|
+
typography: boolean;
|
|
199
|
+
link: boolean; // リンク設定(<a>タグ)
|
|
200
|
+
fill: boolean;
|
|
201
|
+
stroke: boolean;
|
|
202
|
+
effects: boolean;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* スタイルクリップボード(Figmaライクなスタイルコピー&ペースト用)
|
|
207
|
+
* 位置・サイズ以外のスタイルプロパティを保持
|
|
208
|
+
*/
|
|
209
|
+
export interface StyleClipboard {
|
|
210
|
+
// タイポグラフィ
|
|
211
|
+
fontSize?: number;
|
|
212
|
+
fontFamily?: string;
|
|
213
|
+
fontWeight?: string;
|
|
214
|
+
lineHeight?: string;
|
|
215
|
+
letterSpacing?: string;
|
|
216
|
+
textAlign?: string;
|
|
217
|
+
textDecoration?: string;
|
|
218
|
+
fontStyle?: string;
|
|
219
|
+
color?: string;
|
|
220
|
+
|
|
221
|
+
// 背景・塗り
|
|
222
|
+
backgroundColor?: string;
|
|
223
|
+
backgroundImage?: string;
|
|
224
|
+
backgroundSize?: string;
|
|
225
|
+
backgroundPosition?: string;
|
|
226
|
+
backgroundRepeat?: string;
|
|
227
|
+
opacity?: number;
|
|
228
|
+
|
|
229
|
+
// ボーダー
|
|
230
|
+
borderWidth?: number;
|
|
231
|
+
borderColor?: string;
|
|
232
|
+
borderStyle?: string;
|
|
233
|
+
borderRadius?: number;
|
|
234
|
+
borderRadiusTopLeft?: number;
|
|
235
|
+
borderRadiusTopRight?: number;
|
|
236
|
+
borderRadiusBottomRight?: number;
|
|
237
|
+
borderRadiusBottomLeft?: number;
|
|
238
|
+
|
|
239
|
+
// シャドウ
|
|
240
|
+
boxShadow?: string;
|
|
241
|
+
|
|
242
|
+
// フィルター
|
|
243
|
+
filter?: string;
|
|
244
|
+
mixBlendMode?: string;
|
|
245
|
+
backdropFilter?: string;
|
|
246
|
+
|
|
247
|
+
// パディング
|
|
248
|
+
paddingTop?: number;
|
|
249
|
+
paddingRight?: number;
|
|
250
|
+
paddingBottom?: number;
|
|
251
|
+
paddingLeft?: number;
|
|
252
|
+
|
|
253
|
+
// Flexbox(オートレイアウト)
|
|
254
|
+
display?: string;
|
|
255
|
+
flexDirection?: string;
|
|
256
|
+
flexWrap?: string;
|
|
257
|
+
justifyContent?: string;
|
|
258
|
+
alignItems?: string;
|
|
259
|
+
gap?: number;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// エディタのメッセージタイプ
|
|
263
|
+
export type EditorMessageType =
|
|
264
|
+
| 'SLIDE_CONTENT_CHANGED'
|
|
265
|
+
| 'ELEMENT_SELECTED'
|
|
266
|
+
| 'ELEMENT_DESELECTED'
|
|
267
|
+
| 'TOOL_FINISHED'
|
|
268
|
+
| 'DOM_TREE_UPDATED';
|
|
269
|
+
|
|
270
|
+
// エディタのメッセージ
|
|
271
|
+
export interface EditorMessage {
|
|
272
|
+
type: EditorMessageType;
|
|
273
|
+
html?: string;
|
|
274
|
+
element?: SelectedElementInfo;
|
|
275
|
+
tree?: DOMTreeNode[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ドラッグ状態
|
|
279
|
+
export interface DragState {
|
|
280
|
+
element: HTMLElement | null;
|
|
281
|
+
elements: HTMLElement[]; // 複数選択時のドラッグ用
|
|
282
|
+
startX: number;
|
|
283
|
+
startY: number;
|
|
284
|
+
origLeft: number;
|
|
285
|
+
origTop: number;
|
|
286
|
+
origPositions: { left: number; top: number }[]; // 複数選択時の元位置
|
|
287
|
+
isDragging: boolean;
|
|
288
|
+
hasMoved: boolean;
|
|
289
|
+
// Flex reorder mode (auto-layout)
|
|
290
|
+
flexReorderMode: boolean;
|
|
291
|
+
flexParent: HTMLElement | null;
|
|
292
|
+
originalIndex: number;
|
|
293
|
+
targetIndex: number;
|
|
294
|
+
// Enhanced auto-layout drag (hierarchy change support)
|
|
295
|
+
autoLayoutDragMode: boolean; // オートレイアウトドラッグモード
|
|
296
|
+
dragGhost: HTMLElement | null; // ドラッグゴースト(カーソル追従する縮小コピー)
|
|
297
|
+
originalParent: HTMLElement | null; // ドラッグ元の親要素
|
|
298
|
+
currentDropTarget: HTMLElement | null; // 現在のドロップターゲット
|
|
299
|
+
dropPosition: 'before' | 'after' | 'inside' | null; // ドロップ位置
|
|
300
|
+
dropIndex: number; // ドロップ先のインデックス
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// リサイズ状態
|
|
304
|
+
export interface ResizeState {
|
|
305
|
+
isResizing: boolean;
|
|
306
|
+
isRotating: boolean; // 回転モード
|
|
307
|
+
element: HTMLElement | null;
|
|
308
|
+
elements: HTMLElement[]; // 複数選択時の対象要素
|
|
309
|
+
handle: string;
|
|
310
|
+
startX: number;
|
|
311
|
+
startY: number;
|
|
312
|
+
origLeft: number;
|
|
313
|
+
origTop: number;
|
|
314
|
+
origWidth: number;
|
|
315
|
+
origHeight: number;
|
|
316
|
+
origRadius: number;
|
|
317
|
+
// 複数選択時のバウンディングボックスと各要素の初期状態
|
|
318
|
+
selectionBounds: {
|
|
319
|
+
left: number;
|
|
320
|
+
top: number;
|
|
321
|
+
width: number;
|
|
322
|
+
height: number;
|
|
323
|
+
right: number;
|
|
324
|
+
bottom: number;
|
|
325
|
+
} | null;
|
|
326
|
+
origElementStates: {
|
|
327
|
+
left: number;
|
|
328
|
+
top: number;
|
|
329
|
+
width: number;
|
|
330
|
+
height: number;
|
|
331
|
+
}[];
|
|
332
|
+
// Scale Tool用状態
|
|
333
|
+
origScaleX: number;
|
|
334
|
+
origScaleY: number;
|
|
335
|
+
rotation: number;
|
|
336
|
+
// 回転用状態
|
|
337
|
+
rotationStartAngle: number;
|
|
338
|
+
centerX: number;
|
|
339
|
+
centerY: number;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 要素キャプチャ(絶対配置変換用)
|
|
343
|
+
export interface ElementCapture {
|
|
344
|
+
element: HTMLElement;
|
|
345
|
+
rect: DOMRect;
|
|
346
|
+
parent: HTMLElement | null;
|
|
347
|
+
parentRect: DOMRect | null;
|
|
348
|
+
computedStyle: CSSStyleDeclaration;
|
|
349
|
+
parentComputedStyle: CSSStyleDeclaration | null;
|
|
350
|
+
// 拡張情報(改善版変換用)
|
|
351
|
+
originalTransform?: string; // 元のtransform
|
|
352
|
+
scale?: number; // iframeのズームスケール
|
|
353
|
+
/** 変換前の style 属性(自己検証で崩れを検知したとき巻き戻すため) */
|
|
354
|
+
prevStyle?: string | null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// FrontendVisualEditorのProps
|
|
358
|
+
export interface FrontendVisualEditorProps {
|
|
359
|
+
html: string;
|
|
360
|
+
/** コンテンツID(スライドID、ページID等) */
|
|
361
|
+
contentId?: string;
|
|
362
|
+
/** 親ID(プレゼンテーションID、プロジェクトID等) */
|
|
363
|
+
parentId?: string;
|
|
364
|
+
/** @deprecated Use contentId instead */
|
|
365
|
+
slideId?: string;
|
|
366
|
+
/** @deprecated Use parentId instead */
|
|
367
|
+
presentationId?: string;
|
|
368
|
+
onSave: (html: string) => Promise<void>;
|
|
369
|
+
onClose: () => void;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** @deprecated Use FrontendVisualEditorProps instead */
|
|
373
|
+
export type SlideVisualEditorProps = FrontendVisualEditorProps;
|
|
374
|
+
|
|
375
|
+
// マーキー選択(範囲選択)の状態
|
|
376
|
+
export interface MarqueeState {
|
|
377
|
+
isActive: boolean;
|
|
378
|
+
startX: number; // キャンバス座標
|
|
379
|
+
startY: number;
|
|
380
|
+
currentX: number;
|
|
381
|
+
currentY: number;
|
|
382
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 選択要素の配置(整列)と等間隔配置。
|
|
3
|
+
*
|
|
4
|
+
* 【なぜ offset* を使うか】
|
|
5
|
+
* アートボードはズームのために CSS transform で拡縮している。getBoundingClientRect() は
|
|
6
|
+
* その拡縮が乗った画面pxを返すので、書き込み先の style.left(素のアートボードpx)と
|
|
7
|
+
* 単位が食い違う。offsetLeft/offsetTop/offsetWidth/offsetHeight は transform の影響を
|
|
8
|
+
* 受けないレイアウト値なので、実測と書き込みの単位が必ず一致する。
|
|
9
|
+
*
|
|
10
|
+
* 【基準の決め方】
|
|
11
|
+
* ・2個以上 … 選択群のバウンディングボックス(PowerPointと同じ「選択したオブジェクトを揃える」)
|
|
12
|
+
* ・1個 … アートボード(1920×1080)
|
|
13
|
+
*
|
|
14
|
+
* 【入れ子の扱い】
|
|
15
|
+
* スライドの要素は「カードの中の見出し」のように入れ子になっていることが多く、
|
|
16
|
+
* offsetLeft は要素ごとに違う親を原点にした値になる。そのまま比べると別々の
|
|
17
|
+
* ものさしで測った数字を並べることになるので、いったん**アートボード座標**へ
|
|
18
|
+
* 直して比較し、書き戻すときにその要素の原点へ戻す。
|
|
19
|
+
*
|
|
20
|
+
* 位置は style.left / style.top へ書き戻すため、動かせるのは絶対配置の要素だけ。
|
|
21
|
+
* 通常フロー(static)や relative の要素は left/top を書いても意図どおりに動かないので触らない。
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** アートボードの設計寸法(スライドは常にこの大きさ) */
|
|
25
|
+
const ARTBOARD_W = 1920;
|
|
26
|
+
const ARTBOARD_H = 1080;
|
|
27
|
+
|
|
28
|
+
export type AlignMode = 'left' | 'hcenter' | 'right' | 'top' | 'vcenter' | 'bottom';
|
|
29
|
+
export type DistributeAxis = 'h' | 'v';
|
|
30
|
+
|
|
31
|
+
/** 実測した1要素。位置はアートボード座標、原点はその要素の left/top の起点 */
|
|
32
|
+
type Box = {
|
|
33
|
+
el: HTMLElement;
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
/** この要素の left/top が測られている原点(アートボード座標) */
|
|
39
|
+
originX: number;
|
|
40
|
+
originY: number;
|
|
41
|
+
/** left/top を書いて動かせるか。動かせない要素も「選択群の広さ」には数える */
|
|
42
|
+
movable: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** left/top を書いて動かせる要素か(絶対配置のみ) */
|
|
46
|
+
function isMovable(el: HTMLElement): boolean {
|
|
47
|
+
const pos = el.ownerDocument.defaultView?.getComputedStyle(el).position;
|
|
48
|
+
return pos === 'absolute' || pos === 'fixed';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** アートボードの左上から見た位置。offsetParent を遡って足し上げる */
|
|
52
|
+
function artboardOffset(el: HTMLElement): { x: number; y: number } {
|
|
53
|
+
let x = 0;
|
|
54
|
+
let y = 0;
|
|
55
|
+
let node: HTMLElement | null = el;
|
|
56
|
+
while (node) {
|
|
57
|
+
x += node.offsetLeft;
|
|
58
|
+
y += node.offsetTop;
|
|
59
|
+
const parent = node.offsetParent as HTMLElement | null;
|
|
60
|
+
if (!parent || parent.id === 'artboard') break;
|
|
61
|
+
node = parent;
|
|
62
|
+
}
|
|
63
|
+
return { x, y };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 選択要素をアートボード座標で実測する。
|
|
68
|
+
* 動かせない要素も返すのは、基準になる「選択群の広さ」から外すと、
|
|
69
|
+
* 例えば2つ選んだうちの片方が動かせないときに、残り1つが
|
|
70
|
+
* アートボード基準へ勝手に切り替わってしまうため。
|
|
71
|
+
*/
|
|
72
|
+
function measure(els: HTMLElement[]): Box[] {
|
|
73
|
+
return els.map((el) => {
|
|
74
|
+
const { x, y } = artboardOffset(el);
|
|
75
|
+
return {
|
|
76
|
+
el,
|
|
77
|
+
x,
|
|
78
|
+
y,
|
|
79
|
+
width: el.offsetWidth,
|
|
80
|
+
height: el.offsetHeight,
|
|
81
|
+
originX: x - el.offsetLeft,
|
|
82
|
+
originY: y - el.offsetTop,
|
|
83
|
+
movable: isMovable(el),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** アートボード座標で指定した位置へ置く(その要素の原点に直してから書く) */
|
|
89
|
+
function setX(b: Box, x: number): void {
|
|
90
|
+
b.el.style.left = `${Math.round(x - b.originX)}px`;
|
|
91
|
+
}
|
|
92
|
+
function setY(b: Box, y: number): void {
|
|
93
|
+
b.el.style.top = `${Math.round(y - b.originY)}px`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 整列の基準になる枠(アートボード座標)。複数選択なら選択群、単一ならアートボード全面 */
|
|
97
|
+
function referenceRect(boxes: Box[]): { left: number; top: number; right: number; bottom: number } {
|
|
98
|
+
if (boxes.length > 1) {
|
|
99
|
+
return {
|
|
100
|
+
left: Math.min(...boxes.map((b) => b.x)),
|
|
101
|
+
top: Math.min(...boxes.map((b) => b.y)),
|
|
102
|
+
right: Math.max(...boxes.map((b) => b.x + b.width)),
|
|
103
|
+
bottom: Math.max(...boxes.map((b) => b.y + b.height)),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { left: 0, top: 0, right: ARTBOARD_W, bottom: ARTBOARD_H };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 指定した揃え方で要素を動かす。戻り値は実際に動かした要素。
|
|
111
|
+
* 複数選択時は選択群のバウンディングボックス、単一選択時はアートボードが基準。
|
|
112
|
+
*/
|
|
113
|
+
export function alignElements(els: HTMLElement[], mode: AlignMode): HTMLElement[] {
|
|
114
|
+
const boxes = measure(els);
|
|
115
|
+
const movable = boxes.filter((b) => b.movable);
|
|
116
|
+
if (!movable.length) return [];
|
|
117
|
+
// 基準は選択全体、動かすのは動かせるものだけ
|
|
118
|
+
const ref = referenceRect(boxes);
|
|
119
|
+
const cx = (ref.left + ref.right) / 2;
|
|
120
|
+
const cy = (ref.top + ref.bottom) / 2;
|
|
121
|
+
|
|
122
|
+
for (const b of movable) {
|
|
123
|
+
switch (mode) {
|
|
124
|
+
case 'left': setX(b, ref.left); break;
|
|
125
|
+
case 'hcenter': setX(b, cx - b.width / 2); break;
|
|
126
|
+
case 'right': setX(b, ref.right - b.width); break;
|
|
127
|
+
case 'top': setY(b, ref.top); break;
|
|
128
|
+
case 'vcenter': setY(b, cy - b.height / 2); break;
|
|
129
|
+
case 'bottom': setY(b, ref.bottom - b.height); break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return movable.map((b) => b.el);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 等間隔に配置する(3個以上)。両端の要素はそのままで、間の要素の**すき間**が
|
|
137
|
+
* 均等になるように置き直す(PowerPointの「左右に整列」と同じ)。
|
|
138
|
+
* 要素の幅がまちまちでも見た目の間隔が揃うよう、中心間ではなく辺と辺の間で割る。
|
|
139
|
+
*/
|
|
140
|
+
export function distributeElements(els: HTMLElement[], axis: DistributeAxis): HTMLElement[] {
|
|
141
|
+
// 等間隔は「並べ替える対象」そのものが要るので、動かせる要素だけで組む
|
|
142
|
+
const boxes = measure(els).filter((b) => b.movable);
|
|
143
|
+
if (boxes.length < 3) return [];
|
|
144
|
+
|
|
145
|
+
const horizontal = axis === 'h';
|
|
146
|
+
const start = (b: Box) => (horizontal ? b.x : b.y);
|
|
147
|
+
const size = (b: Box) => (horizontal ? b.width : b.height);
|
|
148
|
+
const place = horizontal ? setX : setY;
|
|
149
|
+
|
|
150
|
+
const sorted = [...boxes].sort((a, b) => start(a) - start(b));
|
|
151
|
+
const first = sorted[0];
|
|
152
|
+
const last = sorted[sorted.length - 1];
|
|
153
|
+
|
|
154
|
+
// 両端の内側に残る空きを、間の要素で分け合う
|
|
155
|
+
const span = start(last) - (start(first) + size(first));
|
|
156
|
+
const innerSize = sorted.slice(1, -1).reduce((sum, b) => sum + size(b), 0);
|
|
157
|
+
const gap = (span - innerSize) / (sorted.length - 1);
|
|
158
|
+
|
|
159
|
+
let cursor = start(first) + size(first);
|
|
160
|
+
const moved: HTMLElement[] = [];
|
|
161
|
+
for (let i = 1; i < sorted.length - 1; i++) {
|
|
162
|
+
const b = sorted[i];
|
|
163
|
+
cursor += gap;
|
|
164
|
+
place(b, cursor);
|
|
165
|
+
cursor += size(b);
|
|
166
|
+
moved.push(b.el);
|
|
167
|
+
}
|
|
168
|
+
return moved;
|
|
169
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* devサーバーのAPI応答を安全に読む。
|
|
3
|
+
*
|
|
4
|
+
* プラグイン未読込(テンプレ更新後にdevサーバーが古いまま)だと、/__ 系は
|
|
5
|
+
* 404の空応答やSPAのHTMLが返り、素の res.json() は
|
|
6
|
+
* 「Unexpected end of JSON input」で落ちて原因が伝わらない。
|
|
7
|
+
* ここで空/HTMLを検出して「サーバーの再起動が必要」という言葉に変える。
|
|
8
|
+
*/
|
|
9
|
+
export async function readApiJson<T>(res: Response): Promise<T> {
|
|
10
|
+
const text = await res.text();
|
|
11
|
+
if (!text.trim() || text.trimStart().startsWith('<')) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'開発サーバーが新しい機能を読み込んでいません。devサーバー(npm run dev)を再起動してください',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(text) as T;
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error(`サーバー応答を解釈できません: ${text.slice(0, 80)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 縦横比ロック(アスペクト比ロック)の共有ストア
|
|
3
|
+
*
|
|
4
|
+
* [なぜ Context ではなくモジュールスコープなのか]
|
|
5
|
+
* ロック状態を読む必要があるのは
|
|
6
|
+
* 1) プロパティパネル(トグル UI と W/H 入力)— React の再描画が要る
|
|
7
|
+
* 2) useDragResize のマウスハンドラ — iframe ロード時のクロージャに閉じ込められる
|
|
8
|
+
* の 2 箇所。2) は「イベントハンドラが古い値を掴む」問題があるため React state を
|
|
9
|
+
* そのまま渡せない(同ファイル冒頭に zoom で同じ注意書きがある)。
|
|
10
|
+
* 関数で最新値を読めるモジュールスコープのストアにしておけば、両方から同じ値を見られる。
|
|
11
|
+
*
|
|
12
|
+
* [なぜ永続化しないのか]
|
|
13
|
+
* Figma と同様、ロックは選択を変えても保たれるがセッションを越えては引き継がない。
|
|
14
|
+
* リロード後に「なぜか比率が固定されている」状態を復元すると事故のもとなのでメモリ内のみ。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useSyncExternalStore } from 'react';
|
|
18
|
+
|
|
19
|
+
let locked = false;
|
|
20
|
+
const listeners = new Set<() => void>();
|
|
21
|
+
|
|
22
|
+
/** 現在のロック状態(イベントハンドラからはこれを呼ぶ) */
|
|
23
|
+
export function isAspectRatioLocked(): boolean {
|
|
24
|
+
return locked;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** ロック状態を変更する(変化が無ければ通知しない) */
|
|
28
|
+
export function setAspectRatioLocked(next: boolean): void {
|
|
29
|
+
if (locked === next) return;
|
|
30
|
+
locked = next;
|
|
31
|
+
listeners.forEach((listener) => listener());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 変更購読(useSyncExternalStore 用) */
|
|
35
|
+
export function subscribeAspectRatioLock(onStoreChange: () => void): () => void {
|
|
36
|
+
listeners.add(onStoreChange);
|
|
37
|
+
return () => {
|
|
38
|
+
listeners.delete(onStoreChange);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** React からロック状態を購読する */
|
|
43
|
+
export function useAspectRatioLock(): boolean {
|
|
44
|
+
return useSyncExternalStore(
|
|
45
|
+
subscribeAspectRatioLock,
|
|
46
|
+
isAspectRatioLocked,
|
|
47
|
+
// SSR / ハイドレーション時は必ず false(サーバに状態は無い)
|
|
48
|
+
() => false,
|
|
49
|
+
);
|
|
50
|
+
}
|