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.
Files changed (115) hide show
  1. package/.prettierignore +16 -0
  2. package/.prettierrc +9 -0
  3. package/.vscode/extensions.json +4 -0
  4. package/.vscode/launch.json +11 -0
  5. package/.vscode/settings.json +18 -0
  6. package/README.md +0 -0
  7. package/index.html +32 -0
  8. package/package.json +272 -0
  9. package/public/vite.svg +1 -0
  10. package/src/packages/animation/Animated.ts +189 -0
  11. package/src/packages/animation/Damped.ts +39 -0
  12. package/src/packages/animation/Tweened.ts +51 -0
  13. package/src/packages/animation/index.ts +10 -0
  14. package/src/packages/attribute/index.ts +59 -0
  15. package/src/packages/canvas-2d/index.ts +137 -0
  16. package/src/packages/controls/Controls.ts +15 -0
  17. package/src/packages/controls/KeyboardControls.ts +63 -0
  18. package/src/packages/controls/LinearControls.ts +27 -0
  19. package/src/packages/controls/User.ts +20 -0
  20. package/src/packages/controls/WheelControls.ts +92 -0
  21. package/src/packages/controls/index.ts +5 -0
  22. package/src/packages/css-unit-parser/index.ts +32 -0
  23. package/src/packages/custom-element/index.ts +19 -0
  24. package/src/packages/device/Device.ts +113 -0
  25. package/src/packages/device/Viewport.ts +67 -0
  26. package/src/packages/device/index.ts +2 -0
  27. package/src/packages/element-constructor/ElementConstructor.ts +577 -0
  28. package/src/packages/element-constructor/htmlTags.ts +679 -0
  29. package/src/packages/element-constructor/index.ts +4 -0
  30. package/src/packages/element-constructor/specialObjects.ts +8 -0
  31. package/src/packages/element-constructor/svgTags.ts +588 -0
  32. package/src/packages/en3/attachments/En3SourceManager.ts +116 -0
  33. package/src/packages/en3/core/en3.ts +306 -0
  34. package/src/packages/en3/index.ts +52 -0
  35. package/src/packages/en3/instances/en3LazyLoader.ts +22 -0
  36. package/src/packages/en3/libs/MeshoptDecoder.js +138 -0
  37. package/src/packages/en3/loaders/en3GLTFLoader.ts +54 -0
  38. package/src/packages/en3/loaders/en3TextureLoader.ts +3 -0
  39. package/src/packages/en3/objects/En3Clip.ts +53 -0
  40. package/src/packages/en3/objects/En3ClipHelpers.ts +12 -0
  41. package/src/packages/en3/objects/En3GLTF.ts +35 -0
  42. package/src/packages/en3/objects/En3Image.ts +18 -0
  43. package/src/packages/en3/objects/En3ImageLike.ts +101 -0
  44. package/src/packages/en3/objects/En3SourceConsumer.ts +5 -0
  45. package/src/packages/en3/objects/En3Video.ts +88 -0
  46. package/src/packages/en3/test/En3HTML.ts +55 -0
  47. package/src/packages/en3/test/En3ModifiedMaterial.ts +221 -0
  48. package/src/packages/en3/test/En3Raycaster.ts +187 -0
  49. package/src/packages/en3/utils/coverTexture.ts +29 -0
  50. package/src/packages/en3/utils/dispose.ts +27 -0
  51. package/src/packages/en3/utils/traverseMaterials.ts +10 -0
  52. package/src/packages/en3/utils/traverseMeshes.ts +9 -0
  53. package/src/packages/image/index.ts +19 -0
  54. package/src/packages/intersector/index.ts +83 -0
  55. package/src/packages/ladder/index.ts +112 -0
  56. package/src/packages/layout-box/index.ts +417 -0
  57. package/src/packages/loading/index.ts +131 -0
  58. package/src/packages/measurer/CumulativeOffsetLeft.ts +8 -0
  59. package/src/packages/measurer/CumulativeOffsetTop.ts +8 -0
  60. package/src/packages/measurer/Meaurer.ts +38 -0
  61. package/src/packages/measurer/index.ts +3 -0
  62. package/src/packages/media/index.ts +38 -0
  63. package/src/packages/morph/Link.ts +32 -0
  64. package/src/packages/morph/Morph.ts +246 -0
  65. package/src/packages/morph/index.ts +10 -0
  66. package/src/packages/notifier/index.ts +41 -0
  67. package/src/packages/order/index.ts +14 -0
  68. package/src/packages/resizer/index.ts +55 -0
  69. package/src/packages/router/Link.ts +33 -0
  70. package/src/packages/router/Route.ts +152 -0
  71. package/src/packages/router/RouteElement.ts +34 -0
  72. package/src/packages/router/Router.ts +190 -0
  73. package/src/packages/router/index.ts +13 -0
  74. package/src/packages/scroll/ScrollElement.ts +618 -0
  75. package/src/packages/scroll/ScrollUserElement.ts +21 -0
  76. package/src/packages/scroll/ScrollbarElement.ts +170 -0
  77. package/src/packages/scroll/index.ts +2 -0
  78. package/src/packages/scroll-entries/index.ts +74 -0
  79. package/src/packages/source/SourceClass.ts +77 -0
  80. package/src/packages/source/SourceElement.ts +177 -0
  81. package/src/packages/source/SourceManager.ts +61 -0
  82. package/src/packages/source/SourceSet.ts +52 -0
  83. package/src/packages/source/index.ts +8 -0
  84. package/src/packages/store/Composed.ts +33 -0
  85. package/src/packages/store/Derived.ts +24 -0
  86. package/src/packages/store/DerivedArray.ts +36 -0
  87. package/src/packages/store/Resource.ts +38 -0
  88. package/src/packages/store/Store.ts +144 -0
  89. package/src/packages/store/StoreRegistry.ts +105 -0
  90. package/src/packages/store/index.ts +23 -0
  91. package/src/packages/ticker/index.ts +173 -0
  92. package/src/packages/utils/array.ts +3 -0
  93. package/src/packages/utils/attributes.ts +19 -0
  94. package/src/packages/utils/browser.ts +2 -0
  95. package/src/packages/utils/canvas.ts +46 -0
  96. package/src/packages/utils/collisions.ts +12 -0
  97. package/src/packages/utils/coordinates.ts +40 -0
  98. package/src/packages/utils/decoding.ts +11 -0
  99. package/src/packages/utils/dev.ts +5 -0
  100. package/src/packages/utils/dom.ts +48 -0
  101. package/src/packages/utils/easings.ts +69 -0
  102. package/src/packages/utils/file.ts +17 -0
  103. package/src/packages/utils/function.ts +29 -0
  104. package/src/packages/utils/index.ts +61 -0
  105. package/src/packages/utils/layout.ts +22 -0
  106. package/src/packages/utils/math.ts +74 -0
  107. package/src/packages/utils/number.ts +26 -0
  108. package/src/packages/utils/object.ts +108 -0
  109. package/src/packages/utils/string.ts +49 -0
  110. package/src/packages/utils/ts-shape.ts +25 -0
  111. package/src/packages/utils/ts-utility.ts +47 -0
  112. package/src/packages/video/index.ts +39 -0
  113. package/src/playground/index.ts +0 -0
  114. package/tsconfig.json +31 -0
  115. package/vite.config.ts +65 -0
@@ -0,0 +1,59 @@
1
+ import { Store, StoreOptions } from '$packages/store'
2
+ import { ElementOrSelector, isBrowser, getElement, parseAttributeValue } from '$packages/utils'
3
+
4
+ export class Attribute<T extends string | number | boolean> extends Store<T> {
5
+ #element: HTMLElement | null = null
6
+ #name: string
7
+ #mutationObserver: MutationObserver = null!
8
+
9
+ constructor(
10
+ elementOrSelector: ElementOrSelector,
11
+ name: string,
12
+ defaultValue: T,
13
+ options?: StoreOptions<T>
14
+ ) {
15
+ super(defaultValue, options)
16
+
17
+ this.#name = name
18
+
19
+ this.subscribe((e) => {
20
+ this.#element?.setAttribute(this.#name, e.current.toString())
21
+ })
22
+
23
+ if (isBrowser) {
24
+ this.#element = getElement(elementOrSelector)!
25
+
26
+ this.#mutationObserver = new MutationObserver((mutations) => {
27
+ mutations.forEach((mutation) => {
28
+ if (mutation.type === 'attributes' && mutation.attributeName === this.#name) {
29
+ this.#tryUpdate()
30
+ }
31
+ })
32
+ })
33
+ }
34
+ }
35
+
36
+ public unobserve() {
37
+ if (isBrowser) {
38
+ this.#mutationObserver.disconnect()
39
+ }
40
+ }
41
+
42
+ public observe() {
43
+ if (isBrowser && this.#element) {
44
+ this.#mutationObserver.observe(this.#element, {
45
+ attributes: true,
46
+ })
47
+
48
+ this.#tryUpdate()
49
+ }
50
+ }
51
+
52
+ #tryUpdate() {
53
+ const value = this.#element!.getAttribute(this.#name)
54
+
55
+ if (value != undefined) {
56
+ this.current = parseAttributeValue(value) as T
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,137 @@
1
+ import { define, CustomElement } from '$packages/custom-element'
2
+ import { Notifier } from '$packages/notifier'
3
+ import { resizer } from '$packages/resizer'
4
+ import { ticker, TickerCallback } from '$packages/ticker'
5
+ import { clamp } from '$packages/utils'
6
+
7
+ export interface Canvas2DRenderDetail {
8
+ pixelRatio: number
9
+ width: number
10
+ height: number
11
+ element: HTMLElement
12
+ canvasElement: HTMLElement
13
+ context: CanvasRenderingContext2D
14
+ timestamp: number
15
+ elapsed: number
16
+ }
17
+
18
+ export type Canvas2DRenderCallback = (detail: Canvas2DRenderDetail) => void
19
+
20
+ @define('canvas-2d')
21
+ export class Canvas2DElement extends CustomElement {
22
+ #renderEvent = new Notifier<Canvas2DRenderCallback>()
23
+
24
+ #canvasElement: HTMLCanvasElement = null!
25
+ #context: CanvasRenderingContext2D = null!
26
+
27
+ #width = 0
28
+ #height = 0
29
+ #pixelRatio = 1
30
+
31
+ #timestamp = 0
32
+ #elapsed = 1
33
+
34
+ public get renderEvent() {
35
+ return this.#renderEvent
36
+ }
37
+
38
+ public get canvasElement() {
39
+ return this.#canvasElement
40
+ }
41
+
42
+ public get context() {
43
+ return this.#context
44
+ }
45
+
46
+ public get pixelRatio() {
47
+ return this.#pixelRatio
48
+ }
49
+
50
+ public get width() {
51
+ return this.#width
52
+ }
53
+
54
+ public get height() {
55
+ return this.#height
56
+ }
57
+
58
+ public get detail(): Canvas2DRenderDetail {
59
+ return {
60
+ width: this.#width,
61
+ height: this.#height,
62
+ element: this,
63
+ canvasElement: this.#canvasElement,
64
+ pixelRatio: this.#pixelRatio,
65
+ context: this.#context,
66
+ timestamp: this.#timestamp,
67
+ elapsed: this.#elapsed,
68
+ }
69
+ }
70
+
71
+ protected connectedCallback() {
72
+ this.style.display = 'block'
73
+ this.style.width = '100%'
74
+ this.style.height = '100%'
75
+
76
+ this.#canvasElement = document.createElement('canvas')
77
+
78
+ this.#canvasElement.style.cssText = `
79
+ display: block;
80
+ width: 100%;
81
+ height: 100%;
82
+ `
83
+
84
+ this.#context = this.#canvasElement.getContext('2d')!
85
+
86
+ this.appendChild(this.#canvasElement)
87
+
88
+ resizer.subscribe(this.#resizeListener)
89
+
90
+ if (!this.hasAttribute('static')) {
91
+ ticker.subscribe(this.#tickListener, {
92
+ culling: this,
93
+ maxFPS: this.hasAttribute('fps') ? parseInt(this.getAttribute('fps')!) : undefined,
94
+ })
95
+ }
96
+ }
97
+
98
+ protected disconnectedCallback() {
99
+ resizer.unsubscribe(this.#resizeListener)
100
+ ticker.unsubscribe(this.#tickListener)
101
+
102
+ this.#renderEvent.close()
103
+ this.#canvasElement.remove()
104
+ this.style.display = ''
105
+ this.style.width = ''
106
+ this.style.height = ''
107
+ }
108
+
109
+ #resizeListener = () => {
110
+ this.#pixelRatio = clamp(devicePixelRatio, 1, 2)
111
+
112
+ const rect = this.getBoundingClientRect()
113
+
114
+ this.#width = rect.width
115
+ this.#height = rect.height
116
+
117
+ this.#canvasElement.width = this.#width * this.pixelRatio
118
+ this.#canvasElement.height = this.#height * this.pixelRatio
119
+
120
+ this.context.scale(this.pixelRatio, this.pixelRatio)
121
+
122
+ this.renderEvent.notify(this.detail)
123
+ }
124
+
125
+ #tickListener: TickerCallback = (e) => {
126
+ this.#timestamp = e.timestamp
127
+ this.#elapsed = e.elapsed
128
+
129
+ this.#renderEvent.notify(this.detail)
130
+ }
131
+ }
132
+
133
+ declare global {
134
+ interface HTMLElementTagNameMap {
135
+ 'canvas-2d': Canvas2DElement
136
+ }
137
+ }
@@ -0,0 +1,15 @@
1
+ import { Notifier } from '$packages/notifier'
2
+
3
+ export type ControlsValue = number | 'max' | 'min'
4
+ export type ControlsCallback = (value: ControlsValue) => void
5
+
6
+ export abstract class Controls {
7
+ #changeEvent = new Notifier<ControlsCallback>()
8
+
9
+ public get changeEvent() {
10
+ return this.#changeEvent
11
+ }
12
+
13
+ public abstract connect(): void
14
+ public abstract disconnect(): void
15
+ }
@@ -0,0 +1,63 @@
1
+ import { isBrowser, getElement } from '$packages/utils'
2
+ import { Controls } from './Controls'
3
+ import { user } from './User'
4
+
5
+ export interface KeyboardControlsOptions {
6
+ element?: HTMLElement
7
+ }
8
+
9
+ export class KeyboardControls extends Controls {
10
+ #element: HTMLElement | Window = null!
11
+
12
+ constructor(options?: KeyboardControlsOptions) {
13
+ super()
14
+
15
+ if (isBrowser) {
16
+ this.#element = options?.element ? getElement(options.element) || window : window
17
+ }
18
+ }
19
+
20
+ public connect() {
21
+ if (isBrowser) {
22
+ this.#element.addEventListener('keydown', this.#keydownListener as EventListener)
23
+ }
24
+ }
25
+
26
+ public disconnect() {
27
+ if (isBrowser) {
28
+ this.#element.removeEventListener('keydown', this.#keydownListener as EventListener)
29
+ }
30
+ }
31
+
32
+ #keydownListener = (e: KeyboardEvent) => {
33
+ const dir = e.shiftKey ? -1 : 1
34
+
35
+ let interaction = true
36
+
37
+ if (e.code === 'Space') {
38
+ this.changeEvent.notify(dir * 500)
39
+ } else if (e.code === 'ArrowLeft') {
40
+ this.changeEvent.notify(-1 * 100)
41
+ } else if (e.code === 'ArrowRight') {
42
+ this.changeEvent.notify(1 * 100)
43
+ } else if (e.code === 'ArrowUp') {
44
+ this.changeEvent.notify(-1 * 100)
45
+ } else if (e.code === 'ArrowDown') {
46
+ this.changeEvent.notify(1 * 100)
47
+ } else if (e.code === 'PageUp') {
48
+ this.changeEvent.notify(-1 * 1000)
49
+ } else if (e.code === 'PageDown') {
50
+ this.changeEvent.notify(1 * 1000)
51
+ } else if (e.code === 'Home') {
52
+ this.changeEvent.notify('min')
53
+ } else if (e.code === 'End') {
54
+ this.changeEvent.notify('max')
55
+ } else {
56
+ interaction = false
57
+ }
58
+
59
+ if (interaction) {
60
+ user.registerInteraction()
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,27 @@
1
+ import { ticker, TickerCallback } from '$packages/ticker'
2
+ import { Controls } from './Controls'
3
+
4
+ export interface LinearControlsOptions {
5
+ speed?: number
6
+ }
7
+
8
+ export class LinearControls extends Controls {
9
+ speed: number
10
+
11
+ constructor(options?: LinearControlsOptions) {
12
+ super()
13
+ this.speed = options?.speed || 1
14
+ }
15
+
16
+ public connect() {
17
+ ticker.subscribe(this.#animationFrameCallback)
18
+ }
19
+
20
+ public disconnect() {
21
+ ticker.unsubscribe(this.#animationFrameCallback)
22
+ }
23
+
24
+ #animationFrameCallback: TickerCallback = (e) => {
25
+ this.changeEvent.notify(e.elapsed * this.speed)
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ class User {
2
+ #idleTimeoutId: ReturnType<typeof setTimeout> | undefined
3
+ #isIdle = true
4
+
5
+ public get isIdle() {
6
+ return this.#isIdle
7
+ }
8
+
9
+ public registerInteraction() {
10
+ clearTimeout(this.#idleTimeoutId)
11
+
12
+ this.#isIdle = false
13
+
14
+ this.#idleTimeoutId = setTimeout(() => {
15
+ this.#isIdle = true
16
+ }, 2000)
17
+ }
18
+ }
19
+
20
+ export const user = new User()
@@ -0,0 +1,92 @@
1
+ import { Axes2D, isBrowser, getElement } from '$packages/utils'
2
+ import { Controls } from './Controls'
3
+ import { user } from './User'
4
+
5
+ export interface WheelControlsOptions {
6
+ axis?: Axes2D
7
+ speed?: number
8
+ debounce?: boolean
9
+ element?: HTMLElement
10
+ }
11
+
12
+ export class WheelControls extends Controls {
13
+ axis: Axes2D
14
+ speed: number
15
+ debounce: boolean
16
+
17
+ #element: HTMLElement | Window = null!
18
+
19
+ #timeout: ReturnType<typeof setTimeout> | undefined
20
+ #prevEventDate: number
21
+
22
+ constructor(options?: WheelControlsOptions) {
23
+ super()
24
+
25
+ this.axis = options?.axis || 'y'
26
+ this.speed = options?.speed || 1
27
+ this.debounce = options?.debounce || false
28
+
29
+ this.#prevEventDate = Date.now()
30
+
31
+ if (isBrowser) {
32
+ this.#element = options?.element ? getElement(options.element) || window : window
33
+ }
34
+ }
35
+
36
+ public connect() {
37
+ if (isBrowser) {
38
+ this.#element.addEventListener('wheel', this.#wheelListener as EventListener, {
39
+ passive: false,
40
+ })
41
+ }
42
+ }
43
+
44
+ public disconnect() {
45
+ if (isBrowser) {
46
+ this.#element.removeEventListener('wheel', this.#wheelListener as EventListener)
47
+ }
48
+ }
49
+
50
+ #wheelListener = (event: WheelEvent) => {
51
+ let delta = 0
52
+
53
+ if (
54
+ (this.axis === 'x' && Math.abs(event.deltaY) > Math.abs(event.deltaX)) ||
55
+ (this.axis === 'y' && Math.abs(event.deltaX) > Math.abs(event.deltaY))
56
+ ) {
57
+ return
58
+ }
59
+
60
+ delta = (this.axis === 'x' ? event.deltaX : event.deltaY) * this.speed
61
+
62
+ user.registerInteraction()
63
+ event.stopPropagation()
64
+ event.preventDefault()
65
+
66
+ if (this.debounce) {
67
+ const now = Date.now()
68
+
69
+ if (now - this.#prevEventDate > 40) {
70
+ delta = 100 * Math.sign(delta)
71
+ }
72
+
73
+ this.#prevEventDate = now
74
+
75
+ if (Math.abs(delta) < 100) {
76
+ return
77
+ }
78
+
79
+ if (this.#timeout) {
80
+ return
81
+ }
82
+
83
+ this.changeEvent.notify(delta)
84
+
85
+ this.#timeout = setTimeout(() => {
86
+ this.#timeout = undefined
87
+ }, 80)
88
+ } else {
89
+ this.changeEvent.notify(delta)
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,5 @@
1
+ export { Controls, type ControlsValue } from './Controls'
2
+ export { KeyboardControls } from './KeyboardControls'
3
+ export { LinearControls, type LinearControlsOptions } from './LinearControls'
4
+ export { WheelControls, type WheelControlsOptions } from './WheelControls'
5
+ export { user } from './User'
@@ -0,0 +1,32 @@
1
+ class CSSUnitParser {
2
+ #dummyElement: HTMLElement = null!
3
+
4
+ public parse(value: string) {
5
+ this.#createDummy()
6
+
7
+ this.#dummyElement.style.left = value
8
+ const computedWidth = getComputedStyle(this.#dummyElement).getPropertyValue('left')
9
+
10
+ return parseFloat(computedWidth)
11
+ }
12
+
13
+ #createDummy() {
14
+ if (!this.#dummyElement) {
15
+ this.#dummyElement = document.createElement('div')
16
+ this.#dummyElement.style.cssText = `
17
+ position: fixed;
18
+ top: 0;
19
+ left: 0;
20
+ width: 0;
21
+ height: 0;
22
+ visibility: hidden;
23
+ `
24
+ }
25
+
26
+ if (!document.body.contains(this.#dummyElement)) {
27
+ document.body.prepend(this.#dummyElement)
28
+ }
29
+ }
30
+ }
31
+
32
+ export const cssUnitParser = new CSSUnitParser()
@@ -0,0 +1,19 @@
1
+ import { isBrowser } from '$packages/utils'
2
+
3
+ export function define(name: string) {
4
+ return function (Constructor: CustomElementConstructor) {
5
+ if (isBrowser && !customElements.get(name)) {
6
+ customElements.define(name, Constructor)
7
+ }
8
+ }
9
+ }
10
+
11
+ const HTMLElement = (
12
+ isBrowser
13
+ ? window.HTMLElement
14
+ : class {
15
+ attachShadow(..._: any): any {}
16
+ }
17
+ ) as typeof window.HTMLElement
18
+
19
+ export class CustomElement extends HTMLElement {}
@@ -0,0 +1,113 @@
1
+ import { RESIZE_ORDER } from '$packages/order'
2
+ import { resizer } from '$packages/resizer'
3
+ import { isBrowser } from '$packages/utils'
4
+ import { TierResult, getGPUTier } from 'detect-gpu'
5
+
6
+ export type DeviceOS = 'macOS' | 'iOS' | 'Windows' | 'Android' | 'Linux' | 'unknown'
7
+
8
+ class Device {
9
+ #OS = 'unknown'
10
+ #gpu = 'unknown'
11
+ #gpuTier = 0
12
+ #gpuDetection: Promise<TierResult> = null!
13
+ #isMobile = false
14
+ #isTouch = false
15
+ #isWebgl = false
16
+ #isWebp = false
17
+ #isApple = false
18
+
19
+ constructor() {
20
+ if (isBrowser) {
21
+ this.#gpuDetection = getGPUTier()
22
+
23
+ this.#gpuDetection.then((v) => {
24
+ this.#gpu = v.gpu || 'unknown'
25
+ this.#gpuTier = v.tier
26
+ })
27
+
28
+ resizer.subscribe(() => {
29
+ this.#isMobile = /Mobi|Android/i.test(navigator.userAgent)
30
+
31
+ this.#isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0
32
+
33
+ setTimeout(() => {
34
+ this.#isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0
35
+ }, 0)
36
+ }, RESIZE_ORDER.DEVICE)
37
+
38
+ {
39
+ const canvas = document.createElement('canvas')
40
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
41
+ this.#isWebgl = (gl && gl instanceof WebGLRenderingContext) || false
42
+ }
43
+
44
+ {
45
+ const canvas = document.createElement('canvas')
46
+ if (canvas.getContext('2d')) {
47
+ this.#isWebp = canvas.toDataURL('image/webp').indexOf('data:image/webp') == 0
48
+ }
49
+ }
50
+
51
+ const userAgent = window.navigator.userAgent
52
+ const platform =
53
+ (window.navigator as any)?.userAgentData?.platform || window.navigator.platform
54
+ const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K', 'macOS']
55
+ const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']
56
+ const iosPlatforms = ['iPhone', 'iPad', 'iPod']
57
+
58
+ if (macosPlatforms.includes(platform)) {
59
+ this.#OS = 'macOS'
60
+ this.#isApple = true
61
+ } else if (iosPlatforms.includes(platform)) {
62
+ this.#OS = 'iOS'
63
+ this.#isApple = true
64
+ } else if (windowsPlatforms.includes(platform)) {
65
+ this.#OS = 'Windows'
66
+ } else if (/Android/.test(userAgent)) {
67
+ this.#OS = 'Android'
68
+ } else if (/Linux/.test(platform)) {
69
+ this.#OS = 'Linux'
70
+ } else {
71
+ this.#OS = 'unknown'
72
+ }
73
+ }
74
+ }
75
+
76
+ public get OS() {
77
+ return this.#OS
78
+ }
79
+
80
+ public get gpu() {
81
+ return this.#gpu
82
+ }
83
+
84
+ public get gpuTier() {
85
+ return this.#gpuTier
86
+ }
87
+
88
+ public get gpuDetection() {
89
+ return this.#gpuDetection
90
+ }
91
+
92
+ public get isMobile() {
93
+ return this.#isMobile
94
+ }
95
+
96
+ public get isTouch() {
97
+ return this.#isTouch
98
+ }
99
+
100
+ public get isWebgl() {
101
+ return this.#isWebgl
102
+ }
103
+
104
+ public get isWebp() {
105
+ return this.#isWebp
106
+ }
107
+
108
+ public get isApple() {
109
+ return this.#isApple
110
+ }
111
+ }
112
+
113
+ export const device = new Device()
@@ -0,0 +1,67 @@
1
+ import { RESIZE_ORDER } from '$packages/order'
2
+ import { resizer } from '$packages/resizer'
3
+ import { Store } from '$packages/store'
4
+ import { isBrowser } from '$packages/utils'
5
+
6
+ export enum ViewportBreakpoints {
7
+ 'mobile' = '600px',
8
+ 'tablet' = '1024px',
9
+ 'notebook' = '1280px',
10
+ 'desktop' = '1281px',
11
+ }
12
+
13
+ export enum ViewportMediaRules {
14
+ '<=mobile' = '(max-width: 600px)',
15
+ '>=mobile' = '(min-width: 601px)',
16
+ '<=tablet' = '(max-width: 1024px)',
17
+ '>=tablet' = '(min-width: 1025px)',
18
+ '<=notebook' = '(max-width: 1280px)',
19
+ '>=notebook' = '(min-width: 1281px)',
20
+ '<=desktop' = '(max-width: 1280px)',
21
+ '>=desktop' = '(min-width: 1281px)',
22
+ }
23
+
24
+ class Viewport {
25
+ #width = 0
26
+ #height = 0
27
+ #pixelRatio = 0
28
+ #store_type = new Store<keyof typeof ViewportBreakpoints | undefined>(undefined)
29
+
30
+ constructor() {
31
+ if (isBrowser) {
32
+ resizer.subscribe(() => {
33
+ this.#width = document.documentElement.clientWidth
34
+ this.#height = innerHeight
35
+ this.#pixelRatio = devicePixelRatio
36
+
37
+ if (matchMedia(ViewportMediaRules['<=mobile']).matches) {
38
+ this.#store_type.current = 'mobile'
39
+ } else if (matchMedia(ViewportMediaRules['<=tablet']).matches) {
40
+ this.#store_type.current = 'tablet'
41
+ } else if (matchMedia(ViewportMediaRules['<=notebook']).matches) {
42
+ this.#store_type.current = 'notebook'
43
+ } else if (matchMedia(ViewportMediaRules['>=desktop']).matches) {
44
+ this.#store_type.current = 'desktop'
45
+ }
46
+ }, RESIZE_ORDER.DEVICE)
47
+ }
48
+ }
49
+
50
+ public get width() {
51
+ return this.#width
52
+ }
53
+
54
+ public get height() {
55
+ return this.#height
56
+ }
57
+
58
+ public get store_type() {
59
+ return this.#store_type
60
+ }
61
+
62
+ public get pixelRatio() {
63
+ return this.#pixelRatio
64
+ }
65
+ }
66
+
67
+ export const viewport = new Viewport()
@@ -0,0 +1,2 @@
1
+ export { device, type DeviceOS } from './Device'
2
+ export { viewport, ViewportBreakpoints, ViewportMediaRules } from './Viewport'