@shortkitsdk/web 0.3.0 → 0.3.1

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.
@@ -908,7 +908,7 @@
908
908
  }
909
909
  setMuted(muted) {
910
910
  this.isMuted = muted;
911
- for (const [, player] of this.pool._players) {
911
+ for (const [, player] of this.pool.assignments) {
912
912
  player.muted = muted;
913
913
  }
914
914
  this._pushPlayerState({ isMuted: muted });
@@ -921,7 +921,7 @@
921
921
  }
922
922
  setPlaybackRate(rate) {
923
923
  this.playbackRate = rate;
924
- for (const [, player] of this.pool._players) {
924
+ for (const [, player] of this.pool.assignments) {
925
925
  player.playbackRate = rate;
926
926
  }
927
927
  this._pushPlayerState({ playbackRate: rate });
@@ -1398,11 +1398,10 @@
1398
1398
  const player = this.pool.getPlayer(id);
1399
1399
  if (player) {
1400
1400
  player.pause();
1401
- player.style.opacity = "0";
1402
1401
  player._skRevealedFor = null;
1403
1402
  }
1404
1403
  this._stopTimeLoop(id);
1405
- this._clearOverlay(id);
1404
+ this._detachOverlay(id);
1406
1405
  this._sk._tracker.deactivateContent();
1407
1406
  }
1408
1407
  // --- Time loop ---
@@ -1584,14 +1583,20 @@
1584
1583
  }
1585
1584
  }
1586
1585
  /** Clear the overlay container's DOM and unsubscribe tracked listeners. */
1587
- _clearOverlay(itemId) {
1586
+ /** Unsubscribe overlay event listeners without clearing DOM. */
1587
+ _detachOverlay(itemId) {
1588
1588
  const entry = this._overlayContainers.get(itemId);
1589
1589
  if (!entry) return;
1590
1590
  if (entry.unsub) {
1591
1591
  entry.unsub();
1592
1592
  entry.unsub = null;
1593
1593
  }
1594
- entry.el.innerHTML = "";
1594
+ }
1595
+ /** Unsubscribe and clear overlay DOM (used on destroy). */
1596
+ _clearOverlay(itemId) {
1597
+ this._detachOverlay(itemId);
1598
+ const entry = this._overlayContainers.get(itemId);
1599
+ if (entry) entry.el.innerHTML = "";
1595
1600
  }
1596
1601
  }
1597
1602
  class EmbeddedFeedManager {
@@ -1705,7 +1710,7 @@
1705
1710
  }
1706
1711
  setMuted(muted) {
1707
1712
  this.isMuted = muted;
1708
- for (const [, player] of this.pool._players) {
1713
+ for (const [, player] of this.pool.assignments) {
1709
1714
  player.muted = muted;
1710
1715
  }
1711
1716
  this._pushPlayerState({ isMuted: muted });
@@ -1718,7 +1723,7 @@
1718
1723
  }
1719
1724
  setPlaybackRate(rate) {
1720
1725
  this.playbackRate = rate;
1721
- for (const [, player] of this.pool._players) {
1726
+ for (const [, player] of this.pool.assignments) {
1722
1727
  player.playbackRate = rate;
1723
1728
  }
1724
1729
  this._pushPlayerState({ playbackRate: rate });
@@ -1990,11 +1995,10 @@
1990
1995
  const p = this.pool.getPlayer(id);
1991
1996
  if (p) {
1992
1997
  p.pause();
1993
- p.style.opacity = "0";
1994
1998
  p._skRevealedFor = null;
1995
1999
  }
1996
2000
  this._stopTimeLoop(id);
1997
- this._clearOverlay(id);
2001
+ this._detachOverlay(id);
1998
2002
  }
1999
2003
  // --- Time loop (state emission only, no DOM updates) ---
2000
2004
  _startTimeLoop(id, el, player) {
@@ -2193,14 +2197,20 @@
2193
2197
  } catch (e) {
2194
2198
  }
2195
2199
  }
2196
- _clearOverlay(itemId) {
2200
+ /** Unsubscribe overlay event listeners without clearing DOM. */
2201
+ _detachOverlay(itemId) {
2197
2202
  const entry = this._overlayContainers.get(itemId);
2198
2203
  if (!entry) return;
2199
2204
  if (entry.unsub) {
2200
2205
  entry.unsub();
2201
2206
  entry.unsub = null;
2202
2207
  }
2203
- entry.el.innerHTML = "";
2208
+ }
2209
+ /** Unsubscribe and clear overlay DOM (used on destroy). */
2210
+ _clearOverlay(itemId) {
2211
+ this._detachOverlay(itemId);
2212
+ const entry = this._overlayContainers.get(itemId);
2213
+ if (entry) entry.el.innerHTML = "";
2204
2214
  }
2205
2215
  }
2206
2216
  const MuteOnSvg = '<svg viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>';
@@ -2550,6 +2560,7 @@
2550
2560
  this._isOpen = true;
2551
2561
  this._onClose = onClose;
2552
2562
  this._openItemId = item.id;
2563
+ this._openThumbnailUrl = item.thumbnailUrl || null;
2553
2564
  const sourceRect = sourceEl.getBoundingClientRect();
2554
2565
  const sourceRadius = parseFloat(getComputedStyle(sourceEl).borderRadius) || 12;
2555
2566
  this.overlayEl.classList.add("skp-active");
@@ -2594,6 +2605,21 @@
2594
2605
  feedEl.style.transform = "none";
2595
2606
  feedEl.style.borderRadius = `${targetRadius}px`;
2596
2607
  });
2608
+ this.feedManager.startObserver();
2609
+ this.feedManager._activateItem(item.id);
2610
+ this.overlayEl.classList.add("skp-feed-ready");
2611
+ const itemOverlay = feedItemEl?.querySelector('[data-ref="overlay"]');
2612
+ const chromeEls = [...this.overlayEl.querySelectorAll(".skp-feed-close, .sk-sidebar")];
2613
+ if (itemOverlay) chromeEls.push(itemOverlay);
2614
+ chromeEls.forEach((el) => {
2615
+ el.style.opacity = "0";
2616
+ el.style.transition = "none";
2617
+ });
2618
+ this.overlayEl.getBoundingClientRect();
2619
+ chromeEls.forEach((el) => {
2620
+ el.style.transition = "opacity .35s cubic-bezier(.32,.72,0,1)";
2621
+ el.style.opacity = "1";
2622
+ });
2597
2623
  await this._waitForTransitionEnd(feedEl, 450);
2598
2624
  feedEl.classList.remove("skp-flip-animating");
2599
2625
  feedEl.style.transformOrigin = "";
@@ -2601,9 +2627,10 @@
2601
2627
  feedEl.style.scrollSnapType = "";
2602
2628
  feedEl.style.transform = "";
2603
2629
  feedEl.style.borderRadius = "";
2604
- this.feedManager.startObserver();
2605
- this.feedManager._activateItem(item.id);
2606
- this.overlayEl.classList.add("skp-feed-ready");
2630
+ chromeEls.forEach((el) => {
2631
+ el.style.opacity = "";
2632
+ el.style.transition = "";
2633
+ });
2607
2634
  this._escHandler = (e) => {
2608
2635
  if (e.key === "Escape") this.close();
2609
2636
  };
@@ -2616,33 +2643,44 @@
2616
2643
  const feedWrapper = feedEl.parentNode;
2617
2644
  const feedRect = feedEl.getBoundingClientRect();
2618
2645
  const activeItemId = this.feedManager?.activeItemId;
2619
- let transferBack = false, transferVideo = null, transferHls = null;
2620
- if (activeItemId === this._openItemId) {
2621
- const ejected = this.feedManager?.pool.ejectPlayer(activeItemId);
2622
- if (ejected) {
2623
- transferBack = true;
2624
- transferVideo = ejected.video;
2625
- transferHls = ejected.hls;
2626
- }
2627
- }
2646
+ const canTransfer = activeItemId === this._openItemId;
2628
2647
  let targetRect = null;
2629
2648
  if (this._onClose) targetRect = this._onClose("getSourceRect");
2630
2649
  if (feedRect && targetRect) {
2631
- if (this.feedManager?.activeItemId && !transferBack) {
2650
+ if (this.feedManager?.activeItemId && !canTransfer) {
2632
2651
  this.feedManager._deactivateItem(this.feedManager.activeItemId);
2633
2652
  }
2653
+ const targetItemId = canTransfer ? this._openItemId : activeItemId;
2654
+ if (targetItemId && this.feedManager) {
2655
+ const targetEl = this.feedManager.itemEls.get(targetItemId);
2656
+ if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
2657
+ }
2658
+ const savedScrollTop = feedEl.scrollTop;
2634
2659
  feedEl.style.position = "fixed";
2635
2660
  feedEl.style.left = `${feedRect.left}px`;
2636
2661
  feedEl.style.top = `${feedRect.top}px`;
2637
2662
  feedEl.style.width = `${feedRect.width}px`;
2638
2663
  feedEl.style.height = `${feedRect.height}px`;
2639
- feedEl.style.zIndex = "9999";
2664
+ feedEl.style.zIndex = String(getComputedStyle(this.overlayEl).zIndex || 9999);
2640
2665
  feedEl.style.margin = "0";
2641
2666
  feedEl.style.flex = "none";
2642
2667
  feedEl.style.aspectRatio = "unset";
2643
2668
  feedEl.style.maxHeight = "none";
2644
2669
  document.body.appendChild(feedEl);
2670
+ feedEl.scrollTop = savedScrollTop;
2645
2671
  this.overlayEl.classList.remove("skp-active", "skp-feed-ready");
2672
+ let thumbOverlay = null;
2673
+ if (this._openThumbnailUrl) {
2674
+ const crossfadeItemId = canTransfer ? this._openItemId : activeItemId;
2675
+ const crossfadeEl = crossfadeItemId && this.feedManager?.itemEls.get(crossfadeItemId);
2676
+ const videoContainer = crossfadeEl?.querySelector('[data-ref="videoContainer"]');
2677
+ if (videoContainer) {
2678
+ thumbOverlay = document.createElement("img");
2679
+ thumbOverlay.src = this._openThumbnailUrl;
2680
+ thumbOverlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s cubic-bezier(.32,.72,0,1);pointer-events:none;z-index:2;";
2681
+ videoContainer.appendChild(thumbOverlay);
2682
+ }
2683
+ }
2646
2684
  const scaleX = targetRect.width / feedRect.width;
2647
2685
  const scaleY = targetRect.height / feedRect.height;
2648
2686
  const translateX = targetRect.left - feedRect.left;
@@ -2652,29 +2690,58 @@
2652
2690
  feedEl.style.transformOrigin = "0 0";
2653
2691
  feedEl.style.overflow = "hidden";
2654
2692
  feedEl.style.scrollSnapType = "none";
2693
+ feedEl.style.pointerEvents = "none";
2655
2694
  feedEl.getBoundingClientRect();
2656
2695
  feedEl.classList.add("skp-flip-animating");
2657
2696
  requestAnimationFrame(() => {
2658
2697
  feedEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
2659
2698
  feedEl.style.borderRadius = `${endRadius}px`;
2699
+ if (thumbOverlay) thumbOverlay.style.opacity = "1";
2660
2700
  });
2661
2701
  await this._waitForTransitionEnd(feedEl, 450);
2702
+ if (thumbOverlay) thumbOverlay.remove();
2703
+ let transferVideo = null;
2704
+ let transferHls = null;
2705
+ if (canTransfer) {
2706
+ const ejected = this.feedManager?.pool.ejectPlayer(this._openItemId);
2707
+ if (ejected) {
2708
+ transferVideo = ejected.video;
2709
+ transferHls = ejected.hls;
2710
+ }
2711
+ }
2662
2712
  feedEl.style.visibility = "hidden";
2663
2713
  feedEl.classList.remove("skp-flip-animating");
2664
- ["position", "left", "top", "width", "height", "zIndex", "margin", "flex", "aspectRatio", "maxHeight", "transformOrigin", "overflow", "scrollSnapType", "transform", "borderRadius"].forEach((p) => feedEl.style[p] = "");
2714
+ ["position", "left", "top", "width", "height", "zIndex", "margin", "flex", "aspectRatio", "maxHeight", "transformOrigin", "overflow", "scrollSnapType", "pointerEvents", "transform", "borderRadius"].forEach((p) => feedEl.style[p] = "");
2665
2715
  const sidebarEl = feedWrapper.querySelector(".sk-sidebar");
2666
2716
  feedWrapper.insertBefore(feedEl, sidebarEl);
2667
2717
  feedEl.style.visibility = "";
2718
+ if (this._onClose) {
2719
+ this._onClose("closed", {
2720
+ transferVideo,
2721
+ transferHls,
2722
+ transferItemId: transferVideo ? this._openItemId : null
2723
+ });
2724
+ this._onClose = null;
2725
+ }
2668
2726
  } else {
2669
2727
  this.overlayEl.classList.remove("skp-active", "skp-feed-ready");
2670
- }
2671
- if (this._onClose) {
2672
- this._onClose("closed", {
2673
- transferVideo: transferBack ? transferVideo : null,
2674
- transferHls: transferBack ? transferHls : null,
2675
- transferItemId: transferBack ? this._openItemId : null
2676
- });
2677
- this._onClose = null;
2728
+ if (this._onClose) {
2729
+ let transferVideo = null;
2730
+ let transferHls = null;
2731
+ if (canTransfer) {
2732
+ const ejected = this.feedManager?.pool.ejectPlayer(this._openItemId);
2733
+ if (ejected) {
2734
+ transferVideo = ejected.video;
2735
+ transferHls = ejected.hls;
2736
+ }
2737
+ }
2738
+ this._onClose("closed", {
2739
+ transferVideo,
2740
+ transferHls,
2741
+ transferItemId: transferVideo ? this._openItemId : null
2742
+ });
2743
+ this._onClose = null;
2744
+ }
2678
2745
  }
2679
2746
  if (this.feedManager) {
2680
2747
  this.feedManager.destroy();
@@ -2726,6 +2793,7 @@
2726
2793
  this._onClose = onClose;
2727
2794
  this._openSlotIndex = startIndex;
2728
2795
  this._openItemId = item.id;
2796
+ this._openThumbnailUrl = item.thumbnailUrl || null;
2729
2797
  const slotRect = slotEl.getBoundingClientRect();
2730
2798
  const slotRadius = parseFloat(getComputedStyle(slotEl).borderRadius) || 12;
2731
2799
  if (_isPreview) {
@@ -2772,6 +2840,21 @@
2772
2840
  feedEl.style.transform = "none";
2773
2841
  feedEl.style.borderRadius = `${targetRadius}px`;
2774
2842
  });
2843
+ this.feedManager.startObserver();
2844
+ this.feedManager._activateItem(item.id);
2845
+ this.overlayEl.classList.add("skw-feed-ready");
2846
+ const itemOverlay = feedItemEl?.querySelector('[data-ref="overlay"]');
2847
+ const chromeEls = [...this.overlayEl.querySelectorAll(".skw-feed-close, .sk-sidebar")];
2848
+ if (itemOverlay) chromeEls.push(itemOverlay);
2849
+ chromeEls.forEach((el) => {
2850
+ el.style.opacity = "0";
2851
+ el.style.transition = "none";
2852
+ });
2853
+ this.overlayEl.getBoundingClientRect();
2854
+ chromeEls.forEach((el) => {
2855
+ el.style.transition = "opacity .35s cubic-bezier(.32,.72,0,1)";
2856
+ el.style.opacity = "1";
2857
+ });
2775
2858
  await this._waitForTransitionEnd(feedEl, 450);
2776
2859
  feedEl.classList.remove("skw-flip-animating");
2777
2860
  feedEl.style.transformOrigin = "";
@@ -2779,9 +2862,10 @@
2779
2862
  feedEl.style.scrollSnapType = "";
2780
2863
  feedEl.style.transform = "";
2781
2864
  feedEl.style.borderRadius = "";
2782
- this.feedManager.startObserver();
2783
- this.feedManager._activateItem(item.id);
2784
- this.overlayEl.classList.add("skw-feed-ready");
2865
+ chromeEls.forEach((el) => {
2866
+ el.style.opacity = "";
2867
+ el.style.transition = "";
2868
+ });
2785
2869
  this._escHandler = (e) => {
2786
2870
  if (e.key === "Escape") this.close();
2787
2871
  };
@@ -2808,6 +2892,12 @@
2808
2892
  if (this.feedManager?.activeItemId && !canTransfer) {
2809
2893
  this.feedManager._deactivateItem(this.feedManager.activeItemId);
2810
2894
  }
2895
+ const targetItemId = canTransfer ? this._openItemId : activeItemId;
2896
+ if (targetItemId && this.feedManager) {
2897
+ const targetEl = this.feedManager.itemEls.get(targetItemId);
2898
+ if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
2899
+ }
2900
+ const savedScrollTop = feedEl.scrollTop;
2811
2901
  feedEl.style.position = "fixed";
2812
2902
  feedEl.style.left = `${feedRect.left}px`;
2813
2903
  feedEl.style.top = `${feedRect.top}px`;
@@ -2819,7 +2909,20 @@
2819
2909
  feedEl.style.aspectRatio = "unset";
2820
2910
  feedEl.style.maxHeight = "none";
2821
2911
  document.body.appendChild(feedEl);
2912
+ feedEl.scrollTop = savedScrollTop;
2822
2913
  this.overlayEl.classList.remove("skw-active", "skw-feed-ready");
2914
+ let thumbOverlay = null;
2915
+ if (this._openThumbnailUrl) {
2916
+ const targetItemId2 = canTransfer ? this._openItemId : activeItemId;
2917
+ const targetEl = targetItemId2 && this.feedManager?.itemEls.get(targetItemId2);
2918
+ const videoContainer = targetEl?.querySelector('[data-ref="videoContainer"]');
2919
+ if (videoContainer) {
2920
+ thumbOverlay = document.createElement("img");
2921
+ thumbOverlay.src = this._openThumbnailUrl;
2922
+ thumbOverlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s cubic-bezier(.32,.72,0,1);pointer-events:none;z-index:2;";
2923
+ videoContainer.appendChild(thumbOverlay);
2924
+ }
2925
+ }
2823
2926
  const slotRadius = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--skw-radius").trim()) || 12;
2824
2927
  const scaleX = targetSlotRect.width / feedRect.width;
2825
2928
  const scaleY = targetSlotRect.height / feedRect.height;
@@ -2829,13 +2932,16 @@
2829
2932
  feedEl.style.transformOrigin = "0 0";
2830
2933
  feedEl.style.overflow = "hidden";
2831
2934
  feedEl.style.scrollSnapType = "none";
2935
+ feedEl.style.pointerEvents = "none";
2832
2936
  feedEl.getBoundingClientRect();
2833
2937
  feedEl.classList.add("skw-flip-animating");
2834
2938
  requestAnimationFrame(() => {
2835
2939
  feedEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
2836
2940
  feedEl.style.borderRadius = `${endRadius}px`;
2941
+ if (thumbOverlay) thumbOverlay.style.opacity = "1";
2837
2942
  });
2838
2943
  await this._waitForTransitionEnd(feedEl, 450);
2944
+ if (thumbOverlay) thumbOverlay.remove();
2839
2945
  let transferVideo = null;
2840
2946
  let transferHls = null;
2841
2947
  if (canTransfer) {
@@ -2860,6 +2966,7 @@
2860
2966
  feedEl.style.transformOrigin = "";
2861
2967
  feedEl.style.overflow = "";
2862
2968
  feedEl.style.scrollSnapType = "";
2969
+ feedEl.style.pointerEvents = "";
2863
2970
  feedEl.style.transform = "";
2864
2971
  feedEl.style.borderRadius = "";
2865
2972
  feedWrapper.insertBefore(feedEl, feedWrapper.firstChild);
@@ -3598,7 +3705,7 @@
3598
3705
 
3599
3706
  /* Video container */
3600
3707
  .sk-video-container{position:absolute;inset:0;width:100%;height:100%;overflow:hidden;background-size:cover;background-position:center}
3601
- .sk-video-container video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .15s ease}
3708
+ .sk-video-container video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .35s ease}
3602
3709
 
3603
3710
  /* Tap zone */
3604
3711
  .sk-tap-zone{position:absolute;inset:0;z-index:3;cursor:pointer}
@@ -3614,7 +3721,7 @@
3614
3721
 
3615
3722
  /* Widget slot */
3616
3723
  .skw-slot{position:relative;overflow:hidden;cursor:pointer}
3617
- .skw-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .2s ease}
3724
+ .skw-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s ease}
3618
3725
  .skw-slot-thumb{position:absolute;inset:0;background-size:cover;background-position:center}
3619
3726
  .skw-slot-overlay{position:absolute;inset:0;z-index:4;pointer-events:none}
3620
3727
  .skw-slot-overlay>*{pointer-events:auto}