Skip to content

Commit 7d321bb

Browse files
committed
fix(studio): unify property panel audio detection
1 parent 98dc5ba commit 7d321bb

4 files changed

Lines changed: 43 additions & 74 deletions

File tree

packages/studio/src/components/editor/PropertyPanel.test.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ afterEach(() => {
2222
vi.resetModules();
2323
});
2424

25-
function baseElement() {
25+
function baseElement(): NonNullable<PropertyPanelProps["element"]> {
2626
return {
2727
element: document.createElement("div"),
2828
id: "mono-label",
@@ -81,7 +81,7 @@ function nonTextElement() {
8181
// flat multi-field layer list (FlatTextLayerList + FlatTextFieldEditor) —
8282
// must not double-render the "Text" heading (FlatGroup's own heading; this
8383
// component never renders one of its own).
84-
function multiFieldTextElement() {
84+
function multiFieldTextElement(): NonNullable<PropertyPanelProps["element"]> {
8585
const base = baseElement();
8686
return {
8787
...base,
@@ -179,8 +179,10 @@ function sixGroupElement() {
179179

180180
/** An `<audio>` clip: placed on the timeline, but nothing a tween could move. */
181181
function audioClipElement() {
182+
const element = document.createElement("audio");
182183
return {
183184
...baseElement(),
185+
element,
184186
id: "vo-1",
185187
selector: "#vo-1",
186188
label: "Vo 1",
@@ -199,8 +201,10 @@ function audioClipElement() {
199201
* Start/Duration back on a thing that has no range.
200202
*/
201203
function audioBusElement() {
204+
const element = document.createElement("hf-audio-group");
202205
return {
203206
...baseElement(),
207+
element,
204208
id: "voiceover",
205209
selector: "#voiceover",
206210
label: "Voiceover",
@@ -229,7 +233,7 @@ const INFERRED_TIMING_ANIMATION = {
229233

230234
async function renderPanel(
231235
flatEnabled: boolean,
232-
elementOverride: ReturnType<typeof baseElement> = baseElement(),
236+
elementOverride: NonNullable<PropertyPanelProps["element"]> = baseElement(),
233237
propsOverride: Partial<PropertyPanelProps> = {},
234238
currentTime?: number,
235239
) {
@@ -987,6 +991,31 @@ describe("PropertyPanel — flat group entrance animation scoping (fix round)",
987991
});
988992

989993
describe("PropertyPanel — Motion is for things that move", () => {
994+
it.each([
995+
["a custom music tag", () => document.createElement("music")],
996+
[
997+
"an element with an audio source",
998+
() => {
999+
const element = document.createElement("div");
1000+
element.setAttribute("src", "voiceover.mp3");
1001+
return element;
1002+
},
1003+
],
1004+
])("recognizes %s through the shared audio predicate", async (_label, makeElement) => {
1005+
const fixture = {
1006+
...audioClipElement(),
1007+
element: makeElement(),
1008+
tagName: "div",
1009+
};
1010+
const { host, root } = await renderPanel(true, fixture);
1011+
const titles = Array.from(
1012+
host.querySelectorAll<HTMLElement>("[data-flat-group-collapsed], [data-flat-group-open]"),
1013+
).map((node) => node.textContent ?? "");
1014+
expect(titles.some((title) => title.includes("Motion"))).toBe(false);
1015+
expect(titles.some((title) => title.includes("Timing"))).toBe(true);
1016+
act(() => root.unmount());
1017+
});
1018+
9901019
it(
9911020
"calls the section Timing on an audio clip, and offers no tween editor",
9921021
async () => {

packages/studio/src/components/editor/PropertyPanel.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { type PropertyPanelProps } from "./propertyPanelHelpers";
3636
import { GestureRecordPanelButton } from "./GestureRecordControl";
3737
import { PropertyPanelEmptyState } from "./PropertyPanelEmptyState";
3838
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
39-
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
39+
import { isAudioDomElement } from "../../utils/timelineInspector";
4040

4141
// Re-export helpers that external consumers import from this module
4242
export {
@@ -132,8 +132,7 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
132132
* `<hf-audio-group>` has no visual to hide at all, and its mute lives on its
133133
* own row.
134134
*/
135-
const selectedTag = element?.tagName?.toLowerCase();
136-
const audioSelection = selectedTag === "audio" || selectedTag === HF_AUDIO_GROUP_TAG;
135+
const audioSelection = isAudioDomElement(element?.element);
137136
// Live during playback, the store's when paused — see the hook. Shared with the
138137
// audio FX panel, which follows the playhead for the same reason: a value the
139138
// timeline drives has to be shown moving, not frozen at what the attribute says.

packages/studio/src/components/editor/PropertyPanelFlat.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { scopedElementKey } from "../../hooks/gsapKeyframeCacheHelpers";
2-
import { useEffect, useRef, useState } from "react";
2+
import { useEffect, useMemo, useRef, useState } from "react";
33
import { useShallow } from "zustand/react/shallow";
44
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
55
import { slugifyDesignInput } from "../../utils/designInputTracking";
66
import { isTextEditableSelection } from "./domEditing";
77
import type { PropertyPanelFlatProps } from "./propertyPanelFlatProps";
88
import { formatPxMetricValue } from "./propertyPanelHelpers";
99
import { audioFxSummary } from "./audioFxSummary";
10-
import { HF_AUDIO_GROUP_TAG, resolveAudioGroups } from "@hyperframes/core/audio-groups";
10+
import { resolveAudioGroups } from "@hyperframes/core/audio-groups";
1111
import { PropertyPanelFlatHeader } from "./PropertyPanelFlatHeader";
1212
import { PropertyPanelFlatFooter } from "./PropertyPanelFlatFooter";
1313
import { closedGroupHeader, isSelectionHidden } from "./propertyPanelFlatClosedGroup";
@@ -43,6 +43,7 @@ import {
4343
EMPTY_GSAP_EFFECT_HANDLERS,
4444
type FlatGroupDescriptor,
4545
} from "./propertyPanelFlatDescriptors";
46+
import { isAudioDomElement } from "../../utils/timelineInspector";
4647

4748
/** The flat inspector shell with one shared open-group state. */
4849
// fallow-ignore-next-line complexity
@@ -170,6 +171,7 @@ export function PropertyPanelFlat({
170171
timelineSessionEpoch: state.timelineSessionEpoch,
171172
})),
172173
);
174+
const storeElements = usePlayerStore((state) => state.elements);
173175
// Identity of the element THIS panel actually renders (not the store's
174176
// selectedElementId, which flips synchronously on selection while the panel
175177
// still renders the previous element during async DOM-selection resolution):
@@ -282,8 +284,7 @@ export function PropertyPanelFlat({
282284
onSetAllKeyframeEases,
283285
}
284286
: null;
285-
const selectedTag = element.tagName?.toLowerCase();
286-
const audioSelection = selectedTag === "audio" || selectedTag === HF_AUDIO_GROUP_TAG;
287+
const audioSelection = isAudioDomElement(element.element);
287288
// Handlers being wired is necessary but not sufficient: App.tsx always passes
288289
// them, so this alone showed the tween editor for every selection — including
289290
// an `<audio>` clip and an `<hf-audio-group>` bus, neither of which has a
@@ -300,12 +301,13 @@ export function PropertyPanelFlat({
300301
// "in Voiceover" for a member (see `audioFxSummary`). Resolved from the live
301302
// document because membership lives on the members, so the owning group's
302303
// LABEL is not on the selected element.
303-
const audioGroupLabel = ((): string | undefined => {
304+
const audioGroupLabel = useMemo((): string | undefined => {
304305
const doc = element.element?.ownerDocument;
305306
const id = element.id;
306307
if (!doc || !id) return undefined;
307-
return resolveAudioGroups(doc).find((g) => g.memberIds.includes(id))?.label;
308-
})();
308+
return resolveAudioGroups(doc).find((group) => group.memberIds.includes(id))?.label;
309+
// eslint-disable-next-line react-hooks/exhaustive-deps -- store replacement signals live group membership changed
310+
}, [element, storeElements]);
309311

310312
const groups: FlatGroupDescriptor[] = [];
311313
if (isTextEditable) {

packages/studio/src/hooks/useAudioSoloBridge.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)