From eef742e317e85efde4c2a51f0a5927608132caa9 Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Wed, 17 Apr 2024 21:11:05 +0300 Subject: [PATCH 1/4] feat: focusable grid item --- src/components/GridItem/GridItem.js | 16 ++++++++++++++++ src/components/GridItem/GridItem.scss | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index ec10344..014bbe9 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -37,6 +37,10 @@ class GridItem extends React.PureComponent { static contextType = DashKitContext; + state = { + isFocused: false, + }; + renderOverlay() { const {overlayControls} = this.props; const {editMode} = this.context; @@ -60,6 +64,14 @@ class GridItem extends React.PureComponent { ); } + onFocusHandler = () => { + this.setState({isFocused: true}); + }; + + onBlurHandler = () => { + this.setState({isFocused: false}); + }; + render() { // из-за бага, что Grid Items unmounts при изменении static, isDraggable, isResaizable // https://github.com/STRML/react-grid-layout/issues/721 @@ -92,6 +104,7 @@ class GridItem extends React.PureComponent { className={b( { 'is-dragging': isDragging, + 'is-focused': this.state.isFocused, 'with-custom-handle': withCustomHandle, }, preparedClassName, @@ -100,6 +113,9 @@ class GridItem extends React.PureComponent { style={style} ref={this.props.forwardedRef} {...reactGridLayoutProps} + onFocus={this.onFocusHandler} + onBlur={this.onBlurHandler} + tabIndex={-1} >
Date: Thu, 18 Apr 2024 13:23:36 +0300 Subject: [PATCH 2/4] feat: added global focus observer --- src/components/GridItem/GridItem.js | 39 ++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index 014bbe9..1615d4f 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -11,6 +11,39 @@ import './GridItem.scss'; const b = cn('dashkit-grid-item'); +class WindowFocusObserver { + constructor() { + this.subscribers = 0; + this.isFocused = !document.hidden; + + window.addEventListener('blur', this.blurHandler, true); + window.addEventListener('focus', this.focusHandler, true); + } + + blurHandler = (e) => { + if (e.target === window) { + this.isFocused = false; + } + }; + + focusHandler = (e) => { + if (e.target === window) { + this.isFocused = true; + } + }; + + // Method to get state after all blur\focus events in document are triggered + async getFocuseState() { + return new Promise((resolve) => { + requestAnimationFrame(() => { + resolve(this.isFocused); + }); + }); + } +} + +const windowFocusObserver = new WindowFocusObserver(); + class GridItem extends React.PureComponent { static propTypes = { adjustWidgetLayout: PropTypes.func.isRequired, @@ -69,7 +102,11 @@ class GridItem extends React.PureComponent { }; onBlurHandler = () => { - this.setState({isFocused: false}); + windowFocusObserver.getFocuseState().then((isWindowFocused) => { + if (isWindowFocused) { + this.setState({isFocused: false}); + } + }); }; render() { From 518001fba6dbcda62ab855dd3da7f7069f6d8b97 Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Thu, 18 Apr 2024 21:01:29 +0300 Subject: [PATCH 3/4] feat: add tab abort handler --- src/components/GridItem/GridItem.js | 10 +++++++++- src/components/GridItem/GridItem.scss | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index 1615d4f..8284ddf 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -99,13 +99,21 @@ class GridItem extends React.PureComponent { onFocusHandler = () => { this.setState({isFocused: true}); + + if (this.controller) { + this.controller.abort(); + } }; onBlurHandler = () => { + this.controller = new AbortController(); + windowFocusObserver.getFocuseState().then((isWindowFocused) => { - if (isWindowFocused) { + if (!this.controller.signal.aborted && isWindowFocused) { this.setState({isFocused: false}); } + + this.controller = null; }); }; diff --git a/src/components/GridItem/GridItem.scss b/src/components/GridItem/GridItem.scss index c506519..a8bcb7b 100644 --- a/src/components/GridItem/GridItem.scss +++ b/src/components/GridItem/GridItem.scss @@ -2,6 +2,10 @@ position: relative; $_border-radius: 3px; + &:focus { + outline: none; + } + &__overlay { --_-dashkit-overlay-color: var(--dashkit-overlay-color, var(--g-color-base-generic)); --_-dashkit-overlay-border-color: var(--dashkit-overlay-border-color, rgba(0, 0, 0, 0.1)); From 58d2236e6e1388e116ff95a3dd4d2d71f10f768c Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Fri, 19 Apr 2024 10:40:42 +0300 Subject: [PATCH 4/4] feat: add focusable property --- README.md | 1 + src/components/DashKit/DashKit.tsx | 2 ++ .../DashKit/__stories__/DashKitShowcase.tsx | 1 + src/components/GridItem/GridItem.js | 12 +++++++++--- src/components/GridLayout/GridLayout.js | 12 ++++++++++-- src/hocs/withContext.js | 2 ++ 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 131a1b0..9303fe2 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ interface DashKitProps { - **context**: Object that will be propped up on all widgets. - **overlayControls**: Object that overrides widget controls at the time of editing. If not transmitted, basic controls will be displayed. - **noOverlay**: If `true`, overlay and controls are not displayed while editing. +- **focusable**: If `true`, grid items will be focusable. - **draggableHandleClassName** : СSS class name of the element that makes the widget draggable. ## Usage diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index 40ef055..a86c292 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -34,6 +34,7 @@ interface DashKitDefaultProps { settings: SettingsProps; context: ContextProps; noOverlay: boolean; + focusable?: boolean; } export interface DashKitProps extends DashKitGeneralProps, Partial {} @@ -55,6 +56,7 @@ export class DashKit extends React.PureComponent { }, context: {}, noOverlay: false, + focusable: false, }; static registerPlugins(...plugins: Plugin[]) { diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index 802a5fb..fea6a49 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -208,6 +208,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { settings={this.state.settings} ref={this.dashKitRef} overlayControls={controls} + focusable={!editMode} /> diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index 8284ddf..1585d03 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -61,6 +61,7 @@ class GridItem extends React.PureComponent { className: PropTypes.string, style: PropTypes.object, noOverlay: PropTypes.bool, + focusable: PropTypes.bool, withCustomHandle: PropTypes.bool, onMouseDown: PropTypes.func, onMouseUp: PropTypes.func, @@ -130,6 +131,7 @@ class GridItem extends React.PureComponent { className, isDragging, noOverlay, + focusable, withCustomHandle, } = this.props; const {editMode} = this.context; @@ -158,9 +160,13 @@ class GridItem extends React.PureComponent { style={style} ref={this.props.forwardedRef} {...reactGridLayoutProps} - onFocus={this.onFocusHandler} - onBlur={this.onBlurHandler} - tabIndex={-1} + {...(focusable + ? { + onFocus: this.onFocusHandler, + onBlur: this.onBlurHandler, + tabIndex: -1, + } + : {})} >
diff --git a/src/hocs/withContext.js b/src/hocs/withContext.js index 5602999..78ff6ef 100644 --- a/src/hocs/withContext.js +++ b/src/hocs/withContext.js @@ -184,6 +184,7 @@ function useMemoStateContext(props) { config: props.config, context: props.context, noOverlay: props.noOverlay, + focusable: props.focusable, defaultGlobalParams: props.globalParams, globalParams: props.globalParams, editMode: props.editMode, @@ -207,6 +208,7 @@ function useMemoStateContext(props) { props.config, props.context, props.noOverlay, + props.focusable, props.globalParams, props.editMode, props.settings,