@teleporthq/teleport-shared 0.43.70 → 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/dist/cjs/index.d.ts +5 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -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/esm/index.d.ts +5 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -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/package.json +3 -3
- package/src/index.ts +8 -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
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scroll-scene lane engine as SOURCE TEXT: the constants, the lane presets
|
|
3
|
+
* and the functions that parse a `data-scroll-bind`, read a lane at a progress
|
|
4
|
+
* and write it onto an element. It is plain DOM JavaScript with no framework in
|
|
5
|
+
* it, which is why two runtimes can share it word for word:
|
|
6
|
+
*
|
|
7
|
+
* - the Next export wraps it in the TqScrollScene React component, where
|
|
8
|
+
* framer-motion supplies the scroll progress;
|
|
9
|
+
* - the HTML export wraps it in a small vanilla controller, where a scroll
|
|
10
|
+
* listener supplies the same progress.
|
|
11
|
+
*
|
|
12
|
+
* One copy means a new lane property or preset reaches both exports at once.
|
|
13
|
+
* The text mirrors the canvas renderer's scroll-scene-lanes module, so the
|
|
14
|
+
* canvas, the Next site and the HTML site move identically. (No backticks in
|
|
15
|
+
* the engine's own comments: it is a template literal.)
|
|
16
|
+
*/
|
|
17
|
+
import { settledMomentForLanes } from './moment'
|
|
18
|
+
import { activeChapterIndex } from './chapter-index'
|
|
19
|
+
import { passedScenePoints, scenePointList, scenePointRank } from './points'
|
|
20
|
+
import { unclipStickyAncestors } from './unclip'
|
|
21
|
+
|
|
22
|
+
export const scrollSceneEngineSource = (): string => `const SCROLL_BIND_ATTR = 'data-scroll-bind'
|
|
23
|
+
// A chapter coming on stage is announced on the chapter element (bubbling), so
|
|
24
|
+
// a workflow trigger bound to the chapter can listen without knowing the scene.
|
|
25
|
+
const CHAPTER_REACHED_EVENT = 'tq-chapter-reached'
|
|
26
|
+
const CHAPTER_ACTIVE_ATTR = 'data-chapter-active'
|
|
27
|
+
const CHAPTER_COUNT_ATTR = 'data-chapter-count'
|
|
28
|
+
// Scrolling DOWN past a quarter / halfway / three quarters / the end of the
|
|
29
|
+
// scene is announced on the track (the element with the scene's id), bubbling;
|
|
30
|
+
// the furthest point passed so far is stamped for listeners that attach later.
|
|
31
|
+
const SCENE_POINT_EVENT = 'tq-scene-point-passed'
|
|
32
|
+
const SCENE_POINT_ATTR = 'data-scene-point'
|
|
33
|
+
|
|
34
|
+
const LANE_PROPS = [
|
|
35
|
+
'x',
|
|
36
|
+
'y',
|
|
37
|
+
'scale',
|
|
38
|
+
'rotate',
|
|
39
|
+
'rotate-x',
|
|
40
|
+
'rotate-y',
|
|
41
|
+
'opacity',
|
|
42
|
+
'blur',
|
|
43
|
+
'grayscale',
|
|
44
|
+
'saturate',
|
|
45
|
+
'brightness',
|
|
46
|
+
'clip',
|
|
47
|
+
'clip-y',
|
|
48
|
+
'count',
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
// Mirrors SCROLL_COUNT_ATTR / SCROLL_COUNT_CSS in the editor's lane contract.
|
|
52
|
+
const COUNT_ATTR = 'data-scroll-count'
|
|
53
|
+
const HIDDEN_ATTR = 'data-scene-hidden'
|
|
54
|
+
const HIDDEN_CSS =
|
|
55
|
+
'[data-scene-hidden], [data-scene-hidden] * { pointer-events: none !important; }'
|
|
56
|
+
const COUNT_CSS =
|
|
57
|
+
'[data-scroll-count]::before { counter-reset: tq-count var(--tq-count, 0); content: counter(tq-count); }'
|
|
58
|
+
const clampPercent = (value) => Math.min(100, Math.max(0, Math.round(value * 100) / 100))
|
|
59
|
+
|
|
60
|
+
const LANE_PRESETS = {
|
|
61
|
+
'depth-1': [{ prop: 'y', at: [0, 1], values: [40, -40] }],
|
|
62
|
+
'depth-2': [{ prop: 'y', at: [0, 1], values: [80, -80] }],
|
|
63
|
+
'depth-3': [{ prop: 'y', at: [0, 1], values: [140, -140] }],
|
|
64
|
+
'fade-window': [{ prop: 'opacity', at: [0, 0.15, 0.85, 1], values: [0, 1, 1, 0] }],
|
|
65
|
+
'rail-x': [{ prop: 'x', at: [0, 1], values: [0, -100], unit: '%' }],
|
|
66
|
+
'zoom-through': [{ prop: 'scale', at: [0, 1], values: [0.85, 1.1] }],
|
|
67
|
+
'unblur-in': [{ prop: 'blur', at: [0, 0.3], values: [10, 0] }],
|
|
68
|
+
'rise-in': [
|
|
69
|
+
{ prop: 'y', at: [0, 0.35], values: [70, 0] },
|
|
70
|
+
{ prop: 'opacity', at: [0, 0.3], values: [0, 1] },
|
|
71
|
+
],
|
|
72
|
+
'ghost-out': [
|
|
73
|
+
{ prop: 'opacity', at: [0.7, 1], values: [1, 0] },
|
|
74
|
+
{ prop: 'blur', at: [0.7, 1], values: [0, 8] },
|
|
75
|
+
],
|
|
76
|
+
'color-in': [{ prop: 'grayscale', at: [0, 0.45], values: [1, 0] }],
|
|
77
|
+
'sun-up': [{ prop: 'brightness', at: [0, 0.4], values: [0.35, 1] }],
|
|
78
|
+
'fade-to-mono': [
|
|
79
|
+
{ prop: 'grayscale', at: [0.6, 1], values: [0, 1] },
|
|
80
|
+
{ prop: 'saturate', at: [0.6, 1], values: [1, 0.6] },
|
|
81
|
+
],
|
|
82
|
+
'crash-zoom': [
|
|
83
|
+
{ prop: 'scale', at: [0, 0.25], values: [1.6, 1] },
|
|
84
|
+
{ prop: 'opacity', at: [0, 0.15], values: [0, 1] },
|
|
85
|
+
],
|
|
86
|
+
'tilt-reveal': [
|
|
87
|
+
{ prop: 'rotate-x', at: [0, 0.4], values: [35, 0] },
|
|
88
|
+
{ prop: 'opacity', at: [0, 0.3], values: [0, 1] },
|
|
89
|
+
],
|
|
90
|
+
'spin-in': [
|
|
91
|
+
{ prop: 'rotate', at: [0, 0.35], values: [-90, 0] },
|
|
92
|
+
{ prop: 'opacity', at: [0, 0.25], values: [0, 1] },
|
|
93
|
+
],
|
|
94
|
+
breathe: [{ prop: 'scale', at: [0, 0.5, 1], values: [0.98, 1.03, 0.98] }],
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const isValidLane = (lane) => {
|
|
98
|
+
if (!lane || typeof lane !== 'object' || !LANE_PROPS.includes(lane.prop)) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
if (!Array.isArray(lane.at) || !Array.isArray(lane.values)) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
if (lane.at.length < 2 || lane.at.length !== lane.values.length) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
const numbers = (list) => list.every((entry) => typeof entry === 'number' && isFinite(entry))
|
|
108
|
+
if (!numbers(lane.at) || !numbers(lane.values)) {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
for (let index = 1; index < lane.at.length; index++) {
|
|
112
|
+
if (lane.at[index] < lane.at[index - 1]) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return lane.unit === undefined || lane.unit === 'px' || lane.unit === '%' || lane.unit === 'vw'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
${settledMomentForLanes.toString()}
|
|
120
|
+
|
|
121
|
+
${activeChapterIndex.toString()}
|
|
122
|
+
|
|
123
|
+
${scenePointList.toString()}
|
|
124
|
+
|
|
125
|
+
${passedScenePoints.toString()}
|
|
126
|
+
|
|
127
|
+
${scenePointRank.toString()}
|
|
128
|
+
|
|
129
|
+
${unclipStickyAncestors.toString()}
|
|
130
|
+
|
|
131
|
+
const parseScrollBind = (value) => {
|
|
132
|
+
const raw = String(value || '').trim()
|
|
133
|
+
if (!raw) {
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
136
|
+
if (LANE_PRESETS[raw]) {
|
|
137
|
+
return LANE_PRESETS[raw].map((lane) => ({ ...lane }))
|
|
138
|
+
}
|
|
139
|
+
if (!raw.startsWith('[')) {
|
|
140
|
+
return []
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(raw)
|
|
144
|
+
return Array.isArray(parsed) ? parsed.filter(isValidLane) : []
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return []
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const laneValueAt = (lane, p) => {
|
|
151
|
+
const at = lane.at
|
|
152
|
+
const values = lane.values
|
|
153
|
+
if (p <= at[0]) {
|
|
154
|
+
return values[0]
|
|
155
|
+
}
|
|
156
|
+
if (p >= at[at.length - 1]) {
|
|
157
|
+
return values[values.length - 1]
|
|
158
|
+
}
|
|
159
|
+
for (let index = 1; index < at.length; index++) {
|
|
160
|
+
if (p <= at[index]) {
|
|
161
|
+
const span = at[index] - at[index - 1]
|
|
162
|
+
const local = span === 0 ? 1 : (p - at[index - 1]) / span
|
|
163
|
+
return values[index - 1] + (values[index] - values[index - 1]) * local
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return values[values.length - 1]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const FILTER_PROP_ORDER = ['blur', 'grayscale', 'saturate', 'brightness']
|
|
170
|
+
|
|
171
|
+
// Motion lanes write the INDIVIDUAL transform properties (translate / scale /
|
|
172
|
+
// rotate) and never the transform property itself: the browser composes them
|
|
173
|
+
// with whatever the author put in transform (a translateX(-50%) centering, a
|
|
174
|
+
// tilt), so the scene never clobbers the user's own layout. 3D lanes use the
|
|
175
|
+
// axis form of rotate; the stage supplies the vanishing point. Mirrors the
|
|
176
|
+
// canvas. (No backticks in this file's comments — it is a template literal.)
|
|
177
|
+
const applyLanesAt = (element, lanes, p) => {
|
|
178
|
+
const filterByProp = {}
|
|
179
|
+
let translateX
|
|
180
|
+
let translateY
|
|
181
|
+
let rotate
|
|
182
|
+
let scale
|
|
183
|
+
for (const lane of lanes) {
|
|
184
|
+
const value = laneValueAt(lane, p)
|
|
185
|
+
switch (lane.prop) {
|
|
186
|
+
case 'x':
|
|
187
|
+
translateX = value + (lane.unit || 'px')
|
|
188
|
+
break
|
|
189
|
+
case 'y':
|
|
190
|
+
translateY = value + (lane.unit || 'px')
|
|
191
|
+
break
|
|
192
|
+
case 'scale':
|
|
193
|
+
scale = String(value)
|
|
194
|
+
break
|
|
195
|
+
case 'rotate':
|
|
196
|
+
rotate = rotate === undefined ? value + 'deg' : rotate
|
|
197
|
+
break
|
|
198
|
+
case 'rotate-x':
|
|
199
|
+
rotate = 'x ' + value + 'deg'
|
|
200
|
+
break
|
|
201
|
+
case 'rotate-y':
|
|
202
|
+
rotate = 'y ' + value + 'deg'
|
|
203
|
+
break
|
|
204
|
+
case 'opacity':
|
|
205
|
+
element.style.opacity = String(value)
|
|
206
|
+
// A faded-out element must not swallow clicks (stacked chapters: the
|
|
207
|
+
// invisible top one would take every tap meant for the visible story).
|
|
208
|
+
// Attribute + CSS rule, never inline pointer-events, so lanes cannot
|
|
209
|
+
// overwrite a pointer-events the author set. Mirrors the editor.
|
|
210
|
+
if (value <= 0.02) {
|
|
211
|
+
element.setAttribute(HIDDEN_ATTR, '')
|
|
212
|
+
} else {
|
|
213
|
+
element.removeAttribute(HIDDEN_ATTR)
|
|
214
|
+
}
|
|
215
|
+
break
|
|
216
|
+
case 'blur':
|
|
217
|
+
filterByProp.blur = 'blur(' + value + 'px)'
|
|
218
|
+
break
|
|
219
|
+
case 'grayscale':
|
|
220
|
+
filterByProp.grayscale = 'grayscale(' + value + ')'
|
|
221
|
+
break
|
|
222
|
+
case 'saturate':
|
|
223
|
+
filterByProp.saturate = 'saturate(' + value + ')'
|
|
224
|
+
break
|
|
225
|
+
case 'brightness':
|
|
226
|
+
filterByProp.brightness = 'brightness(' + value + ')'
|
|
227
|
+
break
|
|
228
|
+
case 'clip':
|
|
229
|
+
element.style.clipPath = 'inset(0 ' + clampPercent(100 - value) + '% 0 0)'
|
|
230
|
+
break
|
|
231
|
+
case 'clip-y':
|
|
232
|
+
element.style.clipPath = 'inset(0 0 ' + clampPercent(100 - value) + '% 0)'
|
|
233
|
+
break
|
|
234
|
+
case 'count':
|
|
235
|
+
element.style.setProperty('--tq-count', String(Math.round(value)))
|
|
236
|
+
if (!element.hasAttribute(COUNT_ATTR)) {
|
|
237
|
+
element.setAttribute(COUNT_ATTR, '')
|
|
238
|
+
}
|
|
239
|
+
break
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (translateX !== undefined || translateY !== undefined) {
|
|
243
|
+
element.style.translate = (translateX || '0px') + ' ' + (translateY || '0px')
|
|
244
|
+
}
|
|
245
|
+
if (scale !== undefined) {
|
|
246
|
+
element.style.scale = scale
|
|
247
|
+
}
|
|
248
|
+
if (rotate !== undefined) {
|
|
249
|
+
element.style.rotate = rotate
|
|
250
|
+
}
|
|
251
|
+
const filterParts = FILTER_PROP_ORDER.map((prop) => filterByProp[prop]).filter(Boolean)
|
|
252
|
+
if (filterParts.length > 0) {
|
|
253
|
+
element.style.filter = filterParts.join(' ')
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// A scene set to keep the story for visitors who ask for less motion
|
|
258
|
+
// (scene-reduced-motion "story"): the scroll still drives the scene, chapters
|
|
259
|
+
// still come and go and every text still appears in turn, but nothing moves.
|
|
260
|
+
// Slides, zooms, rotations and wipes are dropped, so each element stays where
|
|
261
|
+
// the layout puts it; fades and filters still follow the scroll; a counting
|
|
262
|
+
// number shows the figure it counts to. Mirrors the editor's lane contract.
|
|
263
|
+
const MOVEMENT_PROPS = ['x', 'y', 'scale', 'rotate', 'rotate-x', 'rotate-y', 'clip', 'clip-y']
|
|
264
|
+
|
|
265
|
+
const storyLanes = (lanes) =>
|
|
266
|
+
lanes
|
|
267
|
+
.filter((lane) => !MOVEMENT_PROPS.includes(lane.prop))
|
|
268
|
+
.map((lane) => {
|
|
269
|
+
if (lane.prop !== 'count') {
|
|
270
|
+
return lane
|
|
271
|
+
}
|
|
272
|
+
const figure = lane.values[lane.values.length - 1]
|
|
273
|
+
return { prop: 'count', at: [0, 1], values: [figure, figure] }
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// What a moving lane painted, taken off again when the scene switches to the
|
|
277
|
+
// story without movement while the page is open.
|
|
278
|
+
const clearMovement = (element, lanes) => {
|
|
279
|
+
for (const lane of lanes) {
|
|
280
|
+
if (lane.prop === 'x' || lane.prop === 'y') {
|
|
281
|
+
element.style.translate = ''
|
|
282
|
+
} else if (lane.prop === 'scale') {
|
|
283
|
+
element.style.scale = ''
|
|
284
|
+
} else if (lane.prop === 'rotate' || lane.prop === 'rotate-x' || lane.prop === 'rotate-y') {
|
|
285
|
+
element.style.rotate = ''
|
|
286
|
+
} else if (lane.prop === 'clip' || lane.prop === 'clip-y') {
|
|
287
|
+
element.style.clipPath = ''
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// A row wider than the stage that travelled sideways with the scroll (a
|
|
293
|
+
// rail) would show only its first cards once the movement is dropped, so it
|
|
294
|
+
// becomes a row the visitor swipes: every card stays reachable.
|
|
295
|
+
const SWIPE_ATTR = 'data-scene-swipe'
|
|
296
|
+
const letRowsSwipe = (bound, stage) => {
|
|
297
|
+
if (!stage) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const stageWidth = stage.getBoundingClientRect().width
|
|
301
|
+
for (const child of bound) {
|
|
302
|
+
const travels = child.lanes.some((lane) => lane.prop === 'x')
|
|
303
|
+
if (!travels || child.element.getBoundingClientRect().width <= stageWidth + 1) {
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
child.element.style.maxWidth = '100%'
|
|
307
|
+
child.element.style.overflowX = 'auto'
|
|
308
|
+
child.element.setAttribute(SWIPE_ATTR, '')
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const collectBound = (track) => {
|
|
313
|
+
const bound = []
|
|
314
|
+
track.querySelectorAll('[' + SCROLL_BIND_ATTR + ']').forEach((element) => {
|
|
315
|
+
const lanes = parseScrollBind(element.getAttribute(SCROLL_BIND_ATTR))
|
|
316
|
+
if (lanes.length > 0) {
|
|
317
|
+
bound.push({ element, lanes })
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
return bound
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const normalizeSceneLength = (value) => {
|
|
324
|
+
const match = /^(\\d{2,4})vh$/.exec(String(value || '').trim())
|
|
325
|
+
if (!match) {
|
|
326
|
+
return '300vh'
|
|
327
|
+
}
|
|
328
|
+
// Hard safety bound only — the editor owns the story-sized soft cap.
|
|
329
|
+
return Math.min(4000, Math.max(100, Number(match[1]))) + 'vh'
|
|
330
|
+
}`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { settledMomentForLanes, MomentLane } from './moment'
|
|
2
|
+
export { activeChapterIndex } from './chapter-index'
|
|
3
|
+
export { passedScenePoints, scenePointList, scenePointRank } from './points'
|
|
4
|
+
export { unclipStickyAncestors } from './unclip'
|
|
5
|
+
export { scrollSceneEngineSource as engineSource } from './engine-source'
|
|
6
|
+
export {
|
|
7
|
+
announcementsSource,
|
|
8
|
+
restackBodySource,
|
|
9
|
+
chapterHelpersSource,
|
|
10
|
+
anchorNavigationBodySource,
|
|
11
|
+
gentleSnapBodySource,
|
|
12
|
+
} from './controller-source'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The progress where a chapter is fully ON STAGE — every lane has arrived
|
|
3
|
+
* and none has started leaving — the centre of that settled span, in the
|
|
4
|
+
* lanes' own (absolute) progress space. The gentle snap and anchor
|
|
5
|
+
* navigation aim here instead of the window's midpoint, which catches the
|
|
6
|
+
* last beat still rising (a half-visible line, a half-arrived card).
|
|
7
|
+
* Null when nothing settles (a chapter made only of through-motion, e.g. a
|
|
8
|
+
* crossing line — its best moment is the window's middle, the caller's
|
|
9
|
+
* fallback).
|
|
10
|
+
*
|
|
11
|
+
* MIRRORED VERBATIM from teleport-gui's chapter-window.ts (the editor draws
|
|
12
|
+
* the same moment on the timeline). Plain JS inside: this function's SOURCE
|
|
13
|
+
* is injected into the generated TqScrollScene runtime via toString(), so no
|
|
14
|
+
* TypeScript-only runtime constructs and no template literals.
|
|
15
|
+
*/
|
|
16
|
+
export interface MomentLane {
|
|
17
|
+
at: number[]
|
|
18
|
+
values: number[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function settledMomentForLanes(lanes: MomentLane[]): number | null {
|
|
22
|
+
let arrive = -Infinity
|
|
23
|
+
let leave = Infinity
|
|
24
|
+
let lastStop = -Infinity
|
|
25
|
+
let found = false
|
|
26
|
+
for (const lane of lanes) {
|
|
27
|
+
if (lane.at.length < 3) {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
let settledAt: number | null = null
|
|
31
|
+
for (let index = 1; index < lane.values.length - 1; index++) {
|
|
32
|
+
if (lane.values[index] === lane.values[index + 1] && lane.values[index] !== lane.values[0]) {
|
|
33
|
+
settledAt = index
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (settledAt === null && lane.values[0] === lane.values[1]) {
|
|
38
|
+
settledAt = 0
|
|
39
|
+
}
|
|
40
|
+
if (settledAt === null) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
found = true
|
|
44
|
+
arrive = Math.max(arrive, lane.at[settledAt])
|
|
45
|
+
lastStop = Math.max(lastStop, lane.at[lane.at.length - 1])
|
|
46
|
+
const settledValue = lane.values[settledAt]
|
|
47
|
+
for (let index = settledAt + 1; index < lane.values.length; index++) {
|
|
48
|
+
if (lane.values[index] !== settledValue) {
|
|
49
|
+
leave = Math.min(leave, lane.at[index - 1])
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!found) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
if (leave === Infinity) {
|
|
58
|
+
leave = lastStop
|
|
59
|
+
}
|
|
60
|
+
if (leave < arrive) {
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
return Math.round(((arrive + leave) / 2) * 10000) / 10000
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "scrolled past a point" contract of a published Scroll Scene: the four
|
|
3
|
+
* plain points in scroll order, which of them a move from one progress to
|
|
4
|
+
* another passes DOWNWARD, and the rank of a point for late listeners. The
|
|
5
|
+
* scene announces every passed point as a `tq-scene-point-passed` DOM event
|
|
6
|
+
* on its track (the element carrying the scene's id) and stamps the furthest
|
|
7
|
+
* point passed so far as `data-scene-point`.
|
|
8
|
+
*
|
|
9
|
+
* Plain JS inside: these functions' SOURCE is injected into the generated
|
|
10
|
+
* runtime via toString(), so no TypeScript-only runtime constructs, no
|
|
11
|
+
* template literals and no reference to anything outside the function. The
|
|
12
|
+
* list mirrors SCROLL_POINT_PROGRESS in teleport-plugin-next-workflows
|
|
13
|
+
* (pinned by a test).
|
|
14
|
+
*/
|
|
15
|
+
export function scenePointList(): Array<{ key: string; at: number }> {
|
|
16
|
+
return [
|
|
17
|
+
{ key: 'quarter', at: 0.25 },
|
|
18
|
+
{ key: 'half', at: 0.5 },
|
|
19
|
+
{ key: 'three-quarters', at: 0.75 },
|
|
20
|
+
{ key: 'end', at: 1 },
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Points passed going DOWN from one progress to another, in scroll order; none when going up. */
|
|
25
|
+
export function passedScenePoints(from: number, to: number): string[] {
|
|
26
|
+
const passed = []
|
|
27
|
+
const points = scenePointList()
|
|
28
|
+
for (let index = 0; index < points.length; index++) {
|
|
29
|
+
const point = points[index]
|
|
30
|
+
if (point.at > from + 0.000001 && point.at <= to + 0.000001) {
|
|
31
|
+
passed.push(point.key)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return passed
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 1-based rank of a point in scroll order, 0 for anything else. */
|
|
38
|
+
export function scenePointRank(key: string): number {
|
|
39
|
+
const points = scenePointList()
|
|
40
|
+
for (let index = 0; index < points.length; index++) {
|
|
41
|
+
if (points[index].key === key) {
|
|
42
|
+
return index + 1
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep a pinned scene's stage pinnable: an ancestor with `overflow: hidden`
|
|
3
|
+
* (either axis) turns position:sticky off, silently. Authors and the AI write
|
|
4
|
+
* it for good reasons (a horizontal rail must clip its overhang — run
|
|
5
|
+
* e7f332df wrote `section.rail-scene { overflow: hidden }` around the
|
|
6
|
+
* homepage's story and the stage scrolled straight through). `overflow: clip`
|
|
7
|
+
* keeps the clipping and, unlike hidden, does not make the element a scroll
|
|
8
|
+
* container, so the stage sticks again. Ancestors that really scroll (auto /
|
|
9
|
+
* scroll) are left alone: sticky then rightly binds to that container.
|
|
10
|
+
*
|
|
11
|
+
* Injected into the generated widget via toString — no imports, no template
|
|
12
|
+
* literals (the generated project transpiles it).
|
|
13
|
+
*/
|
|
14
|
+
export function unclipStickyAncestors(
|
|
15
|
+
track: Element,
|
|
16
|
+
getStyle: (element: Element) => CSSStyleDeclaration
|
|
17
|
+
): string[] {
|
|
18
|
+
const unclipped: string[] = []
|
|
19
|
+
let element = track.parentElement
|
|
20
|
+
while (element && element !== element.ownerDocument.documentElement) {
|
|
21
|
+
const style = getStyle(element)
|
|
22
|
+
const both = style.overflow === 'hidden'
|
|
23
|
+
const axes: string[] = []
|
|
24
|
+
if (both || style.overflowX === 'hidden') {
|
|
25
|
+
axes.push('x')
|
|
26
|
+
}
|
|
27
|
+
if (both || style.overflowY === 'hidden') {
|
|
28
|
+
axes.push('y')
|
|
29
|
+
}
|
|
30
|
+
if (axes.length > 0) {
|
|
31
|
+
const host = element as HTMLElement
|
|
32
|
+
for (let i = 0; i < axes.length; i++) {
|
|
33
|
+
host.style.setProperty('overflow-' + axes[i], 'clip')
|
|
34
|
+
}
|
|
35
|
+
host.setAttribute('data-scene-unclipped', axes.join(''))
|
|
36
|
+
unclipped.push(
|
|
37
|
+
host.tagName.toLowerCase() +
|
|
38
|
+
(host.className ? '.' + String(host.className).split(' ')[0] : '')
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
element = element.parentElement
|
|
42
|
+
}
|
|
43
|
+
return unclipped
|
|
44
|
+
}
|