2020-07-20 15:54:50 +02:00
import { InputElement } from "./InputElement" ;
2020-07-21 00:38:03 +02:00
import Translations from "../i18n/Translations" ;
2020-08-17 17:23:15 +02:00
import { UIEventSource } from "../../Logic/UIEventSource" ;
2021-06-10 01:36:20 +02:00
import BaseUIElement from "../BaseUIElement" ;
2020-08-22 02:12:46 +02:00
2020-09-25 12:44:04 +02:00
export class TextField extends InputElement < string > {
2020-08-30 01:13:18 +02:00
public readonly enterPressed = new UIEventSource < string > ( undefined ) ;
2020-08-31 02:59:47 +02:00
public readonly IsSelected : UIEventSource < boolean > = new UIEventSource < boolean > ( false ) ;
2021-06-12 02:58:32 +02:00
private readonly value : UIEventSource < string > ;
2021-06-10 01:36:20 +02:00
private _element : HTMLElement ;
private readonly _isValid : ( s : string , country ? : ( ) = > string ) = > boolean ;
2020-09-26 03:02:19 +02:00
2020-09-25 12:44:04 +02:00
constructor ( options ? : {
2021-06-10 01:36:20 +02:00
placeholder? : string | BaseUIElement ,
2020-09-25 12:44:04 +02:00
value? : UIEventSource < string > ,
2020-10-04 01:04:46 +02:00
htmlType? : string ,
2021-05-11 02:39:51 +02:00
inputMode? : string ,
2021-06-10 01:36:20 +02:00
label? : BaseUIElement ,
2020-09-24 23:56:08 +02:00
textAreaRows? : number ,
2021-06-12 02:58:32 +02:00
inputStyle? : string ,
2020-12-05 03:22:17 +01:00
isValid ? : ( ( s : string , country ? : ( ) = > string ) = > boolean )
2020-07-20 13:28:45 +02:00
} ) {
2021-06-10 01:36:20 +02:00
super ( ) ;
2020-07-20 21:39:07 +02:00
const self = this ;
2020-09-25 12:44:04 +02:00
options = options ? ? { } ;
this . value = options ? . value ? ? new UIEventSource < string > ( undefined ) ;
2021-06-10 01:36:20 +02:00
this . _isValid = options . isValid ? ? ( _ = > true ) ;
2021-06-12 02:58:32 +02:00
2020-09-10 21:06:56 +02:00
this . onClick ( ( ) = > {
self . IsSelected . setData ( true )
} ) ;
2020-09-25 12:44:04 +02:00
2021-06-10 01:36:20 +02:00
2021-06-12 02:58:32 +02:00
const placeholder = Translations . W ( options . placeholder ? ? "" ) . ConstructElement ( ) . innerText . replace ( "'" , "'" ) ;
2021-06-10 01:36:20 +02:00
this . SetClass ( "form-text-field" )
2021-06-12 02:58:32 +02:00
let inputEl : HTMLElement
if ( options . htmlType === "area" ) {
2021-07-11 15:44:17 +02:00
this . SetClass ( "w-full box-border max-w-full" )
2021-06-10 01:36:20 +02:00
const el = document . createElement ( "textarea" )
el . placeholder = placeholder
el . rows = options . textAreaRows
el . cols = 50
inputEl = el ;
2021-06-12 02:58:32 +02:00
} else {
2021-06-10 01:36:20 +02:00
const el = document . createElement ( "input" )
2021-06-12 02:58:32 +02:00
el . type = options . htmlType ? ? "text"
el . inputMode = options . inputMode
2021-06-10 01:36:20 +02:00
el . placeholder = placeholder
2021-06-12 02:58:32 +02:00
el . style . cssText = options . inputStyle
2021-06-10 01:36:20 +02:00
inputEl = el
}
const form = document . createElement ( "form" )
2021-06-12 02:58:32 +02:00
form . appendChild ( inputEl )
2021-06-10 01:36:20 +02:00
form . onsubmit = ( ) = > false ;
2021-06-12 02:58:32 +02:00
if ( options . label ) {
form . appendChild ( options . label . ConstructElement ( ) )
}
2021-06-10 01:36:20 +02:00
this . _element = form ;
const field = inputEl ;
2021-06-30 15:38:14 +02:00
this . value . addCallbackAndRunD ( value = > {
// We leave the textfield as is in the case of undefined or null (handled by addCallbackAndRunD) - make sure we do not erase it!
2021-06-16 21:23:03 +02:00
field [ "value" ] = value ;
2021-06-12 02:58:32 +02:00
if ( self . IsValid ( value ) ) {
2021-06-10 01:36:20 +02:00
self . RemoveClass ( "invalid" )
2021-06-12 02:58:32 +02:00
} else {
2021-06-10 01:36:20 +02:00
self . SetClass ( "invalid" )
}
2020-09-25 12:44:04 +02:00
2021-06-10 01:36:20 +02:00
} )
2020-09-25 12:44:04 +02:00
2020-07-01 02:12:33 +02:00
field . oninput = ( ) = > {
2021-06-10 01:36:20 +02:00
2020-09-26 21:00:03 +02:00
// How much characters are on the right, not including spaces?
2020-09-26 01:10:10 +02:00
// @ts-ignore
2021-06-12 02:58:32 +02:00
const endDistance = field . value . substring ( field . selectionEnd ) . replace ( / /g , '' ) . length ;
2020-07-05 18:59:47 +02:00
// @ts-ignore
2020-09-26 21:00:03 +02:00
let val : string = field . value ;
2020-09-26 03:02:19 +02:00
if ( ! self . IsValid ( val ) ) {
self . value . setData ( undefined ) ;
} else {
self . value . setData ( val ) ;
}
2020-09-26 01:10:10 +02:00
// Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change
// See https://github.com/pietervdvn/MapComplete/issues/103
2020-09-26 03:02:19 +02:00
// We reread the field value - it might have changed!
2021-06-10 01:36:20 +02:00
2020-09-26 03:02:19 +02:00
// @ts-ignore
2020-09-26 21:00:03 +02:00
val = field . value ;
2020-09-27 00:20:48 +02:00
let newCursorPos = val . length - endDistance ;
2021-06-12 02:58:32 +02:00
while ( newCursorPos >= 0 &&
2020-09-26 21:00:03 +02:00
// We count the number of _actual_ characters (non-space characters) on the right of the new value
// This count should become bigger then the end distance
2020-09-27 00:20:48 +02:00
val . substr ( newCursorPos ) . replace ( / /g , '' ) . length < endDistance
2021-06-12 02:58:32 +02:00
) {
newCursorPos -- ;
2020-09-26 21:00:03 +02:00
}
// @ts-ignore
2021-06-15 16:18:58 +02:00
TextField . SetCursorPosition ( field , newCursorPos ) ;
2020-07-01 02:12:33 +02:00
} ;
2021-06-12 02:58:32 +02:00
2020-08-31 02:59:47 +02:00
field . addEventListener ( "focusin" , ( ) = > self . IsSelected . setData ( true ) ) ;
field . addEventListener ( "focusout" , ( ) = > self . IsSelected . setData ( false ) ) ;
2020-09-10 21:06:56 +02:00
2020-07-01 02:12:33 +02:00
field . addEventListener ( "keyup" , function ( event ) {
if ( event . key === "Enter" ) {
2020-07-05 18:59:47 +02:00
// @ts-ignore
2020-07-01 02:12:33 +02:00
self . enterPressed . setData ( field . value ) ;
}
2021-06-12 02:58:32 +02:00
} ) ;
2021-06-10 01:36:20 +02:00
2020-07-01 02:12:33 +02:00
}
2021-06-10 01:36:20 +02:00
private static SetCursorPosition ( textfield : HTMLElement , i : number ) {
2021-06-12 02:58:32 +02:00
if ( textfield === undefined || textfield === null ) {
2020-09-11 00:23:19 +02:00
return ;
}
2020-09-25 12:44:04 +02:00
if ( i === - 1 ) {
2020-09-11 00:23:19 +02:00
// @ts-ignore
2021-06-10 01:36:20 +02:00
i = textfield . value . length ;
2020-09-11 00:23:19 +02:00
}
2021-06-10 01:36:20 +02:00
textfield . focus ( ) ;
2020-09-11 00:23:19 +02:00
// @ts-ignore
2021-06-10 01:36:20 +02:00
textfield . setSelectionRange ( i , i ) ;
2020-09-26 01:10:10 +02:00
2020-09-11 00:23:19 +02:00
}
2021-06-12 02:58:32 +02:00
GetValue ( ) : UIEventSource < string > {
return this . value ;
}
2020-09-25 12:44:04 +02:00
IsValid ( t : string ) : boolean {
2020-09-26 03:02:19 +02:00
if ( t === undefined || t === null ) {
return false
}
2020-09-26 21:00:03 +02:00
return this . _isValid ( t , undefined ) ;
2020-07-20 15:54:50 +02:00
}
2021-06-12 02:58:32 +02:00
protected InnerConstructElement ( ) : HTMLElement {
return this . _element ;
}
2020-09-27 00:20:48 +02:00
}