@linear_non/stellar-kit 1.1.2 → 1.1.4
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/FontLoader.js +47 -0
- package/classes/ImageLoader.js +63 -0
- package/classes/MasterLoader.js +50 -0
- package/classes/index.js +4 -1
- package/events/Emitter.js +6 -0
- package/index.js +10 -0
- package/kitStore.js +2 -0
- package/package.json +3 -1
- package/utils/grid.js +154 -0
- package/utils/index.js +2 -0
- package/workers/image-loader.js +10 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// FontLoader.js
|
|
2
|
+
import { emitter, EVENTS } from "../events"
|
|
3
|
+
|
|
4
|
+
export default class FontLoader {
|
|
5
|
+
static type = "fonts"
|
|
6
|
+
static events = { PROGRESS: EVENTS.APP_FONTS_PROGRESS, LOADED: EVENTS.APP_FONTS_LOADED }
|
|
7
|
+
constructor({ fonts = [] }) {
|
|
8
|
+
// fonts = [{ family: "Inter", url: "/fonts/Inter.woff2" }, ...]
|
|
9
|
+
|
|
10
|
+
this.fonts = fonts
|
|
11
|
+
this.loaded = 0
|
|
12
|
+
this.total = fonts.length
|
|
13
|
+
this.isLoaded = false
|
|
14
|
+
this.items = []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async load() {
|
|
18
|
+
const promises = this.fonts.map(font => this.loadSingle(font))
|
|
19
|
+
await Promise.all(promises)
|
|
20
|
+
|
|
21
|
+
this.isLoaded = true
|
|
22
|
+
emitter.emit(EVENTS.APP_FONTS_LOADED, this.items)
|
|
23
|
+
return this.items
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async loadSingle({ family, url, descriptors = {} }) {
|
|
27
|
+
const font = new FontFace(family, `url(${url})`, descriptors)
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const loadedFace = await font.load()
|
|
31
|
+
document.fonts.add(loadedFace)
|
|
32
|
+
this.items.push(loadedFace)
|
|
33
|
+
this.loaded++
|
|
34
|
+
|
|
35
|
+
emitter.emit(EVENTS.APP_FONTS_PROGRESS, {
|
|
36
|
+
loaded: this.loaded,
|
|
37
|
+
total: this.total,
|
|
38
|
+
percent: Math.round((this.loaded / this.total) * 100),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return loadedFace
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error("FontLoader error:", err)
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ImageLoader.js
|
|
2
|
+
import { emitter, EVENTS } from "../events"
|
|
3
|
+
|
|
4
|
+
export default class ImageLoader {
|
|
5
|
+
static type = "images"
|
|
6
|
+
static events = { PROGRESS: EVENTS.APP_IMAGES_PROGRESS, LOADED: EVENTS.APP_IMAGES_LOADED }
|
|
7
|
+
constructor({ origin, fileName, extension, total, urls = [] }) {
|
|
8
|
+
this.origin = origin
|
|
9
|
+
this.fileName = fileName
|
|
10
|
+
this.extension = extension
|
|
11
|
+
this.total = total
|
|
12
|
+
this.urls = urls
|
|
13
|
+
|
|
14
|
+
this.images = []
|
|
15
|
+
this.loaded = 0
|
|
16
|
+
this.isLoaded = false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async load() {
|
|
20
|
+
// If explicit URLs are passed, use them
|
|
21
|
+
const urls = this.urls.length
|
|
22
|
+
? this.urls
|
|
23
|
+
: Array.from({ length: this.total }, (_, i) => `${this.origin}/${this.fileName}${i}.${this.extension}`)
|
|
24
|
+
|
|
25
|
+
this.total = urls.length
|
|
26
|
+
|
|
27
|
+
const promises = urls.map((url, i) => this.loadSingle(url, i))
|
|
28
|
+
await Promise.all(promises)
|
|
29
|
+
|
|
30
|
+
this.isLoaded = true
|
|
31
|
+
emitter.emit(EVENTS.APP_IMAGES_LOADED, this.images)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
loadSingle(url, index) {
|
|
35
|
+
return new Promise(resolve => {
|
|
36
|
+
const worker = new Worker(
|
|
37
|
+
new URL("../workers/image-loader.js", import.meta.url),
|
|
38
|
+
{ type: "module" } // important
|
|
39
|
+
)
|
|
40
|
+
worker.postMessage({ url, index })
|
|
41
|
+
|
|
42
|
+
worker.addEventListener("message", e => {
|
|
43
|
+
const { blob, index } = e.data
|
|
44
|
+
const img = new Image()
|
|
45
|
+
img.src = URL.createObjectURL(blob)
|
|
46
|
+
|
|
47
|
+
img.onload = () => {
|
|
48
|
+
this.images[index] = img
|
|
49
|
+
this.loaded++
|
|
50
|
+
|
|
51
|
+
emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
|
|
52
|
+
loaded: this.loaded,
|
|
53
|
+
total: this.total,
|
|
54
|
+
percent: Math.round((this.loaded / this.total) * 100),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
resolve(img)
|
|
58
|
+
worker.terminate()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//MasterLoader.js
|
|
2
|
+
import { emitter, EVENTS } from "../events"
|
|
3
|
+
|
|
4
|
+
export default class MasterLoader {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.loaders = []
|
|
7
|
+
this.results = {}
|
|
8
|
+
this.total = 0
|
|
9
|
+
this.loaded = 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
add(loaderClass, options) {
|
|
13
|
+
const loader = new loaderClass(options)
|
|
14
|
+
this.loaders.push({ loaderClass, loader })
|
|
15
|
+
this.total += loader.total || 0
|
|
16
|
+
return loader
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async loadAll() {
|
|
20
|
+
const promises = this.loaders.map(({ loaderClass, loader }) => {
|
|
21
|
+
return new Promise(resolve => {
|
|
22
|
+
const onProgress = () => {
|
|
23
|
+
this.loaded++
|
|
24
|
+
emitter.emit(EVENTS.APP_MASTER_PROGRESS, {
|
|
25
|
+
loaded: this.loaded,
|
|
26
|
+
total: this.total,
|
|
27
|
+
percent: Math.round((this.loaded / this.total) * 100),
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const onLoaded = items => {
|
|
32
|
+
const type = loaderClass.type
|
|
33
|
+
this.results[type] = (this.results[type] || []).concat(items)
|
|
34
|
+
// cleanup
|
|
35
|
+
emitter.off?.(loaderClass.events.PROGRESS, onProgress)
|
|
36
|
+
emitter.off?.(loaderClass.events.LOADED, onLoaded)
|
|
37
|
+
resolve()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
emitter.on(loaderClass.events.PROGRESS, onProgress)
|
|
41
|
+
emitter.on(loaderClass.events.LOADED, onLoaded)
|
|
42
|
+
|
|
43
|
+
loader.load()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await Promise.all(promises)
|
|
48
|
+
emitter.emit(EVENTS.APP_MASTER_LOADED, this.results)
|
|
49
|
+
}
|
|
50
|
+
}
|
package/classes/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import Component from "./Component"
|
|
2
2
|
import Manager from "./Manager"
|
|
3
|
+
import MasterLoader from "./MasterLoader"
|
|
4
|
+
import ImageLoader from "./ImageLoader"
|
|
5
|
+
import FontLoader from "./FontLoader"
|
|
3
6
|
|
|
4
|
-
export { Component, Manager }
|
|
7
|
+
export { Component, Manager, MasterLoader, ImageLoader, FontLoader }
|
package/events/Emitter.js
CHANGED
|
@@ -53,6 +53,12 @@ const EVENTS = {
|
|
|
53
53
|
APP_MOUSEDOWN: "mousedown",
|
|
54
54
|
APP_MOUSEUP: "mouseup",
|
|
55
55
|
APP_SPLITTEXT_READY: "splittext:ready",
|
|
56
|
+
APP_IMAGES_LOADED: "images:loaded",
|
|
57
|
+
APP_IMAGES_PROGRESS: "images:progress",
|
|
58
|
+
APP_MASTER_LOADED: "master:loaded",
|
|
59
|
+
APP_MASTER_PROGRESS: "master:progress",
|
|
60
|
+
APP_FONTS_PROGRESS: "fonts:progress",
|
|
61
|
+
APP_FONTS_LOADED: "fonts:loaded",
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
const PRIORITY = {
|
package/index.js
CHANGED
|
@@ -13,4 +13,14 @@ export function setupKit({ isSmooth = sniffer.isDesktop } = {}) {
|
|
|
13
13
|
kitStore.mouse = new Mouse()
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export function setSizes(d = kitStore.sizes.d, m = kitStore.sizes.m) {
|
|
17
|
+
// update store if values are provided
|
|
18
|
+
kitStore.sizes.d = d
|
|
19
|
+
kitStore.sizes.m = m
|
|
20
|
+
|
|
21
|
+
// set css vars
|
|
22
|
+
document.documentElement.style.setProperty("--desktop", d)
|
|
23
|
+
document.documentElement.style.setProperty("--mobile", m)
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
export { kitStore }
|
package/kitStore.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linear_non/stellar-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Stellar frontend core for Non-Linear Studio projects.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
8
|
"./utils": "./utils/index.js",
|
|
9
9
|
"./classes": "./classes/index.js",
|
|
10
|
+
"./workers": "./workers/*",
|
|
10
11
|
"./events": "./events/index.js",
|
|
11
12
|
"./gsap": "./libraries/gsap/index.js"
|
|
12
13
|
},
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"utils/",
|
|
23
24
|
"libraries/",
|
|
24
25
|
"libraries/gsap/",
|
|
26
|
+
"workers/",
|
|
25
27
|
"kitStore.js",
|
|
26
28
|
"index.js"
|
|
27
29
|
],
|
package/utils/grid.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import kitStore from "../kitStore"
|
|
2
|
+
import { getWindowSizes } from "./window"
|
|
3
|
+
import { emitter, EVENTS } from "../events"
|
|
4
|
+
import { gsap } from "../libraries/gsap"
|
|
5
|
+
|
|
6
|
+
export default class Grid {
|
|
7
|
+
constructor(obj) {
|
|
8
|
+
this.d = obj.desktop
|
|
9
|
+
this.m = obj.mobile
|
|
10
|
+
this.state = { toggle: false }
|
|
11
|
+
this.sizes = getWindowSizes()
|
|
12
|
+
|
|
13
|
+
this.handleKeyDown = e => this.onVisibilityToggle(e)
|
|
14
|
+
|
|
15
|
+
this.build()
|
|
16
|
+
this.on()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
build() {
|
|
20
|
+
this.el = document.createElement("div")
|
|
21
|
+
this.el.classList.add("-grid")
|
|
22
|
+
Object.assign(this.el.style, {
|
|
23
|
+
position: "fixed",
|
|
24
|
+
zIndex: "99999",
|
|
25
|
+
height: "100%",
|
|
26
|
+
inset: "0",
|
|
27
|
+
pointerEvents: "none",
|
|
28
|
+
width: "100%",
|
|
29
|
+
opacity: "0",
|
|
30
|
+
visibility: "hidden",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
this.dContainer = document.createElement("div")
|
|
34
|
+
this.dContainer.classList.add("-grid-desktop")
|
|
35
|
+
this.el.appendChild(this.dContainer)
|
|
36
|
+
|
|
37
|
+
this.mContainer = document.createElement("div")
|
|
38
|
+
this.mContainer.classList.add("-grid-mobile")
|
|
39
|
+
this.el.appendChild(this.mContainer)
|
|
40
|
+
|
|
41
|
+
const flexFull = {
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
alignItems: "stretch",
|
|
44
|
+
boxSizing: "border-box",
|
|
45
|
+
height: "100%",
|
|
46
|
+
width: "100%",
|
|
47
|
+
gap: "0",
|
|
48
|
+
display: "flex",
|
|
49
|
+
}
|
|
50
|
+
Object.assign(this.dContainer.style, flexFull)
|
|
51
|
+
Object.assign(this.mContainer.style, flexFull)
|
|
52
|
+
|
|
53
|
+
this.gridSetup(this.dContainer, true)
|
|
54
|
+
this.gridSetup(this.mContainer, false)
|
|
55
|
+
this.applyBreakpointVisibility()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
attach() {
|
|
59
|
+
if (!this.el.isConnected) document.body.appendChild(this.el)
|
|
60
|
+
}
|
|
61
|
+
detach() {
|
|
62
|
+
if (this.el?.isConnected) this.el.remove()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
gridSetup(container, isDesktop = true) {
|
|
66
|
+
const { wrap, gap, count } = isDesktop ? this.d : this.m
|
|
67
|
+
const col = this.checkCols(wrap, gap, count, isDesktop)
|
|
68
|
+
|
|
69
|
+
container.innerHTML = ""
|
|
70
|
+
if (wrap > 0) {
|
|
71
|
+
container.style.setProperty("padding-left", `${wrap / 10}rem`)
|
|
72
|
+
container.style.setProperty("padding-right", `${wrap / 10}rem`)
|
|
73
|
+
}
|
|
74
|
+
container.style.setProperty("gap", gap > 0 ? `${gap / 10}rem` : "0")
|
|
75
|
+
|
|
76
|
+
const frag = document.createDocumentFragment()
|
|
77
|
+
for (let i = 0; i < count; i++) {
|
|
78
|
+
const child = document.createElement("div")
|
|
79
|
+
child.classList.add("row")
|
|
80
|
+
Object.assign(child.style, {
|
|
81
|
+
background: "red",
|
|
82
|
+
width: `${col / 10}rem`,
|
|
83
|
+
height: "100%",
|
|
84
|
+
opacity: i % 2 === 0 ? "0.4" : "0.6",
|
|
85
|
+
})
|
|
86
|
+
frag.appendChild(child)
|
|
87
|
+
}
|
|
88
|
+
container.appendChild(frag)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
checkCols(wrap, gap, count, isDesktop = true) {
|
|
92
|
+
const { sizes } = kitStore
|
|
93
|
+
const base = isDesktop ? sizes.d : sizes.m
|
|
94
|
+
const w = wrap * 2
|
|
95
|
+
const g = (count - 1) * gap
|
|
96
|
+
return (base - w - g) / count
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
applyBreakpointVisibility() {
|
|
100
|
+
const isDesktop = this.sizes.M_UP
|
|
101
|
+
this.dContainer.style.display = isDesktop ? "flex" : "none"
|
|
102
|
+
this.mContainer.style.display = isDesktop ? "none" : "flex"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
rebuildForBreakpoint() {
|
|
106
|
+
this.applyBreakpointVisibility()
|
|
107
|
+
if (this.sizes.M_UP) {
|
|
108
|
+
this.gridSetup(this.dContainer, true)
|
|
109
|
+
} else {
|
|
110
|
+
this.gridSetup(this.mContainer, false)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
resize = () => {
|
|
115
|
+
this.sizes = getWindowSizes()
|
|
116
|
+
this.rebuildForBreakpoint()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onVisibilityToggle(e) {
|
|
120
|
+
const isCtrlPressed = e.metaKey || e.ctrlKey
|
|
121
|
+
if (e.type !== "keydown" || e.repeat) return
|
|
122
|
+
if (isCtrlPressed && (e.key?.toLowerCase() === "g" || e.code === "KeyG")) {
|
|
123
|
+
if (!this.state.toggle) {
|
|
124
|
+
this.attach()
|
|
125
|
+
gsap.to(this.el, { duration: 0.35, autoAlpha: 1 })
|
|
126
|
+
} else {
|
|
127
|
+
gsap.to(this.el, {
|
|
128
|
+
duration: 0.35,
|
|
129
|
+
autoAlpha: 0,
|
|
130
|
+
onComplete: () => this.detach(),
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
this.state.toggle = !this.state.toggle
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
on() {
|
|
138
|
+
document.addEventListener("keydown", this.handleKeyDown)
|
|
139
|
+
emitter.on(EVENTS.APP_RESIZE, this.resize)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
off() {
|
|
143
|
+
document.removeEventListener("keydown", this.handleKeyDown)
|
|
144
|
+
emitter.off(EVENTS.APP_RESIZE, this.resize)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
destroy() {
|
|
148
|
+
this.off()
|
|
149
|
+
this.detach()
|
|
150
|
+
this.el = null
|
|
151
|
+
this.dContainer = null
|
|
152
|
+
this.mContainer = null
|
|
153
|
+
}
|
|
154
|
+
}
|
package/utils/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Grid from "./grid"
|
|
1
2
|
export { qs, qsa, getid, gettag } from "./selector"
|
|
2
3
|
export { bounds } from "./bounds"
|
|
3
4
|
export { offset } from "./offset"
|
|
@@ -7,3 +8,4 @@ export { lerp, clamp, norm, round } from "./math"
|
|
|
7
8
|
export { supportWebp, supportMouseTouch } from "./support"
|
|
8
9
|
export { listener } from "./listener"
|
|
9
10
|
export { splitText, reverseSplit } from "./splitText"
|
|
11
|
+
export { Grid }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
self.addEventListener("message", async e => {
|
|
2
|
+
const { url, index } = e.data
|
|
3
|
+
try {
|
|
4
|
+
const res = await fetch(url)
|
|
5
|
+
const blob = await res.blob()
|
|
6
|
+
self.postMessage({ url, blob, index })
|
|
7
|
+
} catch (err) {
|
|
8
|
+
console.error("Image worker error:", err)
|
|
9
|
+
}
|
|
10
|
+
})
|