A Cursor Restore Rule for Formatting a Product Code Field
Imagine a product code field that shows codes in groups, like "ABCD 1234 EF". The user types, the field inserts a space, and the caret jumps to the end. If they were fixing a mistake in the middle, typing now lands in the wrong place. This article proposes a caret restore rule and a script to test it. It is a design proposal, not a claim about any existing component.
The core idea
Do not track the caret by its raw index. Track it by how many meaningful characters, those that are not inserted spaces, sit before it. After formatting, place the caret after the same count of meaningful characters.
The rule in steps
- Before reformatting, read the caret position and count the non-space characters to its left. Call this N.
- Strip spaces and apply your allowed character rules, noting how many characters were removed to the left of the caret, since those reduce N.
- Rebuild the grouped string.
- Walk the new string and find the position just after the Nth meaningful character.
- Set the caret there.
Branching choices
If the caret lands exactly where a space was inserted, choose a side. Consider placing it after the space when the user is typing forward, so the next character appears naturally, and before the space when the user is deleting backward. Document your choice.
If the user selects a range and types over it, treat the selection start as the anchor and compute N from there.
If the user pastes text, count meaningful characters in the pasted portion that survive cleaning and place the caret after them, not at the end of the whole value.
If the field uses a mask that rejects characters, decide whether a rejected key leaves the caret unchanged. Usually it should.
If an input method composition is active, such as for accented or non-Latin input, avoid reformatting until composition ends.
Caret-position input test script
Run each step on a desktop browser and a touch device. Use the example format of four letters, four digits, two letters, with spaces after the fourth and eighth meaningful characters.
Test 1, typing forward from empty: type ABCDE. Expect "ABCD E" with the caret at the end.
Test 2, insert in the middle: with "ABCD 1234" place the caret after B and type X. Expect "ABXC D123 4" and the caret right after X. The next typed character appears after X.
Test 3, delete across a space: with "ABCD 1234" place the caret after the space and press backspace. Expect the space handled sensibly: either the D is removed or the caret simply skips the space. Pick one, then check every later run matches.
Test 4, forward delete before a space: caret after D in "ABCD 1234", press delete. Expect the 1 removed and no stray space.
Test 5, paste in the middle: paste "zz" after B. Expect characters accepted or rejected per your rules, caret after the accepted ones.
Test 6, select and replace: select "BC" and type Q. Expect "AQD..." regrouped, caret after Q.
Test 7, undo: press the undo shortcut. Record what happens and whether the caret remains usable. Treat a broken undo as a known limitation to note, not necessarily a blocker.
Test 8, rapid typing: type fast in the middle. Expect no caret jumps to the end.
Hypothetical worked example
Start with "ABCD 1234". The caret sits after "AB", so N is 2. The user types X. The cleaned string is "ABXCD1234" and N is now 3 because X was inserted left of the caret. Regrouping gives "ABXC D123 4". The position after the third meaningful character is after X, index 3. The caret goes to index 3. Had the caret been placed by the old raw index of 3 in the old string, it would also be fine here, but after typing at index 4 of "ABCD 1234", raw indexing shifts when the space moves, which is where the count method helps.
Stop conditions
Stop and simplify if any test shows the caret jumping to the end, if you need browser-specific hacks in more than one place, or if screen reader users report confusing announcements. A fallback is to leave spaces out of the field and show the grouped form beside it as a read-only preview.
Acceptance
Accept the rule when all eight tests pass on the browsers and devices you support, with results recorded per device. Unclear outcomes count as failures until retested.