Compare commits

..

4 Commits

Author SHA1 Message Date
semantic-release-bot
b5dc08a977 chore(release): 3.3.2 [CI SKIP] 2022-10-02 02:31:09 +00:00
AAGaming
40871af853 fix(modal): allow children 2022-10-01 22:30:33 -04:00
Lukas Senionis
c910dbde79 Add dialog components (#22) 2022-10-01 21:41:47 -04:00
Lukas Senionis
4cbb30c21c Add custom-hook: useQuickAccessVisible (#28) 2022-10-01 21:38:46 -04:00
10 changed files with 149 additions and 22 deletions

View File

@@ -1,3 +1,10 @@
## [3.3.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.1...v3.3.2) (2022-10-02)
### Bug Fixes
* **modal:** allow children ([40871af](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/40871af8539858f435c83123a56d4b31b63d627d))
## [3.3.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.0...v3.3.1) (2022-10-02)

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "decky-frontend-lib",
"version": "3.3.1",
"version": "3.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "decky-frontend-lib",
"version": "3.3.1",
"version": "3.3.2",
"license": "GPL-2.0-or-later",
"dependencies": {
"minimist": "^1.2.6"

View File

@@ -1,6 +1,6 @@
{
"name": "decky-frontend-lib",
"version": "3.3.1",
"version": "3.3.2",
"description": "A library for building decky plugins",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -0,0 +1 @@
export * from './usequickaccessvisible';

View File

@@ -0,0 +1,67 @@
import { useState, useEffect } from 'react';
declare global {
var FocusNavController: any;
}
/**
* Returns state indicating the visibility of quick access menu.
*
* @remarks
* During development it is possible to open the quick access menu without giving it
* focus in some cases. In such cases, the quick access menu state is invisible.
*
* This seems to be impossible to replicate when running the deck normally. Even in
* the edge cases it always seems to have a focus.
*
* @returns `true` if quick access menu is visible (focused) and `false` otherwise.
*
* @example
* import { VFC, useEffect } from "react";
* import { useQuickAccessVisible } from "decky-frontend-lib";
*
* export const PluginPanelView: VFC<{}> = ({ }) => {
* const isVisible = useQuickAccessVisible();
*
* useEffect(() => {
* if (!isVisible) {
* return;
* }
*
* const interval = setInterval(() => console.log("Hello world!"), 1000);
* return () => {
* clearInterval(interval);
* }
* }, [isVisible])
*
* return (
* <div>
* {isVisible ? "VISIBLE" : "INVISIBLE"}
* </div>
* );
* };
*/
export function useQuickAccessVisible(): boolean {
// Assuming that the component is rendered in QAM already, so true by default...
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const quickAccessWindow: Window | null = FocusNavController?.GetGamepadNavTreeByID("QuickAccess-NA")?.m_Root?.m_element?.ownerDocument.defaultView ?? null;
if (quickAccessWindow === null) {
console.error("Could not get window of QuickAccess menu!");
return;
}
const onBlur = () => setIsVisible(false);
const onFocus = () => setIsVisible(true);
quickAccessWindow.addEventListener("blur", onBlur);
quickAccessWindow.addEventListener("focus", onFocus);
return () => {
quickAccessWindow.removeEventListener("blur", onBlur);
quickAccessWindow.removeEventListener("focus", onFocus);
};
}, []);
return isVisible;
}

View File

@@ -1,22 +1,8 @@
import { CSSProperties, FC, RefAttributes } from 'react';
import { FC } from 'react';
import { DialogButton, DialogButtonProps } from "./Dialog";
import { CommonUIModule } from '../webpack';
import { FooterLegendProps } from './FooterLegend';
export interface DialogButtonProps extends RefAttributes<HTMLDivElement>, FooterLegendProps {
label?: string;
style?: CSSProperties;
className?: string;
noFocusRing?: boolean;
description?: string;
layout?: 'below';
onClick?(e: MouseEvent): void;
disabled?: boolean;
bottomSeparator?: boolean;
export interface ButtonProps extends DialogButtonProps {
}
export const DialogButton = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
mod?.render?.toString()?.includes('DialogButton'),
) as FC<DialogButtonProps>;
// Button isn't exported, so call DialogButton to grab it
export const Button = (DialogButton as any)?.render({}).type as FC<ButtonProps>;

View File

@@ -0,0 +1,63 @@
import { CommonUIModule } from "../webpack";
import { CSSProperties, FC, RefAttributes } from "react";
import { FooterLegendProps } from './FooterLegend';
export interface DialogCommonProps extends RefAttributes<HTMLDivElement> {
style?: CSSProperties;
className?: string;
}
export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps {
noFocusRing?: boolean;
disabled?: boolean;
onClick?(e: MouseEvent): void;
onPointerDown?(e: PointerEvent): void;
onPointerUp?(e: PointerEvent): void;
onPointerCancel?(e: PointerEvent): void;
onMouseDown?(e: MouseEvent): void;
onMouseUp?(e: MouseEvent): void;
onTouchStart?(e: TouchEvent): void;
onTouchEnd?(e: TouchEvent): void;
onTouchCancel?(e: TouchEvent): void;
onSubmit?(e: SubmitEvent): void;
}
const CommonDialogDivs = Object.values(CommonUIModule).filter((m: any) => typeof m === "object" && m?.render?.toString().includes('"div",Object.assign({},'));
const MappedDialogDivs = new Map(Object.values(CommonDialogDivs).map((m: any) => {
const renderedDiv = m.render({});
// Take only the first class name segment as it identifies the element we want
return [renderedDiv.props.className.split(" ")[0], m]
}));
export const DialogHeader = MappedDialogDivs.get("DialogHeader") as FC<DialogCommonProps>;
export const DialogSubHeader = MappedDialogDivs.get("DialogSubHeader") as FC<DialogCommonProps>;
export const DialogFooter = MappedDialogDivs.get("DialogFooter") as FC<DialogCommonProps>;
export const DialogLabel = MappedDialogDivs.get("DialogLabel") as FC<DialogCommonProps>;
export const DialogBodyText = MappedDialogDivs.get("DialogBodyText") as FC<DialogCommonProps>;
export const DialogBody = MappedDialogDivs.get("DialogBody") as FC<DialogCommonProps>;
export const DialogControlsSection = MappedDialogDivs.get("DialogControlsSection") as FC<DialogCommonProps>;
export const DialogControlsSectionHeader = MappedDialogDivs.get("DialogControlsSectionHeader") as FC<DialogCommonProps>;
export const DialogButtonPrimary = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('DialogButton') &&
mod?.render?.toString()?.includes('Primary')
) as FC<DialogButtonProps>;
export const DialogButtonSecondary = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
mod?.render?.toString()?.includes('DialogButton') &&
mod?.render?.toString()?.includes('Secondary')
) as FC<DialogButtonProps>;
export const DialogButtonSmall = Object.values(CommonUIModule).find(
(mod: any) =>
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
mod?.render?.toString()?.includes('DialogButton') &&
mod?.render?.toString()?.includes('Small')
) as FC<DialogButtonProps>;
// This is the "main" button. The Primary can act as a submit button,
// therefore secondary is chosen (also for backwards comp. reasons)
export const DialogButton = DialogButtonSecondary;

View File

@@ -37,6 +37,7 @@ export const showModal: (modal: ReactNode, parent?: EventTarget, props?: ShowMod
});
export interface ModalRootProps {
children: ReactNode;
onCancel?(): void;
closeModal?(): void;
onOK?(): void;

View File

@@ -1,6 +1,7 @@
export * from './Button';
export * from './ButtonItem';
export * from './Carousel';
export * from './Dialog';
export * from './Dropdown';
export * from './Field';
export * from './Focusable';

View File

@@ -1,5 +1,6 @@
// export * from './deck-libs';
export * from './custom-components';
export * from './custom-hooks';
export * from './deck-components';
export * from './plugin';
export * from './webpack';