Skip to content

Commit

Permalink
feat: focusable grid item (#142)
Browse files Browse the repository at this point in the history
* feat: focusable grid item

* feat: added global focus observer

* feat: add tab abort handler

* feat: add focusable property
  • Loading branch information
flops authored Apr 19, 2024
1 parent 2075f6b commit aa02ea1
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/components/DashKit/DashKit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface DashKitDefaultProps {
settings: SettingsProps;
context: ContextProps;
noOverlay: boolean;
focusable?: boolean;
}

export interface DashKitProps extends DashKitGeneralProps, Partial<DashKitDefaultProps> {}
Expand All @@ -55,6 +56,7 @@ export class DashKit extends React.PureComponent<DashKitInnerProps> {
},
context: {},
noOverlay: false,
focusable: false,
};

static registerPlugins(...plugins: Plugin[]) {
Expand Down
1 change: 1 addition & 0 deletions src/components/DashKit/__stories__/DashKitShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> {
settings={this.state.settings}
ref={this.dashKitRef}
overlayControls={controls}
focusable={!editMode}
/>
</DemoRow>
</Demo>
Expand Down
67 changes: 67 additions & 0 deletions src/components/GridItem/GridItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -28,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,
Expand All @@ -37,6 +71,10 @@ class GridItem extends React.PureComponent {

static contextType = DashKitContext;

state = {
isFocused: false,
};

renderOverlay() {
const {overlayControls} = this.props;
const {editMode} = this.context;
Expand All @@ -60,6 +98,26 @@ 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 (!this.controller.signal.aborted && isWindowFocused) {
this.setState({isFocused: false});
}

this.controller = null;
});
};

render() {
// из-за бага, что Grid Items unmounts при изменении static, isDraggable, isResaizable
// https://github.com/STRML/react-grid-layout/issues/721
Expand All @@ -73,6 +131,7 @@ class GridItem extends React.PureComponent {
className,
isDragging,
noOverlay,
focusable,
withCustomHandle,
} = this.props;
const {editMode} = this.context;
Expand All @@ -92,6 +151,7 @@ class GridItem extends React.PureComponent {
className={b(
{
'is-dragging': isDragging,
'is-focused': this.state.isFocused,
'with-custom-handle': withCustomHandle,
},
preparedClassName,
Expand All @@ -100,6 +160,13 @@ class GridItem extends React.PureComponent {
style={style}
ref={this.props.forwardedRef}
{...reactGridLayoutProps}
{...(focusable
? {
onFocus: this.onFocusHandler,
onBlur: this.onBlurHandler,
tabIndex: -1,
}
: {})}
>
<div className={b('item', {editMode: editMode && !_editActive && !noOverlay})}>
<Item
Expand Down
8 changes: 8 additions & 0 deletions src/components/GridItem/GridItem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -62,6 +66,10 @@
will-change: transform;
}

.react-grid-item.dashkit-grid-item_is-focused {
z-index: 2;
}

.react-grid-item.react-grid-placeholder {
--_-dashkit-placeholder-color: var(--dashkit-placeholder-color, #fc0);
--_-dashkit-placeholder-opacity: var(--dashkit-placeholder-opacity, 0.2);
Expand Down
12 changes: 10 additions & 2 deletions src/components/GridLayout/GridLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,15 @@ export default class GridLayout extends React.PureComponent {
};

render() {
const {layout, config, registerManager, editMode, noOverlay, draggableHandleClassName} =
this.context;
const {
layout,
config,
registerManager,
editMode,
noOverlay,
focusable,
draggableHandleClassName,
} = this.context;
this.pluginsRefs.length = config.items.length;

return (
Expand Down Expand Up @@ -140,6 +147,7 @@ export default class GridLayout extends React.PureComponent {
adjustWidgetLayout={this.adjustWidgetLayout}
isDragging={this.state.isDragging}
noOverlay={noOverlay}
focusable={focusable}
withCustomHandle={Boolean(draggableHandleClassName)}
overlayControls={this.props.overlayControls}
/>
Expand Down
2 changes: 2 additions & 0 deletions src/hocs/withContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -207,6 +208,7 @@ function useMemoStateContext(props) {
props.config,
props.context,
props.noOverlay,
props.focusable,
props.globalParams,
props.editMode,
props.settings,
Expand Down

0 comments on commit aa02ea1

Please sign in to comment.