@linear_non/stellar-kit 3.0.9 → 3.0.12

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/README.md CHANGED
@@ -1,67 +1,75 @@
1
1
  # stellar-kit
2
2
 
3
- A modular frontend utility kit for custom websites. Built for performance, extensibility, and clean developer ergonomics — with support for smooth scrolling, events, tick updates, mouse tracking, and layout helpers.
3
+ > `@linear_non/stellar-kit` Front-end core framework for Non-Linear Studio projects.
4
4
 
5
- ## Features
5
+ A modular frontend toolkit that handles the application lifecycle, scroll management (internal smooth scroll via VirtualScroll or external via Lenis), per-frame animation ticking, component orchestration, asset loading, and responsive layout utilities.
6
6
 
7
- - RAF-based update loop (`Raf`)
8
- - Virtual scroll support (`Scroll`)
9
- - Viewport-based resize tracking (`Resize`)
10
- - Centralized event system (`Emitter`)
11
- - Mouse tracking (`Mouse`)
12
- - Utility helpers (DOM selection, bounds, clamping, etc.)
13
- - Designed for use with Astro, GSAP, Three.js, and modular frontend setups
14
-
15
- ## 📦 Installation
7
+ ## Installation
16
8
 
17
9
  ```bash
18
10
  npm install @linear_non/stellar-kit
19
-
20
11
  ```
21
12
 
22
- ## 🧱 Folder Structure
13
+ ## Quick Start
23
14
 
24
- ```
25
- stellar-kit/
26
- ├── classes/ # Core logic: Manager, Component.
27
- ├── events/ # Emitter system: Raf, Resize, Scroll, Mouse, etc.
28
- ├── utils/ # Utilities: bounds, clamp, selectors, sniffer, etc.
29
- ├── plugins/ # Helpers Grid, Observer, SplitText, etc.
30
- ├── kitStore.js # Central store shared across all modules
31
- └── index.js # Entry point for setupKit and kitStore access
15
+ ```js
16
+ import { Application } from "@linear_non/stellar-kit/core"
17
+
18
+ await Application.initialize({
19
+ url: window.location,
20
+ page: { element: "[data-taxi-view]", container: "#app" },
21
+ isSmooth: true,
22
+ useExternalScroll: false,
23
+ dim: { d: 1440, m: 390 },
24
+ })
32
25
  ```
33
26
 
34
- ## 🧪 Local Development
27
+ ## Imports
35
28
 
36
- ```bash
37
- npm install
38
- npm run dev
29
+ ```js
30
+ import { kitStore } from "@linear_non/stellar-kit"
31
+ import { Application, PageEngine } from "@linear_non/stellar-kit/core"
32
+ import { Component, Manager, MasterLoader, ImageLoader, FontLoader } from "@linear_non/stellar-kit/classes"
33
+ import { emitter, EVENTS, PRIORITY } from "@linear_non/stellar-kit/events"
34
+ import { Grid, Observer, splitText, reverseSplit } from "@linear_non/stellar-kit/plugins"
35
+ import { qs, qsa, bounds, lerp, clamp, sniffer, listener } from "@linear_non/stellar-kit/utils"
36
+ import { gsap, ScrollTrigger, SplitText } from "@linear_non/stellar-kit/gsap"
39
37
  ```
40
38
 
41
- > The `dev/` folder provides a Vite playground to test modules like Smooth, Scrollbar, and events. You can import any part of the kit and prototype in isolation.
39
+ ## Architecture
42
40
 
43
- ## 🛠️ Usage Example
41
+ | Module | Purpose |
42
+ | --------- | ------------------------------------------------------------- |
43
+ | `core` | `Application` singleton + `PageEngine` per-page lifecycle |
44
+ | `classes` | `Component` base class, `Manager`, asset loaders |
45
+ | `events` | `Emitter` pub/sub, `Raf`, `Resize`, `Scroll`, `Mouse` |
46
+ | `plugins` | `Grid` overlay, `Observer` (ScrollTrigger), `splitText` |
47
+ | `utils` | DOM helpers, math, sniffer, viewport, debug |
48
+ | `gsap` | Pre-configured GSAP with ScrollTrigger, SplitText, CustomEase |
44
49
 
45
- ```js
46
- // main.js or index.js of your project
47
- import { kitStore, setupKit } from "stellar-kit"
50
+ ## Scroll Modes
48
51
 
49
- setupKit() // Initializes Resize, Raf, Scroll, Mouse, etc.
52
+ **Internal** VirtualScroll + lerp interpolation in Raf:
50
53
 
51
- // Access anywhere
52
- kitStore.scroll
53
- kitStore.raf
54
- kitStore.mouse
54
+ ```js
55
+ engine.setup({ components, smooth })
55
56
  ```
56
57
 
57
- You can also import from specific paths:
58
+ **External (Lenis)** Lenis manages scroll, PageEngine sets up ScrollTrigger proxy:
58
59
 
59
60
  ```js
60
- import { bounds, clamp, qs } from "stellar-kit/utils"
61
- import { Manager } from "stellar-kit/classes"
62
- import { Raf } from "stellar-kit/events"
61
+ engine.setup({ components, lenis, wrapper })
62
+ ```
63
+
64
+ ## Local Development
65
+
66
+ ```bash
67
+ npm install
68
+ npm run dev
63
69
  ```
64
70
 
65
- ---
71
+ The `dev/` folder provides a Vite playground for testing.
72
+
73
+ ## License
66
74
 
67
- Made with ❤️ by [Non-Linear Studio](https://non-linear.studio)
75
+ MIT [Non-Linear Studio](https://non-linear.studio)
@@ -31,8 +31,6 @@ export default class Component {
31
31
  this.off()
32
32
  }
33
33
 
34
- setWebgl() {}
35
-
36
34
  render() {
37
35
  this.setup()
38
36
  this.on()
@@ -213,6 +213,10 @@ export class ApplicationManager {
213
213
  }
214
214
 
215
215
  await this._waitDOM()
216
+
217
+ // Auto-enable debug mode from ?debug URL param
218
+ Debug.init()
219
+
216
220
  Debug.log(
217
221
  "APP",
218
222
  "DOM ready with",
@@ -310,4 +314,4 @@ export class ApplicationManager {
310
314
  if (document.readyState !== "loading") return Promise.resolve()
311
315
  return new Promise(res => document.addEventListener("DOMContentLoaded", res, { once: true }))
312
316
  }
313
- }
317
+ }
@@ -148,6 +148,15 @@ export default class PageEngine {
148
148
  this.manager?.animateIn?.()
149
149
  }
150
150
 
151
+ /**
152
+ * Trigger exit animations on all components.
153
+ * Call this from the page/renderer before leaving
154
+ * (e.g. during onLeave or transition out).
155
+ */
156
+ animateOut() {
157
+ this.manager?.animateOut?.()
158
+ }
159
+
151
160
  /**
152
161
  * Setup Lenis: register with global Raf and configure ScrollTrigger.
153
162
  * Raf.js handles the lenis.raf() call on each tick.
@@ -294,7 +303,6 @@ export default class PageEngine {
294
303
  */
295
304
  off() {
296
305
  this.listeners("off")
297
- this.destroy()
298
306
  }
299
307
 
300
308
  /**
@@ -317,10 +325,12 @@ export default class PageEngine {
317
325
  }
318
326
 
319
327
  /**
320
- * Cleanup Smooth + Manager.
328
+ * Cleanup listeners, Smooth + Manager.
321
329
  * Called by Page.onLeaveCompleted().
322
330
  */
323
331
  destroy() {
332
+ this.off()
333
+
324
334
  this.manager?.destroy?.()
325
335
  this.manager = null
326
336
 
package/events/Raf.js CHANGED
@@ -313,12 +313,18 @@ export default class Raf {
313
313
  })
314
314
 
315
315
  ScrollTrigger.scrollerProxy(scroller, {
316
- scrollTop: () => {
316
+ scrollTop: value => {
317
+ if (value !== undefined) {
318
+ this.scroll.target = value
319
+ this.scroll.current = value
320
+ this.scroll.rounded = value
321
+ }
317
322
  return this.getScroll()
318
323
  },
319
324
  getBoundingClientRect() {
320
325
  return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }
321
326
  },
327
+ pinType: "transform",
322
328
  })
323
329
  }
324
330
 
@@ -1,7 +1,7 @@
1
1
  // VirtualScroll.js
2
2
  import { kitStore } from "../kitStore"
3
3
  import emitter from "./Emitter"
4
- import { supportMouseTouch } from "../utils"
4
+ import { sniffer } from "../utils"
5
5
  import Lethargy from "lethargy"
6
6
 
7
7
  const EVT_ID = "virtualscroll"
@@ -25,7 +25,6 @@ export default class VirtualScroll {
25
25
  this.event = { x: 0, y: 0, deltaX: 0, deltaY: 0 }
26
26
  this.touchStartX = null
27
27
  this.touchStartY = null
28
- this.bodyTouchAction = null
29
28
  this.focusingInput = false
30
29
 
31
30
  if (this.options.limitInertia) this.lethargy = new Lethargy()
@@ -34,8 +33,11 @@ export default class VirtualScroll {
34
33
  this.listenerOptions = { passive: this.options.passive }
35
34
  }
36
35
 
37
- document.addEventListener("focusin", () => (this.focusingInput = true))
38
- document.addEventListener("focusout", () => (this.focusingInput = false))
36
+ this._onFocusIn = () => (this.focusingInput = true)
37
+ this._onFocusOut = () => (this.focusingInput = false)
38
+
39
+ document.addEventListener("focusin", this._onFocusIn)
40
+ document.addEventListener("focusout", this._onFocusOut)
39
41
  }
40
42
 
41
43
  notify(e) {
@@ -56,10 +58,10 @@ export default class VirtualScroll {
56
58
  if (this.lethargy && !this.lethargy.check(e)) return
57
59
 
58
60
  const evt = this.event
59
- evt.deltaX = e.wheelDeltaX || e.deltaX * -1
60
- evt.deltaY = e.wheelDeltaY || e.deltaY * -1
61
+ evt.deltaX = e.deltaX * -1
62
+ evt.deltaY = e.deltaY * -1
61
63
 
62
- if (supportMouseTouch().isFirefox && e.deltaMode === 1) {
64
+ if (sniffer.isFirefox && e.deltaMode === 1) {
63
65
  evt.deltaX *= this.options.firefoxMultiplier
64
66
  evt.deltaY *= this.options.firefoxMultiplier
65
67
  }
@@ -70,16 +72,6 @@ export default class VirtualScroll {
70
72
  this.notify(e)
71
73
  }
72
74
 
73
- onMouseWheel = e => {
74
- if (this.options.limitInertia && !this.lethargy.check(e)) return
75
-
76
- const evt = this.event
77
- evt.deltaX = e.wheelDeltaX || 0
78
- evt.deltaY = e.wheelDeltaY || e.wheelDelta
79
-
80
- this.notify(e)
81
- }
82
-
83
75
  onTouchStart = e => {
84
76
  const t = e.targetTouches ? e.targetTouches[0] : e
85
77
  this.touchStartX = t.pageX
@@ -132,44 +124,23 @@ export default class VirtualScroll {
132
124
  }
133
125
 
134
126
  bind = () => {
135
- const support = supportMouseTouch()
127
+ this.el.addEventListener("wheel", this.onWheel, this.listenerOptions)
136
128
 
137
- if (support.hasWheelEvent) this.el.addEventListener("wheel", this.onWheel, this.listenerOptions)
138
-
139
- if (support.hasMouseWheelEvent)
140
- this.el.addEventListener("mousewheel", this.onMouseWheel, this.listenerOptions)
141
-
142
- if (support.hasTouch && this.options.useTouch) {
129
+ if (sniffer.isTouch && this.options.useTouch) {
143
130
  this.el.addEventListener("touchstart", this.onTouchStart, this.listenerOptions)
144
131
  this.el.addEventListener("touchmove", this.onTouchMove, this.listenerOptions)
145
132
  }
146
133
 
147
- if (support.hasPointer && support.hasTouchWin) {
148
- this.bodyTouchAction = document.body.style.msTouchAction
149
- document.body.style.msTouchAction = "none"
150
- this.el.addEventListener("MSPointerDown", this.onTouchStart, true)
151
- this.el.addEventListener("MSPointerMove", this.onTouchMove, true)
152
- }
153
-
154
- if (support.hasKeyDown && this.options.useKeyboard) document.addEventListener("keydown", this.onKeyDown)
134
+ if (this.options.useKeyboard) document.addEventListener("keydown", this.onKeyDown)
155
135
  }
156
136
 
157
137
  unbind = () => {
158
- const support = supportMouseTouch()
159
-
160
- if (support.hasWheelEvent) this.el.removeEventListener("wheel", this.onWheel)
161
- if (support.hasMouseWheelEvent) this.el.removeEventListener("mousewheel", this.onMouseWheel)
162
- if (support.hasTouch) {
138
+ this.el.removeEventListener("wheel", this.onWheel)
139
+ if (sniffer.isTouch) {
163
140
  this.el.removeEventListener("touchstart", this.onTouchStart)
164
141
  this.el.removeEventListener("touchmove", this.onTouchMove)
165
142
  }
166
- if (support.hasPointer && support.hasTouchWin) {
167
- document.body.style.msTouchAction = this.bodyTouchAction
168
- this.el.removeEventListener("MSPointerDown", this.onTouchStart, true)
169
- this.el.removeEventListener("MSPointerMove", this.onTouchMove, true)
170
- }
171
- if (support.hasKeyDown && this.options.useKeyboard)
172
- document.removeEventListener("keydown", this.onKeyDown)
143
+ if (this.options.useKeyboard) document.removeEventListener("keydown", this.onKeyDown)
173
144
  }
174
145
 
175
146
  on = (cb, priority = 0) => {
@@ -192,5 +163,8 @@ export default class VirtualScroll {
192
163
  destroy = () => {
193
164
  this.unbind()
194
165
  emitter.events[EVT_ID] = []
166
+
167
+ document.removeEventListener("focusin", this._onFocusIn)
168
+ document.removeEventListener("focusout", this._onFocusOut)
195
169
  }
196
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-kit",
3
- "version": "3.0.9",
3
+ "version": "3.0.12",
4
4
  "description": "Stellar frontend core for Non-Linear Studio projects.",
5
5
  "type": "module",
6
6
  "main": "./core/index.js",
@@ -33,7 +33,7 @@ export default class Observer extends Emitter {
33
33
  end,
34
34
  scrub,
35
35
  once,
36
- markers,
36
+ markers: markers || kitStore.flags.isDebug,
37
37
  scroller: kitStore.scroller || undefined,
38
38
  onEnter: forward("enter"),
39
39
  onLeave: forward("leave"),
package/utils/debug.js CHANGED
@@ -18,9 +18,21 @@ function fmt(scope) {
18
18
  *
19
19
  * - Controlled via kitStore.flags.isDebug
20
20
  * - No-op when debug is off
21
+ * - Call Debug.init() to auto-enable via ?debug URL param
21
22
  */
22
23
 
23
24
  export const Debug = {
25
+ /**
26
+ * Auto-enable debug mode if the URL contains a `?debug` query parameter.
27
+ * e.g. `https://example.com/?debug=1` or `?debug`
28
+ * Call this early (e.g. in Application.initialize).
29
+ */
30
+ init() {
31
+ const params = new URLSearchParams(window.location.search)
32
+ if (params.has("debug")) {
33
+ this.enable()
34
+ }
35
+ },
24
36
  enable() {
25
37
  kitStore.flags.isDebug = true
26
38
  },
package/utils/index.js CHANGED
@@ -5,6 +5,6 @@ export { offset } from "./offset"
5
5
  export { getViewport, getWindowSizes, setViewportHeight } from "./window"
6
6
  export { sniffer } from "./sniffer"
7
7
  export { lerp, clamp, norm, round } from "./math"
8
- export { supportWebp, supportMouseTouch } from "./support"
8
+ export { supportWebp } from "./support"
9
9
  export { listener } from "./listener"
10
10
  export { raf2 } from "./timing"
package/utils/sniffer.js CHANGED
@@ -2,20 +2,12 @@
2
2
  export const sniffer = {
3
3
  uA: navigator.userAgent.toLowerCase(),
4
4
 
5
- get isWindowsMobile() {
6
- return /windows phone|iemobile|wpdesktop/.test(this.uA)
7
- },
8
-
9
- get isMobileOpera() {
10
- return /opera mini/i.test(this.uA)
11
- },
12
-
13
5
  get isIOS() {
14
6
  return /iphone|ipad|ipod/i.test(this.uA)
15
7
  },
16
8
 
17
9
  get isIpad() {
18
- return !this.isWindowsMobile && /ipad/i.test(this.uA) && this.isIOS
10
+ return /ipad/i.test(this.uA) && this.isIOS
19
11
  },
20
12
 
21
13
  get isLatestIpad() {
@@ -23,15 +15,15 @@ export const sniffer = {
23
15
  },
24
16
 
25
17
  get isIphone() {
26
- return !this.isWindowsMobile && /iphone/i.test(this.uA) && this.isIOS
18
+ return /iphone/i.test(this.uA) && this.isIOS
27
19
  },
28
20
 
29
21
  get isMobileAndroid() {
30
- return !this.isWindowsMobile && /android.*mobile/.test(this.uA)
22
+ return /android.*mobile/.test(this.uA)
31
23
  },
32
24
 
33
25
  get isTabletAndroid() {
34
- return !this.isWindowsMobile && !this.isMobileAndroid && /android/i.test(this.uA)
26
+ return !this.isMobileAndroid && /android/i.test(this.uA)
35
27
  },
36
28
 
37
29
  get isAndroid() {
@@ -39,7 +31,7 @@ export const sniffer = {
39
31
  },
40
32
 
41
33
  get isPhone() {
42
- return this.isMobileAndroid || (this.isIOS && !this.isIpad) || this.isWindowsMobile
34
+ return this.isMobileAndroid || (this.isIOS && !this.isIpad)
43
35
  },
44
36
 
45
37
  get isTablet() {
@@ -62,18 +54,6 @@ export const sniffer = {
62
54
  return this.uA.includes("opr")
63
55
  },
64
56
 
65
- get isIE11() {
66
- return !window.ActiveXObject && "ActiveXObject" in window
67
- },
68
-
69
- get isIE() {
70
- return this.uA.includes("msie") || this.isIE11 || this.uA.includes("edge")
71
- },
72
-
73
- get isEdge() {
74
- return this.uA.includes("edge")
75
- },
76
-
77
57
  get isWindows() {
78
58
  return /windows/i.test(this.uA)
79
59
  },
@@ -83,8 +63,7 @@ export const sniffer = {
83
63
  window.chrome !== null &&
84
64
  window.chrome !== undefined &&
85
65
  navigator.vendor.toLowerCase() === "google inc." &&
86
- !this.isOpera &&
87
- !this.isEdge
66
+ !this.isOpera
88
67
  )
89
68
  },
90
69
 
@@ -97,13 +76,15 @@ export const sniffer = {
97
76
  },
98
77
 
99
78
  get isTouch() {
100
- return "ontouchstart" in window
79
+ return (
80
+ "ontouchstart" in window ||
81
+ !!window.TouchEvent ||
82
+ (navigator.maxTouchPoints && navigator.maxTouchPoints > 0)
83
+ )
101
84
  },
102
85
 
103
86
  get sniff() {
104
87
  return {
105
- isWindowsMobile: this.isWindowsMobile,
106
- isMobileOpera: this.isMobileOpera,
107
88
  isIOS: this.isIOS,
108
89
  isIpad: this.isIpad,
109
90
  isIphone: this.isIphone,
@@ -113,9 +94,6 @@ export const sniffer = {
113
94
  isFirefox: this.isFirefox,
114
95
  isSafari: this.isSafari,
115
96
  isOpera: this.isOpera,
116
- isIE11: this.isIE11,
117
- isIE: this.isIE,
118
- isEdge: this.isEdge,
119
97
  isChrome: this.isChrome,
120
98
  isMac: this.isMac,
121
99
  isPhone: this.isPhone,
package/utils/support.js CHANGED
@@ -6,18 +6,3 @@ export const supportWebp = () => {
6
6
  }
7
7
  return false
8
8
  }
9
-
10
- export const supportMouseTouch = () => {
11
- return {
12
- hasWheelEvent: "onwheel" in document,
13
- hasMouseWheelEvent: "onmousewheel" in document,
14
- hasTouch:
15
- "ontouchstart" in window ||
16
- window.TouchEvent ||
17
- (navigator.maxTouchPoints && navigator.maxTouchPoints > 0),
18
- hasTouchWin: navigator.msMaxTouchPoints && navigator.msMaxTouchPoints > 1,
19
- hasPointer: !!window.navigator.msPointerEnabled,
20
- hasKeyDown: "onkeydown" in document,
21
- isFirefox: navigator.userAgent.indexOf("Firefox") > -1,
22
- }
23
- }