@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,804 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind CSS 変換ユーティリティ
|
|
3
|
+
*
|
|
4
|
+
* CSSプロパティ値をTailwindクラスに変換し、要素に適用する
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
import {
|
|
9
|
+
TAILWIND_CONFLICT_GROUPS,
|
|
10
|
+
ENUM_MAPPINGS,
|
|
11
|
+
NUMERIC_PREFIXES,
|
|
12
|
+
COLOR_PREFIXES,
|
|
13
|
+
STYLE_ONLY_PROPERTIES,
|
|
14
|
+
SPECIAL_VALUES,
|
|
15
|
+
} from './tailwind-mappings';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 要素のクラス名を安全に取得(SVG要素対応)
|
|
19
|
+
* SVG要素のclassNameはSVGAnimatedStringオブジェクトなので、
|
|
20
|
+
* getAttribute('class')を使用して文字列として取得する
|
|
21
|
+
*/
|
|
22
|
+
function getElementClassName(element: Element): string {
|
|
23
|
+
return element.getAttribute('class') || '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 要素のクラス名を安全に設定(SVG要素対応)
|
|
28
|
+
*/
|
|
29
|
+
function setElementClassName(element: Element, className: string): void {
|
|
30
|
+
if (className) {
|
|
31
|
+
element.setAttribute('class', className);
|
|
32
|
+
} else {
|
|
33
|
+
element.removeAttribute('class');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 変換結果の型
|
|
39
|
+
*/
|
|
40
|
+
export interface ConversionResult {
|
|
41
|
+
/** 追加するTailwindクラス(nullの場合はstyle属性を使用) */
|
|
42
|
+
className: string | null;
|
|
43
|
+
/** style属性に適用する値(nullの場合はTailwindクラスを使用) */
|
|
44
|
+
styleValue: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* CSSプロパティ値をTailwindクラスに変換
|
|
49
|
+
*
|
|
50
|
+
* @param property CSSプロパティ名(camelCase)
|
|
51
|
+
* @param value CSSプロパティ値
|
|
52
|
+
* @returns 変換結果(className または styleValue のいずれか)
|
|
53
|
+
*/
|
|
54
|
+
export function cssToTailwindClass(
|
|
55
|
+
property: string,
|
|
56
|
+
value: string
|
|
57
|
+
): ConversionResult {
|
|
58
|
+
// 空値の場合はスキップ
|
|
59
|
+
if (!value || value === '') {
|
|
60
|
+
return { className: null, styleValue: null };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// CSS変数参照はstyle属性に直接設定
|
|
64
|
+
if (value.startsWith('var(')) {
|
|
65
|
+
// DEBUG: var() を検出した時のログ
|
|
66
|
+
console.log('[DEBUG tailwind-utils] cssToTailwindClass detected var():', {
|
|
67
|
+
property,
|
|
68
|
+
value,
|
|
69
|
+
result: { className: null, styleValue: value },
|
|
70
|
+
});
|
|
71
|
+
return { className: null, styleValue: value };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// style属性のみのプロパティはフォールバック
|
|
75
|
+
if (STYLE_ONLY_PROPERTIES.includes(property)) {
|
|
76
|
+
return { className: null, styleValue: value };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 1. 特殊値マッピングをチェック(auto, 100%, 50%など)
|
|
80
|
+
const specialMap = SPECIAL_VALUES[property];
|
|
81
|
+
if (specialMap && specialMap[value]) {
|
|
82
|
+
return { className: specialMap[value], styleValue: null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 2. 列挙型マッピングをチェック
|
|
86
|
+
const enumMap = ENUM_MAPPINGS[property];
|
|
87
|
+
if (enumMap && enumMap[value]) {
|
|
88
|
+
return { className: enumMap[value], styleValue: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 3. 色プロパティの処理
|
|
92
|
+
const colorPrefix = COLOR_PREFIXES[property];
|
|
93
|
+
if (colorPrefix) {
|
|
94
|
+
return convertColorValue(colorPrefix, value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 4. 数値プレフィックスの処理
|
|
98
|
+
const numericPrefix = NUMERIC_PREFIXES[property];
|
|
99
|
+
if (numericPrefix) {
|
|
100
|
+
return convertNumericValue(numericPrefix, value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 5. 特殊なプロパティの処理
|
|
104
|
+
switch (property) {
|
|
105
|
+
case 'fontFamily':
|
|
106
|
+
return convertFontFamily(value);
|
|
107
|
+
|
|
108
|
+
case 'flexGrow':
|
|
109
|
+
return {
|
|
110
|
+
className: value === '1' || value === '1' ? 'grow' : 'grow-0',
|
|
111
|
+
styleValue: null,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
case 'flexShrink':
|
|
115
|
+
return {
|
|
116
|
+
className: value === '1' || value === '1' ? 'shrink' : 'shrink-0',
|
|
117
|
+
styleValue: null,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
case 'gridTemplateColumns':
|
|
121
|
+
return convertGridTemplate('grid-cols', value);
|
|
122
|
+
|
|
123
|
+
case 'gridTemplateRows':
|
|
124
|
+
return convertGridTemplate('grid-rows', value);
|
|
125
|
+
|
|
126
|
+
default:
|
|
127
|
+
// マッピングなし - style属性にフォールバック
|
|
128
|
+
return { className: null, styleValue: value };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 色値をTailwindクラスに変換
|
|
134
|
+
*/
|
|
135
|
+
function convertColorValue(prefix: string, value: string): ConversionResult {
|
|
136
|
+
// transparent
|
|
137
|
+
if (value === 'transparent') {
|
|
138
|
+
return { className: `${prefix}-transparent`, styleValue: null };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// currentColor
|
|
142
|
+
if (value === 'currentColor' || value === 'currentcolor') {
|
|
143
|
+
return { className: `${prefix}-current`, styleValue: null };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// inherit
|
|
147
|
+
if (value === 'inherit') {
|
|
148
|
+
return { className: `${prefix}-inherit`, styleValue: null };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// HEX色はそのまま任意値として使用
|
|
152
|
+
if (value.startsWith('#')) {
|
|
153
|
+
return { className: `${prefix}-[${value}]`, styleValue: null };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// RGB/RGBA/HSL/HSLA
|
|
157
|
+
if (
|
|
158
|
+
value.startsWith('rgb') ||
|
|
159
|
+
value.startsWith('hsl') ||
|
|
160
|
+
value.startsWith('oklch') ||
|
|
161
|
+
value.startsWith('oklab')
|
|
162
|
+
) {
|
|
163
|
+
// スペースをアンダースコアに置換してTailwindクラスとして使用
|
|
164
|
+
const sanitized = value.replace(/\s+/g, '_').replace(/,/g, '_');
|
|
165
|
+
return { className: `${prefix}-[${sanitized}]`, styleValue: null };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 名前付きカラー(CSS named colors)はstyle属性にフォールバック
|
|
169
|
+
// Tailwindの標準色と一致しない可能性があるため
|
|
170
|
+
return { className: null, styleValue: value };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 数値値をTailwindクラスに変換
|
|
175
|
+
*/
|
|
176
|
+
function convertNumericValue(prefix: string, value: string): ConversionResult {
|
|
177
|
+
// 0は特別扱い
|
|
178
|
+
if (value === '0' || value === '0px') {
|
|
179
|
+
return { className: `${prefix}-0`, styleValue: null };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 負の値
|
|
183
|
+
const isNegative = value.startsWith('-');
|
|
184
|
+
const absValue = isNegative ? value.slice(1) : value;
|
|
185
|
+
|
|
186
|
+
// px値、rem値、%値、vh/vw値などはそのまま任意値として使用
|
|
187
|
+
const classValue = isNegative ? `-${prefix}-[${absValue}]` : `${prefix}-[${value}]`;
|
|
188
|
+
return { className: classValue, styleValue: null };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* font-familyをTailwindクラスに変換
|
|
193
|
+
*/
|
|
194
|
+
function convertFontFamily(value: string): ConversionResult {
|
|
195
|
+
// クォートを除去し、スペースをアンダースコアに置換
|
|
196
|
+
const sanitized = value
|
|
197
|
+
.replace(/["']/g, '')
|
|
198
|
+
.replace(/,\s*/g, ',')
|
|
199
|
+
.replace(/\s+/g, '_');
|
|
200
|
+
|
|
201
|
+
return { className: `font-[${sanitized}]`, styleValue: null };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* grid-template-columns/rowsをTailwindクラスに変換
|
|
206
|
+
*/
|
|
207
|
+
function convertGridTemplate(
|
|
208
|
+
prefix: string,
|
|
209
|
+
value: string
|
|
210
|
+
): ConversionResult {
|
|
211
|
+
// repeat(n, 1fr) パターン
|
|
212
|
+
const repeatMatch = value.match(/^repeat\((\d+),\s*1fr\)$/);
|
|
213
|
+
if (repeatMatch) {
|
|
214
|
+
return { className: `${prefix}-${repeatMatch[1]}`, styleValue: null };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// none
|
|
218
|
+
if (value === 'none') {
|
|
219
|
+
return { className: `${prefix}-none`, styleValue: null };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// subgrid
|
|
223
|
+
if (value === 'subgrid') {
|
|
224
|
+
return { className: `${prefix}-subgrid`, styleValue: null };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// その他は任意値
|
|
228
|
+
const sanitized = value.replace(/\s+/g, '_');
|
|
229
|
+
return { className: `${prefix}-[${sanitized}]`, styleValue: null };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* テキストクラスがフォントサイズクラスかどうかを判定
|
|
234
|
+
* text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-[12px] など
|
|
235
|
+
*/
|
|
236
|
+
function isTextSizeClass(cls: string): boolean {
|
|
237
|
+
if (!cls.startsWith('text-')) return false;
|
|
238
|
+
|
|
239
|
+
// Tailwind標準のフォントサイズクラス
|
|
240
|
+
const sizeKeywords = [
|
|
241
|
+
'text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl',
|
|
242
|
+
'text-2xl', 'text-3xl', 'text-4xl', 'text-5xl', 'text-6xl',
|
|
243
|
+
'text-7xl', 'text-8xl', 'text-9xl',
|
|
244
|
+
];
|
|
245
|
+
if (sizeKeywords.includes(cls)) return true;
|
|
246
|
+
|
|
247
|
+
// 任意値のサイズクラス: text-[12px], text-[1.5rem], text-[2em] など
|
|
248
|
+
const arbitraryMatch = cls.match(/^text-\[([^\]]+)\]$/);
|
|
249
|
+
if (arbitraryMatch) {
|
|
250
|
+
const value = arbitraryMatch[1];
|
|
251
|
+
// 数値で始まり、単位(px, rem, em, pt, vw, vh, %)で終わるか、純粋な数値
|
|
252
|
+
return /^-?\d/.test(value) && /(?:px|rem|em|pt|vw|vh|%|ch|ex|cap|ic|lh|rlh|vi|vb|vmin|vmax|cqw|cqh|cqi|cqb|cqmin|cqmax)?$/.test(value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* テキストクラスがテキストカラークラスかどうかを判定
|
|
260
|
+
* text-red-500, text-[#ff0000], text-inherit, text-transparent など
|
|
261
|
+
*/
|
|
262
|
+
function isTextColorClass(cls: string): boolean {
|
|
263
|
+
if (!cls.startsWith('text-')) return false;
|
|
264
|
+
|
|
265
|
+
// サイズクラスではない text- クラスはカラークラスとみなす
|
|
266
|
+
if (isTextSizeClass(cls)) return false;
|
|
267
|
+
|
|
268
|
+
// 明示的なカラーキーワード
|
|
269
|
+
if (cls === 'text-inherit' || cls === 'text-current' || cls === 'text-transparent') return true;
|
|
270
|
+
|
|
271
|
+
// 任意値のカラークラス: text-[#ff0000], text-[rgb(...)], text-[hsl(...)] など
|
|
272
|
+
const arbitraryMatch = cls.match(/^text-\[([^\]]+)\]$/);
|
|
273
|
+
if (arbitraryMatch) {
|
|
274
|
+
const value = arbitraryMatch[1];
|
|
275
|
+
// #で始まる(HEX)、rgb/hsl/oklch等で始まる、または var(--) で始まる
|
|
276
|
+
return value.startsWith('#') ||
|
|
277
|
+
value.startsWith('rgb') ||
|
|
278
|
+
value.startsWith('hsl') ||
|
|
279
|
+
value.startsWith('oklch') ||
|
|
280
|
+
value.startsWith('oklab') ||
|
|
281
|
+
value.startsWith('var(');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Tailwindの標準カラークラス(text-red-500, text-blue-700など)
|
|
285
|
+
// これらは text-{color}-{shade} のパターンに一致
|
|
286
|
+
const colorMatch = cls.match(/^text-([a-z]+)-(\d+)$/);
|
|
287
|
+
if (colorMatch) return true;
|
|
288
|
+
|
|
289
|
+
// text-white, text-black などの単純なカラー名
|
|
290
|
+
const simpleColors = ['text-white', 'text-black', 'text-slate', 'text-gray', 'text-zinc', 'text-neutral', 'text-stone', 'text-red', 'text-orange', 'text-amber', 'text-yellow', 'text-lime', 'text-green', 'text-emerald', 'text-teal', 'text-cyan', 'text-sky', 'text-blue', 'text-indigo', 'text-violet', 'text-purple', 'text-fuchsia', 'text-pink', 'text-rose'];
|
|
291
|
+
if (simpleColors.includes(cls)) return true;
|
|
292
|
+
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 競合するクラスを要素から削除
|
|
298
|
+
*
|
|
299
|
+
* @param element 対象要素
|
|
300
|
+
* @param property 更新するCSSプロパティ名
|
|
301
|
+
*/
|
|
302
|
+
export function removeConflictingClasses(
|
|
303
|
+
element: HTMLElement,
|
|
304
|
+
property: string
|
|
305
|
+
): void {
|
|
306
|
+
const conflictPrefixes = TAILWIND_CONFLICT_GROUPS[property];
|
|
307
|
+
|
|
308
|
+
// fontSize と color は特別処理(twMergeに任せるため空配列だが、
|
|
309
|
+
// 同種のクラスのみ削除する特別なロジックを適用)
|
|
310
|
+
if (property === 'fontSize') {
|
|
311
|
+
const currentClasses = getElementClassName(element).split(/\s+/).filter(Boolean);
|
|
312
|
+
const filteredClasses = currentClasses.filter(cls => !isTextSizeClass(cls));
|
|
313
|
+
setElementClassName(element, filteredClasses.join(' '));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (property === 'color') {
|
|
318
|
+
const currentClasses = getElementClassName(element).split(/\s+/).filter(Boolean);
|
|
319
|
+
const filteredClasses = currentClasses.filter(cls => !isTextColorClass(cls));
|
|
320
|
+
setElementClassName(element, filteredClasses.join(' '));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!conflictPrefixes || conflictPrefixes.length === 0) return;
|
|
325
|
+
|
|
326
|
+
const currentClasses = getElementClassName(element).split(/\s+/).filter(Boolean);
|
|
327
|
+
const filteredClasses = currentClasses.filter((cls) => {
|
|
328
|
+
// 各競合プレフィックスに対してチェック
|
|
329
|
+
return !conflictPrefixes.some((prefix) => {
|
|
330
|
+
// 完全一致(position: static, relativeなど)
|
|
331
|
+
if (cls === prefix) return true;
|
|
332
|
+
// プレフィックス一致(w-, h-, bg-など)
|
|
333
|
+
if (prefix.endsWith('-') && cls.startsWith(prefix)) return true;
|
|
334
|
+
// 負の値のプレフィックス一致(-top-[10px]など)
|
|
335
|
+
if (prefix.endsWith('-') && cls.startsWith(`-${prefix}`)) return true;
|
|
336
|
+
// font-[ で始まるパターン(font-family用)
|
|
337
|
+
if (prefix === 'font-[' && cls.startsWith('font-[')) return true;
|
|
338
|
+
return false;
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
setElementClassName(element, filteredClasses.join(' '));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 指定されたCSSプロパティのstyle属性をクリア
|
|
347
|
+
*
|
|
348
|
+
* @param element 対象要素
|
|
349
|
+
* @param property CSSプロパティ名
|
|
350
|
+
*/
|
|
351
|
+
export function clearInlineStyle(
|
|
352
|
+
element: HTMLElement,
|
|
353
|
+
property: string
|
|
354
|
+
): void {
|
|
355
|
+
// style属性から該当プロパティを削除
|
|
356
|
+
element.style.removeProperty(
|
|
357
|
+
property.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 任意値クラス(arbitrary value)かどうかを判定
|
|
363
|
+
* 例: w-[100px], text-[185px], bg-[#ff0000]
|
|
364
|
+
*/
|
|
365
|
+
function isArbitraryValueClass(className: string): boolean {
|
|
366
|
+
return className.includes('[') && className.includes(']');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* レイアウト関連のプロパティかどうかを判定
|
|
371
|
+
* これらのプロパティは、Tailwind Browserのコンパイル遅延や
|
|
372
|
+
* 既存CSSルールとの競合を避けるため、インラインスタイルも併用する
|
|
373
|
+
*/
|
|
374
|
+
const LAYOUT_CRITICAL_PROPERTIES = new Set([
|
|
375
|
+
'display',
|
|
376
|
+
'flexDirection',
|
|
377
|
+
'flexWrap',
|
|
378
|
+
'justifyContent',
|
|
379
|
+
'alignItems',
|
|
380
|
+
'alignContent',
|
|
381
|
+
'gap',
|
|
382
|
+
'rowGap',
|
|
383
|
+
'columnGap',
|
|
384
|
+
'gridTemplateColumns',
|
|
385
|
+
'gridTemplateRows',
|
|
386
|
+
'gridAutoFlow',
|
|
387
|
+
'justifyItems',
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Tailwindスタイルを要素に適用
|
|
392
|
+
*
|
|
393
|
+
* 1. 競合するTailwindクラスを削除
|
|
394
|
+
* 2. 新しいTailwindクラスを追加
|
|
395
|
+
* 3. style属性が必要な場合は適用
|
|
396
|
+
* 4. 任意値クラスの場合は、インラインスタイルもフォールバックとして設定
|
|
397
|
+
* (Tailwind Browser v4のJITコンパイルが遅れる場合の対策)
|
|
398
|
+
*
|
|
399
|
+
* @param element 対象要素
|
|
400
|
+
* @param styles 適用するスタイル(CSSプロパティ名: 値)
|
|
401
|
+
*/
|
|
402
|
+
/**
|
|
403
|
+
* エディタが選択・ドラッグ等の状態管理に付けるクラス。
|
|
404
|
+
* デザインのクラスではないので、Tailwindの重複解決(twMerge)に混ぜてはいけない。
|
|
405
|
+
*/
|
|
406
|
+
const EDITOR_MANAGED_CLASSES = new Set([
|
|
407
|
+
'selected',
|
|
408
|
+
'dragging',
|
|
409
|
+
'editing',
|
|
410
|
+
'rotating',
|
|
411
|
+
'panning',
|
|
412
|
+
'hover-preview',
|
|
413
|
+
'marquee-hover',
|
|
414
|
+
'marquee-active',
|
|
415
|
+
'text-editable-hover',
|
|
416
|
+
'drag-ghost',
|
|
417
|
+
]);
|
|
418
|
+
|
|
419
|
+
export function applyTailwindStyles(
|
|
420
|
+
element: HTMLElement,
|
|
421
|
+
styles: Record<string, string>
|
|
422
|
+
): void {
|
|
423
|
+
// DEBUG: applyTailwindStyles 開始時のログ
|
|
424
|
+
console.log('[DEBUG tailwind-utils] applyTailwindStyles called:', {
|
|
425
|
+
elementId: element.id,
|
|
426
|
+
elementTagName: element.tagName,
|
|
427
|
+
inputStyles: styles,
|
|
428
|
+
currentClassName: element.getAttribute('class'),
|
|
429
|
+
currentInlineStyle: element.getAttribute('style'),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const classesToAdd: string[] = [];
|
|
433
|
+
const stylesToApply: Record<string, string> = {};
|
|
434
|
+
const propertiesToClear: string[] = [];
|
|
435
|
+
// 任意値クラス用のフォールバックスタイル
|
|
436
|
+
const arbitraryFallbackStyles: Record<string, string> = {};
|
|
437
|
+
|
|
438
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
439
|
+
// 競合クラスを削除
|
|
440
|
+
removeConflictingClasses(element, property);
|
|
441
|
+
|
|
442
|
+
// 変換
|
|
443
|
+
const { className, styleValue } = cssToTailwindClass(property, value);
|
|
444
|
+
|
|
445
|
+
if (className) {
|
|
446
|
+
classesToAdd.push(className);
|
|
447
|
+
|
|
448
|
+
// 以下の場合はインラインスタイルもフォールバックとして設定:
|
|
449
|
+
// 1. 任意値クラス(Tailwind Browser v4が動的クラスをコンパイルするまでの間)
|
|
450
|
+
// 2. レイアウト関連プロパティ(既存CSSルールとの競合を避けるため)
|
|
451
|
+
if (isArbitraryValueClass(className) || LAYOUT_CRITICAL_PROPERTIES.has(property)) {
|
|
452
|
+
arbitraryFallbackStyles[property] = value;
|
|
453
|
+
} else {
|
|
454
|
+
// 通常のTailwindクラスの場合のみ、インラインスタイルをクリア
|
|
455
|
+
propertiesToClear.push(property);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (styleValue) {
|
|
460
|
+
stylesToApply[property] = styleValue;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// 既存のstyle属性をクリア(通常のTailwindクラスで管理するプロパティのみ)
|
|
465
|
+
propertiesToClear.forEach((prop) => {
|
|
466
|
+
clearInlineStyle(element, prop);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// クラスを適用(tailwind-mergeで重複解決)
|
|
470
|
+
if (classesToAdd.length > 0) {
|
|
471
|
+
// エディタが状態管理に付けるクラスは twMerge に見せない。
|
|
472
|
+
// tailwind-merge は `text-editable-hover` を文字色クラス(text-{color})と解釈し、
|
|
473
|
+
// 既存の text-white/85 などを「同グループの古い指定」として削除してしまう
|
|
474
|
+
// (実害: テキストをドラッグしただけで文字色クラスが消えて色が変わった)。
|
|
475
|
+
// 退避してからマージし、最後に付け直す。
|
|
476
|
+
const parts = getElementClassName(element).split(/\s+/).filter(Boolean);
|
|
477
|
+
const editorClasses = parts.filter((c) => EDITOR_MANAGED_CLASSES.has(c));
|
|
478
|
+
const designClasses = parts.filter((c) => !EDITOR_MANAGED_CLASSES.has(c));
|
|
479
|
+
const merged = twMerge(designClasses.join(' '), ...classesToAdd);
|
|
480
|
+
setElementClassName(element, [merged, ...editorClasses].join(' ').trim());
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// style属性を適用(STYLE_ONLY_PROPERTIESからの値)
|
|
484
|
+
Object.entries(stylesToApply).forEach(([prop, val]) => {
|
|
485
|
+
(element.style as any)[prop] = val;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// 任意値クラス用のフォールバックスタイルを適用
|
|
489
|
+
// これにより、Tailwind Browserがコンパイルする前でもスタイルが表示される
|
|
490
|
+
Object.entries(arbitraryFallbackStyles).forEach(([prop, val]) => {
|
|
491
|
+
(element.style as any)[prop] = val;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// DEBUG: applyTailwindStyles 最終適用結果のログ
|
|
495
|
+
console.log('[DEBUG tailwind-utils] applyTailwindStyles completed:', {
|
|
496
|
+
elementId: element.id,
|
|
497
|
+
classesToAdd,
|
|
498
|
+
stylesToApply,
|
|
499
|
+
arbitraryFallbackStyles,
|
|
500
|
+
propertiesToClear,
|
|
501
|
+
finalClassName: element.getAttribute('class'),
|
|
502
|
+
finalInlineStyle: element.getAttribute('style'),
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Tailwind Browserに再コンパイルをリクエスト(利用可能な場合)
|
|
506
|
+
triggerTailwindRecompile(element);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Tailwind Browserに再コンパイルをトリガー
|
|
511
|
+
* iframe内のTailwind Browserインスタンスに変更を通知
|
|
512
|
+
*/
|
|
513
|
+
function triggerTailwindRecompile(element: HTMLElement): void {
|
|
514
|
+
try {
|
|
515
|
+
const doc = element.ownerDocument;
|
|
516
|
+
const win = doc?.defaultView as Window & {
|
|
517
|
+
tailwindcss?: { refresh?: () => void };
|
|
518
|
+
__twind?: { observe?: (el: Element) => void };
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
if (win?.tailwindcss?.refresh) {
|
|
522
|
+
// Tailwind CSS Browser v4の場合
|
|
523
|
+
win.tailwindcss.refresh();
|
|
524
|
+
} else if (win?.__twind?.observe) {
|
|
525
|
+
// Twindの場合(互換性のため)
|
|
526
|
+
win.__twind.observe(element);
|
|
527
|
+
}
|
|
528
|
+
} catch (e) {
|
|
529
|
+
// 再コンパイルに失敗しても、フォールバックスタイルが適用されているので問題なし
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Tailwindクラスから値を抽出(PropertyPanel用)
|
|
535
|
+
*
|
|
536
|
+
* @param className 要素のclassName
|
|
537
|
+
* @param property CSSプロパティ名
|
|
538
|
+
* @returns 抽出された値(見つからない場合はnull)
|
|
539
|
+
*/
|
|
540
|
+
export function extractValueFromTailwindClass(
|
|
541
|
+
className: unknown,
|
|
542
|
+
property: string
|
|
543
|
+
): string | null {
|
|
544
|
+
if (!className) return null;
|
|
545
|
+
|
|
546
|
+
// classNameの型に応じて文字列を取得
|
|
547
|
+
// - string: そのまま使用
|
|
548
|
+
// - SVGAnimatedString: baseValプロパティ
|
|
549
|
+
// - DOMTokenList: valueプロパティ
|
|
550
|
+
// - その他: String()で変換
|
|
551
|
+
let classNameStr: string;
|
|
552
|
+
if (typeof className === 'string') {
|
|
553
|
+
classNameStr = className;
|
|
554
|
+
} else if (typeof className === 'object' && className !== null) {
|
|
555
|
+
// SVGAnimatedString (baseVal) または DOMTokenList (value) をチェック
|
|
556
|
+
const obj = className as { baseVal?: string; value?: string };
|
|
557
|
+
classNameStr = obj.baseVal ?? obj.value ?? String(className);
|
|
558
|
+
} else {
|
|
559
|
+
classNameStr = String(className);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (!classNameStr || classNameStr === '[object Object]') return null;
|
|
563
|
+
|
|
564
|
+
const classes = classNameStr.split(/\s+/);
|
|
565
|
+
|
|
566
|
+
// 1. 特殊値マッピングから逆引き
|
|
567
|
+
const specialMap = SPECIAL_VALUES[property];
|
|
568
|
+
if (specialMap) {
|
|
569
|
+
for (const [value, tailwindClass] of Object.entries(specialMap)) {
|
|
570
|
+
if (classes.includes(tailwindClass)) {
|
|
571
|
+
return value;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 2. 列挙型マッピングから逆引き
|
|
577
|
+
const enumMap = ENUM_MAPPINGS[property];
|
|
578
|
+
if (enumMap) {
|
|
579
|
+
for (const [value, tailwindClass] of Object.entries(enumMap)) {
|
|
580
|
+
if (classes.includes(tailwindClass)) {
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// 3. 色プレフィックスの場合
|
|
587
|
+
const colorPrefix = COLOR_PREFIXES[property];
|
|
588
|
+
if (colorPrefix) {
|
|
589
|
+
const colorClass = classes.find((cls) => {
|
|
590
|
+
return (
|
|
591
|
+
cls.startsWith(`${colorPrefix}-[`) ||
|
|
592
|
+
cls === `${colorPrefix}-transparent` ||
|
|
593
|
+
cls === `${colorPrefix}-current` ||
|
|
594
|
+
cls === `${colorPrefix}-inherit`
|
|
595
|
+
);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
if (colorClass) {
|
|
599
|
+
if (colorClass === `${colorPrefix}-transparent`) return 'transparent';
|
|
600
|
+
if (colorClass === `${colorPrefix}-current`) return 'currentColor';
|
|
601
|
+
if (colorClass === `${colorPrefix}-inherit`) return 'inherit';
|
|
602
|
+
|
|
603
|
+
// 任意値から抽出
|
|
604
|
+
const match = colorClass.match(/\[([^\]]+)\]/);
|
|
605
|
+
if (match) {
|
|
606
|
+
// アンダースコアをスペースに戻す
|
|
607
|
+
return match[1].replace(/_/g, ' ');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 4. 数値プレフィックスの場合
|
|
614
|
+
const numericPrefix = NUMERIC_PREFIXES[property];
|
|
615
|
+
if (numericPrefix) {
|
|
616
|
+
// 負の値も含めて検索
|
|
617
|
+
const numericClass = classes.find((cls) => {
|
|
618
|
+
return (
|
|
619
|
+
cls.startsWith(`${numericPrefix}-`) ||
|
|
620
|
+
cls.startsWith(`-${numericPrefix}-`)
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
if (numericClass) {
|
|
625
|
+
// 特殊値(auto, full, 1/2など)のチェック
|
|
626
|
+
if (numericClass.endsWith('-auto')) return 'auto';
|
|
627
|
+
if (numericClass.endsWith('-full')) return '100%';
|
|
628
|
+
if (numericClass.includes('-1/2')) return '50%';
|
|
629
|
+
if (numericClass.includes('-1/3')) return '33.333333%';
|
|
630
|
+
if (numericClass.includes('-2/3')) return '66.666667%';
|
|
631
|
+
if (numericClass.includes('-1/4')) return '25%';
|
|
632
|
+
if (numericClass.includes('-3/4')) return '75%';
|
|
633
|
+
if (numericClass.endsWith('-fit')) return 'fit-content';
|
|
634
|
+
if (numericClass.endsWith('-min')) return 'min-content';
|
|
635
|
+
if (numericClass.endsWith('-max')) return 'max-content';
|
|
636
|
+
if (numericClass.endsWith('-screen')) {
|
|
637
|
+
if (property === 'width') return '100vw';
|
|
638
|
+
if (property === 'height') return '100vh';
|
|
639
|
+
}
|
|
640
|
+
if (numericClass.endsWith('-dvh')) return '100dvh';
|
|
641
|
+
|
|
642
|
+
// 0
|
|
643
|
+
if (numericClass === `${numericPrefix}-0`) return '0';
|
|
644
|
+
|
|
645
|
+
// 任意値から抽出
|
|
646
|
+
const match = numericClass.match(/\[([^\]]+)\]/);
|
|
647
|
+
if (match) {
|
|
648
|
+
// 負の値の場合はマイナス記号を付ける
|
|
649
|
+
const isNegative = numericClass.startsWith('-');
|
|
650
|
+
return isNegative ? `-${match[1]}` : match[1];
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// 5. 特殊なプロパティ
|
|
657
|
+
switch (property) {
|
|
658
|
+
case 'fontFamily': {
|
|
659
|
+
const fontClass = classes.find((cls) => cls.startsWith('font-['));
|
|
660
|
+
if (fontClass) {
|
|
661
|
+
const match = fontClass.match(/font-\[([^\]]+)\]/);
|
|
662
|
+
if (match) {
|
|
663
|
+
// アンダースコアをスペースに戻す
|
|
664
|
+
return match[1].replace(/_/g, ' ');
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
case 'flexGrow': {
|
|
671
|
+
if (classes.includes('grow')) return '1';
|
|
672
|
+
if (classes.includes('grow-0')) return '0';
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
case 'flexShrink': {
|
|
677
|
+
if (classes.includes('shrink')) return '1';
|
|
678
|
+
if (classes.includes('shrink-0')) return '0';
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
case 'gridTemplateColumns': {
|
|
683
|
+
const colClass = classes.find((cls) => cls.startsWith('grid-cols-'));
|
|
684
|
+
if (colClass) {
|
|
685
|
+
if (colClass === 'grid-cols-none') return 'none';
|
|
686
|
+
if (colClass === 'grid-cols-subgrid') return 'subgrid';
|
|
687
|
+
const match = colClass.match(/grid-cols-(\d+)/);
|
|
688
|
+
if (match) return `repeat(${match[1]}, 1fr)`;
|
|
689
|
+
const arbitraryMatch = colClass.match(/grid-cols-\[([^\]]+)\]/);
|
|
690
|
+
if (arbitraryMatch) return arbitraryMatch[1].replace(/_/g, ' ');
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
case 'gridTemplateRows': {
|
|
696
|
+
const rowClass = classes.find((cls) => cls.startsWith('grid-rows-'));
|
|
697
|
+
if (rowClass) {
|
|
698
|
+
if (rowClass === 'grid-rows-none') return 'none';
|
|
699
|
+
if (rowClass === 'grid-rows-subgrid') return 'subgrid';
|
|
700
|
+
const match = rowClass.match(/grid-rows-(\d+)/);
|
|
701
|
+
if (match) return `repeat(${match[1]}, 1fr)`;
|
|
702
|
+
const arbitraryMatch = rowClass.match(/grid-rows-\[([^\]]+)\]/);
|
|
703
|
+
if (arbitraryMatch) return arbitraryMatch[1].replace(/_/g, ' ');
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
default:
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 要素のインラインスタイルをTailwindクラスに変換
|
|
715
|
+
*
|
|
716
|
+
* ドラッグやリサイズ中はパフォーマンスのためインラインスタイルを使用し、
|
|
717
|
+
* 操作終了時にこの関数を呼び出してTailwindクラスに変換する
|
|
718
|
+
*
|
|
719
|
+
* @param element 対象要素
|
|
720
|
+
* @param properties 変換するCSSプロパティ名の配列(指定しない場合は主要プロパティ全て)
|
|
721
|
+
*/
|
|
722
|
+
export function convertInlineStylesToTailwind(
|
|
723
|
+
element: HTMLElement,
|
|
724
|
+
properties?: string[]
|
|
725
|
+
): void {
|
|
726
|
+
// 変換対象のプロパティ(デフォルト)
|
|
727
|
+
const targetProperties = properties || [
|
|
728
|
+
'width',
|
|
729
|
+
'height',
|
|
730
|
+
'minWidth',
|
|
731
|
+
'maxWidth',
|
|
732
|
+
'minHeight',
|
|
733
|
+
'maxHeight',
|
|
734
|
+
'top',
|
|
735
|
+
'right',
|
|
736
|
+
'bottom',
|
|
737
|
+
'left',
|
|
738
|
+
'padding',
|
|
739
|
+
'paddingTop',
|
|
740
|
+
'paddingRight',
|
|
741
|
+
'paddingBottom',
|
|
742
|
+
'paddingLeft',
|
|
743
|
+
'margin',
|
|
744
|
+
'marginTop',
|
|
745
|
+
'marginRight',
|
|
746
|
+
'marginBottom',
|
|
747
|
+
'marginLeft',
|
|
748
|
+
'gap',
|
|
749
|
+
'borderRadius',
|
|
750
|
+
'borderWidth',
|
|
751
|
+
];
|
|
752
|
+
|
|
753
|
+
const stylesToConvert: Record<string, string> = {};
|
|
754
|
+
|
|
755
|
+
// インラインスタイルから値を抽出
|
|
756
|
+
targetProperties.forEach((prop) => {
|
|
757
|
+
// camelCaseからkebab-caseに変換
|
|
758
|
+
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
759
|
+
const value = element.style.getPropertyValue(kebabProp);
|
|
760
|
+
|
|
761
|
+
if (value && value !== '') {
|
|
762
|
+
stylesToConvert[prop] = value;
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// Tailwindクラスに変換して適用
|
|
767
|
+
if (Object.keys(stylesToConvert).length > 0) {
|
|
768
|
+
applyTailwindStyles(element, stylesToConvert);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* 要素のスタイル情報を取得(Tailwindクラス + computedStyle)
|
|
774
|
+
*
|
|
775
|
+
* @param element 対象要素
|
|
776
|
+
* @param property CSSプロパティ名
|
|
777
|
+
* @returns スタイル値(Tailwindクラスから抽出を優先、なければcomputedStyle)
|
|
778
|
+
*/
|
|
779
|
+
export function getElementStyleValue(
|
|
780
|
+
element: HTMLElement,
|
|
781
|
+
property: string
|
|
782
|
+
): string {
|
|
783
|
+
// 1. Tailwindクラスから抽出を試みる
|
|
784
|
+
const tailwindValue = extractValueFromTailwindClass(
|
|
785
|
+
getElementClassName(element),
|
|
786
|
+
property
|
|
787
|
+
);
|
|
788
|
+
if (tailwindValue !== null) {
|
|
789
|
+
return tailwindValue;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// 2. inline styleから取得
|
|
793
|
+
|
|
794
|
+
const inlineValue = (element.style as any)[property];
|
|
795
|
+
if (inlineValue) {
|
|
796
|
+
return inlineValue;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// 3. computedStyleから取得
|
|
800
|
+
const computed = window.getComputedStyle(element);
|
|
801
|
+
return computed.getPropertyValue(
|
|
802
|
+
property.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
803
|
+
);
|
|
804
|
+
}
|