Skip to content

Commit

Permalink
FIO-6578: Fixes an issue with losing focus on Year field when Day com…
Browse files Browse the repository at this point in the history
…ponent has advanced logic (#5301)

* FIO-6578: Fixes an issue with losing focus on Year field when Day component has advanced logic

* refactoring
  • Loading branch information
alexandraRamanenka authored and lane-formio committed Sep 26, 2023
1 parent be88864 commit cc09452
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 28 deletions.
6 changes: 3 additions & 3 deletions src/components/_classes/component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -1708,16 +1708,16 @@ export default class Component extends Element {
if (this.refs.input?.length) {
const { selection, index } = this.root.currentSelection;
let input = this.refs.input[index];
const isInputRangeSelectable = /text|search|password|tel|url/i.test(input.type || '');
const isInputRangeSelectable = (i) => /text|search|password|tel|url/i.test(i?.type || '');
if (input) {
if (isInputRangeSelectable) {
if (isInputRangeSelectable(input)) {
input.setSelectionRange(...selection);
}
}
else {
input = this.refs.input[this.refs.input.length];
const lastCharacter = input.value?.length || 0;
if (isInputRangeSelectable) {
if (isInputRangeSelectable(input)) {
input.setSelectionRange(lastCharacter, lastCharacter);
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/components/_classes/field/Field.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,18 @@ export default class Field extends Component {
}));
}
}

// Saves current caret position to restore it after the component is redrawn
saveCaretPosition(element, index) {
if (this.root?.focusedComponent?.path === this.path) {
try {
this.root.currentSelection = { selection: [element.selectionStart, element.selectionEnd], index };
}
catch (e) {
if (!(e instanceof DOMException)) {
console.debug(e);
}
}
}
}
}
7 changes: 0 additions & 7 deletions src/components/_classes/multivalue/Multivalue.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,6 @@ export default class Multivalue extends Field {
}
}

// Saves current caret position to restore it after the component is redrawn
saveCaretPosition(element, index) {
if (this.root?.focusedComponent?.path === this.path) {
this.root.currentSelection = { selection: [element.selectionStart, element.selectionEnd], index };
}
}

onSelectMaskHandler(event) {
this.updateMask(event.target.maskInput, this.getMaskPattern(event.target.value));
}
Expand Down
55 changes: 42 additions & 13 deletions src/components/day/Day.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash';
import moment from 'moment';
import Field from '../_classes/field/Field';
import { boolValue, getLocaleDateFormatInfo } from '../../utils/utils';
import { boolValue, componentValueTypes, getComponentSavedTypes, getLocaleDateFormatInfo } from '../../utils/utils';

export default class DayComponent extends Field {
static schema(...extend) {
Expand Down Expand Up @@ -288,6 +288,19 @@ export default class DayComponent extends Field {
attach(element) {
this.loadRefs(element, { day: 'single', month: 'single', year: 'single', input: 'multiple' });
const superAttach = super.attach(element);

const updateValueAndSaveFocus = (element, name) => () => {
try {
this.saveCaretPosition(element, name);
}
catch (err) {
console.warn('An error occurred while trying to save caret position', err);
}
this.updateValue(null, {
modified: true,
});
};

if (this.shouldDisabled) {
this.setDisabled(this.refs.day, true);
this.setDisabled(this.refs.month, true);
Expand All @@ -297,13 +310,14 @@ export default class DayComponent extends Field {
}
}
else {
this.addEventListener(this.refs.day, 'input', () => this.updateValue(null, {
modified: true
}));
this.addEventListener(this.refs.day, 'input', updateValueAndSaveFocus(this.refs.day, 'day'));
// TODO: Need to rework this to work with day select as well.
// Change day max input when month changes.
this.addEventListener(this.refs.month, 'input', () => {
const maxDay = this.refs.year ? parseInt(new Date(this.refs.year.value, this.refs.month.value, 0).getDate(), 10)
const maxDay = this.refs.year ? parseInt(
new Date(this.refs.year.value, this.refs.month.value, 0).getDate(),
10
)
: '';
const day = this.getFieldValue('day');
if (!this.component.fields.day.hide && maxDay) {
Expand All @@ -312,16 +326,15 @@ export default class DayComponent extends Field {
if (maxDay && day > maxDay) {
this.refs.day.value = this.refs.day.max;
}
this.updateValue(null, {
modified: true
});
updateValueAndSaveFocus(this.refs.month, 'month')();
});
this.addEventListener(this.refs.year, 'input', () => this.updateValue(null, {
modified: true
}));
this.addEventListener(this.refs.year, 'input', updateValueAndSaveFocus(this.refs.year, 'year'));
this.addEventListener(this.refs.input, this.info.changeEvent, () => this.updateValue(null, {
modified: true
}));
[this.refs.day, this.refs.month, this.refs.year].filter((element) => !!element).forEach((element) => {
super.addFocusBlurEvents(element);
});
}
this.setValue(this.dataValue);
// Force the disabled state with getters and setters.
Expand Down Expand Up @@ -587,8 +600,11 @@ export default class DayComponent extends Field {
return this.getDate(value) || '';
}

focus() {
if (this.dayFirst && this.showDay || !this.dayFirst && !this.showMonth && this.showDay) {
focus(field) {
if (field && typeof field === 'string' && this.refs[field]) {
this.refs[field].focus();
}
else if (this.dayFirst && this.showDay || !this.dayFirst && !this.showMonth && this.showDay) {
this.refs.day?.focus();
}
else if (this.dayFirst && !this.showDay && this.showMonth || !this.dayFirst && this.showMonth) {
Expand All @@ -599,6 +615,19 @@ export default class DayComponent extends Field {
}
}

restoreCaretPosition() {
if (this.root?.currentSelection) {
const { selection, index } = this.root.currentSelection;
if (this.refs[index]) {
const input = this.refs[index];
const isInputRangeSelectable = (i) => /text|search|password|tel|url/i.test(i?.type || '');
if (isInputRangeSelectable(input)) {
input.setSelectionRange(...selection);
}
}
}
}

isPartialDay(value) {
if (!value) {
return false;
Expand Down
51 changes: 50 additions & 1 deletion src/components/day/Day.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
comp1,
comp2,
comp3,
comp4
comp4,
comp5,
comp6,
} from './fixtures';
import PanelComponent from '../panel/Panel';

Expand Down Expand Up @@ -219,4 +221,51 @@ describe('Day Component', () => {
});
comp1.fields.year.hide = false;
});

it('OnBlur validation should work properly with Day component', (done) => {
const element = document.createElement('div');

Formio.createForm(element, comp5).then(form => {
const dayComponent = form.components[0];
dayComponent.setValue('03/12/2023');

setTimeout(() => {
dayComponent.refs.day.focus();
dayComponent.refs.day.value = '';
dayComponent.refs.day.dispatchEvent(new Event('input'));

setTimeout(() => {
assert(!dayComponent.error, 'Day should be valid while changing');
dayComponent.refs.day.dispatchEvent(new Event('blur'));

setTimeout(() => {
assert(dayComponent.error, 'Should set error after Day component was blurred');
done();
}, 200);
}, 200);
}, 200);
}).catch(done);
});

it('Should restore focus after redraw', (done) => {
const element = document.createElement('div');
document.body.appendChild(element);
Formio.createForm(element, comp6).then(form => {
const textField = form.getComponent(['textField']);
textField.setValue('test');

setTimeout(() => {
const day = form.getComponent(['day']);
document.querySelector('select.form-control').focus();
day.refs.month.value = 2;
day.refs.month.dispatchEvent(new Event('input'));

setTimeout(() => {
console.log(global.document.activeElement, day.refs.month);
assert(global.document.activeElement === day.refs.month, 'Should keep focus on the year select');
done();
}, 200);
}, 500);
}).catch(done);
});
});
74 changes: 74 additions & 0 deletions src/components/day/fixtures/comp6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export default {
type: 'form',
display: 'form',
components: [
{
label: 'Text Field',
applyMaskOn: 'change',
tableView: true,
key: 'textField',
type: 'textfield',
input: true,
},
{
label: 'Day',
hideInputLabels: false,
inputsLabelPosition: 'top',
useLocaleSettings: false,
tableView: false,
fields: {
day: {
hide: true,
},
month: {
hide: false,
},
year: {
hide: false,
},
},
defaultValue: '00/00/0000',
key: 'day',
logic: [
{
name: 'Disable when Test is empty',
trigger: {
type: 'simple',
simple: {
show: true,
conjunction: 'all',
conditions: [
{
component: 'textField',
operator: 'isEmpty',
},
],
},
},
actions: [
{
name: 'Disable',
type: 'property',
property: {
label: 'Disabled',
value: 'disabled',
type: 'boolean',
},
state: true,
},
],
},
],
type: 'day',
input: true,
},
{
type: 'button',
label: 'Submit',
key: 'submit',
disableOnInvalid: true,
input: true,
tableView: false,
},
],
};
11 changes: 7 additions & 4 deletions src/components/day/fixtures/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export comp1 from './comp1';
export comp2 from './comp2';
export comp3 from './comp3';
export comp4 from './comp4';
import comp1 from './comp1';
import comp2 from './comp2';
import comp3 from './comp3';
import comp4 from './comp4';
import comp5 from './comp5';
import comp6 from './comp6';
export { comp1, comp2, comp3, comp4, comp5, comp6 };

0 comments on commit cc09452

Please sign in to comment.