@mieweb/ui 0.10.0-dev.29 → 0.10.0-dev.30

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 (118) hide show
  1. package/dist/brands/index.cjs +7 -7
  2. package/dist/brands/index.js +2 -2
  3. package/dist/{chunk-JYZW2PZS.cjs → chunk-254QK722.cjs} +7 -7
  4. package/dist/chunk-254QK722.cjs.map +1 -0
  5. package/dist/{chunk-FBLVPM2K.js → chunk-2JICT4A2.js} +26 -10
  6. package/dist/chunk-2JICT4A2.js.map +1 -0
  7. package/dist/{chunk-S45EERW3.cjs → chunk-35Y4ILCU.cjs} +4 -4
  8. package/dist/{chunk-S45EERW3.cjs.map → chunk-35Y4ILCU.cjs.map} +1 -1
  9. package/dist/{chunk-2BARVD4C.js → chunk-3RXZWHCU.js} +3 -3
  10. package/dist/{chunk-2BARVD4C.js.map → chunk-3RXZWHCU.js.map} +1 -1
  11. package/dist/{chunk-MWVW4APN.cjs → chunk-7UTBDFFL.cjs} +4 -4
  12. package/dist/{chunk-MWVW4APN.cjs.map → chunk-7UTBDFFL.cjs.map} +1 -1
  13. package/dist/{chunk-FJ7YPVKV.js → chunk-7X6R3JIA.js} +3 -3
  14. package/dist/{chunk-FJ7YPVKV.js.map → chunk-7X6R3JIA.js.map} +1 -1
  15. package/dist/{chunk-SYRLCTDV.js → chunk-7ZV67VZE.js} +3 -3
  16. package/dist/{chunk-SYRLCTDV.js.map → chunk-7ZV67VZE.js.map} +1 -1
  17. package/dist/chunk-BNH4SXJB.js +874 -0
  18. package/dist/chunk-BNH4SXJB.js.map +1 -0
  19. package/dist/{chunk-N2CJJHHB.cjs → chunk-BUT6MF5K.cjs} +27 -11
  20. package/dist/chunk-BUT6MF5K.cjs.map +1 -0
  21. package/dist/{chunk-6VFDHJSO.cjs → chunk-COM7RNZU.cjs} +2 -2
  22. package/dist/{chunk-6VFDHJSO.cjs.map → chunk-COM7RNZU.cjs.map} +1 -1
  23. package/dist/{chunk-SQHMGT4W.js → chunk-GW4RFJZ5.js} +7 -7
  24. package/dist/chunk-GW4RFJZ5.js.map +1 -0
  25. package/dist/{chunk-JID7AZTO.cjs → chunk-H3ROMJYI.cjs} +4 -4
  26. package/dist/{chunk-JID7AZTO.cjs.map → chunk-H3ROMJYI.cjs.map} +1 -1
  27. package/dist/{chunk-DZFGTWYF.js → chunk-JBIGSP2T.js} +3 -3
  28. package/dist/{chunk-DZFGTWYF.js.map → chunk-JBIGSP2T.js.map} +1 -1
  29. package/dist/{chunk-33DEVA3P.js → chunk-JHG3ELS2.js} +2 -2
  30. package/dist/{chunk-33DEVA3P.js.map → chunk-JHG3ELS2.js.map} +1 -1
  31. package/dist/{chunk-WWJIW2IH.cjs → chunk-JSXIB6EJ.cjs} +4 -4
  32. package/dist/{chunk-WWJIW2IH.cjs.map → chunk-JSXIB6EJ.cjs.map} +1 -1
  33. package/dist/{chunk-H5YXAMVH.js → chunk-MLAWXPRY.js} +44 -2
  34. package/dist/chunk-MLAWXPRY.js.map +1 -0
  35. package/dist/{chunk-AAYXCZMV.cjs → chunk-NKV76R65.cjs} +4 -4
  36. package/dist/{chunk-AAYXCZMV.cjs.map → chunk-NKV76R65.cjs.map} +1 -1
  37. package/dist/{chunk-PYJDVWXM.js → chunk-NUB5HRKS.js} +3 -3
  38. package/dist/{chunk-PYJDVWXM.js.map → chunk-NUB5HRKS.js.map} +1 -1
  39. package/dist/{chunk-LL6IWKI3.js → chunk-OW3SQJ7P.js} +3 -3
  40. package/dist/{chunk-LL6IWKI3.js.map → chunk-OW3SQJ7P.js.map} +1 -1
  41. package/dist/{chunk-OUZDYJ33.cjs → chunk-QURJVPRH.cjs} +44 -2
  42. package/dist/chunk-QURJVPRH.cjs.map +1 -0
  43. package/dist/{chunk-TZJQB4P2.js → chunk-RM2HPQIC.js} +72 -14
  44. package/dist/chunk-RM2HPQIC.js.map +1 -0
  45. package/dist/{chunk-P4VGVJVI.js → chunk-SD7ONW6S.js} +3 -3
  46. package/dist/{chunk-P4VGVJVI.js.map → chunk-SD7ONW6S.js.map} +1 -1
  47. package/dist/chunk-WXFGHDTV.cjs +896 -0
  48. package/dist/chunk-WXFGHDTV.cjs.map +1 -0
  49. package/dist/chunk-XDU65GLW.js +21 -0
  50. package/dist/chunk-XDU65GLW.js.map +1 -0
  51. package/dist/{chunk-MYHV2ND3.cjs → chunk-XRVMYT5G.cjs} +72 -13
  52. package/dist/chunk-XRVMYT5G.cjs.map +1 -0
  53. package/dist/{chunk-I4LXSHKO.cjs → chunk-YRHBSSQ3.cjs} +4 -4
  54. package/dist/{chunk-I4LXSHKO.cjs.map → chunk-YRHBSSQ3.cjs.map} +1 -1
  55. package/dist/chunk-YW2AKBLS.cjs +23 -0
  56. package/dist/chunk-YW2AKBLS.cjs.map +1 -0
  57. package/dist/{chunk-B2X732V3.cjs → chunk-ZTMYKAVU.cjs} +4 -4
  58. package/dist/{chunk-B2X732V3.cjs.map → chunk-ZTMYKAVU.cjs.map} +1 -1
  59. package/dist/components/Collapsible/index.cjs +5 -5
  60. package/dist/components/Collapsible/index.js +2 -2
  61. package/dist/components/MediaEditor/index.cjs +8 -8
  62. package/dist/components/MediaEditor/index.js +3 -3
  63. package/dist/components/MediaFeed/index.cjs +26 -0
  64. package/dist/components/MediaFeed/index.cjs.map +1 -0
  65. package/dist/components/MediaFeed/index.d.cts +104 -0
  66. package/dist/components/MediaFeed/index.d.ts +104 -0
  67. package/dist/components/MediaFeed/index.js +17 -0
  68. package/dist/components/MediaFeed/index.js.map +1 -0
  69. package/dist/components/MediaPlayer/index.cjs +5 -5
  70. package/dist/components/MediaPlayer/index.d.cts +18 -0
  71. package/dist/components/MediaPlayer/index.d.ts +18 -0
  72. package/dist/components/MediaPlayer/index.js +2 -2
  73. package/dist/components/RadialExplorer/index.cjs +3 -3
  74. package/dist/components/RadialExplorer/index.js +2 -2
  75. package/dist/components/SliderCalculator/index.cjs +4 -4
  76. package/dist/components/SliderCalculator/index.js +2 -2
  77. package/dist/components/SuperChat/index.cjs +654 -292
  78. package/dist/components/SuperChat/index.cjs.map +1 -1
  79. package/dist/components/SuperChat/index.d.cts +35 -6
  80. package/dist/components/SuperChat/index.d.ts +35 -6
  81. package/dist/components/SuperChat/index.js +648 -286
  82. package/dist/components/SuperChat/index.js.map +1 -1
  83. package/dist/components/SuperChat/plugins/index.d.cts +2 -2
  84. package/dist/components/SuperChat/plugins/index.d.ts +2 -2
  85. package/dist/components/ThemeProvider/index.cjs +9 -9
  86. package/dist/components/ThemeProvider/index.js +3 -3
  87. package/dist/components/Tooltip/index.cjs +3 -3
  88. package/dist/components/Tooltip/index.js +2 -2
  89. package/dist/esheet.cjs +6 -6
  90. package/dist/esheet.js +3 -3
  91. package/dist/globe.cjs +2 -2
  92. package/dist/globe.js +1 -1
  93. package/dist/hooks/index.cjs +4 -4
  94. package/dist/hooks/index.js +2 -2
  95. package/dist/index.cjs +164 -214
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.d.cts +120 -1
  98. package/dist/index.d.ts +120 -1
  99. package/dist/index.js +25 -82
  100. package/dist/index.js.map +1 -1
  101. package/dist/styles.css +1 -1
  102. package/dist/tailwind-preset.cjs +4 -4
  103. package/dist/tailwind-preset.js +1 -1
  104. package/dist/{types-DOxdTwmq.d.cts → types-Cx1FHLq2.d.cts} +130 -1
  105. package/dist/{types-DOxdTwmq.d.ts → types-Cx1FHLq2.d.ts} +130 -1
  106. package/package.json +1 -1
  107. package/dist/chunk-6HFFWEM3.cjs +0 -22
  108. package/dist/chunk-6HFFWEM3.cjs.map +0 -1
  109. package/dist/chunk-FBLVPM2K.js.map +0 -1
  110. package/dist/chunk-H5YXAMVH.js.map +0 -1
  111. package/dist/chunk-HB7C7NB5.js +0 -20
  112. package/dist/chunk-HB7C7NB5.js.map +0 -1
  113. package/dist/chunk-JYZW2PZS.cjs.map +0 -1
  114. package/dist/chunk-MYHV2ND3.cjs.map +0 -1
  115. package/dist/chunk-N2CJJHHB.cjs.map +0 -1
  116. package/dist/chunk-OUZDYJ33.cjs.map +0 -1
  117. package/dist/chunk-SQHMGT4W.js.map +0 -1
  118. package/dist/chunk-TZJQB4P2.js.map +0 -1
@@ -0,0 +1,896 @@
1
+ 'use strict';
2
+
3
+ var chunkBUT6MF5K_cjs = require('./chunk-BUT6MF5K.cjs');
4
+ var chunkJEIW7BSV_cjs = require('./chunk-JEIW7BSV.cjs');
5
+ var chunkT4L7Q5FA_cjs = require('./chunk-T4L7Q5FA.cjs');
6
+ var chunkYW2AKBLS_cjs = require('./chunk-YW2AKBLS.cjs');
7
+ var chunkX6A27RGO_cjs = require('./chunk-X6A27RGO.cjs');
8
+ var chunkS5RVSXDV_cjs = require('./chunk-S5RVSXDV.cjs');
9
+ var React2 = require('react');
10
+ var lucideReact = require('lucide-react');
11
+ var jsxRuntime = require('react/jsx-runtime');
12
+
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
32
+
33
+ function sourceHref(src) {
34
+ try {
35
+ const url = new URL(src, "https://localhost/");
36
+ return ["http:", "https:"].includes(url.protocol) ? src : void 0;
37
+ } catch {
38
+ return void 0;
39
+ }
40
+ }
41
+ function SourceLink({ src, labels }) {
42
+ const href = sourceHref(src);
43
+ return href ? /* @__PURE__ */ jsxRuntime.jsx(
44
+ "a",
45
+ {
46
+ href,
47
+ target: "_blank",
48
+ rel: "noopener noreferrer",
49
+ className: "text-primary-700 dark:text-primary-300 focus-visible:ring-ring rounded underline underline-offset-4 focus-visible:ring-2 focus-visible:outline-none",
50
+ children: labels.openSource
51
+ }
52
+ ) : null;
53
+ }
54
+ function NativeFeedMedia({
55
+ media,
56
+ title,
57
+ active,
58
+ autoPlay,
59
+ muted,
60
+ loop,
61
+ labels,
62
+ onActivate,
63
+ playbackPosition,
64
+ playbackRequestId
65
+ }) {
66
+ const mediaElementRef = React2__namespace.useRef(null);
67
+ const [state, setState] = React2__namespace.useState("idle");
68
+ const [blocked, setBlocked] = React2__namespace.useState(false);
69
+ const [retryVersion, setRetryVersion] = React2__namespace.useState(0);
70
+ const wantsPlayback = React2__namespace.useRef(playbackPosition.playRequested ?? false);
71
+ const latestPlaybackRequest = React2__namespace.useRef(playbackRequestId);
72
+ latestPlaybackRequest.current = playbackRequestId;
73
+ const handledPlaybackRequest = React2__namespace.useRef(
74
+ void 0
75
+ );
76
+ const ownershipPause = React2__namespace.useRef(false);
77
+ const attemptId = React2__namespace.useRef(0);
78
+ const activeRef = React2__namespace.useRef(active);
79
+ const canSavePosition = React2__namespace.useRef(false);
80
+ activeRef.current = active;
81
+ const tryPlay = React2__namespace.useCallback(async () => {
82
+ const element = mediaElementRef.current;
83
+ if (!element || !activeRef.current) return;
84
+ const attempt = ++attemptId.current;
85
+ setBlocked(false);
86
+ try {
87
+ await element.play();
88
+ if (!activeRef.current || element !== mediaElementRef.current)
89
+ element.pause();
90
+ } catch (error) {
91
+ if (!activeRef.current || attempt !== attemptId.current) return;
92
+ if (error instanceof Error && error.name === "AbortError") return;
93
+ setBlocked(true);
94
+ }
95
+ }, []);
96
+ const pauseForOwnership = React2__namespace.useCallback(
97
+ (element) => {
98
+ if (element && element === mediaElementRef.current && !element.paused)
99
+ ownershipPause.current = true;
100
+ element?.pause();
101
+ },
102
+ []
103
+ );
104
+ React2__namespace.useEffect(() => {
105
+ const element = mediaElementRef.current;
106
+ const attempts = attemptId;
107
+ const restorePosition = () => {
108
+ if (!element || !activeRef.current) return;
109
+ if (playbackPosition.timeMs > 0) {
110
+ element.currentTime = playbackPosition.timeMs / 1e3;
111
+ }
112
+ canSavePosition.current = true;
113
+ };
114
+ if (active && element) {
115
+ wantsPlayback.current = playbackPosition.playRequested ?? false;
116
+ if (element.readyState >= 1) restorePosition();
117
+ else element.addEventListener("loadedmetadata", restorePosition);
118
+ }
119
+ if (active && (latestPlaybackRequest.current === void 0 || latestPlaybackRequest.current === handledPlaybackRequest.current) && (autoPlay || wantsPlayback.current)) {
120
+ void tryPlay();
121
+ } else if (!active) {
122
+ attemptId.current++;
123
+ pauseForOwnership(element);
124
+ setBlocked(false);
125
+ }
126
+ return () => {
127
+ attempts.current++;
128
+ element?.removeEventListener("loadedmetadata", restorePosition);
129
+ if (element && canSavePosition.current)
130
+ playbackPosition.timeMs = element.currentTime * 1e3;
131
+ pauseForOwnership(element);
132
+ };
133
+ }, [
134
+ active,
135
+ autoPlay,
136
+ pauseForOwnership,
137
+ playbackPosition,
138
+ retryVersion,
139
+ tryPlay
140
+ ]);
141
+ React2__namespace.useEffect(() => {
142
+ if (!active || playbackRequestId === void 0 || playbackRequestId === handledPlaybackRequest.current)
143
+ return;
144
+ handledPlaybackRequest.current = playbackRequestId;
145
+ wantsPlayback.current = true;
146
+ playbackPosition.playRequested = true;
147
+ void tryPlay();
148
+ }, [active, playbackRequestId, playbackPosition, tryPlay]);
149
+ const togglePlayback = () => {
150
+ if (state === "playing") {
151
+ wantsPlayback.current = false;
152
+ playbackPosition.playRequested = false;
153
+ mediaElementRef.current?.pause();
154
+ } else {
155
+ wantsPlayback.current = true;
156
+ playbackPosition.playRequested = true;
157
+ onActivate();
158
+ if (active) void tryPlay();
159
+ }
160
+ };
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
+ chunkBUT6MF5K_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;
181
+ }
182
+ ownershipPause.current = false;
183
+ }
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
+ chunkX6A27RGO_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
+ ] });
219
+ }
220
+ function ImageFeedMedia({ media, title, labels }) {
221
+ const [failed, setFailed] = React2__namespace.useState(false);
222
+ return failed ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: "flex flex-col gap-3 p-6 text-center", children: [
223
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground m-0", children: labels.mediaError }),
224
+ /* @__PURE__ */ jsxRuntime.jsx(SourceLink, { src: media.src, labels })
225
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
226
+ "img",
227
+ {
228
+ src: media.src,
229
+ alt: media.alt ?? title,
230
+ loading: "lazy",
231
+ className: "h-full max-h-full w-full object-contain",
232
+ onError: () => setFailed(true)
233
+ }
234
+ );
235
+ }
236
+ function youtubeId(src) {
237
+ const validId = /^[A-Za-z0-9_-]{11}$/;
238
+ if (validId.test(src)) return src;
239
+ try {
240
+ const url = new URL(src);
241
+ const host = url.hostname.toLowerCase();
242
+ const parts = url.pathname.split("/").filter(Boolean);
243
+ let id = null;
244
+ if (host === "youtu.be" || host === "www.youtu.be") {
245
+ id = parts[0] ?? null;
246
+ } else if ([
247
+ "youtube.com",
248
+ "www.youtube.com",
249
+ "m.youtube.com",
250
+ "youtube-nocookie.com",
251
+ "www.youtube-nocookie.com"
252
+ ].includes(host)) {
253
+ id = url.searchParams.get("v") ?? (["embed", "shorts", "live"].includes(parts[0]) ? parts[1] : null);
254
+ }
255
+ return id && validId.test(id) ? id : null;
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+ function YouTubeFeedMedia({
261
+ media,
262
+ title,
263
+ active,
264
+ autoPlay,
265
+ muted,
266
+ loop,
267
+ labels,
268
+ onActivate,
269
+ playbackRequestId
270
+ }) {
271
+ const id = youtubeId(media.src);
272
+ const [requestedPlayback, setRequestedPlayback] = React2__namespace.useState(false);
273
+ const handledPlaybackRequest = React2__namespace.useRef(
274
+ void 0
275
+ );
276
+ const pendingPlayback = active && playbackRequestId !== void 0 && playbackRequestId !== handledPlaybackRequest.current;
277
+ React2__namespace.useEffect(() => {
278
+ if (!active) setRequestedPlayback(false);
279
+ else if (playbackRequestId !== void 0 && playbackRequestId !== handledPlaybackRequest.current) {
280
+ handledPlaybackRequest.current = playbackRequestId;
281
+ setRequestedPlayback(true);
282
+ }
283
+ }, [active, playbackRequestId]);
284
+ const embedUrl = new URL(
285
+ `https://www.youtube-nocookie.com/embed/${id ?? ""}`
286
+ );
287
+ for (const [key, value] of Object.entries({
288
+ autoplay: autoPlay || requestedPlayback || pendingPlayback ? "1" : "0",
289
+ mute: muted ? "1" : "0",
290
+ controls: "1",
291
+ playsinline: "1",
292
+ rel: "0",
293
+ loop: loop ? "1" : "0",
294
+ ...loop && id ? { playlist: id } : {}
295
+ }))
296
+ embedUrl.searchParams.set(key, value);
297
+ const [failed, setFailed] = React2__namespace.useState(false);
298
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 w-full flex-col items-center gap-3", children: [
299
+ active && id && !failed ? /* @__PURE__ */ jsxRuntime.jsx(
300
+ "iframe",
301
+ {
302
+ src: embedUrl.href,
303
+ title,
304
+ className: "min-h-0 w-full flex-1 border-0",
305
+ allow: "autoplay; encrypted-media; picture-in-picture; fullscreen",
306
+ referrerPolicy: "strict-origin-when-cross-origin",
307
+ allowFullScreen: true,
308
+ onError: () => setFailed(true)
309
+ }
310
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 w-full flex-1 flex-col items-center justify-center gap-4", children: [
311
+ media.poster && /* @__PURE__ */ jsxRuntime.jsx(
312
+ "img",
313
+ {
314
+ src: media.poster,
315
+ alt: "",
316
+ loading: "lazy",
317
+ className: "min-h-0 max-w-full flex-1 object-contain"
318
+ }
319
+ ),
320
+ id && !failed ? /* @__PURE__ */ jsxRuntime.jsx(
321
+ chunkX6A27RGO_cjs.Button,
322
+ {
323
+ variant: "secondary",
324
+ leftIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-4 w-4", "aria-hidden": "true" }),
325
+ onClick: () => {
326
+ setRequestedPlayback(true);
327
+ onActivate();
328
+ },
329
+ children: labels.play
330
+ }
331
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-muted-foreground m-0", children: labels.mediaError })
332
+ ] }),
333
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground m-0 text-center text-sm", children: labels.youtubeNotice }),
334
+ /* @__PURE__ */ jsxRuntime.jsx(
335
+ SourceLink,
336
+ {
337
+ src: id ? `https://www.youtube.com/watch?v=${id}` : media.src,
338
+ labels
339
+ }
340
+ )
341
+ ] });
342
+ }
343
+ function FeedMedia(props) {
344
+ if (props.media.kind === "image") return /* @__PURE__ */ jsxRuntime.jsx(ImageFeedMedia, { ...props });
345
+ if (props.media.kind === "youtube") return /* @__PURE__ */ jsxRuntime.jsx(YouTubeFeedMedia, { ...props });
346
+ return /* @__PURE__ */ jsxRuntime.jsx(NativeFeedMedia, { ...props });
347
+ }
348
+ var defaultLabels = {
349
+ feed: "Media feed",
350
+ loading: "Loading media\u2026",
351
+ empty: "No media to display.",
352
+ error: "Unable to load the media feed.",
353
+ retry: "Retry",
354
+ fullscreen: "Open fullscreen",
355
+ close: "Close fullscreen",
356
+ previous: "Previous item",
357
+ next: "Next item",
358
+ play: "Play",
359
+ pause: "Pause",
360
+ blockedAutoplay: "Playback did not start. Press Play to try again.",
361
+ mediaError: "This media is unavailable.",
362
+ openSource: "Open source",
363
+ youtubeNotice: "If embedded playback is unavailable, open the source.",
364
+ item: (position, total) => `Media item ${position} of ${total}`,
365
+ position: (position, total) => `${position} / ${total}`
366
+ };
367
+ function MediaFeedInner({
368
+ items,
369
+ getId,
370
+ getMedia,
371
+ getTitle,
372
+ getCaption,
373
+ getAuthor,
374
+ renderActions,
375
+ renderMedia,
376
+ activeItemId,
377
+ defaultActiveItemId,
378
+ onActiveItemChange,
379
+ playbackRequest,
380
+ loading = false,
381
+ error,
382
+ onRetry,
383
+ autoPlay = true,
384
+ muted = true,
385
+ loop = true,
386
+ labels: labelOverrides,
387
+ classNames = {},
388
+ className,
389
+ ...rest
390
+ }, forwardedRef) {
391
+ const labels = { ...defaultLabels, ...labelOverrides };
392
+ const prefersReducedMotion = chunkYW2AKBLS_cjs.usePrefersReducedMotion();
393
+ const allowAutoPlay = autoPlay && !prefersReducedMotion;
394
+ const ids = items.map(getId);
395
+ const idsKey = JSON.stringify(ids);
396
+ const [internalActiveId, setInternalActiveId] = React2__namespace.useState(
397
+ () => defaultActiveItemId ?? ids[0]
398
+ );
399
+ const requestedId = activeItemId ?? internalActiveId;
400
+ const activeId = requestedId !== void 0 && ids.includes(requestedId) ? requestedId : ids[0];
401
+ const activeIndex = activeId === void 0 ? -1 : ids.indexOf(activeId);
402
+ const [fullscreen, setFullscreen] = React2__namespace.useState(false);
403
+ const [retrying, setRetrying] = React2__namespace.useState(false);
404
+ const [retryFailed, setRetryFailed] = React2__namespace.useState(false);
405
+ const rootRef = React2__namespace.useRef(null);
406
+ const viewportRef = React2__namespace.useRef(null);
407
+ const cardRefs = React2__namespace.useRef(/* @__PURE__ */ new Map());
408
+ const playbackPositions = React2__namespace.useRef(
409
+ /* @__PURE__ */ new Map()
410
+ );
411
+ const consumedPlaybackRequests = React2__namespace.useRef(
412
+ /* @__PURE__ */ new Map()
413
+ );
414
+ const fullscreenTriggerRef = React2__namespace.useRef(null);
415
+ const hasObserver = typeof IntersectionObserver !== "undefined";
416
+ const [visibleId, setVisibleId] = React2__namespace.useState(null);
417
+ const visibleIdRef = React2__namespace.useRef(visibleId);
418
+ const [viewportVisible, setViewportVisible] = React2__namespace.useState(false);
419
+ const [pageVisible, setPageVisible] = React2__namespace.useState(
420
+ () => typeof document === "undefined" || document.visibilityState !== "hidden"
421
+ );
422
+ const latest = React2__namespace.useRef({
423
+ items,
424
+ ids,
425
+ activeId,
426
+ activeItemId,
427
+ onActiveItemChange,
428
+ fullscreen
429
+ });
430
+ latest.current = {
431
+ items,
432
+ ids,
433
+ activeId,
434
+ activeItemId,
435
+ onActiveItemChange,
436
+ fullscreen
437
+ };
438
+ const setRoot = React2__namespace.useCallback(
439
+ (node) => {
440
+ rootRef.current = node;
441
+ if (typeof forwardedRef === "function") forwardedRef(node);
442
+ else if (forwardedRef) forwardedRef.current = node;
443
+ },
444
+ [forwardedRef]
445
+ );
446
+ const select = React2__namespace.useCallback((id) => {
447
+ const current = latest.current;
448
+ const index = current.ids.indexOf(id);
449
+ if (index < 0 || id === current.activeId) return;
450
+ if (current.activeItemId === void 0) setInternalActiveId(id);
451
+ current.onActiveItemChange?.(current.items[index]);
452
+ }, []);
453
+ React2__namespace.useEffect(() => {
454
+ const root = rootRef.current;
455
+ if (!root || typeof IntersectionObserver === "undefined") {
456
+ setViewportVisible(true);
457
+ return;
458
+ }
459
+ const observer = new IntersectionObserver(([entry]) => {
460
+ setViewportVisible(entry.isIntersecting);
461
+ });
462
+ observer.observe(root);
463
+ return () => observer.disconnect();
464
+ }, []);
465
+ React2__namespace.useEffect(() => {
466
+ const viewport = viewportRef.current;
467
+ if (!viewport || typeof IntersectionObserver === "undefined") return;
468
+ const ratios = /* @__PURE__ */ new Map();
469
+ const observer = new IntersectionObserver(
470
+ (entries) => {
471
+ for (const entry of entries) {
472
+ const id = entry.target.dataset.mediaId;
473
+ if (id !== void 0)
474
+ ratios.set(id, entry.isIntersecting ? entry.intersectionRatio : 0);
475
+ }
476
+ let bestId = null;
477
+ let bestRatio = 0;
478
+ for (const [id, ratio] of ratios) {
479
+ if (ratio > bestRatio) {
480
+ bestId = id;
481
+ bestRatio = ratio;
482
+ }
483
+ }
484
+ visibleIdRef.current = bestId;
485
+ setVisibleId(bestId);
486
+ if (bestId !== null && !latest.current.fullscreen) select(bestId);
487
+ },
488
+ { root: viewport, threshold: [0, 0.25, 0.5, 0.75, 1] }
489
+ );
490
+ cardRefs.current.forEach((card) => observer.observe(card));
491
+ return () => observer.disconnect();
492
+ }, [idsKey, loading, error, select]);
493
+ React2__namespace.useEffect(() => {
494
+ const update = () => setPageVisible(document.visibilityState !== "hidden");
495
+ document.addEventListener("visibilitychange", update);
496
+ return () => document.removeEventListener("visibilitychange", update);
497
+ }, []);
498
+ React2__namespace.useEffect(() => {
499
+ if (activeId === void 0) return;
500
+ if (!hasObserver) {
501
+ visibleIdRef.current = activeId;
502
+ setVisibleId(activeId);
503
+ }
504
+ if (visibleIdRef.current === activeId && hasObserver) return;
505
+ const card = cardRefs.current.get(activeId);
506
+ if (card && viewportRef.current) {
507
+ viewportRef.current.scrollTo?.({ top: card.offsetTop, behavior: "auto" });
508
+ }
509
+ }, [activeId, idsKey, hasObserver, loading, error]);
510
+ React2__namespace.useEffect(() => {
511
+ if (!items.length) setFullscreen(false);
512
+ }, [items.length]);
513
+ React2__namespace.useEffect(() => {
514
+ if (loading || error || activeIndex < 0 || !playbackRequest || playbackRequest.itemId !== activeId)
515
+ return;
516
+ if (!pageVisible || !fullscreen && (!viewportVisible || visibleId !== activeId))
517
+ return;
518
+ consumedPlaybackRequests.current.set(
519
+ playbackRequest.itemId,
520
+ playbackRequest.requestId
521
+ );
522
+ }, [
523
+ activeId,
524
+ activeIndex,
525
+ fullscreen,
526
+ pageVisible,
527
+ viewportVisible,
528
+ visibleId,
529
+ playbackRequest,
530
+ loading,
531
+ error
532
+ ]);
533
+ React2__namespace.useEffect(() => {
534
+ const positions = playbackPositions.current;
535
+ for (const id of positions.keys()) {
536
+ if (!latest.current.ids.includes(id)) positions.delete(id);
537
+ }
538
+ }, [idsKey]);
539
+ React2__namespace.useEffect(() => {
540
+ if (fullscreen || !fullscreenTriggerRef.current) return;
541
+ if (fullscreenTriggerRef.current.isConnected)
542
+ fullscreenTriggerRef.current.focus();
543
+ else viewportRef.current?.focus();
544
+ fullscreenTriggerRef.current = null;
545
+ }, [fullscreen]);
546
+ const navigate = (index, focus = false) => {
547
+ const id = ids[Math.max(0, Math.min(index, ids.length - 1))];
548
+ if (id === void 0) return;
549
+ select(id);
550
+ const card = cardRefs.current.get(id);
551
+ if (card && viewportRef.current) {
552
+ viewportRef.current.scrollTo?.({ top: card.offsetTop, behavior: "auto" });
553
+ if (focus) card.focus({ preventScroll: true });
554
+ }
555
+ };
556
+ const handleKeyDown = (event) => {
557
+ const target = event.target;
558
+ if (event.target !== event.currentTarget && target.dataset?.slot !== "item")
559
+ return;
560
+ let next;
561
+ switch (event.key) {
562
+ case "ArrowDown":
563
+ case "PageDown":
564
+ next = activeIndex + 1;
565
+ break;
566
+ case "ArrowUp":
567
+ case "PageUp":
568
+ next = activeIndex - 1;
569
+ break;
570
+ case "Home":
571
+ next = 0;
572
+ break;
573
+ case "End":
574
+ next = items.length - 1;
575
+ break;
576
+ default:
577
+ return;
578
+ }
579
+ event.preventDefault();
580
+ navigate(next, event.target !== event.currentTarget);
581
+ };
582
+ const retry = async () => {
583
+ if (!onRetry || retrying) return;
584
+ setRetrying(true);
585
+ setRetryFailed(false);
586
+ try {
587
+ await onRetry();
588
+ } catch {
589
+ setRetryFailed(true);
590
+ } finally {
591
+ setRetrying(false);
592
+ }
593
+ };
594
+ const renderCard = (item, index, immersive) => {
595
+ const id = getId(item);
596
+ const media = getMedia(item);
597
+ let playbackPosition = playbackPositions.current.get(id);
598
+ if (!playbackPosition || playbackPosition.src !== media.src) {
599
+ playbackPosition = { src: media.src, timeMs: 0 };
600
+ playbackPositions.current.set(id, playbackPosition);
601
+ }
602
+ const title = getTitle?.(item) || labels.item(index + 1, items.length);
603
+ const author = getAuthor?.(item);
604
+ const caption = getCaption?.(item);
605
+ const active = !loading && !error && pageVisible && (immersive ? fullscreen && id === activeId : !fullscreen && viewportVisible && id === activeId && id === visibleId);
606
+ const playbackRequestId = active && playbackRequest?.itemId === id && consumedPlaybackRequests.current.get(id) !== playbackRequest.requestId ? playbackRequest.requestId : void 0;
607
+ return /* @__PURE__ */ jsxRuntime.jsxs(
608
+ "article",
609
+ {
610
+ ref: immersive ? void 0 : (node) => {
611
+ if (node) cardRefs.current.set(id, node);
612
+ else cardRefs.current.delete(id);
613
+ },
614
+ "data-slot": "item",
615
+ "data-media-id": id,
616
+ "data-active": active,
617
+ "aria-label": title,
618
+ "aria-posinset": index + 1,
619
+ "aria-setsize": items.length,
620
+ tabIndex: id === activeId ? 0 : -1,
621
+ 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",
624
+ classNames.item
625
+ ),
626
+ children: [
627
+ /* @__PURE__ */ jsxRuntime.jsx(
628
+ "div",
629
+ {
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
+ )
658
+ }
659
+ ),
660
+ /* @__PURE__ */ jsxRuntime.jsxs(
661
+ "div",
662
+ {
663
+ "data-slot": "details",
664
+ className: chunkS5RVSXDV_cjs.cn(
665
+ "flex max-h-[40%] shrink-0 flex-col gap-2 overflow-y-auto p-4",
666
+ classNames.details
667
+ ),
668
+ children: [
669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
670
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
671
+ author && /* @__PURE__ */ jsxRuntime.jsxs(
672
+ "div",
673
+ {
674
+ "data-slot": "author",
675
+ className: chunkS5RVSXDV_cjs.cn(
676
+ "mb-2 flex items-center gap-2",
677
+ classNames.author
678
+ ),
679
+ children: [
680
+ /* @__PURE__ */ jsxRuntime.jsx(
681
+ chunkJEIW7BSV_cjs.Avatar,
682
+ {
683
+ name: author.name,
684
+ src: author.avatar,
685
+ alt: "",
686
+ size: "sm"
687
+ }
688
+ ),
689
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-medium", children: author.name })
690
+ ]
691
+ }
692
+ ),
693
+ /* @__PURE__ */ jsxRuntime.jsx(
694
+ "h3",
695
+ {
696
+ "data-slot": "title",
697
+ className: chunkS5RVSXDV_cjs.cn("m-0 text-base font-semibold", classNames.title),
698
+ children: title
699
+ }
700
+ )
701
+ ] }),
702
+ !immersive && /* @__PURE__ */ jsxRuntime.jsx(
703
+ chunkX6A27RGO_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
+ )
717
+ ] }),
718
+ caption != null && /* @__PURE__ */ jsxRuntime.jsx(
719
+ "div",
720
+ {
721
+ "data-slot": "caption",
722
+ className: chunkS5RVSXDV_cjs.cn(
723
+ "text-muted-foreground text-sm",
724
+ classNames.caption
725
+ ),
726
+ children: caption
727
+ }
728
+ ),
729
+ renderActions && /* @__PURE__ */ jsxRuntime.jsx(
730
+ "div",
731
+ {
732
+ "data-slot": "actions",
733
+ className: chunkS5RVSXDV_cjs.cn(
734
+ "flex flex-wrap items-center gap-2",
735
+ classNames.actions
736
+ ),
737
+ children: renderActions(item)
738
+ }
739
+ )
740
+ ]
741
+ }
742
+ )
743
+ ]
744
+ },
745
+ id
746
+ );
747
+ };
748
+ const controls = /* @__PURE__ */ jsxRuntime.jsxs(
749
+ "div",
750
+ {
751
+ "data-slot": "controls",
752
+ className: chunkS5RVSXDV_cjs.cn(
753
+ "flex shrink-0 items-center justify-between gap-3 py-2",
754
+ classNames.controls
755
+ ),
756
+ children: [
757
+ /* @__PURE__ */ jsxRuntime.jsx(
758
+ chunkX6A27RGO_cjs.Button,
759
+ {
760
+ variant: "ghost",
761
+ size: "sm",
762
+ className: "h-auto min-h-8 flex-1 py-2 [&_[data-slot=button-label]]:whitespace-normal",
763
+ disabled: activeIndex <= 0,
764
+ onClick: () => navigate(activeIndex - 1),
765
+ leftIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4", "aria-hidden": "true" }),
766
+ children: labels.previous
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsxRuntime.jsx(
770
+ "span",
771
+ {
772
+ role: "status",
773
+ "aria-live": "polite",
774
+ className: "text-muted-foreground shrink-0 text-sm whitespace-nowrap",
775
+ children: labels.position(activeIndex + 1, items.length)
776
+ }
777
+ ),
778
+ /* @__PURE__ */ jsxRuntime.jsx(
779
+ chunkX6A27RGO_cjs.Button,
780
+ {
781
+ variant: "ghost",
782
+ size: "sm",
783
+ className: "h-auto min-h-8 flex-1 py-2 [&_[data-slot=button-label]]:whitespace-normal",
784
+ disabled: activeIndex >= items.length - 1,
785
+ onClick: () => navigate(activeIndex + 1),
786
+ rightIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4", "aria-hidden": "true" }),
787
+ children: labels.next
788
+ }
789
+ )
790
+ ]
791
+ }
792
+ );
793
+ return /* @__PURE__ */ jsxRuntime.jsxs(
794
+ "div",
795
+ {
796
+ ref: setRoot,
797
+ "data-slot": "root",
798
+ "data-component": "media-feed",
799
+ className: chunkS5RVSXDV_cjs.cn(
800
+ "bg-background text-foreground flex h-[min(48rem,80dvh)] min-h-0 w-full flex-col",
801
+ classNames.root,
802
+ className
803
+ ),
804
+ ...rest,
805
+ children: [
806
+ loading || error || !items.length ? /* @__PURE__ */ jsxRuntime.jsxs(
807
+ "div",
808
+ {
809
+ "data-slot": "status",
810
+ role: error ? "alert" : "status",
811
+ "aria-busy": loading || retrying,
812
+ className: chunkS5RVSXDV_cjs.cn(
813
+ "flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-6 text-center",
814
+ classNames.status
815
+ ),
816
+ children: [
817
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0", children: loading ? labels.loading : error || labels.empty }),
818
+ error && onRetry && /* @__PURE__ */ jsxRuntime.jsx(
819
+ chunkX6A27RGO_cjs.Button,
820
+ {
821
+ variant: "outline",
822
+ isLoading: retrying,
823
+ loadingText: labels.loading,
824
+ onClick: () => {
825
+ void retry();
826
+ },
827
+ children: labels.retry
828
+ }
829
+ ),
830
+ retryFailed && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "m-0", children: labels.error })
831
+ ]
832
+ }
833
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
834
+ /* @__PURE__ */ jsxRuntime.jsx(
835
+ "div",
836
+ {
837
+ ref: viewportRef,
838
+ "data-slot": "viewport",
839
+ role: "feed",
840
+ "aria-label": labels.feed,
841
+ "aria-busy": false,
842
+ tabIndex: 0,
843
+ onKeyDown: handleKeyDown,
844
+ 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",
846
+ classNames.viewport
847
+ ),
848
+ children: items.map((item, index) => renderCard(item, index, false))
849
+ }
850
+ ),
851
+ controls
852
+ ] }),
853
+ /* @__PURE__ */ jsxRuntime.jsxs(
854
+ chunkT4L7Q5FA_cjs.Modal,
855
+ {
856
+ open: fullscreen && !loading && !error && !!items.length,
857
+ onOpenChange: setFullscreen,
858
+ size: "full",
859
+ className: "h-dvh min-h-0 sm:h-[calc(100dvh-2rem)]",
860
+ children: [
861
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkT4L7Q5FA_cjs.ModalHeader, { children: [
862
+ /* @__PURE__ */ jsxRuntime.jsx(chunkT4L7Q5FA_cjs.ModalTitle, { children: labels.feed }),
863
+ /* @__PURE__ */ jsxRuntime.jsx(chunkT4L7Q5FA_cjs.ModalClose, { "aria-label": labels.close })
864
+ ] }),
865
+ /* @__PURE__ */ jsxRuntime.jsxs(
866
+ "div",
867
+ {
868
+ "data-slot": "fullscreen",
869
+ "data-component": "media-feed",
870
+ role: "feed",
871
+ "aria-label": labels.feed,
872
+ tabIndex: 0,
873
+ onKeyDown: handleKeyDown,
874
+ className: chunkS5RVSXDV_cjs.cn(
875
+ "flex min-h-0 flex-1 flex-col px-2 pb-2 sm:px-4",
876
+ classNames.fullscreen
877
+ ),
878
+ children: [
879
+ activeIndex >= 0 && renderCard(items[activeIndex], activeIndex, true),
880
+ controls
881
+ ]
882
+ }
883
+ )
884
+ ]
885
+ }
886
+ )
887
+ ]
888
+ }
889
+ );
890
+ }
891
+ var MediaFeed = React2__namespace.forwardRef(MediaFeedInner);
892
+ MediaFeed.displayName = "MediaFeed";
893
+
894
+ exports.MediaFeed = MediaFeed;
895
+ //# sourceMappingURL=chunk-WXFGHDTV.cjs.map
896
+ //# sourceMappingURL=chunk-WXFGHDTV.cjs.map