Skip to content
Draft
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
14 changes: 11 additions & 3 deletions projects/element-ng/ip-input/address-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,14 @@ export const splitIpV4Sections = (
};
};

export const splitIpV6Sections = (options: Ip6SplitOptions): { value: string } => {
export const splitIpV6Sections = (
options: Ip6SplitOptions
): { value: string; cursorDelta: number } => {
const { type, input, pos, zeroCompression, cidr } = options;
const sections: Section[] = [{ value: '' }];
let cursorDelta = 0;
if (!input) {
return { value: '' };
return { value: '', cursorDelta: 0 };
}

for (let i = 0; i < input.length; i++) {
Expand Down Expand Up @@ -130,12 +133,17 @@ export const splitIpV6Sections = (options: Ip6SplitOptions): { value: string } =
const { value, current } = sections[i];
if (value.length > 4) {
const append: Section[] = [];
let charsProcessed = 0;
for (let p = 0; p < value.length; p += 4) {
const part = value.substring(p, p + 4);
append.push({ value: part });
if (part.length === 4) {
append.push({ value: ':' });
if (current && pos >= charsProcessed) {
cursorDelta++;
}
Comment on lines +142 to +144

Choose a reason for hiding this comment

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

high

The logic to calculate cursorDelta is incorrect because it compares pos (an index in the raw input string) with charsProcessed (an index within a cleaned section value). This can lead to incorrect cursor positioning when inserting characters in the middle of an IP address segment, as pos and charsProcessed are in different coordinate systems.

To fix this, you need to determine the cursor's position relative to the hexadecimal characters within its section.

Here is a suggested approach:

  1. In the first for loop (lines 111-128), determine the cursor's position relative to the hex characters in its section and store it.

    let cursorRelativePos = -1;
    for (let i = 0; i <= input.length; i++) {
      if (pos === i) {
        sections.at(-1)!.current = true;
        cursorRelativePos = sections.at(-1)!.value.length;
      }
      if (i >= input.length) {
        continue;
      }
      // ... existing logic to build sections ...
    }
  2. Then, use this cursorRelativePos here to correctly calculate cursorDelta.

    if (current && cursorRelativePos >= charsProcessed + 4) {
      cursorDelta++;
    }

Additionally, the new tests for cursor positioning in si-ip6-input.directive.spec.ts only cover appending characters. It would be beneficial to add a test case for inserting a character in the middle of a section to verify the fix and prevent future regressions.

}
charsProcessed += 4;
}

sections.splice(i, 1, ...append);
Expand Down Expand Up @@ -178,5 +186,5 @@ export const splitIpV6Sections = (options: Ip6SplitOptions): { value: string } =
.splice(0, cidr ? 17 : 15)
.map(s => s.value)
.join('');
return { value };
return { value, cursorDelta };
};
42 changes: 42 additions & 0 deletions projects/element-ng/ip-input/si-ip6-input.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,46 @@ describe('SiIp6InputDirective', () => {
});
});
});

describe('cursor positioning', () => {
it('should retain cursor position when typing in middle of section', () => {
input.value = '2001:0d0';
input.dispatchEvent(new InputEvent('input', { data: '0', inputType: 'insertText' }));
expect(input.value).toBe('2001:0D0');
expect(input.selectionStart).toBe(8);
});

it('should handle multiple section splits correctly', () => {
for (const c of '12345') {
input.value += c;
input.dispatchEvent(new InputEvent('input', { data: c, inputType: 'insertText' }));
}
expect(input.value).toBe('1234:5');
expect(input.selectionStart).toBe(6);
});

it('should retain cursor at end when typing at end', () => {
input.value = '2001:d';
input.dispatchEvent(new InputEvent('input', { data: 'd', inputType: 'insertText' }));
expect(input.value).toBe('2001:D');
expect(input.selectionStart).toBe(6);
});

it('should retain cursor position with zero compression', () => {
input.value = '::1';
input.dispatchEvent(new InputEvent('input', { data: '1', inputType: 'insertText' }));
expect(input.value).toBe('::1');
expect(input.selectionStart).toBe(3);
});

it('should handle cursor in CIDR section', () => {
component.cidr.set(true);
fixture.detectChanges();

input.value = '2001:db8::1/64';
input.dispatchEvent(new InputEvent('input', { data: '4', inputType: 'insertText' }));
expect(input.value).toBe('2001:DB8::1/64');
expect(input.selectionStart).toBe(14);
});
});

Choose a reason for hiding this comment

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

medium

The current tests for cursor positioning do not cover cases where a section split is triggered by inserting characters in the middle of a string. This scenario is where the cursorDelta calculation is most critical. Please add a new test case to cover this, which will help validate the fix for the logic in address-utils.ts.

    it('should retain cursor position when section splits from middle insertion', () => {
      // Simulate typing 'a' in '12345678' at position 4 -> '1234a|5678'
      input.value = '1234a5678';
      input.setSelectionRange(5, 5);
      input.dispatchEvent(new InputEvent('input', { data: 'a', inputType: 'insertText' }));
      expect(input.value).toBe('1234:A567:8');
      expect(input.selectionStart).toBe(6);
    });

});
11 changes: 10 additions & 1 deletion projects/element-ng/ip-input/si-ip6-input.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export class SiIp6InputDirective
return;
}

// TODO: Restore cursor position
const ipv6 = splitIpV6Sections({
type,
input: value,
Expand All @@ -79,5 +78,15 @@ export class SiIp6InputDirective
cidr: this.cidr()
});
this.renderer.setProperty(this.inputEl, 'value', ipv6.value);

if (type === 'insert') {
const el = this.elementRef.nativeElement;
if (value?.length === pos) {
el.setSelectionRange(ipv6.value.length, ipv6.value.length);
} else {
const newPos = pos + ipv6.cursorDelta;
el.setSelectionRange(newPos, newPos);
}
}
}
}
Loading