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