Added nice selection

added styles in cell
added editor
This commit is contained in:
Eugene 2023-07-21 16:26:01 +03:00
parent b4aa4a4b67
commit 9ef465b72d
10 changed files with 164 additions and 61 deletions

View File

@ -10,14 +10,13 @@
"preview": "vite preview",
"predeploy": "npm run dist",
"deploy": "gh-pages -d dist"
},
"devDependencies": {
"gh-pages": "^5.0.0",
"typescript": "^5.0.2",
"vite": "^4.4.0"
},
"dependencies": {
"gh-pages": "^5.0.0",
"sass": "^1.63.6"
}
}

View File

@ -5,14 +5,14 @@ settings:
excludeLinksFromLockfile: false
dependencies:
gh-pages:
specifier: ^5.0.0
version: 5.0.0
sass:
specifier: ^1.63.6
version: 1.63.6
devDependencies:
gh-pages:
specifier: ^5.0.0
version: 5.0.0
typescript:
specifier: ^5.0.2
version: 5.0.2
@ -232,20 +232,20 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
array-uniq: 1.0.3
dev: false
dev: true
/array-uniq@1.0.3:
resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==}
engines: {node: '>=0.10.0'}
dev: false
dev: true
/async@3.2.4:
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
dev: false
dev: true
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: false
dev: true
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
@ -256,7 +256,7 @@ packages:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: false
dev: true
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
@ -280,19 +280,19 @@ packages:
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: false
dev: true
/commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
dev: false
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: false
dev: true
/email-addresses@5.0.0:
resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==}
dev: false
dev: true
/esbuild@0.18.14:
resolution: {integrity: sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w==}
@ -327,12 +327,12 @@ packages:
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
dev: false
dev: true
/filename-reserved-regex@2.0.0:
resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==}
engines: {node: '>=4'}
dev: false
dev: true
/filenamify@4.3.0:
resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==}
@ -341,7 +341,7 @@ packages:
filename-reserved-regex: 2.0.0
strip-outer: 1.0.1
trim-repeated: 1.0.0
dev: false
dev: true
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
@ -356,7 +356,7 @@ packages:
commondir: 1.0.1
make-dir: 3.1.0
pkg-dir: 4.2.0
dev: false
dev: true
/find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
@ -364,7 +364,7 @@ packages:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: false
dev: true
/fs-extra@8.1.0:
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
@ -373,11 +373,11 @@ packages:
graceful-fs: 4.2.11
jsonfile: 4.0.0
universalify: 0.1.2
dev: false
dev: true
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: false
dev: true
/fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
@ -398,7 +398,7 @@ packages:
find-cache-dir: 3.3.2
fs-extra: 8.1.0
globby: 6.1.0
dev: false
dev: true
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@ -415,7 +415,7 @@ packages:
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: false
dev: true
/globby@6.1.0:
resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==}
@ -426,11 +426,11 @@ packages:
object-assign: 4.1.1
pify: 2.3.0
pinkie-promise: 2.0.1
dev: false
dev: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: false
dev: true
/immutable@4.3.1:
resolution: {integrity: sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==}
@ -440,11 +440,11 @@ packages:
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: false
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false
dev: true
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
@ -470,27 +470,27 @@ packages:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies:
graceful-fs: 4.2.11
dev: false
dev: true
/locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: false
dev: true
/make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
dependencies:
semver: 6.3.1
dev: false
dev: true
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: false
dev: true
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
@ -505,42 +505,42 @@ packages:
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: false
dev: true
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: false
dev: true
/p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: false
dev: true
/p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: false
dev: true
/p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: false
dev: true
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: false
dev: true
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: false
dev: true
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
@ -553,26 +553,26 @@ packages:
/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
dev: false
dev: true
/pinkie-promise@2.0.1:
resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==}
engines: {node: '>=0.10.0'}
dependencies:
pinkie: 2.0.4
dev: false
dev: true
/pinkie@2.0.4:
resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==}
engines: {node: '>=0.10.0'}
dev: false
dev: true
/pkg-dir@4.2.0:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
dependencies:
find-up: 4.1.0
dev: false
dev: true
/postcss@8.4.26:
resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==}
@ -609,7 +609,7 @@ packages:
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
dev: false
dev: true
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
@ -620,7 +620,7 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
escape-string-regexp: 1.0.5
dev: false
dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
@ -633,7 +633,7 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
escape-string-regexp: 1.0.5
dev: false
dev: true
/typescript@5.0.2:
resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==}
@ -644,7 +644,7 @@ packages:
/universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
dev: false
dev: true
/vite@4.4.0(sass@1.63.6):
resolution: {integrity: sha512-Wf+DCEjuM8aGavEYiF77hnbxEZ+0+/jC9nABR46sh5Xi+GYeSvkeEFRiVuI3x+tPjxgZeS91h1jTAQTPFgePpA==}
@ -684,4 +684,4 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: false
dev: true

View File

@ -1,4 +1,6 @@
import { Spreadsheet } from "../main";
import { Position } from "../modules/cell";
import { RenderBox } from "../modules/renderBox";
export class Editor {
element: HTMLInputElement
@ -6,14 +8,57 @@ export class Editor {
constructor(root: Spreadsheet) {
this.root = root
const element = document.createElement('input')
element.classList.add('editor')
this.element = element
this.hide()
}
hide() {
this.element.style.display = 'none'
this.element.classList.add('hide')
this.element.blur()
window.removeEventListener('click', this.handleClickOutside)
}
show() {
show(position: Position) {
const { height, width, x, y } = new RenderBox(this.root.config, position);
const cell = this.root.getCell(position)
this.element.classList.remove('hide')
this.element.style.top = (y - this.root.viewport.top) + 'px'
this.element.style.left = (x - this.root.viewport.left) + 'px'
this.element.style.width = width + 'px'
this.element.style.height = height + 'px'
this.element.style.display = 'block'
window.addEventListener('click', this.handleClickOutside)
this.element.addEventListener('keydown', this.handleKeydown)
this.element.value = cell.value
this.element.focus()
}
handleKeydown = (event: KeyboardEvent) => {
const {key} = event
switch(key) {
case 'Escape': {
this.hide();
break;
}
case 'Enter': {
this.root.changeCellValues(this.root.selection.selectedCell!, {
value: this.element.value,
displayValue: this.element.value
})
this.hide();
}
}
}
handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement
if (!this.element.contains(target)) {
this.hide()
}
}
}

View File

@ -37,16 +37,22 @@ export class Scroller {
if(this.root.selection.selectedRange) {
this.root.selection.selectedRange.to = lastSelectedCell
}
this.root.renderSheet()
})
this.element.addEventListener('mouseup', () => {
this.isSelecting = false
console.log(this.root.selection)
this.root.renderSheet()
})
this.element.addEventListener('dblclick', event => {
event.preventDefault();
const position = this.root.getCellByCoords(event.offsetX, event.offsetY)
this.root.showEditor(position)
})
}
private handleClick = (event: MouseEvent) => {
if(event.button !== 0) return; // Left mouse button
const {offsetX, offsetY} = event
const clickedCell = this.root.getCellByCoords(offsetX, offsetY)
this.isSelecting = true

View File

@ -58,7 +58,6 @@ export class Sheet {
const lastRowIdx = this.root.viewport.lastRow + 3
const firstColIdx = this.root.viewport.firstCol
console.log()
let rowsCount = 0
@ -73,7 +72,6 @@ export class Sheet {
rowsCount++;
}
console.log(`Rendered ${rowsCount} rows!`)
}
}

View File

@ -4,7 +4,7 @@ import { Scroller } from "./components/scroller";
import { Sheet } from "./components/sheet";
import { Table } from "./components/table";
import { Toolbar } from "./components/toolbar";
import { Cell } from "./modules/cell";
import { Cell, CellConstructorProps, Position } from "./modules/cell";
import { Config, ViewProperties } from "./modules/config";
import { Selection } from "./modules/selection";
import { Styles } from "./modules/styles";
@ -101,6 +101,22 @@ export class Spreadsheet {
return this.sheet.getCellByCoords(x, y)
}
getCell(position: Position): Cell {
const {column, row} = position
return this.data[row][column]
}
changeCellValues(position: Position, values: Partial<Omit<CellConstructorProps, 'position'>>) {
const {column, row} = position
this.data[row][column].changeValues(values)
this.renderCell(row, column)
}
showEditor(position: Position) {
this.editor.show(position)
}
renderSheet() {
this.sheet.renderSheet()
}

View File

@ -8,6 +8,16 @@ export type CellConstructorProps = {
position: Position
}
export class CellStyles {
fontSize: number = 16
fontColor: string = 'black'
background: string = 'white'
borderColor: string = 'black'
selectedBackground = '#4287f5'
selectedFontColor = '#ffffff'
}
export class Position {
row: number
column: number
@ -23,6 +33,8 @@ export class Cell {
/** This refers to the values that were obtained by calculations, for example, after calculating the formula */
resultValue: string
position: Position
style: CellStyles = new CellStyles()
constructor(props: CellConstructorProps) {
this.value = props.value
this.displayValue = props.displayValue
@ -30,23 +42,40 @@ export class Cell {
this.position = props.position
}
changeValues(values: Partial<Omit<CellConstructorProps, 'position'>>) {
Object.assign(this, values)
}
private isCellInRange(root: Spreadsheet): boolean {
const { column, row } = this.position
const { selectedRange } = root.selection
if (!selectedRange) return false;
const isCellInRow = row >= Math.min(selectedRange.from.row, selectedRange.to.row) && row <= Math.max(selectedRange.to.row, selectedRange.from.row)
const isCellInCol = column >= Math.min(selectedRange.from.column, selectedRange.to.column) && column <= Math.max(selectedRange.to.column, selectedRange.from.column)
return isCellInCol && isCellInRow
}
render(root: Spreadsheet) {
let { height, width, x, y } = new RenderBox(root.config, this.position)
const { ctx } = root
const isCellSelected = (root.selection.selectedCell?.row === this.position.row && root.selection.selectedCell.column === this.position.column)
const isCellInRange = this.isCellInRange(root)
y -= root.viewport.top
x -= root.viewport.left
ctx.clearRect(x, y, width, height)
ctx.fillStyle = isCellSelected ? 'red' : 'white'
ctx.fillStyle = isCellSelected || isCellInRange ? this.style.selectedBackground : this.style.background
ctx.strokeStyle = 'black'
ctx.fillRect(x, y, width - 1, height - 1)
ctx.strokeRect(x, y, width, height)
ctx.fillStyle = 'black'
ctx.fillStyle = isCellSelected || isCellInRange ? this.style.selectedFontColor : this.style.fontColor
ctx.textAlign = 'left'
ctx.font = `16px Arial`
ctx.font = `${this.style.fontSize}px Arial`
ctx.textBaseline = 'middle'
ctx.fillText(this.displayValue, x + 2, y + height / 2, width)
}

View File

@ -1,3 +1,4 @@
export class Styles {
export class Styles {
}

View File

@ -51,8 +51,6 @@ export class Viewport {
this.lastRow = this.getLastRow()
this.firstCol = this.getFirstCol()
this.lastCol = this.getLastCol()
console.log({ first: this.firstCol, last: this.lastCol })
}
/** Get index of first row in viewport */

View File

@ -22,3 +22,14 @@
box-sizing: border-box;
transform: translateZ(0);
}
.editor {
position: absolute;
box-sizing: border-box;
font-size: 16px;
font-family: Arial, Helvetica, sans-serif;
}
.hide {
visibility: hidden;
}