Skip to content

Commit f390bf7

Browse files
committed
[IMPL] useScreen hook
1 parent d50c93d commit f390bf7

12 files changed

Lines changed: 314 additions & 9 deletions

File tree

apps/react-tools-demo/scripts/generateMarkdown.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ function buildHooksUtilsMarkdownObject(file) {
226226
return obj;
227227
}
228228
const lines = getJsDoc(fileSplitted);
229+
let codeLines = fileSplitted.slice(fileSplitted.findIndex(line => line === lines.at(-1))+2);
229230
lines.forEach((line, index) => {
230231
const depuratedLine = (line.split(" * ")[1]);
231232
if(depuratedLine.startsWith("@param")) {
@@ -268,7 +269,6 @@ function buildHooksUtilsMarkdownObject(file) {
268269
}
269270
}
270271
});
271-
let codeLines = fileSplitted.slice(fileSplitted.findIndex(line => line === lines.at(-1))+2);
272272
let typeLineIndex = codeLines.findIndex(line => !line.startsWith("//") && !line.startsWith(" */") && !line.startsWith("*/") && !line.startsWith(("/*")));
273273
let typeLine = codeLines[typeLineIndex];
274274
if(typeLine.startsWith("export")) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useScreen } from "../../../../../../packages/react-tools/src"
2+
3+
/**
4+
The component shows screens information.
5+
*/
6+
export const UseScreen = () => {
7+
const [details] = useScreen(true);
8+
9+
return <div style={{ textAlign: "left", padding: "0 1em", maxHeight: 300, overflow: "auto", border: "1px solid lightgray" }}>
10+
<p><strong>Current screen:</strong></p>
11+
<pre>{JSON.stringify(details.currentScreen, null, 2)}</pre>
12+
<p><strong>Screens:</strong></p>
13+
{
14+
details.screens?.map((el, index) => (
15+
<pre key={index}>{el.label}</pre>
16+
))
17+
}
18+
</div>
19+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export const COMPONENTS = [
6161
"useMouse",
6262
"useLongPress",
6363
"useBeforeUnload",
64-
"useDoubleClick"
64+
"useDoubleClick",
65+
"useScreen"
6566
],
6667
//API DOM
6768
[

apps/react-tools-demo/src/markdown/useBattery.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const UseBattery = () => {
2323
## API
2424

2525
```tsx
26-
useBattery * - __isSupported__: boolean that indicates if Battery Status API is available.
26+
useBattery (opts?: { onChargingChange?: (evt: Event) => void, onChargingTimeChange?: (evt: Event) => void, onDischargingTimeChange?: (evt: Event) => void, onLevelChange?: (evt: Event) => void }): BatteryStatus
2727
```
2828

2929
> ### Params

apps/react-tools-demo/src/markdown/useGeolocation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const UseGeolocation = () => {
4242
## API
4343

4444
```tsx
45-
useGeolocation * - _first element_: is the location object with two properties: __isSupported__ and __position__.
45+
useGeolocation ({mode, locationOptions, onError}: { locationOptions?: PositionOptions, mode: "observe" | "current" | "manual", onError?: (error: GeolocationPositionError) => void }): [GeoLocationObject, ()=>Promise<void>, ()=>Promise<()=>void>]
4646
```
4747

4848
> ### Params
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# useScreen
2+
Hook to work with [Screen Orientation API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Orientation_API) and [Window Management API](https://developer.mozilla.org/en-US/docs/Web/API/Window_Management_API).
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseScreen = () => {
8+
const [details] = useScreen();
9+
10+
return <div style={{ textAlign: "left", padding: "0 1em", maxHeight: 300, overflow: "auto", border: "1px solid lightgray" }}>
11+
<p><strong>Current screen:</strong></p>
12+
<pre>{JSON.stringify(details.currentScreen, null, 2)}</pre>
13+
<p><strong>Screens:</strong></p>
14+
{
15+
details.screens?.map((el, index) => (
16+
<pre key={index}>{JSON.stringify(el, null, 2)}</pre>
17+
))
18+
}
19+
</div>
20+
}
21+
```
22+
23+
> The component shows screens information.
24+
25+
26+
## API
27+
28+
```tsx
29+
useScreen (allScreen?:boolean): [ScreenDetails, (orientation: OrientationLockType) => Promise<void>, ()=>void]
30+
```
31+
32+
> ### Params
33+
>
34+
> - __allScreen=false?__: _boolean_
35+
to interact with all screens or only with current screen.
36+
>
37+
38+
> ### Returns
39+
>
40+
> : __Array__:
41+
- _ScreenDetails_
42+
- _(orientation: OrientationLockType)=>void_
43+
- _()=> void_
44+
> It contains:
45+
> - __details__: an object with two properties:
46+
> - _currentScreen_: object of type _ScreenDetail_ with informations of current screen.
47+
> - _screens_: a _ScreenDetail_ array of all available screens, if browser supports this functionality, otherwise _undefined_.
48+
> - A _ScreenDetail_ object has these properties:
49+
> - __availHeight__
50+
> - __availWidth__
51+
> - __height__
52+
> - __width__
53+
> - __colorDepth__
54+
> - __pixelDepth__
55+
> - __orientation__:
56+
> - __angle__
57+
> - __type__
58+
> - __availLeft__: only available if browser supports them, otherwise is _undefined_
59+
> - __availTop__: only available if browser supports them, otherwise is _undefined_
60+
> - __left__: only available if browser supports them, otherwise is _undefined_
61+
> - __top__: only available if browser supports them, otherwise is _undefined_
62+
> - __devicePixelRatio__: only available if browser supports them, otherwise is _undefined_
63+
> - __isInternal__: only available if browser supports them, otherwise is _undefined_
64+
> - __isPrimary__: only available if browser supports them, otherwise is _undefined_
65+
> - __label__: only available if browser supports them, otherwise is _undefined_
66+
> - __lock__: function that locks the orientation of the containing document to the specified orientation. Typically orientation locking is only enabled on mobile devices, and when the browser context is full screen.
67+
> - __unlock__: function that unlocks the orientation of the containing document from its default orientation.
68+
>

packages/react-tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
- [x] useLongPress
6868
- [x] useDoubleClick
6969
- [x] useBeforeUnload
70-
- [ ] useOrientation
70+
- [x] useScreen (orientation and ecc)
7171
- [ ] useKeysEvents
7272
- [ ] useHotKeys (https://mantine.dev/hooks/use-hotkeys/)
7373
- [ ] useImageOnLoad

packages/react-tools/src/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,5 @@ export { useShare } from './useShare';
6666
export { useEyeDropper } from './useEyeDropper';
6767
export { useDialogBox } from './useDialogBox';
6868
export { useBeforeUnload } from './useBeforeUnload';
69-
export { useDoubleClick } from './useDoubleClick';
69+
export { useDoubleClick } from './useDoubleClick';
70+
export { useScreen } from './useScreen';
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { useCallback, useMemo, useRef } from "react";
2+
import { useSyncExternalStore } from "."
3+
import { OrientationLockType, ScreenDetail, ScreenDetails, ScreenDetailsEvt } from "../models";
4+
5+
const listeners = new Set<() => void>();
6+
7+
/**
8+
* **`useScreen`**: Hook to work with [Screen Orientation API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Orientation_API) and [Window Management API](https://developer.mozilla.org/en-US/docs/Web/API/Window_Management_API).
9+
* @param {boolean} [allScreen=false] - to interact with all screens or only with current screen.
10+
* @returns {[ScreenDetails, (orientation: OrientationLockType)=>void, ()=> void]}
11+
* It contains:
12+
* - __details__: an object with two properties:
13+
* - _currentScreen_: object of type _ScreenDetail_ with informations of current screen.
14+
* - _screens_: a _ScreenDetail_ array of all available screens, if browser supports this functionality, otherwise _undefined_.
15+
* - A _ScreenDetail_ object has these properties:
16+
* - __availHeight__
17+
* - __availWidth__
18+
* - __height__
19+
* - __width__
20+
* - __colorDepth__
21+
* - __pixelDepth__
22+
* - __orientation__:
23+
* - __angle__
24+
* - __type__
25+
* - __availLeft__: only available if browser supports them, otherwise is _undefined_
26+
* - __availTop__: only available if browser supports them, otherwise is _undefined_
27+
* - __left__: only available if browser supports them, otherwise is _undefined_
28+
* - __top__: only available if browser supports them, otherwise is _undefined_
29+
* - __devicePixelRatio__: only available if browser supports them, otherwise is _undefined_
30+
* - __isInternal__: only available if browser supports them, otherwise is _undefined_
31+
* - __isPrimary__: only available if browser supports them, otherwise is _undefined_
32+
* - __label__: only available if browser supports them, otherwise is _undefined_
33+
* - __lock__: function that locks the orientation of the containing document to the specified orientation. Typically orientation locking is only enabled on mobile devices, and when the browser context is full screen.
34+
* - __unlock__: function that unlocks the orientation of the containing document from its default orientation.
35+
*/
36+
export const useScreen = (allScreen?:boolean): [ScreenDetails, (orientation: OrientationLockType) => Promise<void>, ()=>void] => {
37+
const screensDetailsCached = useRef<ScreenDetailsEvt>();
38+
const changes = useRef(false);
39+
40+
const details = useSyncExternalStore(
41+
useCallback(notif => {
42+
const listener = () => {
43+
console.log("orientation")
44+
changes.current = true;
45+
listeners.forEach(l => l());
46+
};
47+
listeners.add(notif);
48+
let pending = false;
49+
if (listeners.size === 1) {
50+
screen.orientation.onchange = listener;
51+
if ("getScreenDetails" in window) {
52+
pending = true;
53+
(window.getScreenDetails as () => Promise<ScreenDetailsEvt>)()
54+
.then(screensDetails => {
55+
if (pending) {
56+
screensDetails.addEventListener("currentscreenchange", listener);
57+
allScreen && screensDetails.addEventListener("screenschange", listener);
58+
screensDetailsCached.current = screensDetails;
59+
notif();
60+
}
61+
})
62+
.catch(() => {
63+
void 0;
64+
})
65+
}
66+
}
67+
return () => {
68+
listeners.delete(notif);
69+
if (listeners.size === 0) {
70+
if (screensDetailsCached.current) {
71+
screensDetailsCached.current.removeEventListener("currentscreenchange", listener);
72+
allScreen && screensDetailsCached.current.removeEventListener("screenschange", listener);
73+
}
74+
pending = false;
75+
screen.orientation.onchange = null;
76+
}
77+
}
78+
}, [allScreen]),
79+
useMemo(() => {
80+
let cachedDetails: ScreenDetails = {
81+
currentScreen: {
82+
availHeight: screen.availWidth,
83+
availWidth: screen.availWidth,
84+
height: screen.height,
85+
width: screen.width,
86+
colorDepth: screen.colorDepth,
87+
pixelDepth: screen.pixelDepth,
88+
orientation: {
89+
angle: screen.orientation.angle,
90+
type: screen.orientation.type,
91+
},
92+
availLeft: undefined,
93+
availTop: undefined,
94+
left: undefined,
95+
top: undefined,
96+
devicePixelRatio: undefined,
97+
isInternal: undefined,
98+
isPrimary: undefined,
99+
label: undefined
100+
},
101+
screens: undefined
102+
}
103+
return () => {
104+
const keys = Reflect.ownKeys(cachedDetails.currentScreen);
105+
if (cachedDetails.currentScreen.isPrimary === undefined && screensDetailsCached.current) {
106+
cachedDetails = {
107+
currentScreen: {
108+
...cachedDetails.currentScreen,
109+
availLeft: screensDetailsCached.current.currentScreen.availLeft,
110+
availTop: screensDetailsCached.current.currentScreen.availTop,
111+
left: screensDetailsCached.current.currentScreen.left,
112+
top: screensDetailsCached.current.currentScreen.top,
113+
devicePixelRatio: screensDetailsCached.current.currentScreen.devicePixelRatio,
114+
isInternal: screensDetailsCached.current.currentScreen.isInternal,
115+
isPrimary: screensDetailsCached.current.currentScreen.isPrimary,
116+
label: screensDetailsCached.current.currentScreen.label
117+
},
118+
screens: allScreen && screensDetailsCached.current?.screens ? screensDetailsCached.current.screens : undefined
119+
}
120+
}
121+
let areDifferent = false;
122+
if (changes.current) {
123+
changes.current = false;
124+
for (let i = 0, size = keys.length; i < size; i++) {
125+
if (keys[i] === "orientation") {
126+
if (cachedDetails.currentScreen.orientation.angle !== screen.orientation.angle || cachedDetails.currentScreen.orientation.type !== screen.orientation.type) {
127+
areDifferent = true;
128+
}
129+
} else if (keys[i] in screen && cachedDetails.currentScreen[keys[i] as keyof ScreenDetails["currentScreen"]] !== screen[keys[i] as keyof typeof screen]) {
130+
areDifferent = true;
131+
}
132+
if (areDifferent) {
133+
cachedDetails = {
134+
currentScreen: {
135+
availHeight: screen.availWidth,
136+
availWidth: screen.availWidth,
137+
height: screen.height,
138+
width: screen.width,
139+
colorDepth: screen.colorDepth,
140+
pixelDepth: screen.pixelDepth,
141+
orientation: {
142+
angle: screen.orientation.angle,
143+
type: screen.orientation.type,
144+
},
145+
availLeft: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.availLeft : undefined,
146+
availTop: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.availTop : undefined,
147+
left: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.left : undefined,
148+
top: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.top : undefined,
149+
devicePixelRatio: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.devicePixelRatio : undefined,
150+
isInternal: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.isInternal : undefined,
151+
isPrimary: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.isPrimary : undefined,
152+
label: screensDetailsCached.current ? screensDetailsCached.current.currentScreen.label : undefined
153+
},
154+
screens: allScreen && screensDetailsCached.current?.screens ? screensDetailsCached.current.screens : undefined
155+
};
156+
break;
157+
}
158+
}
159+
}
160+
return cachedDetails;
161+
}
162+
}, [allScreen])
163+
);
164+
165+
const lock = useRef((orientation: OrientationLockType) => (screen.orientation as ScreenOrientation & { lock: (orientation: OrientationLockType) => Promise<void> }).lock(orientation));
166+
167+
const unlock = useRef(() => screen.orientation.unlock());
168+
169+
return [
170+
details,
171+
lock.current,
172+
unlock.current
173+
]
174+
}

packages/react-tools/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export type {
1010
useResponsiveBreakpoints,
1111
ConnectionState,
1212
BatteryStatus,
13-
GeoLocationObject
13+
GeoLocationObject,
14+
ScreenDetail,
15+
ScreenDetails,
16+
OrientationLockType
1417
} from './models'
1518

1619
export {
@@ -82,7 +85,8 @@ export {
8285
useEyeDropper,
8386
useDialogBox,
8487
useBeforeUnload,
85-
useDoubleClick
88+
useDoubleClick,
89+
useScreen
8690
} from './hooks'
8791

8892
export {

0 commit comments

Comments
 (0)