@teleporthq/teleport-shared 0.43.69 → 0.43.71
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/__tests__/motion-runtime/motion-engine.ts +102 -0
- package/__tests__/motion-runtime/scroll-video-engine.ts +28 -0
- package/__tests__/scroll-scene/story-lanes.ts +126 -0
- package/__tests__/utils/inline-script-error-guard.ts +121 -0
- package/dist/cjs/index.d.ts +6 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/motion-runtime/index.d.ts +3 -0
- package/dist/cjs/motion-runtime/index.d.ts.map +1 -0
- package/dist/cjs/motion-runtime/index.js +8 -0
- package/dist/cjs/motion-runtime/index.js.map +1 -0
- package/dist/cjs/motion-runtime/motion-engine-source.d.ts +16 -0
- package/dist/cjs/motion-runtime/motion-engine-source.d.ts.map +1 -0
- package/dist/cjs/motion-runtime/motion-engine-source.js +20 -0
- package/dist/cjs/motion-runtime/motion-engine-source.js.map +1 -0
- package/dist/cjs/motion-runtime/scroll-video-engine-source.d.ts +9 -0
- package/dist/cjs/motion-runtime/scroll-video-engine-source.d.ts.map +1 -0
- package/dist/cjs/motion-runtime/scroll-video-engine-source.js +13 -0
- package/dist/cjs/motion-runtime/scroll-video-engine-source.js.map +1 -0
- package/dist/cjs/page-transition/index.d.ts +4 -0
- package/dist/cjs/page-transition/index.d.ts.map +1 -0
- package/dist/cjs/page-transition/index.js +20 -0
- package/dist/cjs/page-transition/index.js.map +1 -0
- package/dist/cjs/page-transition/morph-source.d.ts +20 -0
- package/dist/cjs/page-transition/morph-source.d.ts.map +1 -0
- package/dist/cjs/page-transition/morph-source.js +25 -0
- package/dist/cjs/page-transition/morph-source.js.map +1 -0
- package/dist/cjs/page-transition/variants.d.ts +87 -0
- package/dist/cjs/page-transition/variants.d.ts.map +1 -0
- package/dist/cjs/page-transition/variants.js +330 -0
- package/dist/cjs/page-transition/variants.js.map +1 -0
- package/dist/cjs/page-transition/view-transition-css.d.ts +21 -0
- package/dist/cjs/page-transition/view-transition-css.d.ts.map +1 -0
- package/dist/cjs/page-transition/view-transition-css.js +188 -0
- package/dist/cjs/page-transition/view-transition-css.js.map +1 -0
- package/dist/cjs/scroll-scene/chapter-index.d.ts +15 -0
- package/dist/cjs/scroll-scene/chapter-index.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/chapter-index.js +30 -0
- package/dist/cjs/scroll-scene/chapter-index.js.map +1 -0
- package/dist/cjs/scroll-scene/controller-source.d.ts +26 -0
- package/dist/cjs/scroll-scene/controller-source.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/controller-source.js +34 -0
- package/dist/cjs/scroll-scene/controller-source.js.map +1 -0
- package/dist/cjs/scroll-scene/engine-source.d.ts +2 -0
- package/dist/cjs/scroll-scene/engine-source.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/engine-source.js +26 -0
- package/dist/cjs/scroll-scene/engine-source.js.map +1 -0
- package/dist/cjs/scroll-scene/index.d.ts +7 -0
- package/dist/cjs/scroll-scene/index.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/index.js +22 -0
- package/dist/cjs/scroll-scene/index.js.map +1 -0
- package/dist/cjs/scroll-scene/moment.d.ts +21 -0
- package/dist/cjs/scroll-scene/moment.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/moment.js +50 -0
- package/dist/cjs/scroll-scene/moment.js.map +1 -0
- package/dist/cjs/scroll-scene/points.d.ts +23 -0
- package/dist/cjs/scroll-scene/points.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/points.js +51 -0
- package/dist/cjs/scroll-scene/points.js.map +1 -0
- package/dist/cjs/scroll-scene/unclip.d.ts +15 -0
- package/dist/cjs/scroll-scene/unclip.d.ts.map +1 -0
- package/dist/cjs/scroll-scene/unclip.js +44 -0
- package/dist/cjs/scroll-scene/unclip.js.map +1 -0
- package/dist/cjs/state-bindings/index.d.ts +74 -0
- package/dist/cjs/state-bindings/index.d.ts.map +1 -0
- package/dist/cjs/state-bindings/index.js +193 -0
- package/dist/cjs/state-bindings/index.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils/inline-script-error-guard.d.ts +29 -0
- package/dist/cjs/utils/inline-script-error-guard.d.ts.map +1 -0
- package/dist/cjs/utils/inline-script-error-guard.js +38 -0
- package/dist/cjs/utils/inline-script-error-guard.js.map +1 -0
- package/dist/esm/index.d.ts +6 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/motion-runtime/index.d.ts +3 -0
- package/dist/esm/motion-runtime/index.d.ts.map +1 -0
- package/dist/esm/motion-runtime/index.js +3 -0
- package/dist/esm/motion-runtime/index.js.map +1 -0
- package/dist/esm/motion-runtime/motion-engine-source.d.ts +16 -0
- package/dist/esm/motion-runtime/motion-engine-source.d.ts.map +1 -0
- package/dist/esm/motion-runtime/motion-engine-source.js +16 -0
- package/dist/esm/motion-runtime/motion-engine-source.js.map +1 -0
- package/dist/esm/motion-runtime/scroll-video-engine-source.d.ts +9 -0
- package/dist/esm/motion-runtime/scroll-video-engine-source.d.ts.map +1 -0
- package/dist/esm/motion-runtime/scroll-video-engine-source.js +9 -0
- package/dist/esm/motion-runtime/scroll-video-engine-source.js.map +1 -0
- package/dist/esm/page-transition/index.d.ts +4 -0
- package/dist/esm/page-transition/index.d.ts.map +1 -0
- package/dist/esm/page-transition/index.js +4 -0
- package/dist/esm/page-transition/index.js.map +1 -0
- package/dist/esm/page-transition/morph-source.d.ts +20 -0
- package/dist/esm/page-transition/morph-source.d.ts.map +1 -0
- package/dist/esm/page-transition/morph-source.js +21 -0
- package/dist/esm/page-transition/morph-source.js.map +1 -0
- package/dist/esm/page-transition/variants.d.ts +87 -0
- package/dist/esm/page-transition/variants.d.ts.map +1 -0
- package/dist/esm/page-transition/variants.js +318 -0
- package/dist/esm/page-transition/variants.js.map +1 -0
- package/dist/esm/page-transition/view-transition-css.d.ts +21 -0
- package/dist/esm/page-transition/view-transition-css.d.ts.map +1 -0
- package/dist/esm/page-transition/view-transition-css.js +161 -0
- package/dist/esm/page-transition/view-transition-css.js.map +1 -0
- package/dist/esm/scroll-scene/chapter-index.d.ts +15 -0
- package/dist/esm/scroll-scene/chapter-index.d.ts.map +1 -0
- package/dist/esm/scroll-scene/chapter-index.js +26 -0
- package/dist/esm/scroll-scene/chapter-index.js.map +1 -0
- package/dist/esm/scroll-scene/controller-source.d.ts +26 -0
- package/dist/esm/scroll-scene/controller-source.d.ts.map +1 -0
- package/dist/esm/scroll-scene/controller-source.js +26 -0
- package/dist/esm/scroll-scene/controller-source.js.map +1 -0
- package/dist/esm/scroll-scene/engine-source.d.ts +2 -0
- package/dist/esm/scroll-scene/engine-source.d.ts.map +1 -0
- package/dist/esm/scroll-scene/engine-source.js +22 -0
- package/dist/esm/scroll-scene/engine-source.js.map +1 -0
- package/dist/esm/scroll-scene/index.d.ts +7 -0
- package/dist/esm/scroll-scene/index.d.ts.map +1 -0
- package/dist/esm/scroll-scene/index.js +7 -0
- package/dist/esm/scroll-scene/index.js.map +1 -0
- package/dist/esm/scroll-scene/moment.d.ts +21 -0
- package/dist/esm/scroll-scene/moment.d.ts.map +1 -0
- package/dist/esm/scroll-scene/moment.js +46 -0
- package/dist/esm/scroll-scene/moment.js.map +1 -0
- package/dist/esm/scroll-scene/points.d.ts +23 -0
- package/dist/esm/scroll-scene/points.d.ts.map +1 -0
- package/dist/esm/scroll-scene/points.js +45 -0
- package/dist/esm/scroll-scene/points.js.map +1 -0
- package/dist/esm/scroll-scene/unclip.d.ts +15 -0
- package/dist/esm/scroll-scene/unclip.d.ts.map +1 -0
- package/dist/esm/scroll-scene/unclip.js +40 -0
- package/dist/esm/scroll-scene/unclip.js.map +1 -0
- package/dist/esm/state-bindings/index.d.ts +74 -0
- package/dist/esm/state-bindings/index.d.ts.map +1 -0
- package/dist/esm/state-bindings/index.js +176 -0
- package/dist/esm/state-bindings/index.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils/inline-script-error-guard.d.ts +29 -0
- package/dist/esm/utils/inline-script-error-guard.d.ts.map +1 -0
- package/dist/esm/utils/inline-script-error-guard.js +34 -0
- package/dist/esm/utils/inline-script-error-guard.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +10 -0
- package/src/motion-runtime/index.ts +2 -0
- package/src/motion-runtime/motion-engine-source.ts +289 -0
- package/src/motion-runtime/scroll-video-engine-source.ts +591 -0
- package/src/page-transition/index.ts +3 -0
- package/src/page-transition/morph-source.ts +86 -0
- package/src/page-transition/variants.ts +407 -0
- package/src/page-transition/view-transition-css.ts +237 -0
- package/src/scroll-scene/chapter-index.ts +25 -0
- package/src/scroll-scene/controller-source.ts +451 -0
- package/src/scroll-scene/engine-source.ts +330 -0
- package/src/scroll-scene/index.ts +12 -0
- package/src/scroll-scene/moment.ts +64 -0
- package/src/scroll-scene/points.ts +46 -0
- package/src/scroll-scene/unclip.ts +44 -0
- package/src/state-bindings/index.ts +207 -0
- package/src/utils/inline-script-error-guard.ts +51 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { motionEngineSource } from '../../src/motion-runtime/motion-engine-source'
|
|
2
|
+
|
|
3
|
+
type Style = Record<string, string>
|
|
4
|
+
|
|
5
|
+
interface FakeElement {
|
|
6
|
+
style: Style
|
|
7
|
+
attributes: Record<string, string>
|
|
8
|
+
parentElement: FakeElement | null
|
|
9
|
+
children: FakeElement[]
|
|
10
|
+
hasAttribute: (name: string) => boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const fake = (
|
|
14
|
+
attributes: Record<string, string> = {},
|
|
15
|
+
children: FakeElement[] = []
|
|
16
|
+
): FakeElement => {
|
|
17
|
+
const node: FakeElement = {
|
|
18
|
+
style: {},
|
|
19
|
+
attributes,
|
|
20
|
+
parentElement: null,
|
|
21
|
+
children,
|
|
22
|
+
hasAttribute: (name) => name in attributes,
|
|
23
|
+
}
|
|
24
|
+
children.forEach((child) => {
|
|
25
|
+
child.parentElement = node
|
|
26
|
+
})
|
|
27
|
+
return node
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The engine is shipped as text; these run that very text.
|
|
31
|
+
const engine = new Function(
|
|
32
|
+
`${motionEngineSource()}
|
|
33
|
+
return { applyScrollState, letMarqueeSwipe }`
|
|
34
|
+
)() as {
|
|
35
|
+
applyScrollState: (
|
|
36
|
+
element: { style: Style },
|
|
37
|
+
from: Record<string, unknown>,
|
|
38
|
+
to: Record<string, unknown>,
|
|
39
|
+
progress: number
|
|
40
|
+
) => void
|
|
41
|
+
letMarqueeSwipe: (track: FakeElement | null) => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const styleAt = (from: Record<string, unknown>, to: Record<string, unknown>, progress: number) => {
|
|
45
|
+
const element = { style: {} as Style }
|
|
46
|
+
engine.applyScrollState(element, from, to, progress)
|
|
47
|
+
return element.style
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('the scroll-linked state both exports write', () => {
|
|
51
|
+
it('glides a travel measured in the element’s own width, as the canvas does', () => {
|
|
52
|
+
expect(styleAt({ x: '0%' }, { x: '-50%' }, 0.5).translate).toBe('-25% 0px')
|
|
53
|
+
expect(styleAt({ x: '0%' }, { x: '-50%' }, 0).translate).toBe('0% 0px')
|
|
54
|
+
expect(styleAt({ x: '0%' }, { x: '-50%' }, 1).translate).toBe('-50% 0px')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('glides any unit both ends share', () => {
|
|
58
|
+
expect(styleAt({ y: '40px' }, { y: '0px' }, 0.25).translate).toBe('0px 30px')
|
|
59
|
+
expect(styleAt({ rotate: '-8deg' }, { rotate: '0deg' }, 0.25).rotate).toBe('-6deg')
|
|
60
|
+
expect(styleAt({ x: '2rem' }, { x: '4rem' }, 0.25).translate).toBe('2.5rem 0px')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('snaps when the two ends use different units, never mid-flight', () => {
|
|
64
|
+
expect(styleAt({ x: '0%' }, { x: '-200px' }, 0.5).translate).toBe('0% 0px')
|
|
65
|
+
expect(styleAt({ x: '0%' }, { x: '-200px' }, 1).translate).toBe('-200px 0px')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('leaves numbers and filters as they were', () => {
|
|
69
|
+
expect(styleAt({ opacity: 0, y: 40 }, { opacity: 1, y: 0 }, 0.5)).toEqual({
|
|
70
|
+
opacity: '0.5',
|
|
71
|
+
translate: '0px 20px',
|
|
72
|
+
})
|
|
73
|
+
expect(styleAt({ filter: 'blur(8px)' }, { filter: 'blur(0px)' }, 0.5).filter).toBe('blur(4px)')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('a row that slides by itself, for a visitor who asks for less motion', () => {
|
|
78
|
+
it('stands still and can be swiped: the frame scrolls sideways, the copy goes', () => {
|
|
79
|
+
const row = fake()
|
|
80
|
+
const copy = fake({ 'data-marquee-copy': 'true' })
|
|
81
|
+
const track = fake({}, [row, copy])
|
|
82
|
+
const frame = fake({ 'data-marquee': 'true' }, [track])
|
|
83
|
+
|
|
84
|
+
engine.letMarqueeSwipe(track)
|
|
85
|
+
|
|
86
|
+
expect(frame.style.overflowX).toBe('auto')
|
|
87
|
+
expect(track.style.minWidth).toBe('100%')
|
|
88
|
+
expect(copy.style.display).toBe('none')
|
|
89
|
+
expect(row.style.display).toBeUndefined()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('leaves a Motion element that is not the loop of a row alone', () => {
|
|
93
|
+
const motion = fake({}, [fake({ 'data-marquee-copy': 'true' })])
|
|
94
|
+
const parent = fake({}, [motion])
|
|
95
|
+
|
|
96
|
+
engine.letMarqueeSwipe(motion)
|
|
97
|
+
engine.letMarqueeSwipe(null)
|
|
98
|
+
|
|
99
|
+
expect(parent.style.overflowX).toBeUndefined()
|
|
100
|
+
expect(motion.style.minWidth).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { scrollVideoEngineSource } from '../../src/motion-runtime/scroll-video-engine-source'
|
|
2
|
+
|
|
3
|
+
// The engine is shipped as text; these run that very text.
|
|
4
|
+
const engine = new Function(
|
|
5
|
+
`${scrollVideoEngineSource()}
|
|
6
|
+
return { clipPositionFor, normalizeWindow }`
|
|
7
|
+
)() as {
|
|
8
|
+
clipPositionFor: (windowProgress: number, backToStart: boolean) => number
|
|
9
|
+
normalizeWindow: (start: unknown, end: unknown) => { start: number; end: number }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('the scroll-video engine both exports run', () => {
|
|
13
|
+
it('plays the clip once through the window by default', () => {
|
|
14
|
+
expect([-0.2, 0, 0.25, 0.5, 1, 1.4].map((p) => engine.clipPositionFor(p, false))).toEqual([
|
|
15
|
+
0, 0, 0.25, 0.5, 1, 1,
|
|
16
|
+
])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('played back to the start: the last frame halfway, the first frame again at the end', () => {
|
|
20
|
+
expect([0, 0.25, 0.5, 0.75, 1, 1.4].map((p) => engine.clipPositionFor(p, true))).toEqual([
|
|
21
|
+
0, 0.5, 1, 0.5, 0, 0,
|
|
22
|
+
])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('keeps a window playable', () => {
|
|
26
|
+
expect(engine.normalizeWindow(40, 40)).toEqual({ start: 40, end: 41 })
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { scrollSceneEngineSource } from '../../src/scroll-scene/engine-source'
|
|
2
|
+
|
|
3
|
+
type Lane = { prop: string; at: number[]; values: number[]; unit?: string }
|
|
4
|
+
type FakeElement = {
|
|
5
|
+
style: Record<string, string> & { setProperty: (name: string, value: string) => void }
|
|
6
|
+
attributes: Record<string, string>
|
|
7
|
+
width: number
|
|
8
|
+
setAttribute: (name: string, value: string) => void
|
|
9
|
+
removeAttribute: (name: string) => void
|
|
10
|
+
hasAttribute: (name: string) => boolean
|
|
11
|
+
getBoundingClientRect: () => { width: number }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The engine is shipped as text; these run that very text.
|
|
15
|
+
const engine = new Function(
|
|
16
|
+
`${scrollSceneEngineSource()}
|
|
17
|
+
return { storyLanes, applyLanesAt, clearMovement, letRowsSwipe, parseScrollBind }`
|
|
18
|
+
)() as {
|
|
19
|
+
storyLanes: (lanes: Lane[]) => Lane[]
|
|
20
|
+
applyLanesAt: (element: FakeElement, lanes: Lane[], p: number) => void
|
|
21
|
+
clearMovement: (element: FakeElement, lanes: Lane[]) => void
|
|
22
|
+
letRowsSwipe: (bound: Array<{ element: FakeElement; lanes: Lane[] }>, stage: unknown) => void
|
|
23
|
+
parseScrollBind: (value: string) => Lane[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const element = (width = 400): FakeElement => {
|
|
27
|
+
const style = {} as FakeElement['style']
|
|
28
|
+
style.setProperty = (name: string, value: string) => {
|
|
29
|
+
style[name] = value
|
|
30
|
+
}
|
|
31
|
+
const attributes: Record<string, string> = {}
|
|
32
|
+
return {
|
|
33
|
+
style,
|
|
34
|
+
attributes,
|
|
35
|
+
width,
|
|
36
|
+
setAttribute: (name, value) => {
|
|
37
|
+
attributes[name] = value
|
|
38
|
+
},
|
|
39
|
+
removeAttribute: (name) => {
|
|
40
|
+
delete attributes[name]
|
|
41
|
+
},
|
|
42
|
+
hasAttribute: (name) => name in attributes,
|
|
43
|
+
getBoundingClientRect: () => ({ width }),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const RISE_IN = engine.parseScrollBind('rise-in')
|
|
48
|
+
|
|
49
|
+
describe('a story scene for visitors who ask for less motion', () => {
|
|
50
|
+
it('keeps the fades and filters and drops every movement', () => {
|
|
51
|
+
const lanes: Lane[] = [
|
|
52
|
+
{ prop: 'y', at: [0, 0.35], values: [70, 0] },
|
|
53
|
+
{ prop: 'x', at: [0, 1], values: [0, -100], unit: '%' },
|
|
54
|
+
{ prop: 'scale', at: [0, 1], values: [0.85, 1.1] },
|
|
55
|
+
{ prop: 'rotate', at: [0, 0.35], values: [-90, 0] },
|
|
56
|
+
{ prop: 'rotate-x', at: [0, 0.4], values: [35, 0] },
|
|
57
|
+
{ prop: 'clip', at: [0, 0.4], values: [0, 100] },
|
|
58
|
+
{ prop: 'clip-y', at: [0, 0.4], values: [0, 100] },
|
|
59
|
+
{ prop: 'opacity', at: [0, 0.3], values: [0, 1] },
|
|
60
|
+
{ prop: 'blur', at: [0, 0.3], values: [10, 0] },
|
|
61
|
+
{ prop: 'grayscale', at: [0, 0.45], values: [1, 0] },
|
|
62
|
+
]
|
|
63
|
+
expect(engine.storyLanes(lanes).map((lane) => lane.prop)).toEqual([
|
|
64
|
+
'opacity',
|
|
65
|
+
'blur',
|
|
66
|
+
'grayscale',
|
|
67
|
+
])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('shows a counting number at the figure it counts to', () => {
|
|
71
|
+
expect(engine.storyLanes([{ prop: 'count', at: [0.1, 0.4], values: [0, 48] }])).toEqual([
|
|
72
|
+
{ prop: 'count', at: [0, 1], values: [48, 48] },
|
|
73
|
+
])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('fades an entrance in where the layout puts it, without the rise', () => {
|
|
77
|
+
const heading = element()
|
|
78
|
+
engine.applyLanesAt(heading, engine.storyLanes(RISE_IN), 0.15)
|
|
79
|
+
|
|
80
|
+
expect(heading.style.opacity).toBe('0.5')
|
|
81
|
+
expect(heading.style.translate).toBeUndefined()
|
|
82
|
+
engine.applyLanesAt(heading, engine.storyLanes(RISE_IN), 0.6)
|
|
83
|
+
expect(heading.style.opacity).toBe('1')
|
|
84
|
+
expect(heading.style.translate).toBeUndefined()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('takes off the movement a scene painted before it switched to the story', () => {
|
|
88
|
+
const card = element()
|
|
89
|
+
const lanes: Lane[] = [
|
|
90
|
+
{ prop: 'y', at: [0, 1], values: [40, -40] },
|
|
91
|
+
{ prop: 'scale', at: [0, 1], values: [0.85, 1.1] },
|
|
92
|
+
{ prop: 'rotate', at: [0, 1], values: [-5, 5] },
|
|
93
|
+
{ prop: 'clip', at: [0, 1], values: [0, 100] },
|
|
94
|
+
]
|
|
95
|
+
engine.applyLanesAt(card, lanes, 0.5)
|
|
96
|
+
expect(card.style.translate).toBe('0px 0px')
|
|
97
|
+
|
|
98
|
+
engine.clearMovement(card, lanes)
|
|
99
|
+
|
|
100
|
+
expect(card.style.translate).toBe('')
|
|
101
|
+
expect(card.style.scale).toBe('')
|
|
102
|
+
expect(card.style.rotate).toBe('')
|
|
103
|
+
expect(card.style.clipPath).toBe('')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('lets a row wider than the stage that travelled sideways be swiped, and nothing else', () => {
|
|
107
|
+
const stage = { getBoundingClientRect: () => ({ width: 1440 }) }
|
|
108
|
+
const rail = element(2400)
|
|
109
|
+
const slideIn = element(600)
|
|
110
|
+
const wideStill = element(2400)
|
|
111
|
+
engine.letRowsSwipe(
|
|
112
|
+
[
|
|
113
|
+
{ element: rail, lanes: engine.parseScrollBind('rail-x') },
|
|
114
|
+
{ element: slideIn, lanes: [{ prop: 'x', at: [0, 0.3], values: [-60, 0] }] },
|
|
115
|
+
{ element: wideStill, lanes: RISE_IN },
|
|
116
|
+
],
|
|
117
|
+
stage
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
expect(rail.style.overflowX).toBe('auto')
|
|
121
|
+
expect(rail.style.maxWidth).toBe('100%')
|
|
122
|
+
expect(rail.attributes['data-scene-swipe']).toBe('')
|
|
123
|
+
expect(slideIn.style.overflowX).toBeUndefined()
|
|
124
|
+
expect(wideStill.style.overflowX).toBeUndefined()
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INLINE_SCRIPT_ERROR_GUARD,
|
|
3
|
+
documentRunsProjectScripts,
|
|
4
|
+
} from '../../src/utils/inline-script-error-guard'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A project's custom code threw `Cannot read properties of null (reading
|
|
8
|
+
* 'addEventListener')` from an inline script on every page, and the generated
|
|
9
|
+
* Next.js app showed it as an "Unhandled Runtime Error" overlay. The guard runs
|
|
10
|
+
* first in the document and stops errors whose file is the document itself —
|
|
11
|
+
* the project's inline scripts — before any other listener sees them.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const PAGE_URL = 'https://site.test/category/architecture?ref=nav'
|
|
15
|
+
|
|
16
|
+
interface FakeErrorEvent {
|
|
17
|
+
error: unknown
|
|
18
|
+
filename: string
|
|
19
|
+
preventDefault: jest.Mock
|
|
20
|
+
stopImmediatePropagation: jest.Mock
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const installGuard = () => {
|
|
24
|
+
const listeners: Array<{
|
|
25
|
+
type: string
|
|
26
|
+
handler: (event: FakeErrorEvent) => void
|
|
27
|
+
capture: boolean
|
|
28
|
+
}> = []
|
|
29
|
+
const fakeWindow = {
|
|
30
|
+
addEventListener: (type: string, handler: (event: FakeErrorEvent) => void, capture: boolean) =>
|
|
31
|
+
listeners.push({ type, handler, capture }),
|
|
32
|
+
}
|
|
33
|
+
const fakeConsole = { warn: jest.fn() }
|
|
34
|
+
// tslint:disable-next-line:function-constructor
|
|
35
|
+
new Function('window', 'document', 'console', INLINE_SCRIPT_ERROR_GUARD)(
|
|
36
|
+
fakeWindow,
|
|
37
|
+
{ URL: `${PAGE_URL}#top` },
|
|
38
|
+
fakeConsole
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const dispatch = (filename: string, error: unknown = new TypeError('boom')) => {
|
|
42
|
+
const event: FakeErrorEvent = {
|
|
43
|
+
error,
|
|
44
|
+
filename,
|
|
45
|
+
preventDefault: jest.fn(),
|
|
46
|
+
stopImmediatePropagation: jest.fn(),
|
|
47
|
+
}
|
|
48
|
+
listeners.forEach((listener) => listener.handler(event))
|
|
49
|
+
return event
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { listeners, dispatch, fakeConsole }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('INLINE_SCRIPT_ERROR_GUARD', () => {
|
|
56
|
+
it('listens for errors in the capture phase, ahead of every later listener', () => {
|
|
57
|
+
const { listeners } = installGuard()
|
|
58
|
+
|
|
59
|
+
expect(listeners).toHaveLength(1)
|
|
60
|
+
expect(listeners[0]).toMatchObject({ type: 'error', capture: true })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("stops an error thrown by one of the document's own inline scripts", () => {
|
|
64
|
+
const { dispatch, fakeConsole } = installGuard()
|
|
65
|
+
const error = new TypeError("Cannot read properties of null (reading 'addEventListener')")
|
|
66
|
+
|
|
67
|
+
const event = dispatch(PAGE_URL, error)
|
|
68
|
+
|
|
69
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
70
|
+
expect(event.stopImmediatePropagation).toHaveBeenCalled()
|
|
71
|
+
expect(fakeConsole.warn).toHaveBeenCalledWith(expect.any(String), error)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('leaves errors from the app bundles and from external scripts alone', () => {
|
|
75
|
+
const { dispatch, fakeConsole } = installGuard()
|
|
76
|
+
|
|
77
|
+
const fromBundle = dispatch('https://site.test/_next/static/chunks/pages/index.js')
|
|
78
|
+
const fromCdn = dispatch('https://unpkg.com/blaze-slider/dist/blaze-slider.min.js')
|
|
79
|
+
|
|
80
|
+
expect(fromBundle.stopImmediatePropagation).not.toHaveBeenCalled()
|
|
81
|
+
expect(fromCdn.stopImmediatePropagation).not.toHaveBeenCalled()
|
|
82
|
+
expect(fakeConsole.warn).not.toHaveBeenCalled()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('leaves events that carry no error alone', () => {
|
|
86
|
+
// A failed image or stylesheet, or a muted cross-origin "Script error."
|
|
87
|
+
const { dispatch } = installGuard()
|
|
88
|
+
|
|
89
|
+
expect(dispatch(PAGE_URL, null).stopImmediatePropagation).not.toHaveBeenCalled()
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('documentRunsProjectScripts', () => {
|
|
94
|
+
it('is true for custom code with a script, in the head or the body', () => {
|
|
95
|
+
expect(
|
|
96
|
+
documentRunsProjectScripts({ customCode: { head: '<script>init()</script>' }, assets: [] })
|
|
97
|
+
).toBe(true)
|
|
98
|
+
expect(
|
|
99
|
+
documentRunsProjectScripts({
|
|
100
|
+
customCode: { body: '<SCRIPT src="/a.js"></SCRIPT>' },
|
|
101
|
+
assets: [],
|
|
102
|
+
})
|
|
103
|
+
).toBe(true)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('is true for an inline script asset', () => {
|
|
107
|
+
expect(
|
|
108
|
+
documentRunsProjectScripts({ assets: [{ type: 'script', content: 'console.log(1)' }] })
|
|
109
|
+
).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('is false when the project runs no script of its own', () => {
|
|
113
|
+
expect(documentRunsProjectScripts({ assets: [] })).toBe(false)
|
|
114
|
+
expect(
|
|
115
|
+
documentRunsProjectScripts({
|
|
116
|
+
customCode: { head: '<style>body { margin: 0 }</style>', body: '<noscript>JS</noscript>' },
|
|
117
|
+
assets: [{ type: 'script', path: 'https://cdn.test/lib.js' }],
|
|
118
|
+
})
|
|
119
|
+
).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
})
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ import * as RichTextEmbeds from './utils/rich-text-embeds';
|
|
|
28
28
|
import * as RichTextEmbedsCodegen from './utils/rich-text-embeds-codegen';
|
|
29
29
|
import * as DataCache from './cache';
|
|
30
30
|
import * as PageRevalidate from './page-revalidate';
|
|
31
|
+
import * as ScrollSceneRuntime from './scroll-scene';
|
|
32
|
+
import * as MotionRuntime from './motion-runtime';
|
|
33
|
+
import * as PageTransition from './page-transition';
|
|
34
|
+
import * as StateBindings from './state-bindings';
|
|
31
35
|
import * as WebPush from './utils/web-push';
|
|
32
|
-
|
|
36
|
+
import * as InlineScriptErrorGuard from './utils/inline-script-error-guard';
|
|
37
|
+
export { Constants, StringUtils, UIDLUtils, GenericUtils, LocalImports, JSIdentifiers, ASTScope, ASTStatementOrder, ASTEffectCleanup, RoutePaths, LayoutTopology, HtmlContentModel, StyleDeclarations, StorefrontTax, ProductDiscounts, ProductRatings, RegionalPricing, DiscountEngine, ProductOptions, CartLinePricing, CartCurrency, TableAccess, SessionCookieResolver, SubscriptionAccess, FontPreconnect, EmailDate, RichTextEmbeds, RichTextEmbedsCodegen, DataCache, PageRevalidate, ScrollSceneRuntime, MotionRuntime, PageTransition, StateBindings, WebPush, InlineScriptErrorGuard, };
|
|
33
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAA;AAC/C,OAAO,KAAK,YAAY,MAAM,iBAAiB,CAAA;AAC/C,OAAO,KAAK,YAAY,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAA;AACvD,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,iBAAiB,MAAM,6BAA6B,CAAA;AAChE,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,iBAAiB,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAA;AACvD,OAAO,KAAK,gBAAgB,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,eAAe,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAA;AAC5D,OAAO,KAAK,YAAY,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,qBAAqB,MAAM,iCAAiC,CAAA;AACxE,OAAO,KAAK,kBAAkB,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAA;AAC/C,OAAO,KAAK,cAAc,MAAM,0BAA0B,CAAA;AAC1D,OAAO,KAAK,qBAAqB,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,SAAS,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,cAAc,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAA;AAC/C,OAAO,KAAK,YAAY,MAAM,iBAAiB,CAAA;AAC/C,OAAO,KAAK,YAAY,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAA;AACvD,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,iBAAiB,MAAM,6BAA6B,CAAA;AAChE,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,iBAAiB,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAA;AACvD,OAAO,KAAK,gBAAgB,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,eAAe,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAA;AAC5D,OAAO,KAAK,YAAY,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,qBAAqB,MAAM,iCAAiC,CAAA;AACxE,OAAO,KAAK,kBAAkB,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAA;AAC/C,OAAO,KAAK,cAAc,MAAM,0BAA0B,CAAA;AAC1D,OAAO,KAAK,qBAAqB,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,SAAS,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,cAAc,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,kBAAkB,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,cAAc,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAA;AAC3C,OAAO,KAAK,sBAAsB,MAAM,mCAAmC,CAAA;AAE3E,OAAO,EACL,SAAS,EACT,WAAW,EACX,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,aAAa,EACb,OAAO,EACP,sBAAsB,GACvB,CAAA"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.WebPush = exports.PageRevalidate = exports.DataCache = exports.RichTextEmbedsCodegen = exports.RichTextEmbeds = exports.EmailDate = exports.FontPreconnect = exports.SubscriptionAccess = exports.SessionCookieResolver = exports.TableAccess = exports.CartCurrency = exports.CartLinePricing = exports.ProductOptions = exports.DiscountEngine = exports.RegionalPricing = exports.ProductRatings = exports.ProductDiscounts = exports.StorefrontTax = exports.StyleDeclarations = exports.HtmlContentModel = exports.LayoutTopology = exports.RoutePaths = exports.ASTEffectCleanup = exports.ASTStatementOrder = exports.ASTScope = exports.JSIdentifiers = exports.LocalImports = exports.GenericUtils = exports.UIDLUtils = exports.StringUtils = exports.Constants = void 0;
|
|
26
|
+
exports.InlineScriptErrorGuard = exports.WebPush = exports.StateBindings = exports.PageTransition = exports.MotionRuntime = exports.ScrollSceneRuntime = exports.PageRevalidate = exports.DataCache = exports.RichTextEmbedsCodegen = exports.RichTextEmbeds = exports.EmailDate = exports.FontPreconnect = exports.SubscriptionAccess = exports.SessionCookieResolver = exports.TableAccess = exports.CartCurrency = exports.CartLinePricing = exports.ProductOptions = exports.DiscountEngine = exports.RegionalPricing = exports.ProductRatings = exports.ProductDiscounts = exports.StorefrontTax = exports.StyleDeclarations = exports.HtmlContentModel = exports.LayoutTopology = exports.RoutePaths = exports.ASTEffectCleanup = exports.ASTStatementOrder = exports.ASTScope = exports.JSIdentifiers = exports.LocalImports = exports.GenericUtils = exports.UIDLUtils = exports.StringUtils = exports.Constants = void 0;
|
|
27
27
|
var Constants = __importStar(require("./constants"));
|
|
28
28
|
exports.Constants = Constants;
|
|
29
29
|
var StringUtils = __importStar(require("./utils/string-utils"));
|
|
@@ -84,6 +84,16 @@ var DataCache = __importStar(require("./cache"));
|
|
|
84
84
|
exports.DataCache = DataCache;
|
|
85
85
|
var PageRevalidate = __importStar(require("./page-revalidate"));
|
|
86
86
|
exports.PageRevalidate = PageRevalidate;
|
|
87
|
+
var ScrollSceneRuntime = __importStar(require("./scroll-scene"));
|
|
88
|
+
exports.ScrollSceneRuntime = ScrollSceneRuntime;
|
|
89
|
+
var MotionRuntime = __importStar(require("./motion-runtime"));
|
|
90
|
+
exports.MotionRuntime = MotionRuntime;
|
|
91
|
+
var PageTransition = __importStar(require("./page-transition"));
|
|
92
|
+
exports.PageTransition = PageTransition;
|
|
93
|
+
var StateBindings = __importStar(require("./state-bindings"));
|
|
94
|
+
exports.StateBindings = StateBindings;
|
|
87
95
|
var WebPush = __importStar(require("./utils/web-push"));
|
|
88
96
|
exports.WebPush = WebPush;
|
|
97
|
+
var InlineScriptErrorGuard = __importStar(require("./utils/inline-script-error-guard"));
|
|
98
|
+
exports.InlineScriptErrorGuard = InlineScriptErrorGuard;
|
|
89
99
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAAwC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAAwC;AAsCtC,8BAAS;AArCX,gEAAmD;AAsCjD,kCAAW;AArCb,4DAA+C;AAsC7C,8BAAS;AArCX,4DAA+C;AAsC7C,oCAAY;AArCd,kEAAqD;AAsCnD,oCAAY;AArCd,oEAAuD;AAsCrD,sCAAa;AArCf,0DAA6C;AAsC3C,4BAAQ;AArCV,6EAAgE;AAsC9D,8CAAiB;AArCnB,2EAA8D;AAsC5D,4CAAgB;AArClB,8DAAiD;AAsC/C,gCAAU;AArCZ,sEAAyD;AAsCvD,wCAAc;AArChB,2EAA8D;AAsC5D,4CAAgB;AArClB,4EAA+D;AAsC7D,8CAAiB;AArCnB,oEAAuD;AAsCrD,sCAAa;AArCf,0EAA6D;AAsC3D,4CAAgB;AArClB,sEAAyD;AAsCvD,wCAAc;AArChB,wEAA2D;AAsCzD,0CAAe;AArCjB,sEAAyD;AAsCvD,wCAAc;AArChB,sEAAyD;AAsCvD,wCAAc;AArChB,yEAA4D;AAsC1D,0CAAe;AArCjB,kEAAqD;AAsCnD,oCAAY;AArCd,gEAAmD;AAsCjD,kCAAW;AArCb,qFAAwE;AAsCtE,sDAAqB;AArCvB,8EAAiE;AAsC/D,gDAAkB;AArCpB,sEAAyD;AAsCvD,wCAAc;AArChB,4DAA+C;AAsC7C,8BAAS;AArCX,uEAA0D;AAsCxD,wCAAc;AArChB,sFAAyE;AAsCvE,sDAAqB;AArCvB,iDAAoC;AAsClC,8BAAS;AArCX,gEAAmD;AAsCjD,wCAAc;AArChB,iEAAoD;AAsClD,gDAAkB;AArCpB,8DAAiD;AAsC/C,sCAAa;AArCf,gEAAmD;AAsCjD,wCAAc;AArChB,8DAAiD;AAsC/C,sCAAa;AArCf,wDAA2C;AAsCzC,0BAAO;AArCT,wFAA2E;AAsCzE,wDAAsB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/motion-runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scrollVideoEngineSource = exports.motionEngineSource = void 0;
|
|
4
|
+
var motion_engine_source_1 = require("./motion-engine-source");
|
|
5
|
+
Object.defineProperty(exports, "motionEngineSource", { enumerable: true, get: function () { return motion_engine_source_1.motionEngineSource; } });
|
|
6
|
+
var scroll_video_engine_source_1 = require("./scroll-video-engine-source");
|
|
7
|
+
Object.defineProperty(exports, "scrollVideoEngineSource", { enumerable: true, get: function () { return scroll_video_engine_source_1.scrollVideoEngineSource; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/motion-runtime/index.ts"],"names":[],"mappings":";;;AAAA,+DAA2D;AAAlD,0HAAA,kBAAkB,OAAA;AAC3B,2EAAsE;AAA7D,qIAAA,uBAAuB,OAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element-motion engine as SOURCE TEXT: easing curves, the preset table, the
|
|
3
|
+
* from/to state helpers and the viewport-fraction maths. Plain JavaScript with
|
|
4
|
+
* no framework in it, shared word for word by the two exports:
|
|
5
|
+
*
|
|
6
|
+
* - the Next export wraps it in the TqMotion React component (framer-motion
|
|
7
|
+
* plays the entrance and supplies the scroll progress);
|
|
8
|
+
* - the HTML export wraps it in a vanilla controller (the Web Animations API
|
|
9
|
+
* plays the entrance, a scroll listener supplies the progress).
|
|
10
|
+
*
|
|
11
|
+
* One copy means a new preset reaches both exports at once. It mirrors the
|
|
12
|
+
* canvas renderer's motion-keyframes module. (No backticks in the engine's own
|
|
13
|
+
* comments unless escaped: it is a template literal.)
|
|
14
|
+
*/
|
|
15
|
+
export declare const motionEngineSource: () => string;
|
|
16
|
+
//# sourceMappingURL=motion-engine-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-engine-source.d.ts","sourceRoot":"","sources":["../../../src/motion-runtime/motion-engine-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,QAAO,MAkRpC,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.motionEngineSource = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The element-motion engine as SOURCE TEXT: easing curves, the preset table, the
|
|
6
|
+
* from/to state helpers and the viewport-fraction maths. Plain JavaScript with
|
|
7
|
+
* no framework in it, shared word for word by the two exports:
|
|
8
|
+
*
|
|
9
|
+
* - the Next export wraps it in the TqMotion React component (framer-motion
|
|
10
|
+
* plays the entrance and supplies the scroll progress);
|
|
11
|
+
* - the HTML export wraps it in a vanilla controller (the Web Animations API
|
|
12
|
+
* plays the entrance, a scroll listener supplies the progress).
|
|
13
|
+
*
|
|
14
|
+
* One copy means a new preset reaches both exports at once. It mirrors the
|
|
15
|
+
* canvas renderer's motion-keyframes module. (No backticks in the engine's own
|
|
16
|
+
* comments unless escaped: it is a template literal.)
|
|
17
|
+
*/
|
|
18
|
+
var motionEngineSource = function () { return "const EASINGS = {\n ease: [0.25, 0.1, 0.25, 1],\n 'ease-in': [0.42, 0, 1, 1],\n 'ease-out': [0, 0, 0.58, 1],\n 'ease-in-out': [0.42, 0, 0.58, 1],\n linear: [0, 0, 1, 1],\n spring: [0.34, 1.56, 0.64, 1],\n back: [0.68, -0.6, 0.32, 1.6],\n bounce: [0.22, 1.2, 0.36, 1],\n}\n\n// Mirrors the canvas renderer's motion-keyframes helpers so a scroll-linked\n// element interpolates EVERY from/to property (x/y/scale/rotate/opacity/filter)\n// identically in the canvas, the preview and the published site. The key ORDER\n// is the order the canvas composes them in: it has to match `cssFromState` in\n// the renderer's motion-keyframes exactly, or a staggered card and its canvas\n// twin would compose the same values differently. Both the scroll-linked path\n// (`applyScrollState`) and the in-place stagger (`cssFromState`) build on these.\nconst TRANSFORM_KEYS = ['x', 'y', 'scale', 'rotate']\n\n// The transform family is written as the INDIVIDUAL transform properties\n// (translate / scale / rotate) and never as the transform property itself, so\n// whatever the author put in transform (a translateX(-50%) centering, a tilt)\n// survives the animation and the browser composes the two. Mirrors the canvas.\nconst withUnit = (value, unit) => (typeof value === 'number' ? value + unit : String(value))\n\nconst writeTransformProps = (css, state) => {\n if (state.x !== undefined || state.y !== undefined) {\n css.translate =\n withUnit(state.x !== undefined ? state.x : 0, 'px') +\n ' ' +\n withUnit(state.y !== undefined ? state.y : 0, 'px')\n }\n if (state.scale !== undefined) {\n css.scale = String(state.scale)\n }\n if (state.rotate !== undefined) {\n css.rotate = withUnit(state.rotate, 'deg')\n }\n}\n\n// Composed filters (blur(10px) grayscale(1) -> blur(0px) grayscale(0))\n// interpolate function-by-function when both sides carry the same sequence \u2014\n// mirrors the canvas contract.\nconst FILTER_LIST_RE = /^(?:[a-z-]+\\(\\s*-?\\d*\\.?\\d+(?:px|deg|%)?\\s*\\)\\s*)+$/\nconst FILTER_FN_RE = /([a-z-]+)\\(\\s*(-?\\d*\\.?\\d+)(px|deg|%)?\\s*\\)/g\n\nconst interpolateFilter = (from, to, p) => {\n if (!FILTER_LIST_RE.test(from.trim()) || !FILTER_LIST_RE.test(to.trim())) {\n return null\n }\n const fromFns = Array.from(from.matchAll(FILTER_FN_RE))\n const toFns = Array.from(to.matchAll(FILTER_FN_RE))\n if (fromFns.length !== toFns.length || fromFns.length === 0) {\n return null\n }\n const parts = []\n for (let index = 0; index < fromFns.length; index++) {\n const fn = fromFns[index][1]\n const unit = fromFns[index][3] || ''\n if (fn !== toFns[index][1] || unit !== (toFns[index][3] || '')) {\n return null\n }\n const value =\n parseFloat(fromFns[index][2]) +\n (parseFloat(toFns[index][2]) - parseFloat(fromFns[index][2])) * p\n parts.push(fn + '(' + value + unit + ')')\n }\n return parts.join(' ')\n}\n\n// A value with a unit (-50%, 2rem) glides in that unit when both ends use the\n// same one, as it does on the canvas. A played entrance gets this from\n// framer-motion or the Web Animations API; the scroll-linked path gets it here.\nconst UNIT_VALUE_RE = /^(-?\\d*\\.?\\d+)(px|%|deg|rem|em|vw|vh)$/\n\nconst interpolateUnitValue = (from, to, p) => {\n const start = UNIT_VALUE_RE.exec(from.trim())\n const end = UNIT_VALUE_RE.exec(to.trim())\n if (!start || !end || start[2] !== end[2]) {\n return null\n }\n const startAmount = parseFloat(start[1])\n return startAmount + (parseFloat(end[1]) - startAmount) * p + start[2]\n}\n\nconst interpolateValue = (from, to, p) => {\n if (typeof from === 'number' && typeof to === 'number') {\n return from + (to - from) * p\n }\n if (typeof from === 'string' && typeof to === 'string') {\n const united = interpolateUnitValue(from, to, p)\n if (united !== null) {\n return united\n }\n const filter = interpolateFilter(from, to, p)\n if (filter !== null) {\n return filter\n }\n }\n return p >= 1 ? to : from\n}\n\nconst applyScrollState = (element, fromVars, toVars, p) => {\n if (!element) {\n return\n }\n const keys = new Set([...Object.keys(fromVars), ...Object.keys(toVars)])\n const transformState = {}\n keys.forEach((key) => {\n const fromValue = fromVars[key] !== undefined ? fromVars[key] : toVars[key]\n const toValue = toVars[key] !== undefined ? toVars[key] : fromVars[key]\n const value = interpolateValue(fromValue, toValue, p)\n if (TRANSFORM_KEYS.includes(key)) {\n transformState[key] = value\n } else {\n element.style[key] = String(value)\n }\n })\n const transformCss = {}\n writeTransformProps(transformCss, transformState)\n Object.keys(transformCss).forEach((prop) => {\n element.style[prop] = transformCss[prop]\n })\n}\n\n// useScroll offset pairs per motion-scroll-offset mode \u2014 mirrors the canvas\n// runtime's scrollProgressForOffset semantics.\nconst SCROLL_OFFSET_RANGES = {\n pass: ['start end', 'end start'],\n contained: ['start start', 'end end'],\n enter: ['start end', 'start start'],\n exit: ['end end', 'end start'],\n}\n\nconst presetStates = (preset, distance) => {\n switch (preset) {\n case 'fade-in':\n return { from: { opacity: 0 }, to: { opacity: 1 } }\n case 'slide-up':\n return { from: { opacity: 0, y: distance }, to: { opacity: 1, y: 0 } }\n case 'slide-down':\n return { from: { opacity: 0, y: -distance }, to: { opacity: 1, y: 0 } }\n case 'slide-left':\n return { from: { opacity: 0, x: distance }, to: { opacity: 1, x: 0 } }\n case 'slide-right':\n return { from: { opacity: 0, x: -distance }, to: { opacity: 1, x: 0 } }\n case 'scale-in':\n return { from: { opacity: 0, scale: 0.8 }, to: { opacity: 1, scale: 1 } }\n case 'zoom-in':\n return { from: { opacity: 0, scale: 0.5 }, to: { opacity: 1, scale: 1 } }\n case 'pop-in':\n return { from: { opacity: 0, scale: 0.6 }, to: { opacity: 1, scale: 1 } }\n case 'rotate-in':\n return { from: { opacity: 0, rotate: -8 }, to: { opacity: 1, rotate: 0 } }\n case 'tilt-in':\n return { from: { opacity: 0, rotate: -10, y: distance }, to: { opacity: 1, rotate: 0, y: 0 } }\n case 'blur-in':\n return { from: { opacity: 0, filter: 'blur(12px)' }, to: { opacity: 1, filter: 'blur(0px)' } }\n case 'blur-up':\n return {\n from: { opacity: 0, y: distance, filter: 'blur(8px)' },\n to: { opacity: 1, y: 0, filter: 'blur(0px)' },\n }\n case 'drop-in':\n return {\n from: { opacity: 0, y: -distance, scale: 1.05 },\n to: { opacity: 1, y: 0, scale: 1 },\n }\n case 'swing-in':\n return { from: { opacity: 0, rotate: -20, y: distance }, to: { opacity: 1, rotate: 0, y: 0 } }\n case 'spin-in':\n return {\n from: { opacity: 0, rotate: -180, scale: 0.6 },\n to: { opacity: 1, rotate: 0, scale: 1 },\n }\n case 'blur-zoom':\n return {\n from: { opacity: 0, scale: 1.35, filter: 'blur(10px)' },\n to: { opacity: 1, scale: 1, filter: 'blur(0px)' },\n }\n case 'zoom-out':\n return { from: { opacity: 0, scale: 1.4 }, to: { opacity: 1, scale: 1 } }\n case 'develop':\n return {\n from: { opacity: 0, filter: 'blur(6px) grayscale(1)' },\n to: { opacity: 1, filter: 'blur(0px) grayscale(0)' },\n }\n case 'glow-in':\n return {\n from: { opacity: 0, filter: 'brightness(1.8) saturate(0.4)' },\n to: { opacity: 1, filter: 'brightness(1) saturate(1)' },\n }\n // Attention loops: rest at the natural state (NO opacity ramp \u2014 a looping\n // opacity would strobe) and read as animation only when repeated (mirror).\n case 'pulse':\n return { from: { scale: 1 }, to: { scale: 1.06 } }\n case 'float':\n return { from: { y: 0 }, to: { y: -12 } }\n case 'spin':\n return { from: { rotate: 0 }, to: { rotate: 360 } }\n case 'wobble':\n return { from: { rotate: -3 }, to: { rotate: 3 } }\n case 'none':\n return { from: {}, to: {} }\n default:\n return { from: { opacity: 0 }, to: { opacity: 1 } }\n }\n}\n\n// Turn a resolved motion state ({ opacity, x, y, scale, rotate, filter, \u2026 }) into\n// an inline style object: the transform keys become the individual translate /\n// scale / rotate properties and every other key passes through as its own CSS\n// property. Mirrors the canvas.\nconst cssFromState = (state) => {\n const css = {}\n writeTransformProps(css, state)\n Object.keys(state).forEach((key) => {\n if (TRANSFORM_KEYS.indexOf(key) !== -1) {\n return\n }\n css[key] = state[key]\n })\n return css\n}\n\n// How much of the element is inside the viewport right now, as a 0..1 fraction\n// of its own area \u2014 the same quantity IntersectionObserver thresholds on, so the\n// failsafe below can apply exactly the threshold useInView was given.\nconst visibleFraction = (rect, vh, vw) => {\n const height = rect.bottom - rect.top\n const width = rect.right - rect.left\n if (height <= 0 || width <= 0) {\n return 0\n }\n const visibleHeight = Math.max(0, Math.min(rect.bottom, vh) - Math.max(rect.top, 0))\n const visibleWidth = Math.max(0, Math.min(rect.right, vw) - Math.max(rect.left, 0))\n return (visibleHeight * visibleWidth) / (height * width)\n}\n\n// The largest fraction this element could EVER have on screen. An element taller\n// than the viewport can never be 60% visible, so an inViewAmount above that is\n// unsatisfiable and would hold the content at opacity 0 permanently. Clamping to\n// what is physically reachable turns \"impossible\" into \"as visible as it gets\".\nconst reachableFraction = (rect, vh, vw) => {\n const height = rect.bottom - rect.top\n const width = rect.right - rect.left\n if (height <= 0 || width <= 0) {\n return 0\n }\n return Math.min(1, vh / height) * Math.min(1, vw / width)\n}\n\n// A row that slides by itself is the editor's loop: a clipping frame\n// (data-marquee) holding this Motion track, which holds the row and the hidden\n// copy that closes the loop (data-marquee-copy). For a visitor who asks for\n// less motion the track does not play, so the row stands still and the frame\n// lets the visitor swipe it instead: every item stays reachable. The copy\n// goes, or every item would show twice, and the track stops reserving room\n// for it. Mirrors letMarqueeSwipe in the editor's marquee contract.\nconst MARQUEE_FRAME_ATTR = 'data-marquee'\nconst MARQUEE_COPY_ATTR = 'data-marquee-copy'\nconst letMarqueeSwipe = (track) => {\n const frame = track && track.parentElement\n if (!frame || !frame.hasAttribute(MARQUEE_FRAME_ATTR)) {\n return\n }\n frame.style.overflowX = 'auto'\n track.style.minWidth = '100%'\n for (const child of Array.from(track.children)) {\n if (child.hasAttribute(MARQUEE_COPY_ATTR)) {\n child.style.display = 'none'\n }\n }\n}"; };
|
|
19
|
+
exports.motionEngineSource = motionEngineSource;
|
|
20
|
+
//# sourceMappingURL=motion-engine-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-engine-source.js","sourceRoot":"","sources":["../../../src/motion-runtime/motion-engine-source.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;GAaG;AACI,IAAM,kBAAkB,GAAG,cAAc,OAAA,ksVAkR9C,EAlR8C,CAkR9C,CAAA;AAlRW,QAAA,kBAAkB,sBAkR7B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scroll-video maths as SOURCE TEXT: the clip window, the progress a
|
|
3
|
+
* rectangle has made through the viewport, and the clip held in memory. Shared
|
|
4
|
+
* word for word by the Next export (TqScrollVideo) and the HTML export's
|
|
5
|
+
* vanilla controller; mirrors the canvas renderer's scroll-video-runtime and
|
|
6
|
+
* its full-file buffering.
|
|
7
|
+
*/
|
|
8
|
+
export declare const scrollVideoEngineSource: () => string;
|
|
9
|
+
//# sourceMappingURL=scroll-video-engine-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-video-engine-source.d.ts","sourceRoot":"","sources":["../../../src/motion-runtime/scroll-video-engine-source.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,QAC9B,MAskBJ,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scrollVideoEngineSource = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The scroll-video maths as SOURCE TEXT: the clip window, the progress a
|
|
6
|
+
* rectangle has made through the viewport, and the clip held in memory. Shared
|
|
7
|
+
* word for word by the Next export (TqScrollVideo) and the HTML export's
|
|
8
|
+
* vanilla controller; mirrors the canvas renderer's scroll-video-runtime and
|
|
9
|
+
* its full-file buffering.
|
|
10
|
+
*/
|
|
11
|
+
var scrollVideoEngineSource = function () { return "const clamp01 = (value) => Math.min(1, Math.max(0, value))\n\nconst MIN_WINDOW_SPAN = 1\n\nconst normalizeWindow = (startRaw, endRaw) => {\n const parse = (value, fallback) => {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? Math.min(100, Math.max(0, parsed)) : fallback\n }\n const start = parse(startRaw, 0)\n const end = parse(endRaw, 100)\n if (end - start >= MIN_WINDOW_SPAN) {\n return { start, end }\n }\n return start <= 100 - MIN_WINDOW_SPAN\n ? { start, end: start + MIN_WINDOW_SPAN }\n : { start: 100 - MIN_WINDOW_SPAN, end: 100 }\n}\n\n// Mirrors the canvas renderer's scrollProgressForOffset for the two modes a\n// scrub video needs: 'contained' (a pinned scene's stage holds while its track\n// scrolls under it) and 'pass' (a plain trip through the viewport).\nconst progressForRect = (rect, viewportHeight, offset) => {\n const vh = viewportHeight || 1\n let raw\n if (offset === 'contained') {\n const travel = rect.height - vh\n raw = travel > 0 ? -rect.top / travel : (vh - rect.top) / (vh + rect.height)\n } else {\n raw = (vh - rect.top) / (vh + rect.height)\n }\n return clamp01(raw)\n}\n\n// Where in the clip a window progress lands. Played back to the start, the\n// clip reaches its last frame halfway through the window and its first frame\n// again at the end (the editor's clipPositionForWindowProgress, word for word).\nconst clipPositionFor = (windowProgress, backToStart) => {\n const position = clamp01(windowProgress)\n if (!backToStart) {\n return position\n }\n return position <= 0.5 ? position * 2 : (1 - position) * 2\n}\n\n// \u2500\u2500 Holding the clip \u2500\u2500\n// A scroll clip is consumed frame by frame at arbitrary positions, so a seek\n// must never wait on the network: a streaming <video> holds only what it has\n// fetched around the playhead (Safari on iPhone fetches nothing ahead), so a\n// fast scroll outruns it and the frame freezes. Two ways to hold it, tried in\n// order once the scene is about a screen away (the assets bucket allows the\n// read from any site):\n// 1. Streamed by the second. A clip the media worker made is fragmented,\n// with an index of every fragment in its first bytes. With MediaSource\n// the page keeps the seconds around the scroll position in the element\n// and fetches fragments as the position moves \u2014 a clip of any length costs\n// the same memory and is ready to scrub almost at once.\n// 2. Whole, in memory: the file fetched once and played from a blob \u2014 an\n// older encode, an upload, a browser without MediaSource (an iPhone), an\n// index that could not be read.\n// Either way the streamed element keeps scrubbing meanwhile; then a copy takes\n// its place at the frame on screen and the streamed one lets go of its\n// download. A fresh element, not a new src on the old one: engines get stuck\n// after a src swap on an element that has already seeked. The page asks the\n// streamed element for its first frame only (preload=\"metadata\"), so the clip\n// is not downloaded twice; where the clip cannot be held (a host that refuses,\n// a whole clip too big, no fetch) it goes back to preloading the way it\n// streamed before. A visitor saving data keeps the first frame only.\nconst MAX_BUFFERED_CLIP_BYTES = 100 * 1024 * 1024\n// The first bytes of a clip: its header and, for a fragmented clip, the index.\nconst CLIP_HEAD_BYTES = 65536\n// Streamed: the seconds kept around the scroll position \u2014 fetched in order of\n// need (the second on screen, then ahead, then behind) and let go beyond it. A\n// clip shorter than twice this ends up whole in the element, like before, only\n// scrubbable long before it has all arrived.\nconst STREAM_KEEP_SECONDS = 12\n// One request fetches consecutive missing fragments up to this much.\nconst STREAM_REQUEST_SECONDS = 2\nconst STREAM_REQUEST_BYTES = 6 * 1024 * 1024\n\n// The fragment index a scrub encode carries in its first bytes (ftyp, moov,\n// then a sidx naming every fragment): where each second of the clip is in the\n// file. Null for a file without one, which is then held whole.\nconst readClipIndex = (head) => {\n const view = new DataView(head)\n const boxType = (at) => String.fromCharCode(view.getUint8(at + 4), view.getUint8(at + 5), view.getUint8(at + 6), view.getUint8(at + 7))\n let at = 0\n let moovEnd = 0\n let sidx = null\n while (at + 8 <= head.byteLength) {\n const size = view.getUint32(at)\n const type = boxType(at)\n if (size < 8) {\n return null\n }\n if (type === 'moov') {\n moovEnd = at + size\n } else if (type === 'sidx') {\n sidx = { at: at, size: size }\n break\n } else if (type !== 'ftyp' && type !== 'free' && type !== 'skip') {\n break\n }\n at += size\n }\n if (!moovEnd || !sidx || sidx.at + sidx.size > head.byteLength || sidx.at < moovEnd) {\n return null\n }\n // the codec, from the avcC box inside moov: H.264, profile and level as the file says\n let codec = null\n const bytes = new Uint8Array(head)\n for (let i = 0; i < moovEnd - 8; i++) {\n if (bytes[i] === 0x61 && bytes[i + 1] === 0x76 && bytes[i + 2] === 0x63 && bytes[i + 3] === 0x43) {\n const hex = (value) => (value < 16 ? '0' : '') + value.toString(16).toUpperCase()\n codec = 'avc1.' + hex(bytes[i + 5]) + hex(bytes[i + 6]) + hex(bytes[i + 7])\n break\n }\n }\n if (!codec) {\n return null\n }\n const body = sidx.at + 8\n const version = view.getUint8(body)\n const timescale = view.getUint32(body + 8)\n let cursor = body + 12\n let firstOffset\n if (version === 0) {\n firstOffset = view.getUint32(cursor + 4)\n cursor += 8\n } else {\n firstOffset = Number(view.getBigUint64(cursor + 8))\n cursor += 16\n }\n const count = view.getUint16(cursor + 2)\n cursor += 4\n if (!timescale || !count) {\n return null\n }\n const fragments = []\n let start = sidx.at + sidx.size + firstOffset\n let time = 0\n for (let i = 0; i < count; i++) {\n const size = view.getUint32(cursor) & 0x7fffffff\n const duration = view.getUint32(cursor + 4) / timescale\n fragments.push({ start: start, end: start + size - 1, time: time, duration: duration })\n start += size\n time += duration\n cursor += 12\n }\n return { initEnd: moovEnd, codec: codec, fragments: fragments, duration: time }\n}\n\n// A copy of the streamed element, to take its place once it shows the frame on screen.\nconst copyOf = (video) => {\n const copy = video.cloneNode(false)\n copy.removeAttribute('src')\n copy.muted = true\n copy.style.opacity = '0'\n return copy\n}\n\n// Streams the clip by the second through MediaSource. Null where the browser\n// cannot (no MediaSource, or not for this codec) \u2014 the clip is then held whole.\n// Ranged requests go to the clip under a URL of their own: WebKit holds a second\n// request for a URL it is still downloading (the streamed element's) until that\n// one is done, which would delay the first second by the whole file.\nconst rangedUrl = (src) => src + (src.indexOf('?') >= 0 ? '&' : '?') + 'tq-stream'\n\nconst streamClip = (video, src, head, index, onSwap, signal, onFail) => {\n const MediaSourceCtor = window.MediaSource\n const mime = 'video/mp4; codecs=\"' + index.codec + '\"'\n if (!MediaSourceCtor || typeof MediaSourceCtor.isTypeSupported !== 'function' || !MediaSourceCtor.isTypeSupported(mime)) {\n return null\n }\n const fragments = index.fragments\n const source = new MediaSourceCtor()\n const objectUrl = URL.createObjectURL(source)\n const copy = copyOf(video)\n copy.setAttribute('data-clip-held', 'stream')\n let buffer = null\n let loaded = []\n let failed = 0\n let busy = false\n let inFlight = false\n let pausedUntil = 0\n let wanted = 0\n let swapped = false\n let stopped = false\n const pending = []\n const fragmentAt = (seconds) => {\n for (let i = 0; i < fragments.length; i++) {\n if (seconds < fragments[i].time + fragments[i].duration) {\n return i\n }\n }\n return fragments.length - 1\n }\n const takeOver = () => {\n if (stopped) {\n return\n }\n copy.style.opacity = ''\n video.style.display = 'none'\n video.removeAttribute('src')\n video.load()\n onSwap(copy)\n }\n const run = () => {\n if (stopped || !buffer || busy || buffer.updating || !pending.length) {\n return\n }\n const task = pending.shift()\n busy = true\n try {\n if (task.remove) {\n buffer.remove(task.from, task.to)\n } else {\n buffer.appendBuffer(task.data)\n }\n } catch (e) {\n busy = false\n if (task.remove) {\n run()\n return\n }\n // no room for another second: let every other second go, then try once more\n if (!task.retried) {\n task.retried = true\n letGo(true)\n pending.push(task)\n } else {\n for (let i = task.from; i <= task.to; i++) {\n loaded[i] = false\n }\n }\n run()\n }\n }\n const onUpdateEnd = () => {\n busy = false\n if (stopped) {\n return\n }\n if (!swapped && loaded[fragmentAt(wanted)]) {\n swapped = true\n copy.addEventListener('seeked', takeOver, { once: true })\n copy.currentTime = wanted\n }\n run()\n fetchNext()\n }\n const letGo = (everything) => {\n const keepFrom = everything ? wanted : wanted - STREAM_KEEP_SECONDS\n const keepTo = everything ? wanted : wanted + STREAM_KEEP_SECONDS\n let from = -1\n for (let i = 0; i <= fragments.length; i++) {\n const far = i < fragments.length && loaded[i] && (fragments[i].time + fragments[i].duration < keepFrom || fragments[i].time > keepTo)\n if (far && from < 0) {\n from = i\n }\n if (!far && from >= 0) {\n for (let k = from; k < i; k++) {\n loaded[k] = false\n }\n pending.push({ remove: true, from: fragments[from].time, to: fragments[i - 1].time + fragments[i - 1].duration })\n from = -1\n }\n }\n }\n const fetchNext = () => {\n if (stopped || !buffer || inFlight || Date.now() < pausedUntil) {\n return\n }\n const at = fragmentAt(wanted)\n // the second on screen first, then ahead, then behind, out to what is kept\n const order = [at]\n for (let k = 1; k < fragments.length; k++) {\n if (at + k < fragments.length && fragments[at + k].time - wanted <= STREAM_KEEP_SECONDS) {\n order.push(at + k)\n }\n if (\n at - k >= 0 &&\n wanted - (fragments[at - k].time + fragments[at - k].duration) <= STREAM_KEEP_SECONDS\n ) {\n order.push(at - k)\n }\n }\n let first = -1\n for (let i = 0; i < order.length; i++) {\n if (!loaded[order[i]]) {\n first = order[i]\n break\n }\n }\n if (first < 0) {\n return\n }\n let last = first\n while (\n last + 1 < fragments.length &&\n !loaded[last + 1] &&\n fragments[last + 1].end - fragments[first].start < STREAM_REQUEST_BYTES &&\n fragments[last + 1].time + fragments[last + 1].duration - fragments[first].time <= STREAM_REQUEST_SECONDS\n ) {\n last++\n }\n inFlight = true\n fetch(rangedUrl(src), { headers: { range: 'bytes=' + fragments[first].start + '-' + fragments[last].end }, signal: signal })\n .then((response) => (response.status === 206 ? response.arrayBuffer() : Promise.reject(new Error('no range'))))\n .then((data) => {\n inFlight = false\n if (stopped) {\n return\n }\n for (let i = first; i <= last; i++) {\n loaded[i] = true\n }\n letGo(false)\n pending.push({ data: data, from: first, to: last })\n run()\n })\n .catch(() => {\n inFlight = false\n if (stopped) {\n return\n }\n // a second that could not be fetched: hold the clip whole instead when\n // nothing has played yet, otherwise try again in a moment\n failed++\n pausedUntil = Date.now() + 2000\n if (!swapped && failed >= 2) {\n onFail()\n }\n })\n }\n source.addEventListener('sourceopen', () => {\n if (stopped) {\n return\n }\n try {\n buffer = source.addSourceBuffer(mime)\n } catch (e) {\n onFail()\n return\n }\n buffer.mode = 'segments'\n buffer.addEventListener('updateend', onUpdateEnd)\n buffer.addEventListener('error', onFail)\n // the clip's length comes from the index, not from a header written before the fragments\n source.duration = index.duration\n pending.push({ data: head.slice(0, index.initEnd), from: 0, to: -1 })\n run()\n fetchNext()\n }, { once: true })\n copy.src = objectUrl\n video.parentNode.insertBefore(copy, video.nextSibling)\n // The streamed element's own download is let go: the copy takes over long\n // before it would show a frame (a browser may fetch the whole file for the\n // element's metadata), and its bytes would only slow the seconds down.\n video.removeAttribute('src')\n video.load()\n return {\n want: (progress) => {\n wanted = clamp01(progress) * index.duration\n fetchNext()\n },\n stop: () => {\n stopped = true\n copy.remove()\n URL.revokeObjectURL(objectUrl)\n },\n }\n}\n\n// Holds the clip whole: fetched once (or already fetched) and played from memory.\nconst holdWholeClip = (video, src, onSwap, signal, prefetched, onFail) => {\n let stopped = false\n let objectUrl = null\n let copy = null\n const takeOver = () => {\n if (stopped) {\n return\n }\n copy.style.opacity = ''\n video.style.display = 'none'\n video.removeAttribute('src')\n video.load()\n onSwap(copy)\n }\n const hold = (blob) => {\n if (stopped) {\n return\n }\n objectUrl = URL.createObjectURL(blob)\n copy = copyOf(video)\n copy.setAttribute('data-clip-held', 'memory')\n copy.addEventListener('loadedmetadata', () => {\n copy.addEventListener('seeked', takeOver, { once: true })\n copy.currentTime = video.currentTime\n }, { once: true })\n copy.src = objectUrl\n video.parentNode.insertBefore(copy, video.nextSibling)\n }\n if (prefetched) {\n hold(prefetched)\n } else {\n fetch(src, signal ? { signal: signal } : undefined)\n .then((response) => {\n if (!response.ok || Number(response.headers.get('content-length')) > MAX_BUFFERED_CLIP_BYTES) {\n throw new Error('keep streaming')\n }\n return response.blob()\n })\n .then(hold)\n .catch(() => {\n if (!stopped) {\n onFail()\n }\n })\n }\n return {\n want: () => {},\n stop: () => {\n stopped = true\n if (copy) {\n copy.remove()\n }\n if (objectUrl) {\n URL.revokeObjectURL(objectUrl)\n }\n },\n }\n}\n\nconst holdClip = (host, video, src, onSwap) => {\n const noHold = { want: () => {}, stop: () => {} }\n const stream = () => {\n video.preload = 'auto'\n }\n if (navigator.connection && navigator.connection.saveData) {\n return noHold\n }\n if (!src || /^(blob|data):/.test(src) || typeof fetch !== 'function' || !window.URL || !URL.createObjectURL) {\n stream()\n return noHold\n }\n let stopped = false\n let observer = null\n let holder = null\n let released = false\n let wanted = 0\n const controller = typeof AbortController === 'function' ? new AbortController() : null\n const signal = controller ? controller.signal : undefined\n const swapped = (copy) => {\n released = true\n onSwap(copy)\n }\n const giveUp = () => {\n if (!stopped && !released) {\n stream()\n }\n }\n const whole = (prefetched) => {\n holder = holdWholeClip(video, src, swapped, signal, prefetched, giveUp)\n }\n // The first bytes tell which way the clip can be held. A host that will not\n // answer a ranged request (or its cross-origin permission) still serves the\n // whole file to a plain request, the way clips were held before.\n const load = () => {\n fetch(rangedUrl(src), { headers: { range: 'bytes=0-' + (CLIP_HEAD_BYTES - 1) }, signal: signal })\n .then((response) => {\n if (!response.ok) {\n throw new Error('keep streaming')\n }\n const total = response.status === 206\n ? Number((response.headers.get('content-range') || '').split('/')[1])\n : Number(response.headers.get('content-length'))\n return response.arrayBuffer().then((head) => ({ head: head, partial: response.status === 206, total: total }))\n })\n .then((answer) => {\n if (stopped) {\n return\n }\n // a host that answered with the whole file: it is already here\n if (!answer.partial) {\n if (answer.head.byteLength > MAX_BUFFERED_CLIP_BYTES) {\n throw new Error('too big')\n }\n whole(new Blob([answer.head], { type: 'video/mp4' }))\n return\n }\n const index = readClipIndex(answer.head)\n if (index && typeof MediaSource !== 'undefined') {\n holder = streamClip(video, src, answer.head, index, swapped, signal, () => {\n if (!stopped && !released) {\n holder.stop()\n whole(null)\n }\n })\n if (holder) {\n holder.want(wanted)\n return\n }\n }\n if (answer.total > MAX_BUFFERED_CLIP_BYTES) {\n throw new Error('too big')\n }\n whole(null)\n })\n .catch((error) => {\n if (stopped) {\n return\n }\n if (String(error && error.message) === 'too big') {\n giveUp()\n return\n }\n whole(null)\n })\n }\n // A scene about a screen away loads at once \u2014 when it is there from the\n // start, when it comes near, or at the first scrub request that finds it\n // near (an observer's first word waits for a rendering update, which a page\n // still loading its media may not give for a while).\n let started = false\n const near = () => {\n const box = host.getBoundingClientRect()\n const screen = window.innerHeight || 1\n return box.bottom > -screen && box.top < screen * 2\n }\n const start = () => {\n if (started) {\n return\n }\n started = true\n if (observer) {\n observer.disconnect()\n observer = null\n }\n load()\n }\n if (near()) {\n start()\n } else if (typeof IntersectionObserver === 'function') {\n observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n start()\n }\n }, { rootMargin: '100% 0px' })\n observer.observe(host)\n } else {\n start()\n }\n return {\n // where the scrub is, as clip progress (0-1, known before any metadata): a\n // streamed clip keeps the seconds around it ready\n want: (progress) => {\n wanted = progress\n if (!started && near()) {\n start()\n }\n if (holder) {\n holder.want(progress)\n }\n },\n stop: () => {\n stopped = true\n if (observer) {\n observer.disconnect()\n }\n if (controller) {\n controller.abort()\n }\n if (holder) {\n holder.stop()\n }\n video.style.display = ''\n if (video.isConnected && !video.getAttribute('src')) {\n video.setAttribute('src', src)\n }\n },\n }\n}"; };
|
|
12
|
+
exports.scrollVideoEngineSource = scrollVideoEngineSource;
|
|
13
|
+
//# sourceMappingURL=scroll-video-engine-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-video-engine-source.js","sourceRoot":"","sources":["../../../src/motion-runtime/scroll-video-engine-source.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACI,IAAM,uBAAuB,GAClC,cAAc,OAAA,mqmBAskBd,EAtkBc,CAskBd,CAAA;AAvkBW,QAAA,uBAAuB,2BAukBlC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/page-transition/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA"}
|