@linear_non/stellar-kit 2.1.21 → 3.0.0
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/classes/Manager.js +50 -3
- package/core/Application.js +299 -0
- package/core/PageEngine.js +221 -0
- package/core/index.js +8 -0
- package/events/Emitter.js +3 -2
- package/events/Raf.js +49 -36
- package/events/Resize.js +22 -13
- package/events/Scroll.js +38 -19
- package/events/VirtualScroll.js +1 -1
- package/index.js +5 -1
- package/kitStore.js +9 -6
- package/package.json +6 -3
- package/plugins/Grid.js +1 -1
- package/utils/debug.js +64 -0
- package/utils/index.js +1 -0
- package/utils/listener.js +19 -3
- package/utils/math.js +30 -10
- package/utils/offset.js +22 -9
- package/utils/selector.js +35 -5
package/classes/Manager.js
CHANGED
|
@@ -1,26 +1,63 @@
|
|
|
1
|
-
// Manager.js
|
|
2
1
|
import { qsa } from "../utils"
|
|
3
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("./Component").default} BaseComponent
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Component life cycle manager.
|
|
9
|
+
*
|
|
10
|
+
* @typedef {Object} ComponentDefinition
|
|
11
|
+
* @property {string} name
|
|
12
|
+
* @property {new (options: any) => BaseComponent} instance
|
|
13
|
+
* @property {any} [data]
|
|
14
|
+
*/
|
|
4
15
|
export default class Manager {
|
|
16
|
+
/**
|
|
17
|
+
* @param {{ data?: any }} [options]
|
|
18
|
+
*/
|
|
5
19
|
constructor({ data = {} } = {}) {
|
|
20
|
+
/** @type {{ name: string; component: BaseComponent }[]} */
|
|
6
21
|
this.components = [] // { name, component }
|
|
22
|
+
|
|
23
|
+
/** @type {any} */
|
|
7
24
|
this.data = data
|
|
8
25
|
}
|
|
9
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Get all components by name.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} name
|
|
31
|
+
* @returns {BaseComponent[]}
|
|
32
|
+
*/
|
|
10
33
|
getComponents(name) {
|
|
11
34
|
return this.components.filter(c => c.name === name).map(c => c.component)
|
|
12
35
|
}
|
|
13
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Get a single component by name and index.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} name
|
|
41
|
+
* @param {number} [index=0]
|
|
42
|
+
* @returns {BaseComponent|null}
|
|
43
|
+
*/
|
|
14
44
|
getComponent(name, index = 0) {
|
|
15
45
|
return this.getComponents(name)[index] || null
|
|
16
46
|
}
|
|
17
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Add components based on definition.
|
|
50
|
+
*
|
|
51
|
+
* @param {ComponentDefinition} def
|
|
52
|
+
* @param {any} [renderer=null]
|
|
53
|
+
* @returns {void}
|
|
54
|
+
*/
|
|
18
55
|
addComponent(def, renderer = null) {
|
|
19
56
|
if (!def || !def.instance || !def.name) return
|
|
20
57
|
|
|
21
58
|
const { name, instance, data = null } = def
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
/** @type {string} */
|
|
24
61
|
let selector
|
|
25
62
|
if (name.startsWith(".") || name.startsWith("[")) {
|
|
26
63
|
selector = name
|
|
@@ -49,7 +86,14 @@ export default class Manager {
|
|
|
49
86
|
})
|
|
50
87
|
}
|
|
51
88
|
|
|
52
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Call a method on all components.
|
|
91
|
+
*
|
|
92
|
+
* @param {keyof BaseComponent} method
|
|
93
|
+
* @param {...any} args
|
|
94
|
+
* @returns {void}
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
53
97
|
_call(method, ...args) {
|
|
54
98
|
this.components.forEach(entry => {
|
|
55
99
|
const c = entry.component
|
|
@@ -70,6 +114,9 @@ export default class Manager {
|
|
|
70
114
|
this._call("animateOut")
|
|
71
115
|
}
|
|
72
116
|
|
|
117
|
+
/**
|
|
118
|
+
* @param {any} obj
|
|
119
|
+
*/
|
|
73
120
|
tick(obj) {
|
|
74
121
|
this._call("tick", obj)
|
|
75
122
|
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// Application.js
|
|
2
|
+
import { kitStore } from "../kitStore.js"
|
|
3
|
+
|
|
4
|
+
// Events
|
|
5
|
+
import { emitter, EVENTS, Scroll } from "../events/index.js"
|
|
6
|
+
import Resize from "../events/Resize.js"
|
|
7
|
+
import Mouse from "../events/Mouse.js"
|
|
8
|
+
import Raf from "../events/Raf.js"
|
|
9
|
+
|
|
10
|
+
// Libraries
|
|
11
|
+
import { ScrollTrigger } from "../libraries/gsap/index.js"
|
|
12
|
+
|
|
13
|
+
// Utils
|
|
14
|
+
import { sniffer } from "../utils/sniffer.js"
|
|
15
|
+
import { qs } from "../utils/selector.js"
|
|
16
|
+
import { Debug } from "../utils/debug.js"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Core lifecycle manager for all global systems:
|
|
20
|
+
* - Resize
|
|
21
|
+
* - Mouse
|
|
22
|
+
* - Scroll
|
|
23
|
+
* - Raf (ticker)
|
|
24
|
+
*
|
|
25
|
+
* Responsible for:
|
|
26
|
+
* - Store initialization
|
|
27
|
+
* - System initialization and activation
|
|
28
|
+
* - Router integration
|
|
29
|
+
* - View transitions
|
|
30
|
+
* - DOM readiness handling
|
|
31
|
+
*/
|
|
32
|
+
export class ApplicationManager {
|
|
33
|
+
constructor() {
|
|
34
|
+
/**
|
|
35
|
+
* Whether the application completed initialization.
|
|
36
|
+
* @type {boolean}
|
|
37
|
+
*/
|
|
38
|
+
this.initialized = false
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Debug mode state.
|
|
42
|
+
* @type {boolean}
|
|
43
|
+
*/
|
|
44
|
+
this.isDebug = false
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Global system instances (initialized in _initSystems).
|
|
48
|
+
* @type {{
|
|
49
|
+
* resize: Resize|null,
|
|
50
|
+
* mouse: Mouse|null,
|
|
51
|
+
* scroll: Scroll|null,
|
|
52
|
+
* raf: Raf|null
|
|
53
|
+
* }}
|
|
54
|
+
*/
|
|
55
|
+
this.systems = {
|
|
56
|
+
resize: null,
|
|
57
|
+
mouse: null,
|
|
58
|
+
scroll: null,
|
|
59
|
+
raf: null,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Enable or disable debug logging.
|
|
65
|
+
* @param {boolean} [state=true]
|
|
66
|
+
* @returns {ApplicationManager}
|
|
67
|
+
*/
|
|
68
|
+
debug(state = true) {
|
|
69
|
+
Debug[state ? "enable" : "disable"]()
|
|
70
|
+
return this
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initialize internal state store and environment flags.
|
|
75
|
+
* @param {{
|
|
76
|
+
* url: URL|string,
|
|
77
|
+
* page?: { element?: string, container?: string },
|
|
78
|
+
* load?: string,
|
|
79
|
+
* dim?: { d?: number, m?: number }
|
|
80
|
+
* }} config
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
_initStore({ url, page, load, dim }) {
|
|
84
|
+
const { element, container } = page || {}
|
|
85
|
+
const isHomePage = url.pathname === "/" || url === "/"
|
|
86
|
+
|
|
87
|
+
kitStore.pageContent = container ? qs(container) : null
|
|
88
|
+
kitStore.currentPage = element ? qs(element) : null
|
|
89
|
+
|
|
90
|
+
kitStore.load = load ? qs(load) : null
|
|
91
|
+
kitStore.currentURL = url ?? null
|
|
92
|
+
kitStore.flags.isHomePage = Boolean(isHomePage)
|
|
93
|
+
|
|
94
|
+
if (dim) {
|
|
95
|
+
kitStore.sizes.d = dim.d ?? kitStore.sizes.d
|
|
96
|
+
kitStore.sizes.m = dim.m ?? kitStore.sizes.m
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._setSizes(kitStore.sizes.d, kitStore.sizes.m)
|
|
100
|
+
|
|
101
|
+
kitStore.flags.isLocked = true
|
|
102
|
+
|
|
103
|
+
Debug.log("APP", "Store initialized", {
|
|
104
|
+
url: kitStore.currentURL,
|
|
105
|
+
pageContent: kitStore.pageContent,
|
|
106
|
+
currentPage: kitStore.currentPage,
|
|
107
|
+
sizes: kitStore.sizes,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize and activate all global systems.
|
|
113
|
+
* @param {{ isSmooth: boolean }} options
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
_initSystems({ isSmooth }) {
|
|
117
|
+
const resize = new Resize()
|
|
118
|
+
const mouse = new Mouse()
|
|
119
|
+
const scroll = new Scroll({ isSmooth: isSmooth })
|
|
120
|
+
const raf = new Raf()
|
|
121
|
+
|
|
122
|
+
this.systems = { resize, mouse, scroll, raf }
|
|
123
|
+
|
|
124
|
+
kitStore.resize = resize
|
|
125
|
+
kitStore.mouse = mouse
|
|
126
|
+
kitStore.scroll = scroll
|
|
127
|
+
kitStore.raf = raf
|
|
128
|
+
|
|
129
|
+
kitStore.flags.isSmooth = isSmooth
|
|
130
|
+
|
|
131
|
+
// Phase 1: init
|
|
132
|
+
Object.values(this.systems).forEach(sys => {
|
|
133
|
+
if (sys && typeof sys.init === "function") sys.init()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Phase 2: activate listeners/tickers
|
|
137
|
+
this.on()
|
|
138
|
+
|
|
139
|
+
Debug.log("APP", "Systems initialized", this.systems)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Activate all system listeners (Resize, Mouse, Scroll, Raf).
|
|
144
|
+
*/
|
|
145
|
+
on() {
|
|
146
|
+
Object.values(this.systems).forEach(sys => {
|
|
147
|
+
if (sys && typeof sys.on === "function") sys.on()
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Deactivate all system listeners.
|
|
153
|
+
*/
|
|
154
|
+
stop() {
|
|
155
|
+
Object.values(this.systems).forEach(sys => {
|
|
156
|
+
if (sys && typeof sys.off === "function") sys.off()
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fully destroy all systems and reset the application state.
|
|
162
|
+
*/
|
|
163
|
+
destroy() {
|
|
164
|
+
this.stop()
|
|
165
|
+
Object.values(this.systems).forEach(sys => {
|
|
166
|
+
if (sys && typeof sys.destroy === "function") sys.destroy()
|
|
167
|
+
})
|
|
168
|
+
this.initialized = false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Initialize the full application lifecycle.
|
|
173
|
+
* Ensures DOMReady + store + systems + flags.
|
|
174
|
+
*
|
|
175
|
+
* @param {{
|
|
176
|
+
* url: URL|string,
|
|
177
|
+
* page: { element?: string, container?: string },
|
|
178
|
+
* load?: string,
|
|
179
|
+
* isSmooth?: boolean,
|
|
180
|
+
* dim?: { d?: number, m?: number }
|
|
181
|
+
* }} config
|
|
182
|
+
* @returns {Promise<typeof kitStore>}
|
|
183
|
+
*/
|
|
184
|
+
async initialize(config) {
|
|
185
|
+
if (this.initialized) return kitStore
|
|
186
|
+
if (!sniffer.isDesktop) {
|
|
187
|
+
config.isSmooth = false
|
|
188
|
+
} else if (!config.isSmooth && sniffer.isDesktop) {
|
|
189
|
+
config.isSmooth = true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await this._waitDOM()
|
|
193
|
+
Debug.log("APP", "DOM ready with", config.isSmooth ? "smooth " : "native " + "scroll.")
|
|
194
|
+
|
|
195
|
+
this._initStore(config)
|
|
196
|
+
this._initSystems({ isSmooth: config.isSmooth })
|
|
197
|
+
|
|
198
|
+
this.initialized = true
|
|
199
|
+
Debug.log("APP", "Application ready.", kitStore)
|
|
200
|
+
|
|
201
|
+
return kitStore
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Attach Taxi router events into the global lifecycle.
|
|
206
|
+
* @param {import("@unseenco/taxi").Core|null} router
|
|
207
|
+
*/
|
|
208
|
+
attachTaxi(router) {
|
|
209
|
+
if (!router) return
|
|
210
|
+
kitStore.taxi = router
|
|
211
|
+
|
|
212
|
+
router.on("NAVIGATE_IN", () => {
|
|
213
|
+
const { currentLocation } = router
|
|
214
|
+
kitStore.currentURL = currentLocation
|
|
215
|
+
|
|
216
|
+
if (kitStore.raf?.setScroll) kitStore.raf.setScroll(0)
|
|
217
|
+
|
|
218
|
+
Debug.log("ROUTE", "NAVIGATE_IN")
|
|
219
|
+
emitter.emit(EVENTS.APP_ROUTE_IN, { url: currentLocation })
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
router.on("NAVIGATE_END", () => {
|
|
223
|
+
this._updatePageRefs()
|
|
224
|
+
this._afterViewChange()
|
|
225
|
+
|
|
226
|
+
Debug.log("ROUTE", "NAVIGATE_END")
|
|
227
|
+
emitter.emit(EVENTS.APP_ROUTE_END, { url: kitStore.currentURL })
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
router.on("NAVIGATE_OUT", () => {
|
|
231
|
+
Debug.log("ROUTE", "NAVIGATE_OUT")
|
|
232
|
+
emitter.emit(EVENTS.APP_ROUTE_OUT, { url: kitStore.currentURL })
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Update references to current page and container after a Taxi view swap.
|
|
238
|
+
* @private
|
|
239
|
+
*/
|
|
240
|
+
_updatePageRefs() {
|
|
241
|
+
const currentPage = qs("[data-taxi-view]")
|
|
242
|
+
const pageContent = kitStore.pageContent ? kitStore.pageContent : qs("#app")
|
|
243
|
+
|
|
244
|
+
kitStore.currentPage = currentPage || null
|
|
245
|
+
kitStore.pageContent = pageContent || null
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Run post-navigation updates:
|
|
250
|
+
* - Reset Scroll bounds
|
|
251
|
+
* - Reset GSAP scroll proxy
|
|
252
|
+
* - Soft resize
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
_afterViewChange() {
|
|
256
|
+
if (this.systems.scroll?.setScrollBounds) {
|
|
257
|
+
this.systems.scroll.setScrollBounds()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (this.systems.raf?.setScrollTrigger) {
|
|
261
|
+
this.systems.raf.setScrollTrigger()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this._softResize()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Set CSS variables for responsive layout.
|
|
269
|
+
* @param {number} d
|
|
270
|
+
* @param {number} m
|
|
271
|
+
* @private
|
|
272
|
+
*/
|
|
273
|
+
_setSizes(d, m) {
|
|
274
|
+
document.documentElement.style.setProperty("--desktop", d)
|
|
275
|
+
document.documentElement.style.setProperty("--mobile", m)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Trigger a "soft resize":
|
|
280
|
+
* - Emit resize event
|
|
281
|
+
* - Refresh ScrollTrigger
|
|
282
|
+
* @private
|
|
283
|
+
*/
|
|
284
|
+
_softResize() {
|
|
285
|
+
Debug.log("APP", "Soft resize triggered.")
|
|
286
|
+
emitter.emit(EVENTS.APP_SMOOTH_RESIZE)
|
|
287
|
+
ScrollTrigger.refresh()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Wait for DOM ready before execution.
|
|
292
|
+
* @returns {Promise<void>}
|
|
293
|
+
* @private
|
|
294
|
+
*/
|
|
295
|
+
_waitDOM() {
|
|
296
|
+
if (document.readyState !== "loading") return Promise.resolve()
|
|
297
|
+
return new Promise(res => document.addEventListener("DOMContentLoaded", res, { once: true }))
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Manager } from "../classes/index.js"
|
|
2
|
+
import { emitter, EVENTS } from "../events/index.js"
|
|
3
|
+
import { Debug, qsa } from "../utils/index.js"
|
|
4
|
+
import { kitStore } from "../kitStore.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} EngineOwner
|
|
8
|
+
* The page instance passed from project-level Page (extends Renderer).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} EngineScrollState
|
|
13
|
+
* @property {number} current - Current scroll value coming from Raf.
|
|
14
|
+
* @property {"up"|"down"} direction - Derived scroll direction.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} EngineMouseState
|
|
19
|
+
* @property {number} x - Current mouse X.
|
|
20
|
+
* @property {number} y - Current mouse Y.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} EngineSetupConfig
|
|
25
|
+
* @property {Array<any>} components - Array of component constructors/definitions.
|
|
26
|
+
* @property {any} smooth - Smooth instance for scroll control.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export default class PageEngine {
|
|
30
|
+
/**
|
|
31
|
+
* @param {{ owner: EngineOwner }} param0
|
|
32
|
+
*/
|
|
33
|
+
constructor({ owner }) {
|
|
34
|
+
/**
|
|
35
|
+
* @type {EngineOwner}
|
|
36
|
+
*/
|
|
37
|
+
this.owner = owner
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @type {Array<any>}
|
|
41
|
+
*/
|
|
42
|
+
this.components = []
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
this.componentsLength = 0
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @type {Manager | null}
|
|
51
|
+
*/
|
|
52
|
+
this.manager = null
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Smooth instance passed in by project Page
|
|
56
|
+
* @type {any | null}
|
|
57
|
+
*/
|
|
58
|
+
this.smooth = null
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @type {EngineScrollState}
|
|
62
|
+
*/
|
|
63
|
+
this.scroll = { current: 0, direction: "down" }
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @type {EngineMouseState}
|
|
67
|
+
*/
|
|
68
|
+
this.mouse = { x: 0, y: 0 }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Store component definitions.
|
|
73
|
+
* Page calls this during setComponents().
|
|
74
|
+
*
|
|
75
|
+
* @param {Array<any>} components
|
|
76
|
+
*/
|
|
77
|
+
setComponents(components) {
|
|
78
|
+
Debug.log("ENGINE", "Set Components", { components })
|
|
79
|
+
|
|
80
|
+
this.components = components || []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Called by Page.initCore().
|
|
85
|
+
* Recreates Manager and binds a new Smooth instance.
|
|
86
|
+
*
|
|
87
|
+
* @param {EngineSetupConfig} param0
|
|
88
|
+
*/
|
|
89
|
+
setup({ components, smooth }) {
|
|
90
|
+
Debug.log("ENGINE", "Setup", { components, smooth })
|
|
91
|
+
|
|
92
|
+
this.components = components || []
|
|
93
|
+
this.smooth = smooth || null
|
|
94
|
+
|
|
95
|
+
this.manager = new Manager()
|
|
96
|
+
|
|
97
|
+
if (this.manager && this.components.length) {
|
|
98
|
+
this.components.forEach(obj => {
|
|
99
|
+
this.manager.addComponent(obj, this.owner)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.manager.initialize()
|
|
104
|
+
this.manager.animateIn?.()
|
|
105
|
+
|
|
106
|
+
this.on()
|
|
107
|
+
this.trackImages()
|
|
108
|
+
this.resize()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Adds `.loaded` class to images when loaded.
|
|
113
|
+
* Looks for `.lazy` images.
|
|
114
|
+
*/
|
|
115
|
+
trackImages() {
|
|
116
|
+
const imgs = qsa(".lazy")
|
|
117
|
+
if (!imgs || !imgs.length) return
|
|
118
|
+
|
|
119
|
+
imgs.forEach(img => {
|
|
120
|
+
if (img.complete) {
|
|
121
|
+
img.classList.add("loaded")
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
img.addEventListener(
|
|
126
|
+
"load",
|
|
127
|
+
() => {
|
|
128
|
+
img.classList.add("loaded")
|
|
129
|
+
},
|
|
130
|
+
{ once: true }
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Receives scroll and mouse data from Raf (APP_TICK).
|
|
137
|
+
* Packages them and forwards to Manager.tick().
|
|
138
|
+
*
|
|
139
|
+
* @param {{ current: number, diff: number, mouse: EngineMouseState }} param0
|
|
140
|
+
*/
|
|
141
|
+
tick = ({ current, diff, mouse }) => {
|
|
142
|
+
const { isResizing, isLocked } = kitStore.flags
|
|
143
|
+
|
|
144
|
+
if (isResizing || isLocked) return
|
|
145
|
+
if (!this.manager) return
|
|
146
|
+
|
|
147
|
+
this.scroll.current = current
|
|
148
|
+
this.scroll.direction = diff > 0 ? "down" : "up"
|
|
149
|
+
|
|
150
|
+
this.mouse.x = mouse.x
|
|
151
|
+
this.mouse.y = mouse.y
|
|
152
|
+
|
|
153
|
+
this.manager.tick?.({
|
|
154
|
+
mouse: this.mouse,
|
|
155
|
+
scroll: this.scroll,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Called during APP_RESIZE.
|
|
161
|
+
* Updates Smooth then calls Manager.resize().
|
|
162
|
+
*/
|
|
163
|
+
resize = () => {
|
|
164
|
+
Debug.log("ENGINE", "Resize")
|
|
165
|
+
|
|
166
|
+
this.smooth?.update?.()
|
|
167
|
+
this.manager?.resize?.()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Called during APP_SMOOTH_RESIZE.
|
|
172
|
+
* Delegates to Manager.smoothResize().
|
|
173
|
+
*/
|
|
174
|
+
smoothResize = () => {
|
|
175
|
+
Debug.log("ENGINE", "Smooth Resize")
|
|
176
|
+
|
|
177
|
+
this.manager?.smoothResize?.()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Internal listener binder/unbinder.
|
|
182
|
+
*
|
|
183
|
+
* @param {"on" | "off"} action
|
|
184
|
+
*/
|
|
185
|
+
listeners(action) {
|
|
186
|
+
Debug.log("ENGINE", `Listeners`, action.toUpperCase())
|
|
187
|
+
|
|
188
|
+
emitter[action](EVENTS.APP_TICK, this.tick)
|
|
189
|
+
emitter[action](EVENTS.APP_RESIZE, this.resize)
|
|
190
|
+
emitter[action](EVENTS.APP_SMOOTH_RESIZE, this.smoothResize)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Register RAF listeners.
|
|
195
|
+
*/
|
|
196
|
+
on() {
|
|
197
|
+
this.listeners("on")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Unregister RAF listeners.
|
|
202
|
+
*/
|
|
203
|
+
off() {
|
|
204
|
+
this.listeners("off")
|
|
205
|
+
this.destroy()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Cleanup Smooth + Manager.
|
|
210
|
+
* Called by Page.onLeaveCompleted().
|
|
211
|
+
*/
|
|
212
|
+
destroy() {
|
|
213
|
+
this.manager?.destroy?.()
|
|
214
|
+
this.manager = null
|
|
215
|
+
|
|
216
|
+
if (this.smooth) {
|
|
217
|
+
this.smooth.destroy?.()
|
|
218
|
+
this.smooth = null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
package/core/index.js
ADDED
package/events/Emitter.js
CHANGED
|
@@ -6,6 +6,7 @@ class Emitter {
|
|
|
6
6
|
|
|
7
7
|
emit(event, ...args) {
|
|
8
8
|
const callbacks = this.events[event] || []
|
|
9
|
+
|
|
9
10
|
for (let i = 0, { length } = callbacks; i < length; i++) {
|
|
10
11
|
callbacks[i].cb(...args)
|
|
11
12
|
}
|
|
@@ -15,6 +16,7 @@ class Emitter {
|
|
|
15
16
|
const data = { cb, priority }
|
|
16
17
|
this.events[event]?.push(data) || (this.events[event] = [data])
|
|
17
18
|
this.events[event].sort((a, b) => a.priority - b.priority)
|
|
19
|
+
|
|
18
20
|
return () => {
|
|
19
21
|
this.events[event] = this.events[event]?.filter(v => cb !== v.cb)
|
|
20
22
|
}
|
|
@@ -29,9 +31,7 @@ class Emitter {
|
|
|
29
31
|
cb(...args)
|
|
30
32
|
this.off(event, onceCallback)
|
|
31
33
|
}
|
|
32
|
-
|
|
33
34
|
this.on(event, onceCallback, priority)
|
|
34
|
-
|
|
35
35
|
return () => {
|
|
36
36
|
this.off(event, cb)
|
|
37
37
|
}
|
|
@@ -45,6 +45,7 @@ class Emitter {
|
|
|
45
45
|
const emitter = new Emitter()
|
|
46
46
|
|
|
47
47
|
const EVENTS = {
|
|
48
|
+
APP_UNLOCKED: "app:unlocked",
|
|
48
49
|
APP_TICK: "tick",
|
|
49
50
|
APP_RESIZE: "resize",
|
|
50
51
|
APP_SMOOTH_RESIZE: "smooth:resize",
|
package/events/Raf.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import kitStore from "../kitStore"
|
|
1
|
+
import { kitStore } from "../kitStore"
|
|
3
2
|
import emitter, { EVENTS } from "./Emitter"
|
|
4
3
|
import { lerp } from "../utils"
|
|
5
4
|
import { gsap, ScrollTrigger } from "../libraries/gsap"
|
|
@@ -19,14 +18,50 @@ export default class Raf {
|
|
|
19
18
|
target: null,
|
|
20
19
|
}
|
|
21
20
|
|
|
21
|
+
this.diff = 0
|
|
22
|
+
|
|
23
|
+
this.tick = this.tick.bind(this)
|
|
24
|
+
this.onScroll = this.onScroll.bind(this)
|
|
25
|
+
this.onMouseMove = this.onMouseMove.bind(this)
|
|
26
|
+
this.onAppResize = this.onAppResize.bind(this)
|
|
27
|
+
|
|
28
|
+
this.onPageHide = this.stop.bind(this)
|
|
29
|
+
this.onPageShow = this.resume.bind(this)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
init() {
|
|
22
33
|
this.setScrollTrigger()
|
|
23
|
-
this.on()
|
|
24
34
|
}
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
on() {
|
|
37
|
+
gsap.ticker.add(this.tick)
|
|
38
|
+
|
|
39
|
+
emitter.on(EVENTS.APP_SCROLL, this.onScroll)
|
|
40
|
+
emitter.on(EVENTS.APP_MOUSEMOVE, this.onMouseMove)
|
|
41
|
+
emitter.on(EVENTS.APP_SMOOTH_RESIZE, this.onAppResize)
|
|
42
|
+
|
|
43
|
+
window.addEventListener("pagehide", this.onPageHide)
|
|
44
|
+
window.addEventListener("pageshow", this.onPageShow)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
off() {
|
|
48
|
+
gsap.ticker.remove(this.tick)
|
|
49
|
+
|
|
50
|
+
emitter.off(EVENTS.APP_SCROLL, this.onScroll)
|
|
51
|
+
emitter.off(EVENTS.APP_MOUSEMOVE, this.onMouseMove)
|
|
52
|
+
emitter.off(EVENTS.APP_SMOOTH_RESIZE, this.onAppResize)
|
|
53
|
+
|
|
54
|
+
window.removeEventListener("pagehide", this.onPageHide)
|
|
55
|
+
window.removeEventListener("pageshow", this.onPageShow)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
destroy() {
|
|
59
|
+
this.off()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onAppResize() {
|
|
27
63
|
const { fh } = kitStore.sizes
|
|
28
64
|
|
|
29
|
-
// clamp our smooth state
|
|
30
65
|
this.scroll.target = Math.min(Math.max(this.scroll.target, 0), fh)
|
|
31
66
|
this.scroll.current = Math.min(Math.max(this.scroll.current, 0), fh)
|
|
32
67
|
this.scroll.rounded = Math.min(Math.max(this.scroll.rounded, 0), fh)
|
|
@@ -34,8 +69,9 @@ export default class Raf {
|
|
|
34
69
|
ScrollTrigger.update()
|
|
35
70
|
}
|
|
36
71
|
|
|
37
|
-
tick
|
|
72
|
+
tick() {
|
|
38
73
|
if (kitStore.flags.isResizing) return
|
|
74
|
+
|
|
39
75
|
const { target, current, ease } = this.scroll
|
|
40
76
|
|
|
41
77
|
this.scroll.current = lerp(current, target, ease)
|
|
@@ -55,7 +91,6 @@ export default class Raf {
|
|
|
55
91
|
getScroll() {
|
|
56
92
|
const { pageContent, flags } = kitStore
|
|
57
93
|
const container = pageContent ? pageContent.parentNode : document.body
|
|
58
|
-
|
|
59
94
|
return flags.isSmooth ? this.scroll.rounded : container.scrollTop
|
|
60
95
|
}
|
|
61
96
|
|
|
@@ -64,13 +99,13 @@ export default class Raf {
|
|
|
64
99
|
this.scroll.target = Math.min(Math.max(this.scroll.target, 0), fh)
|
|
65
100
|
}
|
|
66
101
|
|
|
67
|
-
onScroll
|
|
102
|
+
onScroll({ y }) {
|
|
68
103
|
if (kitStore.flags.isLocked) return
|
|
69
104
|
this.scroll.target += y
|
|
70
105
|
this.clamp()
|
|
71
106
|
}
|
|
72
107
|
|
|
73
|
-
onMouseMove
|
|
108
|
+
onMouseMove({ x, y, target }) {
|
|
74
109
|
this.mouse.x = x
|
|
75
110
|
this.mouse.y = y
|
|
76
111
|
this.mouse.target = target
|
|
@@ -102,52 +137,30 @@ export default class Raf {
|
|
|
102
137
|
|
|
103
138
|
setScrollTrigger() {
|
|
104
139
|
const { pageContent } = kitStore
|
|
140
|
+
const scroller = pageContent || document.body
|
|
105
141
|
|
|
106
142
|
ScrollTrigger.defaults({
|
|
107
|
-
scroller
|
|
143
|
+
scroller,
|
|
108
144
|
})
|
|
109
145
|
|
|
110
|
-
ScrollTrigger.scrollerProxy(
|
|
146
|
+
ScrollTrigger.scrollerProxy(scroller, {
|
|
111
147
|
scrollTop: () => {
|
|
112
148
|
return this.getScroll()
|
|
113
149
|
},
|
|
114
150
|
getBoundingClientRect() {
|
|
115
|
-
// Important that width and height are dynamic
|
|
116
151
|
return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }
|
|
117
152
|
},
|
|
118
153
|
})
|
|
119
154
|
}
|
|
120
155
|
|
|
121
|
-
stop
|
|
156
|
+
stop() {
|
|
122
157
|
gsap.ticker.remove(this.tick)
|
|
123
158
|
}
|
|
124
159
|
|
|
125
|
-
resume
|
|
160
|
+
resume() {
|
|
126
161
|
this.scroll.current = this.scroll.target
|
|
127
162
|
this.scroll.rounded = this.scroll.target
|
|
128
163
|
gsap.ticker.add(this.tick)
|
|
129
164
|
ScrollTrigger.update()
|
|
130
165
|
}
|
|
131
|
-
|
|
132
|
-
on() {
|
|
133
|
-
gsap.ticker.add(this.tick)
|
|
134
|
-
emitter.on(EVENTS.APP_SCROLL, this.onScroll)
|
|
135
|
-
emitter.on(EVENTS.APP_MOUSEMOVE, this.onMouseMove)
|
|
136
|
-
emitter.on(EVENTS.APP_SMOOTH_RESIZE, this.onAppResize)
|
|
137
|
-
window.addEventListener("pagehide", () => this.stop)
|
|
138
|
-
window.addEventListener("pageshow", () => this.resume)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
off() {
|
|
142
|
-
gsap.ticker.remove(this.tick)
|
|
143
|
-
emitter.off(EVENTS.APP_SCROLL, this.onScroll)
|
|
144
|
-
emitter.off(EVENTS.APP_MOUSEMOVE, this.onMouseMove)
|
|
145
|
-
emitter.off(EVENTS.APP_SMOOTH_RESIZE, this.onAppResize)
|
|
146
|
-
window.removeEventListener("pagehide", () => this.stop)
|
|
147
|
-
window.removeEventListener("pageshow", () => this.resume)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
destroy() {
|
|
151
|
-
this.off()
|
|
152
|
-
}
|
|
153
166
|
}
|
package/events/Resize.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Resize.js
|
|
2
1
|
import { sizes, flags, breakpoints } from "../kitStore"
|
|
3
2
|
import emitter, { EVENTS } from "./Emitter"
|
|
4
3
|
import debounce from "lodash.debounce"
|
|
@@ -8,7 +7,27 @@ import { ScrollTrigger } from "../libraries/gsap"
|
|
|
8
7
|
export default class Resize {
|
|
9
8
|
constructor() {
|
|
10
9
|
this.wasDesktop = sniffer.isDesktop
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
this.handleResize = debounce(this.onResize.bind(this), 200)
|
|
12
|
+
this.handleLoadOnce = this.onLoadOnce.bind(this)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
init() {
|
|
16
|
+
this.onResize()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
on() {
|
|
20
|
+
window.addEventListener("resize", this.handleResize, { passive: true })
|
|
21
|
+
window.addEventListener("load", this.handleLoadOnce, { once: true })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
off() {
|
|
25
|
+
window.removeEventListener("resize", this.handleResize)
|
|
26
|
+
window.removeEventListener("load", this.handleLoadOnce)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
destroy() {
|
|
30
|
+
this.off()
|
|
12
31
|
}
|
|
13
32
|
|
|
14
33
|
onResize = () => {
|
|
@@ -16,7 +35,7 @@ export default class Resize {
|
|
|
16
35
|
|
|
17
36
|
const { width, height } = getViewport()
|
|
18
37
|
const bp = getWindowSizes()
|
|
19
|
-
sniffer.update()
|
|
38
|
+
sniffer.update()
|
|
20
39
|
|
|
21
40
|
Object.assign(sizes, {
|
|
22
41
|
vw: width,
|
|
@@ -69,14 +88,4 @@ export default class Resize {
|
|
|
69
88
|
document.body.classList.toggle("is-desktop", isDesktop)
|
|
70
89
|
document.body.classList.toggle("is-device", !isDesktop)
|
|
71
90
|
}
|
|
72
|
-
|
|
73
|
-
on() {
|
|
74
|
-
window.addEventListener("resize", debounce(this.onResize, 200), { passive: true })
|
|
75
|
-
window.addEventListener("load", this.onLoadOnce, { once: true })
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
init() {
|
|
79
|
-
this.on()
|
|
80
|
-
this.onResize() // Run once on init
|
|
81
|
-
}
|
|
82
91
|
}
|
package/events/Scroll.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Scroll.js
|
|
2
1
|
import { sizes } from "../kitStore"
|
|
3
2
|
import emitter, { EVENTS } from "./Emitter"
|
|
4
3
|
import VirtualScroll from "./VirtualScroll"
|
|
@@ -7,24 +6,52 @@ import { bounds, sniffer } from "../utils"
|
|
|
7
6
|
export default class Scroll {
|
|
8
7
|
constructor({ isSmooth = false } = {}) {
|
|
9
8
|
this.isSmooth = isSmooth
|
|
9
|
+
this.virtualScroll = null
|
|
10
10
|
|
|
11
|
+
this.onVirtualScroll = this.onVirtualScroll.bind(this)
|
|
12
|
+
this.onScroll = this.onScroll.bind(this)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
init() {
|
|
16
|
+
this.setScrollBounds()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
on() {
|
|
11
20
|
if (this.isSmooth) {
|
|
12
21
|
document.body.classList.add("is-smooth")
|
|
13
22
|
|
|
14
|
-
this.virtualScroll
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
if (!this.virtualScroll) {
|
|
24
|
+
this.virtualScroll = new VirtualScroll({
|
|
25
|
+
mouseMultiplier: sniffer.isWindows ? 1.1 : 0.45,
|
|
26
|
+
touchMultiplier: 3.5,
|
|
27
|
+
firefoxMultiplier: sniffer.isWindows ? 40 : 90,
|
|
28
|
+
passive: true,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.virtualScroll.on(this.onVirtualScroll)
|
|
33
|
+
} else {
|
|
34
|
+
document.addEventListener("scroll", this.onScroll, {
|
|
18
35
|
passive: true,
|
|
36
|
+
capture: true,
|
|
19
37
|
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
20
40
|
|
|
21
|
-
|
|
41
|
+
off() {
|
|
42
|
+
if (this.virtualScroll) {
|
|
43
|
+
this.virtualScroll.off(this.onVirtualScroll)
|
|
22
44
|
} else {
|
|
23
|
-
|
|
24
|
-
document.addEventListener("scroll", this.onScroll, true)
|
|
45
|
+
document.removeEventListener("scroll", this.onScroll, true)
|
|
25
46
|
}
|
|
47
|
+
}
|
|
26
48
|
|
|
27
|
-
|
|
49
|
+
destroy() {
|
|
50
|
+
this.off()
|
|
51
|
+
if (this.virtualScroll) {
|
|
52
|
+
this.virtualScroll.destroy()
|
|
53
|
+
this.virtualScroll = null
|
|
54
|
+
}
|
|
28
55
|
}
|
|
29
56
|
|
|
30
57
|
setScrollBounds() {
|
|
@@ -33,19 +60,11 @@ export default class Scroll {
|
|
|
33
60
|
sizes.fh = height > sizes.vh ? height - sizes.vh : 0
|
|
34
61
|
}
|
|
35
62
|
|
|
36
|
-
onVirtualScroll
|
|
63
|
+
onVirtualScroll(e) {
|
|
37
64
|
emitter.emit(EVENTS.APP_SCROLL, { y: e.deltaY * -1 })
|
|
38
65
|
}
|
|
39
66
|
|
|
40
|
-
onScroll(
|
|
67
|
+
onScroll() {
|
|
41
68
|
emitter.emit(EVENTS.APP_SCROLL, { y: window.scrollY })
|
|
42
69
|
}
|
|
43
|
-
|
|
44
|
-
destroy() {
|
|
45
|
-
if (this.virtualScroll) {
|
|
46
|
-
this.virtualScroll.destroy()
|
|
47
|
-
} else {
|
|
48
|
-
document.removeEventListener("scroll", this.onScroll, true)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
70
|
}
|
package/events/VirtualScroll.js
CHANGED
package/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Legacy setup functions for Stellar Kit.
|
|
3
|
+
* These were previously part of Application.js but have been moved out
|
|
4
|
+
*/
|
|
5
|
+
|
|
2
6
|
import { Mouse, Resize, Raf, Scroll } from "./events"
|
|
3
7
|
import kitStore from "./kitStore"
|
|
4
8
|
import { sniffer } from "./utils"
|
package/kitStore.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
//kitStore.js
|
|
1
|
+
// kitStore.js
|
|
2
|
+
|
|
2
3
|
export const sizes = {
|
|
3
4
|
vw: 0,
|
|
4
5
|
vh: 0,
|
|
5
6
|
fh: 0,
|
|
6
|
-
d: 1440,
|
|
7
|
-
m: 390,
|
|
7
|
+
d: 1440,
|
|
8
|
+
m: 390,
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
// in case you want to "cache" your assets
|
|
11
11
|
export const assets = {}
|
|
12
12
|
|
|
13
13
|
export const flags = {
|
|
@@ -32,11 +32,14 @@ export const mouse = {
|
|
|
32
32
|
y: 0,
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Single canonical store object.
|
|
37
|
+
*/
|
|
38
|
+
export const kitStore = {
|
|
36
39
|
sizes,
|
|
37
40
|
breakpoints,
|
|
38
41
|
mouse,
|
|
39
42
|
flags,
|
|
40
|
-
pageContent: null,
|
|
43
|
+
pageContent: null,
|
|
41
44
|
assets,
|
|
42
45
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linear_non/stellar-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Stellar frontend core for Non-Linear Studio projects.",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "/core/index.js",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./
|
|
7
|
+
".": "./kitStore.js",
|
|
8
8
|
"./utils": "./utils/index.js",
|
|
9
|
+
"./core": "./core/index.js",
|
|
9
10
|
"./classes": "./classes/index.js",
|
|
10
11
|
"./events": "./events/index.js",
|
|
11
12
|
"./plugins": "./plugins/index.js",
|
|
@@ -14,10 +15,12 @@
|
|
|
14
15
|
"type": "module",
|
|
15
16
|
"scripts": {
|
|
16
17
|
"dev": "vite",
|
|
18
|
+
"build": "vite build",
|
|
17
19
|
"format": "prettier --write .",
|
|
18
20
|
"check": "vite build --emptyOutDir --watch"
|
|
19
21
|
},
|
|
20
22
|
"files": [
|
|
23
|
+
"core/",
|
|
21
24
|
"classes/",
|
|
22
25
|
"events/",
|
|
23
26
|
"plugins/",
|
package/plugins/Grid.js
CHANGED
package/utils/debug.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { kitStore } from "../kitStore.js"
|
|
2
|
+
|
|
3
|
+
const COLORS = {
|
|
4
|
+
APP: "#03A9F4",
|
|
5
|
+
ENGINE: "#4CAF50",
|
|
6
|
+
PAGE: "#FF9800",
|
|
7
|
+
MANAGER: "#E91E63",
|
|
8
|
+
ROUTE: "#9C27B0",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function fmt(scope) {
|
|
12
|
+
const color = COLORS[scope] || "#999"
|
|
13
|
+
return [`%c[${scope}]`, `color:${color};font-weight:bold`]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Lightweight global debug helper.
|
|
18
|
+
*
|
|
19
|
+
* - Controlled via kitStore.flags.isDebug
|
|
20
|
+
* - No-op when debug is off
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const Debug = {
|
|
24
|
+
enable() {
|
|
25
|
+
kitStore.flags.isDebug = true
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
disable() {
|
|
29
|
+
kitStore.flags.isDebug = false
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
isEnabled() {
|
|
33
|
+
return Boolean(kitStore.flags.isDebug)
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
log(scope, ...args) {
|
|
37
|
+
if (!kitStore.flags.isDebug) return
|
|
38
|
+
const [tag, style] = fmt(scope)
|
|
39
|
+
console.log(tag, style, ...args)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
warn(scope, ...args) {
|
|
43
|
+
if (!kitStore.flags.isDebug) return
|
|
44
|
+
const [tag, style] = fmt(scope)
|
|
45
|
+
console.warn(tag, style, ...args)
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
error(scope, ...args) {
|
|
49
|
+
if (!kitStore.flags.isDebug) return
|
|
50
|
+
const [tag, style] = fmt(scope)
|
|
51
|
+
console.error(tag, style, ...args)
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
group(scope, fn) {
|
|
55
|
+
if (!kitStore.flags.isDebug) return
|
|
56
|
+
const [tag, style] = fmt(scope)
|
|
57
|
+
console.group(tag, style)
|
|
58
|
+
try {
|
|
59
|
+
fn()
|
|
60
|
+
} finally {
|
|
61
|
+
console.groupEnd()
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}
|
package/utils/index.js
CHANGED
package/utils/listener.js
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {EventTarget | EventTarget[] | NodeListOf<EventTarget> | HTMLCollectionOf<EventTarget>} ListenerTarget
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {"add" | "remove"} ListenerAction
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Cross-browser helper to add/remove event listeners on a single element or a collection.
|
|
10
|
+
*
|
|
11
|
+
* @param {ListenerTarget} el - Element or collection of elements.
|
|
12
|
+
* @param {ListenerAction} action - `"add"` to add, `"remove"` to remove.
|
|
13
|
+
* @param {string} type - Event type, e.g. `"click"`, `"scroll"`.
|
|
14
|
+
* @param {(event: Event) => void} cb - Event handler callback.
|
|
15
|
+
* @param {boolean} [p] - If `true`, listener is registered as passive.
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
2
18
|
export const listener = (el, action, type, cb, p) => {
|
|
3
19
|
const passive = p === true ? { passive: true } : false
|
|
4
20
|
|
|
5
|
-
if (el
|
|
21
|
+
if (el && "length" in el && typeof el !== "string") {
|
|
6
22
|
for (let i = 0; i < el.length; i++) {
|
|
7
23
|
el[i][action + "EventListener"](type, cb, passive)
|
|
8
24
|
}
|
|
9
|
-
} else {
|
|
25
|
+
} else if (el) {
|
|
10
26
|
el[action + "EventListener"](type, cb, passive)
|
|
11
27
|
}
|
|
12
28
|
}
|
package/utils/math.js
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Linearly interpolates between two numbers.
|
|
3
|
+
* @param {number} a
|
|
4
|
+
* @param {number} b
|
|
5
|
+
* @param {number} n - Normalized factor [0–1]
|
|
6
|
+
* @returns {number}
|
|
7
|
+
*/
|
|
8
|
+
export const lerp = (a, b, n) => a * (1 - n) + b * n
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalizes a value into a 0–1 range.
|
|
12
|
+
* @param {number} val
|
|
13
|
+
* @param {number} min
|
|
14
|
+
* @param {number} max
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
export const norm = (val, min, max) => (val - min) / (max - min)
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
19
|
+
/**
|
|
20
|
+
* Clamps a number between a minimum and maximum.
|
|
21
|
+
* @param {number} val
|
|
22
|
+
* @param {number} min
|
|
23
|
+
* @param {number} max
|
|
24
|
+
* @returns {number}
|
|
25
|
+
*/
|
|
26
|
+
export const clamp = (val, min, max) => Math.min(Math.max(val, min), max)
|
|
13
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Rounds a number to a given precision.
|
|
30
|
+
* @param {number} n
|
|
31
|
+
* @param {number} [p=3] - Decimal precision
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
14
34
|
export const round = (n, p) => {
|
|
15
35
|
const precision = p !== undefined ? Math.pow(10, p) : 1000
|
|
16
36
|
return Math.round(n * precision) / precision
|
package/utils/offset.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Computes the cumulative offset of an element relative to the document.
|
|
3
|
+
* Use this instead of `bounds()` when smooth-scroll systems shift the viewport.
|
|
4
|
+
*
|
|
5
|
+
* @param {HTMLElement} el
|
|
6
|
+
* @returns {{left: number, top: number, bottom: number}}
|
|
7
|
+
*/
|
|
4
8
|
export const offset = el => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
if (!el) return { left: 0, top: 0, bottom: 0 }
|
|
10
|
+
|
|
11
|
+
let left = 0
|
|
12
|
+
let top = 0
|
|
13
|
+
const base = el
|
|
14
|
+
|
|
15
|
+
/** Walk up the offset parent chain */
|
|
8
16
|
while (el) {
|
|
9
|
-
left += el.offsetLeft
|
|
10
|
-
top += el.offsetTop
|
|
17
|
+
left += el.offsetLeft || 0
|
|
18
|
+
top += el.offsetTop || 0
|
|
11
19
|
el = el.offsetParent
|
|
12
20
|
}
|
|
13
|
-
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
left,
|
|
24
|
+
top,
|
|
25
|
+
bottom: top + base.offsetHeight,
|
|
26
|
+
}
|
|
14
27
|
}
|
package/utils/selector.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Query a single DOM element.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} selector - CSS selector
|
|
5
|
+
* @param {ParentNode} [scope=document] - Scope to search within
|
|
6
|
+
* @returns {Element|null}
|
|
7
|
+
*/
|
|
8
|
+
export const qs = (selector, scope = document) => scope.querySelector(selector)
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Query multiple DOM elements.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} selector - CSS selector
|
|
14
|
+
* @param {ParentNode} [scope=document] - Scope to search within
|
|
15
|
+
* @returns {Element[]} - Array of matched elements
|
|
16
|
+
*/
|
|
17
|
+
export const qsa = (selector, scope = document) => Array.from(scope.querySelectorAll(selector))
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get an element by ID.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} id - Element ID
|
|
23
|
+
* @param {Document|HTMLElement} [scope=document] - Scope to search within
|
|
24
|
+
* @returns {HTMLElement|null}
|
|
25
|
+
*/
|
|
26
|
+
export const getid = (id, scope = document) => scope.getElementById(id)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get elements by tag name.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} tag - Tag name
|
|
32
|
+
* @param {Document|HTMLElement} [scope=document] - Scope to search within
|
|
33
|
+
* @returns {HTMLCollectionOf<Element>}
|
|
34
|
+
*/
|
|
35
|
+
export const gettag = (tag, scope = document) => scope.getElementsByTagName(tag)
|