diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 83b606cd..7c9f4e89 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -24,7 +24,7 @@ import animationIntersector from "../animationIntersector"; import { months } from "../../helpers/date"; import RichTextProcessor from "../../lib/richtextprocessor"; import mediaSizes from "../../helpers/mediaSizes"; -import { isAndroid, isApple, isSafari } from "../../helpers/userAgent"; +import { isAndroid, isApple, isSafari, isAppleMobile } from "../../helpers/userAgent"; import { langPack } from "../../lib/langPack"; import AvatarElement from "../avatar"; import { formatPhoneNumber } from "../misc"; @@ -2428,9 +2428,11 @@ export default class ChatBubbles { //this.scrollable.scrollTop = this.scrollable.scrollHeight; isTouchSupported && isApple && (this.scrollable.container.style.overflow = ''); - this.scrollable.container.style.display = 'none'; - void this.scrollable.container.offsetLeft; // reflow - this.scrollable.container.style.display = ''; + if(isSafari && !isAppleMobile) { // * fix blinking and jumping + this.scrollable.container.style.display = 'none'; + void this.scrollable.container.offsetLeft; // reflow + this.scrollable.container.style.display = ''; + } /* if(DEBUG) { this.log('performHistoryResult: have set up scrollTop:', newScrollTop, this.scrollable.scrollTop, this.isHeavyAnimationInProgress); @@ -2707,14 +2709,18 @@ export default class ChatBubbles { const promises = [topRes.animationPromise, middleRes.animationPromise, bottomRes.animationPromise]; const delays: number[] = [topRes.lastMsDelay, middleRes.lastMsDelay, bottomRes.lastMsDelay]; + let promise: Promise; if(topIds.length || middleIds.length || bottomIds.length) { - dispatchHeavyAnimationEvent(Promise.all(promises), Math.max(...delays) + 200); // * 200 - transition time + promise = Promise.all(promises); + dispatchHeavyAnimationEvent(promise, Math.max(...delays) + 200); // * 200 - transition time } - } - setTimeout(() => { - this.loadMoreHistory(true, true); - }, 0); + (promise || Promise.resolve()).then(() => { + setTimeout(() => { // preload messages + this.loadMoreHistory(reverse, true); + }, 0); + }); + } }); } diff --git a/src/index.hbs b/src/index.hbs index dc2f0b51..0f4dd5ab 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -39,25 +39,6 @@

Sign in to Telegram

Please confirm your country and
enter your phone number.

-
- {{!--
--}} -
- - - -
-
- - -
- - - {{!--
--}} - -
@@ -78,12 +59,7 @@

-
-
- - -
-
+
@@ -91,14 +67,7 @@

Enter Your Password

Your account is protected with
an additional password

-
-
- - - -
- -
+
diff --git a/src/lib/mtproto/authorizer.ts b/src/lib/mtproto/authorizer.ts index 80d5e545..57236bf7 100644 --- a/src/lib/mtproto/authorizer.ts +++ b/src/lib/mtproto/authorizer.ts @@ -10,6 +10,7 @@ import CryptoWorker from "../crypto/cryptoworker"; import { logger, LogLevels } from "../logger"; import { bytesCmp, bytesToHex, bytesFromHex, bytesXor } from "../../helpers/bytes"; +import { DEBUG } from "./mtproto_config"; //import { bigInt2str, greater, int2bigInt, one, powMod, str2bigInt, sub } from "../../vendor/leemon"; /* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse(); @@ -100,10 +101,14 @@ export class Authorizer { transport: transport }; - this.log('mtpSendPlainRequest: creating requestPromise'); + if(DEBUG) { + this.log('mtpSendPlainRequest: creating requestPromise'); + } return transport.send(resultArray).then(result => { - this.log('mtpSendPlainRequest: in good sector', result); + if(DEBUG) { + this.log('mtpSendPlainRequest: in good sector', result); + } if(!result || !result.byteLength) { return Promise.reject(baseError); @@ -148,7 +153,9 @@ export class Authorizer { // need rsaKeysManager.prepare().then(() => {}); - this.log('Send req_pq', auth.nonce.hex); + if(DEBUG) { + this.log('Send req_pq', auth.nonce.hex); + } try { var deserializer = await this.mtpSendPlainRequest(auth.dcId, request.getBytes(true)); } catch(error) { @@ -172,7 +179,9 @@ export class Authorizer { auth.pq = response.pq; auth.fingerprints = response.server_public_key_fingerprints; - this.log('Got ResPQ', bytesToHex(auth.serverNonce), bytesToHex(auth.pq), auth.fingerprints); + if(DEBUG) { + this.log('Got ResPQ', bytesToHex(auth.serverNonce), bytesToHex(auth.pq), auth.fingerprints); + } let publicKey = await rsaKeysManager.select(auth.fingerprints); if(!publicKey) { @@ -181,7 +190,9 @@ export class Authorizer { auth.publicKey = publicKey; - this.log('PQ factorization start', auth.pq); + if(DEBUG) { + this.log('PQ factorization start', auth.pq); + } try { var pAndQ = await CryptoWorker.factorize(auth.pq); @@ -193,7 +204,9 @@ export class Authorizer { auth.p = pAndQ[0]; auth.q = pAndQ[1]; - this.log('PQ factorization done', pAndQ); + if(DEBUG) { + this.log('PQ factorization done', pAndQ); + } /* let p = new Uint32Array(new Uint8Array(auth.p).buffer)[0]; let q = new Uint32Array(new Uint8Array(auth.q).buffer)[0]; console.log(dT(), 'PQ factorization done', pAndQ, p.toString(16), q.toString(16)); */ @@ -258,18 +271,22 @@ export class Authorizer { let requestBytes = request.getBytes(true); - this.log('Send req_DH_params', req_DH_params/* , requestBytes.hex */); + if(DEBUG) { + this.log('Send req_DH_params', req_DH_params/* , requestBytes.hex */); + } try { var deserializer = await this.mtpSendPlainRequest(auth.dcId, requestBytes); } catch(error) { - this.log('Send req_DH_params FAIL!', error); + this.log.error('Send req_DH_params FAIL!', error); throw error; } var response = deserializer.fetchObject('Server_DH_Params', 'RESPONSE'); - this.log('Sent req_DH_params, response:', response); + if(DEBUG) { + this.log('Sent req_DH_params, response:', response); + } if(response._ != 'server_DH_params_fail' && response._ != 'server_DH_params_ok') { throw new Error('[MT] Server_DH_Params response invalid: ' + response._); @@ -347,7 +364,9 @@ export class Authorizer { throw new Error('[MT] server_DH_inner_data serverNonce mismatch'); } - this.log('Done decrypting answer'); + if(DEBUG) { + this.log('Done decrypting answer'); + } auth.g = response.g; auth.dhPrime = response.dh_prime; auth.gA = response.g_a; @@ -367,13 +386,19 @@ export class Authorizer { } public mtpVerifyDhParams(g: number, dhPrime: Uint8Array, gA: Uint8Array) { - this.log('Verifying DH params', g, dhPrime, gA); + if(DEBUG) { + this.log('Verifying DH params', g, dhPrime, gA); + } + var dhPrimeHex = bytesToHex(dhPrime); if(g != 3 || dhPrimeHex !== 'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b') { // The verified value is from https://core.telegram.org/mtproto/security_guidelines throw new Error('[MT] DH params are not verified: unknown dhPrime'); } - this.log('dhPrime cmp OK'); + + if(DEBUG) { + this.log('dhPrime cmp OK'); + } var gABigInt = new BigInteger(bytesToHex(gA), 16); //const _gABigInt = str2bigInt(bytesToHex(gA), 16); @@ -392,7 +417,10 @@ export class Authorizer { //if(greater(gABigInt, sub(_dhPrimeBigInt, one))) { throw new Error('[MT] DH params are not verified: gA >= dhPrime - 1'); } - this.log('1 < gA < dhPrime-1 OK'); + + if(DEBUG) { + this.log('1 < gA < dhPrime-1 OK'); + } var two = new BigInteger(/* null */''); @@ -410,7 +438,10 @@ export class Authorizer { if(gABigInt.compareTo(dhPrimeBigInt.subtract(twoPow)) >= 0) { throw new Error('[MT] DH params are not verified: gA > dhPrime - 2^{2048-64}'); } - this.log('2^{2048-64} < gA < dhPrime-2^{2048-64} OK'); + + if(DEBUG) { + this.log('2^{2048-64} < gA < dhPrime-2^{2048-64} OK'); + } return true; } @@ -450,7 +481,9 @@ export class Authorizer { encrypted_data: encryptedData }); - this.log('Send set_client_DH_params'); + if(DEBUG) { + this.log('Send set_client_DH_params'); + } try { var deserializer = await this.mtpSendPlainRequest(auth.dcId, request.getBytes(true)); @@ -483,7 +516,9 @@ export class Authorizer { authKeyAux = authKeyHash.slice(0, 8), authKeyId = authKeyHash.slice(-8); - this.log('Got Set_client_DH_params_answer', response._, authKey); + if(DEBUG) { + this.log('Got Set_client_DH_params_answer', response._, authKey); + } switch(response._) { case 'dh_gen_ok': var newNonceHash1 = (await CryptoWorker.sha1Hash(auth.newNonce.concat([1], authKeyAux))).slice(-16); @@ -494,7 +529,9 @@ export class Authorizer { } var serverSalt = bytesXor(auth.newNonce.slice(0, 8), auth.serverNonce.slice(0, 8)); - this.log('Auth successfull!', authKeyId, authKey, serverSalt); + if(DEBUG) { + this.log('Auth successfull!', authKeyId, authKey, serverSalt); + } auth.authKeyId = authKeyId; auth.authKey = authKey; diff --git a/src/lib/mtproto/mtproto.worker.ts b/src/lib/mtproto/mtproto.worker.ts index da42b587..6bafe356 100644 --- a/src/lib/mtproto/mtproto.worker.ts +++ b/src/lib/mtproto/mtproto.worker.ts @@ -109,7 +109,7 @@ ctx.addEventListener('message', async(e) => { case 'gzipUncompress': // @ts-ignore return cryptoWorker[task.task].apply(cryptoWorker, task.args).then(result => { - respond({taskId: taskId, result: result}); + respond({taskId, result}); }); case 'setQueueId': @@ -127,16 +127,18 @@ ctx.addEventListener('message', async(e) => { result = await result; } - respond({taskId: taskId, result: result}); - } catch(err) { - respond({taskId: taskId, error: err}); + respond({taskId, result}); + } catch(error) { + respond({taskId, error}); } } case 'getNetworker': { // @ts-ignore - apiManager[task.task].apply(apiManager, task.args); - respond({taskId: taskId, result: null}); + apiManager[task.task].apply(apiManager, task.args).finally(() => { + respond({taskId, result: null}); + }); + break; } @@ -149,9 +151,9 @@ ctx.addEventListener('message', async(e) => { result = await result; } - respond({taskId: taskId, result: result}); - } catch(err) { - respond({taskId: taskId, error: err}); + respond({taskId, result}); + } catch(error) { + respond({taskId, error}); } //throw new Error('Unknown task: ' + task.task); diff --git a/src/pages/pageAuthCode.ts b/src/pages/pageAuthCode.ts index a257b874..2a428563 100644 --- a/src/pages/pageAuthCode.ts +++ b/src/pages/pageAuthCode.ts @@ -11,6 +11,7 @@ import pageIm from './pageIm'; import pagePassword from './pagePassword'; import pageSignIn from './pageSignIn'; import pageSignUp from './pageSignUp'; +import InputField from '../components/inputField'; let authCode: AuthSentCode.authSentCode = null; @@ -28,7 +29,19 @@ let onFirstMount = (): Promise => { const CODELENGTH = (authCode.type as AuthSentCodeType.authSentCodeTypeApp).length; - codeInput = page.pageEl.querySelector('#code') as HTMLInputElement; + const codeInputField = new InputField({ + label: 'Code', + name: 'code', + plainText: true + }); + + codeInput = codeInputField.input as HTMLInputElement; + codeInput.type = 'tel'; + codeInput.setAttribute('required', ''); + codeInput.autocomplete = 'off'; + + page.pageEl.querySelector('.input-wrapper').append(codeInputField.container); + const codeInputLabel = codeInput.nextElementSibling as HTMLLabelElement; const editButton = page.pageEl.querySelector('.phone-edit') as HTMLElement; diff --git a/src/pages/pagePassword.ts b/src/pages/pagePassword.ts index 647c99ed..9651d447 100644 --- a/src/pages/pagePassword.ts +++ b/src/pages/pagePassword.ts @@ -12,6 +12,8 @@ import passwordManager from '../lib/mtproto/passwordManager'; import { cancelEvent } from '../helpers/dom'; import Page from './page'; import pageIm from './pageIm'; +import InputField from '../components/inputField'; +import Button from '../components/button'; const TEST = false; let passwordInput: HTMLInputElement; @@ -22,10 +24,28 @@ let onFirstMount = (): Promise => { let passwordVisible = false; - const btnNext = page.pageEl.querySelector('button') as HTMLButtonElement; - passwordInput = document.getElementById('password') as HTMLInputElement; + const btnNext = Button('btn-primary', {text: 'NEXT'}); + + const passwordInputField = new InputField({ + label: 'Password', + name: 'password', + plainText: true + }); + + passwordInput = passwordInputField.input as HTMLInputElement; + passwordInput.type = 'password'; + passwordInput.setAttribute('required', ''); + passwordInput.autocomplete = 'off'; + const passwordInputLabel = passwordInput.nextElementSibling as HTMLLabelElement; - const toggleVisible = page.pageEl.querySelector('.toggle-visible') as HTMLSpanElement; + + const toggleVisible = document.createElement('span'); + toggleVisible.classList.add('toggle-visible', 'tgico'); + + passwordInputField.container.append(toggleVisible); + + page.pageEl.querySelector('.input-wrapper').append(passwordInputField.container, btnNext); + let getStateInterval: number; let getState = () => { diff --git a/src/pages/pageSignIn.ts b/src/pages/pageSignIn.ts index ce880d37..dfceae52 100644 --- a/src/pages/pageSignIn.ts +++ b/src/pages/pageSignIn.ts @@ -9,6 +9,10 @@ import { findUpTag } from "../helpers/dom"; import Page from "./page"; import pageAuthCode from "./pageAuthCode"; import pageSignQR from './pageSignQR'; +import InputField from "../components/inputField"; +import CheckboxField from "../components/checkbox"; +import Button from "../components/button"; +import { isAppleMobile } from "../helpers/userAgent"; type Country = _Country & { li?: HTMLLIElement[] @@ -35,23 +39,33 @@ let onFirstMount = () => { let lastCountrySelected: Country = null; - var selectCountryCode = page.pageEl.querySelector('input[name="countryCode"]')! as HTMLInputElement; - var parent = selectCountryCode.parentElement; + const inputWrapper = document.createElement('div'); + inputWrapper.classList.add('input-wrapper'); - var wrapper = document.createElement('div'); - wrapper.classList.add('select-wrapper', 'z-depth-3', 'hide'); + const countryInputField = new InputField({ + label: 'Country', + name: 'countryCode', + plainText: true + }); - var list = document.createElement('ul'); - wrapper.appendChild(list); + countryInputField.container.classList.add('input-select'); - //let wrapperScroll = OverlayScrollbars(wrapper, (window as any).scrollbarOptions); - let scroll = new Scrollable(wrapper); + const countryInput = countryInputField.input as HTMLInputElement; + countryInput.autocomplete = 'rrRandomRR'; - let initedSelect = false; + const selectWrapper = document.createElement('div'); + selectWrapper.classList.add('select-wrapper', 'z-depth-3', 'hide'); - page.pageEl.querySelector('.a-qr').addEventListener('click', () => { - pageSignQR.mount(); - }); + const arrowDown = document.createElement('span'); + arrowDown.classList.add('arrow', 'arrow-down'); + countryInputField.container.append(arrowDown); + + const selectList = document.createElement('ul'); + selectWrapper.appendChild(selectList); + + const scroll = new Scrollable(selectWrapper); + + let initedSelect = false; let initSelect = () => { initSelect = null; @@ -88,20 +102,20 @@ let onFirstMount = () => { li.appendChild(span); liArr.push(li); - list.append(li); + selectList.append(li); }); c.li = liArr; }); - list.addEventListener('mousedown', function(e) { + selectList.addEventListener('mousedown', function(e) { let target = e.target as HTMLElement; if(target.tagName != 'LI') target = findUpTag(target, 'LI'); let countryName = target.childNodes[1].textContent;//target.innerText.split('\n').shift(); let phoneCode = target.querySelector('.phone-code').innerText; - selectCountryCode.value = countryName; + countryInput.value = countryName; lastCountrySelected = countries.find(c => c.name == countryName); telEl.value = lastValue = phoneCode; @@ -109,14 +123,14 @@ let onFirstMount = () => { //console.log('clicked', e, countryName, phoneCode); }); - parent.appendChild(wrapper); + countryInputField.container.appendChild(selectWrapper); }; initSelect(); let hideTimeout: number; - selectCountryCode.addEventListener('focus', function(this: typeof selectCountryCode, e) { + countryInput.addEventListener('focus', function(this: typeof countryInput, e) { if(initSelect) { initSelect(); } else { @@ -127,20 +141,20 @@ let onFirstMount = () => { clearTimeout(hideTimeout); - wrapper.classList.remove('hide'); - void wrapper.offsetWidth; // reflow - wrapper.classList.add('active'); + selectWrapper.classList.remove('hide'); + void selectWrapper.offsetWidth; // reflow + selectWrapper.classList.add('active'); }); - selectCountryCode.addEventListener('blur', function(this: typeof selectCountryCode, e) { - wrapper.classList.remove('active'); + countryInput.addEventListener('blur', function(this: typeof countryInput, e) { + selectWrapper.classList.remove('active'); hideTimeout = window.setTimeout(() => { - wrapper.classList.add('hide'); + selectWrapper.classList.add('hide'); }, 200); e.cancelBubble = true; }, {capture: true}); - selectCountryCode.addEventListener('keyup', function(this: typeof selectCountryCode, e) { + countryInput.addEventListener('keyup', function(this: typeof countryInput, e) { if(e.ctrlKey || e.key == 'Control') return false; //let i = new RegExp('^' + this.value, 'i'); @@ -169,24 +183,31 @@ let onFirstMount = () => { } }); - let arrowDown = page.pageEl.querySelector('.arrow-down') as HTMLSpanElement; arrowDown.addEventListener('mousedown', function(this: typeof arrowDown, e) { e.cancelBubble = true; e.preventDefault(); - if(selectCountryCode.matches(':focus')) selectCountryCode.blur(); - else selectCountryCode.focus(); + if(countryInput.matches(':focus')) countryInput.blur(); + else countryInput.focus(); }); let pasted = false; let lastValue = ''; - let telEl = page.pageEl.querySelector('input[name="phone"]') as HTMLInputElement; + + const telInputField = new InputField({ + label: 'Phone Number', + plainText: true, + name: 'phone' + }); + let telEl = telInputField.input as HTMLInputElement; + telEl.type = 'tel'; + telEl.autocomplete = 'rr55RandomRR55'; const telLabel = telEl.nextElementSibling as HTMLLabelElement; telEl.addEventListener('input', function(this: typeof telEl, e) { //console.log('input', this.value); this.classList.remove('error'); const diff = Math.abs(this.value.length - lastValue.length); - if(diff > 1 && !pasted) { + if(diff > 1 && !pasted && isAppleMobile) { this.value = lastValue + this.value; } @@ -200,8 +221,8 @@ let onFirstMount = () => { //console.log(formatted, country); let countryName = country ? country.name : ''/* 'Unknown' */; - if(countryName != selectCountryCode.value && (!lastCountrySelected || !country || lastCountrySelected.phoneCode != country.phoneCode)) { - selectCountryCode.value = countryName; + if(countryName != countryInput.value && (!lastCountrySelected || !country || lastCountrySelected.phoneCode != country.phoneCode)) { + countryInput.value = countryName; lastCountrySelected = country; } @@ -226,7 +247,7 @@ let onFirstMount = () => { //console.log('keypress', this.value); if(!btnNext.style.visibility &&/* this.value.length >= 9 && */ e.key == 'Enter') { return btnNext.click(); - } else if(/\D/.test(e.key)) { + } else if(/\D/.test(e.key) && !(e.metaKey || e.ctrlKey)) { e.preventDefault(); return false; } @@ -236,8 +257,11 @@ let onFirstMount = () => { this.removeAttribute('readonly'); // fix autocomplete });*/ - /* authorizer.auth(2); - networkerFactory.startAll(); */ + const signedCheckboxField = CheckboxField('Keep me signed in', 'keepSession'); + signedCheckboxField.input.checked = true; + + btnNext = Button('btn-primary', {text: 'NEXT'}); + btnNext.style.visibility = 'hidden'; btnNext.addEventListener('click', function(this: HTMLElement, e) { this.setAttribute('disabled', 'true'); @@ -277,33 +301,56 @@ let onFirstMount = () => { } }); }); + + const qrDiv = document.createElement('div'); + qrDiv.classList.add('qr'); + + const qrLink = document.createElement('a'); + qrLink.href = '#'; + qrLink.classList.add('a-qr'); + qrLink.innerText = 'Quick log in using QR code'; + + qrDiv.append(qrLink); + + qrLink.addEventListener('click', () => { + pageSignQR.mount(); + }); + + inputWrapper.append(countryInputField.container, telInputField.container, signedCheckboxField.label, btnNext, qrDiv); + + page.pageEl.querySelector('.container').append(inputWrapper); let tryAgain = () => { apiManager.invokeApi('help.getNearestDc').then((nearestDcResult) => { const dcs = [1, 2, 3, 4, 5]; - //dcs.splice(nearestDcResult.this_dc - 1, 1); + const done: number[] = [nearestDcResult.this_dc]; let promise: Promise; if(nearestDcResult.nearest_dc != nearestDcResult.this_dc) { - //MTProto.apiManager.baseDcID = nearestDcResult.nearest_dc; - promise = apiManager.getNetworker(nearestDcResult.nearest_dc).then(() => { - + done.push(nearestDcResult.nearest_dc) }); } (promise || Promise.resolve()).then(() => { - dcs.forEach(dcId => { - apiManager.getNetworker(dcId, {fileDownload: true}); - }); + const g = () => { + const dcId = dcs.shift(); + if(!dcId) return; + + setTimeout(() => { // * если одновременно запросить все нетворкеры, не будет проходить запрос на код + apiManager.getNetworker(dcId, {fileDownload: true}).finally(g); + }, done.includes(dcId) ? 0 : 3000); + }; + + g(); }); return nearestDcResult; }).then((nearestDcResult) => { - let country = countries.find((c) => c.code == nearestDcResult.country); + let country = countries.find((c) => c.code === nearestDcResult.country); if(country) { - if(!selectCountryCode.value.length && !telEl.value.length) { - selectCountryCode.value = country.name; + if(!countryInput.value.length && !telEl.value.length) { + countryInput.value = country.name; lastCountrySelected = country; telEl.value = lastValue = '+' + country.phoneCode.split(' and ').shift(); } @@ -317,12 +364,10 @@ let onFirstMount = () => { }; const page = new Page('page-sign', true, onFirstMount, () => { - if(!btnNext) { - btnNext = page.pageEl.querySelector('button'); + if(btnNext) { + btnNext.textContent = 'NEXT'; + btnNext.removeAttribute('disabled'); } - - btnNext.textContent = 'NEXT'; - btnNext.removeAttribute('disabled'); appStateManager.pushToState('authState', {_: 'authStateSignIn'}); appStateManager.saveState(); diff --git a/src/scss/partials/_input.scss b/src/scss/partials/_input.scss index 52715b30..db28f482 100644 --- a/src/scss/partials/_input.scss +++ b/src/scss/partials/_input.scss @@ -4,7 +4,12 @@ } .input-field { + --height: 54px; position: relative; + + @include respond-to(handhelds) { + --height: 50px; + } .arrow-down { position: absolute; @@ -35,24 +40,24 @@ label { position: absolute; color: $placeholder-color; + top: 0; left: 1rem; right: auto; z-index: 2; - top: 50%; height: 1.5rem; - transform: translateY(-50%); + transform: translate(0, 0); background-color: #fff; - transition: .2s transform, .2s padding, .1s opacity, .2s top, .2s left; + transition: .2s transform, .2s padding, .1s opacity; transform-origin: left center; pointer-events: none; + margin-top: calc((var(--height) - 1.5rem) / 2); // * Center of first line body.animation-level-0 & { transition: none; } } - input, &-input { - --height: 54px; + &-input { --padding: 1rem; --border-width: 1px; --border-width-top: 2px; @@ -80,9 +85,6 @@ } } - @include respond-to(handhelds) { - --height: 50px; - } /* font-weight: 500; */ /* &:hover { @@ -133,13 +135,9 @@ } &:focus ~ label, &:valid ~ label, &:not(:empty) ~ label, &:disabled ~ label { - //transform: translate(-.3125rem, -2.375rem) scale(.75); - //transform: translate(-.3125rem, -50%) scale(.75); - transform: translateY(-50%) scale(.75); - top: 1px; + transform: translate(-.25rem, calc(var(--height) / -2 + .125rem)) scale(.75); padding: 0 6px; opacity: 1; - left: .75rem; font-weight: 500; } }