Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Problem with cursor jumps and input behaviour for NumberInput #1945

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ Import components from `carbon-components-svelte` in the `script` tag of your Sv

[carbon-preprocess-svelte](https://github.com/carbon-design-system/carbon-preprocess-svelte) is a collection of Svelte preprocessors for Carbon.

> [!NOTE]
> Using `carbon-preprocess-svelte` is optional and not a prerequisite for this library.
> [!INFO]
> Using `carbon-preprocess-svelte` is optional and not a prerequisite to using this library. It's designed for better developer experience in addition to CSS build performance.

```sh
# Yarn
Expand Down
54 changes: 53 additions & 1 deletion src/NumberInput/NumberInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
export let value = null;

let previousValue = null;

/** Specify the step increment */
export let step = 1;

Expand Down Expand Up @@ -99,6 +101,7 @@
/** Obtain a reference to the input HTML element */
export let ref = null;

import { tick } from "svelte";
import { createEventDispatcher } from "svelte";
import Add from "../icons/Add.svelte";
import Subtract from "../icons/Subtract.svelte";
Expand All @@ -114,11 +117,14 @@
const dispatch = createEventDispatcher();

function updateValue(isIncrementing) {
previousValue = ref.value;

if (isIncrementing) {
ref.stepUp();
} else {
ref.stepDown();
}

value = +ref.value;

dispatch("input", value);
Expand All @@ -141,12 +147,58 @@
return raw != "" ? Number(raw) : null;
}

function onInput({ target }) {
async function onInput({ target, inputType, data }) {
previousValue = parse(value);
const possibleNewValue = parse(target.value);
value = parse(target.value);

const isIntegerTransition =
!data &&
!Number.isInteger(previousValue) &&
Number.isInteger(possibleNewValue);

if (isIntegerTransition && value < previousValue) {
await setSelectionAfterTick(target.value.length);
} else if (isIntegerTransition && previousValue && value > previousValue) {
let parts = previousValue.toString().split(".");

if (inputType === "deleteContentForward" && parts[1]) {
parts[1] = parts[1].substring(1);
} else if (inputType === "deleteContentBackward") {
parts[0] = parts[0].slice(0, -1);
}

const chars =
parts[0].length + (inputType === "deleteContentBackward" ? 1 : 0);
value = parse(parts.join("."));
await setSelectionAfterTick(chars);
} else if (
!isIntegerTransition &&
previousValue &&
value === previousValue &&
inputType === "deleteContentForward"
) {
value = parse(0);
await setSelectionAfterTick(0);
} else {
value = possibleNewValue;
}
Comment on lines +155 to +185
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire logic is hard to understand/follow, though I also don't know if/how it could be improved...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function starts by parsing the current value of the input field and storing it in previousValue. It then parses the new value of the input field and stores it in possibleNewValue and value.

Next, it checks if the input action resulted in a transition from a non-integer to an integer. This is determined by the isIntegerTransition variable, which is true if data is undefined, previousValue is not an integer, and possibleNewValue is an integer.

If isIntegerTransition is true and the new value is less than the previous value, it sets the cursor position to the end of the input field after the next tick.

If isIntegerTransition is true, the new value is greater than the previous value, and the previous value is not undefined, it splits the previous value into integer and fractional parts. Depending on the inputType, it removes a character from the fractional part or the integer part. It then joins the parts back together, parses the result, and sets it as the new value. It also sets the cursor position to a specific position after the next tick.

If isIntegerTransition is false, the new value is equal to the previous value, and the inputType is "deleteContentForward" (meaning the user pressed delete key instead of backspace), it sets the value to 0 and the cursor position to the start of the input field after the next tick.

If none of the above conditions are met, it simply sets the value to possibleNewValue.


dispatch("input", value);
}

function setSelection(position) {
ref.focus();
ref.type = "text";
ref.setSelectionRange(position, position);
ref.type = "number";
}

async function setSelectionAfterTick(position) {
await tick();
setSelection(position);
}

function onChange({ target }) {
dispatch("change", parse(target.value));
}
Expand Down