@hyperframes/studio 0.7.60 → 0.7.62
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-CWpmBjzv.js +459 -0
- package/dist/assets/{index-D6etaey-.js → index-BJzSiIBj.js} +1 -1
- package/dist/assets/index-Ceyz8Qt2.css +1 -0
- package/dist/assets/{index-Dh_WhagG.js → index-DJQPB78e.js} +1 -1
- package/dist/assets/index-vDRVQtTK.js +426 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.html +2 -2
- package/dist/index.js +4392 -3314
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +15 -15
- package/src/components/DesignPanelPromoteProvider.tsx +27 -1
- package/src/components/EditorShell.tsx +12 -0
- package/src/components/StudioHeader.tsx +2 -3
- package/src/components/StudioLeftSidebar.tsx +3 -0
- package/src/components/StudioRightPanel.tsx +34 -26
- package/src/components/editor/DomEditSelectionChrome.test.tsx +51 -0
- package/src/components/editor/PromotableControl.tsx +4 -2
- package/src/components/editor/PropertyPanel.tsx +151 -149
- package/src/components/editor/PropertyPanelFlat.tsx +50 -42
- package/src/components/editor/compositionReliabilityFixture.integration.test.ts +105 -0
- package/src/components/editor/domEditing.test.ts +34 -0
- package/src/components/editor/manualEditingAvailability.test.ts +6 -6
- package/src/components/editor/manualEditingAvailability.ts +5 -3
- package/src/components/editor/propertyPanelColor.test.tsx +31 -0
- package/src/components/editor/propertyPanelColor.tsx +150 -28
- package/src/components/editor/propertyPanelCommitField.tsx +178 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +44 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +4 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +0 -13
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +26 -10
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +21 -32
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +47 -4
- package/src/components/editor/propertyPanelFlatTextSection.tsx +10 -2
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +18 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +3 -92
- package/src/components/editor/propertyPanelSections.tsx +2 -2
- package/src/components/editor/propertyPanelTypes.ts +2 -0
- package/src/components/editor/useInspectorGestureTransaction.test.tsx +37 -0
- package/src/components/editor/useInspectorGestureTransaction.ts +60 -0
- package/src/components/nle/TimelinePane.tsx +6 -0
- package/src/components/sidebar/CompositionsTab.drag.test.tsx +87 -0
- package/src/components/sidebar/CompositionsTab.tsx +45 -2
- package/src/components/sidebar/LeftSidebar.tsx +3 -0
- package/src/components/storyboard/AgentChatMessageButton.test.tsx +47 -0
- package/src/components/storyboard/AgentChatMessageButton.tsx +45 -0
- package/src/components/storyboard/StoryboardFrameFocus.tsx +189 -66
- package/src/components/storyboard/StoryboardLoaded.tsx +121 -23
- package/src/components/storyboard/StoryboardReviewGuide.tsx +300 -0
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +170 -0
- package/src/components/storyboard/storyboardReviewStage.test.ts +40 -0
- package/src/components/storyboard/storyboardReviewStage.ts +45 -0
- package/src/components/storyboard/useFrameComments.ts +22 -6
- package/src/contexts/StudioContext.tsx +4 -0
- package/src/contexts/ViewModeContext.tsx +60 -7
- package/src/hooks/timelineEditingHelpers.ts +10 -4
- package/src/hooks/useAddAssetAtPlayhead.test.ts +44 -0
- package/src/hooks/useAddAssetAtPlayhead.ts +21 -0
- package/src/hooks/usePreviewPersistence.ts +7 -1
- package/src/hooks/useRazorSplit.history.test.tsx +21 -28
- package/src/hooks/useRazorSplit.test.tsx +20 -9
- package/src/hooks/useRazorSplit.testHelpers.ts +34 -22
- package/src/hooks/useRazorSplit.ts +78 -350
- package/src/hooks/useSlideshowTabState.test.ts +96 -0
- package/src/hooks/useSlideshowTabState.ts +61 -0
- package/src/hooks/useTimelineAddAtPlayhead.ts +15 -0
- package/src/hooks/useTimelineAssetDropOps.ts +52 -2
- package/src/hooks/useTimelineEditing.ts +19 -17
- package/src/hooks/useTimelineGroupEditing.ts +6 -7
- package/src/player/components/Timeline.tsx +12 -1
- package/src/player/components/timelineAuthoredMoveTarget.ts +77 -0
- package/src/player/components/timelineAuthoredTrack.ts +29 -0
- package/src/player/components/timelineCallbacks.ts +4 -0
- package/src/player/components/timelineClipDragCommit.test.ts +176 -0
- package/src/player/components/timelineClipDragCommit.ts +77 -105
- package/src/player/components/timelineClipDragPreview.test.ts +33 -1
- package/src/player/components/timelineClipDragPreview.ts +2 -1
- package/src/player/components/timelineCollision.test.ts +18 -0
- package/src/player/components/timelineCollision.ts +12 -9
- package/src/player/components/timelineDragDrop.ts +28 -7
- package/src/player/components/timelineEditCapabilities.ts +4 -1
- package/src/player/components/timelineGroupEditing.test.ts +10 -0
- package/src/player/components/timelineGroupEditing.ts +4 -3
- package/src/player/components/timelineLaneMoveRefresh.ts +14 -0
- package/src/player/components/timelineLayout.ts +3 -1
- package/src/player/components/timelineZMirror.ts +1 -1
- package/src/player/components/useTimelineClipDrag.ts +5 -0
- package/src/player/components/useTimelineEditPinning.ts +12 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +42 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +3 -0
- package/src/player/lib/playbackTypes.ts +9 -0
- package/src/player/lib/runtimeProtocol.test.ts +6 -1
- package/src/player/lib/timelineDOM.test.ts +58 -0
- package/src/player/lib/timelineDOM.ts +26 -12
- package/src/player/lib/timelineElementHelpers.ts +21 -11
- package/src/player/lib/timelineIframeHelpers.ts +153 -107
- package/src/player/store/playerStore.ts +2 -1
- package/src/utils/razorSplitTransaction.test.ts +175 -0
- package/src/utils/razorSplitTransaction.ts +200 -0
- package/src/utils/studioPreviewHelpers.test.ts +27 -0
- package/src/utils/timelineCompositionDrop.test.ts +12 -0
- package/src/utils/timelineCompositionDrop.ts +16 -0
- package/src/utils/timelineCompositionInsert.test.ts +142 -0
- package/src/utils/timelineCompositionInsert.ts +90 -0
- package/src/utils/timelineElementSplit.test.ts +14 -2
- package/src/utils/timelineElementSplit.ts +5 -1
- package/dist/assets/hyperframes-player-3XTTaVNf.js +0 -459
- package/dist/assets/index-DXbu6IPT.css +0 -1
- package/dist/assets/index-cH6NfVV_.js +0 -426
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +0 -23
|
@@ -197,11 +197,11 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
|
|
197
197
|
const manualSizeEditingDisabled = !element.capabilities.canApplyManualSize;
|
|
198
198
|
const manualRotationEditingDisabled = !element.capabilities.canApplyManualRotation;
|
|
199
199
|
const sourceLabel = element.id ? `#${element.id}` : (element.selector ?? "");
|
|
200
|
-
const showEditableSections = element.capabilities.canEditStyles;
|
|
201
200
|
// Capabilities are already resolved on the selection; recompute only sections,
|
|
202
201
|
// feeding the live GSAP tween count (arrives on the gsapAnimations prop, not the
|
|
203
202
|
// selection) so the Timing section shows for pure-GSAP elements with no data-start.
|
|
204
203
|
const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
|
|
204
|
+
const showEditableSections = element.capabilities.canEditStyles && sections.style;
|
|
205
205
|
const manualOffset = readStudioPathOffset(element.element);
|
|
206
206
|
const manualSize = readStudioBoxSize(element.element);
|
|
207
207
|
const resolvedWidth =
|
|
@@ -385,161 +385,163 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
|
|
385
385
|
/>
|
|
386
386
|
)}
|
|
387
387
|
|
|
388
|
-
|
|
389
|
-
<
|
|
390
|
-
<div className=
|
|
391
|
-
<div className="flex-1">
|
|
392
|
-
<
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
{sections.layout && (
|
|
389
|
+
<Section title="Layout" icon={<Move size={15} />}>
|
|
390
|
+
<div className={RESPONSIVE_GRID}>
|
|
391
|
+
<div className="flex items-center gap-1">
|
|
392
|
+
<div className="flex-1">
|
|
393
|
+
<MetricField
|
|
394
|
+
label="X"
|
|
395
|
+
value={formatPxMetricValue(displayX)}
|
|
396
|
+
disabled={manualOffsetEditingDisabled}
|
|
397
|
+
scrub
|
|
398
|
+
onCommit={(next) => commitManualOffset("x", next)}
|
|
399
|
+
/>
|
|
400
|
+
</div>
|
|
401
|
+
{STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
|
|
402
|
+
<KeyframeNavigation
|
|
403
|
+
property="x"
|
|
404
|
+
keyframes={navKeyframes}
|
|
405
|
+
currentPercentage={currentPct}
|
|
406
|
+
onSeek={seekFromKfPct}
|
|
407
|
+
onAddKeyframe={() =>
|
|
408
|
+
onCommitAnimatedProperty &&
|
|
409
|
+
void onCommitAnimatedProperty(element, "x", displayX)
|
|
410
|
+
}
|
|
411
|
+
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("x"), pct)}
|
|
412
|
+
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("x"))}
|
|
413
|
+
/>
|
|
414
|
+
)}
|
|
399
415
|
</div>
|
|
400
|
-
|
|
401
|
-
<
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
416
|
+
<div className="flex items-center gap-1">
|
|
417
|
+
<div className="flex-1">
|
|
418
|
+
<MetricField
|
|
419
|
+
label="Y"
|
|
420
|
+
value={formatPxMetricValue(displayY)}
|
|
421
|
+
disabled={manualOffsetEditingDisabled}
|
|
422
|
+
scrub
|
|
423
|
+
onCommit={(next) => commitManualOffset("y", next)}
|
|
424
|
+
/>
|
|
425
|
+
</div>
|
|
426
|
+
{STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
|
|
427
|
+
<KeyframeNavigation
|
|
428
|
+
property="y"
|
|
429
|
+
keyframes={navKeyframes}
|
|
430
|
+
currentPercentage={currentPct}
|
|
431
|
+
onSeek={seekFromKfPct}
|
|
432
|
+
onAddKeyframe={() =>
|
|
433
|
+
onCommitAnimatedProperty &&
|
|
434
|
+
void onCommitAnimatedProperty(element, "y", displayY)
|
|
435
|
+
}
|
|
436
|
+
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("y"), pct)}
|
|
437
|
+
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("y"))}
|
|
438
|
+
/>
|
|
439
|
+
)}
|
|
424
440
|
</div>
|
|
425
|
-
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
441
|
+
<div className="flex items-center gap-1">
|
|
442
|
+
<div className="flex-1">
|
|
443
|
+
<MetricField
|
|
444
|
+
label="W"
|
|
445
|
+
value={formatPxMetricValue(displayW)}
|
|
446
|
+
disabled={manualSizeEditingDisabled}
|
|
447
|
+
scrub
|
|
448
|
+
onCommit={(next) => commitManualSize("width", next)}
|
|
449
|
+
/>
|
|
450
|
+
</div>
|
|
451
|
+
{STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
|
|
452
|
+
<KeyframeNavigation
|
|
453
|
+
property="width"
|
|
454
|
+
keyframes={navKeyframes}
|
|
455
|
+
currentPercentage={currentPct}
|
|
456
|
+
onSeek={seekFromKfPct}
|
|
457
|
+
onAddKeyframe={() =>
|
|
458
|
+
onCommitAnimatedProperty &&
|
|
459
|
+
void onCommitAnimatedProperty(element, "width", displayW)
|
|
460
|
+
}
|
|
461
|
+
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("width"), pct)}
|
|
462
|
+
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("width"))}
|
|
463
|
+
/>
|
|
464
|
+
)}
|
|
449
465
|
</div>
|
|
450
|
-
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
466
|
+
<div className="flex items-center gap-1">
|
|
467
|
+
<div className="flex-1">
|
|
468
|
+
<MetricField
|
|
469
|
+
label="H"
|
|
470
|
+
value={formatPxMetricValue(displayH)}
|
|
471
|
+
disabled={manualSizeEditingDisabled}
|
|
472
|
+
scrub
|
|
473
|
+
onCommit={(next) => commitManualSize("height", next)}
|
|
474
|
+
/>
|
|
475
|
+
</div>
|
|
476
|
+
{STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
|
|
477
|
+
<KeyframeNavigation
|
|
478
|
+
property="height"
|
|
479
|
+
keyframes={navKeyframes}
|
|
480
|
+
currentPercentage={currentPct}
|
|
481
|
+
onSeek={seekFromKfPct}
|
|
482
|
+
onAddKeyframe={() =>
|
|
483
|
+
onCommitAnimatedProperty &&
|
|
484
|
+
void onCommitAnimatedProperty(element, "height", displayH)
|
|
485
|
+
}
|
|
486
|
+
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("height"), pct)}
|
|
487
|
+
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("height"))}
|
|
488
|
+
/>
|
|
489
|
+
)}
|
|
474
490
|
</div>
|
|
475
|
-
|
|
476
|
-
<
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
491
|
+
<div className="flex items-center gap-1">
|
|
492
|
+
<div className="flex-1">
|
|
493
|
+
<MetricField
|
|
494
|
+
label="R"
|
|
495
|
+
value={`${displayR}°`}
|
|
496
|
+
disabled={manualRotationEditingDisabled}
|
|
497
|
+
onCommit={(next) => commitManualRotation(next.replace("°", ""))}
|
|
498
|
+
/>
|
|
499
|
+
</div>
|
|
500
|
+
{STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
|
|
501
|
+
<KeyframeNavigation
|
|
502
|
+
property="rotation"
|
|
503
|
+
keyframes={navKeyframes}
|
|
504
|
+
currentPercentage={currentPct}
|
|
505
|
+
onSeek={seekFromKfPct}
|
|
506
|
+
onAddKeyframe={() =>
|
|
507
|
+
onCommitAnimatedProperty &&
|
|
508
|
+
void onCommitAnimatedProperty(element, "rotation", displayR)
|
|
509
|
+
}
|
|
510
|
+
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("rotation"), pct)}
|
|
511
|
+
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("rotation"))}
|
|
512
|
+
/>
|
|
513
|
+
)}
|
|
498
514
|
</div>
|
|
499
|
-
{STUDIO_KEYFRAMES_ENABLED && gsapAnimId && (
|
|
500
|
-
<KeyframeNavigation
|
|
501
|
-
property="rotation"
|
|
502
|
-
keyframes={navKeyframes}
|
|
503
|
-
currentPercentage={currentPct}
|
|
504
|
-
onSeek={seekFromKfPct}
|
|
505
|
-
onAddKeyframe={() =>
|
|
506
|
-
onCommitAnimatedProperty &&
|
|
507
|
-
void onCommitAnimatedProperty(element, "rotation", displayR)
|
|
508
|
-
}
|
|
509
|
-
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp("rotation"), pct)}
|
|
510
|
-
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp("rotation"))}
|
|
511
|
-
/>
|
|
512
|
-
)}
|
|
513
515
|
</div>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
onLivePreviewProps={createGsapLivePreview(iframeRef)}
|
|
530
|
-
/>
|
|
531
|
-
<div className="mt-3">
|
|
532
|
-
<div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
|
|
533
|
-
Stacking
|
|
534
|
-
</div>
|
|
535
|
-
<MetricField
|
|
536
|
-
label="Z-index"
|
|
537
|
-
value={String(parseInt(styles["z-index"] || "auto", 10) || 0)}
|
|
538
|
-
scrub
|
|
539
|
-
onCommit={(next) => onSetStyle("z-index", next)}
|
|
516
|
+
<PropertyPanel3dTransform
|
|
517
|
+
gsapRuntimeValues={gsap3dValues}
|
|
518
|
+
gsapAnimId={gsapAnimId}
|
|
519
|
+
resolveAnimIdForProp={animIdForProp}
|
|
520
|
+
gsapKeyframes={navKeyframes}
|
|
521
|
+
currentPct={currentPct}
|
|
522
|
+
elStart={elStart}
|
|
523
|
+
elDuration={elDuration}
|
|
524
|
+
element={element}
|
|
525
|
+
onCommitAnimatedProperty={onCommitAnimatedProperty}
|
|
526
|
+
onCommitAnimatedProperties={onCommitAnimatedProperties}
|
|
527
|
+
onSeekToTime={onSeekToTime}
|
|
528
|
+
onRemoveKeyframe={onRemoveKeyframe}
|
|
529
|
+
onConvertToKeyframes={onConvertToKeyframes}
|
|
530
|
+
onLivePreviewProps={createGsapLivePreview(iframeRef)}
|
|
540
531
|
/>
|
|
541
|
-
|
|
542
|
-
|
|
532
|
+
<div className="mt-3">
|
|
533
|
+
<div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
|
|
534
|
+
Stacking
|
|
535
|
+
</div>
|
|
536
|
+
<MetricField
|
|
537
|
+
label="Z-index"
|
|
538
|
+
value={String(parseInt(styles["z-index"] || "auto", 10) || 0)}
|
|
539
|
+
scrub
|
|
540
|
+
onCommit={(next) => onSetStyle("z-index", next)}
|
|
541
|
+
/>
|
|
542
|
+
</div>
|
|
543
|
+
</Section>
|
|
544
|
+
)}
|
|
543
545
|
|
|
544
546
|
{STUDIO_GSAP_PANEL_ENABLED &&
|
|
545
547
|
onUpdateGsapProperty &&
|
|
@@ -78,6 +78,7 @@ export function PropertyPanelFlat({
|
|
|
78
78
|
onClearSelection,
|
|
79
79
|
onUngroup,
|
|
80
80
|
onSetStyle,
|
|
81
|
+
onPreviewStyle,
|
|
81
82
|
onSetAttribute,
|
|
82
83
|
onSetAttributes,
|
|
83
84
|
onSetAttributeLive,
|
|
@@ -86,6 +87,7 @@ export function PropertyPanelFlat({
|
|
|
86
87
|
onRemoveBackground,
|
|
87
88
|
onSetText,
|
|
88
89
|
onSetTextFieldStyle,
|
|
90
|
+
onPreviewTextFieldStyle,
|
|
89
91
|
onAddTextField,
|
|
90
92
|
onRemoveTextField,
|
|
91
93
|
onAskAgent,
|
|
@@ -147,6 +149,7 @@ export function PropertyPanelFlat({
|
|
|
147
149
|
| "onClearSelection"
|
|
148
150
|
| "onUngroup"
|
|
149
151
|
| "onSetStyle"
|
|
152
|
+
| "onPreviewStyle"
|
|
150
153
|
| "onSetAttribute"
|
|
151
154
|
| "onSetAttributes"
|
|
152
155
|
| "onSetAttributeLive"
|
|
@@ -155,6 +158,7 @@ export function PropertyPanelFlat({
|
|
|
155
158
|
| "onRemoveBackground"
|
|
156
159
|
| "onSetText"
|
|
157
160
|
| "onSetTextFieldStyle"
|
|
161
|
+
| "onPreviewTextFieldStyle"
|
|
158
162
|
| "onAddTextField"
|
|
159
163
|
| "onRemoveTextField"
|
|
160
164
|
| "onAskAgent"
|
|
@@ -360,6 +364,7 @@ export function PropertyPanelFlat({
|
|
|
360
364
|
onImportFonts={onImportFonts}
|
|
361
365
|
onSetText={onSetText}
|
|
362
366
|
onSetTextFieldStyle={onSetTextFieldStyle}
|
|
367
|
+
onPreviewTextFieldStyle={onPreviewTextFieldStyle}
|
|
363
368
|
onAddTextField={onAddTextField}
|
|
364
369
|
onRemoveTextField={onRemoveTextField}
|
|
365
370
|
/>
|
|
@@ -382,54 +387,57 @@ export function PropertyPanelFlat({
|
|
|
382
387
|
styles={styles}
|
|
383
388
|
assets={assets}
|
|
384
389
|
onSetStyle={onSetStyle}
|
|
390
|
+
onPreviewStyle={onPreviewStyle}
|
|
385
391
|
onImportAssets={onImportAssets}
|
|
386
392
|
gsapBorderRadius={gsapBorderRadius}
|
|
387
393
|
/>
|
|
388
394
|
),
|
|
389
395
|
});
|
|
390
396
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
397
|
+
if (sections.layout) {
|
|
398
|
+
groups.push({
|
|
399
|
+
id: "layout",
|
|
400
|
+
title: "Layout",
|
|
401
|
+
// No scrub accessory: FlatRow/CommitField has no pointer-drag scrubbing
|
|
402
|
+
// (wheel/arrow keys only) — advertising "drag values to scrub" here lies.
|
|
403
|
+
summary: `${formatPxMetricValue(displayX)},${formatPxMetricValue(displayY)} · ${Math.round(displayW)}×${Math.round(displayH)}`,
|
|
404
|
+
content: (
|
|
405
|
+
<FlatLayoutSection
|
|
406
|
+
element={element}
|
|
407
|
+
styles={styles}
|
|
408
|
+
onSetStyle={onSetStyle}
|
|
409
|
+
disabled={!element.capabilities.canEditStyles}
|
|
410
|
+
displayX={displayX}
|
|
411
|
+
displayY={displayY}
|
|
412
|
+
displayW={displayW}
|
|
413
|
+
displayH={displayH}
|
|
414
|
+
displayR={displayR}
|
|
415
|
+
manualOffsetEditingDisabled={manualOffsetEditingDisabled}
|
|
416
|
+
manualSizeEditingDisabled={manualSizeEditingDisabled}
|
|
417
|
+
manualRotationEditingDisabled={manualRotationEditingDisabled}
|
|
418
|
+
commitManualOffset={commitManualOffset}
|
|
419
|
+
commitManualSize={commitManualSize}
|
|
420
|
+
commitManualRotation={commitManualRotation}
|
|
421
|
+
gsapAnimId={gsapAnimId}
|
|
422
|
+
navKeyframes={navKeyframes}
|
|
423
|
+
currentPct={currentPct}
|
|
424
|
+
seekFromKfPct={seekFromKfPct}
|
|
425
|
+
animIdForProp={animIdForProp}
|
|
426
|
+
resolveAnimIdForProp={animIdForProp}
|
|
427
|
+
gsapRuntimeValues={gsapRuntimeValues}
|
|
428
|
+
gsapKeyframes={navKeyframes}
|
|
429
|
+
elStart={elStart}
|
|
430
|
+
elDuration={elDuration}
|
|
431
|
+
onCommitAnimatedProperty={onCommitAnimatedProperty}
|
|
432
|
+
onCommitAnimatedProperties={onCommitAnimatedProperties}
|
|
433
|
+
onSeekToTime={onSeekToTime}
|
|
434
|
+
onRemoveKeyframe={onRemoveKeyframe}
|
|
435
|
+
onConvertToKeyframes={onConvertToKeyframes}
|
|
436
|
+
onLivePreviewProps={createGsapLivePreview(previewIframeRef ?? { current: null })}
|
|
437
|
+
/>
|
|
438
|
+
),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
433
441
|
if (showMotionGroup) {
|
|
434
442
|
groups.push({
|
|
435
443
|
id: "motion",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { patchElementInHtml } from "@hyperframes/studio-server/source-mutation";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { buildDomEditStylePatchOperation } from "./domEditing";
|
|
8
|
+
|
|
9
|
+
const fixtureDir = join(
|
|
10
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
11
|
+
"../../../tests/e2e/fixtures/composition-reliability",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
function fixture(path: string): string {
|
|
15
|
+
return readFileSync(join(fixtureDir, path), "utf8");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parse(html: string): Document {
|
|
19
|
+
return new DOMParser().parseFromString(html, "text/html");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function inTemplate(document: Document, selector: string): Element | null {
|
|
23
|
+
for (const template of Array.from(document.querySelectorAll("template"))) {
|
|
24
|
+
const match = template.content.querySelector(selector);
|
|
25
|
+
if (match) return match;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("composition reliability acceptance fixture", () => {
|
|
31
|
+
const indexSource = fixture("index.html");
|
|
32
|
+
const titleSource = fixture("compositions/title-card.html");
|
|
33
|
+
const nestedSource = fixture("compositions/nested-shell.html");
|
|
34
|
+
|
|
35
|
+
it("owns repeated root hosts, a nested host, transparent headline topology, and collisions", () => {
|
|
36
|
+
const index = parse(indexSource);
|
|
37
|
+
const repeated = Array.from(
|
|
38
|
+
index.querySelectorAll('[data-composition-src="compositions/title-card.html"]'),
|
|
39
|
+
);
|
|
40
|
+
expect(repeated).toHaveLength(2);
|
|
41
|
+
expect(repeated.map((host) => host.getAttribute("data-start"))).toEqual(["0", "4"]);
|
|
42
|
+
expect(
|
|
43
|
+
index.querySelector('[data-composition-src="compositions/nested-shell.html"]'),
|
|
44
|
+
).toBeTruthy();
|
|
45
|
+
|
|
46
|
+
const nested = parse(nestedSource);
|
|
47
|
+
const nestedHost = inTemplate(nested, '[data-composition-src="title-card.html"]');
|
|
48
|
+
expect(nestedHost).toBeTruthy();
|
|
49
|
+
const nestedDependency = nestedHost?.getAttribute("data-composition-src");
|
|
50
|
+
expect(
|
|
51
|
+
nestedDependency ? existsSync(resolve(fixtureDir, "compositions", nestedDependency)) : false,
|
|
52
|
+
).toBe(true);
|
|
53
|
+
|
|
54
|
+
const title = parse(titleSource);
|
|
55
|
+
const mask = inTemplate(title, ".hl-mask");
|
|
56
|
+
const headline = inTemplate(title, ".hl-mask > .hl-text");
|
|
57
|
+
expect(mask?.textContent).toContain("Reliable compositions");
|
|
58
|
+
expect(inTemplate(title, "style")?.textContent).toMatch(
|
|
59
|
+
/\.hl-mask\s*\{[^}]*overflow:\s*hidden;[^}]*background:\s*transparent;/,
|
|
60
|
+
);
|
|
61
|
+
expect(headline?.tagName).toBe("H1");
|
|
62
|
+
|
|
63
|
+
const collisionA = index.querySelector('[data-hf-id="collision-a"]')!;
|
|
64
|
+
const collisionB = index.querySelector('[data-hf-id="collision-b"]')!;
|
|
65
|
+
const layered = index.querySelector('[data-hf-id="layer-overlap"]')!;
|
|
66
|
+
expect(collisionA.getAttribute("data-track-index")).toBe(
|
|
67
|
+
collisionB.getAttribute("data-track-index"),
|
|
68
|
+
);
|
|
69
|
+
expect(
|
|
70
|
+
Number(collisionA.getAttribute("data-start")) +
|
|
71
|
+
Number(collisionA.getAttribute("data-duration")),
|
|
72
|
+
).toBe(Number(collisionB.getAttribute("data-start")));
|
|
73
|
+
expect(layered.getAttribute("data-start")).toBe(collisionB.getAttribute("data-start"));
|
|
74
|
+
expect(layered.getAttribute("data-track-index")).not.toBe(
|
|
75
|
+
collisionB.getAttribute("data-track-index"),
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("keeps timeline host edits in the root source and headline color in the template source", () => {
|
|
80
|
+
const moved = patchElementInHtml(indexSource, { hfId: "title-host-a" }, [
|
|
81
|
+
{ type: "attribute", property: "start", value: "5" },
|
|
82
|
+
{ type: "attribute", property: "duration", value: "2" },
|
|
83
|
+
]);
|
|
84
|
+
expect(moved.matched).toBe(true);
|
|
85
|
+
expect(moved.html).toContain('data-hf-id="title-host-a"');
|
|
86
|
+
expect(moved.html).toContain('data-start="5"');
|
|
87
|
+
expect(moved.html).toContain('data-duration="2"');
|
|
88
|
+
expect(moved.html).not.toContain('data-hf-id="title-text"');
|
|
89
|
+
expect(titleSource).not.toContain("#12b886");
|
|
90
|
+
|
|
91
|
+
const recolored = patchElementInHtml(titleSource, { hfId: "title-text" }, [
|
|
92
|
+
buildDomEditStylePatchOperation("color", "#12b886"),
|
|
93
|
+
]);
|
|
94
|
+
expect(recolored.matched).toBe(true);
|
|
95
|
+
const recoloredDocument = parse(recolored.html);
|
|
96
|
+
expect(
|
|
97
|
+
inTemplate(recoloredDocument, '[data-hf-id="title-text"]')?.getAttribute("style"),
|
|
98
|
+
).toContain("color: #12b886");
|
|
99
|
+
expect(
|
|
100
|
+
inTemplate(recoloredDocument, '[data-hf-id="title-mask"]')?.getAttribute("style"),
|
|
101
|
+
).toBeNull();
|
|
102
|
+
expect(recolored.html).not.toContain('data-hf-id="title-host-a"');
|
|
103
|
+
expect(indexSource).not.toContain("#12b886");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -739,6 +739,40 @@ describe("resolveDomEditSelection", () => {
|
|
|
739
739
|
expect(selection?.selector).toBe("#copy");
|
|
740
740
|
});
|
|
741
741
|
|
|
742
|
+
it("keeps a transparent overflow mask structural when directly selecting its headline", async () => {
|
|
743
|
+
const document = createDocument(`
|
|
744
|
+
<template id="source-template"></template>
|
|
745
|
+
<section class="hl-block">
|
|
746
|
+
<div class="hl-mask" style="overflow: hidden; background: transparent">
|
|
747
|
+
<h1 class="hl-text">Launch title</h1>
|
|
748
|
+
</div>
|
|
749
|
+
</section>
|
|
750
|
+
`);
|
|
751
|
+
const headline = document.querySelector<HTMLElement>(".hl-text")!;
|
|
752
|
+
setElementRect(headline, { left: 44, top: 52, width: 220, height: 48 });
|
|
753
|
+
const selection = await resolveDomEditSelection(headline, {
|
|
754
|
+
activeCompositionPath: "index.html",
|
|
755
|
+
isMasterView: false,
|
|
756
|
+
preferClipAncestor: false,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
expect(selection?.element).toBe(headline);
|
|
760
|
+
expect(selection?.selector).toBe(".hl-text");
|
|
761
|
+
expect(selection?.textFields).toMatchObject([{ source: "self", tagName: "h1" }]);
|
|
762
|
+
expect(selection?.boundingBox).toEqual({ x: 44, y: 52, width: 220, height: 48 });
|
|
763
|
+
// Explicit layer navigation remains free to resolve the structural mask.
|
|
764
|
+
const mask = document.querySelector<HTMLElement>(".hl-mask")!;
|
|
765
|
+
expect(
|
|
766
|
+
(
|
|
767
|
+
await resolveDomEditSelection(mask, {
|
|
768
|
+
activeCompositionPath: "index.html",
|
|
769
|
+
isMasterView: false,
|
|
770
|
+
preferClipAncestor: false,
|
|
771
|
+
})
|
|
772
|
+
)?.element,
|
|
773
|
+
).toBe(mask);
|
|
774
|
+
});
|
|
775
|
+
|
|
742
776
|
// fallow-ignore-next-line code-duplication
|
|
743
777
|
it("collects simple child text blocks as separate editable fields", async () => {
|
|
744
778
|
const document = createDocument(`
|
|
@@ -106,13 +106,13 @@ describe("manual editing availability", () => {
|
|
|
106
106
|
expect(resolveStudioBooleanEnvFlag({ UNKNOWN: "maybe" }, ["UNKNOWN"], false)).toBe(false);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
it("defaults the flat inspector flag to
|
|
110
|
-
const
|
|
111
|
-
expect(
|
|
109
|
+
it("defaults the flat inspector flag to true and honors an explicit override", async () => {
|
|
110
|
+
const on = await loadAvailabilityWithEnv({});
|
|
111
|
+
expect(on.STUDIO_FLAT_INSPECTOR_ENABLED).toBe(true);
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
VITE_STUDIO_FLAT_INSPECTOR_ENABLED: "
|
|
113
|
+
const off = await loadAvailabilityWithEnv({
|
|
114
|
+
VITE_STUDIO_FLAT_INSPECTOR_ENABLED: "false",
|
|
115
115
|
});
|
|
116
|
-
expect(
|
|
116
|
+
expect(off.STUDIO_FLAT_INSPECTOR_ENABLED).toBe(false);
|
|
117
117
|
});
|
|
118
118
|
});
|