diff --git a/frontend/code/components/componentBase.ts b/frontend/code/components/componentBase.ts index 693c4fbe..626a4f20 100644 --- a/frontend/code/components/componentBase.ts +++ b/frontend/code/components/componentBase.ts @@ -159,7 +159,7 @@ export abstract class ComponentBase { this.innerAlignElement!.classList.add('stretch-child-x'); } else { this.innerAlignElement!.style.left = `${align[0] * 100}%`; - this.innerAlignElement!.style.width = 'min-content'; + this.innerAlignElement!.style.width = 'max-content'; this.innerAlignElement!.classList.remove('stretch-child-x'); transform += `translateX(-${align[0] * 100}%) `; } @@ -170,7 +170,7 @@ export abstract class ComponentBase { this.innerAlignElement!.classList.add('stretch-child-y'); } else { this.innerAlignElement!.style.top = `${align[1] * 100}%`; - this.innerAlignElement!.style.height = 'min-content'; + this.innerAlignElement!.style.height = 'max-content'; this.innerAlignElement!.classList.remove('stretch-child-y'); transform += `translateY(-${align[1] * 100}%) `; } diff --git a/frontend/code/components/dropdown.ts b/frontend/code/components/dropdown.ts index 0c1c9b3d..54554ee1 100644 --- a/frontend/code/components/dropdown.ts +++ b/frontend/code/components/dropdown.ts @@ -389,7 +389,11 @@ export class DropdownComponent extends ComponentBase { this._highlightOption(match); }); - match.addEventListener('click', (event) => { + // With a `click` handler, the element loses focus for a + // little while, which is noticeable because the floating label will + // quickly move down and then back up. To avoid this, we use + // `mousedown` instead. + match.addEventListener('mousedown', (event) => { this.submitInput(optionName); markEventAsHandled(event); }); diff --git a/frontend/code/inputBox.ts b/frontend/code/inputBox.ts index 19df54ad..70e17a25 100644 --- a/frontend/code/inputBox.ts +++ b/frontend/code/inputBox.ts @@ -1,16 +1,14 @@ +import { markEventAsHandled } from './eventHandling'; + /// A text input field providing the following features and more: /// /// - A floating label /// - prefix text - -import { markEventAsHandled } from './eventHandling'; - /// - suffix text export class InputBox { private element: HTMLElement; private prefixTextElement: HTMLElement; - private columnElement: HTMLElement; private suffixElementContainer: HTMLElement; private suffixTextElement: HTMLElement; @@ -21,55 +19,43 @@ export class InputBox { constructor(parentElement: Element) { this.element = document.createElement('div'); this.element.classList.add('rio-input-box'); - - // Children of `this.element` - this.prefixTextElement = document.createElement('div'); - this.prefixTextElement.classList.add( - 'rio-input-box-hint-text', - 'rio-input-box-prefix-text' - ); - this.element.appendChild(this.prefixTextElement); - - this.columnElement = document.createElement('div'); - this.columnElement.classList.add('rio-input-box-column'); - this.element.appendChild(this.columnElement); - - this.suffixElementContainer = document.createElement('div'); - this.suffixElementContainer.classList.add('rio-single-container'); - this.element.appendChild(this.suffixElementContainer); - - this.suffixTextElement = document.createElement('div'); - this.suffixTextElement.classList.add( - 'rio-input-box-hint-text', - 'rio-input-box-suffix-text' - ); - this.element.appendChild(this.suffixTextElement); - - let plainBar = document.createElement('div'); - plainBar.classList.add('rio-input-box-plain-bar'); - this.element.appendChild(plainBar); - - let colorBar = document.createElement('div'); - colorBar.classList.add('rio-input-box-color-bar'); - this.element.appendChild(colorBar); - - // Children of `this.columnElement` - this.labelWidthReserverElement = document.createElement('div'); - this.labelWidthReserverElement.classList.add( - 'rio-input-box-label-width-reserver' - ); - this.columnElement.appendChild(this.labelWidthReserverElement); - - this.labelElement = document.createElement('div'); - this.labelElement.classList.add('rio-input-box-label'); - this.columnElement.appendChild(this.labelElement); - - this._inputElement = document.createElement('input'); - this._inputElement.type = 'text'; - this.columnElement.appendChild(this._inputElement); - parentElement.appendChild(this.element); + this.element.innerHTML = ` +
+
+
+
+ +
+
+
+
+
+
+
+ `; + + this.prefixTextElement = this.element.querySelector( + '.rio-input-box-prefix-text' + ) as HTMLElement; + this.suffixElementContainer = this.element.querySelector( + '.rio-input-box-suffix-element > *' + ) as HTMLElement; + this.suffixTextElement = this.element.querySelector( + '.rio-input-box-suffix-text' + ) as HTMLElement; + + this.labelWidthReserverElement = this.element.querySelector( + '.rio-input-box-label-width-reserver' + ) as HTMLElement; + this.labelElement = this.element.querySelector( + '.rio-input-box-label' + ) as HTMLElement; + this._inputElement = this.element.querySelector( + 'input' + ) as HTMLInputElement; + // Detect clicks on any part of the component and focus the input // // The `mousedown` are needed to prevent any potential drag events from @@ -86,20 +72,24 @@ export class InputBox { // The `click` events pass focus to the input and move the cursor. // This has to be done in `mouseup`, rather than `mousedown`, because // otherwise the browser removes the focus again on mouseup. - this.prefixTextElement.addEventListener('click', (event) => { + let selectStart = (event: Event) => { this._inputElement.focus(); this._inputElement.setSelectionRange(0, 0); markEventAsHandled(event); - }); + }; + this.prefixTextElement.addEventListener('click', selectStart); - this.suffixTextElement.addEventListener('click', (event) => { + let selectEnd = (event: Event) => { this._inputElement.focus(); this._inputElement.setSelectionRange( this._inputElement.value.length, this._inputElement.value.length ); markEventAsHandled(event); - }); + }; + + this.suffixElementContainer.addEventListener('click', selectEnd); + this.suffixTextElement.addEventListener('click', selectEnd); // Override mousedown and eat the event so other components don't get it this._inputElement.addEventListener('mousedown', (event) => { diff --git a/frontend/css/style.scss b/frontend/css/style.scss index 833d3620..1a427561 100644 --- a/frontend/css/style.scss +++ b/frontend/css/style.scss @@ -706,6 +706,15 @@ $rio-input-box-text-distance-from-bottom: 0.4rem; // To be aligned with the * { + height: $rio-input-box-height; + } +} + .rio-input-box-plain-bar, .rio-input-box-color-bar { position: absolute; @@ -822,10 +831,21 @@ $rio-input-box-text-distance-from-bottom: 0.4rem; // To be aligned with the input { + input { cursor: pointer; } } + + // InputBoxes have a bunch of elements that respond to clicks, which is a + // problem because the dropdown closes when its input element loses focus. + // Disable mouse events for those elements. + & > .rio-input-box { + pointer-events: none; + + input { + pointer-events: auto; + } + } } .rio-dropdown-popup {