@furystack/shades 14.0.0 → 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.
Files changed (76) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/esm/component-factory.spec.d.ts.map +1 -1
  3. package/esm/component-factory.spec.js +2 -2
  4. package/esm/component-factory.spec.js.map +1 -1
  5. package/esm/components/nested-hooks.js +2 -2
  6. package/esm/components/nested-hooks.js.map +1 -1
  7. package/esm/components/nested-hooks.spec.js +14 -14
  8. package/esm/components/nested-hooks.spec.js.map +1 -1
  9. package/esm/components/nested-navigate.js +1 -1
  10. package/esm/components/nested-navigate.js.map +1 -1
  11. package/esm/components/nested-navigate.spec.js +16 -16
  12. package/esm/components/nested-navigate.spec.js.map +1 -1
  13. package/esm/components/nested-replace.js +1 -1
  14. package/esm/components/nested-replace.js.map +1 -1
  15. package/esm/components/nested-replace.spec.js +6 -6
  16. package/esm/components/nested-replace.spec.js.map +1 -1
  17. package/esm/components/nested-route-link.js +1 -1
  18. package/esm/components/nested-route-link.js.map +1 -1
  19. package/esm/components/nested-route-link.spec.js +8 -8
  20. package/esm/components/nested-route-link.spec.js.map +1 -1
  21. package/esm/components/nested-router.js +3 -3
  22. package/esm/components/nested-router.js.map +1 -1
  23. package/esm/components/nested-router.spec.js +24 -24
  24. package/esm/components/nested-router.spec.js.map +1 -1
  25. package/esm/services/location-service.d.ts +39 -42
  26. package/esm/services/location-service.d.ts.map +1 -1
  27. package/esm/services/location-service.js +115 -134
  28. package/esm/services/location-service.js.map +1 -1
  29. package/esm/services/location-service.spec.js +32 -30
  30. package/esm/services/location-service.spec.js.map +1 -1
  31. package/esm/services/route-match-service.d.ts +3 -2
  32. package/esm/services/route-match-service.d.ts.map +1 -1
  33. package/esm/services/route-match-service.js +13 -22
  34. package/esm/services/route-match-service.js.map +1 -1
  35. package/esm/services/route-match-service.spec.js +40 -51
  36. package/esm/services/route-match-service.spec.js.map +1 -1
  37. package/esm/services/route-meta-utils.spec.js +3 -3
  38. package/esm/services/route-meta-utils.spec.js.map +1 -1
  39. package/esm/services/screen-service.d.ts +13 -72
  40. package/esm/services/screen-service.d.ts.map +1 -1
  41. package/esm/services/screen-service.js +45 -137
  42. package/esm/services/screen-service.js.map +1 -1
  43. package/esm/services/screen-service.spec.js +19 -18
  44. package/esm/services/screen-service.spec.js.map +1 -1
  45. package/esm/services/spatial-navigation-service.d.ts +17 -39
  46. package/esm/services/spatial-navigation-service.d.ts.map +1 -1
  47. package/esm/services/spatial-navigation-service.js +280 -313
  48. package/esm/services/spatial-navigation-service.js.map +1 -1
  49. package/esm/services/spatial-navigation-service.spec.js +163 -158
  50. package/esm/services/spatial-navigation-service.spec.js.map +1 -1
  51. package/esm/shade.d.ts +1 -1
  52. package/esm/shade.d.ts.map +1 -1
  53. package/esm/shade.js +10 -7
  54. package/esm/shade.js.map +1 -1
  55. package/package.json +5 -4
  56. package/src/component-factory.spec.tsx +3 -2
  57. package/src/components/nested-hooks.spec.ts +14 -14
  58. package/src/components/nested-hooks.ts +2 -2
  59. package/src/components/nested-navigate.spec.ts +17 -16
  60. package/src/components/nested-navigate.ts +1 -1
  61. package/src/components/nested-replace.spec.ts +7 -6
  62. package/src/components/nested-replace.ts +1 -1
  63. package/src/components/nested-route-link.spec.tsx +8 -8
  64. package/src/components/nested-route-link.tsx +1 -1
  65. package/src/components/nested-router.spec.tsx +24 -24
  66. package/src/components/nested-router.tsx +3 -3
  67. package/src/services/location-service.spec.ts +35 -31
  68. package/src/services/location-service.tsx +156 -122
  69. package/src/services/route-match-service.spec.ts +41 -32
  70. package/src/services/route-match-service.ts +17 -8
  71. package/src/services/route-meta-utils.spec.ts +3 -3
  72. package/src/services/screen-service.spec.ts +19 -18
  73. package/src/services/screen-service.ts +65 -110
  74. package/src/services/spatial-navigation-service.spec.ts +165 -160
  75. package/src/services/spatial-navigation-service.ts +299 -287
  76. package/src/shade.ts +11 -8
@@ -1,4 +1,5 @@
1
- import { Injectable } from '@furystack/inject'
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+ (all sizes)
27
- * - `sm`: 600px+ (small tablets and up)
28
- * - `md`: 960px+ (tablets and up)
29
- * - `lg`: 1280px+ (desktops and up)
30
- * - `xl`: 1920px+ (large desktops)
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.getInstance(ScreenService);
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
- * // Show desktop layout
37
+ * // Desktop layout
39
38
  * }
40
39
  *
41
- * // Subscribe to size changes for responsive behavior
42
40
  * screenService.screenSize.atLeast.md.subscribe((isAtLeastMd) => {
43
- * if (isAtLeastMd) {
44
- * console.log('Desktop or tablet view');
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
- @Injectable({ lifetime: 'singleton' })
66
- export class ScreenService implements Disposable {
67
- private getOrientation = () => (window.matchMedia?.('(orientation:landscape').matches ? 'landscape' : 'portrait')
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
- * Cleans up event listeners when the service is disposed.
83
- */
84
- public [Symbol.dispose]() {
85
- window.removeEventListener('resize', this.onResizeListener)
86
- this.orientation[Symbol.dispose]()
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
- * Observable values for checking if the screen is at least a certain size.
94
- *
95
- * Each observable emits `true` when the viewport width is >= the breakpoint threshold,
96
- * and `false` otherwise. Values update automatically on window resize.
97
- *
98
- * @example
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
- private screenSizeAtLeast(size: ScreenSize) {
119
- return window.innerWidth >= this.breakpoints[size].minSize
120
- }
68
+ const screenSizeAtLeast = (size: ScreenSize): boolean => window.innerWidth >= breakpoints[size].minSize
121
69
 
122
- /**
123
- * Observable value for tracking the screen orientation.
124
- * Emits 'landscape' or 'portrait' based on the current viewport dimensions.
125
- *
126
- * @example
127
- * ```typescript
128
- * screenService.orientation.subscribe((orientation) => {
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
- private onResizeListener = () => {
138
- this.refreshValues()
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
- private refreshValues() {
142
- this.orientation.setValue(this.getOrientation())
143
- ScreenSizes.forEach((size) => {
144
- this.screenSize.atLeast[size].setValue(this.screenSizeAtLeast(size))
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
- constructor() {
149
- window.addEventListener('resize', this.onResizeListener)
150
- this.refreshValues()
151
- }
152
- }
101
+ return {
102
+ breakpoints,
103
+ screenSize: { atLeast },
104
+ orientation,
105
+ }
106
+ },
107
+ })