From 8f61e43d8d2c5c5965d9c9a011acf24063773987 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 25 Jun 2026 04:02:22 +0000 Subject: [PATCH 1/2] feat(slideshow): auto-set interactive on inner player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The slideshow now sets the `interactive` attribute on its inner instances at mount time, so pointer events reach the composition iframe automatically. Removes the agent-compliance burden of having to remember to add `interactive` on every player tag inside a slideshow. Idempotent: an author-supplied `interactive` attribute (any value, including `interactive="false"`) is preserved. A MutationObserver also picks up players inserted dynamically after the initial mount. Standalone player usage outside a slideshow still requires the explicit attribute — that surface is unchanged. Skill guidance at skills/slideshow/SKILL.md updated to reflect the automatic behavior. --- .../slideshow/hyperframes-slideshow.test.ts | 67 +++++++++++++++++++ .../src/slideshow/hyperframes-slideshow.ts | 41 +++++++++++- skills/slideshow/SKILL.md | 4 +- 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/packages/player/src/slideshow/hyperframes-slideshow.test.ts b/packages/player/src/slideshow/hyperframes-slideshow.test.ts index f86b862e28..10e0fb8ef3 100644 --- a/packages/player/src/slideshow/hyperframes-slideshow.test.ts +++ b/packages/player/src/slideshow/hyperframes-slideshow.test.ts @@ -2179,3 +2179,70 @@ describe(" Fix 4 — back affordance (postMessage only; c el.remove(); }); }); + +// --------------------------------------------------------------------------- +// Mechanical `interactive` attribute on inner +// --------------------------------------------------------------------------- +// The slideshow auto-applies the `interactive` attribute to every inner +// , so clickable controls, links, native media controls, +// and custom players inside the composition iframe receive pointer events +// without the author having to remember the attribute. The player's default +// is `pointer-events: none` on the iframe; `interactive` flips it to `auto` +// via the `:host([interactive])` rule in player styles. +// --------------------------------------------------------------------------- +describe(" auto-sets `interactive` on inner ", () => { + beforeEach(async () => { + await import("./hyperframes-slideshow.js"); + }); + + const tick = () => new Promise((r) => setTimeout(r, 0)); + + it("inner gets `interactive` attribute after mount", async () => { + const el = document.createElement("hyperframes-slideshow"); + const player = document.createElement("hyperframes-player"); + el.appendChild(player); + document.body.appendChild(el); + + // Allow the deferred initTimer macrotask to run. + await tick(); + + expect(player.hasAttribute("interactive")).toBe(true); + expect(player.getAttribute("interactive")).toBe(""); + + el.remove(); + }); + + it("preserves an author-set `interactive` attribute value (idempotent)", async () => { + const el = document.createElement("hyperframes-slideshow"); + const player = document.createElement("hyperframes-player"); + // Author explicitly opts out by setting their own value — must not clobber. + player.setAttribute("interactive", "false"); + el.appendChild(player); + document.body.appendChild(el); + + await tick(); + + expect(player.getAttribute("interactive")).toBe("false"); + + el.remove(); + }); + + it("dynamically-inserted children also get `interactive`", async () => { + const el = document.createElement("hyperframes-slideshow"); + document.body.appendChild(el); + + await tick(); + + // Late insertion — picked up by the MutationObserver. + const player = document.createElement("hyperframes-player"); + el.appendChild(player); + + // MutationObserver callbacks deliver on a microtask; flush twice to be safe. + await tick(); + await tick(); + + expect(player.hasAttribute("interactive")).toBe(true); + + el.remove(); + }); +}); diff --git a/packages/player/src/slideshow/hyperframes-slideshow.ts b/packages/player/src/slideshow/hyperframes-slideshow.ts index 167a2d3213..46f8f60923 100644 --- a/packages/player/src/slideshow/hyperframes-slideshow.ts +++ b/packages/player/src/slideshow/hyperframes-slideshow.ts @@ -167,6 +167,7 @@ export class HyperframesSlideshow extends HTMLElement { private initGeneration = 0; private _muted = false; private mediaWireInterval: ReturnType | null = null; + private playerObserver: MutationObserver | null = null; private applyingRemoteMedia = false; private lastMediaTimeBroadcastMs = 0; private audienceMutedPlaybackKeys = new Set(); @@ -215,6 +216,7 @@ export class HyperframesSlideshow extends HTMLElement { window.addEventListener("message", this.onMessage); document.addEventListener("fullscreenchange", this.onFsChange); this.initChannel(); + this.observeInteractivePlayers(); // Defer player-dependent init to a macrotask so that child elements are // parsed before we query for . This matters when the // bundle is loaded synchronously (e.g.