diff --git a/projects/element-ng/ip-input/address-utils.ts b/projects/element-ng/ip-input/address-utils.ts index d5a9e3988..8668fb3f3 100644 --- a/projects/element-ng/ip-input/address-utils.ts +++ b/projects/element-ng/ip-input/address-utils.ts @@ -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++) { @@ -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++; + } } + charsProcessed += 4; } sections.splice(i, 1, ...append); @@ -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 }; }; diff --git a/projects/element-ng/ip-input/si-ip6-input.directive.spec.ts b/projects/element-ng/ip-input/si-ip6-input.directive.spec.ts index 143d98498..0c4d8ff3d 100644 --- a/projects/element-ng/ip-input/si-ip6-input.directive.spec.ts +++ b/projects/element-ng/ip-input/si-ip6-input.directive.spec.ts @@ -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); + }); + }); }); diff --git a/projects/element-ng/ip-input/si-ip6-input.directive.ts b/projects/element-ng/ip-input/si-ip6-input.directive.ts index 13a64bc31..2bcde4a8e 100644 --- a/projects/element-ng/ip-input/si-ip6-input.directive.ts +++ b/projects/element-ng/ip-input/si-ip6-input.directive.ts @@ -70,7 +70,6 @@ export class SiIp6InputDirective return; } - // TODO: Restore cursor position const ipv6 = splitIpV6Sections({ type, input: value, @@ -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); + } + } } }