@hyperframes/studio 0.7.58 → 0.7.59
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/dist/assets/{hyperframes-player-CtTDO63S.js → hyperframes-player-Bm07FLkl.js} +1 -1
- package/dist/assets/{index-C47jAC3Q.js → index-B995FG46.js} +1 -1
- package/dist/assets/index-CrkAdJkb.js +426 -0
- package/dist/assets/index-Dj5p8U_A.css +1 -0
- package/dist/assets/{index-DeQPzqwH.js → index-FvzmPhfG.js} +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.html +2 -2
- package/dist/index.js +13514 -8528
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +3 -3
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioRightPanel.tsx +29 -51
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineTrackVisibility.test.ts +52 -0
- package/src/hooks/timelineTrackVisibility.ts +27 -10
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.ts +29 -175
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.ts +17 -5
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useElementLifecycleOps.ts +8 -3
- package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
- package/src/hooks/useFileManager.ts +82 -60
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +4 -1
- package/src/hooks/useTimelineEditing.ts +16 -10
- package/src/hooks/useTimelineEditingTypes.ts +3 -0
- package/src/hooks/useTimelineGroupEditing.ts +16 -2
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +490 -57
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +292 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +30 -27
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.ts +17 -0
- package/dist/assets/index-B_UvTX3E.js +0 -423
- package/dist/assets/index-uahwWkgw.css +0 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type FontOption,
|
|
19
19
|
type LocalFontData,
|
|
20
20
|
} from "./propertyPanelHelpers";
|
|
21
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
21
22
|
|
|
22
23
|
/* ------------------------------------------------------------------ */
|
|
23
24
|
/* Font helper functions */
|
|
@@ -123,16 +124,19 @@ function loadImportedFontStylesheet(asset: ImportedFontAsset): void {
|
|
|
123
124
|
export function FontFamilyField({
|
|
124
125
|
value,
|
|
125
126
|
disabled,
|
|
127
|
+
flat,
|
|
126
128
|
importedFonts,
|
|
127
129
|
onImportFonts,
|
|
128
130
|
onCommit,
|
|
129
131
|
}: {
|
|
130
132
|
value: string;
|
|
131
133
|
disabled?: boolean;
|
|
134
|
+
flat?: boolean;
|
|
132
135
|
importedFonts: ImportedFontAsset[];
|
|
133
136
|
onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
|
|
134
137
|
onCommit: (nextValue: string) => void;
|
|
135
138
|
}) {
|
|
139
|
+
const track = useTrackDesignInput();
|
|
136
140
|
const currentFamily = primaryFontFamily(value);
|
|
137
141
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
138
142
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
@@ -148,6 +152,10 @@ export function FontFamilyField({
|
|
|
148
152
|
const [fontNotice, setFontNotice] = useState<string | null>(null);
|
|
149
153
|
const canQueryLocalFonts =
|
|
150
154
|
typeof window !== "undefined" && typeof window.queryLocalFonts === "function";
|
|
155
|
+
const commitFontFamily = (nextValue: string) => {
|
|
156
|
+
if (nextValue !== value) track("select", "Font family");
|
|
157
|
+
onCommit(nextValue);
|
|
158
|
+
};
|
|
151
159
|
|
|
152
160
|
useEffect(() => {
|
|
153
161
|
if (!open) return;
|
|
@@ -245,7 +253,7 @@ export function FontFamilyField({
|
|
|
245
253
|
for (const font of imported) loadImportedFontStylesheet(font);
|
|
246
254
|
const first = imported[0];
|
|
247
255
|
if (first) {
|
|
248
|
-
|
|
256
|
+
commitFontFamily(buildFontFamilyValue(first.family));
|
|
249
257
|
setQuery("");
|
|
250
258
|
setOpen(false);
|
|
251
259
|
} else {
|
|
@@ -347,7 +355,7 @@ export function FontFamilyField({
|
|
|
347
355
|
: await importSystemFont(option.family);
|
|
348
356
|
if (imported) {
|
|
349
357
|
loadImportedFontStylesheet(imported);
|
|
350
|
-
|
|
358
|
+
commitFontFamily(buildFontFamilyValue(imported.family));
|
|
351
359
|
setQuery("");
|
|
352
360
|
setOpen(false);
|
|
353
361
|
return;
|
|
@@ -361,11 +369,135 @@ export function FontFamilyField({
|
|
|
361
369
|
(f) => f.family.toLowerCase() === option.family.toLowerCase(),
|
|
362
370
|
);
|
|
363
371
|
if (imported) loadImportedFontStylesheet(imported);
|
|
364
|
-
|
|
372
|
+
commitFontFamily(buildFontFamilyValue(option.family));
|
|
365
373
|
setQuery("");
|
|
366
374
|
setOpen(false);
|
|
367
375
|
};
|
|
368
376
|
|
|
377
|
+
const dropdown = open && (
|
|
378
|
+
<div className="absolute left-0 right-0 top-[calc(100%+6px)] z-50 overflow-hidden rounded-xl border border-neutral-700 bg-neutral-950 shadow-2xl">
|
|
379
|
+
<div className="grid grid-cols-[minmax(0,1fr)_auto_auto] gap-2 border-b border-neutral-800 p-2">
|
|
380
|
+
<input
|
|
381
|
+
ref={inputRef}
|
|
382
|
+
type="text"
|
|
383
|
+
value={query}
|
|
384
|
+
disabled={disabled}
|
|
385
|
+
placeholder={loadingGoogleFonts ? "Loading Google Fonts..." : "Search fonts"}
|
|
386
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
387
|
+
onKeyDown={(e) => {
|
|
388
|
+
if (e.key === "Escape") {
|
|
389
|
+
e.preventDefault();
|
|
390
|
+
setOpen(false);
|
|
391
|
+
}
|
|
392
|
+
if (e.key === "Enter" && filteredOptions[0]) {
|
|
393
|
+
e.preventDefault();
|
|
394
|
+
commitFamily(filteredOptions[0]);
|
|
395
|
+
}
|
|
396
|
+
}}
|
|
397
|
+
className="min-w-0 rounded-lg border border-neutral-800 bg-neutral-900 px-2.5 py-2 text-[11px] font-medium text-neutral-100 outline-none placeholder:text-neutral-600 focus:border-neutral-600"
|
|
398
|
+
/>
|
|
399
|
+
{canQueryLocalFonts && (
|
|
400
|
+
<button
|
|
401
|
+
type="button"
|
|
402
|
+
disabled={disabled || loadingLocalFonts}
|
|
403
|
+
onClick={loadBrowserLocalFonts}
|
|
404
|
+
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-neutral-700"
|
|
405
|
+
>
|
|
406
|
+
{loadingLocalFonts ? "..." : "Local"}
|
|
407
|
+
</button>
|
|
408
|
+
)}
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
disabled={disabled || importingFonts || !onImportFonts}
|
|
412
|
+
onClick={() => fontInputRef.current?.click()}
|
|
413
|
+
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-neutral-700"
|
|
414
|
+
>
|
|
415
|
+
{importingFonts ? "..." : "Import"}
|
|
416
|
+
</button>
|
|
417
|
+
<input
|
|
418
|
+
ref={fontInputRef}
|
|
419
|
+
type="file"
|
|
420
|
+
accept=".ttf,.otf,.ttc,.woff,.woff2,.eot,font/*"
|
|
421
|
+
multiple
|
|
422
|
+
aria-label="Import local font files"
|
|
423
|
+
disabled={disabled || importingFonts || !onImportFonts}
|
|
424
|
+
className="hidden"
|
|
425
|
+
onChange={async (event) => {
|
|
426
|
+
await handleImportFonts(event.target.files);
|
|
427
|
+
event.target.value = "";
|
|
428
|
+
}}
|
|
429
|
+
/>
|
|
430
|
+
</div>
|
|
431
|
+
{fontNotice && (
|
|
432
|
+
<div className="border-b border-neutral-800 px-3 py-2 text-[10px] leading-4 text-neutral-500">
|
|
433
|
+
{fontNotice}
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
<div className="max-h-64 overflow-y-auto p-1">
|
|
437
|
+
{filteredOptions.length === 0 ? (
|
|
438
|
+
<div className="px-2 py-3 text-[11px] text-neutral-500">No fonts found.</div>
|
|
439
|
+
) : (
|
|
440
|
+
filteredOptions.map((option) => (
|
|
441
|
+
<button
|
|
442
|
+
key={`${option.source}-${option.family}`}
|
|
443
|
+
type="button"
|
|
444
|
+
onClick={() => commitFamily(option)}
|
|
445
|
+
className={`flex w-full min-w-0 items-center justify-between gap-3 rounded-lg px-2 py-2 text-left text-[11px] transition-colors ${
|
|
446
|
+
option.family === currentFamily
|
|
447
|
+
? "bg-studio-accent/15 text-neutral-50"
|
|
448
|
+
: "text-neutral-300 hover:bg-neutral-900 hover:text-neutral-100"
|
|
449
|
+
}`}
|
|
450
|
+
>
|
|
451
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
452
|
+
<span className="truncate font-medium">{option.family}</span>
|
|
453
|
+
{renderAliasFor(option.family) && (
|
|
454
|
+
<span className="flex-shrink-0 text-[9px] text-neutral-500">
|
|
455
|
+
→ {renderAliasFor(option.family)}
|
|
456
|
+
</span>
|
|
457
|
+
)}
|
|
458
|
+
</span>
|
|
459
|
+
<span className="flex-shrink-0 text-[9px] uppercase tracking-[0.14em] text-neutral-600">
|
|
460
|
+
{option.source}
|
|
461
|
+
</span>
|
|
462
|
+
</button>
|
|
463
|
+
))
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
if (flat) {
|
|
470
|
+
return (
|
|
471
|
+
<div ref={containerRef} className="relative flex min-h-[30px] items-center justify-between">
|
|
472
|
+
<span className="text-[11px] text-panel-text-2">Font</span>
|
|
473
|
+
<button
|
|
474
|
+
type="button"
|
|
475
|
+
data-flat-font-trigger="true"
|
|
476
|
+
disabled={disabled}
|
|
477
|
+
onClick={() => setOpen((next) => !next)}
|
|
478
|
+
className="flex items-center gap-1.5 disabled:cursor-not-allowed"
|
|
479
|
+
>
|
|
480
|
+
<span
|
|
481
|
+
className="max-w-[200px] truncate font-mono text-[11px] text-panel-text-0"
|
|
482
|
+
style={{ fontFamily: value }}
|
|
483
|
+
>
|
|
484
|
+
{currentFamily}
|
|
485
|
+
</span>
|
|
486
|
+
<svg
|
|
487
|
+
width="10"
|
|
488
|
+
height="10"
|
|
489
|
+
viewBox="0 0 10 10"
|
|
490
|
+
fill="currentColor"
|
|
491
|
+
className="flex-shrink-0 text-panel-text-5"
|
|
492
|
+
>
|
|
493
|
+
<path d="M2 3l3 4 3-4z" />
|
|
494
|
+
</svg>
|
|
495
|
+
</button>
|
|
496
|
+
{dropdown}
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
369
501
|
return (
|
|
370
502
|
<div ref={containerRef} className="relative grid min-w-0 gap-1.5">
|
|
371
503
|
<span className={LABEL}>Font family</span>
|
|
@@ -385,98 +517,7 @@ export function FontFamilyField({
|
|
|
385
517
|
Font
|
|
386
518
|
</span>
|
|
387
519
|
</button>
|
|
388
|
-
|
|
389
|
-
{open && (
|
|
390
|
-
<div className="absolute left-0 right-0 top-[calc(100%+6px)] z-50 overflow-hidden rounded-xl border border-neutral-700 bg-neutral-950 shadow-2xl">
|
|
391
|
-
<div className="grid grid-cols-[minmax(0,1fr)_auto_auto] gap-2 border-b border-neutral-800 p-2">
|
|
392
|
-
<input
|
|
393
|
-
ref={inputRef}
|
|
394
|
-
type="text"
|
|
395
|
-
value={query}
|
|
396
|
-
disabled={disabled}
|
|
397
|
-
placeholder={loadingGoogleFonts ? "Loading Google Fonts..." : "Search fonts"}
|
|
398
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
399
|
-
onKeyDown={(e) => {
|
|
400
|
-
if (e.key === "Escape") {
|
|
401
|
-
e.preventDefault();
|
|
402
|
-
setOpen(false);
|
|
403
|
-
}
|
|
404
|
-
if (e.key === "Enter" && filteredOptions[0]) {
|
|
405
|
-
e.preventDefault();
|
|
406
|
-
commitFamily(filteredOptions[0]);
|
|
407
|
-
}
|
|
408
|
-
}}
|
|
409
|
-
className="min-w-0 rounded-lg border border-neutral-800 bg-neutral-900 px-2.5 py-2 text-[11px] font-medium text-neutral-100 outline-none placeholder:text-neutral-600 focus:border-neutral-600"
|
|
410
|
-
/>
|
|
411
|
-
{canQueryLocalFonts && (
|
|
412
|
-
<button
|
|
413
|
-
type="button"
|
|
414
|
-
disabled={disabled || loadingLocalFonts}
|
|
415
|
-
onClick={loadBrowserLocalFonts}
|
|
416
|
-
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-neutral-700"
|
|
417
|
-
>
|
|
418
|
-
{loadingLocalFonts ? "..." : "Local"}
|
|
419
|
-
</button>
|
|
420
|
-
)}
|
|
421
|
-
<button
|
|
422
|
-
type="button"
|
|
423
|
-
disabled={disabled || importingFonts || !onImportFonts}
|
|
424
|
-
onClick={() => fontInputRef.current?.click()}
|
|
425
|
-
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-neutral-700"
|
|
426
|
-
>
|
|
427
|
-
{importingFonts ? "..." : "Import"}
|
|
428
|
-
</button>
|
|
429
|
-
<input
|
|
430
|
-
ref={fontInputRef}
|
|
431
|
-
type="file"
|
|
432
|
-
accept=".ttf,.otf,.ttc,.woff,.woff2,.eot,font/*"
|
|
433
|
-
multiple
|
|
434
|
-
aria-label="Import local font files"
|
|
435
|
-
disabled={disabled || importingFonts || !onImportFonts}
|
|
436
|
-
className="hidden"
|
|
437
|
-
onChange={async (event) => {
|
|
438
|
-
await handleImportFonts(event.target.files);
|
|
439
|
-
event.target.value = "";
|
|
440
|
-
}}
|
|
441
|
-
/>
|
|
442
|
-
</div>
|
|
443
|
-
{fontNotice && (
|
|
444
|
-
<div className="border-b border-neutral-800 px-3 py-2 text-[10px] leading-4 text-neutral-500">
|
|
445
|
-
{fontNotice}
|
|
446
|
-
</div>
|
|
447
|
-
)}
|
|
448
|
-
<div className="max-h-64 overflow-y-auto p-1">
|
|
449
|
-
{filteredOptions.length === 0 ? (
|
|
450
|
-
<div className="px-2 py-3 text-[11px] text-neutral-500">No fonts found.</div>
|
|
451
|
-
) : (
|
|
452
|
-
filteredOptions.map((option) => (
|
|
453
|
-
<button
|
|
454
|
-
key={`${option.source}-${option.family}`}
|
|
455
|
-
type="button"
|
|
456
|
-
onClick={() => commitFamily(option)}
|
|
457
|
-
className={`flex w-full min-w-0 items-center justify-between gap-3 rounded-lg px-2 py-2 text-left text-[11px] transition-colors ${
|
|
458
|
-
option.family === currentFamily
|
|
459
|
-
? "bg-studio-accent/15 text-neutral-50"
|
|
460
|
-
: "text-neutral-300 hover:bg-neutral-900 hover:text-neutral-100"
|
|
461
|
-
}`}
|
|
462
|
-
>
|
|
463
|
-
<span className="flex min-w-0 items-center gap-1.5">
|
|
464
|
-
<span className="truncate font-medium">{option.family}</span>
|
|
465
|
-
{renderAliasFor(option.family) && (
|
|
466
|
-
<span className="flex-shrink-0 text-[9px] text-neutral-500">
|
|
467
|
-
→ {renderAliasFor(option.family)}
|
|
468
|
-
</span>
|
|
469
|
-
)}
|
|
470
|
-
</span>
|
|
471
|
-
<span className="flex-shrink-0 text-[9px] uppercase tracking-[0.14em] text-neutral-600">
|
|
472
|
-
{option.source}
|
|
473
|
-
</span>
|
|
474
|
-
</button>
|
|
475
|
-
))
|
|
476
|
-
)}
|
|
477
|
-
</div>
|
|
478
|
-
</div>
|
|
479
|
-
)}
|
|
520
|
+
{dropdown}
|
|
480
521
|
</div>
|
|
481
522
|
);
|
|
482
523
|
}
|
|
@@ -29,6 +29,26 @@ export function isSelectedElementHidden(
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* 5-part element identity for keying panel remounts on selection change —
|
|
34
|
+
* id or selector alone collides for id-less same-selector siblings, leaving
|
|
35
|
+
* mount-initialized state pointed at the previous element. sourceFile is
|
|
36
|
+
* required too: the same local id/selector can legitimately recur across
|
|
37
|
+
* different composition files (host vs. an inlined sub-composition, or two
|
|
38
|
+
* unrelated sub-comps), and without it those collide onto the same key.
|
|
39
|
+
*/
|
|
40
|
+
export function selectionIdentityKey(
|
|
41
|
+
element: Pick<DomEditSelection, "id" | "hfId" | "selector" | "selectorIndex" | "sourceFile">,
|
|
42
|
+
): string {
|
|
43
|
+
return [
|
|
44
|
+
element.id ?? "",
|
|
45
|
+
element.hfId ?? "",
|
|
46
|
+
element.selector ?? "",
|
|
47
|
+
String(element.selectorIndex ?? ""),
|
|
48
|
+
element.sourceFile ?? "",
|
|
49
|
+
].join("|");
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
/* ------------------------------------------------------------------ */
|
|
33
53
|
/* Font types & constants (shared by font and section modules) */
|
|
34
54
|
/* ------------------------------------------------------------------ */
|
|
@@ -505,3 +525,56 @@ export function readGsapBorderRadiusForPanel(
|
|
|
505
525
|
return null;
|
|
506
526
|
}
|
|
507
527
|
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Builds the multi-line "element info" text copied to the clipboard for an AI
|
|
531
|
+
* agent. Shared by both the legacy and flat inspector headers (the flat split
|
|
532
|
+
* needs the same string), so it lives here rather than as a PropertyPanel
|
|
533
|
+
* closure. Pure — the caller owns the clipboard write, toast, and copied state.
|
|
534
|
+
*/
|
|
535
|
+
// fallow-ignore-next-line complexity
|
|
536
|
+
export function buildElementInfoText(
|
|
537
|
+
element: DomEditSelection,
|
|
538
|
+
sourceLabel: string,
|
|
539
|
+
gsapAnimations: GsapAnimation[],
|
|
540
|
+
previewIframeRef?: React.RefObject<HTMLIFrameElement | null>,
|
|
541
|
+
): string {
|
|
542
|
+
const file = element.sourceFile ?? "index.html";
|
|
543
|
+
let lineNum: number | null = null;
|
|
544
|
+
try {
|
|
545
|
+
const src = previewIframeRef?.current?.contentDocument?.documentElement?.outerHTML ?? "";
|
|
546
|
+
if (src && element.id) {
|
|
547
|
+
const idx = src.indexOf(`id="${element.id}"`);
|
|
548
|
+
if (idx > -1) lineNum = src.slice(0, idx).split("\n").length;
|
|
549
|
+
}
|
|
550
|
+
if (!lineNum && element.selector) {
|
|
551
|
+
const tag = element.tagName.toLowerCase();
|
|
552
|
+
const cls = element.selector.startsWith(".") ? element.selector.slice(1).split(".")[0] : null;
|
|
553
|
+
const search = cls ? `class="${cls}` : `<${tag}`;
|
|
554
|
+
const idx = src.indexOf(search);
|
|
555
|
+
if (idx > -1) lineNum = src.slice(0, idx).split("\n").length;
|
|
556
|
+
}
|
|
557
|
+
} catch {}
|
|
558
|
+
const fileLoc = lineNum ? `${file}:${lineNum}` : file;
|
|
559
|
+
const lines = [
|
|
560
|
+
`Element: ${element.label} (${sourceLabel})`,
|
|
561
|
+
`File: ${fileLoc}`,
|
|
562
|
+
`Position: x=${Math.round(element.boundingBox.x)}, y=${Math.round(element.boundingBox.y)}`,
|
|
563
|
+
`Size: ${Math.round(element.boundingBox.width)}×${Math.round(element.boundingBox.height)}`,
|
|
564
|
+
`Tag: <${element.tagName}>`,
|
|
565
|
+
];
|
|
566
|
+
if (element.computedStyles["z-index"] && element.computedStyles["z-index"] !== "auto") {
|
|
567
|
+
lines.push(`Z-index: ${element.computedStyles["z-index"]}`);
|
|
568
|
+
}
|
|
569
|
+
if (gsapAnimations.length > 0) {
|
|
570
|
+
const anim = gsapAnimations[0];
|
|
571
|
+
lines.push(
|
|
572
|
+
`Animation: ${anim.method}() ${anim.duration}s at ${anim.position}s, ease: ${anim.ease ?? "default"}`,
|
|
573
|
+
);
|
|
574
|
+
const props = Object.entries(anim.properties)
|
|
575
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
576
|
+
.join(", ");
|
|
577
|
+
if (props) lines.push(`Properties: ${props}`);
|
|
578
|
+
}
|
|
579
|
+
return lines.join("\n");
|
|
580
|
+
}
|