@mieweb/ui 0.11.0-dev.36 → 0.11.0-dev.37

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.
Files changed (100) hide show
  1. package/agent/skills/mieweb-ui-catalog/SKILL.md +5 -3
  2. package/agent/skills/mieweb-ui-catalog/selection-guidance.md +6 -3
  3. package/dist/{chunk-TUYWCUKH.js → chunk-4LKAZW2W.js} +7 -3
  4. package/dist/chunk-4LKAZW2W.js.map +1 -0
  5. package/dist/{chunk-MNXIN4MS.js → chunk-5WA6IYZD.js} +60 -2
  6. package/dist/chunk-5WA6IYZD.js.map +1 -0
  7. package/dist/{chunk-DT3X3RBM.js → chunk-7U7SONM5.js} +3 -3
  8. package/dist/{chunk-DT3X3RBM.js.map → chunk-7U7SONM5.js.map} +1 -1
  9. package/dist/{chunk-OGFSFUIX.cjs → chunk-AHYDJN4H.cjs} +501 -120
  10. package/dist/chunk-AHYDJN4H.cjs.map +1 -0
  11. package/dist/{chunk-C4GEPUE7.js → chunk-CKECOCYJ.js} +2 -2
  12. package/dist/{chunk-C4GEPUE7.js.map → chunk-CKECOCYJ.js.map} +1 -1
  13. package/dist/{chunk-V5VPECJI.js → chunk-CRR7AE5D.js} +11 -7
  14. package/dist/chunk-CRR7AE5D.js.map +1 -0
  15. package/dist/chunk-D3FNZMOB.cjs +4 -0
  16. package/dist/{chunk-GLXTVCHN.cjs.map → chunk-D3FNZMOB.cjs.map} +1 -1
  17. package/dist/{chunk-ZK2R6UGF.cjs → chunk-DCV34MEF.cjs} +4 -4
  18. package/dist/{chunk-ZK2R6UGF.cjs.map → chunk-DCV34MEF.cjs.map} +1 -1
  19. package/dist/chunk-G4VQEQGW.js +3 -0
  20. package/dist/{chunk-RISZWWEQ.js.map → chunk-G4VQEQGW.js.map} +1 -1
  21. package/dist/{chunk-S3LTUSO5.cjs → chunk-GG3IK5TL.cjs} +7 -7
  22. package/dist/{chunk-S3LTUSO5.cjs.map → chunk-GG3IK5TL.cjs.map} +1 -1
  23. package/dist/{chunk-QHCAWDMG.cjs → chunk-HDZQLTBP.cjs} +7 -3
  24. package/dist/chunk-HDZQLTBP.cjs.map +1 -0
  25. package/dist/chunk-JCIQIR5V.js +546 -0
  26. package/dist/chunk-JCIQIR5V.js.map +1 -0
  27. package/dist/{chunk-AIO2GCTL.js → chunk-JUY2JLFL.js} +501 -120
  28. package/dist/chunk-JUY2JLFL.js.map +1 -0
  29. package/dist/{chunk-2S76TRBJ.cjs → chunk-JUYBJLPI.cjs} +4 -4
  30. package/dist/{chunk-2S76TRBJ.cjs.map → chunk-JUYBJLPI.cjs.map} +1 -1
  31. package/dist/{chunk-UDUYZDTA.js → chunk-JWMMWNTT.js} +3 -3
  32. package/dist/{chunk-UDUYZDTA.js.map → chunk-JWMMWNTT.js.map} +1 -1
  33. package/dist/{chunk-RM4CBS7Y.js → chunk-LQ22CY7X.js} +4 -4
  34. package/dist/{chunk-RM4CBS7Y.js.map → chunk-LQ22CY7X.js.map} +1 -1
  35. package/dist/{chunk-AKMTPCLC.cjs → chunk-LTCHD3K5.cjs} +2 -2
  36. package/dist/{chunk-AKMTPCLC.cjs.map → chunk-LTCHD3K5.cjs.map} +1 -1
  37. package/dist/{chunk-GDKOYVX3.cjs → chunk-NZUQL42A.cjs} +11 -7
  38. package/dist/chunk-NZUQL42A.cjs.map +1 -0
  39. package/dist/chunk-QAEAC7HO.cjs +4 -0
  40. package/dist/chunk-QAEAC7HO.cjs.map +1 -0
  41. package/dist/chunk-SCGXD334.js +3 -0
  42. package/dist/chunk-SCGXD334.js.map +1 -0
  43. package/dist/chunk-TZKBW4GA.cjs +578 -0
  44. package/dist/chunk-TZKBW4GA.cjs.map +1 -0
  45. package/dist/{chunk-47AOS5EC.cjs → chunk-XX6PNPF2.cjs} +60 -2
  46. package/dist/chunk-XX6PNPF2.cjs.map +1 -0
  47. package/dist/components/AudioPlayer/index.cjs +7 -6
  48. package/dist/components/AudioPlayer/index.js +2 -1
  49. package/dist/components/MediaEditor/index.cjs +10 -9
  50. package/dist/components/MediaEditor/index.js +5 -4
  51. package/dist/components/MediaFeed/index.cjs +8 -5
  52. package/dist/components/MediaFeed/index.d.cts +33 -0
  53. package/dist/components/MediaFeed/index.d.ts +33 -0
  54. package/dist/components/MediaFeed/index.js +7 -4
  55. package/dist/components/MediaPlayer/index.cjs +13 -6
  56. package/dist/components/MediaPlayer/index.d.cts +103 -5
  57. package/dist/components/MediaPlayer/index.d.ts +103 -5
  58. package/dist/components/MediaPlayer/index.js +6 -3
  59. package/dist/components/Slider/index.cjs +5 -5
  60. package/dist/components/Slider/index.d.cts +6 -0
  61. package/dist/components/Slider/index.d.ts +6 -0
  62. package/dist/components/Slider/index.js +1 -1
  63. package/dist/components/SliderCalculator/index.cjs +4 -4
  64. package/dist/components/SliderCalculator/index.js +2 -2
  65. package/dist/components/SuperChat/index.cjs +16 -14
  66. package/dist/components/SuperChat/index.cjs.map +1 -1
  67. package/dist/components/SuperChat/index.d.cts +2 -2
  68. package/dist/components/SuperChat/index.d.ts +2 -2
  69. package/dist/components/SuperChat/index.js +8 -6
  70. package/dist/components/SuperChat/index.js.map +1 -1
  71. package/dist/components/SuperChat/plugins/index.d.cts +2 -2
  72. package/dist/components/SuperChat/plugins/index.d.ts +2 -2
  73. package/dist/hooks/index.cjs +2 -2
  74. package/dist/hooks/index.js +1 -1
  75. package/dist/index.cjs +73 -68
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.d.cts +187 -90
  78. package/dist/index.d.ts +187 -90
  79. package/dist/index.js +14 -13
  80. package/dist/index.js.map +1 -1
  81. package/dist/styles.css +1 -1
  82. package/dist/tailwind-preset.cjs +4 -4
  83. package/dist/tailwind-preset.js +1 -1
  84. package/dist/{types-DSWbrVkM.d.cts → types-BfVolads.d.cts} +33 -0
  85. package/dist/{types-DSWbrVkM.d.ts → types-BfVolads.d.ts} +33 -0
  86. package/package.json +1 -1
  87. package/dist/chunk-47AOS5EC.cjs.map +0 -1
  88. package/dist/chunk-AIO2GCTL.js.map +0 -1
  89. package/dist/chunk-ECKIFQSD.cjs +0 -183
  90. package/dist/chunk-ECKIFQSD.cjs.map +0 -1
  91. package/dist/chunk-GDKOYVX3.cjs.map +0 -1
  92. package/dist/chunk-GLXTVCHN.cjs +0 -4
  93. package/dist/chunk-MNXIN4MS.js.map +0 -1
  94. package/dist/chunk-OGFSFUIX.cjs.map +0 -1
  95. package/dist/chunk-QHCAWDMG.cjs.map +0 -1
  96. package/dist/chunk-R5P7NAJQ.js +0 -159
  97. package/dist/chunk-R5P7NAJQ.js.map +0 -1
  98. package/dist/chunk-RISZWWEQ.js +0 -3
  99. package/dist/chunk-TUYWCUKH.js.map +0 -1
  100. package/dist/chunk-V5VPECJI.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkECKIFQSD_cjs = require('./chunk-ECKIFQSD.cjs');
3
+ var chunkTZKBW4GA_cjs = require('./chunk-TZKBW4GA.cjs');
4
4
  var chunkGKDHMNUA_cjs = require('./chunk-GKDHMNUA.cjs');
5
5
  var chunkFRPELUI5_cjs = require('./chunk-FRPELUI5.cjs');
6
6
  var chunkYW2AKBLS_cjs = require('./chunk-YW2AKBLS.cjs');
@@ -61,7 +61,8 @@ function NativeFeedMedia({
61
61
  labels,
62
62
  onActivate,
63
63
  playbackPosition,
64
- playbackRequestId
64
+ playbackRequestId,
65
+ overlay
65
66
  }) {
66
67
  const mediaElementRef = React2__namespace.useRef(null);
67
68
  const [state, setState] = React2__namespace.useState("idle");
@@ -77,24 +78,29 @@ function NativeFeedMedia({
77
78
  const attemptId = React2__namespace.useRef(0);
78
79
  const activeRef = React2__namespace.useRef(active);
79
80
  const canSavePosition = React2__namespace.useRef(false);
81
+ const autoAttempt = React2__namespace.useRef(null);
80
82
  activeRef.current = active;
81
- const tryPlay = React2__namespace.useCallback(async () => {
83
+ const tryPlay = React2__namespace.useCallback(async (automatic = false) => {
82
84
  const element = mediaElementRef.current;
83
85
  if (!element || !activeRef.current) return;
84
86
  const attempt = ++attemptId.current;
85
87
  setBlocked(false);
88
+ autoAttempt.current = automatic ? attempt : null;
86
89
  try {
87
90
  await element.play();
88
- if (!activeRef.current || element !== mediaElementRef.current)
91
+ if ((!activeRef.current || element !== mediaElementRef.current) && !chunkTZKBW4GA_cjs.isInPictureInPicture(element))
89
92
  element.pause();
90
93
  } catch (error) {
91
94
  if (!activeRef.current || attempt !== attemptId.current) return;
92
95
  if (error instanceof Error && error.name === "AbortError") return;
93
96
  setBlocked(true);
97
+ } finally {
98
+ if (autoAttempt.current === attempt) autoAttempt.current = null;
94
99
  }
95
100
  }, []);
96
101
  const pauseForOwnership = React2__namespace.useCallback(
97
102
  (element) => {
103
+ if (chunkTZKBW4GA_cjs.isInPictureInPicture(element)) return;
98
104
  if (element && element === mediaElementRef.current && !element.paused)
99
105
  ownershipPause.current = true;
100
106
  element?.pause();
@@ -117,9 +123,13 @@ function NativeFeedMedia({
117
123
  else element.addEventListener("loadedmetadata", restorePosition);
118
124
  }
119
125
  if (active && (latestPlaybackRequest.current === void 0 || latestPlaybackRequest.current === handledPlaybackRequest.current) && (autoPlay || wantsPlayback.current)) {
120
- void tryPlay();
126
+ void tryPlay(!wantsPlayback.current);
121
127
  } else if (!active) {
122
128
  attemptId.current++;
129
+ const fullscreen = document.fullscreenElement;
130
+ if (element && fullscreen && (fullscreen === element || fullscreen === element.closest('[data-slot="media-player-box"]')))
131
+ void document.exitFullscreen?.()?.catch(() => {
132
+ });
123
133
  pauseForOwnership(element);
124
134
  setBlocked(false);
125
135
  }
@@ -138,6 +148,17 @@ function NativeFeedMedia({
138
148
  retryVersion,
139
149
  tryPlay
140
150
  ]);
151
+ React2__namespace.useEffect(() => {
152
+ const element = mediaElementRef.current;
153
+ if (!element) return;
154
+ const landed = () => {
155
+ if (!activeRef.current && !chunkTZKBW4GA_cjs.isInPictureInPicture(element)) element.pause();
156
+ };
157
+ for (const name of chunkTZKBW4GA_cjs.PIP_EVENTS) element.addEventListener(name, landed);
158
+ return () => {
159
+ for (const name of chunkTZKBW4GA_cjs.PIP_EVENTS) element.removeEventListener(name, landed);
160
+ };
161
+ }, [retryVersion]);
141
162
  React2__namespace.useEffect(() => {
142
163
  if (!active || playbackRequestId === void 0 || playbackRequestId === handledPlaybackRequest.current)
143
164
  return;
@@ -158,64 +179,126 @@ function NativeFeedMedia({
158
179
  if (active) void tryPlay();
159
180
  }
160
181
  };
161
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 w-full flex-col items-center justify-center gap-3", children: [
162
- /* @__PURE__ */ jsxRuntime.jsx(
163
- chunkECKIFQSD_cjs.MediaPlayer,
164
- {
165
- src: media.src,
166
- kind: media.kind === "audio" ? "audio" : "video",
167
- mediaElementRef,
168
- muted,
169
- loop,
170
- poster: media.poster,
171
- preload: active ? "metadata" : "none",
172
- controls: active,
173
- "aria-label": title,
174
- onStateChange: (next) => {
175
- setState(next);
176
- if (next === "playing") ownershipPause.current = false;
177
- if (next === "paused") {
178
- if (activeRef.current && !ownershipPause.current) {
179
- wantsPlayback.current = false;
180
- playbackPosition.playRequested = false;
182
+ return /* @__PURE__ */ jsxRuntime.jsxs(
183
+ "div",
184
+ {
185
+ className: chunkS5RVSXDV_cjs.cn(
186
+ "flex h-full min-h-0 w-full flex-col items-center justify-center",
187
+ overlay ? "relative" : "gap-3"
188
+ ),
189
+ children: [
190
+ /* @__PURE__ */ jsxRuntime.jsx(
191
+ chunkTZKBW4GA_cjs.MediaPlayer,
192
+ {
193
+ src: media.src,
194
+ kind: media.kind === "audio" ? "audio" : "video",
195
+ mediaElementRef,
196
+ muted,
197
+ loop,
198
+ poster: media.poster,
199
+ preload: active ? "metadata" : "none",
200
+ controls: active,
201
+ controlsStyle: overlay ? "overlay" : "native",
202
+ "aria-label": title,
203
+ onStateChange: (next) => {
204
+ setState(next);
205
+ if (next === "playing") {
206
+ ownershipPause.current = false;
207
+ setBlocked(false);
208
+ if ((activeRef.current || chunkTZKBW4GA_cjs.isInPictureInPicture(mediaElementRef.current)) && autoAttempt.current !== attemptId.current) {
209
+ wantsPlayback.current = true;
210
+ playbackPosition.playRequested = true;
211
+ }
212
+ }
213
+ if (next === "paused") {
214
+ if ((activeRef.current || chunkTZKBW4GA_cjs.isInPictureInPicture(mediaElementRef.current)) && !ownershipPause.current) {
215
+ wantsPlayback.current = false;
216
+ playbackPosition.playRequested = false;
217
+ }
218
+ ownershipPause.current = false;
219
+ }
220
+ },
221
+ onTimeUpdate: (timeMs) => {
222
+ if (canSavePosition.current) playbackPosition.timeMs = timeMs;
223
+ },
224
+ onRetry: () => {
225
+ ownershipPause.current = false;
226
+ canSavePosition.current = false;
227
+ setState("idle");
228
+ setBlocked(false);
229
+ setRetryVersion((version) => version + 1);
230
+ },
231
+ labels: {
232
+ error: labels.mediaError,
233
+ retry: labels.retry,
234
+ play: labels.play,
235
+ pause: labels.pause,
236
+ mute: labels.mute,
237
+ unmute: labels.unmute,
238
+ seek: labels.seek,
239
+ fullscreen: labels.playerFullscreen,
240
+ exitFullscreen: labels.exitPlayerFullscreen,
241
+ pictureInPicture: labels.pictureInPicture,
242
+ exitPictureInPicture: labels.exitPictureInPicture,
243
+ time: labels.time,
244
+ rate: labels.playerRate,
245
+ speed: labels.playerSpeed,
246
+ showCaptions: labels.showCaptions,
247
+ hideCaptions: labels.hideCaptions
248
+ },
249
+ className: "min-h-0 flex-1 [&_video]:h-full [&_video]:w-full"
250
+ }
251
+ ),
252
+ overlay ? (
253
+ // An item that isn't the active one has no controls yet: its ▶
254
+ // selects it and starts it.
255
+ !active && state !== "error" && state !== "playing" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
256
+ chunkFSARTLUE_cjs.Button,
257
+ {
258
+ type: "button",
259
+ variant: "ghost",
260
+ size: "icon",
261
+ "aria-label": labels.play,
262
+ title: labels.play,
263
+ className: chunkS5RVSXDV_cjs.cn(
264
+ chunkTZKBW4GA_cjs.overlayButtonClass,
265
+ chunkTZKBW4GA_cjs.playBadgeClass,
266
+ "pointer-events-auto pointer-fine:h-14 pointer-fine:w-14"
267
+ ),
268
+ onClick: togglePlayback,
269
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: chunkTZKBW4GA_cjs.playBadgeIconClass, "aria-hidden": "true" })
181
270
  }
182
- ownershipPause.current = false;
271
+ ) })
272
+ ) : state !== "error" && /* @__PURE__ */ jsxRuntime.jsx(
273
+ chunkFSARTLUE_cjs.Button,
274
+ {
275
+ type: "button",
276
+ variant: "secondary",
277
+ size: "sm",
278
+ leftIcon: state === "playing" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "h-4 w-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-4 w-4", "aria-hidden": "true" }),
279
+ onClick: togglePlayback,
280
+ children: state === "playing" ? labels.pause : labels.play
183
281
  }
184
- },
185
- onTimeUpdate: (timeMs) => {
186
- if (canSavePosition.current) playbackPosition.timeMs = timeMs;
187
- },
188
- onRetry: () => {
189
- ownershipPause.current = false;
190
- canSavePosition.current = false;
191
- setState("idle");
192
- setBlocked(false);
193
- setRetryVersion((version) => version + 1);
194
- },
195
- labels: { error: labels.mediaError, retry: labels.retry },
196
- className: "min-h-0 flex-1 [&_video]:h-full [&_video]:w-full"
197
- }
198
- ),
199
- state !== "error" && /* @__PURE__ */ jsxRuntime.jsx(
200
- chunkFSARTLUE_cjs.Button,
201
- {
202
- variant: "secondary",
203
- size: "sm",
204
- leftIcon: state === "playing" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "h-4 w-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-4 w-4", "aria-hidden": "true" }),
205
- onClick: togglePlayback,
206
- children: state === "playing" ? labels.pause : labels.play
207
- }
208
- ),
209
- blocked && /* @__PURE__ */ jsxRuntime.jsx(
210
- "p",
211
- {
212
- role: "status",
213
- className: "text-muted-foreground m-0 text-center text-sm",
214
- children: labels.blockedAutoplay
215
- }
216
- ),
217
- state === "error" && /* @__PURE__ */ jsxRuntime.jsx(SourceLink, { src: media.src, labels })
218
- ] });
282
+ ),
283
+ blocked && state !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
284
+ "p",
285
+ {
286
+ role: "status",
287
+ className: chunkS5RVSXDV_cjs.cn(
288
+ "m-0 text-center text-sm",
289
+ overlay ? (
290
+ // Along the top, clear of the details and actions at the foot
291
+ // and of a host's Back button in the top corner.
292
+ "pointer-events-none absolute inset-x-0 top-3 flex justify-center px-14"
293
+ ) : "text-muted-foreground"
294
+ ),
295
+ children: overlay ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded bg-black/60 px-2 py-1 text-white", children: labels.blockedAutoplay }) : labels.blockedAutoplay
296
+ }
297
+ ),
298
+ state === "error" && /* @__PURE__ */ jsxRuntime.jsx(SourceLink, { src: media.src, labels })
299
+ ]
300
+ }
301
+ );
219
302
  }
220
303
  function ImageFeedMedia({ media, title, labels }) {
221
304
  const [failed, setFailed] = React2__namespace.useState(false);
@@ -320,6 +403,7 @@ function YouTubeFeedMedia({
320
403
  id && !failed ? /* @__PURE__ */ jsxRuntime.jsx(
321
404
  chunkFSARTLUE_cjs.Button,
322
405
  {
406
+ type: "button",
323
407
  variant: "secondary",
324
408
  leftIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-4 w-4", "aria-hidden": "true" }),
325
409
  onClick: () => {
@@ -357,6 +441,21 @@ var defaultLabels = {
357
441
  next: "Next item",
358
442
  play: "Play",
359
443
  pause: "Pause",
444
+ // The player's own controls, in the overlay layout.
445
+ mute: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.mute,
446
+ unmute: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.unmute,
447
+ seek: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.seek,
448
+ playerFullscreen: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.fullscreen,
449
+ exitPlayerFullscreen: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.exitFullscreen,
450
+ pictureInPicture: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.pictureInPicture,
451
+ exitPictureInPicture: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.exitPictureInPicture,
452
+ time: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.time,
453
+ playerRate: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.rate,
454
+ playerSpeed: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.speed,
455
+ showCaptions: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.showCaptions,
456
+ hideCaptions: chunkTZKBW4GA_cjs.defaultMediaControlsLabels.hideCaptions,
457
+ showMore: "Show more",
458
+ showLess: "Show less",
360
459
  blockedAutoplay: "Playback did not start. Press Play to try again.",
361
460
  mediaError: "This media is unavailable.",
362
461
  openSource: "Open source",
@@ -364,6 +463,181 @@ var defaultLabels = {
364
463
  item: (position, total) => `Media item ${position} of ${total}`,
365
464
  position: (position, total) => `${position} / ${total}`
366
465
  };
466
+ var OVERLAY_BAR_SCRIM = "[&_[data-slot=media-controls-bar]]:bg-none [&_[data-slot=media-controls-bar]]:bg-black/55";
467
+ function useControlsBarHeight(ref) {
468
+ const [height, setHeight] = React2__namespace.useState(0);
469
+ React2__namespace.useLayoutEffect(() => {
470
+ const item = ref.current?.closest('[data-slot="item"]');
471
+ if (!item) return;
472
+ let bar = null;
473
+ const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(
474
+ () => setHeight(bar?.getBoundingClientRect().height ?? 0)
475
+ );
476
+ const attach = () => {
477
+ const next = item.querySelector('[data-slot="media-controls-bar"]');
478
+ if (next === bar) return;
479
+ if (bar) resize?.unobserve(bar);
480
+ bar = next;
481
+ if (bar) resize?.observe(bar);
482
+ setHeight(bar?.getBoundingClientRect().height ?? 0);
483
+ };
484
+ attach();
485
+ const mutations = typeof MutationObserver === "undefined" ? null : new MutationObserver(attach);
486
+ mutations?.observe(item, { childList: true, subtree: true });
487
+ return () => {
488
+ mutations?.disconnect();
489
+ resize?.disconnect();
490
+ };
491
+ }, [ref]);
492
+ return height;
493
+ }
494
+ function OverlayDetails({
495
+ author,
496
+ title,
497
+ caption,
498
+ actions,
499
+ fullscreenButton,
500
+ labels,
501
+ classNames
502
+ }) {
503
+ const rootRef = React2__namespace.useRef(null);
504
+ const barHeight = useControlsBarHeight(rootRef);
505
+ const showCaption = caption != null && caption !== "" && !(typeof caption === "string" && caption.trim() === (title ?? "").trim());
506
+ const textId = React2__namespace.useId();
507
+ const titleRef = React2__namespace.useRef(null);
508
+ const captionRef = React2__namespace.useRef(null);
509
+ const [expanded, setExpanded] = React2__namespace.useState(false);
510
+ const [clipped, setClipped] = React2__namespace.useState(false);
511
+ React2__namespace.useLayoutEffect(() => {
512
+ if (expanded) return;
513
+ const titleEl = titleRef.current;
514
+ const captionEl = captionRef.current;
515
+ const measure = () => setClipped(
516
+ !!titleEl && titleEl.scrollWidth > titleEl.clientWidth + 1 || !!captionEl && captionEl.scrollHeight > captionEl.clientHeight + 1
517
+ );
518
+ measure();
519
+ if (typeof ResizeObserver === "undefined") return;
520
+ const observer = new ResizeObserver(measure);
521
+ for (const el of [titleEl, captionEl]) if (el) observer.observe(el);
522
+ return () => observer.disconnect();
523
+ }, [title, caption, expanded]);
524
+ return /* @__PURE__ */ jsxRuntime.jsxs(
525
+ "div",
526
+ {
527
+ ref: rootRef,
528
+ "data-slot": "details",
529
+ "data-expanded": expanded || void 0,
530
+ style: { bottom: barHeight, maxHeight: `calc(100% - ${barHeight}px)` },
531
+ className: chunkS5RVSXDV_cjs.cn(
532
+ "pointer-events-none absolute inset-x-0 z-10 flex items-end justify-between gap-3 bg-gradient-to-t from-black/55 via-black/30 to-transparent ps-3 pe-2 pt-16 pb-3 text-white [text-shadow:0_1px_2px_rgb(0_0_0/0.6)]",
533
+ // A darker scrim behind the full text, which can run over a bright frame.
534
+ expanded && "via-black/55",
535
+ classNames.details
536
+ ),
537
+ children: [
538
+ /* @__PURE__ */ jsxRuntime.jsx(
539
+ "div",
540
+ {
541
+ id: textId,
542
+ className: chunkS5RVSXDV_cjs.cn(
543
+ "flex max-w-xl min-w-0 flex-1 flex-col leading-5",
544
+ expanded && "pointer-events-auto min-h-0 self-stretch overflow-y-auto overscroll-contain"
545
+ ),
546
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto", children: [
547
+ author && /* @__PURE__ */ jsxRuntime.jsxs(
548
+ "div",
549
+ {
550
+ "data-slot": "author",
551
+ className: chunkS5RVSXDV_cjs.cn(
552
+ "mb-1 flex min-w-0 items-center gap-2",
553
+ classNames.author
554
+ ),
555
+ children: [
556
+ /* @__PURE__ */ jsxRuntime.jsx(
557
+ chunkGKDHMNUA_cjs.Avatar,
558
+ {
559
+ name: author.name,
560
+ src: author.avatar,
561
+ alt: "",
562
+ size: "sm",
563
+ className: "shrink-0 ring-1 ring-white/70"
564
+ }
565
+ ),
566
+ /* @__PURE__ */ jsxRuntime.jsx(
567
+ "p",
568
+ {
569
+ dir: "auto",
570
+ className: "m-0 w-fit max-w-full truncate text-sm font-semibold",
571
+ children: author.name
572
+ }
573
+ )
574
+ ]
575
+ }
576
+ ),
577
+ title && /* @__PURE__ */ jsxRuntime.jsx(
578
+ "h3",
579
+ {
580
+ ref: titleRef,
581
+ "data-slot": "title",
582
+ dir: "auto",
583
+ className: chunkS5RVSXDV_cjs.cn(
584
+ "m-0 w-fit max-w-full text-sm font-semibold",
585
+ expanded ? "break-words" : "truncate",
586
+ classNames.title
587
+ ),
588
+ children: title
589
+ }
590
+ ),
591
+ showCaption && // A div, not a p: a host caption can hold block content, and its
592
+ // links stay tappable through the details layer.
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ "div",
595
+ {
596
+ ref: captionRef,
597
+ "data-slot": "caption",
598
+ dir: "auto",
599
+ className: chunkS5RVSXDV_cjs.cn(
600
+ "pointer-events-auto m-0 w-fit max-w-full text-sm break-words text-white/90",
601
+ !expanded && "line-clamp-2",
602
+ classNames.caption
603
+ ),
604
+ children: caption
605
+ }
606
+ ),
607
+ (clipped || expanded) && /* @__PURE__ */ jsxRuntime.jsx(
608
+ "button",
609
+ {
610
+ type: "button",
611
+ "data-slot": "details-toggle",
612
+ "aria-expanded": expanded,
613
+ "aria-controls": textId,
614
+ onClick: () => setExpanded((open) => !open),
615
+ className: "pointer-events-auto mt-0.5 rounded-sm text-sm font-semibold text-white/80 underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-white/70",
616
+ children: expanded ? labels.showLess : labels.showMore
617
+ }
618
+ )
619
+ ] })
620
+ }
621
+ ),
622
+ (actions || fullscreenButton) && // The action column, at the bottom end beside the text.
623
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-auto flex shrink-0 flex-col items-center gap-3", children: [
624
+ actions && /* @__PURE__ */ jsxRuntime.jsx(
625
+ "div",
626
+ {
627
+ "data-slot": "actions",
628
+ className: chunkS5RVSXDV_cjs.cn(
629
+ "flex flex-col items-center gap-3",
630
+ classNames.actions
631
+ ),
632
+ children: actions
633
+ }
634
+ ),
635
+ fullscreenButton
636
+ ] })
637
+ ]
638
+ }
639
+ );
640
+ }
367
641
  function MediaFeedInner({
368
642
  items,
369
643
  getId,
@@ -373,6 +647,8 @@ function MediaFeedInner({
373
647
  getAuthor,
374
648
  renderActions,
375
649
  renderMedia,
650
+ layout = "stacked",
651
+ allowFullscreen: allowFullscreenProp,
376
652
  activeItemId,
377
653
  defaultActiveItemId,
378
654
  onActiveItemChange,
@@ -389,6 +665,8 @@ function MediaFeedInner({
389
665
  ...rest
390
666
  }, forwardedRef) {
391
667
  const labels = { ...defaultLabels, ...labelOverrides };
668
+ const overlay = layout === "overlay";
669
+ const allowFullscreen = allowFullscreenProp ?? !overlay;
392
670
  const prefersReducedMotion = chunkYW2AKBLS_cjs.usePrefersReducedMotion();
393
671
  const allowAutoPlay = autoPlay && !prefersReducedMotion;
394
672
  const ids = items.map(getId);
@@ -508,8 +786,8 @@ function MediaFeedInner({
508
786
  }
509
787
  }, [activeId, idsKey, hasObserver, loading, error]);
510
788
  React2__namespace.useEffect(() => {
511
- if (!items.length) setFullscreen(false);
512
- }, [items.length]);
789
+ if (!items.length || !allowFullscreen) setFullscreen(false);
790
+ }, [items.length, allowFullscreen]);
513
791
  React2__namespace.useEffect(() => {
514
792
  if (loading || error || activeIndex < 0 || !playbackRequest || playbackRequest.itemId !== activeId)
515
793
  return;
@@ -599,12 +877,76 @@ function MediaFeedInner({
599
877
  playbackPosition = { src: media.src, timeMs: 0 };
600
878
  playbackPositions.current.set(id, playbackPosition);
601
879
  }
602
- const title = getTitle?.(item) || labels.item(index + 1, items.length);
880
+ const ownTitle = getTitle?.(item);
881
+ const title = ownTitle || labels.item(index + 1, items.length);
603
882
  const author = getAuthor?.(item);
604
883
  const caption = getCaption?.(item);
605
884
  const active = !loading && !error && pageVisible && (immersive ? fullscreen && id === activeId : !fullscreen && viewportVisible && id === activeId && id === visibleId);
606
885
  const playbackRequestId = active && playbackRequest?.itemId === id && consumedPlaybackRequests.current.get(id) !== playbackRequest.requestId ? playbackRequest.requestId : void 0;
607
- return /* @__PURE__ */ jsxRuntime.jsxs(
886
+ const fullscreenButton = allowFullscreen && !immersive && /* @__PURE__ */ jsxRuntime.jsx(
887
+ chunkFSARTLUE_cjs.Button,
888
+ {
889
+ type: "button",
890
+ variant: "ghost",
891
+ size: "sm",
892
+ "aria-label": labels.fullscreen,
893
+ title: labels.fullscreen,
894
+ className: overlay ? chunkS5RVSXDV_cjs.cn(chunkTZKBW4GA_cjs.overlayButtonClass, "shrink-0 bg-transparent") : void 0,
895
+ onClick: (event) => {
896
+ fullscreenTriggerRef.current = event.currentTarget;
897
+ const open = () => {
898
+ select(id);
899
+ setFullscreen(true);
900
+ };
901
+ const video = event.currentTarget.closest('[data-slot="item"]')?.querySelector("video");
902
+ if (chunkTZKBW4GA_cjs.isInPictureInPicture(video))
903
+ void chunkTZKBW4GA_cjs.exitPictureInPicture(video).then(() => {
904
+ if (chunkTZKBW4GA_cjs.isInPictureInPicture(video)) video?.pause();
905
+ open();
906
+ });
907
+ else open();
908
+ },
909
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4", "aria-hidden": "true" })
910
+ }
911
+ );
912
+ const mediaSlot = /* @__PURE__ */ jsxRuntime.jsx(
913
+ "div",
914
+ {
915
+ "data-slot": "media",
916
+ className: chunkS5RVSXDV_cjs.cn(
917
+ overlay ? (
918
+ // Black letterboxing around the clip, as on VideoCard.
919
+ "absolute inset-0 flex items-center justify-center bg-black"
920
+ ) : "bg-muted flex min-h-0 flex-1 items-center justify-center p-3",
921
+ classNames.media
922
+ ),
923
+ children: renderMedia ? renderMedia(media, {
924
+ item,
925
+ active,
926
+ autoPlay: allowAutoPlay,
927
+ muted,
928
+ loop,
929
+ playbackRequestId
930
+ }) : /* @__PURE__ */ jsxRuntime.jsx(
931
+ FeedMedia,
932
+ {
933
+ media,
934
+ title,
935
+ active,
936
+ autoPlay: allowAutoPlay,
937
+ muted,
938
+ loop,
939
+ labels,
940
+ onActivate: () => select(id),
941
+ playbackPosition,
942
+ playbackRequestId,
943
+ overlay
944
+ },
945
+ `${id}:${media.kind}:${media.src}:${immersive}`
946
+ )
947
+ }
948
+ );
949
+ return /* @__PURE__ */ jsxRuntime.jsx(
608
950
  "article",
609
951
  {
610
952
  ref: immersive ? void 0 : (node) => {
@@ -619,44 +961,29 @@ function MediaFeedInner({
619
961
  "aria-setsize": items.length,
620
962
  tabIndex: id === activeId ? 0 : -1,
621
963
  className: chunkS5RVSXDV_cjs.cn(
622
- "border-border bg-card text-card-foreground focus-visible:ring-ring flex min-h-0 w-full snap-start flex-col overflow-hidden border focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset",
623
- immersive ? "h-full flex-1 border-0" : "h-full shrink-0 rounded-xl",
964
+ overlay ? chunkS5RVSXDV_cjs.cn(
965
+ "relative h-full min-h-0 w-full snap-start snap-always overflow-hidden bg-black focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-white/70",
966
+ OVERLAY_BAR_SCRIM
967
+ ) : "border-border bg-card text-card-foreground focus-visible:ring-ring flex min-h-0 w-full snap-start flex-col overflow-hidden border focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset",
968
+ overlay ? immersive ? "flex-1" : "shrink-0" : immersive ? "h-full flex-1 border-0" : "h-full shrink-0 rounded-xl",
624
969
  classNames.item
625
970
  ),
626
- children: [
971
+ children: overlay ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
627
972
  /* @__PURE__ */ jsxRuntime.jsx(
628
- "div",
973
+ OverlayDetails,
629
974
  {
630
- "data-slot": "media",
631
- className: chunkS5RVSXDV_cjs.cn(
632
- "bg-muted flex min-h-0 flex-1 items-center justify-center p-3",
633
- classNames.media
634
- ),
635
- children: renderMedia ? renderMedia(media, {
636
- item,
637
- active,
638
- autoPlay: allowAutoPlay,
639
- muted,
640
- loop,
641
- playbackRequestId
642
- }) : /* @__PURE__ */ jsxRuntime.jsx(
643
- FeedMedia,
644
- {
645
- media,
646
- title,
647
- active,
648
- autoPlay: allowAutoPlay,
649
- muted,
650
- loop,
651
- labels,
652
- onActivate: () => select(id),
653
- playbackPosition,
654
- playbackRequestId
655
- },
656
- `${id}:${media.kind}:${media.src}:${immersive}`
657
- )
975
+ author,
976
+ title: ownTitle,
977
+ caption,
978
+ actions: renderActions?.(item),
979
+ fullscreenButton,
980
+ labels,
981
+ classNames
658
982
  }
659
983
  ),
984
+ mediaSlot
985
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
986
+ mediaSlot,
660
987
  /* @__PURE__ */ jsxRuntime.jsxs(
661
988
  "div",
662
989
  {
@@ -694,26 +1021,15 @@ function MediaFeedInner({
694
1021
  "h3",
695
1022
  {
696
1023
  "data-slot": "title",
697
- className: chunkS5RVSXDV_cjs.cn("m-0 text-base font-semibold", classNames.title),
1024
+ className: chunkS5RVSXDV_cjs.cn(
1025
+ "m-0 text-base font-semibold",
1026
+ classNames.title
1027
+ ),
698
1028
  children: title
699
1029
  }
700
1030
  )
701
1031
  ] }),
702
- !immersive && /* @__PURE__ */ jsxRuntime.jsx(
703
- chunkFSARTLUE_cjs.Button,
704
- {
705
- variant: "ghost",
706
- size: "sm",
707
- "aria-label": labels.fullscreen,
708
- title: labels.fullscreen,
709
- onClick: (event) => {
710
- fullscreenTriggerRef.current = event.currentTarget;
711
- select(id);
712
- setFullscreen(true);
713
- },
714
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4", "aria-hidden": "true" })
715
- }
716
- )
1032
+ fullscreenButton
717
1033
  ] }),
718
1034
  caption != null && /* @__PURE__ */ jsxRuntime.jsx(
719
1035
  "div",
@@ -740,12 +1056,67 @@ function MediaFeedInner({
740
1056
  ]
741
1057
  }
742
1058
  )
743
- ]
1059
+ ] })
744
1060
  },
745
1061
  id
746
1062
  );
747
1063
  };
748
- const controls = /* @__PURE__ */ jsxRuntime.jsxs(
1064
+ const controls = overlay ? (
1065
+ // Over the middle of the feed's end edge, stacked the way the feed
1066
+ // scrolls, above the actions and the player's bar at the bottom. On a
1067
+ // short screen (a phone held sideways, as SuperChat reckons it) the middle
1068
+ // runs into the actions, so they move to the top end, clear of a host's
1069
+ // Back at the top start.
1070
+ /* @__PURE__ */ jsxRuntime.jsxs(
1071
+ "div",
1072
+ {
1073
+ "data-slot": "controls",
1074
+ className: chunkS5RVSXDV_cjs.cn(
1075
+ "pointer-events-none absolute end-2 top-1/2 z-10 flex -translate-y-1/2 flex-col items-center gap-1.5 [@media(max-height:30rem)]:top-2 [@media(max-height:30rem)]:translate-y-0",
1076
+ classNames.controls
1077
+ ),
1078
+ children: [
1079
+ /* @__PURE__ */ jsxRuntime.jsx(
1080
+ chunkFSARTLUE_cjs.Button,
1081
+ {
1082
+ type: "button",
1083
+ variant: "ghost",
1084
+ size: "icon",
1085
+ "aria-label": labels.previous,
1086
+ title: labels.previous,
1087
+ className: chunkS5RVSXDV_cjs.cn(chunkTZKBW4GA_cjs.overlayButtonClass, "pointer-events-auto"),
1088
+ disabled: activeIndex <= 0,
1089
+ onClick: () => navigate(activeIndex - 1),
1090
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-5 w-5", "aria-hidden": "true" })
1091
+ }
1092
+ ),
1093
+ /* @__PURE__ */ jsxRuntime.jsx(
1094
+ "span",
1095
+ {
1096
+ role: "status",
1097
+ "aria-live": "polite",
1098
+ className: "rounded-full bg-black/45 px-1.5 py-0.5 text-xs whitespace-nowrap text-white tabular-nums backdrop-blur-sm",
1099
+ children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: labels.position(activeIndex + 1, items.length) })
1100
+ }
1101
+ ),
1102
+ /* @__PURE__ */ jsxRuntime.jsx(
1103
+ chunkFSARTLUE_cjs.Button,
1104
+ {
1105
+ type: "button",
1106
+ variant: "ghost",
1107
+ size: "icon",
1108
+ "aria-label": labels.next,
1109
+ title: labels.next,
1110
+ className: chunkS5RVSXDV_cjs.cn(chunkTZKBW4GA_cjs.overlayButtonClass, "pointer-events-auto"),
1111
+ disabled: activeIndex >= items.length - 1,
1112
+ onClick: () => navigate(activeIndex + 1),
1113
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-5 w-5", "aria-hidden": "true" })
1114
+ }
1115
+ )
1116
+ ]
1117
+ }
1118
+ )
1119
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
749
1120
  "div",
750
1121
  {
751
1122
  "data-slot": "controls",
@@ -757,6 +1128,7 @@ function MediaFeedInner({
757
1128
  /* @__PURE__ */ jsxRuntime.jsx(
758
1129
  chunkFSARTLUE_cjs.Button,
759
1130
  {
1131
+ type: "button",
760
1132
  variant: "ghost",
761
1133
  size: "sm",
762
1134
  className: "h-auto min-h-8 flex-1 py-2 [&_[data-slot=button-label]]:whitespace-normal",
@@ -772,12 +1144,13 @@ function MediaFeedInner({
772
1144
  role: "status",
773
1145
  "aria-live": "polite",
774
1146
  className: "text-muted-foreground shrink-0 text-sm whitespace-nowrap",
775
- children: labels.position(activeIndex + 1, items.length)
1147
+ children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: labels.position(activeIndex + 1, items.length) })
776
1148
  }
777
1149
  ),
778
1150
  /* @__PURE__ */ jsxRuntime.jsx(
779
1151
  chunkFSARTLUE_cjs.Button,
780
1152
  {
1153
+ type: "button",
781
1154
  variant: "ghost",
782
1155
  size: "sm",
783
1156
  className: "h-auto min-h-8 flex-1 py-2 [&_[data-slot=button-label]]:whitespace-normal",
@@ -796,8 +1169,11 @@ function MediaFeedInner({
796
1169
  ref: setRoot,
797
1170
  "data-slot": "root",
798
1171
  "data-component": "media-feed",
1172
+ "data-layout": layout,
1173
+ "data-theme": overlay ? "dark" : void 0,
799
1174
  className: chunkS5RVSXDV_cjs.cn(
800
1175
  "bg-background text-foreground flex h-[min(48rem,80dvh)] min-h-0 w-full flex-col",
1176
+ overlay && "relative",
801
1177
  classNames.root,
802
1178
  className
803
1179
  ),
@@ -818,6 +1194,7 @@ function MediaFeedInner({
818
1194
  error && onRetry && /* @__PURE__ */ jsxRuntime.jsx(
819
1195
  chunkFSARTLUE_cjs.Button,
820
1196
  {
1197
+ type: "button",
821
1198
  variant: "outline",
822
1199
  isLoading: retrying,
823
1200
  loadingText: labels.loading,
@@ -842,7 +1219,8 @@ function MediaFeedInner({
842
1219
  tabIndex: 0,
843
1220
  onKeyDown: handleKeyDown,
844
1221
  className: chunkS5RVSXDV_cjs.cn(
845
- "focus-visible:ring-ring relative flex min-h-0 flex-1 snap-y snap-mandatory flex-col gap-4 overflow-y-auto overscroll-contain focus-visible:ring-2 focus-visible:outline-none",
1222
+ "focus-visible:ring-ring relative flex min-h-0 flex-1 snap-y snap-mandatory flex-col overflow-y-auto overscroll-contain focus-visible:ring-2 focus-visible:outline-none",
1223
+ !overlay && "gap-4",
846
1224
  classNames.viewport
847
1225
  ),
848
1226
  children: items.map((item, index) => renderCard(item, index, false))
@@ -853,7 +1231,7 @@ function MediaFeedInner({
853
1231
  /* @__PURE__ */ jsxRuntime.jsxs(
854
1232
  chunkFRPELUI5_cjs.Modal,
855
1233
  {
856
- open: fullscreen && !loading && !error && !!items.length,
1234
+ open: allowFullscreen && fullscreen && !loading && !error && !!items.length,
857
1235
  onOpenChange: setFullscreen,
858
1236
  size: "full",
859
1237
  className: "h-dvh min-h-0 sm:h-[calc(100dvh-2rem)]",
@@ -867,12 +1245,15 @@ function MediaFeedInner({
867
1245
  {
868
1246
  "data-slot": "fullscreen",
869
1247
  "data-component": "media-feed",
1248
+ "data-layout": layout,
1249
+ "data-theme": overlay ? "dark" : void 0,
870
1250
  role: "feed",
871
1251
  "aria-label": labels.feed,
872
1252
  tabIndex: 0,
873
1253
  onKeyDown: handleKeyDown,
874
1254
  className: chunkS5RVSXDV_cjs.cn(
875
1255
  "flex min-h-0 flex-1 flex-col px-2 pb-2 sm:px-4",
1256
+ overlay && "relative",
876
1257
  classNames.fullscreen
877
1258
  ),
878
1259
  children: [
@@ -892,5 +1273,5 @@ var MediaFeed = React2__namespace.forwardRef(MediaFeedInner);
892
1273
  MediaFeed.displayName = "MediaFeed";
893
1274
 
894
1275
  exports.MediaFeed = MediaFeed;
895
- //# sourceMappingURL=chunk-OGFSFUIX.cjs.map
896
- //# sourceMappingURL=chunk-OGFSFUIX.cjs.map
1276
+ //# sourceMappingURL=chunk-AHYDJN4H.cjs.map
1277
+ //# sourceMappingURL=chunk-AHYDJN4H.cjs.map