Skip to content

Commit

Permalink
UIQM-399 Linked MARC bib field - editable textbox does not support sh…
Browse files Browse the repository at this point in the history
…ortcut keys (#475)

* UIQM-399 Linked MARC bib field - editable textbox does not support shortcut keys

* UIQM-399 Fix tests
  • Loading branch information
BogdanDenis committed Mar 14, 2023
1 parent 3536324 commit 908fb7b
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 147 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [7.0.0](IN PROGRESS)

* [UIQM-395](https://issues.folio.org/browse/UIQM-395) Adding multiple "$a" in "010" field of linked "MARC Authority" leads to error.
* [UIQM-399](https://issues.folio.org/browse/UIQM-399) Linked MARC bib field - editable textbox does not support shortcut keys

## [6.0.0](https://github.com/folio-org/ui-quick-marc/tree/v6.0.0) (2023-02-23)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, {
useRef,
useLayoutEffect,
useCallback,
} from 'react';
import PropTypes from 'prop-types';

Expand All @@ -10,12 +9,10 @@ import {
HasCommand,
} from '@folio/stripes/components';

import { useSubfieldNavigation } from '../../../hooks';
import {
getResizeStyles,
cursorToNextSubfield,
cursorToPrevSubfield,
} from './utils';
import { KEYBOARD_COMMAND_NAMES } from '../../../common/constants';

export const ContentField = ({
input,
Expand All @@ -24,19 +21,10 @@ export const ContentField = ({
}) => {
const ref = useRef();

const processSubfieldFocus = useCallback(({ target }) => {
const end = target.value.length;

target.setSelectionRange(end, end);
}, []);

const keyCommands = [{
name: KEYBOARD_COMMAND_NAMES.NEXT_SUBFIELD,
handler: cursorToNextSubfield,
}, {
name: KEYBOARD_COMMAND_NAMES.PREV_SUBFIELD,
handler: cursorToPrevSubfield,
}];
const {
keyCommands,
processSubfieldFocus,
} = useSubfieldNavigation();

useLayoutEffect(() => {
if (ref.current) {
Expand Down
45 changes: 0 additions & 45 deletions src/QuickMarcEditor/QuickMarcEditorRows/ContentField/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,3 @@ export const getResizeStyles = (el) => {

return {};
};

const indexOfRegex = (string, regex) => {
const match = string.match(regex);

return match ? string.indexOf(match[0]) : -1;
};

const lastIndexOfRegex = (string, regex) => {
const match = string.match(regex);

return match ? string.lastIndexOf(match[match.length - 1]) : -1;
};

export const cursorToNextSubfield = (e) => {
e.preventDefault();
const cursorPosition = e.target.selectionStart;
const valueAfterCursor = e.target.value.substring(cursorPosition);

const nextSubfieldIndex = indexOfRegex(valueAfterCursor, /\$\w\s/g);

if (nextSubfieldIndex === -1) {
return;
}

const newPosition = nextSubfieldIndex + cursorPosition + 3;

e.target.setSelectionRange(newPosition, newPosition);
};

export const cursorToPrevSubfield = (e) => {
e.preventDefault();
const cursorPosition = e.target.selectionStart;
const startOfCurrentSubfieldPosition = lastIndexOfRegex(e.target.value.substring(0, cursorPosition), /\$\w\s/g);
const valueBeforeCurrentSubfield = e.target.value.substring(0, startOfCurrentSubfieldPosition);

const prevSubfieldIndex = lastIndexOfRegex(valueBeforeCurrentSubfield, /\$\w\s/g);

if (prevSubfieldIndex === -1) {
return;
}

const newPosition = prevSubfieldIndex + 3;

e.target.setSelectionRange(newPosition, newPosition);
};
72 changes: 0 additions & 72 deletions src/QuickMarcEditor/QuickMarcEditorRows/ContentField/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,76 +26,4 @@ describe('ContentField utils', () => {
expect(styles.height).toBe('21.2px');
});
});

describe('cursorToNextSubfield', () => {
let e;

beforeEach(() => {
e = {
preventDefault: jest.fn(),
target: {
value: '',
setSelectionRange: jest.fn(),
},
};
});

describe('when there is a next subfield', () => {
it('should move cursor to the beginning of next subfield', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 13; // cursor at `$a some value|`

utils.cursorToNextSubfield(e);

expect(e.target.setSelectionRange).toHaveBeenCalledWith(17, 17); // cursor at `$b |some other value`
});
});

describe('when there is no next subfield', () => {
it('should keep cursor where it is', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 21; // cursor at `$b some| other value`

utils.cursorToNextSubfield(e);

expect(e.target.setSelectionRange).not.toHaveBeenCalled();
});
});
});

describe('cursorToPrevSubfield', () => {
let e;

beforeEach(() => {
e = {
preventDefault: jest.fn(),
target: {
value: '',
setSelectionRange: jest.fn(),
},
};
});

describe('when there is a prev subfield', () => {
it('should move cursor to the beginning of prev subfield', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 21; // cursor at `$b some| other value`

utils.cursorToPrevSubfield(e);

expect(e.target.setSelectionRange).toHaveBeenCalledWith(3, 3); // cursor at `$a |some value`
});
});

describe('when there is no prev subfield', () => {
it('should keep cursor where it is', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 13; // cursor at `$a some value|`

utils.cursorToPrevSubfield(e);

expect(e.target.setSelectionRange).not.toHaveBeenCalled();
});
});
});
});
39 changes: 26 additions & 13 deletions src/QuickMarcEditor/QuickMarcEditorRows/SplitField/SplitField.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';

import { TextArea } from '@folio/stripes/components';
import {
TextArea,
HasCommand,
} from '@folio/stripes/components';

import { useSubfieldNavigation } from '../../../hooks';

import css from './SplitField.css';

Expand All @@ -19,20 +24,28 @@ const SplitField = ({
}) => {
const intl = useIntl();

const {
keyCommands,
processSubfieldFocus,
} = useSubfieldNavigation();

const renderSubfieldGroup = (fieldProps) => {
return (
<Field
className={css.splitFieldWrapper}
component={TextArea}
rootClass={css.splitFieldRoot}
hasClearIcon={false}
dirty={false}
aria-label={intl.formatMessage({ id: 'ui-quick-marc.record.subfield' })}
marginBottom0
parse={v => v}
style={{ maxWidth: maxWidth - 15 }} // 15px for margin-right
{...fieldProps}
/>
<HasCommand commands={keyCommands}>
<Field
className={css.splitFieldWrapper}
component={TextArea}
rootClass={css.splitFieldRoot}
hasClearIcon={false}
dirty={false}
aria-label={intl.formatMessage({ id: 'ui-quick-marc.record.subfield' })}
marginBottom0
parse={v => v}
style={{ maxWidth: maxWidth - 15 }} // 15px for margin-right
onFocus={processSubfieldFocus}
{...fieldProps}
/>
</HasCommand>
);
};

Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './useAuthorityLinking';
export * from './useSubfieldNavigation';
1 change: 1 addition & 0 deletions src/hooks/useSubfieldNavigation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useSubfieldNavigation';
25 changes: 25 additions & 0 deletions src/hooks/useSubfieldNavigation/useSubfieldNavigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useCallback } from 'react';

import {
cursorToNextSubfield,
cursorToPrevSubfield,
} from './utils';
import { KEYBOARD_COMMAND_NAMES } from '../../common/constants';

export const useSubfieldNavigation = () => {
const processSubfieldFocus = useCallback(({ target }) => {
const end = target.value.length;

target.setSelectionRange(end, end);
}, []);

const keyCommands = [{
name: KEYBOARD_COMMAND_NAMES.NEXT_SUBFIELD,
handler: cursorToNextSubfield,
}, {
name: KEYBOARD_COMMAND_NAMES.PREV_SUBFIELD,
handler: cursorToPrevSubfield,
}];

return { processSubfieldFocus, keyCommands };
};
44 changes: 44 additions & 0 deletions src/hooks/useSubfieldNavigation/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const indexOfRegex = (string, regex) => {
const match = string.match(regex);

return match ? string.indexOf(match[0]) : -1;
};

const lastIndexOfRegex = (string, regex) => {
const match = string.match(regex);

return match ? string.lastIndexOf(match[match.length - 1]) : -1;
};

export const cursorToNextSubfield = (e) => {
e.preventDefault();
const cursorPosition = e.target.selectionStart;
const valueAfterCursor = e.target.value.substring(cursorPosition);

const nextSubfieldIndex = indexOfRegex(valueAfterCursor, /\$\w\s/g);

if (nextSubfieldIndex === -1) {
return;
}

const newPosition = nextSubfieldIndex + cursorPosition + 3;

e.target.setSelectionRange(newPosition, newPosition);
};

export const cursorToPrevSubfield = (e) => {
e.preventDefault();
const cursorPosition = e.target.selectionStart;
const startOfCurrentSubfieldPosition = lastIndexOfRegex(e.target.value.substring(0, cursorPosition), /\$\w\s/g);
const valueBeforeCurrentSubfield = e.target.value.substring(0, startOfCurrentSubfieldPosition);

const prevSubfieldIndex = lastIndexOfRegex(valueBeforeCurrentSubfield, /\$\w\s/g);

if (prevSubfieldIndex === -1) {
return;
}

const newPosition = prevSubfieldIndex + 3;

e.target.setSelectionRange(newPosition, newPosition);
};
75 changes: 75 additions & 0 deletions src/hooks/useSubfieldNavigation/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as utils from './utils';

describe('useSubfieldNavigation utils', () => {
describe('cursorToNextSubfield', () => {
let e;

beforeEach(() => {
e = {
preventDefault: jest.fn(),
target: {
value: '',
setSelectionRange: jest.fn(),
},
};
});

describe('when there is a next subfield', () => {
it('should move cursor to the beginning of next subfield', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 13; // cursor at `$a some value|`

utils.cursorToNextSubfield(e);

expect(e.target.setSelectionRange).toHaveBeenCalledWith(17, 17); // cursor at `$b |some other value`
});
});

describe('when there is no next subfield', () => {
it('should keep cursor where it is', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 21; // cursor at `$b some| other value`

utils.cursorToNextSubfield(e);

expect(e.target.setSelectionRange).not.toHaveBeenCalled();
});
});
});

describe('cursorToPrevSubfield', () => {
let e;

beforeEach(() => {
e = {
preventDefault: jest.fn(),
target: {
value: '',
setSelectionRange: jest.fn(),
},
};
});

describe('when there is a prev subfield', () => {
it('should move cursor to the beginning of prev subfield', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 21; // cursor at `$b some| other value`

utils.cursorToPrevSubfield(e);

expect(e.target.setSelectionRange).toHaveBeenCalledWith(3, 3); // cursor at `$a |some value`
});
});

describe('when there is no prev subfield', () => {
it('should keep cursor where it is', () => {
e.target.value = '$a some value $b some other value';
e.target.selectionStart = 13; // cursor at `$a some value|`

utils.cursorToPrevSubfield(e);

expect(e.target.setSelectionRange).not.toHaveBeenCalled();
});
});
});
});

0 comments on commit 908fb7b

Please sign in to comment.