@linear_non/stellar-kit 2.1.6 → 2.1.8

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,80 @@
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
+ }
28
+
29
+ const els = qsa(selector)
30
+ if (!els.length) return
14
31
 
15
- data.hasTick = obj.hasTick || false
16
- data.hasAnimateIn = obj.hasAnimateIn || false
17
- data.hasAnimateOut = obj.hasAnimateOut || false
18
- data.hasAnimateOnScroll = obj.hasAnimateOnScroll || false
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
- })
70
+ this._call("resize")
60
71
  }
61
72
 
62
73
  smoothResize() {
63
- this.components.forEach(({ component }) => {
64
- component?.smoothResize()
65
- })
74
+ this._call("smoothResize")
66
75
  }
67
76
 
68
77
  destroy() {
69
- this.components.forEach(({ component }) => {
70
- component?.destroy()
71
- })
78
+ this._call("destroy")
72
79
  }
73
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-kit",
3
- "version": "2.1.6",
3
+ "version": "2.1.8",
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,42 +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 remaining = 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)
9
26
 
10
- data.forEach(el => {
11
- const dataset = el.dataset.split || ""
12
- const parts = dataset.split(",")
13
27
  const classes = {}
14
28
 
15
- parts.forEach((type, i) => {
16
- const t = type.trim()
17
- if (t === "lines") classes.linesClass = `line-${i}`
18
- if (t === "words") classes.wordsClass = `word-${i}`
19
- if (t === "chars") classes.charsClass = `char-${i} chr-++`
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`
20
33
  })
21
34
 
35
+ const typeString = types.length ? types.join(",") : raw
36
+
22
37
  const splitInstance = new SplitText(el, {
23
- type: dataset,
38
+ type: typeString,
24
39
  ...classes,
25
40
  })
26
41
 
27
42
  splits.push(splitInstance)
28
- remaining -= 1
29
43
 
30
- if (remaining === 0) {
31
- setTimeout(() => {
32
- emitter.emit(EVENTS.APP_SPLITTEXT_READY, splits)
33
- }, 0)
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)
34
49
  }
35
50
  })
36
51
 
52
+ queueMicrotask(() => {
53
+ emitter.emit(EVENTS.APP_SPLITTEXT_READY, splits, groups)
54
+ })
55
+
37
56
  return emitter
38
57
  }
39
58
 
40
- export const reverseSplit = data => {
41
- data.forEach(split => split.revert())
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
+ }
42
66
  }