@flamingo-stack/openframe-frontend-core 0.0.177 → 0.0.178-snapshot.20260514174355

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 (69) hide show
  1. package/dist/{chunk-C6ZMI4UB.cjs → chunk-4TM2SBMX.cjs} +884 -1513
  2. package/dist/chunk-4TM2SBMX.cjs.map +1 -0
  3. package/dist/{chunk-WX7PT5C7.cjs → chunk-ALW3D72O.cjs} +61 -2
  4. package/dist/chunk-ALW3D72O.cjs.map +1 -0
  5. package/dist/{chunk-KB2N44BY.js → chunk-FMWHOUFE.js} +61 -2
  6. package/dist/chunk-FMWHOUFE.js.map +1 -0
  7. package/dist/{chunk-6LDN3CIY.js → chunk-ZMQP3UZJ.js} +3701 -4330
  8. package/dist/chunk-ZMQP3UZJ.js.map +1 -0
  9. package/dist/components/features/entity-video-section.d.ts +54 -0
  10. package/dist/components/features/entity-video-section.d.ts.map +1 -0
  11. package/dist/components/features/index.cjs +17 -3
  12. package/dist/components/features/index.cjs.map +1 -1
  13. package/dist/components/features/index.d.ts +4 -2
  14. package/dist/components/features/index.d.ts.map +1 -1
  15. package/dist/components/features/index.js +22 -8
  16. package/dist/components/features/video-bites-display.d.ts +38 -0
  17. package/dist/components/features/video-bites-display.d.ts.map +1 -0
  18. package/dist/components/features/video-ratio-tabs.d.ts +62 -0
  19. package/dist/components/features/video-ratio-tabs.d.ts.map +1 -0
  20. package/dist/components/features/video.d.ts +94 -0
  21. package/dist/components/features/video.d.ts.map +1 -0
  22. package/dist/components/index.cjs +17 -3
  23. package/dist/components/index.cjs.map +1 -1
  24. package/dist/components/index.js +22 -8
  25. package/dist/components/media-carousel.d.ts.map +1 -1
  26. package/dist/components/navigation/index.cjs +3 -3
  27. package/dist/components/navigation/index.js +2 -2
  28. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  29. package/dist/components/ui/index.cjs +3 -3
  30. package/dist/components/ui/index.js +2 -2
  31. package/dist/hooks/index.cjs +4 -2
  32. package/dist/hooks/index.cjs.map +1 -1
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/index.d.ts.map +1 -1
  35. package/dist/hooks/index.js +3 -1
  36. package/dist/hooks/use-near-viewport.d.ts +42 -0
  37. package/dist/hooks/use-near-viewport.d.ts.map +1 -0
  38. package/dist/index.cjs +19 -3
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.js +24 -8
  41. package/package.json +2 -2
  42. package/src/components/features/entity-video-section.tsx +175 -0
  43. package/src/components/features/index.ts +9 -2
  44. package/src/components/features/video-bites-display.tsx +216 -0
  45. package/src/components/features/video-ratio-tabs.tsx +174 -0
  46. package/src/components/features/video.tsx +474 -0
  47. package/src/components/media-carousel.tsx +43 -236
  48. package/src/components/shared/product-release/release-detail-page.tsx +26 -19
  49. package/src/hooks/index.ts +3 -0
  50. package/src/hooks/use-near-viewport.ts +118 -0
  51. package/dist/chunk-6LDN3CIY.js.map +0 -1
  52. package/dist/chunk-C6ZMI4UB.cjs.map +0 -1
  53. package/dist/chunk-KB2N44BY.js.map +0 -1
  54. package/dist/chunk-WX7PT5C7.cjs.map +0 -1
  55. package/dist/components/features/video-player.d.ts +0 -47
  56. package/dist/components/features/video-player.d.ts.map +0 -1
  57. package/dist/components/features/youtube-embed.d.ts +0 -17
  58. package/dist/components/features/youtube-embed.d.ts.map +0 -1
  59. package/dist/utils/lite-youtube-embed-stub.d.ts +0 -8
  60. package/dist/utils/lite-youtube-embed-stub.d.ts.map +0 -1
  61. package/dist/utils/lite-youtube-embed.d.ts +0 -9
  62. package/dist/utils/lite-youtube-embed.d.ts.map +0 -1
  63. package/src/components/features/.video-player.md +0 -44
  64. package/src/components/features/.youtube-embed.md +0 -40
  65. package/src/components/features/__tests__/video-player.test.tsx +0 -142
  66. package/src/components/features/video-player.tsx +0 -1030
  67. package/src/components/features/youtube-embed.tsx +0 -275
  68. package/src/utils/lite-youtube-embed-stub.tsx +0 -21
  69. package/src/utils/lite-youtube-embed.tsx +0 -46
@@ -1,8 +0,0 @@
1
- export interface LiteYouTubeEmbedProps {
2
- id: string;
3
- title?: string;
4
- poster?: string;
5
- className?: string;
6
- }
7
- export declare function LiteYouTubeEmbed({ id, title, poster, className }: LiteYouTubeEmbedProps): import("react/jsx-runtime").JSX.Element;
8
- //# sourceMappingURL=lite-youtube-embed-stub.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lite-youtube-embed-stub.d.ts","sourceRoot":"","sources":["../../src/utils/lite-youtube-embed-stub.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAWvF"}
@@ -1,9 +0,0 @@
1
- interface LiteYouTubeEmbedProps {
2
- id: string;
3
- title?: string;
4
- thumbnail?: string;
5
- className?: string;
6
- }
7
- export declare function LiteYouTubeEmbed({ id, title, thumbnail, className }: LiteYouTubeEmbedProps): import("react/jsx-runtime").JSX.Element;
8
- export {};
9
- //# sourceMappingURL=lite-youtube-embed.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lite-youtube-embed.d.ts","sourceRoot":"","sources":["../../src/utils/lite-youtube-embed.tsx"],"names":[],"mappings":"AAEA,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAuB,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAoC5G"}
@@ -1,44 +0,0 @@
1
- <!-- source-hash: ac2c9623eeb0459e6abe9a41a32951c3 -->
2
- A comprehensive React video player component built on ReactPlayer with custom poster overlay, loading states, and error handling for the OpenFrame platform.
3
-
4
- ## Key Components
5
-
6
- - **VideoPlayer**: Main component with configurable playback options and responsive design
7
- - **Play**: SVG icon component for the play button overlay
8
- - **Loader**: Animated SVG spinner for loading states
9
- - **VideoPlayerProps**: TypeScript interface defining all configuration options including aspect ratio control, autoplay, and poster image settings
10
-
11
- ## Usage Example
12
-
13
- ```typescript
14
- import { VideoPlayer } from './video-player';
15
-
16
- // Basic video with poster and title
17
- <VideoPlayer
18
- url="https://example.com/video.mp4"
19
- title="Demo Video"
20
- poster="https://example.com/thumbnail.jpg"
21
- showTitle={true}
22
- controls={true}
23
- />
24
-
25
- // Auto-playing video with native aspect ratio
26
- <VideoPlayer
27
- url="https://youtube.com/watch?v=VIDEO_ID"
28
- autoPlay={true}
29
- muted={true}
30
- loop={true}
31
- useNativeAspectRatio={true}
32
- className="custom-player"
33
- />
34
-
35
- // Video with custom poster click behavior
36
- <VideoPlayer
37
- url="https://vimeo.com/123456789"
38
- poster="custom-thumbnail.png"
39
- showTitle={false}
40
- controls={false}
41
- />
42
- ```
43
-
44
- The component handles client-side hydration, provides comprehensive error states, and integrates with OpenFrame's design system using ODS CSS classes. It supports both 16:9 aspect ratio (default) and native video dimensions through the `useNativeAspectRatio` prop.
@@ -1,40 +0,0 @@
1
- <!-- source-hash: fc74dea08c8b565aa3e51c72946544d6 -->
2
- A React component for embedding YouTube videos with responsive design, loading states, and fallback support for the Flamingo documentation system.
3
-
4
- ## Key Components
5
-
6
- - **`YouTubeEmbed`** - Main YouTube video embed component with configurable options
7
- - **`Play`** - SVG play button icon component
8
- - **`Loader`** - Animated loading spinner icon component
9
- - **`extractYouTubeId`** - Utility function to extract video IDs from various YouTube URL formats
10
- - **`YouTubeLinkParser`** - Component that automatically converts YouTube links to embedded videos
11
-
12
- ## Usage Example
13
-
14
- ```typescript
15
- import { YouTubeEmbed, extractYouTubeId } from './youtube-embed';
16
-
17
- // Basic embed
18
- <YouTubeEmbed videoId="dQw4w9WgXcQ" />
19
-
20
- // With custom options
21
- <YouTubeEmbed
22
- videoId="dQw4w9WgXcQ"
23
- title="Custom Video Title"
24
- showTitle={true}
25
- showMeta={false}
26
- minimalControls={true}
27
- className="my-custom-class"
28
- />
29
-
30
- // Extract video ID from URL
31
- const videoId = extractYouTubeId("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
32
- // Returns: "dQw4w9WgXcQ"
33
-
34
- // Auto-convert YouTube links in markdown
35
- <YouTubeLinkParser href="https://youtu.be/dQw4w9WgXcQ">
36
- Video Title
37
- </YouTubeLinkParser>
38
- ```
39
-
40
- The component uses iframe embedding by default for better CSP compatibility, includes responsive 16:9 aspect ratio containers, and provides error handling with fallback to YouTube's website. It supports various display options including minimal controls mode and customizable meta information display.
@@ -1,142 +0,0 @@
1
- import { fireEvent, render } from '@testing-library/react'
2
- import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
3
-
4
- /**
5
- * Regression test for the case-study video performance fix:
6
- * plan: /Users/michaelassraf/.claude/plans/we-have-terrible-serious-radiant-turing.md
7
- *
8
- * Bug: VideoPlayer mounted two competing <video> elements on every render when no
9
- * `poster` prop was passed — the hidden first-frame extractor (`useVideoFirstFramePoster`)
10
- * raced ReactPlayer's own metadata fetch on the same URL, doubling bandwidth and
11
- * stalling click-to-play on big MP4s.
12
- *
13
- * Fix: opt-in `lazyMount` prop that:
14
- * 1. Skips `useVideoFirstFramePoster` (no hidden video)
15
- * 2. Sets `preload="none"` on ReactPlayer's <video> until user clicks play
16
- * 3. Synchronously calls .play() in the click handler for iOS user-activation safety
17
- *
18
- * Test strategy:
19
- * - To detect "did the hidden poster <video> get created?", we tag the hook's
20
- * unique signature — it sets `crossOrigin="anonymous"` on the element. Spy on
21
- * `document.createElement` and count VIDEO elements that subsequently have
22
- * `crossOrigin` set. React's own DOM render does NOT set crossOrigin on the
23
- * mocked <video>, so this isolates the bug surface cleanly.
24
- * - Mock `react-player` so we don't depend on its internal FilePlayer dynamic
25
- * import (broken under JSDOM / Vitest's module loader).
26
- */
27
-
28
- // Mock react-player as a forwardRef component that exposes the same
29
- // `getInternalPlayer()` surface used by VideoPlayer (line ~697 lazyMount sync play).
30
- // Without this, `playerRef.current?.getInternalPlayer()` returns undefined under
31
- // the test, the `instanceof HTMLVideoElement` guard fails, and the iOS user-
32
- // activation path is silently never exercised by the test.
33
- vi.mock('react-player', async () => {
34
- const React = await import('react')
35
- const RP = React.forwardRef<
36
- { getInternalPlayer: () => HTMLVideoElement | null },
37
- { url: string; config?: { file?: { attributes?: Record<string, unknown> } } }
38
- >(function MockReactPlayer({ url, config }, ref) {
39
- const videoRef = React.useRef<HTMLVideoElement | null>(null)
40
- React.useImperativeHandle(ref, () => ({
41
- getInternalPlayer: () => videoRef.current,
42
- }))
43
- const preload = (config?.file?.attributes?.preload as string) ?? 'metadata'
44
- return <video ref={videoRef} src={url} preload={preload as 'none' | 'metadata' | 'auto'} />
45
- })
46
- return { default: RP }
47
- })
48
-
49
- beforeAll(() => {
50
- // JSDOM doesn't implement these — react-player's internals (and the poster hook) call them.
51
- Object.defineProperty(HTMLMediaElement.prototype, 'canPlayType', {
52
- value: () => 'maybe',
53
- writable: true,
54
- configurable: true,
55
- })
56
- Object.defineProperty(HTMLMediaElement.prototype, 'load', {
57
- value: () => undefined,
58
- writable: true,
59
- configurable: true,
60
- })
61
- })
62
-
63
- import { VideoPlayer } from '../video-player'
64
-
65
- const SAMPLE_MP4 = 'https://example.com/test.mp4'
66
-
67
- /**
68
- * Detects the poster hook's signature via the `data-poster-extractor` attribute
69
- * the hook sets on its hidden <video>. This is more durable than checking
70
- * `crossOrigin === 'anonymous'` (any future code that happens to set crossOrigin
71
- * on a freshly-created video would inflate the count and silently flake-pass).
72
- */
73
- function spyForHiddenPosterVideos(): { count: () => number; restore: () => void } {
74
- const original = document.createElement.bind(document)
75
- const tracked: HTMLVideoElement[] = []
76
- document.createElement = ((tag: string, opts?: ElementCreationOptions) => {
77
- const el = original(tag, opts)
78
- if (tag.toLowerCase() === 'video') tracked.push(el as HTMLVideoElement)
79
- return el
80
- }) as typeof document.createElement
81
- return {
82
- count: () => tracked.filter(v => v.getAttribute('data-poster-extractor') === 'true').length,
83
- restore: () => { document.createElement = original },
84
- }
85
- }
86
-
87
- describe('VideoPlayer lazyMount', () => {
88
- let videoSpy: ReturnType<typeof spyForHiddenPosterVideos> | null = null
89
-
90
- afterEach(() => {
91
- videoSpy?.restore()
92
- videoSpy = null
93
- })
94
-
95
- it('does NOT instantiate a hidden poster <video> when lazyMount=true (the actual fix)', () => {
96
- videoSpy = spyForHiddenPosterVideos()
97
- render(<VideoPlayer url={SAMPLE_MP4} lazyMount />)
98
- expect(videoSpy.count()).toBe(0)
99
- })
100
-
101
- it('renders ReactPlayer with preload="none" pre-click when lazyMount=true', () => {
102
- const { container } = render(<VideoPlayer url={SAMPLE_MP4} lazyMount />)
103
- const videos = container.querySelectorAll('video')
104
- expect(videos.length).toBe(1)
105
- expect(videos[0].getAttribute('preload')).toBe('none')
106
- })
107
-
108
- it('preserves existing preload="metadata" + poster-hook behavior when lazyMount=false (default)', () => {
109
- videoSpy = spyForHiddenPosterVideos()
110
- const { container } = render(<VideoPlayer url={SAMPLE_MP4} />)
111
- expect(container.querySelectorAll('video')[0].getAttribute('preload')).toBe('metadata')
112
- // The poster hook DOES create one hidden <video> when no poster is provided.
113
- // This is the legacy behavior we explicitly opted out of via lazyMount.
114
- expect(videoSpy.count()).toBeGreaterThanOrEqual(1)
115
- })
116
-
117
- it('skips the poster hook when an explicit poster is provided (existing gate, regression check)', () => {
118
- videoSpy = spyForHiddenPosterVideos()
119
- render(<VideoPlayer url={SAMPLE_MP4} poster="https://example.com/poster.jpg" />)
120
- expect(videoSpy.count()).toBe(0)
121
- })
122
-
123
- it('calls play() synchronously inside the click handler when lazyMount=true (iOS user-activation)', () => {
124
- const playSpy = vi.fn().mockResolvedValue(undefined)
125
- Object.defineProperty(HTMLMediaElement.prototype, 'play', {
126
- value: playSpy,
127
- writable: true,
128
- configurable: true,
129
- })
130
-
131
- const { container } = render(<VideoPlayer url={SAMPLE_MP4} lazyMount />)
132
- // The play overlay div has `onClick={handlePlayClick}` and uses the
133
- // 'group cursor-pointer' classes. Find by role/aria isn't reliable since
134
- // the overlay is a styled div, so query by the unique class combo.
135
- const overlay = container.querySelector('div.cursor-pointer.group') as HTMLElement | null
136
- expect(overlay).toBeTruthy()
137
- fireEvent.click(overlay!)
138
- // play() must be called synchronously inside the click event task — if it
139
- // were deferred (e.g., to a useEffect), iOS Safari would block playback.
140
- expect(playSpy).toHaveBeenCalled()
141
- })
142
- })