From a97d0cfe4933e7136a47a232cb5f9e4341aae807 Mon Sep 17 00:00:00 2001
From: Benedikt Seeger <benedikt.seeger@ptb.de>
Date: Wed, 26 Feb 2025 12:47:18 +0100
Subject: [PATCH] improved sugestions

---
 dsiUnits-js/src/dsiUnitInput.js | 48 ++++++++++++++++++++-------------
 1 file changed, 29 insertions(+), 19 deletions(-)

diff --git a/dsiUnits-js/src/dsiUnitInput.js b/dsiUnits-js/src/dsiUnitInput.js
index 200f37b..2e6a116 100644
--- a/dsiUnits-js/src/dsiUnitInput.js
+++ b/dsiUnits-js/src/dsiUnitInput.js
@@ -14,11 +14,12 @@ export class DSIUnitInput extends HTMLElement {
     constructor() {
         super();
         this.attachShadow({ mode: "open" });
-        // Internal state: store raw value.
+        // Internal state.
         this._rawValue = "";
         this.liveUpdate = true;
         this.allowedTokens = defaultAllowedTokens;
         this.selectedSuggestionIndex = -1;
+        this.lastAcceptedToken = ""; // NEW: track last accepted token
 
         // Create elements.
         this.input = document.createElement("input");
@@ -29,18 +30,18 @@ export class DSIUnitInput extends HTMLElement {
         this.suggestions.className = "autocomplete-list";
         this.suggestions.style.display = "none";
 
-        this.display = document.createElement("div"); // For rendered output on blur.
+        this.display = document.createElement("div"); // Rendered output on blur.
         this.display.id = "dsiDisplay";
-        this.display.style.cursor = "text"; // Make it clickable for editing.
+        this.display.style.cursor = "text";
 
-        // Container for input and suggestions.
+        // Container.
         const container = document.createElement("div");
         container.style.position = "relative";
         container.appendChild(this.input);
         container.appendChild(this.suggestions);
         container.appendChild(this.display);
 
-        // Append styles.
+        // Styles.
         const style = document.createElement("style");
         style.textContent = `
       #dsiInput {
@@ -76,7 +77,7 @@ export class DSIUnitInput extends HTMLElement {
     `;
         this.shadowRoot.append(style, container);
 
-        // Bind event handlers.
+        // Bind handlers.
         this.onInput = this.onInput.bind(this);
         this.onKeyDown = this.onKeyDown.bind(this);
         this.onBlur = this.onBlur.bind(this);
@@ -117,19 +118,22 @@ export class DSIUnitInput extends HTMLElement {
         this.display.removeEventListener("click", this.onFocus);
     }
 
-    // Determine contextual suggestions based on previous complete token.
+    // Determines contextual suggestions.
     getContextualSuggestions() {
         const value = this.input.value;
         const tokens = value.split("\\").filter(Boolean);
+        // If there are no complete tokens, allow all suggestions.
         if (tokens.length <= 1) {
             return this.allowedTokens;
         }
+        // Use last accepted token if available.
+        if (this.lastAcceptedToken && (this.lastAcceptedToken in dsiPrefixesHTML)) {
+            return Object.keys(dsiUnitsHTML);
+        }
         const prevToken = tokens[tokens.length - 2];
         if (prevToken in dsiPrefixesHTML) {
-            // After a prefix, suggest only allowed units.
             return Object.keys(dsiUnitsHTML);
         } else if (prevToken in dsiUnitsHTML) {
-            // After a unit, suggest all allowed tokens.
             if (value.includes("\\per")) {
                 return this.allowedTokens.filter(token => token !== "per");
             }
@@ -143,7 +147,7 @@ export class DSIUnitInput extends HTMLElement {
 
     onInput(e) {
         this._rawValue = this.input.value;
-        // Auto-insert a backslash if the user types a space after a complete token.
+        // Auto-insert a backslash after a token if a space is typed.
         if (this.input.value.endsWith(" ")) {
             const trimmed = this.input.value.trimEnd();
             if (!trimmed.endsWith("\\")) {
@@ -161,7 +165,7 @@ export class DSIUnitInput extends HTMLElement {
     updateSuggestions() {
         const cursorPos = this.input.selectionStart;
         const value = this.input.value;
-        // Do not show suggestions if caret is inside the braces of a tothe token.
+        // Suppress suggestions if caret is inside tothe braces.
         const regexTothe = /\\tothe\{([^}]*)$/;
         const substring = value.substring(0, cursorPos);
         const matchTothe = substring.match(regexTothe);
@@ -170,16 +174,23 @@ export class DSIUnitInput extends HTMLElement {
             this.suggestions.style.display = "none";
             return;
         }
-        // Capture the current token (which may be empty).
+        // Capture current token (which may be empty).
         const regex = /\\([a-zA-Z]*)$/;
         const match = substring.match(regex);
         let currentToken = "";
         if (match) {
             currentToken = match[1];
         }
-        // If currentToken exactly matches a known prefix, treat it as complete.
+        // If current token exactly matches a known prefix, treat it as complete.
         if (currentToken && (currentToken in dsiPrefixesHTML)) {
-            console.log("Token complete: currentToken matches known prefix:", currentToken);
+            console.log("Token complete: using accepted prefix", currentToken);
+            // Update lastAcceptedToken.
+            this.lastAcceptedToken = currentToken;
+            // Auto-complete by appending a trailing backslash if not already present.
+            if (!value.endsWith("\\")) {
+                this.input.value = value + "\\";
+                this._rawValue = this.input.value;
+            }
             currentToken = "";
         }
         const tokens = value.split("\\").filter(Boolean);
@@ -206,7 +217,6 @@ export class DSIUnitInput extends HTMLElement {
         }
         list.forEach((token, index) => {
             const item = document.createElement("div");
-            // Always render suggestions with a backslash.
             item.textContent = "\\" + token;
             item.className = "autocomplete-item";
             if (index === this.selectedSuggestionIndex) {
@@ -240,7 +250,7 @@ export class DSIUnitInput extends HTMLElement {
                 if (this.selectedSuggestionIndex !== -1) {
                     e.preventDefault();
                     const selectedItem = items[this.selectedSuggestionIndex];
-                    const token = selectedItem.textContent.slice(1); // remove backslash
+                    const token = selectedItem.textContent.slice(1); // remove leading backslash
                     const tokenStartIndex = this.input.value.lastIndexOf("\\");
                     this.acceptSuggestion(token, tokenStartIndex);
                 }
@@ -264,14 +274,15 @@ export class DSIUnitInput extends HTMLElement {
         const before = value.substring(0, tokenStartIndex);
         const after = value.substring(this.input.selectionStart);
         if (token === "tothe") {
-            // Append {} and place caret inside.
             this.input.value = before + "\\" + token + "{}" + after;
             this._rawValue = this.input.value;
+            this.lastAcceptedToken = token;
             const newPos = before.length + token.length + 2;
             this.input.setSelectionRange(newPos, newPos);
         } else {
             this.input.value = before + "\\" + token + after;
             this._rawValue = this.input.value;
+            this.lastAcceptedToken = token;
         }
         this.suggestions.style.display = "none";
         this.renderOutput();
@@ -279,9 +290,8 @@ export class DSIUnitInput extends HTMLElement {
 
     renderOutput() {
         try {
-            // No special conversion for "per" is needed now.
             const unit = new DSIUnit(this.input.value);
-            // Pass oneLine option if desired.
+            // Call DSIUnit.toHTML with oneLine option.
             this.display.innerHTML = unit.toHTML({ oneLine: true });
             if (unit.warnings && unit.warnings.length > 0) {
                 this.display.title = unit.warnings.join("; ");
-- 
GitLab