forked from MapComplete/MapComplete
		
	
		
			
	
	
		
			123 lines
		
	
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			123 lines
		
	
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import {UIElement} from "../UIElement"; | ||
|  | import {InputElement} from "./InputElement"; | ||
|  | import Translations from "../i18n/Translations"; | ||
|  | import {UIEventSource} from "../../Logic/UIEventSource"; | ||
|  | 
 | ||
|  | export class NumberField extends InputElement<number> { | ||
|  |     private readonly value: UIEventSource<number>; | ||
|  |     public readonly enterPressed = new UIEventSource<string>(undefined); | ||
|  |     private readonly _placeholder: UIElement; | ||
|  |     private options?: { | ||
|  |         placeholder?: string | UIElement, | ||
|  |         value?: UIEventSource<number>, | ||
|  |         isValid?: ((i: number) => boolean), | ||
|  |         min?: number, | ||
|  |         max?: number | ||
|  |     }; | ||
|  |     public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||
|  |     private readonly _isValid: (i:number) => boolean; | ||
|  | 
 | ||
|  |     constructor(options?: { | ||
|  |         placeholder?: string | UIElement, | ||
|  |         value?: UIEventSource<number>, | ||
|  |         isValid?: ((i:number) => boolean), | ||
|  |         min?: number, | ||
|  |         max?:number | ||
|  |     }) { | ||
|  |         super(undefined); | ||
|  |         this.options = options; | ||
|  |         const self = this; | ||
|  |         this.value = new UIEventSource<number>(undefined); | ||
|  |         this.value = options?.value ?? new UIEventSource<number>(undefined); | ||
|  | 
 | ||
|  |         this._isValid = options.isValid ?? ((i) => true); | ||
|  | 
 | ||
|  |         this._placeholder = Translations.W(options.placeholder ?? ""); | ||
|  |         this.ListenTo(this._placeholder._source); | ||
|  | 
 | ||
|  |         this.onClick(() => { | ||
|  |             self.IsSelected.setData(true) | ||
|  |         }); | ||
|  |         this.value.addCallback((t) => { | ||
|  |             const field = document.getElementById("txt-"+this.id); | ||
|  |             if (field === undefined || field === null) { | ||
|  |                 return; | ||
|  |             } | ||
|  |             field.className = self.IsValid(t) ? "" : "invalid"; | ||
|  | 
 | ||
|  |             if (t === undefined || t === null) { | ||
|  |                 return; | ||
|  |             } | ||
|  |             // @ts-ignore
 | ||
|  |             field.value = t; | ||
|  |         }); | ||
|  |         this.dumbMode = false; | ||
|  |     } | ||
|  | 
 | ||
|  |     GetValue(): UIEventSource<number> { | ||
|  |         return this.value; | ||
|  |     } | ||
|  | 
 | ||
|  |     InnerRender(): string { | ||
|  | 
 | ||
|  |         const placeholder = this._placeholder.InnerRender().replace("'", "'"); | ||
|  |          | ||
|  |         let min =  ""; | ||
|  |         if(this.options.min){ | ||
|  |             min = `min='${this.options.min}'`; | ||
|  |         } | ||
|  | 
 | ||
|  |         let max =  ""; | ||
|  |         if(this.options.min){ | ||
|  |             max = `max='${this.options.max}'`; | ||
|  |         } | ||
|  | 
 | ||
|  |         return `<span id="${this.id}"><form onSubmit='return false' class='form-text-field'>` + | ||
|  |             `<input type='number' ${min} ${max} placeholder='${placeholder}' id='txt-${this.id}'>` + | ||
|  |             `</form></span>`; | ||
|  |     } | ||
|  |      | ||
|  |     InnerUpdate() { | ||
|  |         const field = document.getElementById("txt-" + this.id); | ||
|  |         const self = this; | ||
|  |         field.oninput = () => { | ||
|  |              | ||
|  |             // How much characters are on the right, not including spaces?
 | ||
|  |             // @ts-ignore
 | ||
|  |             const endDistance = field.value.substring(field.selectionEnd).replace(/ /g,'').length; | ||
|  |             // @ts-ignore
 | ||
|  |             let val: number = Number(field.value); | ||
|  |             if (!self.IsValid(val)) { | ||
|  |                 self.value.setData(undefined); | ||
|  |             } else { | ||
|  |                 self.value.setData(val); | ||
|  |             } | ||
|  |     | ||
|  |         }; | ||
|  | 
 | ||
|  |         if (this.value.data !== undefined && this.value.data !== null) { | ||
|  |             // @ts-ignore
 | ||
|  |             field.value = this.value.data; | ||
|  |         } | ||
|  | 
 | ||
|  |         field.addEventListener("focusin", () => self.IsSelected.setData(true)); | ||
|  |         field.addEventListener("focusout", () => self.IsSelected.setData(false)); | ||
|  | 
 | ||
|  | 
 | ||
|  |         field.addEventListener("keyup", function (event) { | ||
|  |             if (event.key === "Enter") { | ||
|  |                 // @ts-ignore
 | ||
|  |                 self.enterPressed.setData(field.value); | ||
|  |             } | ||
|  |         }); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     IsValid(t: number): boolean { | ||
|  |         if (t === undefined || t === null) { | ||
|  |             return false | ||
|  |         } | ||
|  |         return this._isValid(t); | ||
|  |     } | ||
|  | 
 | ||
|  | } |