Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/core/ui/composites/TransitionPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ export class TransitionPanel extends UIComponent<TransitionState> {
}

private stepSpeed(direction: number): void {
const effect = this.state.tab === "in" ? this.state.inEffect : this.state.outEffect;
if (!this.supportsSpeedVariants(effect)) {
return;
}

const speeds = TransitionPanel.SPEEDS;
const currentSpeed = this.state.tab === "in" ? this.state.inSpeed : this.state.outSpeed;

Expand All @@ -224,6 +229,14 @@ export class TransitionPanel extends UIComponent<TransitionState> {
return ["slide", "wipe", "carousel"].includes(effect);
}

/**
* Whether the Shotstack transition API exposes Slow/Fast variants for this effect.
* `zoom` is fixed-speed (only `zoom` is valid — not `zoomSlow`/`zoomFast`).
*/
private supportsSpeedVariants(effect: string): boolean {
return Boolean(effect) && effect !== "zoom";
}

private updateUI(): void {
const { tab } = this.state;
const effect = tab === "in" ? this.state.inEffect : this.state.outEffect;
Expand Down Expand Up @@ -257,10 +270,11 @@ export class TransitionPanel extends UIComponent<TransitionState> {
this.speedLabel.textContent = `${speed.toFixed(2)}s`;
}

// Update stepper states
// Update stepper states — disable when the effect has no Slow/Fast API variants
const canAdjustSpeed = this.supportsSpeedVariants(effect);
const speedIdx = TransitionPanel.SPEEDS.indexOf(speed);
if (this.speedDecreaseBtn) this.speedDecreaseBtn.disabled = speedIdx <= 0;
if (this.speedIncreaseBtn) this.speedIncreaseBtn.disabled = speedIdx >= TransitionPanel.SPEEDS.length - 1;
if (this.speedDecreaseBtn) this.speedDecreaseBtn.disabled = !canAdjustSpeed || speedIdx <= 0;
if (this.speedIncreaseBtn) this.speedIncreaseBtn.disabled = !canAdjustSpeed || speedIdx >= TransitionPanel.SPEEDS.length - 1;
}

// ─── Transition Value Parsing/Building ───────────────────────────────────
Expand Down Expand Up @@ -304,6 +318,10 @@ export class TransitionPanel extends UIComponent<TransitionState> {
}

private speedToSuffix(speed: number, effect: string): string {
if (!this.supportsSpeedVariants(effect)) {
return "";
}

const isSlideOrCarousel = effect === "slide" || effect === "carousel";

if (isSlideOrCarousel) {
Expand Down
69 changes: 69 additions & 0 deletions tests/transition-panel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @jest-environment jsdom
*/
import { TransitionPanel } from "../src/core/ui/composites/TransitionPanel";

function createTestContainer(): HTMLDivElement {
const container = document.createElement("div");
document.body.appendChild(container);
return container;
}

function cleanupTestContainer(container: HTMLDivElement): void {
container.remove();
}

function simulateClick(element: Element | null): void {
if (element) {
element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
}
}

describe("TransitionPanel", () => {
let panel: TransitionPanel;
let container: HTMLDivElement;

beforeEach(() => {
panel = new TransitionPanel();
container = createTestContainer();
panel.mount(container);
});

afterEach(() => {
panel.dispose();
cleanupTestContainer(container);
});

describe("zoom transition speed", () => {
it("does not emit zoomSlow or zoomFast when the speed stepper is used", () => {
simulateClick(container.querySelector('[data-effect="zoom"]'));
expect(panel.getClipValue()?.in).toBe("zoom");

const increaseBtn = container.querySelector("[data-speed-increase]") as HTMLButtonElement;
const decreaseBtn = container.querySelector("[data-speed-decrease]") as HTMLButtonElement;
expect(increaseBtn.disabled).toBe(true);
expect(decreaseBtn.disabled).toBe(true);

simulateClick(increaseBtn);
simulateClick(decreaseBtn);

expect(panel.getClipValue()?.in).toBe("zoom");
});

it("still allows Slow/Fast variants for fade", () => {
simulateClick(container.querySelector('[data-effect="fade"]'));
expect(panel.getClipValue()?.in).toBe("fade");

const increaseBtn = container.querySelector("[data-speed-increase]") as HTMLButtonElement;
expect(increaseBtn.disabled).toBe(false);

simulateClick(increaseBtn);
expect(panel.getClipValue()?.in).toBe("fadeSlow");
});

it("strips invalid Slow/Fast suffixes when rebuilding zoom from clip state", () => {
panel.setFromClip({ in: "zoomSlow" });
expect(panel.getClipValue()?.in).toBe("zoom");
});
});
});
Loading