@scalemule/nextjs 0.1.21 → 0.1.23

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.
package/dist/index.d.mts CHANGED
@@ -898,7 +898,7 @@ interface NarrationPlayerProps {
898
898
  providerLabel?: string;
899
899
  narrationLabel?: string;
900
900
  refreshing?: boolean;
901
- onRefresh?: () => void;
901
+ onRefresh?: () => void | Promise<void>;
902
902
  onPlaybackError?: () => void;
903
903
  }
904
904
  declare function NarrationPlayer({ audio, className, providerLabel, narrationLabel, refreshing, onRefresh, onPlaybackError, }: NarrationPlayerProps): react_jsx_runtime.JSX.Element | null;
package/dist/index.d.ts CHANGED
@@ -898,7 +898,7 @@ interface NarrationPlayerProps {
898
898
  providerLabel?: string;
899
899
  narrationLabel?: string;
900
900
  refreshing?: boolean;
901
- onRefresh?: () => void;
901
+ onRefresh?: () => void | Promise<void>;
902
902
  onPlaybackError?: () => void;
903
903
  }
904
904
  declare function NarrationPlayer({ audio, className, providerLabel, narrationLabel, refreshing, onRefresh, onPlaybackError, }: NarrationPlayerProps): react_jsx_runtime.JSX.Element | null;
package/dist/index.js CHANGED
@@ -11289,6 +11289,157 @@ function FeedbackWidget(props) {
11289
11289
  ] });
11290
11290
  }
11291
11291
  var SPEEDS = [1, 1.25, 1.5, 2];
11292
+ var EXPIRY_REFRESH_WINDOW_MS = 3e4;
11293
+ var styles = {
11294
+ root: {
11295
+ borderRadius: "1.75rem",
11296
+ border: "1px solid rgba(226, 232, 240, 0.8)",
11297
+ background: "linear-gradient(180deg, #f8fbff 0%, #edf4ff 100%)",
11298
+ padding: "1rem",
11299
+ color: "#0f172b",
11300
+ boxShadow: "0 22px 70px rgba(4, 12, 24, 0.28)"
11301
+ },
11302
+ layout: {
11303
+ display: "flex",
11304
+ flexWrap: "wrap",
11305
+ alignItems: "center",
11306
+ gap: "1rem"
11307
+ },
11308
+ playButton: {
11309
+ display: "flex",
11310
+ height: "4.5rem",
11311
+ width: "4.5rem",
11312
+ flexShrink: 0,
11313
+ alignItems: "center",
11314
+ justifyContent: "center",
11315
+ borderRadius: "9999px",
11316
+ border: "none",
11317
+ backgroundColor: "#020618",
11318
+ color: "#ffffff",
11319
+ boxShadow: "0 10px 28px rgba(15, 23, 42, 0.25)",
11320
+ cursor: "pointer"
11321
+ },
11322
+ playIcon: {
11323
+ display: "block",
11324
+ width: "1.75rem",
11325
+ height: "1.75rem",
11326
+ fill: "currentColor"
11327
+ },
11328
+ playIconOffset: {
11329
+ marginLeft: "0.25rem"
11330
+ },
11331
+ content: {
11332
+ minWidth: "16rem",
11333
+ flex: "1 1 18rem"
11334
+ },
11335
+ metaRow: {
11336
+ display: "flex",
11337
+ alignItems: "center",
11338
+ justifyContent: "space-between",
11339
+ gap: "0.75rem",
11340
+ fontSize: "0.8rem",
11341
+ fontWeight: 500,
11342
+ color: "#62748e"
11343
+ },
11344
+ metaRight: {
11345
+ display: "flex",
11346
+ alignItems: "center",
11347
+ gap: "0.75rem"
11348
+ },
11349
+ badge: {
11350
+ borderRadius: "9999px",
11351
+ backgroundColor: "#dbeafe",
11352
+ padding: "0.25rem 0.75rem",
11353
+ fontSize: "0.65rem",
11354
+ fontWeight: 600,
11355
+ letterSpacing: "0.24em",
11356
+ textTransform: "uppercase",
11357
+ color: "#1447e6"
11358
+ },
11359
+ scrubber: {
11360
+ position: "relative",
11361
+ marginTop: "1rem",
11362
+ height: "3rem",
11363
+ cursor: "pointer",
11364
+ overflow: "hidden",
11365
+ borderRadius: "1rem",
11366
+ backgroundColor: "rgba(226, 232, 240, 0.9)",
11367
+ paddingInline: "0.25rem"
11368
+ },
11369
+ waveform: {
11370
+ pointerEvents: "none",
11371
+ position: "absolute",
11372
+ inset: 0,
11373
+ height: "100%",
11374
+ width: "100%"
11375
+ },
11376
+ fallbackTrack: {
11377
+ position: "absolute",
11378
+ inset: 0,
11379
+ backgroundColor: "rgba(226, 232, 240, 0.9)"
11380
+ },
11381
+ fallbackProgress: {
11382
+ position: "absolute",
11383
+ insetBlock: 0,
11384
+ left: 0,
11385
+ backgroundColor: "#155dfc"
11386
+ },
11387
+ chipsRow: {
11388
+ marginTop: "1rem",
11389
+ display: "flex",
11390
+ flexWrap: "wrap",
11391
+ alignItems: "center",
11392
+ gap: "0.5rem",
11393
+ fontSize: "0.72rem",
11394
+ fontWeight: 500,
11395
+ color: "#62748e"
11396
+ },
11397
+ chipStrong: {
11398
+ borderRadius: "9999px",
11399
+ border: "1px solid #e2e8f0",
11400
+ backgroundColor: "rgba(255, 255, 255, 0.85)",
11401
+ padding: "0.25rem 0.75rem",
11402
+ color: "#334155"
11403
+ },
11404
+ chipMuted: {
11405
+ borderRadius: "9999px",
11406
+ border: "1px solid #e2e8f0",
11407
+ backgroundColor: "rgba(255, 255, 255, 0.65)",
11408
+ padding: "0.25rem 0.75rem"
11409
+ },
11410
+ controls: {
11411
+ display: "flex",
11412
+ flexShrink: 0,
11413
+ alignItems: "center",
11414
+ gap: "0.5rem",
11415
+ alignSelf: "flex-start"
11416
+ },
11417
+ secondaryButton: {
11418
+ borderRadius: "9999px",
11419
+ border: "1px solid #cad5e2",
11420
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
11421
+ padding: "0.5rem 1rem",
11422
+ fontSize: "1rem",
11423
+ fontWeight: 600,
11424
+ color: "#334155",
11425
+ boxShadow: "0 1px 3px rgba(15, 23, 42, 0.08)",
11426
+ cursor: "pointer"
11427
+ },
11428
+ refreshButton: {
11429
+ borderRadius: "9999px",
11430
+ border: "1px solid #cad5e2",
11431
+ backgroundColor: "rgba(255, 255, 255, 0.75)",
11432
+ padding: "0.5rem 1rem",
11433
+ fontSize: "0.875rem",
11434
+ fontWeight: 600,
11435
+ color: "#334155",
11436
+ cursor: "pointer"
11437
+ },
11438
+ disabled: {
11439
+ cursor: "not-allowed",
11440
+ opacity: 0.6
11441
+ }
11442
+ };
11292
11443
  function formatTime(seconds) {
11293
11444
  if (!Number.isFinite(seconds) || seconds <= 0) {
11294
11445
  return "0:00";
@@ -11316,6 +11467,9 @@ function NarrationPlayer({
11316
11467
  }) {
11317
11468
  const audioRef = React.useRef(null);
11318
11469
  const scrubRef = React.useRef(null);
11470
+ const pendingResumeRef = React.useRef(false);
11471
+ const pendingResumeTimeRef = React.useRef(null);
11472
+ const recoveredUrlRef = React.useRef(null);
11319
11473
  const [playing, setPlaying] = React.useState(false);
11320
11474
  const [currentTime, setCurrentTime] = React.useState(0);
11321
11475
  const [duration, setDuration] = React.useState(audio.duration_ms ? audio.duration_ms / 1e3 : 0);
@@ -11330,10 +11484,26 @@ function NarrationPlayer({
11330
11484
  React.useEffect(() => {
11331
11485
  const element = audioRef.current;
11332
11486
  if (!element) return;
11333
- setCurrentTime(0);
11487
+ recoveredUrlRef.current = null;
11488
+ const resumeTime = pendingResumeTimeRef.current;
11489
+ setCurrentTime(resumeTime ?? 0);
11334
11490
  setPlaying(false);
11335
11491
  element.pause();
11336
- element.currentTime = 0;
11492
+ try {
11493
+ element.currentTime = resumeTime ?? 0;
11494
+ } catch {
11495
+ element.currentTime = 0;
11496
+ }
11497
+ if (pendingResumeRef.current) {
11498
+ pendingResumeRef.current = false;
11499
+ pendingResumeTimeRef.current = null;
11500
+ element.play().catch(() => {
11501
+ setPlaying(false);
11502
+ onPlaybackError?.();
11503
+ });
11504
+ return;
11505
+ }
11506
+ pendingResumeTimeRef.current = null;
11337
11507
  }, [audio.url]);
11338
11508
  React.useEffect(() => {
11339
11509
  const element = audioRef.current;
@@ -11341,10 +11511,49 @@ function NarrationPlayer({
11341
11511
  element.playbackRate = speed;
11342
11512
  element.preservesPitch = true;
11343
11513
  }, [speed]);
11514
+ function isUrlExpiringSoon() {
11515
+ if (!audio.expires_at) {
11516
+ return false;
11517
+ }
11518
+ const expiresAtMs = Date.parse(audio.expires_at);
11519
+ if (!Number.isFinite(expiresAtMs)) {
11520
+ return false;
11521
+ }
11522
+ return expiresAtMs - Date.now() <= EXPIRY_REFRESH_WINDOW_MS;
11523
+ }
11524
+ async function refreshAndResume() {
11525
+ if (!onRefresh || refreshing) {
11526
+ return false;
11527
+ }
11528
+ if (recoveredUrlRef.current === (audio.url ?? null)) {
11529
+ return false;
11530
+ }
11531
+ recoveredUrlRef.current = audio.url ?? null;
11532
+ pendingResumeRef.current = true;
11533
+ pendingResumeTimeRef.current = currentTime;
11534
+ try {
11535
+ await Promise.resolve(onRefresh());
11536
+ return true;
11537
+ } catch {
11538
+ pendingResumeRef.current = false;
11539
+ pendingResumeTimeRef.current = null;
11540
+ onPlaybackError?.();
11541
+ return false;
11542
+ }
11543
+ }
11344
11544
  function togglePlayback() {
11345
11545
  const element = audioRef.current;
11346
11546
  if (!element) return;
11347
11547
  if (element.paused) {
11548
+ if (refreshing) {
11549
+ pendingResumeRef.current = true;
11550
+ pendingResumeTimeRef.current = currentTime;
11551
+ return;
11552
+ }
11553
+ if (onRefresh && isUrlExpiringSoon()) {
11554
+ void refreshAndResume();
11555
+ return;
11556
+ }
11348
11557
  element.play().catch(() => {
11349
11558
  setPlaying(false);
11350
11559
  onPlaybackError?.();
@@ -11371,146 +11580,157 @@ function NarrationPlayer({
11371
11580
  if (!audio.url) {
11372
11581
  return null;
11373
11582
  }
11374
- return /* @__PURE__ */ jsxRuntime.jsxs(
11375
- "div",
11376
- {
11377
- className: "rounded-[1.75rem] border border-slate-200/80 bg-[linear-gradient(180deg,#f8fbff_0%,#edf4ff_100%)] p-4 text-slate-900 shadow-[0_22px_70px_rgba(4,12,24,0.28)] " + (className ?? ""),
11378
- children: [
11379
- /* @__PURE__ */ jsxRuntime.jsx(
11380
- "audio",
11381
- {
11382
- ref: audioRef,
11383
- src: audio.url ?? void 0,
11384
- preload: "metadata",
11385
- onLoadedMetadata: (event) => {
11386
- setDuration(event.currentTarget.duration || 0);
11387
- },
11388
- onTimeUpdate: (event) => {
11389
- setCurrentTime(event.currentTarget.currentTime);
11390
- },
11391
- onPlay: () => setPlaying(true),
11392
- onPause: () => setPlaying(false),
11393
- onEnded: () => setPlaying(false),
11394
- onError: () => onPlaybackError?.()
11583
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles.root, children: [
11584
+ /* @__PURE__ */ jsxRuntime.jsx(
11585
+ "audio",
11586
+ {
11587
+ ref: audioRef,
11588
+ src: audio.url ?? void 0,
11589
+ preload: "metadata",
11590
+ onLoadedMetadata: (event) => {
11591
+ setDuration(event.currentTarget.duration || 0);
11592
+ },
11593
+ onTimeUpdate: (event) => {
11594
+ setCurrentTime(event.currentTarget.currentTime);
11595
+ },
11596
+ onPlay: () => setPlaying(true),
11597
+ onPause: () => setPlaying(false),
11598
+ onEnded: () => setPlaying(false),
11599
+ onError: () => {
11600
+ if (onRefresh) {
11601
+ void refreshAndResume().then((didRefresh) => {
11602
+ if (!didRefresh) {
11603
+ onPlaybackError?.();
11604
+ }
11605
+ });
11606
+ return;
11395
11607
  }
11396
- ),
11397
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-center", children: [
11398
- /* @__PURE__ */ jsxRuntime.jsx(
11399
- "button",
11608
+ onPlaybackError?.();
11609
+ }
11610
+ }
11611
+ ),
11612
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.layout, children: [
11613
+ /* @__PURE__ */ jsxRuntime.jsx(
11614
+ "button",
11615
+ {
11616
+ type: "button",
11617
+ onClick: togglePlayback,
11618
+ "aria-label": playing ? "Pause narration" : "Play narration",
11619
+ style: styles.playButton,
11620
+ children: playing ? /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", style: styles.playIcon, "aria-hidden": "true", children: [
11621
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
11622
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
11623
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
11624
+ "svg",
11400
11625
  {
11401
- type: "button",
11402
- onClick: togglePlayback,
11403
- "aria-label": playing ? "Pause narration" : "Play narration",
11404
- className: "flex h-[4.5rem] w-[4.5rem] shrink-0 items-center justify-center rounded-full bg-slate-950 text-white shadow-[0_10px_28px_rgba(15,23,42,0.25)] transition hover:bg-slate-800",
11405
- children: playing ? /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", className: "h-7 w-7 fill-current", "aria-hidden": "true", children: [
11406
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
11407
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
11408
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "ml-1 h-7 w-7 fill-current", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" }) })
11626
+ viewBox: "0 0 24 24",
11627
+ style: { ...styles.playIcon, ...styles.playIconOffset },
11628
+ "aria-hidden": "true",
11629
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" })
11409
11630
  }
11410
- ),
11411
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
11412
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 text-[0.8rem] font-medium text-slate-500", children: [
11413
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(currentTime / speed) }),
11414
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
11415
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-blue-100 px-3 py-1 text-[0.65rem] font-semibold uppercase tracking-[0.24em] text-blue-700", children: narrationLabel }),
11416
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration / speed) })
11417
- ] })
11418
- ] }),
11419
- /* @__PURE__ */ jsxRuntime.jsx(
11420
- "div",
11631
+ )
11632
+ }
11633
+ ),
11634
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.content, children: [
11635
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaRow, children: [
11636
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(currentTime / speed) }),
11637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaRight, children: [
11638
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.badge, children: narrationLabel }),
11639
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration / speed) })
11640
+ ] })
11641
+ ] }),
11642
+ /* @__PURE__ */ jsxRuntime.jsx(
11643
+ "div",
11644
+ {
11645
+ ref: scrubRef,
11646
+ role: "slider",
11647
+ "aria-label": "Narration progress",
11648
+ "aria-valuemin": 0,
11649
+ "aria-valuemax": Math.max(1, Math.floor(duration)),
11650
+ "aria-valuenow": Math.floor(currentTime),
11651
+ tabIndex: 0,
11652
+ onClick: (event) => {
11653
+ seekTo(event.clientX);
11654
+ },
11655
+ style: styles.scrubber,
11656
+ children: peaks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
11657
+ "svg",
11421
11658
  {
11422
- ref: scrubRef,
11423
- role: "slider",
11424
- "aria-label": "Narration progress",
11425
- "aria-valuemin": 0,
11426
- "aria-valuemax": Math.max(1, Math.floor(duration)),
11427
- "aria-valuenow": Math.floor(currentTime),
11428
- tabIndex: 0,
11429
- onClick: (event) => {
11430
- seekTo(event.clientX);
11431
- },
11432
- className: "relative mt-4 h-12 cursor-pointer overflow-hidden rounded-[1rem] bg-slate-200/90 px-1",
11433
- children: peaks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
11434
- "svg",
11435
- {
11436
- viewBox: `0 0 ${peaks.length} 40`,
11437
- preserveAspectRatio: "none",
11438
- className: "pointer-events-none absolute inset-0 h-full w-full",
11439
- children: peaks.map((peak, index) => {
11440
- const height = Math.max(2, Math.round(Math.abs(peak) * 34));
11441
- const y = (40 - height) / 2;
11442
- const filled = index / peaks.length < progress;
11443
- return /* @__PURE__ */ jsxRuntime.jsx(
11444
- "rect",
11445
- {
11446
- x: index,
11447
- y,
11448
- width: 0.82,
11449
- height,
11450
- rx: 0.4,
11451
- className: filled ? "fill-blue-600" : "fill-blue-300"
11452
- },
11453
- `${audio.id}-${index}`
11454
- );
11455
- })
11456
- }
11457
- ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11458
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-slate-200/90" }),
11459
- /* @__PURE__ */ jsxRuntime.jsx(
11460
- "div",
11659
+ viewBox: `0 0 ${peaks.length} 40`,
11660
+ preserveAspectRatio: "none",
11661
+ style: styles.waveform,
11662
+ children: peaks.map((peak, index) => {
11663
+ const height = Math.max(2, Math.round(Math.abs(peak) * 34));
11664
+ const y = (40 - height) / 2;
11665
+ const filled = index / peaks.length < progress;
11666
+ return /* @__PURE__ */ jsxRuntime.jsx(
11667
+ "rect",
11461
11668
  {
11462
- className: "absolute inset-y-0 left-0 bg-blue-600",
11463
- style: { width: `${progress * 100}%` }
11464
- }
11465
- )
11466
- ] })
11669
+ x: index,
11670
+ y,
11671
+ width: 0.82,
11672
+ height,
11673
+ rx: 0.4,
11674
+ fill: filled ? "#155dfc" : "#90c5ff"
11675
+ },
11676
+ `${audio.id}-${index}`
11677
+ );
11678
+ })
11467
11679
  }
11468
- ),
11469
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-2 text-[0.72rem] font-medium text-slate-500", children: [
11470
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded-full border border-slate-200 bg-white/85 px-3 py-1 text-slate-700", children: [
11471
- "Provider: ",
11472
- providerLabel
11473
- ] }),
11474
- duration > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded-full border border-slate-200 bg-white/65 px-3 py-1", children: [
11475
- "Length ",
11476
- formatTime(duration / speed)
11477
- ] }),
11478
- expiresLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded-full border border-slate-200 bg-white/65 px-3 py-1", children: [
11479
- "Expires ",
11480
- expiresLabel
11481
- ] })
11680
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11681
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.fallbackTrack }),
11682
+ /* @__PURE__ */ jsxRuntime.jsx(
11683
+ "div",
11684
+ {
11685
+ style: { ...styles.fallbackProgress, width: `${progress * 100}%` }
11686
+ }
11687
+ )
11482
11688
  ] })
11689
+ }
11690
+ ),
11691
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.chipsRow, children: [
11692
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipStrong, children: [
11693
+ "Provider: ",
11694
+ providerLabel
11483
11695
  ] }),
11484
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 self-start md:self-center", children: [
11485
- /* @__PURE__ */ jsxRuntime.jsxs(
11486
- "button",
11487
- {
11488
- type: "button",
11489
- onClick: () => {
11490
- setSpeed((currentSpeed) => getNextSpeed(currentSpeed));
11491
- },
11492
- className: "rounded-full border border-slate-300 bg-white/90 px-4 py-2 text-base font-semibold text-slate-700 shadow-sm hover:bg-white",
11493
- children: [
11494
- speed,
11495
- "\xD7"
11496
- ]
11497
- }
11498
- ),
11499
- onRefresh && /* @__PURE__ */ jsxRuntime.jsx(
11500
- "button",
11501
- {
11502
- type: "button",
11503
- onClick: onRefresh,
11504
- disabled: refreshing,
11505
- className: "rounded-full border border-slate-300 bg-white/75 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-white disabled:cursor-not-allowed disabled:opacity-60",
11506
- children: refreshing ? "Refreshing\u2026" : "Refresh"
11507
- }
11508
- )
11696
+ duration > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipMuted, children: [
11697
+ "Length ",
11698
+ formatTime(duration / speed)
11699
+ ] }),
11700
+ expiresLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipMuted, children: [
11701
+ "Expires ",
11702
+ expiresLabel
11509
11703
  ] })
11510
11704
  ] })
11511
- ]
11512
- }
11513
- );
11705
+ ] }),
11706
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.controls, children: [
11707
+ /* @__PURE__ */ jsxRuntime.jsxs(
11708
+ "button",
11709
+ {
11710
+ type: "button",
11711
+ onClick: () => {
11712
+ setSpeed((currentSpeed) => getNextSpeed(currentSpeed));
11713
+ },
11714
+ style: styles.secondaryButton,
11715
+ children: [
11716
+ speed,
11717
+ "\xD7"
11718
+ ]
11719
+ }
11720
+ ),
11721
+ onRefresh && /* @__PURE__ */ jsxRuntime.jsx(
11722
+ "button",
11723
+ {
11724
+ type: "button",
11725
+ onClick: onRefresh,
11726
+ disabled: refreshing,
11727
+ style: refreshing ? { ...styles.refreshButton, ...styles.disabled } : styles.refreshButton,
11728
+ children: refreshing ? "Refreshing\u2026" : "Refresh"
11729
+ }
11730
+ )
11731
+ ] })
11732
+ ] })
11733
+ ] });
11514
11734
  }
11515
11735
 
11516
11736
  // src/validation.ts
package/dist/index.mjs CHANGED
@@ -11269,6 +11269,157 @@ function FeedbackWidget(props) {
11269
11269
  ] });
11270
11270
  }
11271
11271
  var SPEEDS = [1, 1.25, 1.5, 2];
11272
+ var EXPIRY_REFRESH_WINDOW_MS = 3e4;
11273
+ var styles = {
11274
+ root: {
11275
+ borderRadius: "1.75rem",
11276
+ border: "1px solid rgba(226, 232, 240, 0.8)",
11277
+ background: "linear-gradient(180deg, #f8fbff 0%, #edf4ff 100%)",
11278
+ padding: "1rem",
11279
+ color: "#0f172b",
11280
+ boxShadow: "0 22px 70px rgba(4, 12, 24, 0.28)"
11281
+ },
11282
+ layout: {
11283
+ display: "flex",
11284
+ flexWrap: "wrap",
11285
+ alignItems: "center",
11286
+ gap: "1rem"
11287
+ },
11288
+ playButton: {
11289
+ display: "flex",
11290
+ height: "4.5rem",
11291
+ width: "4.5rem",
11292
+ flexShrink: 0,
11293
+ alignItems: "center",
11294
+ justifyContent: "center",
11295
+ borderRadius: "9999px",
11296
+ border: "none",
11297
+ backgroundColor: "#020618",
11298
+ color: "#ffffff",
11299
+ boxShadow: "0 10px 28px rgba(15, 23, 42, 0.25)",
11300
+ cursor: "pointer"
11301
+ },
11302
+ playIcon: {
11303
+ display: "block",
11304
+ width: "1.75rem",
11305
+ height: "1.75rem",
11306
+ fill: "currentColor"
11307
+ },
11308
+ playIconOffset: {
11309
+ marginLeft: "0.25rem"
11310
+ },
11311
+ content: {
11312
+ minWidth: "16rem",
11313
+ flex: "1 1 18rem"
11314
+ },
11315
+ metaRow: {
11316
+ display: "flex",
11317
+ alignItems: "center",
11318
+ justifyContent: "space-between",
11319
+ gap: "0.75rem",
11320
+ fontSize: "0.8rem",
11321
+ fontWeight: 500,
11322
+ color: "#62748e"
11323
+ },
11324
+ metaRight: {
11325
+ display: "flex",
11326
+ alignItems: "center",
11327
+ gap: "0.75rem"
11328
+ },
11329
+ badge: {
11330
+ borderRadius: "9999px",
11331
+ backgroundColor: "#dbeafe",
11332
+ padding: "0.25rem 0.75rem",
11333
+ fontSize: "0.65rem",
11334
+ fontWeight: 600,
11335
+ letterSpacing: "0.24em",
11336
+ textTransform: "uppercase",
11337
+ color: "#1447e6"
11338
+ },
11339
+ scrubber: {
11340
+ position: "relative",
11341
+ marginTop: "1rem",
11342
+ height: "3rem",
11343
+ cursor: "pointer",
11344
+ overflow: "hidden",
11345
+ borderRadius: "1rem",
11346
+ backgroundColor: "rgba(226, 232, 240, 0.9)",
11347
+ paddingInline: "0.25rem"
11348
+ },
11349
+ waveform: {
11350
+ pointerEvents: "none",
11351
+ position: "absolute",
11352
+ inset: 0,
11353
+ height: "100%",
11354
+ width: "100%"
11355
+ },
11356
+ fallbackTrack: {
11357
+ position: "absolute",
11358
+ inset: 0,
11359
+ backgroundColor: "rgba(226, 232, 240, 0.9)"
11360
+ },
11361
+ fallbackProgress: {
11362
+ position: "absolute",
11363
+ insetBlock: 0,
11364
+ left: 0,
11365
+ backgroundColor: "#155dfc"
11366
+ },
11367
+ chipsRow: {
11368
+ marginTop: "1rem",
11369
+ display: "flex",
11370
+ flexWrap: "wrap",
11371
+ alignItems: "center",
11372
+ gap: "0.5rem",
11373
+ fontSize: "0.72rem",
11374
+ fontWeight: 500,
11375
+ color: "#62748e"
11376
+ },
11377
+ chipStrong: {
11378
+ borderRadius: "9999px",
11379
+ border: "1px solid #e2e8f0",
11380
+ backgroundColor: "rgba(255, 255, 255, 0.85)",
11381
+ padding: "0.25rem 0.75rem",
11382
+ color: "#334155"
11383
+ },
11384
+ chipMuted: {
11385
+ borderRadius: "9999px",
11386
+ border: "1px solid #e2e8f0",
11387
+ backgroundColor: "rgba(255, 255, 255, 0.65)",
11388
+ padding: "0.25rem 0.75rem"
11389
+ },
11390
+ controls: {
11391
+ display: "flex",
11392
+ flexShrink: 0,
11393
+ alignItems: "center",
11394
+ gap: "0.5rem",
11395
+ alignSelf: "flex-start"
11396
+ },
11397
+ secondaryButton: {
11398
+ borderRadius: "9999px",
11399
+ border: "1px solid #cad5e2",
11400
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
11401
+ padding: "0.5rem 1rem",
11402
+ fontSize: "1rem",
11403
+ fontWeight: 600,
11404
+ color: "#334155",
11405
+ boxShadow: "0 1px 3px rgba(15, 23, 42, 0.08)",
11406
+ cursor: "pointer"
11407
+ },
11408
+ refreshButton: {
11409
+ borderRadius: "9999px",
11410
+ border: "1px solid #cad5e2",
11411
+ backgroundColor: "rgba(255, 255, 255, 0.75)",
11412
+ padding: "0.5rem 1rem",
11413
+ fontSize: "0.875rem",
11414
+ fontWeight: 600,
11415
+ color: "#334155",
11416
+ cursor: "pointer"
11417
+ },
11418
+ disabled: {
11419
+ cursor: "not-allowed",
11420
+ opacity: 0.6
11421
+ }
11422
+ };
11272
11423
  function formatTime(seconds) {
11273
11424
  if (!Number.isFinite(seconds) || seconds <= 0) {
11274
11425
  return "0:00";
@@ -11296,6 +11447,9 @@ function NarrationPlayer({
11296
11447
  }) {
11297
11448
  const audioRef = useRef(null);
11298
11449
  const scrubRef = useRef(null);
11450
+ const pendingResumeRef = useRef(false);
11451
+ const pendingResumeTimeRef = useRef(null);
11452
+ const recoveredUrlRef = useRef(null);
11299
11453
  const [playing, setPlaying] = useState(false);
11300
11454
  const [currentTime, setCurrentTime] = useState(0);
11301
11455
  const [duration, setDuration] = useState(audio.duration_ms ? audio.duration_ms / 1e3 : 0);
@@ -11310,10 +11464,26 @@ function NarrationPlayer({
11310
11464
  useEffect(() => {
11311
11465
  const element = audioRef.current;
11312
11466
  if (!element) return;
11313
- setCurrentTime(0);
11467
+ recoveredUrlRef.current = null;
11468
+ const resumeTime = pendingResumeTimeRef.current;
11469
+ setCurrentTime(resumeTime ?? 0);
11314
11470
  setPlaying(false);
11315
11471
  element.pause();
11316
- element.currentTime = 0;
11472
+ try {
11473
+ element.currentTime = resumeTime ?? 0;
11474
+ } catch {
11475
+ element.currentTime = 0;
11476
+ }
11477
+ if (pendingResumeRef.current) {
11478
+ pendingResumeRef.current = false;
11479
+ pendingResumeTimeRef.current = null;
11480
+ element.play().catch(() => {
11481
+ setPlaying(false);
11482
+ onPlaybackError?.();
11483
+ });
11484
+ return;
11485
+ }
11486
+ pendingResumeTimeRef.current = null;
11317
11487
  }, [audio.url]);
11318
11488
  useEffect(() => {
11319
11489
  const element = audioRef.current;
@@ -11321,10 +11491,49 @@ function NarrationPlayer({
11321
11491
  element.playbackRate = speed;
11322
11492
  element.preservesPitch = true;
11323
11493
  }, [speed]);
11494
+ function isUrlExpiringSoon() {
11495
+ if (!audio.expires_at) {
11496
+ return false;
11497
+ }
11498
+ const expiresAtMs = Date.parse(audio.expires_at);
11499
+ if (!Number.isFinite(expiresAtMs)) {
11500
+ return false;
11501
+ }
11502
+ return expiresAtMs - Date.now() <= EXPIRY_REFRESH_WINDOW_MS;
11503
+ }
11504
+ async function refreshAndResume() {
11505
+ if (!onRefresh || refreshing) {
11506
+ return false;
11507
+ }
11508
+ if (recoveredUrlRef.current === (audio.url ?? null)) {
11509
+ return false;
11510
+ }
11511
+ recoveredUrlRef.current = audio.url ?? null;
11512
+ pendingResumeRef.current = true;
11513
+ pendingResumeTimeRef.current = currentTime;
11514
+ try {
11515
+ await Promise.resolve(onRefresh());
11516
+ return true;
11517
+ } catch {
11518
+ pendingResumeRef.current = false;
11519
+ pendingResumeTimeRef.current = null;
11520
+ onPlaybackError?.();
11521
+ return false;
11522
+ }
11523
+ }
11324
11524
  function togglePlayback() {
11325
11525
  const element = audioRef.current;
11326
11526
  if (!element) return;
11327
11527
  if (element.paused) {
11528
+ if (refreshing) {
11529
+ pendingResumeRef.current = true;
11530
+ pendingResumeTimeRef.current = currentTime;
11531
+ return;
11532
+ }
11533
+ if (onRefresh && isUrlExpiringSoon()) {
11534
+ void refreshAndResume();
11535
+ return;
11536
+ }
11328
11537
  element.play().catch(() => {
11329
11538
  setPlaying(false);
11330
11539
  onPlaybackError?.();
@@ -11351,146 +11560,157 @@ function NarrationPlayer({
11351
11560
  if (!audio.url) {
11352
11561
  return null;
11353
11562
  }
11354
- return /* @__PURE__ */ jsxs(
11355
- "div",
11356
- {
11357
- className: "rounded-[1.75rem] border border-slate-200/80 bg-[linear-gradient(180deg,#f8fbff_0%,#edf4ff_100%)] p-4 text-slate-900 shadow-[0_22px_70px_rgba(4,12,24,0.28)] " + (className ?? ""),
11358
- children: [
11359
- /* @__PURE__ */ jsx(
11360
- "audio",
11361
- {
11362
- ref: audioRef,
11363
- src: audio.url ?? void 0,
11364
- preload: "metadata",
11365
- onLoadedMetadata: (event) => {
11366
- setDuration(event.currentTarget.duration || 0);
11367
- },
11368
- onTimeUpdate: (event) => {
11369
- setCurrentTime(event.currentTarget.currentTime);
11370
- },
11371
- onPlay: () => setPlaying(true),
11372
- onPause: () => setPlaying(false),
11373
- onEnded: () => setPlaying(false),
11374
- onError: () => onPlaybackError?.()
11563
+ return /* @__PURE__ */ jsxs("div", { className, style: styles.root, children: [
11564
+ /* @__PURE__ */ jsx(
11565
+ "audio",
11566
+ {
11567
+ ref: audioRef,
11568
+ src: audio.url ?? void 0,
11569
+ preload: "metadata",
11570
+ onLoadedMetadata: (event) => {
11571
+ setDuration(event.currentTarget.duration || 0);
11572
+ },
11573
+ onTimeUpdate: (event) => {
11574
+ setCurrentTime(event.currentTarget.currentTime);
11575
+ },
11576
+ onPlay: () => setPlaying(true),
11577
+ onPause: () => setPlaying(false),
11578
+ onEnded: () => setPlaying(false),
11579
+ onError: () => {
11580
+ if (onRefresh) {
11581
+ void refreshAndResume().then((didRefresh) => {
11582
+ if (!didRefresh) {
11583
+ onPlaybackError?.();
11584
+ }
11585
+ });
11586
+ return;
11375
11587
  }
11376
- ),
11377
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-center", children: [
11378
- /* @__PURE__ */ jsx(
11379
- "button",
11588
+ onPlaybackError?.();
11589
+ }
11590
+ }
11591
+ ),
11592
+ /* @__PURE__ */ jsxs("div", { style: styles.layout, children: [
11593
+ /* @__PURE__ */ jsx(
11594
+ "button",
11595
+ {
11596
+ type: "button",
11597
+ onClick: togglePlayback,
11598
+ "aria-label": playing ? "Pause narration" : "Play narration",
11599
+ style: styles.playButton,
11600
+ children: playing ? /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", style: styles.playIcon, "aria-hidden": "true", children: [
11601
+ /* @__PURE__ */ jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
11602
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
11603
+ ] }) : /* @__PURE__ */ jsx(
11604
+ "svg",
11380
11605
  {
11381
- type: "button",
11382
- onClick: togglePlayback,
11383
- "aria-label": playing ? "Pause narration" : "Play narration",
11384
- className: "flex h-[4.5rem] w-[4.5rem] shrink-0 items-center justify-center rounded-full bg-slate-950 text-white shadow-[0_10px_28px_rgba(15,23,42,0.25)] transition hover:bg-slate-800",
11385
- children: playing ? /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className: "h-7 w-7 fill-current", "aria-hidden": "true", children: [
11386
- /* @__PURE__ */ jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
11387
- /* @__PURE__ */ jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
11388
- ] }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "ml-1 h-7 w-7 fill-current", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" }) })
11606
+ viewBox: "0 0 24 24",
11607
+ style: { ...styles.playIcon, ...styles.playIconOffset },
11608
+ "aria-hidden": "true",
11609
+ children: /* @__PURE__ */ jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" })
11389
11610
  }
11390
- ),
11391
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
11392
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 text-[0.8rem] font-medium text-slate-500", children: [
11393
- /* @__PURE__ */ jsx("span", { children: formatTime(currentTime / speed) }),
11394
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
11395
- /* @__PURE__ */ jsx("span", { className: "rounded-full bg-blue-100 px-3 py-1 text-[0.65rem] font-semibold uppercase tracking-[0.24em] text-blue-700", children: narrationLabel }),
11396
- /* @__PURE__ */ jsx("span", { children: formatTime(duration / speed) })
11397
- ] })
11398
- ] }),
11399
- /* @__PURE__ */ jsx(
11400
- "div",
11611
+ )
11612
+ }
11613
+ ),
11614
+ /* @__PURE__ */ jsxs("div", { style: styles.content, children: [
11615
+ /* @__PURE__ */ jsxs("div", { style: styles.metaRow, children: [
11616
+ /* @__PURE__ */ jsx("span", { children: formatTime(currentTime / speed) }),
11617
+ /* @__PURE__ */ jsxs("div", { style: styles.metaRight, children: [
11618
+ /* @__PURE__ */ jsx("span", { style: styles.badge, children: narrationLabel }),
11619
+ /* @__PURE__ */ jsx("span", { children: formatTime(duration / speed) })
11620
+ ] })
11621
+ ] }),
11622
+ /* @__PURE__ */ jsx(
11623
+ "div",
11624
+ {
11625
+ ref: scrubRef,
11626
+ role: "slider",
11627
+ "aria-label": "Narration progress",
11628
+ "aria-valuemin": 0,
11629
+ "aria-valuemax": Math.max(1, Math.floor(duration)),
11630
+ "aria-valuenow": Math.floor(currentTime),
11631
+ tabIndex: 0,
11632
+ onClick: (event) => {
11633
+ seekTo(event.clientX);
11634
+ },
11635
+ style: styles.scrubber,
11636
+ children: peaks.length > 0 ? /* @__PURE__ */ jsx(
11637
+ "svg",
11401
11638
  {
11402
- ref: scrubRef,
11403
- role: "slider",
11404
- "aria-label": "Narration progress",
11405
- "aria-valuemin": 0,
11406
- "aria-valuemax": Math.max(1, Math.floor(duration)),
11407
- "aria-valuenow": Math.floor(currentTime),
11408
- tabIndex: 0,
11409
- onClick: (event) => {
11410
- seekTo(event.clientX);
11411
- },
11412
- className: "relative mt-4 h-12 cursor-pointer overflow-hidden rounded-[1rem] bg-slate-200/90 px-1",
11413
- children: peaks.length > 0 ? /* @__PURE__ */ jsx(
11414
- "svg",
11415
- {
11416
- viewBox: `0 0 ${peaks.length} 40`,
11417
- preserveAspectRatio: "none",
11418
- className: "pointer-events-none absolute inset-0 h-full w-full",
11419
- children: peaks.map((peak, index) => {
11420
- const height = Math.max(2, Math.round(Math.abs(peak) * 34));
11421
- const y = (40 - height) / 2;
11422
- const filled = index / peaks.length < progress;
11423
- return /* @__PURE__ */ jsx(
11424
- "rect",
11425
- {
11426
- x: index,
11427
- y,
11428
- width: 0.82,
11429
- height,
11430
- rx: 0.4,
11431
- className: filled ? "fill-blue-600" : "fill-blue-300"
11432
- },
11433
- `${audio.id}-${index}`
11434
- );
11435
- })
11436
- }
11437
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
11438
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-slate-200/90" }),
11439
- /* @__PURE__ */ jsx(
11440
- "div",
11639
+ viewBox: `0 0 ${peaks.length} 40`,
11640
+ preserveAspectRatio: "none",
11641
+ style: styles.waveform,
11642
+ children: peaks.map((peak, index) => {
11643
+ const height = Math.max(2, Math.round(Math.abs(peak) * 34));
11644
+ const y = (40 - height) / 2;
11645
+ const filled = index / peaks.length < progress;
11646
+ return /* @__PURE__ */ jsx(
11647
+ "rect",
11441
11648
  {
11442
- className: "absolute inset-y-0 left-0 bg-blue-600",
11443
- style: { width: `${progress * 100}%` }
11444
- }
11445
- )
11446
- ] })
11649
+ x: index,
11650
+ y,
11651
+ width: 0.82,
11652
+ height,
11653
+ rx: 0.4,
11654
+ fill: filled ? "#155dfc" : "#90c5ff"
11655
+ },
11656
+ `${audio.id}-${index}`
11657
+ );
11658
+ })
11447
11659
  }
11448
- ),
11449
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-2 text-[0.72rem] font-medium text-slate-500", children: [
11450
- /* @__PURE__ */ jsxs("span", { className: "rounded-full border border-slate-200 bg-white/85 px-3 py-1 text-slate-700", children: [
11451
- "Provider: ",
11452
- providerLabel
11453
- ] }),
11454
- duration > 0 && /* @__PURE__ */ jsxs("span", { className: "rounded-full border border-slate-200 bg-white/65 px-3 py-1", children: [
11455
- "Length ",
11456
- formatTime(duration / speed)
11457
- ] }),
11458
- expiresLabel && /* @__PURE__ */ jsxs("span", { className: "rounded-full border border-slate-200 bg-white/65 px-3 py-1", children: [
11459
- "Expires ",
11460
- expiresLabel
11461
- ] })
11660
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
11661
+ /* @__PURE__ */ jsx("div", { style: styles.fallbackTrack }),
11662
+ /* @__PURE__ */ jsx(
11663
+ "div",
11664
+ {
11665
+ style: { ...styles.fallbackProgress, width: `${progress * 100}%` }
11666
+ }
11667
+ )
11462
11668
  ] })
11669
+ }
11670
+ ),
11671
+ /* @__PURE__ */ jsxs("div", { style: styles.chipsRow, children: [
11672
+ /* @__PURE__ */ jsxs("span", { style: styles.chipStrong, children: [
11673
+ "Provider: ",
11674
+ providerLabel
11463
11675
  ] }),
11464
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 self-start md:self-center", children: [
11465
- /* @__PURE__ */ jsxs(
11466
- "button",
11467
- {
11468
- type: "button",
11469
- onClick: () => {
11470
- setSpeed((currentSpeed) => getNextSpeed(currentSpeed));
11471
- },
11472
- className: "rounded-full border border-slate-300 bg-white/90 px-4 py-2 text-base font-semibold text-slate-700 shadow-sm hover:bg-white",
11473
- children: [
11474
- speed,
11475
- "\xD7"
11476
- ]
11477
- }
11478
- ),
11479
- onRefresh && /* @__PURE__ */ jsx(
11480
- "button",
11481
- {
11482
- type: "button",
11483
- onClick: onRefresh,
11484
- disabled: refreshing,
11485
- className: "rounded-full border border-slate-300 bg-white/75 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-white disabled:cursor-not-allowed disabled:opacity-60",
11486
- children: refreshing ? "Refreshing\u2026" : "Refresh"
11487
- }
11488
- )
11676
+ duration > 0 && /* @__PURE__ */ jsxs("span", { style: styles.chipMuted, children: [
11677
+ "Length ",
11678
+ formatTime(duration / speed)
11679
+ ] }),
11680
+ expiresLabel && /* @__PURE__ */ jsxs("span", { style: styles.chipMuted, children: [
11681
+ "Expires ",
11682
+ expiresLabel
11489
11683
  ] })
11490
11684
  ] })
11491
- ]
11492
- }
11493
- );
11685
+ ] }),
11686
+ /* @__PURE__ */ jsxs("div", { style: styles.controls, children: [
11687
+ /* @__PURE__ */ jsxs(
11688
+ "button",
11689
+ {
11690
+ type: "button",
11691
+ onClick: () => {
11692
+ setSpeed((currentSpeed) => getNextSpeed(currentSpeed));
11693
+ },
11694
+ style: styles.secondaryButton,
11695
+ children: [
11696
+ speed,
11697
+ "\xD7"
11698
+ ]
11699
+ }
11700
+ ),
11701
+ onRefresh && /* @__PURE__ */ jsx(
11702
+ "button",
11703
+ {
11704
+ type: "button",
11705
+ onClick: onRefresh,
11706
+ disabled: refreshing,
11707
+ style: refreshing ? { ...styles.refreshButton, ...styles.disabled } : styles.refreshButton,
11708
+ children: refreshing ? "Refreshing\u2026" : "Refresh"
11709
+ }
11710
+ )
11711
+ ] })
11712
+ ] })
11713
+ ] });
11494
11714
  }
11495
11715
 
11496
11716
  // src/validation.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",