@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.
@@ -906,7 +906,7 @@ class FeedManager {
906
906
  }
907
907
  setMuted(muted) {
908
908
  this.isMuted = muted;
909
- for (const [, player] of this.pool._players) {
909
+ for (const [, player] of this.pool.assignments) {
910
910
  player.muted = muted;
911
911
  }
912
912
  this._pushPlayerState({ isMuted: muted });
@@ -919,7 +919,7 @@ class FeedManager {
919
919
  }
920
920
  setPlaybackRate(rate) {
921
921
  this.playbackRate = rate;
922
- for (const [, player] of this.pool._players) {
922
+ for (const [, player] of this.pool.assignments) {
923
923
  player.playbackRate = rate;
924
924
  }
925
925
  this._pushPlayerState({ playbackRate: rate });
@@ -1396,11 +1396,10 @@ class FeedManager {
1396
1396
  const player = this.pool.getPlayer(id);
1397
1397
  if (player) {
1398
1398
  player.pause();
1399
- player.style.opacity = "0";
1400
1399
  player._skRevealedFor = null;
1401
1400
  }
1402
1401
  this._stopTimeLoop(id);
1403
- this._clearOverlay(id);
1402
+ this._detachOverlay(id);
1404
1403
  this._sk._tracker.deactivateContent();
1405
1404
  }
1406
1405
  // --- Time loop ---
@@ -1582,14 +1581,20 @@ class FeedManager {
1582
1581
  }
1583
1582
  }
1584
1583
  /** Clear the overlay container's DOM and unsubscribe tracked listeners. */
1585
- _clearOverlay(itemId) {
1584
+ /** Unsubscribe overlay event listeners without clearing DOM. */
1585
+ _detachOverlay(itemId) {
1586
1586
  const entry = this._overlayContainers.get(itemId);
1587
1587
  if (!entry) return;
1588
1588
  if (entry.unsub) {
1589
1589
  entry.unsub();
1590
1590
  entry.unsub = null;
1591
1591
  }
1592
- entry.el.innerHTML = "";
1592
+ }
1593
+ /** Unsubscribe and clear overlay DOM (used on destroy). */
1594
+ _clearOverlay(itemId) {
1595
+ this._detachOverlay(itemId);
1596
+ const entry = this._overlayContainers.get(itemId);
1597
+ if (entry) entry.el.innerHTML = "";
1593
1598
  }
1594
1599
  }
1595
1600
  class EmbeddedFeedManager {
@@ -1703,7 +1708,7 @@ class EmbeddedFeedManager {
1703
1708
  }
1704
1709
  setMuted(muted) {
1705
1710
  this.isMuted = muted;
1706
- for (const [, player] of this.pool._players) {
1711
+ for (const [, player] of this.pool.assignments) {
1707
1712
  player.muted = muted;
1708
1713
  }
1709
1714
  this._pushPlayerState({ isMuted: muted });
@@ -1716,7 +1721,7 @@ class EmbeddedFeedManager {
1716
1721
  }
1717
1722
  setPlaybackRate(rate) {
1718
1723
  this.playbackRate = rate;
1719
- for (const [, player] of this.pool._players) {
1724
+ for (const [, player] of this.pool.assignments) {
1720
1725
  player.playbackRate = rate;
1721
1726
  }
1722
1727
  this._pushPlayerState({ playbackRate: rate });
@@ -1988,11 +1993,10 @@ class EmbeddedFeedManager {
1988
1993
  const p = this.pool.getPlayer(id);
1989
1994
  if (p) {
1990
1995
  p.pause();
1991
- p.style.opacity = "0";
1992
1996
  p._skRevealedFor = null;
1993
1997
  }
1994
1998
  this._stopTimeLoop(id);
1995
- this._clearOverlay(id);
1999
+ this._detachOverlay(id);
1996
2000
  }
1997
2001
  // --- Time loop (state emission only, no DOM updates) ---
1998
2002
  _startTimeLoop(id, el, player) {
@@ -2191,14 +2195,20 @@ class EmbeddedFeedManager {
2191
2195
  } catch (e) {
2192
2196
  }
2193
2197
  }
2194
- _clearOverlay(itemId) {
2198
+ /** Unsubscribe overlay event listeners without clearing DOM. */
2199
+ _detachOverlay(itemId) {
2195
2200
  const entry = this._overlayContainers.get(itemId);
2196
2201
  if (!entry) return;
2197
2202
  if (entry.unsub) {
2198
2203
  entry.unsub();
2199
2204
  entry.unsub = null;
2200
2205
  }
2201
- entry.el.innerHTML = "";
2206
+ }
2207
+ /** Unsubscribe and clear overlay DOM (used on destroy). */
2208
+ _clearOverlay(itemId) {
2209
+ this._detachOverlay(itemId);
2210
+ const entry = this._overlayContainers.get(itemId);
2211
+ if (entry) entry.el.innerHTML = "";
2202
2212
  }
2203
2213
  }
2204
2214
  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>';
@@ -2548,6 +2558,7 @@ class PlayerFeedView {
2548
2558
  this._isOpen = true;
2549
2559
  this._onClose = onClose;
2550
2560
  this._openItemId = item.id;
2561
+ this._openThumbnailUrl = item.thumbnailUrl || null;
2551
2562
  const sourceRect = sourceEl.getBoundingClientRect();
2552
2563
  const sourceRadius = parseFloat(getComputedStyle(sourceEl).borderRadius) || 12;
2553
2564
  this.overlayEl.classList.add("skp-active");
@@ -2592,6 +2603,21 @@ class PlayerFeedView {
2592
2603
  feedEl.style.transform = "none";
2593
2604
  feedEl.style.borderRadius = `${targetRadius}px`;
2594
2605
  });
2606
+ this.feedManager.startObserver();
2607
+ this.feedManager._activateItem(item.id);
2608
+ this.overlayEl.classList.add("skp-feed-ready");
2609
+ const itemOverlay = feedItemEl?.querySelector('[data-ref="overlay"]');
2610
+ const chromeEls = [...this.overlayEl.querySelectorAll(".skp-feed-close, .sk-sidebar")];
2611
+ if (itemOverlay) chromeEls.push(itemOverlay);
2612
+ chromeEls.forEach((el) => {
2613
+ el.style.opacity = "0";
2614
+ el.style.transition = "none";
2615
+ });
2616
+ this.overlayEl.getBoundingClientRect();
2617
+ chromeEls.forEach((el) => {
2618
+ el.style.transition = "opacity .35s cubic-bezier(.32,.72,0,1)";
2619
+ el.style.opacity = "1";
2620
+ });
2595
2621
  await this._waitForTransitionEnd(feedEl, 450);
2596
2622
  feedEl.classList.remove("skp-flip-animating");
2597
2623
  feedEl.style.transformOrigin = "";
@@ -2599,9 +2625,10 @@ class PlayerFeedView {
2599
2625
  feedEl.style.scrollSnapType = "";
2600
2626
  feedEl.style.transform = "";
2601
2627
  feedEl.style.borderRadius = "";
2602
- this.feedManager.startObserver();
2603
- this.feedManager._activateItem(item.id);
2604
- this.overlayEl.classList.add("skp-feed-ready");
2628
+ chromeEls.forEach((el) => {
2629
+ el.style.opacity = "";
2630
+ el.style.transition = "";
2631
+ });
2605
2632
  this._escHandler = (e) => {
2606
2633
  if (e.key === "Escape") this.close();
2607
2634
  };
@@ -2614,33 +2641,44 @@ class PlayerFeedView {
2614
2641
  const feedWrapper = feedEl.parentNode;
2615
2642
  const feedRect = feedEl.getBoundingClientRect();
2616
2643
  const activeItemId = this.feedManager?.activeItemId;
2617
- let transferBack = false, transferVideo = null, transferHls = null;
2618
- if (activeItemId === this._openItemId) {
2619
- const ejected = this.feedManager?.pool.ejectPlayer(activeItemId);
2620
- if (ejected) {
2621
- transferBack = true;
2622
- transferVideo = ejected.video;
2623
- transferHls = ejected.hls;
2624
- }
2625
- }
2644
+ const canTransfer = activeItemId === this._openItemId;
2626
2645
  let targetRect = null;
2627
2646
  if (this._onClose) targetRect = this._onClose("getSourceRect");
2628
2647
  if (feedRect && targetRect) {
2629
- if (this.feedManager?.activeItemId && !transferBack) {
2648
+ if (this.feedManager?.activeItemId && !canTransfer) {
2630
2649
  this.feedManager._deactivateItem(this.feedManager.activeItemId);
2631
2650
  }
2651
+ const targetItemId = canTransfer ? this._openItemId : activeItemId;
2652
+ if (targetItemId && this.feedManager) {
2653
+ const targetEl = this.feedManager.itemEls.get(targetItemId);
2654
+ if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
2655
+ }
2656
+ const savedScrollTop = feedEl.scrollTop;
2632
2657
  feedEl.style.position = "fixed";
2633
2658
  feedEl.style.left = `${feedRect.left}px`;
2634
2659
  feedEl.style.top = `${feedRect.top}px`;
2635
2660
  feedEl.style.width = `${feedRect.width}px`;
2636
2661
  feedEl.style.height = `${feedRect.height}px`;
2637
- feedEl.style.zIndex = "9999";
2662
+ feedEl.style.zIndex = String(getComputedStyle(this.overlayEl).zIndex || 9999);
2638
2663
  feedEl.style.margin = "0";
2639
2664
  feedEl.style.flex = "none";
2640
2665
  feedEl.style.aspectRatio = "unset";
2641
2666
  feedEl.style.maxHeight = "none";
2642
2667
  document.body.appendChild(feedEl);
2668
+ feedEl.scrollTop = savedScrollTop;
2643
2669
  this.overlayEl.classList.remove("skp-active", "skp-feed-ready");
2670
+ let thumbOverlay = null;
2671
+ if (this._openThumbnailUrl) {
2672
+ const crossfadeItemId = canTransfer ? this._openItemId : activeItemId;
2673
+ const crossfadeEl = crossfadeItemId && this.feedManager?.itemEls.get(crossfadeItemId);
2674
+ const videoContainer = crossfadeEl?.querySelector('[data-ref="videoContainer"]');
2675
+ if (videoContainer) {
2676
+ thumbOverlay = document.createElement("img");
2677
+ thumbOverlay.src = this._openThumbnailUrl;
2678
+ 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;";
2679
+ videoContainer.appendChild(thumbOverlay);
2680
+ }
2681
+ }
2644
2682
  const scaleX = targetRect.width / feedRect.width;
2645
2683
  const scaleY = targetRect.height / feedRect.height;
2646
2684
  const translateX = targetRect.left - feedRect.left;
@@ -2650,29 +2688,58 @@ class PlayerFeedView {
2650
2688
  feedEl.style.transformOrigin = "0 0";
2651
2689
  feedEl.style.overflow = "hidden";
2652
2690
  feedEl.style.scrollSnapType = "none";
2691
+ feedEl.style.pointerEvents = "none";
2653
2692
  feedEl.getBoundingClientRect();
2654
2693
  feedEl.classList.add("skp-flip-animating");
2655
2694
  requestAnimationFrame(() => {
2656
2695
  feedEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
2657
2696
  feedEl.style.borderRadius = `${endRadius}px`;
2697
+ if (thumbOverlay) thumbOverlay.style.opacity = "1";
2658
2698
  });
2659
2699
  await this._waitForTransitionEnd(feedEl, 450);
2700
+ if (thumbOverlay) thumbOverlay.remove();
2701
+ let transferVideo = null;
2702
+ let transferHls = null;
2703
+ if (canTransfer) {
2704
+ const ejected = this.feedManager?.pool.ejectPlayer(this._openItemId);
2705
+ if (ejected) {
2706
+ transferVideo = ejected.video;
2707
+ transferHls = ejected.hls;
2708
+ }
2709
+ }
2660
2710
  feedEl.style.visibility = "hidden";
2661
2711
  feedEl.classList.remove("skp-flip-animating");
2662
- ["position", "left", "top", "width", "height", "zIndex", "margin", "flex", "aspectRatio", "maxHeight", "transformOrigin", "overflow", "scrollSnapType", "transform", "borderRadius"].forEach((p) => feedEl.style[p] = "");
2712
+ ["position", "left", "top", "width", "height", "zIndex", "margin", "flex", "aspectRatio", "maxHeight", "transformOrigin", "overflow", "scrollSnapType", "pointerEvents", "transform", "borderRadius"].forEach((p) => feedEl.style[p] = "");
2663
2713
  const sidebarEl = feedWrapper.querySelector(".sk-sidebar");
2664
2714
  feedWrapper.insertBefore(feedEl, sidebarEl);
2665
2715
  feedEl.style.visibility = "";
2716
+ if (this._onClose) {
2717
+ this._onClose("closed", {
2718
+ transferVideo,
2719
+ transferHls,
2720
+ transferItemId: transferVideo ? this._openItemId : null
2721
+ });
2722
+ this._onClose = null;
2723
+ }
2666
2724
  } else {
2667
2725
  this.overlayEl.classList.remove("skp-active", "skp-feed-ready");
2668
- }
2669
- if (this._onClose) {
2670
- this._onClose("closed", {
2671
- transferVideo: transferBack ? transferVideo : null,
2672
- transferHls: transferBack ? transferHls : null,
2673
- transferItemId: transferBack ? this._openItemId : null
2674
- });
2675
- this._onClose = null;
2726
+ if (this._onClose) {
2727
+ let transferVideo = null;
2728
+ let transferHls = null;
2729
+ if (canTransfer) {
2730
+ const ejected = this.feedManager?.pool.ejectPlayer(this._openItemId);
2731
+ if (ejected) {
2732
+ transferVideo = ejected.video;
2733
+ transferHls = ejected.hls;
2734
+ }
2735
+ }
2736
+ this._onClose("closed", {
2737
+ transferVideo,
2738
+ transferHls,
2739
+ transferItemId: transferVideo ? this._openItemId : null
2740
+ });
2741
+ this._onClose = null;
2742
+ }
2676
2743
  }
2677
2744
  if (this.feedManager) {
2678
2745
  this.feedManager.destroy();
@@ -2724,6 +2791,7 @@ class FeedView {
2724
2791
  this._onClose = onClose;
2725
2792
  this._openSlotIndex = startIndex;
2726
2793
  this._openItemId = item.id;
2794
+ this._openThumbnailUrl = item.thumbnailUrl || null;
2727
2795
  const slotRect = slotEl.getBoundingClientRect();
2728
2796
  const slotRadius = parseFloat(getComputedStyle(slotEl).borderRadius) || 12;
2729
2797
  if (_isPreview) {
@@ -2770,6 +2838,21 @@ class FeedView {
2770
2838
  feedEl.style.transform = "none";
2771
2839
  feedEl.style.borderRadius = `${targetRadius}px`;
2772
2840
  });
2841
+ this.feedManager.startObserver();
2842
+ this.feedManager._activateItem(item.id);
2843
+ this.overlayEl.classList.add("skw-feed-ready");
2844
+ const itemOverlay = feedItemEl?.querySelector('[data-ref="overlay"]');
2845
+ const chromeEls = [...this.overlayEl.querySelectorAll(".skw-feed-close, .sk-sidebar")];
2846
+ if (itemOverlay) chromeEls.push(itemOverlay);
2847
+ chromeEls.forEach((el) => {
2848
+ el.style.opacity = "0";
2849
+ el.style.transition = "none";
2850
+ });
2851
+ this.overlayEl.getBoundingClientRect();
2852
+ chromeEls.forEach((el) => {
2853
+ el.style.transition = "opacity .35s cubic-bezier(.32,.72,0,1)";
2854
+ el.style.opacity = "1";
2855
+ });
2773
2856
  await this._waitForTransitionEnd(feedEl, 450);
2774
2857
  feedEl.classList.remove("skw-flip-animating");
2775
2858
  feedEl.style.transformOrigin = "";
@@ -2777,9 +2860,10 @@ class FeedView {
2777
2860
  feedEl.style.scrollSnapType = "";
2778
2861
  feedEl.style.transform = "";
2779
2862
  feedEl.style.borderRadius = "";
2780
- this.feedManager.startObserver();
2781
- this.feedManager._activateItem(item.id);
2782
- this.overlayEl.classList.add("skw-feed-ready");
2863
+ chromeEls.forEach((el) => {
2864
+ el.style.opacity = "";
2865
+ el.style.transition = "";
2866
+ });
2783
2867
  this._escHandler = (e) => {
2784
2868
  if (e.key === "Escape") this.close();
2785
2869
  };
@@ -2806,6 +2890,12 @@ class FeedView {
2806
2890
  if (this.feedManager?.activeItemId && !canTransfer) {
2807
2891
  this.feedManager._deactivateItem(this.feedManager.activeItemId);
2808
2892
  }
2893
+ const targetItemId = canTransfer ? this._openItemId : activeItemId;
2894
+ if (targetItemId && this.feedManager) {
2895
+ const targetEl = this.feedManager.itemEls.get(targetItemId);
2896
+ if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
2897
+ }
2898
+ const savedScrollTop = feedEl.scrollTop;
2809
2899
  feedEl.style.position = "fixed";
2810
2900
  feedEl.style.left = `${feedRect.left}px`;
2811
2901
  feedEl.style.top = `${feedRect.top}px`;
@@ -2817,7 +2907,20 @@ class FeedView {
2817
2907
  feedEl.style.aspectRatio = "unset";
2818
2908
  feedEl.style.maxHeight = "none";
2819
2909
  document.body.appendChild(feedEl);
2910
+ feedEl.scrollTop = savedScrollTop;
2820
2911
  this.overlayEl.classList.remove("skw-active", "skw-feed-ready");
2912
+ let thumbOverlay = null;
2913
+ if (this._openThumbnailUrl) {
2914
+ const targetItemId2 = canTransfer ? this._openItemId : activeItemId;
2915
+ const targetEl = targetItemId2 && this.feedManager?.itemEls.get(targetItemId2);
2916
+ const videoContainer = targetEl?.querySelector('[data-ref="videoContainer"]');
2917
+ if (videoContainer) {
2918
+ thumbOverlay = document.createElement("img");
2919
+ thumbOverlay.src = this._openThumbnailUrl;
2920
+ 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;";
2921
+ videoContainer.appendChild(thumbOverlay);
2922
+ }
2923
+ }
2821
2924
  const slotRadius = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--skw-radius").trim()) || 12;
2822
2925
  const scaleX = targetSlotRect.width / feedRect.width;
2823
2926
  const scaleY = targetSlotRect.height / feedRect.height;
@@ -2827,13 +2930,16 @@ class FeedView {
2827
2930
  feedEl.style.transformOrigin = "0 0";
2828
2931
  feedEl.style.overflow = "hidden";
2829
2932
  feedEl.style.scrollSnapType = "none";
2933
+ feedEl.style.pointerEvents = "none";
2830
2934
  feedEl.getBoundingClientRect();
2831
2935
  feedEl.classList.add("skw-flip-animating");
2832
2936
  requestAnimationFrame(() => {
2833
2937
  feedEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
2834
2938
  feedEl.style.borderRadius = `${endRadius}px`;
2939
+ if (thumbOverlay) thumbOverlay.style.opacity = "1";
2835
2940
  });
2836
2941
  await this._waitForTransitionEnd(feedEl, 450);
2942
+ if (thumbOverlay) thumbOverlay.remove();
2837
2943
  let transferVideo = null;
2838
2944
  let transferHls = null;
2839
2945
  if (canTransfer) {
@@ -2858,6 +2964,7 @@ class FeedView {
2858
2964
  feedEl.style.transformOrigin = "";
2859
2965
  feedEl.style.overflow = "";
2860
2966
  feedEl.style.scrollSnapType = "";
2967
+ feedEl.style.pointerEvents = "";
2861
2968
  feedEl.style.transform = "";
2862
2969
  feedEl.style.borderRadius = "";
2863
2970
  feedWrapper.insertBefore(feedEl, feedWrapper.firstChild);
@@ -3596,7 +3703,7 @@ const CSS = `
3596
3703
 
3597
3704
  /* Video container */
3598
3705
  .sk-video-container{position:absolute;inset:0;width:100%;height:100%;overflow:hidden;background-size:cover;background-position:center}
3599
- .sk-video-container video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .15s ease}
3706
+ .sk-video-container video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .35s ease}
3600
3707
 
3601
3708
  /* Tap zone */
3602
3709
  .sk-tap-zone{position:absolute;inset:0;z-index:3;cursor:pointer}
@@ -3612,7 +3719,7 @@ const CSS = `
3612
3719
 
3613
3720
  /* Widget slot */
3614
3721
  .skw-slot{position:relative;overflow:hidden;cursor:pointer}
3615
- .skw-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .2s ease}
3722
+ .skw-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s ease}
3616
3723
  .skw-slot-thumb{position:absolute;inset:0;background-size:cover;background-position:center}
3617
3724
  .skw-slot-overlay{position:absolute;inset:0;z-index:4;pointer-events:none}
3618
3725
  .skw-slot-overlay>*{pointer-events:auto}