@furystack/shades 13.2.2 → 15.0.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/CHANGELOG.md +157 -0
- package/esm/component-factory.spec.d.ts.map +1 -1
- package/esm/component-factory.spec.js +2 -2
- package/esm/component-factory.spec.js.map +1 -1
- package/esm/components/define-nested-routes.d.ts +27 -0
- package/esm/components/define-nested-routes.d.ts.map +1 -0
- package/esm/components/define-nested-routes.js +26 -0
- package/esm/components/define-nested-routes.js.map +1 -0
- package/esm/components/define-nested-routes.spec.d.ts +2 -0
- package/esm/components/define-nested-routes.spec.d.ts.map +1 -0
- package/esm/components/define-nested-routes.spec.js +81 -0
- package/esm/components/define-nested-routes.spec.js.map +1 -0
- package/esm/components/index.d.ts +3 -3
- package/esm/components/index.d.ts.map +1 -1
- package/esm/components/index.js +3 -3
- package/esm/components/index.js.map +1 -1
- package/esm/components/nested-hooks.d.ts +62 -0
- package/esm/components/nested-hooks.d.ts.map +1 -0
- package/esm/components/nested-hooks.js +73 -0
- package/esm/components/nested-hooks.js.map +1 -0
- package/esm/components/nested-hooks.spec.d.ts +2 -0
- package/esm/components/nested-hooks.spec.d.ts.map +1 -0
- package/esm/components/nested-hooks.spec.js +211 -0
- package/esm/components/nested-hooks.spec.js.map +1 -0
- package/esm/components/nested-navigate.d.ts +52 -21
- package/esm/components/nested-navigate.d.ts.map +1 -1
- package/esm/components/nested-navigate.js +32 -17
- package/esm/components/nested-navigate.js.map +1 -1
- package/esm/components/nested-navigate.spec.js +109 -19
- package/esm/components/nested-navigate.spec.js.map +1 -1
- package/esm/components/nested-replace.d.ts +36 -0
- package/esm/components/nested-replace.d.ts.map +1 -0
- package/esm/components/nested-replace.js +38 -0
- package/esm/components/nested-replace.js.map +1 -0
- package/esm/components/nested-replace.spec.d.ts +2 -0
- package/esm/components/nested-replace.spec.d.ts.map +1 -0
- package/esm/components/nested-replace.spec.js +59 -0
- package/esm/components/nested-replace.spec.js.map +1 -0
- package/esm/components/nested-route-link.d.ts +36 -26
- package/esm/components/nested-route-link.d.ts.map +1 -1
- package/esm/components/nested-route-link.js +18 -20
- package/esm/components/nested-route-link.js.map +1 -1
- package/esm/components/nested-route-link.spec.js +67 -17
- package/esm/components/nested-route-link.spec.js.map +1 -1
- package/esm/components/nested-route-types.d.ts +79 -2
- package/esm/components/nested-route-types.d.ts.map +1 -1
- package/esm/components/nested-router.d.ts +77 -7
- package/esm/components/nested-router.d.ts.map +1 -1
- package/esm/components/nested-router.js +139 -13
- package/esm/components/nested-router.js.map +1 -1
- package/esm/components/nested-router.spec.js +283 -52
- package/esm/components/nested-router.spec.js.map +1 -1
- package/esm/services/location-service.d.ts +39 -42
- package/esm/services/location-service.d.ts.map +1 -1
- package/esm/services/location-service.js +115 -134
- package/esm/services/location-service.js.map +1 -1
- package/esm/services/location-service.spec.js +59 -24
- package/esm/services/location-service.spec.js.map +1 -1
- package/esm/services/route-match-service.d.ts +3 -2
- package/esm/services/route-match-service.d.ts.map +1 -1
- package/esm/services/route-match-service.js +13 -22
- package/esm/services/route-match-service.js.map +1 -1
- package/esm/services/route-match-service.spec.js +42 -49
- package/esm/services/route-match-service.spec.js.map +1 -1
- package/esm/services/route-meta-utils.d.ts +28 -6
- package/esm/services/route-meta-utils.d.ts.map +1 -1
- package/esm/services/route-meta-utils.js +18 -4
- package/esm/services/route-meta-utils.js.map +1 -1
- package/esm/services/route-meta-utils.spec.js +60 -5
- package/esm/services/route-meta-utils.spec.js.map +1 -1
- package/esm/services/screen-service.d.ts +13 -72
- package/esm/services/screen-service.d.ts.map +1 -1
- package/esm/services/screen-service.js +45 -137
- package/esm/services/screen-service.js.map +1 -1
- package/esm/services/screen-service.spec.js +19 -18
- package/esm/services/screen-service.spec.js.map +1 -1
- package/esm/services/spatial-navigation-service.d.ts +17 -39
- package/esm/services/spatial-navigation-service.d.ts.map +1 -1
- package/esm/services/spatial-navigation-service.js +280 -313
- package/esm/services/spatial-navigation-service.js.map +1 -1
- package/esm/services/spatial-navigation-service.spec.js +163 -158
- package/esm/services/spatial-navigation-service.spec.js.map +1 -1
- package/esm/shade.d.ts +1 -1
- package/esm/shade.d.ts.map +1 -1
- package/esm/shade.js +10 -7
- package/esm/shade.js.map +1 -1
- package/esm/vnode.d.ts +1 -1
- package/esm/vnode.js +1 -1
- package/package.json +5 -4
- package/src/component-factory.spec.tsx +3 -2
- package/src/components/define-nested-routes.spec.ts +91 -0
- package/src/components/define-nested-routes.ts +27 -0
- package/src/components/index.ts +3 -3
- package/src/components/nested-hooks.spec.ts +254 -0
- package/src/components/nested-hooks.ts +109 -0
- package/src/components/nested-navigate.spec.ts +136 -32
- package/src/components/nested-navigate.ts +75 -26
- package/src/components/nested-replace.spec.ts +89 -0
- package/src/components/nested-replace.ts +46 -0
- package/src/components/nested-route-link.spec.tsx +90 -22
- package/src/components/nested-route-link.tsx +54 -29
- package/src/components/nested-route-types.ts +82 -2
- package/src/components/nested-router.spec.tsx +334 -51
- package/src/components/nested-router.tsx +184 -24
- package/src/services/location-service.spec.ts +68 -25
- package/src/services/location-service.tsx +156 -122
- package/src/services/route-match-service.spec.ts +41 -28
- package/src/services/route-match-service.ts +17 -8
- package/src/services/route-meta-utils.spec.ts +78 -10
- package/src/services/route-meta-utils.ts +44 -15
- package/src/services/screen-service.spec.ts +19 -18
- package/src/services/screen-service.ts +65 -110
- package/src/services/spatial-navigation-service.spec.ts +165 -160
- package/src/services/spatial-navigation-service.ts +299 -287
- package/src/shade.ts +11 -8
- package/src/vnode.ts +1 -1
- package/esm/components/link-to-route.d.ts +0 -10
- package/esm/components/link-to-route.d.ts.map +0 -1
- package/esm/components/link-to-route.js +0 -16
- package/esm/components/link-to-route.js.map +0 -1
- package/esm/components/link-to-route.spec.d.ts +0 -2
- package/esm/components/link-to-route.spec.d.ts.map +0 -1
- package/esm/components/link-to-route.spec.js +0 -30
- package/esm/components/link-to-route.spec.js.map +0 -1
- package/esm/components/route-link.d.ts +0 -10
- package/esm/components/route-link.d.ts.map +0 -1
- package/esm/components/route-link.js +0 -25
- package/esm/components/route-link.js.map +0 -1
- package/esm/components/route-link.spec.d.ts +0 -2
- package/esm/components/route-link.spec.d.ts.map +0 -1
- package/esm/components/route-link.spec.js +0 -34
- package/esm/components/route-link.spec.js.map +0 -1
- package/esm/components/router.d.ts +0 -44
- package/esm/components/router.d.ts.map +0 -1
- package/esm/components/router.js +0 -66
- package/esm/components/router.js.map +0 -1
- package/esm/components/router.spec.d.ts +0 -2
- package/esm/components/router.spec.d.ts.map +0 -1
- package/esm/components/router.spec.js +0 -161
- package/esm/components/router.spec.js.map +0 -1
- package/src/components/link-to-route.spec.tsx +0 -45
- package/src/components/link-to-route.tsx +0 -26
- package/src/components/route-link.spec.tsx +0 -44
- package/src/components/route-link.tsx +0 -29
- package/src/components/router.spec.tsx +0 -217
- package/src/components/router.tsx +0 -103
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Injector } from '@furystack/inject'
|
|
2
2
|
|
|
3
|
+
import type { ExtractRoutePaths } from '../components/nested-route-types.js'
|
|
3
4
|
import type { MatchChainEntry, NestedRoute, NestedRouteMeta } from '../components/nested-router.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -57,29 +58,57 @@ export const buildDocumentTitle = (
|
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* A node in a navigation tree extracted from route definitions.
|
|
61
|
+
*
|
|
62
|
+
* When {@link extractNavTree} is called with an explicit route tree type
|
|
63
|
+
* parameter (e.g. `extractNavTree<typeof appRoutes>(...)`), `pattern` narrows
|
|
64
|
+
* to the declared route keys and `fullPath` narrows to the union of all
|
|
65
|
+
* composed paths in the subtree. Without a type parameter the generic falls
|
|
66
|
+
* back to a widened shape so ad-hoc consumers keep compiling.
|
|
67
|
+
*
|
|
68
|
+
* @typeParam TRoutes - The route tree the node was extracted from
|
|
60
69
|
*/
|
|
61
|
-
export type NavTreeNode
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
export type NavTreeNode<
|
|
71
|
+
TRoutes extends Record<string, NestedRoute<any, any, any>> = Record<string, NestedRoute<any, any, any>>,
|
|
72
|
+
> = {
|
|
73
|
+
pattern: keyof TRoutes & string
|
|
74
|
+
fullPath: ExtractRoutePaths<TRoutes> & string
|
|
64
75
|
meta?: NestedRouteMeta
|
|
65
|
-
children?: NavTreeNode
|
|
76
|
+
children?: Array<NavTreeNode<TRoutes>>
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
/**
|
|
69
80
|
* Extracts a navigation tree from route definitions.
|
|
70
|
-
*
|
|
81
|
+
*
|
|
82
|
+
* The returned nodes preserve the route tree's static typing: `pattern` is
|
|
83
|
+
* narrowed to the declared keys of `TRoutes` and `fullPath` is narrowed to
|
|
84
|
+
* the union of composed paths reachable from `TRoutes` (as produced by
|
|
85
|
+
* {@link ExtractRoutePaths}). This makes the output a single, typed source
|
|
86
|
+
* of truth for sidebar navigation, breadcrumbs and other route-tree-aware
|
|
87
|
+
* UIs — without requiring `as` casts at the call site.
|
|
88
|
+
*
|
|
89
|
+
* Child nodes share their parent's `TRoutes` type parameter; the resulting
|
|
90
|
+
* union is a safe upper bound of the actual paths at any depth, which keeps
|
|
91
|
+
* the helper simple while still accepted by `createNestedRouteLink`,
|
|
92
|
+
* `createNestedNavigate` and `createNestedReplace` factories bound to the
|
|
93
|
+
* same route tree.
|
|
94
|
+
*
|
|
71
95
|
* @param routes - The route definitions to extract from
|
|
72
96
|
* @param parentPath - The parent path prefix (used internally for recursion)
|
|
73
97
|
* @returns An array of navigation tree nodes
|
|
74
98
|
*/
|
|
75
|
-
export const extractNavTree =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
export const extractNavTree = <TRoutes extends Record<string, NestedRoute<any, any, any>>>(
|
|
100
|
+
routes: TRoutes,
|
|
101
|
+
parentPath?: string,
|
|
102
|
+
): Array<NavTreeNode<TRoutes>> => {
|
|
103
|
+
const build = (rs: Record<string, NestedRoute<any, any, any>>, pp?: string): Array<NavTreeNode<TRoutes>> =>
|
|
104
|
+
Object.entries(rs).map(([pattern, route]) => {
|
|
105
|
+
const fullPath = pp ? `${pp === '/' ? '' : pp}${pattern}` : pattern
|
|
106
|
+
return {
|
|
107
|
+
pattern,
|
|
108
|
+
fullPath,
|
|
109
|
+
meta: route.meta,
|
|
110
|
+
children: route.children ? build(route.children, fullPath) : undefined,
|
|
111
|
+
} as NavTreeNode<TRoutes>
|
|
112
|
+
})
|
|
113
|
+
return build(routes, parentPath)
|
|
85
114
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createInjector } from '@furystack/inject'
|
|
2
2
|
import { usingAsync } from '@furystack/utils'
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
4
|
import { ScreenService, ScreenSizes } from './screen-service.js'
|
|
@@ -13,16 +13,17 @@ describe('ScreenService', () => {
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
it('Should be constructed', async () => {
|
|
16
|
-
await usingAsync(
|
|
17
|
-
const s = i.
|
|
18
|
-
expect(s).
|
|
16
|
+
await usingAsync(createInjector(), async (i) => {
|
|
17
|
+
const s = i.get(ScreenService)
|
|
18
|
+
expect(s).toBeDefined()
|
|
19
|
+
expect(s.breakpoints).toBeDefined()
|
|
19
20
|
})
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
describe('breakpoints', () => {
|
|
23
24
|
it('Should have correct breakpoint definitions', async () => {
|
|
24
|
-
await usingAsync(
|
|
25
|
-
const s = i.
|
|
25
|
+
await usingAsync(createInjector(), async (i) => {
|
|
26
|
+
const s = i.get(ScreenService)
|
|
26
27
|
|
|
27
28
|
expect(s.breakpoints.xs.minSize).toBe(0)
|
|
28
29
|
expect(s.breakpoints.sm.minSize).toBe(600)
|
|
@@ -35,8 +36,8 @@ describe('ScreenService', () => {
|
|
|
35
36
|
|
|
36
37
|
describe('screenSize.atLeast', () => {
|
|
37
38
|
it('Should have observable for each screen size', async () => {
|
|
38
|
-
await usingAsync(
|
|
39
|
-
const s = i.
|
|
39
|
+
await usingAsync(createInjector(), async (i) => {
|
|
40
|
+
const s = i.get(ScreenService)
|
|
40
41
|
|
|
41
42
|
for (const size of ScreenSizes) {
|
|
42
43
|
expect(s.screenSize.atLeast[size]).toBeDefined()
|
|
@@ -46,16 +47,16 @@ describe('ScreenService', () => {
|
|
|
46
47
|
})
|
|
47
48
|
|
|
48
49
|
it('Should return true for xs on any screen size', async () => {
|
|
49
|
-
await usingAsync(
|
|
50
|
-
const s = i.
|
|
50
|
+
await usingAsync(createInjector(), async (i) => {
|
|
51
|
+
const s = i.get(ScreenService)
|
|
51
52
|
// xs has minSize 0, so it should always be true
|
|
52
53
|
expect(s.screenSize.atLeast.xs.getValue()).toBe(true)
|
|
53
54
|
})
|
|
54
55
|
})
|
|
55
56
|
|
|
56
57
|
it('Should update screenSize observables on window resize', async () => {
|
|
57
|
-
await usingAsync(
|
|
58
|
-
const s = i.
|
|
58
|
+
await usingAsync(createInjector(), async (i) => {
|
|
59
|
+
const s = i.get(ScreenService)
|
|
59
60
|
|
|
60
61
|
// Mock window.innerWidth to simulate a large screen
|
|
61
62
|
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1920)
|
|
@@ -86,8 +87,8 @@ describe('ScreenService', () => {
|
|
|
86
87
|
|
|
87
88
|
describe('orientation', () => {
|
|
88
89
|
it('Should have an orientation observable', async () => {
|
|
89
|
-
await usingAsync(
|
|
90
|
-
const s = i.
|
|
90
|
+
await usingAsync(createInjector(), async (i) => {
|
|
91
|
+
const s = i.get(ScreenService)
|
|
91
92
|
const orientation = s.orientation.getValue()
|
|
92
93
|
expect(['landscape', 'portrait']).toContain(orientation)
|
|
93
94
|
})
|
|
@@ -98,10 +99,10 @@ describe('ScreenService', () => {
|
|
|
98
99
|
const matchMediaMock = vi.fn()
|
|
99
100
|
window.matchMedia = matchMediaMock
|
|
100
101
|
|
|
101
|
-
await usingAsync(
|
|
102
|
+
await usingAsync(createInjector(), async (i) => {
|
|
102
103
|
// Set initial orientation to landscape
|
|
103
104
|
matchMediaMock.mockReturnValue({ matches: true } as MediaQueryList)
|
|
104
|
-
const s = i.
|
|
105
|
+
const s = i.get(ScreenService)
|
|
105
106
|
|
|
106
107
|
// Verify initial landscape
|
|
107
108
|
window.dispatchEvent(new Event('resize'))
|
|
@@ -119,8 +120,8 @@ describe('ScreenService', () => {
|
|
|
119
120
|
it('Should remove resize event listener on dispose', async () => {
|
|
120
121
|
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener')
|
|
121
122
|
|
|
122
|
-
await usingAsync(
|
|
123
|
-
i.
|
|
123
|
+
await usingAsync(createInjector(), async (i) => {
|
|
124
|
+
i.get(ScreenService)
|
|
124
125
|
})
|
|
125
126
|
|
|
126
127
|
expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Token } from '@furystack/inject'
|
|
2
|
+
import { defineService } from '@furystack/inject'
|
|
2
3
|
import { ObservableValue } from '@furystack/utils'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -19,134 +20,88 @@ export type Breakpoint = { name: ScreenSize; minSize: number; maxSize?: number }
|
|
|
19
20
|
/**
|
|
20
21
|
* Service for detecting and subscribing to screen size and orientation changes.
|
|
21
22
|
*
|
|
22
|
-
* This service provides reactive observables for responsive design decisions.
|
|
23
23
|
* Use `screenSize.atLeast[size]` to check if the viewport is at least a certain size.
|
|
24
24
|
*
|
|
25
25
|
* **Breakpoint Thresholds:**
|
|
26
|
-
* - `xs`: 0px+
|
|
27
|
-
* - `sm`: 600px+
|
|
28
|
-
* - `md`: 960px+
|
|
29
|
-
* - `lg`: 1280px+
|
|
30
|
-
* - `xl`: 1920px+
|
|
26
|
+
* - `xs`: 0px+
|
|
27
|
+
* - `sm`: 600px+
|
|
28
|
+
* - `md`: 960px+
|
|
29
|
+
* - `lg`: 1280px+
|
|
30
|
+
* - `xl`: 1920px+
|
|
31
31
|
*
|
|
32
32
|
* @example
|
|
33
33
|
* ```typescript
|
|
34
|
-
* const screenService = injector.
|
|
34
|
+
* const screenService = injector.get(ScreenService)
|
|
35
35
|
*
|
|
36
|
-
* // Check if screen is at least medium size
|
|
37
36
|
* if (screenService.screenSize.atLeast.md.getValue()) {
|
|
38
|
-
* //
|
|
37
|
+
* // Desktop layout
|
|
39
38
|
* }
|
|
40
39
|
*
|
|
41
|
-
* // Subscribe to size changes for responsive behavior
|
|
42
40
|
* screenService.screenSize.atLeast.md.subscribe((isAtLeastMd) => {
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* } else {
|
|
46
|
-
* console.log('Mobile view');
|
|
47
|
-
* }
|
|
48
|
-
* });
|
|
49
|
-
*
|
|
50
|
-
* // Get current breakpoint by checking from largest to smallest
|
|
51
|
-
* const getCurrentBreakpoint = (): ScreenSize => {
|
|
52
|
-
* if (screenService.screenSize.atLeast.xl.getValue()) return 'xl';
|
|
53
|
-
* if (screenService.screenSize.atLeast.lg.getValue()) return 'lg';
|
|
54
|
-
* if (screenService.screenSize.atLeast.md.getValue()) return 'md';
|
|
55
|
-
* if (screenService.screenSize.atLeast.sm.getValue()) return 'sm';
|
|
56
|
-
* return 'xs';
|
|
57
|
-
* };
|
|
58
|
-
*
|
|
59
|
-
* // Subscribe to orientation changes
|
|
60
|
-
* screenService.orientation.subscribe((orientation) => {
|
|
61
|
-
* console.log(`Screen is now in ${orientation} mode`);
|
|
62
|
-
* });
|
|
41
|
+
* // react to breakpoint changes
|
|
42
|
+
* })
|
|
63
43
|
* ```
|
|
64
44
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* The definitions of the breakpoint thresholds in pixels.
|
|
71
|
-
* Each breakpoint represents the minimum width for that size category.
|
|
72
|
-
*/
|
|
73
|
-
public readonly breakpoints: { [K in ScreenSize]: { minSize: number } } = {
|
|
74
|
-
xl: { minSize: 1920 },
|
|
75
|
-
lg: { minSize: 1280 },
|
|
76
|
-
md: { minSize: 960 },
|
|
77
|
-
sm: { minSize: 600 },
|
|
78
|
-
xs: { minSize: 0 },
|
|
45
|
+
export interface ScreenService {
|
|
46
|
+
readonly breakpoints: { [K in ScreenSize]: { minSize: number } }
|
|
47
|
+
readonly screenSize: {
|
|
48
|
+
atLeast: { [K in ScreenSize]: ObservableValue<boolean> }
|
|
79
49
|
}
|
|
50
|
+
readonly orientation: ObservableValue<'landscape' | 'portrait'>
|
|
51
|
+
}
|
|
80
52
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Object.values(this.screenSize.atLeast).forEach((observable) => {
|
|
88
|
-
observable[Symbol.dispose]()
|
|
89
|
-
})
|
|
90
|
-
}
|
|
53
|
+
export const ScreenService: Token<ScreenService, 'singleton'> = defineService({
|
|
54
|
+
name: '@furystack/shades/ScreenService',
|
|
55
|
+
lifetime: 'singleton',
|
|
56
|
+
factory: ({ onDispose }) => {
|
|
57
|
+
const getOrientation = (): 'landscape' | 'portrait' =>
|
|
58
|
+
window.matchMedia?.('(orientation:landscape').matches ? 'landscape' : 'portrait'
|
|
91
59
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* ```typescript
|
|
100
|
-
* // Hide sidebar on small screens
|
|
101
|
-
* screenService.screenSize.atLeast.md.subscribe((isAtLeastMd) => {
|
|
102
|
-
* sidebarVisible.setValue(isAtLeastMd);
|
|
103
|
-
* });
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
public readonly screenSize: {
|
|
107
|
-
atLeast: { [K in ScreenSize]: ObservableValue<boolean> }
|
|
108
|
-
} = {
|
|
109
|
-
atLeast: {
|
|
110
|
-
xs: new ObservableValue<boolean>(this.screenSizeAtLeast('xs')),
|
|
111
|
-
sm: new ObservableValue<boolean>(this.screenSizeAtLeast('sm')),
|
|
112
|
-
md: new ObservableValue<boolean>(this.screenSizeAtLeast('md')),
|
|
113
|
-
lg: new ObservableValue<boolean>(this.screenSizeAtLeast('lg')),
|
|
114
|
-
xl: new ObservableValue<boolean>(this.screenSizeAtLeast('xl')),
|
|
115
|
-
},
|
|
116
|
-
}
|
|
60
|
+
const breakpoints: { [K in ScreenSize]: { minSize: number } } = {
|
|
61
|
+
xl: { minSize: 1920 },
|
|
62
|
+
lg: { minSize: 1280 },
|
|
63
|
+
md: { minSize: 960 },
|
|
64
|
+
sm: { minSize: 600 },
|
|
65
|
+
xs: { minSize: 0 },
|
|
66
|
+
}
|
|
117
67
|
|
|
118
|
-
|
|
119
|
-
return window.innerWidth >= this.breakpoints[size].minSize
|
|
120
|
-
}
|
|
68
|
+
const screenSizeAtLeast = (size: ScreenSize): boolean => window.innerWidth >= breakpoints[size].minSize
|
|
121
69
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* if (orientation === 'landscape') {
|
|
130
|
-
* // Adjust layout for landscape mode
|
|
131
|
-
* }
|
|
132
|
-
* });
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
135
|
-
public orientation = new ObservableValue<'landscape' | 'portrait'>(this.getOrientation())
|
|
70
|
+
const atLeast: { [K in ScreenSize]: ObservableValue<boolean> } = {
|
|
71
|
+
xs: new ObservableValue<boolean>(screenSizeAtLeast('xs')),
|
|
72
|
+
sm: new ObservableValue<boolean>(screenSizeAtLeast('sm')),
|
|
73
|
+
md: new ObservableValue<boolean>(screenSizeAtLeast('md')),
|
|
74
|
+
lg: new ObservableValue<boolean>(screenSizeAtLeast('lg')),
|
|
75
|
+
xl: new ObservableValue<boolean>(screenSizeAtLeast('xl')),
|
|
76
|
+
}
|
|
136
77
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
78
|
+
const orientation = new ObservableValue<'landscape' | 'portrait'>(getOrientation())
|
|
79
|
+
|
|
80
|
+
const refreshValues = (): void => {
|
|
81
|
+
orientation.setValue(getOrientation())
|
|
82
|
+
for (const size of ScreenSizes) {
|
|
83
|
+
atLeast[size].setValue(screenSizeAtLeast(size))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const onResizeListener = (): void => {
|
|
88
|
+
refreshValues()
|
|
89
|
+
}
|
|
140
90
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
91
|
+
window.addEventListener('resize', onResizeListener)
|
|
92
|
+
refreshValues()
|
|
93
|
+
|
|
94
|
+
onDispose(() => {
|
|
95
|
+
window.removeEventListener('resize', onResizeListener)
|
|
96
|
+
// eslint-disable-next-line furystack/prefer-using-wrapper -- Disposal is deferred to the injector's onDispose hook.
|
|
97
|
+
orientation[Symbol.dispose]()
|
|
98
|
+
Object.values(atLeast).forEach((observable) => observable[Symbol.dispose]())
|
|
145
99
|
})
|
|
146
|
-
}
|
|
147
100
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
101
|
+
return {
|
|
102
|
+
breakpoints,
|
|
103
|
+
screenSize: { atLeast },
|
|
104
|
+
orientation,
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
})
|