@linear_non/stellar-libs 1.0.42 → 1.0.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-libs",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "Reusable JavaScript libraries for Non-Linear Studio projects.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -25,7 +25,7 @@
25
25
  "author": "Non-Linear Studio",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "@linear_non/stellar-kit": "^2.1.11"
28
+ "@linear_non/stellar-kit": "^2.1.17"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@linear_non/prettier-config": "^1.0.6",
@@ -12,16 +12,9 @@ export default class Smooth {
12
12
  this.sections = null
13
13
  this.scrollbar = null
14
14
  this.dpr = Math.max(1, Math.round(window.devicePixelRatio || 1))
15
-
16
15
  this.init()
17
16
  }
18
17
 
19
- /**
20
- * - top / bottom: virtual layout positions
21
- * - offset: parallax centering adjustment
22
- * - speed: dataset speed multiplier
23
- * - parent: optional reference to another section for nested offsets
24
- */
25
18
  getSections() {
26
19
  const { isSmooth } = kitStore.flags
27
20
  if (!this.elems || !isSmooth) return
@@ -31,21 +24,17 @@ export default class Smooth {
31
24
 
32
25
  this.elems.forEach(el => {
33
26
  el.style.transform = "translate3d(0, 0, 0)"
34
-
35
- const speed = el.dataset.speed || 1
27
+ const raw = el.dataset.speed != null ? parseFloat(el.dataset.speed) : 1
28
+ const speed = Number.isFinite(raw) ? raw : 1
36
29
  const { height, offset } = this.getVars(el, speed)
37
30
 
38
31
  const top = cursor
39
32
  const bottom = top + height
40
- cursor = bottom
41
33
 
42
- let parent = el.parentNode.closest("[data-smooth]")
34
+ cursor = bottom
43
35
 
44
- if (parent) {
45
- this.sections.some(obj => {
46
- if (obj.el === parent) parent = obj
47
- })
48
- }
36
+ const parentEl = el.parentElement?.closest?.("[data-smooth]")
37
+ const parent = parentEl ? this.sections.find(s => s.el === parentEl) || null : null
49
38
 
50
39
  this.sections.push({
51
40
  el,
@@ -56,11 +45,14 @@ export default class Smooth {
56
45
  speed,
57
46
  out: true,
58
47
  transform: 0,
48
+ _roundedY: 0,
59
49
  })
60
50
  })
61
51
  }
62
52
 
63
53
  tick = ({ current }) => {
54
+ const { isSmooth, isResizing } = kitStore.flags
55
+ if (!isSmooth || isResizing) return
64
56
  this.current = current
65
57
  this.transformSections()
66
58
  }
@@ -71,11 +63,14 @@ export default class Smooth {
71
63
 
72
64
  this.sections.forEach(section => {
73
65
  const { isVisible, transform } = this.isVisible(section)
74
-
75
66
  if (isVisible || isResizing || !section.out) {
67
+ const { y, css } = this.getTransform(transform)
68
+ if (y !== section._roundedY) {
69
+ section.el.style.transform = css
70
+ section._roundedY = y
71
+ }
76
72
  section.out = !isVisible
77
73
  section.transform = transform
78
- section.el.style.transform = this.getTransform(transform)
79
74
  }
80
75
  })
81
76
  }
@@ -83,15 +78,11 @@ export default class Smooth {
83
78
  isVisible(section) {
84
79
  const { vh } = kitStore.sizes
85
80
  const { top, bottom, offset, speed, parent } = section
86
-
87
81
  const extra = (parent && parent.transform) || 0
88
-
89
82
  const translate = this.current * speed
90
83
  const transform = translate - offset - extra
91
-
92
84
  const start = top - translate
93
85
  const end = bottom - translate
94
-
95
86
  const isVisible = end > 0 && start < vh
96
87
 
97
88
  return { isVisible, transform }
@@ -100,8 +91,7 @@ export default class Smooth {
100
91
  getTransform(transform) {
101
92
  let y = -transform
102
93
  y = Math.round(y * this.dpr) / this.dpr
103
-
104
- return `translate3d(0, ${y}px, 0)`
94
+ return { y, css: `translate3d(0, ${y}px, 0)` }
105
95
  }
106
96
 
107
97
  getVars(el, speed) {
@@ -119,26 +109,32 @@ export default class Smooth {
119
109
 
120
110
  resize = () => {
121
111
  const { isSmooth } = kitStore.flags
112
+
122
113
  if (!isSmooth) return
123
114
 
124
- this.update(this.elems)
115
+ this.scroll?.setScrollBounds()
116
+
117
+ const { fh } = kitStore.sizes
118
+ this.current = Math.min(Math.max(this.current, 0), fh || 0)
119
+
120
+ this.getSections()
125
121
  emitter.emit(EVENTS.APP_SMOOTH_RESIZE)
122
+ this.transformSections()
123
+ this.scrollbar?.update()
126
124
  }
127
125
 
128
126
  update(elems) {
129
127
  kitStore.flags.isResizing = true
130
-
131
128
  this.scroll.setScrollBounds()
132
129
  this.elems = elems || qsa("[data-smooth]")
133
130
  this.scrollbar?.update()
134
-
135
131
  this.getSections()
136
132
  this.transformSections()
137
-
138
133
  kitStore.flags.isResizing = false
139
134
  }
140
135
 
141
136
  clean() {
137
+ if (this.sections) this.sections.forEach(s => (s.el.style.transform = ""))
142
138
  this.elems = this.sections = null
143
139
  }
144
140
 
@@ -154,15 +150,15 @@ export default class Smooth {
154
150
 
155
151
  destroy() {
156
152
  this.off()
153
+ this.scrollbar?.destroy()
157
154
  this.clean()
158
155
  }
159
156
 
160
157
  init(elems) {
161
158
  this.elems = elems || qsa("[data-smooth]")
162
159
 
163
- const container = kitStore.currentPage
164
160
  this.scrollbar = new Scrollbar({
165
- container,
161
+ container: kitStore.currentPage,
166
162
  })
167
163
 
168
164
  this.on()
@@ -1,4 +1,4 @@
1
- import { EVENTS, PRIORITY, emitter } from "@linear_non/stellar-kit/events"
1
+ import { EVENTS } from "@linear_non/stellar-kit/events"
2
2
  import { Observer, reverseSplit, splitText } from "@linear_non/stellar-kit/plugins"
3
3
 
4
4
  const NOOP = () => {}
@@ -13,64 +13,62 @@ function normalizeTargets(splitTargets) {
13
13
  export default class SplitonScroll {
14
14
  constructor({
15
15
  el,
16
- splitText: splitTargets,
16
+ splitText,
17
17
  isReady,
18
18
  reverse,
19
+ resize,
19
20
  start = "top bottom",
20
21
  end = "bottom top",
21
22
  scrub = false,
22
23
  once = true,
23
24
  }) {
24
25
  this.element = el
25
- this.targets = normalizeTargets(splitTargets)
26
+ this.targets = normalizeTargets(splitText)
26
27
 
27
28
  this.splits = null
28
29
  this.groups = null
29
30
 
30
31
  this.isReadyCallback = isReady || NOOP
31
32
  this.reverseCallback = reverse || NOOP
33
+ this.isResizeCallback = resize || NOOP
32
34
  this.start = start
33
35
  this.end = end
34
36
  this.scrub = scrub
35
37
  this.once = once
36
38
 
37
- this._readyNotified = false
38
- this._resolveReady = null
39
39
  this.ready = new Promise(resolve => {
40
40
  this._resolveReady = resolve
41
41
  })
42
42
 
43
- this._needsResplit = false
44
-
45
- this._onResizeStart = this._onResizeStart.bind(this)
46
- this._onSmoothResize = this._onSmoothResize.bind(this)
47
- this._offResizeStart = null
48
- this._offSmoothResize = null
43
+ this._readyFired = false
44
+ this._rebuilding = false
49
45
 
50
46
  if (!this.element || !this.targets.length) {
51
- const payload = { splits: [], groups: {} }
52
- this._notifyReadyOnce(payload)
47
+ this._notifyReadyOnce([])
53
48
  return
54
49
  }
55
50
 
51
+ this.init()
52
+ }
53
+
54
+ init() {
55
+ // Initialize observer
56
56
  this.addObserver()
57
- this.addResizeListeners()
58
57
 
58
+ // If already in view, handle immediately
59
59
  if (this.observer && (this.observer.isActive || this.observer.progress > 0)) {
60
60
  this.handleEnter()
61
61
  }
62
62
  }
63
63
 
64
- _notifyReadyOnce(payload) {
65
- if (this._readyNotified) return
66
- this._readyNotified = true
67
-
68
- this.isReadyCallback(payload.splits, payload.groups)
64
+ // Set is ready only once
65
+ _notifyReadyOnce(splits, groups) {
66
+ if (this._readyFired) return
67
+ this._readyFired = true
69
68
 
70
- if (this._resolveReady) {
71
- this._resolveReady(payload)
72
- this._resolveReady = null
73
- }
69
+ this.isReadyCallback(splits, groups)
70
+ this._resolveReady?.({ splits, groups })
71
+ this._resolveReady = null
74
72
  }
75
73
 
76
74
  addObserver() {
@@ -86,78 +84,66 @@ export default class SplitonScroll {
86
84
  this.observer.on("leave", () => this.handleLeave())
87
85
  }
88
86
 
89
- addResizeListeners() {
90
- // Raw resize start, clear split
91
- this._offResizeStart = emitter.on(EVENTS.APP_RESIZE, this._onResizeStart, PRIORITY.first)
87
+ async resize() {
88
+ // if not built yet, just build once
89
+ if (!this.splits) return
92
90
 
93
- // Smooth.update() + setScrollBounds()
94
- this._offSmoothResize = emitter.on(EVENTS.APP_SMOOTH_RESIZE, this._onSmoothResize)
95
- }
91
+ // revert then rebuild
92
+ reverseSplit(this.splits)
93
+ this.splits = null
94
+ this.groups = null
96
95
 
97
- removeResizeListeners() {
98
- if (this._offResizeStart) {
99
- this._offResizeStart()
100
- this._offResizeStart = null
101
- }
102
- if (this._offSmoothResize) {
103
- this._offSmoothResize()
104
- this._offSmoothResize = null
105
- }
96
+ // rebuild splits
97
+ await this.refreshSplits()
106
98
  }
107
99
 
108
- _onResizeStart() {
109
- if (!this.element || !this.targets || !this.targets.length) return
110
-
111
- if (this.splits) {
112
- reverseSplit(this.splits)
113
-
114
- this.splits = null
115
- this.groups = null
116
- }
100
+ async handleEnter() {
101
+ const splitEmitter = splitText(this.targets)
117
102
 
118
- this._needsResplit = true
119
- }
103
+ const { splits, groups } = await new Promise(resolve => {
104
+ const onReady = (readySplits, groupMap) => {
105
+ // Clean up listener
106
+ if (splitEmitter.off) {
107
+ splitEmitter.off(EVENTS.APP_SPLITTEXT_READY, onReady)
108
+ }
120
109
 
121
- _onSmoothResize() {
122
- if (!this._needsResplit) return
123
- if (!this.element || !this.targets || !this.targets.length) return
110
+ resolve({
111
+ splits: readySplits || [],
112
+ groups: groupMap || {},
113
+ })
114
+ }
124
115
 
125
- if (this.observer && typeof this.observer.refresh === "function") {
126
- this.observer.refresh()
127
- }
116
+ // Listen for ready event once
117
+ splitEmitter.once(EVENTS.APP_SPLITTEXT_READY, onReady)
118
+ })
128
119
 
129
- if (this.observer && (this.observer.isActive || this.observer.progress > 0)) {
130
- this.handleEnter()
131
- }
120
+ this.splits = splits
121
+ this.groups = groups
132
122
 
133
- this._needsResplit = false
123
+ this._notifyReadyOnce(splits, groups)
134
124
  }
135
125
 
136
- async handleEnter() {
137
- const splitEmitter = splitText(this.targets)
126
+ async refreshSplits() {
127
+ if (this._rebuilding) return
128
+ this._rebuilding = true
138
129
 
130
+ // (Re)build fresh splits
131
+ const emitter = splitText(this.targets)
139
132
  const { splits, groups } = await new Promise(resolve => {
140
133
  const onReady = (readySplits, groupMap) => {
141
- if (typeof splitEmitter.off === "function") {
142
- splitEmitter.off(EVENTS.APP_SPLITTEXT_READY, onReady)
143
- }
144
134
  resolve({
145
135
  splits: readySplits || [],
146
136
  groups: groupMap || {},
147
137
  })
148
138
  }
149
-
150
- if (typeof splitEmitter.once === "function") {
151
- splitEmitter.once(EVENTS.APP_SPLITTEXT_READY, onReady)
152
- } else {
153
- splitEmitter.on(EVENTS.APP_SPLITTEXT_READY, onReady)
154
- }
139
+ emitter.once(EVENTS.APP_SPLITTEXT_READY, onReady)
155
140
  })
156
141
 
157
142
  this.splits = splits
158
143
  this.groups = groups
144
+ this._rebuilding = false
159
145
 
160
- this._notifyReadyOnce({ splits, groups })
146
+ this.isResizeCallback(splits, groups)
161
147
  }
162
148
 
163
149
  handleLeave() {
@@ -179,9 +165,6 @@ export default class SplitonScroll {
179
165
  }
180
166
 
181
167
  destroy() {
182
- this.observer?.kill()
183
- this.removeResizeListeners()
184
-
185
168
  if (this.splits) {
186
169
  reverseSplit(this.splits)
187
170
  }