| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  | <script lang="ts"> | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   import { UIEventSource } from "../../Logic/UIEventSource" | 
					
						
							|  |  |  |   import type { ValidatorType } from "./Validators" | 
					
						
							|  |  |  |   import Validators from "./Validators" | 
					
						
							|  |  |  |   import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid" | 
					
						
							|  |  |  |   import { Translation } from "../i18n/Translation" | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   import { createEventDispatcher, onDestroy } from "svelte" | 
					
						
							|  |  |  |   import { Validator } from "./Validator" | 
					
						
							|  |  |  |   import { Unit } from "../../Models/Unit" | 
					
						
							|  |  |  |   import UnitInput from "../Popup/UnitInput.svelte" | 
					
						
							|  |  |  |   import { Utils } from "../../Utils" | 
					
						
							|  |  |  |   import { twMerge } from "tailwind-merge" | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |   import type { ValueRange } from "../../Models/ThemeConfig/TagRenderingConfig" | 
					
						
							|  |  |  |   import Translations from "../i18n/Translations" | 
					
						
							|  |  |  |   import BaseUIElement from "../BaseUIElement" | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   export let type: ValidatorType | 
					
						
							|  |  |  |   export let feedback: UIEventSource<Translation> | undefined = undefined | 
					
						
							|  |  |  |   export let cls: string = undefined | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |   export let getCountry: () => string | undefined = undefined | 
					
						
							|  |  |  |   export let placeholder: string | Translation | undefined = undefined | 
					
						
							|  |  |  |   export let autofocus: boolean = false | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   export let unit: Unit = undefined | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |   export let range: ValueRange = undefined | 
					
						
							| 
									
										
										
										
											2023-12-12 03:46:51 +01:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Valid state, exported to the calling component | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   export let value: UIEventSource<string | undefined> | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Internal state bound to the input element. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * This is only copied to 'value' when appropriate so that no invalid values leak outside; | 
					
						
							|  |  |  |    * Additionally, the unit is added when copying | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |   export let unvalidatedText = new UIEventSource(value.data ?? "") | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-20 13:33:38 +01:00
										 |  |  |   if (unvalidatedText == /*Compare by reference!*/ value) { | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |     throw "Value and unvalidatedText may not be the same store!" | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   let validator: Validator = Validators.get(type ?? "string") | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   if (validator === undefined) { | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |     console.warn("Didn't find a validator for type", type) | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined) | 
					
						
							|  |  |  |   let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   function initValueAndDenom() { | 
					
						
							|  |  |  |     if (unit && value.data) { | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |       const [v, denom] = unit.findDenomination(value.data, getCountry) | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |       if (denom) { | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |         unvalidatedText.setData(v) | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |         selectedUnit.setData(denom.canonical) | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |         unvalidatedText.setData(value.data ?? "") | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |       unvalidatedText.setData(value.data ?? "") | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-20 13:33:38 +01:00
										 |  |  |   function onKeyPress(e: KeyboardEvent) { | 
					
						
							| 
									
										
										
										
											2024-09-02 15:00:19 +02:00
										 |  |  |     if (e.key === "Enter" && (!validator.textArea || e.ctrlKey)) { | 
					
						
							| 
									
										
										
										
											2024-02-20 13:33:38 +01:00
										 |  |  |       e.stopPropagation() | 
					
						
							|  |  |  |       e.preventDefault() | 
					
						
							|  |  |  |       dispatch("submit") | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-01-12 23:27:01 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |   initValueAndDenom() | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   $: { | 
					
						
							|  |  |  |     // The type changed -> reset some values | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |     validator = Validators.get(type ?? "string") | 
					
						
							| 
									
										
										
										
											2023-06-11 01:32:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |     _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |     if (unvalidatedText.data?.length > 0) { | 
					
						
							|  |  |  |       feedback?.setData(validator?.getFeedback(unvalidatedText.data, getCountry)) | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2023-12-24 05:01:10 +01:00
										 |  |  |       feedback?.setData(undefined) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |     initValueAndDenom() | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |   const t = Translations.t.validation.generic | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Side effect: sets the feedback, returns true/false if valid | 
					
						
							|  |  |  |    * @param canonicalValue | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   function validateRange(canonicalValue: number): boolean { | 
					
						
							|  |  |  |     if (!range) { | 
					
						
							|  |  |  |       return true | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (canonicalValue < range.warnBelow) { | 
					
						
							|  |  |  |       feedback.set(t.suspiciouslyLow) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (canonicalValue > range.warnAbove) { | 
					
						
							|  |  |  |       feedback.set(t.suspiciouslyHigh) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (canonicalValue > range.max) { | 
					
						
							|  |  |  |       let max: number | string | BaseUIElement = range.max | 
					
						
							|  |  |  |       if (unit) { | 
					
						
							|  |  |  |         max = unit.asHumanLongValue(max) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       feedback.set(t.tooHigh.Subs({ max })) | 
					
						
							|  |  |  |       return false | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (canonicalValue < range.min) { | 
					
						
							|  |  |  |       let min: number | string | BaseUIElement = range.min | 
					
						
							|  |  |  |       if (unit) { | 
					
						
							|  |  |  |         min = unit.asHumanLongValue(min) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       feedback.set(t.tooLow.Subs({ min })) | 
					
						
							|  |  |  |       return false | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   function setValues() { | 
					
						
							|  |  |  |     // Update the value stores | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |     const v = unvalidatedText.data | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |     if (v === "") { | 
					
						
							| 
									
										
										
										
											2023-12-24 05:01:10 +01:00
										 |  |  |       value.setData(undefined) | 
					
						
							| 
									
										
										
										
											2023-12-26 14:21:46 +01:00
										 |  |  |       feedback?.setData(undefined) | 
					
						
							| 
									
										
										
										
											2023-12-24 05:01:10 +01:00
										 |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-21 12:05:20 +02:00
										 |  |  |     feedback?.setData(validator?.getFeedback(v, getCountry)) | 
					
						
							| 
									
										
										
										
											2023-12-24 05:01:10 +01:00
										 |  |  |     if (!validator?.isValid(v, getCountry)) { | 
					
						
							|  |  |  |       value.setData(undefined) | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |       return | 
					
						
							| 
									
										
										
										
											2023-06-16 02:36:11 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-11 01:32:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     if (selectedUnit.data) { | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |       const canonicalValue = unit.valueInCanonical(v + selectedUnit.data) | 
					
						
							|  |  |  |       if (validateRange(canonicalValue)) { | 
					
						
							|  |  |  |         value.setData(unit.toOsm(v, selectedUnit.data)) | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         value.set(undefined) | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |       if (validateRange(v)) { | 
					
						
							|  |  |  |         value.setData(v) | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         value.set(undefined) | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |   onDestroy(unvalidatedText.addCallbackAndRun(() => setValues())) | 
					
						
							| 
									
										
										
										
											2023-12-12 03:46:51 +01:00
										 |  |  |   if (unit === undefined) { | 
					
						
							|  |  |  |     onDestroy( | 
					
						
							|  |  |  |       value.addCallbackAndRunD((fromUpstream) => { | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |         if (unvalidatedText.data !== fromUpstream && fromUpstream !== "") { | 
					
						
							|  |  |  |           unvalidatedText.setData(fromUpstream) | 
					
						
							| 
									
										
										
										
											2023-12-12 03:46:51 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-12-30 15:24:30 +01:00
										 |  |  |       }) | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |     ) | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     // Handled by the UnitInput | 
					
						
							| 
									
										
										
										
											2023-12-12 03:46:51 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-05-20 00:34:39 +02:00
										 |  |  |   onDestroy(selectedUnit.addCallback(() => setValues())) | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   if (validator === undefined) { | 
					
						
							|  |  |  |     throw ( | 
					
						
							|  |  |  |       "Not a valid type (no validator found) for type '" + | 
					
						
							|  |  |  |       type + | 
					
						
							|  |  |  |       "'; did you perhaps mean one of: " + | 
					
						
							|  |  |  |       Utils.sortedByLevenshteinDistance( | 
					
						
							|  |  |  |         type, | 
					
						
							|  |  |  |         Validators.AllValidators.map((v) => v.name), | 
					
						
							| 
									
										
										
										
											2023-12-30 15:24:30 +01:00
										 |  |  |         (v) => v | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |       ) | 
					
						
							|  |  |  |         .slice(0, 5) | 
					
						
							|  |  |  |         .join(", ") | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |   const isValid = unvalidatedText.map((v) => validator?.isValid(v, getCountry) ?? true) | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |   let htmlElem: HTMLInputElement | HTMLTextAreaElement | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-20 13:33:38 +01:00
										 |  |  |   let dispatch = createEventDispatcher<{ selected; submit }>() | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |   $: { | 
					
						
							|  |  |  |     if (htmlElem !== undefined) { | 
					
						
							| 
									
										
										
										
											2023-12-21 01:46:18 +01:00
										 |  |  |       htmlElem.onfocus = () => dispatch("selected") | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |       if (autofocus) { | 
					
						
							|  |  |  |         Utils.focusOn(htmlElem) | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  | </script> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 01:32:24 +02:00
										 |  |  | {#if validator?.textArea} | 
					
						
							| 
									
										
										
										
											2023-12-30 15:24:30 +01:00
										 |  |  |   <textarea | 
					
						
							|  |  |  |     class="w-full" | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |     bind:value={$unvalidatedText} | 
					
						
							| 
									
										
										
										
											2023-12-30 15:24:30 +01:00
										 |  |  |     inputmode={validator?.inputmode ?? "text"} | 
					
						
							|  |  |  |     placeholder={_placeholder} | 
					
						
							|  |  |  |     bind:this={htmlElem} | 
					
						
							| 
									
										
										
										
											2024-01-12 23:27:01 +01:00
										 |  |  |     on:keypress={onKeyPress} | 
					
						
							| 
									
										
										
										
											2023-12-30 15:24:30 +01:00
										 |  |  |   /> | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | {:else} | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |   <div class={twMerge("inline-flex", cls)}> | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     <input | 
					
						
							|  |  |  |       bind:this={htmlElem} | 
					
						
							| 
									
										
										
										
											2024-01-22 02:11:42 +01:00
										 |  |  |       bind:value={$unvalidatedText} | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |       class="w-full" | 
					
						
							|  |  |  |       inputmode={validator?.inputmode ?? "text"} | 
					
						
							|  |  |  |       placeholder={_placeholder} | 
					
						
							| 
									
										
										
										
											2024-01-12 23:27:01 +01:00
										 |  |  |       on:keypress={onKeyPress} | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     /> | 
					
						
							|  |  |  |     {#if !$isValid} | 
					
						
							|  |  |  |       <ExclamationIcon class="-ml-6 h-6 w-6" /> | 
					
						
							|  |  |  |     {/if} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     {#if unit !== undefined} | 
					
						
							| 
									
										
										
										
											2024-02-20 13:33:38 +01:00
										 |  |  |       <UnitInput | 
					
						
							|  |  |  |         {unit} | 
					
						
							|  |  |  |         {selectedUnit} | 
					
						
							|  |  |  |         textValue={unvalidatedText} | 
					
						
							|  |  |  |         upstreamValue={value} | 
					
						
							|  |  |  |         {getCountry} | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2023-11-09 16:30:26 +01:00
										 |  |  |     {/if} | 
					
						
							| 
									
										
										
										
											2023-12-26 22:30:27 +01:00
										 |  |  |   </div> | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  | {/if} |