@linear_non/stellar-kit 2.1.5 → 2.1.7

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.
@@ -1,32 +1,27 @@
1
- // Component.js
2
- import { qs, bounds } from "../utils"
1
+ import { bounds } from "../utils"
3
2
 
4
3
  export default class Component {
5
- constructor(obj) {
6
- this.el = obj.el || null
7
- this.renderer = obj.renderer || null
8
- const id = obj.index || 0
9
- const data = obj.data || null
4
+ constructor({ el = null, index = 0, renderer = null, data = null } = {}) {
5
+ this.el = el
6
+ this.index = index
7
+ this.renderer = renderer
8
+ this.data = data
10
9
 
11
- this.hasTick = data?.hasTick || false
12
- this.hasAnimateIn = data?.hasAnimateIn || false
13
- this.hasAnimateOut = data?.hasAnimateOut || false
14
- this.hasAnimateOnScroll = data?.hasAnimateOnScroll || false
15
-
16
- if (this.el) this.rect = bounds(this.el)
10
+ this.rect = this.el ? bounds(this.el) : null
17
11
  }
18
12
 
19
- setup() {
20
- if (!this.name) return
21
- this.el = qs(`[data-${this.name}]`)
22
- }
13
+ setup() {}
23
14
 
24
15
  animateIn() {}
25
16
  animateOut() {}
26
17
  animateOnScroll() {}
27
18
 
28
- tick(obj) {}
29
- resize() {}
19
+ tick(_ctx) {}
20
+
21
+ resize() {
22
+ if (this.el) this.rect = bounds(this.el)
23
+ }
24
+
30
25
  smoothResize() {}
31
26
 
32
27
  on() {}
@@ -41,6 +36,5 @@ export default class Component {
41
36
  render() {
42
37
  this.setup()
43
38
  this.on()
44
- if (this.hasAnimateOnScroll) this.animateOnScroll()
45
39
  }
46
40
  }
@@ -1,73 +1,76 @@
1
- // Manager.js
2
1
  import { qsa } from "../utils"
3
2
 
4
3
  export default class Manager {
5
- constructor() {
4
+ constructor({ renderer = null, data = {} } = {}) {
6
5
  this.components = []
6
+
7
+ this.renderer = renderer
8
+ this.data = data
7
9
  }
8
10
 
9
- addComponent(obj, renderer) {
10
- const { name, instance } = obj
11
- const els = qsa(`[data-${name}]`)
12
- const data = {}
13
- if (els.length === 0) return
11
+ addComponent(def) {
12
+ if (!def || !def.instance || !def.name) return
13
+
14
+ const { name, instance, data = null } = def
15
+
16
+ // resolve selector from name
17
+ let selector
18
+ if (name.startsWith(".")) {
19
+ // "Class: .c-hero"
20
+ selector = name
21
+ } else if (name.startsWith("[")) {
22
+ // "Dataset: [data-video]"
23
+ selector = name
24
+ } else {
25
+ // "Transform: hero" -> "[data-hero]"
26
+ selector = `[data-${name}]`
27
+ }
14
28
 
15
- data.hasTick = obj.hasTick || false
16
- data.hasAnimateIn = obj.hasAnimateIn || false
17
- data.hasAnimateOut = obj.hasAnimateOut || false
18
- data.hasAnimateOnScroll = obj.hasAnimateOnScroll || false
29
+ const els = qsa(selector)
30
+ if (!els.length) return
31
+
32
+ const mergedData = data ? { ...this.data, ...data } : this.data
19
33
 
20
34
  els.forEach((el, index) => {
21
- const component = new instance({ index, el, data, renderer })
22
- this.components.push({ name, component })
35
+ const c = new instance({
36
+ el,
37
+ index,
38
+ name,
39
+ renderer: this.renderer,
40
+ data: mergedData,
41
+ })
42
+ this.components.push(c)
23
43
  })
24
44
  }
25
45
 
26
- initializeComponents() {
27
- this.components.forEach(({ component }) => {
28
- component?.render()
46
+ _call(method, ...args) {
47
+ this.components.forEach(c => {
48
+ const fn = c[method]
49
+ if (typeof fn === "function") fn.apply(c, args)
29
50
  })
30
51
  }
31
52
 
32
- animateIn() {
33
- this.components.forEach(({ component }) => {
34
- component.hasAnimateIn && component.animateIn()
35
- })
53
+ initialize() {
54
+ this._call("render")
36
55
  }
37
56
 
38
- animateOut() {
39
- this.components.forEach(({ component }) => {
40
- component.hasAnimateOut && component.animateOut()
41
- })
57
+ animateIn() {
58
+ this._call("animateIn")
42
59
  }
43
60
 
44
- animateOnScroll() {
45
- this.components.forEach(({ component }) => {
46
- component.hasAnimateOnScroll && component.animateOnScroll()
47
- })
61
+ animateOut() {
62
+ this._call("animateOut")
48
63
  }
49
64
 
50
- update(obj) {
51
- this.components.forEach(({ component }) => {
52
- component.hasTick && component.tick(obj)
53
- })
65
+ tick(obj) {
66
+ this._call("tick", obj)
54
67
  }
55
68
 
56
69
  resize() {
57
- this.components.forEach(({ component }) => {
58
- component?.resize()
59
- })
60
- }
61
-
62
- smoothResize() {
63
- this.components.forEach(({ component }) => {
64
- component?.smoothResize()
65
- })
70
+ this._call("resize")
66
71
  }
67
72
 
68
73
  destroy() {
69
- this.components.forEach(({ component }) => {
70
- component?.destroy()
71
- })
74
+ this._call("destroy")
72
75
  }
73
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-kit",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "Stellar frontend core for Non-Linear Studio projects.",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -1,6 +1,5 @@
1
- // libs/observer/Observer.js
2
- import { ScrollTrigger } from "../libraries/gsap"
3
1
  import { Emitter } from "../events"
2
+ import { ScrollTrigger } from "../libraries/gsap"
4
3
 
5
4
  const DEFAULTS = {
6
5
  trigger: null,
@@ -14,49 +13,83 @@ const DEFAULTS = {
14
13
  export default class Observer extends Emitter {
15
14
  constructor(opts = {}) {
16
15
  super()
17
- this.opts = { ...DEFAULTS, ...opts }
18
- if (!this.opts.trigger) throw new Error("Observer: `trigger` is required")
16
+
17
+ const merged = { ...DEFAULTS, ...opts }
18
+ if (!merged.trigger) {
19
+ throw new Error("Observer: `trigger` is required")
20
+ }
21
+
22
+ this.opts = merged
23
+
24
+ const { trigger, start, end, scrub, once, markers } = this.opts
25
+ const forward = type => self => this.emit(type, self, this)
19
26
 
20
27
  this.st = ScrollTrigger.create({
21
- trigger: this.opts.trigger,
22
- start: this.opts.start,
23
- end: this.opts.end,
24
- scrub: this.opts.scrub,
25
- once: this.opts.once,
26
- markers: this.opts.markers,
27
- onEnter: self => this.emit("enter", self, this),
28
- onLeave: self => this.emit("leave", self, this),
29
- onEnterBack: self => this.emit("enterBack", self, this),
30
- onLeaveBack: self => this.emit("leaveBack", self, this),
31
- onUpdate: self => this.emit("update", self, this),
28
+ trigger,
29
+ start,
30
+ end,
31
+ scrub,
32
+ once,
33
+ markers,
34
+ onEnter: forward("enter"),
35
+ onLeave: forward("leave"),
36
+ onEnterBack: forward("enterBack"),
37
+ onLeaveBack: forward("leaveBack"),
38
+ onUpdate: forward("update"),
32
39
  })
33
40
 
34
- if (this.opts.listeners) {
35
- for (const [type, fn] of Object.entries(this.opts.listeners)) {
41
+ const listeners = this.opts.listeners
42
+ if (listeners) {
43
+ for (const [type, fn] of Object.entries(listeners)) {
36
44
  this.on(type, fn)
37
45
  }
38
46
  }
39
47
  }
40
48
 
49
+ // allow ["enter","enterBack"]
50
+ on(types, fn) {
51
+ if (Array.isArray(types)) {
52
+ types.forEach(t => super.on(t, fn))
53
+ } else {
54
+ super.on(types, fn)
55
+ }
56
+ return this
57
+ }
58
+
59
+ off(types, fn) {
60
+ if (Array.isArray(types)) {
61
+ types.forEach(t => super.off(t, fn))
62
+ } else {
63
+ super.off(types, fn)
64
+ }
65
+ return this
66
+ }
67
+
41
68
  refresh() {
42
69
  this.st?.refresh()
43
70
  return this
44
71
  }
72
+
45
73
  enable() {
46
74
  this.st?.enable()
47
75
  return this
48
76
  }
77
+
49
78
  disable() {
50
79
  this.st?.disable()
51
80
  return this
52
81
  }
53
82
 
54
- update({ start, end, scrub, markers } = {}) {
55
- if (!this.st) return this
83
+ update(next) {
84
+ if (!this.st || !next) return this
85
+
86
+ const { start, end, scrub, markers } = next
87
+
56
88
  if (start !== undefined) this.st.vars.start = start
57
89
  if (end !== undefined) this.st.vars.end = end
58
90
  if (scrub !== undefined) this.st.vars.scrub = scrub
59
91
  if (markers !== undefined) this.st.vars.markers = markers
92
+
60
93
  this.st.refresh()
61
94
  return this
62
95
  }
@@ -68,13 +101,14 @@ export default class Observer extends Emitter {
68
101
  return this
69
102
  }
70
103
 
71
- // Handy getters
72
104
  get progress() {
73
105
  return this.st?.progress ?? 0
74
106
  }
107
+
75
108
  get isActive() {
76
109
  return !!this.st?.isActive
77
110
  }
111
+
78
112
  get direction() {
79
113
  return this.st?.direction ?? 1
80
114
  }
@@ -1,56 +1,66 @@
1
- // splitText.js
2
1
  import { Emitter, EVENTS } from "../events"
3
2
  import { SplitText } from "../libraries/gsap"
4
3
 
5
4
  export const splitText = data => {
6
5
  const emitter = new Emitter()
7
6
  const splits = []
8
- let splitted = 0
9
- const totalSplits = data.length
7
+ const groups = {}
8
+
9
+ // Normalize to array and drop nulls/undefined
10
+ const items = Array.from(data || []).filter(Boolean)
11
+
12
+ // When no items, emit ready immediately
13
+ if (items.length === 0) {
14
+ queueMicrotask(() => {
15
+ emitter.emit(EVENTS.APP_SPLITTEXT_READY, splits, groups)
16
+ })
17
+ return emitter
18
+ }
19
+
20
+ items.forEach((el, elIndex) => {
21
+ const raw = el.dataset.split || ""
22
+ const types = raw
23
+ .split(/[,\s]+/)
24
+ .map(t => t.trim())
25
+ .filter(Boolean)
10
26
 
11
- data.forEach(el => {
12
- const dataset = el.dataset.split
13
- const data = dataset.split(",")
14
- const split = {}
15
27
  const classes = {}
16
28
 
17
- split.el = el
18
- splitted++
29
+ types.forEach(t => {
30
+ if (t === "lines") classes.linesClass = `line-${elIndex}`
31
+ if (t === "words") classes.wordsClass = `word-${elIndex}`
32
+ if (t === "chars") classes.charsClass = `char-${elIndex} chr`
33
+ })
34
+
35
+ const typeString = types.length ? types.join(",") : raw
19
36
 
20
- data.forEach((type, i) => {
21
- const filter = type.trim()
22
- if (filter == "lines") classes.linesClass = `line-${i}`
23
- if (filter == "words") classes.wordsClass = `word-${i}`
24
- if (filter == "chars") classes.charsClass = `char-${i} chr-++`
37
+ const splitInstance = new SplitText(el, {
38
+ type: typeString,
39
+ ...classes,
25
40
  })
26
41
 
27
- document.fonts.ready
28
- .then(() => {
29
- console.log("--FONT READY--")
30
-
31
- const splitText = new SplitText(el, {
32
- type: dataset.toString(),
33
- ...classes,
34
- })
35
-
36
- splits.push(splitText)
37
-
38
- if (splitted == totalSplits) {
39
- setTimeout(() => {
40
- emitter.emit(EVENTS.APP_SPLITTEXT_READY, splits)
41
- }, 0) // Use a small delay to ensure the event listener is set up
42
- }
43
- })
44
- .catch(error => {
45
- console.error("Error loading fonts:", error)
46
- })
42
+ splits.push(splitInstance)
43
+
44
+ // Grouping via data-split-group attribute if present
45
+ const groupName = el.dataset.splitGroup
46
+ if (groupName) {
47
+ if (!groups[groupName]) groups[groupName] = []
48
+ groups[groupName].push(splitInstance)
49
+ }
50
+ })
51
+
52
+ queueMicrotask(() => {
53
+ emitter.emit(EVENTS.APP_SPLITTEXT_READY, splits, groups)
47
54
  })
48
55
 
49
56
  return emitter
50
57
  }
51
58
 
52
- export const reverseSplit = data => {
53
- data.forEach(split => {
54
- split.revert()
55
- })
59
+ export const reverseSplit = splits => {
60
+ if (!splits) return
61
+ for (const split of splits) {
62
+ if (split && typeof split.revert === "function") {
63
+ split.revert()
64
+ }
65
+ }
56
66
  }