Skip to content

Commit

Permalink
Use @Focus over @click
Browse files Browse the repository at this point in the history
Tabbing to the control wasn't giving it focus. Reverted the event back to focus and then wrapped the event body in a `setTimeout([event body], 1)` so it executes after the click event sets the selection range.

Now it works for node specific values as well by adapting the selector to the nodeId parameter.

Tabbing to the control puts the cursor at the end.

Clicking on the control puts the cursor where the user clicks.

Highlighting text in the control highlights the same text.

Methodology used: We setTimeout(..., 1) to let the click event finish so the cursor location is accurate. After that, we change our form variables normally. Then we collect scroll and caret information. We wait one tick for the form changes we just made to propogate through the UI and then we set the scroll and caret information on the new text-area.
  • Loading branch information
coreyogburn committed Jan 9, 2025
1 parent 430b744 commit 6d125d9
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 16 deletions.
4 changes: 2 additions & 2 deletions html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5581,11 +5581,11 @@ <h4 v-if="!$root.loading">{{ i18n.settingsCustomized }} {{ settingsCustomized }}
</v-btn>
</div>
<div v-if="isMultiline(selected)">
<v-textarea v-if="form.key != selected.id" id="value-output" variant="outlined" :label="i18n.settingGlobal" @click="edit(selected)" v-model="selected.value" readonly :disabled="isReadOnly(selected)" :no-resize="!isReadOnly(selected)" class="config-editor" data-aid="config_item_customize_multiline_display" />
<v-textarea v-if="form.key != selected.id" id="value-output" variant="outlined" :label="i18n.settingGlobal" @focus="edit(selected)" v-model="selected.value" readonly :disabled="isReadOnly(selected)" :no-resize="!isReadOnly(selected)" class="config-editor" data-aid="config_item_customize_multiline_display" />
<v-textarea v-else id="value-input" variant="outlined" :label="i18n.settingGlobal" v-model="form.value" class="editing config-editor" data-aid="config_item_customize_multiline_input" />
</div>
<div v-else>
<v-text-field v-if="form.key != selected.id" id="value-output" variant="outlined" :label="i18n.settingGlobal" @click="edit(selected)" v-model="selected.value" readonly :disabled="isReadOnly(selected)" :type="selected.sensitive ? 'password' : 'text'" data-aid="config_item_customize_singleline_display" />
<v-text-field v-if="form.key != selected.id" id="value-output" variant="outlined" :label="i18n.settingGlobal" @focus="edit(selected)" v-model="selected.value" readonly :disabled="isReadOnly(selected)" :type="selected.sensitive ? 'password' : 'text'" data-aid="config_item_customize_singleline_display" />
<v-text-field v-else id="value-input" variant="outlined" :label="i18n.settingGlobal" v-model="form.value" class="editing" @keydown.enter.prevent="save(selected)" data-aid="config_item_singleline_input" />
</div>
</v-col>
Expand Down
30 changes: 17 additions & 13 deletions html/js/routes/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ routes.push({
this.$root.stopLoading();
},
edit(setting, nodeId) {
setTimeout(() => {
if (nodeId) {
if ((this.form.key == nodeId) || !this.cancel()) return;
this.form.key = nodeId;
Expand All @@ -524,20 +525,23 @@ routes.push({
this.$root.drawAttention('#setting-global-save');
}

// transfer caret position from non-edit element to edit element
const before = document.getElementById('value-output');
const scrollTop = before?.scrollTop || 0;
const selStart = before?.selectionStart || 0;
const selEnd = before?.selectionEnd || 0;
this.$nextTick(() => {
const after = document.getElementById('value-input');
if (after && typeof scrollTop !== 'undefined' &&
// transfer caret position from non-edit element to edit element
let selector = nodeId ? 'node-value-output-' + nodeId : 'value-output';
const before = document.getElementById(selector);
const scrollTop = before?.scrollTop || 0;
const selStart = before?.selectionStart || 0;
const selEnd = before?.selectionEnd || 0;
this.$nextTick(() => {
selector = nodeId ? 'node-value-input-' + nodeId : 'value-input';
const after = document.getElementById(selector);
if (after && typeof scrollTop !== 'undefined' &&
typeof selStart !== 'undefined' && typeof selEnd !== 'undefined') {
after.focus();
after.scrollTop = scrollTop;
after.setSelectionRange(selStart, selEnd);
}
});
after.focus();
after.scrollTop = scrollTop;
after.setSelectionRange(selStart, selEnd);
}
});
}, 1);
},
addNode(setting, nodeId) {
if (this.cancel() && setting && nodeId) {
Expand Down
6 changes: 5 additions & 1 deletion html/js/routes/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,13 +508,14 @@ test('saveRegexValidMultiline', async () => {
expect(mock).toHaveBeenCalledWith('config/', {"id": "test.id", "nodeId": null, "value": "123\n456"});
});

test('edit', () => {
test('edit', async () => {
// Global edit, nothing pending
setupSettings();
comp.cancelDialog = false;
comp.form.value = null;
comp.form.key = null;
comp.edit(comp.settings[0], null);
await new Promise(resolve => setTimeout(resolve, 2));
expect(comp.form.key).toBe("s-id");
expect(comp.form.value).toBe("orig-value");
expect(comp.cancelDialog).toBe(false);
Expand All @@ -526,6 +527,7 @@ test('edit', () => {
comp.form.key = "s-id2";
comp.activeBackup = ["s-id2"];
comp.edit(comp.settings[0], null);
await new Promise(resolve => setTimeout(resolve, 2));
expect(comp.form.key).toBe("s-id2");
expect(comp.form.value).toBe("touched-value");
expect(comp.cancelDialog).toBe(true);
Expand All @@ -535,6 +537,7 @@ test('edit', () => {
comp.form.value = null;
comp.form.key = null;
comp.edit(comp.settings[0], "n1");
await new Promise(resolve => setTimeout(resolve, 2));
expect(comp.form.key).toBe("n1");
expect(comp.form.value).toBe("123");
expect(comp.cancelDialog).toBe(false);
Expand All @@ -544,6 +547,7 @@ test('edit', () => {
comp.form.value = "touched-value";
comp.form.key = "n2";
comp.edit(comp.settings[0], "n1");
await new Promise(resolve => setTimeout(resolve, 2));
expect(comp.form.key).toBe("n2");
expect(comp.form.value).toBe("touched-value");
expect(comp.cancelDialog).toBe(true);
Expand Down

0 comments on commit 6d125d9

Please sign in to comment.