@playpilot/tpi 8.25.1 → 8.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "8.25.1",
3
+ "version": "8.26.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -5,7 +5,6 @@
5
5
  import { track } from '$lib/tracking'
6
6
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
7
7
  import TitlesRail from '../../Rails/TitlesRail.svelte'
8
- import { mobileBreakpoint } from '$lib/constants'
9
8
  import { getLanguage } from '$lib/language'
10
9
  import type { LanguageCode } from '$lib/types/language'
11
10
  import { api } from '$lib/api/api'
@@ -40,7 +39,7 @@
40
39
  {#await fetchRails()}
41
40
  {#each { length: 3 }}
42
41
  <div class="rail">
43
- <TitlesRail titles={new Promise(() => {})} size={windowWidth >= mobileBreakpoint ? 'flexible' : 'huge'} />
42
+ <TitlesRail heading=" " titles={new Promise(() => {})} size="huge" />
44
43
  </div>
45
44
  {/each}
46
45
  {:then rails}
@@ -53,10 +52,11 @@
53
52
  {...properties}
54
53
  bind:expandedTitle
55
54
  bind:expandedRailKey
55
+ lazy
56
56
  onclick={() => track(TrackingEvent.ExploreTitleRailModalView, null, { rail: headings })}
57
57
  onexpand={(title) => track(TrackingEvent.ExploreTitleRailExpandClick, title, { rail: headings })}
58
58
  fetchFunction={(page) => getListTitles({ ...params, page })}
59
- size={windowWidth >= mobileBreakpoint ? (properties.size || 'flexible') : 'huge'}
59
+ size={properties.size || 'flexible'}
60
60
  minimumLength={7} />
61
61
  </div>
62
62
  {/each}
@@ -7,11 +7,13 @@
7
7
  interface Props extends Omit<ComponentProps<typeof TitlesRail>, 'titles'> {
8
8
  fetchFunction: (page: number) => Promise<APIPaginatedResult<TitleData>>
9
9
  maxPage?: number
10
+ lazy?: boolean
10
11
  }
11
12
 
12
13
  const {
13
14
  fetchFunction,
14
15
  maxPage = 4,
16
+ lazy = false,
15
17
  expandedTitle = $bindable(null),
16
18
  expandedRailKey = $bindable(null),
17
19
  ...rest
@@ -24,6 +26,12 @@
24
26
  let page = $state(1)
25
27
  let hasMorePages = $state(false)
26
28
  let loading = $state(true)
29
+ let element: HTMLElement | null = $state(null)
30
+ let hasBeenVisible = $derived(!lazy)
31
+
32
+ $effect(() => {
33
+ if (hasBeenVisible) fetchTitles()
34
+ })
27
35
 
28
36
  $effect(() => {
29
37
  if (loading) return
@@ -35,7 +43,22 @@
35
43
  fetchTitles()
36
44
  })
37
45
 
38
- onMount(fetchTitles)
46
+ onMount(() => {
47
+ if (!lazy) return
48
+ if (!element) return
49
+
50
+ const observer = new IntersectionObserver(([entry]) => {
51
+ if (!entry.isIntersecting) return
52
+
53
+ hasBeenVisible = true
54
+
55
+ observer.disconnect()
56
+ }, { rootMargin: window.innerHeight / 2 + 'px' })
57
+
58
+ observer.observe(element)
59
+
60
+ return () => observer?.disconnect()
61
+ })
39
62
 
40
63
  async function fetchTitles(): Promise<void> {
41
64
  loading = true
@@ -52,8 +75,10 @@
52
75
  }
53
76
  </script>
54
77
 
55
- {#if !titles.length && loading}
56
- <TitlesRail titles={emptyPromise} {...rest} />
57
- {:else}
58
- <TitlesRail {titles} {...rest} bind:shown />
59
- {/if}
78
+ <div bind:this={element}>
79
+ {#if (lazy && !hasBeenVisible) || (!titles.length && loading)}
80
+ <TitlesRail titles={emptyPromise} {...rest} />
81
+ {:else}
82
+ <TitlesRail {titles} {...rest} bind:shown />
83
+ {/if}
84
+ </div>
@@ -49,6 +49,7 @@
49
49
 
50
50
  <style lang="scss">
51
51
  .heading {
52
+ min-height: 1lh;
52
53
  margin: margin(1) 0 margin(0.5);
53
54
  font-size: theme(rail-title-font-size, 18px);
54
55
  line-height: normal;
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
  import ExploreHome from '../../../../../routes/components/Explore/Routes/ExploreHome.svelte'
5
5
  import { fetchTitles } from '$lib/api/titles'
6
6
  import { api } from '$lib/api/api'
7
+ import { mockIntersectionObserver } from '../../../../setup'
7
8
 
8
9
  vi.mock('$lib/api/titles', () => ({
9
10
  fetchTitles: vi.fn(),
@@ -16,6 +17,7 @@ vi.mock('$lib/api/api', () => ({
16
17
  describe('ExploreResults.svelte', () => {
17
18
  beforeEach(() => {
18
19
  vi.resetAllMocks()
20
+ mockIntersectionObserver()
19
21
  })
20
22
 
21
23
  it('Should fetch rails endpoint and titles for each rail', async () => {
@@ -1,108 +1,108 @@
1
- import { cleanup } from '@testing-library/svelte'
2
- import { afterEach, beforeAll, beforeEach, vi } from 'vitest'
3
- import { fakeFetch } from './helpers'
4
-
5
- vi.mock('svelte/transition')
6
-
7
- // https://github.com/jsdom/jsdom/issues/3429#issuecomment-1936128876
8
- const mockAnimations = () => {
9
- Element.prototype.animate ??= vi.fn().mockReturnValue({
10
- onfinish: null,
11
- finished: Promise.resolve(),
12
- cancel: vi.fn(),
13
- startTime: null,
14
- currentTime: null,
15
- })
16
-
17
- Element.prototype.getAnimations ??= vi.fn().mockReturnValue([])
18
- }
19
-
20
- const mockLocalStorage = () => {
21
- /** @type {Record<string, string>} */
22
- let store = {}
23
-
24
- // @ts-ignore
25
- ;(window).localStorage = {
26
- getItem: function (key) {
27
- return store[key] || null
28
- },
29
- setItem: function (key, value) {
30
- store[key] = value.toString()
31
- },
32
- removeItem: function (key) {
33
- delete store[key]
34
- },
35
- clear: function () {
36
- store = {}
37
- },
38
- }
39
- }
40
-
41
- const mockSessionStorage = () => {
42
- /** @type {Record<string, string>} */
43
- let store = {}
44
-
45
- // @ts-ignore
46
- ;(window).sessionStorage = {
47
- getItem: function (key) {
48
- return store[key] || null
49
- },
50
- setItem: function (key, value) {
51
- store[key] = value.toString()
52
- },
53
- removeItem: function (key) {
54
- delete store[key]
55
- },
56
- clear: function () {
57
- store = {}
58
- },
59
- }
60
- }
61
-
62
- const mockIntersectionObserver = () => {
63
- // @ts-ignore
64
- global.IntersectionObserver = vi.fn(() => ({
65
- disconnect: vi.fn(),
66
- observe: vi.fn(),
67
- unobserve: vi.fn(),
68
- }))
69
- }
70
-
71
- beforeAll(() => {
72
- fakeFetch()
73
- mockLocalStorage()
74
- mockSessionStorage()
75
- mockIntersectionObserver()
76
- })
77
-
78
- beforeEach(() => {
79
- // @ts-ignore
80
- window.PlayPilotLinkInjections = { token: 'some-token', require_consent: false }
81
-
82
- // Enable all consent
83
- // @ts-ignore
84
- window.__tcfapi = (command, _version, callback) => {
85
- if (command !== 'addEventListener') return
86
-
87
- setTimeout(() => callback({
88
- purpose: { consents: { 1: true, 2: true, 7: true, 8: true, 9: true, 10: true } },
89
- eventStatus: 'tcloaded',
90
- }, true), 0)
91
-
92
- return 1
93
- }
94
-
95
- // Reset cookies
96
- document.cookie.split(';').forEach((cookie) => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, '=;'))
97
-
98
- window.HTMLElement.prototype.scrollIntoView = vi.fn()
99
-
100
- localStorage.clear()
101
- sessionStorage.clear()
102
-
103
- mockAnimations()
104
- })
105
-
106
- afterEach(() => {
107
- cleanup()
108
- })
1
+ import { cleanup } from '@testing-library/svelte'
2
+ import { afterEach, beforeAll, beforeEach, vi } from 'vitest'
3
+ import { fakeFetch } from './helpers'
4
+
5
+ vi.mock('svelte/transition')
6
+
7
+ // https://github.com/jsdom/jsdom/issues/3429#issuecomment-1936128876
8
+ const mockAnimations = () => {
9
+ Element.prototype.animate ??= vi.fn().mockReturnValue({
10
+ onfinish: null,
11
+ finished: Promise.resolve(),
12
+ cancel: vi.fn(),
13
+ startTime: null,
14
+ currentTime: null,
15
+ })
16
+
17
+ Element.prototype.getAnimations ??= vi.fn().mockReturnValue([])
18
+ }
19
+
20
+ const mockLocalStorage = () => {
21
+ /** @type {Record<string, string>} */
22
+ let store = {}
23
+
24
+ // @ts-ignore
25
+ ;(window).localStorage = {
26
+ getItem: function (key) {
27
+ return store[key] || null
28
+ },
29
+ setItem: function (key, value) {
30
+ store[key] = value.toString()
31
+ },
32
+ removeItem: function (key) {
33
+ delete store[key]
34
+ },
35
+ clear: function () {
36
+ store = {}
37
+ },
38
+ }
39
+ }
40
+
41
+ const mockSessionStorage = () => {
42
+ /** @type {Record<string, string>} */
43
+ let store = {}
44
+
45
+ // @ts-ignore
46
+ ;(window).sessionStorage = {
47
+ getItem: function (key) {
48
+ return store[key] || null
49
+ },
50
+ setItem: function (key, value) {
51
+ store[key] = value.toString()
52
+ },
53
+ removeItem: function (key) {
54
+ delete store[key]
55
+ },
56
+ clear: function () {
57
+ store = {}
58
+ },
59
+ }
60
+ }
61
+
62
+ export const mockIntersectionObserver = () => {
63
+ // @ts-ignore
64
+ global.IntersectionObserver = vi.fn((callback) => ({
65
+ disconnect: vi.fn(),
66
+ observe: vi.fn(() => callback([{ isIntersecting: true }])),
67
+ unobserve: vi.fn(),
68
+ }))
69
+ }
70
+
71
+ beforeAll(() => {
72
+ fakeFetch()
73
+ mockLocalStorage()
74
+ mockSessionStorage()
75
+ mockIntersectionObserver()
76
+ })
77
+
78
+ beforeEach(() => {
79
+ // @ts-ignore
80
+ window.PlayPilotLinkInjections = { token: 'some-token', require_consent: false }
81
+
82
+ // Enable all consent
83
+ // @ts-ignore
84
+ window.__tcfapi = (command, _version, callback) => {
85
+ if (command !== 'addEventListener') return
86
+
87
+ setTimeout(() => callback({
88
+ purpose: { consents: { 1: true, 2: true, 7: true, 8: true, 9: true, 10: true } },
89
+ eventStatus: 'tcloaded',
90
+ }, true), 0)
91
+
92
+ return 1
93
+ }
94
+
95
+ // Reset cookies
96
+ document.cookie.split(';').forEach((cookie) => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, '=;'))
97
+
98
+ window.HTMLElement.prototype.scrollIntoView = vi.fn()
99
+
100
+ localStorage.clear()
101
+ sessionStorage.clear()
102
+
103
+ mockAnimations()
104
+ })
105
+
106
+ afterEach(() => {
107
+ cleanup()
108
+ })