aptechka 0.1.0
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/.prettierignore +16 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +4 -0
- package/.vscode/launch.json +11 -0
- package/.vscode/settings.json +18 -0
- package/README.md +0 -0
- package/index.html +32 -0
- package/package.json +272 -0
- package/public/vite.svg +1 -0
- package/src/packages/animation/Animated.ts +189 -0
- package/src/packages/animation/Damped.ts +39 -0
- package/src/packages/animation/Tweened.ts +51 -0
- package/src/packages/animation/index.ts +10 -0
- package/src/packages/attribute/index.ts +59 -0
- package/src/packages/canvas-2d/index.ts +137 -0
- package/src/packages/controls/Controls.ts +15 -0
- package/src/packages/controls/KeyboardControls.ts +63 -0
- package/src/packages/controls/LinearControls.ts +27 -0
- package/src/packages/controls/User.ts +20 -0
- package/src/packages/controls/WheelControls.ts +92 -0
- package/src/packages/controls/index.ts +5 -0
- package/src/packages/css-unit-parser/index.ts +32 -0
- package/src/packages/custom-element/index.ts +19 -0
- package/src/packages/device/Device.ts +113 -0
- package/src/packages/device/Viewport.ts +67 -0
- package/src/packages/device/index.ts +2 -0
- package/src/packages/element-constructor/ElementConstructor.ts +577 -0
- package/src/packages/element-constructor/htmlTags.ts +679 -0
- package/src/packages/element-constructor/index.ts +4 -0
- package/src/packages/element-constructor/specialObjects.ts +8 -0
- package/src/packages/element-constructor/svgTags.ts +588 -0
- package/src/packages/en3/attachments/En3SourceManager.ts +116 -0
- package/src/packages/en3/core/en3.ts +306 -0
- package/src/packages/en3/index.ts +52 -0
- package/src/packages/en3/instances/en3LazyLoader.ts +22 -0
- package/src/packages/en3/libs/MeshoptDecoder.js +138 -0
- package/src/packages/en3/loaders/en3GLTFLoader.ts +54 -0
- package/src/packages/en3/loaders/en3TextureLoader.ts +3 -0
- package/src/packages/en3/objects/En3Clip.ts +53 -0
- package/src/packages/en3/objects/En3ClipHelpers.ts +12 -0
- package/src/packages/en3/objects/En3GLTF.ts +35 -0
- package/src/packages/en3/objects/En3Image.ts +18 -0
- package/src/packages/en3/objects/En3ImageLike.ts +101 -0
- package/src/packages/en3/objects/En3SourceConsumer.ts +5 -0
- package/src/packages/en3/objects/En3Video.ts +88 -0
- package/src/packages/en3/test/En3HTML.ts +55 -0
- package/src/packages/en3/test/En3ModifiedMaterial.ts +221 -0
- package/src/packages/en3/test/En3Raycaster.ts +187 -0
- package/src/packages/en3/utils/coverTexture.ts +29 -0
- package/src/packages/en3/utils/dispose.ts +27 -0
- package/src/packages/en3/utils/traverseMaterials.ts +10 -0
- package/src/packages/en3/utils/traverseMeshes.ts +9 -0
- package/src/packages/image/index.ts +19 -0
- package/src/packages/intersector/index.ts +83 -0
- package/src/packages/ladder/index.ts +112 -0
- package/src/packages/layout-box/index.ts +417 -0
- package/src/packages/loading/index.ts +131 -0
- package/src/packages/measurer/CumulativeOffsetLeft.ts +8 -0
- package/src/packages/measurer/CumulativeOffsetTop.ts +8 -0
- package/src/packages/measurer/Meaurer.ts +38 -0
- package/src/packages/measurer/index.ts +3 -0
- package/src/packages/media/index.ts +38 -0
- package/src/packages/morph/Link.ts +32 -0
- package/src/packages/morph/Morph.ts +246 -0
- package/src/packages/morph/index.ts +10 -0
- package/src/packages/notifier/index.ts +41 -0
- package/src/packages/order/index.ts +14 -0
- package/src/packages/resizer/index.ts +55 -0
- package/src/packages/router/Link.ts +33 -0
- package/src/packages/router/Route.ts +152 -0
- package/src/packages/router/RouteElement.ts +34 -0
- package/src/packages/router/Router.ts +190 -0
- package/src/packages/router/index.ts +13 -0
- package/src/packages/scroll/ScrollElement.ts +618 -0
- package/src/packages/scroll/ScrollUserElement.ts +21 -0
- package/src/packages/scroll/ScrollbarElement.ts +170 -0
- package/src/packages/scroll/index.ts +2 -0
- package/src/packages/scroll-entries/index.ts +74 -0
- package/src/packages/source/SourceClass.ts +77 -0
- package/src/packages/source/SourceElement.ts +177 -0
- package/src/packages/source/SourceManager.ts +61 -0
- package/src/packages/source/SourceSet.ts +52 -0
- package/src/packages/source/index.ts +8 -0
- package/src/packages/store/Composed.ts +33 -0
- package/src/packages/store/Derived.ts +24 -0
- package/src/packages/store/DerivedArray.ts +36 -0
- package/src/packages/store/Resource.ts +38 -0
- package/src/packages/store/Store.ts +144 -0
- package/src/packages/store/StoreRegistry.ts +105 -0
- package/src/packages/store/index.ts +23 -0
- package/src/packages/ticker/index.ts +173 -0
- package/src/packages/utils/array.ts +3 -0
- package/src/packages/utils/attributes.ts +19 -0
- package/src/packages/utils/browser.ts +2 -0
- package/src/packages/utils/canvas.ts +46 -0
- package/src/packages/utils/collisions.ts +12 -0
- package/src/packages/utils/coordinates.ts +40 -0
- package/src/packages/utils/decoding.ts +11 -0
- package/src/packages/utils/dev.ts +5 -0
- package/src/packages/utils/dom.ts +48 -0
- package/src/packages/utils/easings.ts +69 -0
- package/src/packages/utils/file.ts +17 -0
- package/src/packages/utils/function.ts +29 -0
- package/src/packages/utils/index.ts +61 -0
- package/src/packages/utils/layout.ts +22 -0
- package/src/packages/utils/math.ts +74 -0
- package/src/packages/utils/number.ts +26 -0
- package/src/packages/utils/object.ts +108 -0
- package/src/packages/utils/string.ts +49 -0
- package/src/packages/utils/ts-shape.ts +25 -0
- package/src/packages/utils/ts-utility.ts +47 -0
- package/src/packages/video/index.ts +39 -0
- package/src/playground/index.ts +0 -0
- package/tsconfig.json +31 -0
- package/vite.config.ts +65 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { Notifier } from '$packages/notifier'
|
|
2
|
+
import { isBrowser } from '$packages/utils'
|
|
3
|
+
import { Link } from './Link'
|
|
4
|
+
|
|
5
|
+
export type MorphHistoryAction = 'replace' | 'push' | 'none'
|
|
6
|
+
|
|
7
|
+
export interface MorphParameters {
|
|
8
|
+
base?: string
|
|
9
|
+
waitForHeadToLoad?: boolean
|
|
10
|
+
cachePages?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MorphNavigationEntry {
|
|
14
|
+
pathname: string
|
|
15
|
+
isCached: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MorphPreprocessorEntry extends MorphNavigationEntry {
|
|
19
|
+
resolve: () => void
|
|
20
|
+
reject: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type MorphPreprocessor = (entry: MorphPreprocessorEntry) => void
|
|
24
|
+
|
|
25
|
+
export type MorphNavigationCallback = (entry: MorphNavigationEntry) => void
|
|
26
|
+
|
|
27
|
+
export type MorphPostprocessor = MorphNavigationCallback
|
|
28
|
+
|
|
29
|
+
export class Morph {
|
|
30
|
+
#base: string = null!
|
|
31
|
+
#waitForHeadToLoad: boolean = null!
|
|
32
|
+
#cachePages: boolean = null!
|
|
33
|
+
#morphElements: Array<HTMLElement> = null!
|
|
34
|
+
#links: Array<Link> = []
|
|
35
|
+
#domParser: DOMParser = new DOMParser()
|
|
36
|
+
#cache: Map<string, Document> = new Map()
|
|
37
|
+
#candidatePathname: string | undefined
|
|
38
|
+
#currentPathname: string = null!
|
|
39
|
+
|
|
40
|
+
public preprocessor?: MorphPreprocessor
|
|
41
|
+
public postprocessor?: MorphPostprocessor
|
|
42
|
+
|
|
43
|
+
#beforeNavigationEvent = new Notifier<MorphNavigationCallback>()
|
|
44
|
+
#afterNavigationEvent = new Notifier<MorphNavigationCallback>()
|
|
45
|
+
|
|
46
|
+
constructor(parameters?: MorphParameters) {
|
|
47
|
+
if (isBrowser) {
|
|
48
|
+
this.#base = parameters?.base || '/'
|
|
49
|
+
|
|
50
|
+
if (parameters?.base) {
|
|
51
|
+
this.#base = parameters.base
|
|
52
|
+
|
|
53
|
+
if (!parameters.base.endsWith('/')) {
|
|
54
|
+
this.#base += '/'
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
this.#base = '/'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.#waitForHeadToLoad = parameters?.waitForHeadToLoad === false ? false : true
|
|
61
|
+
this.#cachePages = parameters?.cachePages === false ? false : true
|
|
62
|
+
|
|
63
|
+
this.#morphElements = this.#getMorphElements(document)
|
|
64
|
+
|
|
65
|
+
this.#currentPathname = location.pathname
|
|
66
|
+
|
|
67
|
+
this.#findLinks()
|
|
68
|
+
|
|
69
|
+
addEventListener('popstate', this.#popStateListener)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public beforeNavigationEvent(callback: MorphNavigationCallback) {
|
|
74
|
+
return this.#beforeNavigationEvent.subscribe(callback)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public afterNavigationEvent(callback: MorphNavigationCallback) {
|
|
78
|
+
return this.#afterNavigationEvent.subscribe(callback)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public async navigate(pathname: string, historyAction: MorphHistoryAction = 'push') {
|
|
82
|
+
pathname = this.#preparePathname(pathname)
|
|
83
|
+
|
|
84
|
+
if (this.#candidatePathname === pathname || this.#currentPathname === pathname) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.#candidatePathname = pathname
|
|
89
|
+
|
|
90
|
+
const isCached = this.#cache.has(pathname)
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
let isOkToSwitch = true
|
|
94
|
+
|
|
95
|
+
if (this.preprocessor) {
|
|
96
|
+
try {
|
|
97
|
+
await new Promise<void>((resolve, reject) => {
|
|
98
|
+
this.preprocessor?.({ pathname, resolve, reject, isCached })
|
|
99
|
+
})
|
|
100
|
+
} catch (e: any) {
|
|
101
|
+
if (e) {
|
|
102
|
+
console.error(e)
|
|
103
|
+
} else {
|
|
104
|
+
console.log('Route change canceled')
|
|
105
|
+
}
|
|
106
|
+
isOkToSwitch = false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.#beforeNavigationEvent.notify({
|
|
111
|
+
pathname,
|
|
112
|
+
isCached,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
if (!isOkToSwitch || this.#candidatePathname !== pathname) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const newDocument = await this.#fetchDocument(pathname)
|
|
120
|
+
|
|
121
|
+
if (this.#cachePages) {
|
|
122
|
+
this.#cache.set(pathname, newDocument)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.#candidatePathname !== pathname) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const currentHeadChildren = Array.from(document.head.children)
|
|
130
|
+
const newHeadChildren = Array.from((newDocument.head.cloneNode(true) as HTMLElement).children)
|
|
131
|
+
|
|
132
|
+
const identicalHeadChildren = this.#intersectElements(currentHeadChildren, newHeadChildren)
|
|
133
|
+
const removeHeadChildren = this.#excludeElements(currentHeadChildren, identicalHeadChildren)
|
|
134
|
+
const addHeadChildren = this.#excludeElements(newHeadChildren, identicalHeadChildren)
|
|
135
|
+
|
|
136
|
+
removeHeadChildren.forEach((child) => child.remove())
|
|
137
|
+
addHeadChildren.forEach((child) => document.head.appendChild(child))
|
|
138
|
+
|
|
139
|
+
const elementsWithLoad = addHeadChildren.filter(
|
|
140
|
+
(child) =>
|
|
141
|
+
child.tagName === 'STYLE' || child.tagName === 'SCRIPT' || child.tagName === 'LINK'
|
|
142
|
+
) as Array<HTMLLinkElement>
|
|
143
|
+
|
|
144
|
+
if (this.#waitForHeadToLoad && elementsWithLoad.length) {
|
|
145
|
+
await new Promise<void>(async (res) => {
|
|
146
|
+
let counter = 0
|
|
147
|
+
|
|
148
|
+
for await (const element of elementsWithLoad) {
|
|
149
|
+
element.onload = () => {
|
|
150
|
+
counter++
|
|
151
|
+
|
|
152
|
+
if (counter === elementsWithLoad.length) {
|
|
153
|
+
res()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const newMorphElements = this.#getMorphElements(newDocument)
|
|
161
|
+
|
|
162
|
+
this.#morphElements.forEach((morphElement, i) => {
|
|
163
|
+
const newMorphElement = newMorphElements[i]
|
|
164
|
+
morphElement.innerHTML = newMorphElement.innerHTML
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
this.#currentPathname = pathname
|
|
168
|
+
|
|
169
|
+
if (historyAction === 'push') {
|
|
170
|
+
history.pushState(pathname, '', pathname + location.search)
|
|
171
|
+
} else if (historyAction === 'replace') {
|
|
172
|
+
history.replaceState(pathname, '', pathname + location.search)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.#findLinks()
|
|
176
|
+
|
|
177
|
+
this.postprocessor?.({ pathname, isCached })
|
|
178
|
+
this.#afterNavigationEvent.notify({ pathname, isCached })
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.error(e)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.#candidatePathname = undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async #fetchDocument(pathname: string) {
|
|
187
|
+
const cahcnedDocument = this.#cache.get(pathname)
|
|
188
|
+
|
|
189
|
+
if (cahcnedDocument) {
|
|
190
|
+
return cahcnedDocument
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const fetchResult = await fetch(pathname)
|
|
194
|
+
const text = await fetchResult.text()
|
|
195
|
+
const document = this.#domParser.parseFromString(text, 'text/html')
|
|
196
|
+
return document
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#preparePathname(pathname: string) {
|
|
200
|
+
pathname = pathname.replace(this.#base, '')
|
|
201
|
+
|
|
202
|
+
if (pathname.startsWith('/')) {
|
|
203
|
+
pathname = pathname.slice(1)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return this.#base + pathname
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#findLinks() {
|
|
210
|
+
const linkElements = [...document.documentElement.querySelectorAll('a')].filter((a) =>
|
|
211
|
+
a.getAttribute('href')?.startsWith('/')
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
this.#links.forEach((link) => link.destroy())
|
|
215
|
+
|
|
216
|
+
this.#links = linkElements.map((element) => new Link(element, this))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#getMorphElements(document: Document) {
|
|
220
|
+
const elements = document.querySelectorAll<HTMLElement>('[data-morph]')
|
|
221
|
+
return elements.length ? [...elements] : [document.body]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#intersectElements(elements: Array<Element>, elementsToIntersect: Array<Element>) {
|
|
225
|
+
return elements.filter((element) =>
|
|
226
|
+
elementsToIntersect.find(
|
|
227
|
+
(elementToIntersect) => elementToIntersect.outerHTML === element.outerHTML
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#excludeElements(elements: Array<Element>, elementsToExclude: Array<Element>) {
|
|
233
|
+
return elements.filter(
|
|
234
|
+
(element) =>
|
|
235
|
+
!elementsToExclude.find(
|
|
236
|
+
(elementToExclude) => elementToExclude.outerHTML === element.outerHTML
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#popStateListener = (event: PopStateEvent) => {
|
|
242
|
+
if (event.state) {
|
|
243
|
+
this.navigate(event.state, 'none')
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type ProviderCallback = (...args: any[]) => void
|
|
2
|
+
|
|
3
|
+
interface NotifierSubscriber {
|
|
4
|
+
callback: ProviderCallback
|
|
5
|
+
order: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class Notifier<Callback extends ProviderCallback = ProviderCallback> {
|
|
9
|
+
#subscribers: Array<NotifierSubscriber> = []
|
|
10
|
+
|
|
11
|
+
public close() {
|
|
12
|
+
this.#subscribers = []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public subscribe(callback: Callback, order: number = 0) {
|
|
16
|
+
if (this.#subscribers.find((subscriber) => subscriber.callback === callback)) {
|
|
17
|
+
return () => {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.#subscribers.push({
|
|
21
|
+
callback,
|
|
22
|
+
order,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
this.#subscribers = this.#subscribers.sort((a, b) => a.order - b.order)
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
this.unsubscribe(callback)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public unsubscribe(callback: Callback) {
|
|
33
|
+
this.#subscribers = this.#subscribers.filter((subscriber) => subscriber.callback !== callback)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public notify(...parameters: Parameters<Callback>) {
|
|
37
|
+
for (const subscriber of this.#subscribers) {
|
|
38
|
+
subscriber.callback(...parameters)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Notifier } from '$packages/notifier'
|
|
2
|
+
import { debounce, isBrowser } from '$packages/utils'
|
|
3
|
+
|
|
4
|
+
export type ResizerCallback = () => void
|
|
5
|
+
|
|
6
|
+
export const dispatchResizeEvent = debounce((cause?: string) => {
|
|
7
|
+
if (cause) {
|
|
8
|
+
window.dispatchEvent(
|
|
9
|
+
new CustomEvent('resize', {
|
|
10
|
+
detail: {
|
|
11
|
+
cause: cause,
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
)
|
|
15
|
+
} else {
|
|
16
|
+
window.dispatchEvent(new Event('resize'))
|
|
17
|
+
}
|
|
18
|
+
}, 0)
|
|
19
|
+
|
|
20
|
+
class Resizer extends Notifier<ResizerCallback> {
|
|
21
|
+
#isResizeScheduled = false
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
if (isBrowser) {
|
|
27
|
+
addEventListener('resize', this.#resizeListener)
|
|
28
|
+
this.#resizeListener()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public subscribe(callback: ResizerCallback, order?: number) {
|
|
33
|
+
const unsub = super.subscribe(callback, order)
|
|
34
|
+
|
|
35
|
+
if (!this.#isResizeScheduled) {
|
|
36
|
+
callback()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return unsub
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#resizeListener = () => {
|
|
43
|
+
if (!this.#isResizeScheduled) {
|
|
44
|
+
this.#isResizeScheduled = true
|
|
45
|
+
this.#resize()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#resize = debounce(() => {
|
|
50
|
+
this.notify()
|
|
51
|
+
this.#isResizeScheduled = false
|
|
52
|
+
}, 0)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const resizer = new Resizer()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Router, RouterHistoryAction } from './Router'
|
|
2
|
+
|
|
3
|
+
export class Link {
|
|
4
|
+
#router: Router
|
|
5
|
+
#element: HTMLElement
|
|
6
|
+
#pathname: string
|
|
7
|
+
#historyAction: RouterHistoryAction
|
|
8
|
+
|
|
9
|
+
constructor(router: Router, element: HTMLElement) {
|
|
10
|
+
this.#router = router
|
|
11
|
+
this.#element = element
|
|
12
|
+
this.#pathname = this.#element.getAttribute('href') || '/'
|
|
13
|
+
this.#historyAction =
|
|
14
|
+
(this.#element.getAttribute('data-history-action') as RouterHistoryAction) || 'push'
|
|
15
|
+
|
|
16
|
+
this.#element.addEventListener('click', this.#clickListener)
|
|
17
|
+
|
|
18
|
+
if (location.pathname === this.#pathname) {
|
|
19
|
+
this.#element.classList.add('current')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public destroy() {
|
|
24
|
+
this.#element.removeEventListener('click', this.#clickListener)
|
|
25
|
+
this.#element.classList.remove('current')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#clickListener = (e: Event) => {
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
|
|
31
|
+
this.#router.navigate(this.#pathname, this.#historyAction)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { isBrowser } from '$packages/utils'
|
|
2
|
+
import { RouteElement, RouteParameters } from './RouteElement'
|
|
3
|
+
|
|
4
|
+
export type RouteModule = () => Promise<any>
|
|
5
|
+
|
|
6
|
+
export class Route {
|
|
7
|
+
#pattern: string
|
|
8
|
+
#module: RouteModule
|
|
9
|
+
#urlPattern: URLPattern
|
|
10
|
+
#elementConstructor: typeof RouteElement | null
|
|
11
|
+
#element: RouteElement | null
|
|
12
|
+
#isActive: boolean
|
|
13
|
+
#outlet: HTMLElement | ShadowRoot | null
|
|
14
|
+
#mutationObserver: MutationObserver = null!
|
|
15
|
+
#permanentHeadNodes: Array<Node> = []
|
|
16
|
+
#temporalHeadNodes: Array<Node> = []
|
|
17
|
+
|
|
18
|
+
constructor(pattern: string, module: RouteModule) {
|
|
19
|
+
this.#pattern = pattern
|
|
20
|
+
this.#module = module
|
|
21
|
+
this.#urlPattern = new URLPattern({ pathname: this.#pattern })
|
|
22
|
+
this.#elementConstructor = null
|
|
23
|
+
this.#element = null
|
|
24
|
+
this.#isActive = false
|
|
25
|
+
this.#outlet = null
|
|
26
|
+
|
|
27
|
+
if (isBrowser) {
|
|
28
|
+
this.#mutationObserver = new MutationObserver((mutations) => {
|
|
29
|
+
const mutation = mutations[0]
|
|
30
|
+
|
|
31
|
+
mutation.addedNodes.forEach((addedNode) => {
|
|
32
|
+
if (!this.#elementConstructor) {
|
|
33
|
+
this.#permanentHeadNodes.push(addedNode)
|
|
34
|
+
} else {
|
|
35
|
+
this.#temporalHeadNodes.push(addedNode)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public get pattern() {
|
|
43
|
+
return this.#pattern
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get urlPattern() {
|
|
47
|
+
return this.#urlPattern
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public get isActive() {
|
|
51
|
+
return this.#isActive
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public get element() {
|
|
55
|
+
return this.#element
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public get outlet() {
|
|
59
|
+
return this.#outlet
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public testPathname(pathname: string) {
|
|
63
|
+
return this.urlPattern.test({ pathname: pathname })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async render(containerElement: HTMLElement | ShadowRoot, pathname: string) {
|
|
67
|
+
this.#mutationObserver.observe(document.head, { childList: true, subtree: true })
|
|
68
|
+
|
|
69
|
+
if (!this.#elementConstructor) {
|
|
70
|
+
const content = await this.#module()
|
|
71
|
+
|
|
72
|
+
this.#temporalHeadNodes = [...this.#permanentHeadNodes]
|
|
73
|
+
|
|
74
|
+
if (typeof content.default === 'function') {
|
|
75
|
+
this.#elementConstructor = content.default
|
|
76
|
+
customElements.define('e-' + this.#elementConstructor?.name.toLowerCase(), content.default)
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
this.#permanentHeadNodes.forEach((node) => {
|
|
80
|
+
document.head.appendChild(node)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await this.#waitHeadNodesLoad()
|
|
85
|
+
|
|
86
|
+
if (this.#elementConstructor) {
|
|
87
|
+
const v = this.#urlPattern.exec({ pathname })
|
|
88
|
+
const pathnameParams = v?.pathname.groups || {}
|
|
89
|
+
const searchParams = Object.fromEntries(new URLSearchParams(location.search))
|
|
90
|
+
|
|
91
|
+
const routeParameters: RouteParameters<any, any> = {
|
|
92
|
+
pathnameParams,
|
|
93
|
+
searchParams,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.#element = new this.#elementConstructor(routeParameters)
|
|
97
|
+
|
|
98
|
+
containerElement.appendChild(this.#element)
|
|
99
|
+
|
|
100
|
+
this.#outlet =
|
|
101
|
+
this.#element.querySelector<HTMLElement>('[data-outlet]') ||
|
|
102
|
+
this.#element.shadowRoot?.querySelector<HTMLElement>('[data-outlet]') ||
|
|
103
|
+
this.#element.shadowRoot ||
|
|
104
|
+
this.#element
|
|
105
|
+
|
|
106
|
+
this.#isActive = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.#mutationObserver.disconnect()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public close() {
|
|
113
|
+
this.#mutationObserver.disconnect()
|
|
114
|
+
this.#element?.remove()
|
|
115
|
+
this.#isActive = false
|
|
116
|
+
|
|
117
|
+
this.#temporalHeadNodes.forEach((node) => document.head.removeChild(node))
|
|
118
|
+
this.#temporalHeadNodes = []
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public getAnchorElements() {
|
|
122
|
+
let links: Array<HTMLAnchorElement> = []
|
|
123
|
+
|
|
124
|
+
if (this.#element) {
|
|
125
|
+
links = [...this.#element.querySelectorAll<HTMLAnchorElement>('a')]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this.#element?.shadowRoot) {
|
|
129
|
+
links = [...links, ...this.#element.shadowRoot.querySelectorAll<HTMLAnchorElement>('a')]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return links
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async #waitHeadNodesLoad() {
|
|
136
|
+
const nodes = this.#permanentHeadNodes.filter((node) => {
|
|
137
|
+
if (node instanceof HTMLElement) {
|
|
138
|
+
return node.tagName === 'STYLE' || node.tagName === 'SCRIPT' || node.tagName === 'LINK'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false
|
|
142
|
+
}) as Array<HTMLStyleElement | HTMLScriptElement | HTMLLinkElement>
|
|
143
|
+
|
|
144
|
+
for await (const node of nodes) {
|
|
145
|
+
await new Promise<void>((res) => {
|
|
146
|
+
node.onload = () => {
|
|
147
|
+
res()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CustomElement } from '$packages/custom-element'
|
|
2
|
+
|
|
3
|
+
export type RouteURLParams<T extends string = string> = Partial<{ [key in T]: string }>
|
|
4
|
+
|
|
5
|
+
export type RouteParameters<
|
|
6
|
+
PathnameParams extends string = string,
|
|
7
|
+
SearchParams extends string = string
|
|
8
|
+
> = {
|
|
9
|
+
pathnameParams: RouteURLParams<PathnameParams>
|
|
10
|
+
searchParams: RouteURLParams<SearchParams>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class RouteElement<
|
|
14
|
+
PathnameParams extends string = string,
|
|
15
|
+
SearchParams extends string = string
|
|
16
|
+
> extends CustomElement {
|
|
17
|
+
#pathnameParams: RouteURLParams<PathnameParams>
|
|
18
|
+
#searchParams: RouteURLParams<SearchParams>
|
|
19
|
+
|
|
20
|
+
constructor(parameters: RouteParameters<PathnameParams, SearchParams>) {
|
|
21
|
+
super()
|
|
22
|
+
|
|
23
|
+
this.#pathnameParams = parameters.pathnameParams
|
|
24
|
+
this.#searchParams = parameters.searchParams
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public get pathnameParams() {
|
|
28
|
+
return this.#pathnameParams
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public get searchParams() {
|
|
32
|
+
return this.#searchParams
|
|
33
|
+
}
|
|
34
|
+
}
|