diff --git a/apis/nucleus/src/components/listbox/ListBox.jsx b/apis/nucleus/src/components/listbox/ListBox.jsx
index 76689a93e..341ef9965 100644
--- a/apis/nucleus/src/components/listbox/ListBox.jsx
+++ b/apis/nucleus/src/components/listbox/ListBox.jsx
@@ -43,7 +43,11 @@ export default function ListBox({
const [layout] = useLayout(model);
const [pages, setPages] = useState(null);
const [isLoadingData, setIsLoadingData] = useState(false);
- const { instantPages = [], interactionEvents } = useSelectionsInteractions({
+ const {
+ instantPages = [],
+ interactionEvents,
+ select,
+ } = useSelectionsInteractions({
layout,
selections,
pages,
@@ -186,6 +190,11 @@ export default function ListBox({
checkboxes,
dense,
frequencyMode,
+ actions: {
+ select,
+ confirm: () => selections && selections.confirm.call(selections),
+ cancel: () => selections && selections.cancel.call(selections),
+ },
frequencyMax,
histogram,
}}
diff --git a/apis/nucleus/src/components/listbox/ListBoxRowColumn.jsx b/apis/nucleus/src/components/listbox/ListBoxRowColumn.jsx
index ee7d4736a..3ff799b4c 100644
--- a/apis/nucleus/src/components/listbox/ListBoxRowColumn.jsx
+++ b/apis/nucleus/src/components/listbox/ListBoxRowColumn.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import { FormControlLabel, Grid, Typography } from '@material-ui/core';
@@ -8,6 +8,7 @@ import Lock from '@nebula.js/ui/icons/lock';
import Tick from '@nebula.js/ui/icons/tick';
import ListBoxCheckbox from './ListBoxCheckbox';
import getSegmentsFromRanges from './listbox-highlight';
+import getKeyboardNavigation from './listbox-keyboard-navigation';
const ellipsis = {
width: '100%',
@@ -35,6 +36,9 @@ const useStyles = makeStyles((theme) => ({
'&:focus': {
boxShadow: `inset 0 0 0 2px ${theme.palette.custom.focusBorder} !important`,
},
+ '&:focus-visible': {
+ outline: 'none',
+ },
},
// The interior wrapper for all field content.
@@ -153,10 +157,13 @@ export default function RowColumn({ index, style, data, column = false }) {
checkboxes = false,
dense = false,
frequencyMode = 'N',
+ actions,
frequencyMax = '',
histogram = false,
} = data;
+ const handleKeyDownCallback = useCallback(getKeyboardNavigation(actions), [actions]);
+
const [isSelected, setSelected] = useState(false);
const [cell, setCell] = useState();
@@ -295,11 +302,15 @@ export default function RowColumn({ index, style, data, column = false }) {
container
spacing={0}
className={joinClassNames(['value', ...classArr])}
+ classes={{
+ root: classes.fieldRoot,
+ }}
style={style}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseEnter={onMouseEnter}
+ onKeyDown={handleKeyDownCallback}
role={column ? 'column' : 'row'}
tabIndex={0}
data-n={cell && cell.qElemNumber}
diff --git a/apis/nucleus/src/components/listbox/__tests__/list-box-keyboard-navigation.spec.js b/apis/nucleus/src/components/listbox/__tests__/list-box-keyboard-navigation.spec.js
new file mode 100644
index 000000000..351d76411
--- /dev/null
+++ b/apis/nucleus/src/components/listbox/__tests__/list-box-keyboard-navigation.spec.js
@@ -0,0 +1,142 @@
+import getKeyboardNavigation from '../listbox-keyboard-navigation';
+
+describe('keyboard navigation', () => {
+ let actions;
+ let sandbox;
+ let handleKeyDown;
+
+ before(() => {
+ global.document = {};
+ sandbox = sinon.createSandbox();
+ actions = {
+ select: sandbox.stub(),
+ cancel: sandbox.stub(),
+ confirm: sandbox.stub(),
+ };
+ });
+
+ afterEach(() => {
+ sandbox.reset();
+ });
+
+ after(() => {
+ sandbox.restore();
+ });
+
+ beforeEach(() => {
+ handleKeyDown = getKeyboardNavigation(actions);
+ });
+
+ it('select values with Space', () => {
+ expect(actions.select).not.called;
+ expect(actions.confirm).not.called;
+ expect(actions.cancel).not.called;
+
+ // Space should select values
+ const event = {
+ nativeEvent: { keyCode: 32 },
+ currentTarget: { getAttribute: sandbox.stub().withArgs('data-n').returns(1) },
+ preventDefault: sandbox.stub(),
+ stopPropagation: sandbox.stub(),
+ };
+ handleKeyDown(event);
+ expect(actions.select).calledOnce.calledWithExactly([1]);
+ expect(event.preventDefault).calledOnce;
+ expect(event.stopPropagation).not.called;
+ });
+
+ it('confirm selections with Enter', () => {
+ const eventConfirm = {
+ nativeEvent: { keyCode: 13 },
+ preventDefault: sandbox.stub(),
+ };
+ handleKeyDown(eventConfirm);
+ expect(actions.confirm).calledOnce;
+ });
+
+ it('cancel selections with Escape', () => {
+ const eventCancel = {
+ nativeEvent: { keyCode: 27 },
+ preventDefault: sandbox.stub(),
+ };
+ const blur = sandbox.stub();
+ global.document.activeElement = { blur };
+ handleKeyDown(eventCancel);
+ expect(actions.cancel).calledOnce;
+ expect(blur).calledOnce;
+ });
+
+ it('arrow up should move focus upwards', () => {
+ const focus = sandbox.stub();
+ const eventArrowUp = {
+ nativeEvent: { keyCode: 38 },
+ currentTarget: {
+ parentElement: {
+ previousElementSibling: {
+ querySelector: () => ({ focus }),
+ },
+ },
+ },
+ preventDefault: sandbox.stub(),
+ };
+ handleKeyDown(eventArrowUp);
+ expect(focus).calledOnce;
+ });
+
+ it('arrow down should move focus downwards', () => {
+ const focus = sandbox.stub();
+ const eventArrowDown = {
+ nativeEvent: { keyCode: 40 },
+ currentTarget: {
+ parentElement: {
+ nextElementSibling: {
+ querySelector: () => ({ focus }),
+ },
+ },
+ },
+ preventDefault: sandbox.stub(),
+ };
+ handleKeyDown(eventArrowDown);
+ expect(focus).calledOnce;
+ });
+
+ it('arrow down with Shift should range select values downwards (current and next element)', () => {
+ const focus = sandbox.stub();
+ expect(actions.select).not.called;
+ const eventArrowDown = {
+ nativeEvent: { keyCode: 40, shiftKey: true },
+ currentTarget: {
+ parentElement: {
+ nextElementSibling: {
+ querySelector: () => ({ focus, getAttribute: sandbox.stub().withArgs('data-n').returns(2) }),
+ },
+ },
+ getAttribute: sandbox.stub().withArgs('data-n').returns(1),
+ },
+ preventDefault: sandbox.stub(),
+ };
+ handleKeyDown(eventArrowDown);
+ expect(focus).calledOnce;
+ expect(actions.select).calledOnce.calledWithExactly([2], true);
+ });
+
+ it('arrow up with Shift should range select values upwards (current and previous element)', () => {
+ const focus = sandbox.stub();
+ expect(actions.select).not.called;
+ const eventArrowUp = {
+ nativeEvent: { keyCode: 38, shiftKey: true },
+ currentTarget: {
+ getAttribute: sandbox.stub().withArgs('data-n').returns(2),
+ parentElement: {
+ previousElementSibling: {
+ querySelector: () => ({ focus, getAttribute: sandbox.stub().withArgs('data-n').returns(1) }),
+ },
+ },
+ },
+ preventDefault: sandbox.stub(),
+ };
+ handleKeyDown(eventArrowUp);
+ expect(focus).calledOnce;
+ expect(actions.select).calledOnce.calledWithExactly([1], true);
+ });
+});
diff --git a/apis/nucleus/src/components/listbox/__tests__/list-box-row-column.spec.jsx b/apis/nucleus/src/components/listbox/__tests__/list-box-row-column.spec.jsx
index c6fde83dd..75f6b147a 100644
--- a/apis/nucleus/src/components/listbox/__tests__/list-box-row-column.spec.jsx
+++ b/apis/nucleus/src/components/listbox/__tests__/list-box-row-column.spec.jsx
@@ -3,6 +3,7 @@ import renderer from 'react-test-renderer';
import { Grid, Typography } from '@material-ui/core';
import Lock from '@nebula.js/ui/icons/lock';
import ListBoxCheckbox from '../ListBoxCheckbox';
+import * as getKeyboardNavigation from '../listbox-keyboard-navigation';
const [{ default: ListBoxRowColumn }] = aw.mock(
[
@@ -31,6 +32,25 @@ async function render(content) {
}
describe('', () => {
+ let sandbox;
+ let actions;
+ let handleKeyDown;
+
+ before(() => {
+ global.document = {};
+ sandbox = sinon.createSandbox();
+ handleKeyDown = sandbox.stub(getKeyboardNavigation, 'default').returns(() => 'handle-key-down-callback');
+ actions = 'actions';
+ });
+
+ afterEach(() => {
+ sandbox.reset();
+ });
+
+ after(() => {
+ sandbox.restore();
+ });
+
describe('as row', () => {
const rowCol = 'row';
@@ -38,12 +58,14 @@ describe('', () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
pages: [],
+ actions,
};
+ expect(handleKeyDown).not.called;
const testRenderer = await render(
);
@@ -54,6 +76,7 @@ describe('', () => {
expect(type.props.spacing).to.equal(0);
expect(type.props.style).to.deep.equal({});
expect(type.props.role).to.equal(rowCol);
+ expect(type.props.onKeyDown).to.be.a('function');
expect(type.props.onMouseDown.callCount).to.equal(0);
expect(type.props.onMouseUp.callCount).to.equal(0);
expect(type.props.onMouseEnter.callCount).to.equal(0);
@@ -67,16 +90,20 @@ describe('', () => {
const cbs = testInstance.findAllByType(ListBoxCheckbox);
expect(cbs).to.have.length(0);
await testRenderer.unmount();
+
+ expect(handleKeyDown).calledOnce.calledWith('actions');
});
+
it('should have css class `value`', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
pages: [],
+ actions,
};
const testRenderer = await render(
@@ -89,16 +116,18 @@ describe('', () => {
expect(className.split(' ')).to.include('value');
await testRenderer.unmount();
});
+
it('should render with checkboxes', async () => {
const index = 0;
const style = {};
const data = {
checkboxes: true,
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
pages: [],
+ actions,
};
const testRenderer = await render(
@@ -130,10 +159,11 @@ describe('', () => {
const style = {};
const data = {
isLocked: true,
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -163,14 +193,16 @@ describe('', () => {
expect(type.props.size).to.equal('small');
await testRenderer.unmount();
});
+
it('should set selected', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -197,14 +229,16 @@ describe('', () => {
expect(type.props.className).to.include('selected');
await testRenderer.unmount();
});
+
it('should set alternative', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -231,14 +265,16 @@ describe('', () => {
expect(type.props.className).to.include('alternative');
await testRenderer.unmount();
});
+
it('should set excluded - qState X', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -265,14 +301,16 @@ describe('', () => {
expect(type.props.className).to.include('excluded');
await testRenderer.unmount();
});
+
it('should set excluded - qState XS', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -299,14 +337,16 @@ describe('', () => {
expect(type.props.className).to.include('excluded');
await testRenderer.unmount();
});
+
it('should set excluded - qState XL', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -333,14 +373,16 @@ describe('', () => {
expect(type.props.className).to.include('excluded');
await testRenderer.unmount();
});
+
it('should highlight ranges', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -373,14 +415,16 @@ describe('', () => {
expect(types[1].props.children.props.children).to.equal(' ftw');
await testRenderer.unmount();
});
+
it('should highlight ranges', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -415,14 +459,16 @@ describe('', () => {
expect(hits).to.have.length(2);
await testRenderer.unmount();
});
+
it('should highlight ranges', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
pages: [
{
qArea: {
@@ -456,14 +502,16 @@ describe('', () => {
expect(types[2].props.children.props.children).to.equal(' buddy');
await testRenderer.unmount();
});
+
it('should show frequency when enabled', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
+ actions,
frequencyMode: 'value',
pages: [
{
@@ -497,6 +545,7 @@ describe('', () => {
const style = {};
const data = {
checkboxes: true,
+ actions,
pages: [
{
qArea: {
@@ -541,11 +590,12 @@ describe('', () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
pages: [],
+ actions,
};
const testRenderer = await render(
@@ -571,15 +621,17 @@ describe('', () => {
expect(cbs).to.have.length(0);
await testRenderer.unmount();
});
+
it('should have css class `value`', async () => {
const index = 0;
const style = {};
const data = {
- onMouseDown: sinon.spy(),
- onMouseUp: sinon.spy(),
- onMouseEnter: sinon.spy(),
- onClick: sinon.spy(),
+ onMouseDown: sandbox.spy(),
+ onMouseUp: sandbox.spy(),
+ onMouseEnter: sandbox.spy(),
+ onClick: sandbox.spy(),
pages: [],
+ actions,
};
const testRenderer = await render(
diff --git a/apis/nucleus/src/components/listbox/__tests__/use-selections-interactions.spec.jsx b/apis/nucleus/src/components/listbox/__tests__/use-selections-interactions.spec.jsx
index 1b4e9fe1c..901a6ab4b 100644
--- a/apis/nucleus/src/components/listbox/__tests__/use-selections-interactions.spec.jsx
+++ b/apis/nucleus/src/components/listbox/__tests__/use-selections-interactions.spec.jsx
@@ -88,26 +88,39 @@ describe('use-listbox-interactions', () => {
it('Without range', async () => {
await render();
const arg0 = ref.current.result;
- expect(Object.keys(arg0).sort()).to.deep.equal(['instantPages', 'interactionEvents']);
+ expect(Object.keys(arg0).sort()).to.deep.equal(['instantPages', 'interactionEvents', 'select']);
expect(arg0.instantPages).to.deep.equal([]);
expect(Object.keys(arg0.interactionEvents).sort()).to.deep.equal(['onMouseDown', 'onMouseUp']);
});
it('With range', async () => {
await render({ rangeSelect: true });
const arg0 = ref.current.result;
- expect(Object.keys(arg0).sort()).to.deep.equal(['instantPages', 'interactionEvents']);
+ expect(Object.keys(arg0).sort()).to.deep.equal(['instantPages', 'interactionEvents', 'select']);
expect(arg0.instantPages).to.deep.equal([]);
expect(Object.keys(arg0.interactionEvents).sort()).to.deep.equal(['onMouseDown', 'onMouseEnter', 'onMouseUp']);
});
it('With checkboxes', async () => {
await render({ checkboxes: true });
const arg0 = ref.current.result;
- expect(Object.keys(arg0).sort()).to.deep.equal(['instantPages', 'interactionEvents']);
+ expect(Object.keys(arg0).sort()).to.deep.equal(['instantPages', 'interactionEvents', 'select']);
expect(arg0.instantPages).to.deep.equal([]);
expect(Object.keys(arg0.interactionEvents).sort()).to.deep.equal(['onClick']);
});
});
+ it('Should manually pre-select and select values when calling the manual select method', async () => {
+ await render();
+ const args = ref.current.result;
+ expect(listboxSelections.selectValues).not.called;
+ args.select([1]);
+ expect(listboxSelections.selectValues).calledOnce;
+ expect(listboxSelections.selectValues.args[0][0]).to.deep.equal({
+ elemNumbers: [1],
+ isSingleSelect: false,
+ selections: { key: 'selections' },
+ });
+ });
+
it('should select a value', async () => {
await render();
const arg0 = ref.current.result;
@@ -184,7 +197,7 @@ describe('use-listbox-interactions', () => {
it('should return expected stuff', async () => {
await render({ rangeSelect: true });
const arg0 = ref.current.result;
- expect(Object.keys(arg0)).to.deep.equal(['instantPages', 'interactionEvents']);
+ expect(Object.keys(arg0)).to.deep.equal(['instantPages', 'interactionEvents', 'select']);
expect(arg0.instantPages).to.deep.equal([]);
expect(Object.keys(arg0.interactionEvents).sort()).to.deep.equal(['onMouseDown', 'onMouseEnter', 'onMouseUp']);
});
diff --git a/apis/nucleus/src/components/listbox/listbox-keyboard-navigation.js b/apis/nucleus/src/components/listbox/listbox-keyboard-navigation.js
new file mode 100644
index 000000000..72ee72934
--- /dev/null
+++ b/apis/nucleus/src/components/listbox/listbox-keyboard-navigation.js
@@ -0,0 +1,65 @@
+import KEYS from '../../keys';
+
+const getElement = (elm, next = false) => {
+ const parentElm = elm && elm.parentElement[next ? 'nextElementSibling' : 'previousElementSibling'];
+ return parentElm && parentElm.querySelector('[role]');
+};
+
+export default function getKeyboardNavigation({ select, confirm, cancel }) {
+ let startedRange = false;
+ const setStartedRange = (val) => {
+ startedRange = val;
+ };
+
+ const handleKeyDown = (event) => {
+ let elementToFocus;
+ const { keyCode, shiftKey = false } = event.nativeEvent;
+
+ switch (keyCode) {
+ case KEYS.SHIFT:
+ // This is to ensure we include the first value when starting a range selection.
+ setStartedRange(true);
+ break;
+ case KEYS.SPACE:
+ select([+event.currentTarget.getAttribute('data-n')]);
+ break;
+ case KEYS.ARROW_DOWN:
+ case KEYS.ARROW_RIGHT:
+ elementToFocus = getElement(event.currentTarget, true);
+ if (shiftKey && elementToFocus) {
+ if (startedRange) {
+ select([+event.currentTarget.getAttribute('data-n')], true);
+ setStartedRange(false);
+ }
+ select([+elementToFocus.getAttribute('data-n')], true);
+ }
+ break;
+ case KEYS.ARROW_UP:
+ case KEYS.ARROW_LEFT:
+ elementToFocus = getElement(event.currentTarget, false);
+ if (shiftKey && elementToFocus) {
+ if (startedRange) {
+ select([+event.currentTarget.getAttribute('data-n')], true);
+ setStartedRange(false);
+ }
+ select([+elementToFocus.getAttribute('data-n')], true);
+ }
+ break;
+ case KEYS.ENTER:
+ confirm();
+ break;
+ case KEYS.ESCAPE:
+ cancel();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ break;
+ default:
+ }
+ if (elementToFocus) {
+ elementToFocus.focus();
+ }
+ event.preventDefault();
+ };
+ return handleKeyDown;
+}
diff --git a/apis/nucleus/src/components/listbox/useSelectionsInteractions.js b/apis/nucleus/src/components/listbox/useSelectionsInteractions.js
index 335b0addf..4157bd22b 100644
--- a/apis/nucleus/src/components/listbox/useSelectionsInteractions.js
+++ b/apis/nucleus/src/components/listbox/useSelectionsInteractions.js
@@ -53,6 +53,14 @@ export default function useSelectionsInteractions({
});
};
+ const selectManually = (elementIds = [], additive = false) => {
+ setMouseDown(true);
+ preSelect(elementIds, additive || isRangeSelection);
+ const p = select(elementIds, additive || isRangeSelection);
+ setMouseDown(false);
+ return p;
+ };
+
const onClick = useCallback(
(event) => {
if (selectingValues || selectDisabled()) {
@@ -169,5 +177,6 @@ export default function useSelectionsInteractions({
return {
instantPages,
interactionEvents,
+ select: selectManually, // preselect and select without having to trigger an event
};
}
diff --git a/apis/nucleus/src/keys.js b/apis/nucleus/src/keys.js
new file mode 100644
index 000000000..c951c76a0
--- /dev/null
+++ b/apis/nucleus/src/keys.js
@@ -0,0 +1,34 @@
+const KEYS = Object.freeze({
+ ENTER: 13,
+ ESCAPE: 27,
+ SPACE: 32,
+ TAB: 9,
+ BACKSPACE: 8,
+ DELETE: 46,
+ ALT: 18,
+ CTRL: 17,
+ SHIFT: 16,
+ ARROW_UP: 38,
+ ARROW_DOWN: 40,
+ ARROW_LEFT: 37,
+ ARROW_RIGHT: 39,
+ PAGE_DOWN: 34,
+ PAGE_UP: 33,
+ HOME: 36,
+ END: 35,
+ F10: 121,
+ A: 65,
+ F: 70,
+ ZERO: 48,
+ NINE: 57,
+ NUMPAD_ZERO: 96,
+ NUMPAD_NINE: 105,
+ SUBTRACTION: 189,
+ DECIMAL: 190,
+ NUMPAD_DECIMAL: 110,
+
+ isArrow: (key) =>
+ key === KEYS.ARROW_UP || key === KEYS.ARROW_DOWN || key === KEYS.ARROW_LEFT || key === KEYS.ARROW_RIGHT,
+});
+
+export default KEYS;