├── .bowerrc
├── .config
├── base.js
├── bin
│ └── trigger-on-stdout-change.js
├── development.js
├── languages-development.js
├── languages-production.js
├── loader
│ ├── empty-loader.js
│ └── exports-to-window-loader.js
├── plugin
│ ├── jasmine-html.js
│ └── template.ejs
├── production.js
├── test-e2e.js
├── test-mobile.js
├── test-production.js
├── test-walkontable.js
├── walkontable.js
└── watch.js
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CNAME
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.js
├── bower.json
├── dist
├── README.md
├── handsontable.css
├── handsontable.css.map
├── handsontable.full.css
├── handsontable.full.js
├── handsontable.full.min.css
├── handsontable.full.min.js
├── handsontable.js
├── handsontable.js.map
├── handsontable.min.css
├── handsontable.min.js
├── languages
│ ├── all.js
│ ├── all.min.js
│ ├── de-CH.js
│ ├── de-CH.min.js
│ ├── de-DE.js
│ ├── de-DE.min.js
│ ├── en-US.js
│ ├── en-US.min.js
│ ├── es-MX.js
│ ├── es-MX.min.js
│ ├── fr-FR.js
│ ├── fr-FR.min.js
│ ├── it-IT.js
│ ├── it-IT.min.js
│ ├── ja-JP.js
│ ├── ja-JP.min.js
│ ├── ko-KR.js
│ ├── ko-KR.min.js
│ ├── lv-LV.js
│ ├── lv-LV.min.js
│ ├── nb-NO.js
│ ├── nb-NO.min.js
│ ├── nl-NL.js
│ ├── nl-NL.min.js
│ ├── pl-PL.js
│ ├── pl-PL.min.js
│ ├── pt-BR.js
│ ├── pt-BR.min.js
│ ├── ru-RU.js
│ ├── ru-RU.min.js
│ ├── zh-CN.js
│ ├── zh-CN.min.js
│ ├── zh-TW.js
│ └── zh-TW.min.js
├── moment
│ ├── LICENSE
│ ├── locale
│ │ ├── af.js
│ │ ├── ar-dz.js
│ │ ├── ar-kw.js
│ │ ├── ar-ly.js
│ │ ├── ar-ma.js
│ │ ├── ar-sa.js
│ │ ├── ar-tn.js
│ │ ├── ar.js
│ │ ├── az.js
│ │ ├── be.js
│ │ ├── bg.js
│ │ ├── bm.js
│ │ ├── bn.js
│ │ ├── bo.js
│ │ ├── br.js
│ │ ├── bs.js
│ │ ├── ca.js
│ │ ├── cs.js
│ │ ├── cv.js
│ │ ├── cy.js
│ │ ├── da.js
│ │ ├── de-at.js
│ │ ├── de-ch.js
│ │ ├── de.js
│ │ ├── dv.js
│ │ ├── el.js
│ │ ├── en-au.js
│ │ ├── en-ca.js
│ │ ├── en-gb.js
│ │ ├── en-ie.js
│ │ ├── en-nz.js
│ │ ├── eo.js
│ │ ├── es-do.js
│ │ ├── es-us.js
│ │ ├── es.js
│ │ ├── et.js
│ │ ├── eu.js
│ │ ├── fa.js
│ │ ├── fi.js
│ │ ├── fo.js
│ │ ├── fr-ca.js
│ │ ├── fr-ch.js
│ │ ├── fr.js
│ │ ├── fy.js
│ │ ├── gd.js
│ │ ├── gl.js
│ │ ├── gom-latn.js
│ │ ├── gu.js
│ │ ├── he.js
│ │ ├── hi.js
│ │ ├── hr.js
│ │ ├── hu.js
│ │ ├── hy-am.js
│ │ ├── id.js
│ │ ├── is.js
│ │ ├── it.js
│ │ ├── ja.js
│ │ ├── jv.js
│ │ ├── ka.js
│ │ ├── kk.js
│ │ ├── km.js
│ │ ├── kn.js
│ │ ├── ko.js
│ │ ├── ky.js
│ │ ├── lb.js
│ │ ├── lo.js
│ │ ├── lt.js
│ │ ├── lv.js
│ │ ├── me.js
│ │ ├── mi.js
│ │ ├── mk.js
│ │ ├── ml.js
│ │ ├── mr.js
│ │ ├── ms-my.js
│ │ ├── ms.js
│ │ ├── mt.js
│ │ ├── my.js
│ │ ├── nb.js
│ │ ├── ne.js
│ │ ├── nl-be.js
│ │ ├── nl.js
│ │ ├── nn.js
│ │ ├── pa-in.js
│ │ ├── pl.js
│ │ ├── pt-br.js
│ │ ├── pt.js
│ │ ├── ro.js
│ │ ├── ru.js
│ │ ├── sd.js
│ │ ├── se.js
│ │ ├── si.js
│ │ ├── sk.js
│ │ ├── sl.js
│ │ ├── sq.js
│ │ ├── sr-cyrl.js
│ │ ├── sr.js
│ │ ├── ss.js
│ │ ├── sv.js
│ │ ├── sw.js
│ │ ├── ta.js
│ │ ├── te.js
│ │ ├── tet.js
│ │ ├── th.js
│ │ ├── tl-ph.js
│ │ ├── tlh.js
│ │ ├── tr.js
│ │ ├── tzl.js
│ │ ├── tzm-latn.js
│ │ ├── tzm.js
│ │ ├── uk.js
│ │ ├── ur.js
│ │ ├── uz-latn.js
│ │ ├── uz.js
│ │ ├── vi.js
│ │ ├── x-pseudo.js
│ │ ├── yo.js
│ │ ├── zh-cn.js
│ │ ├── zh-hk.js
│ │ └── zh-tw.js
│ └── moment.js
├── numbro
│ ├── LICENSE
│ ├── LICENSE-Numeraljs
│ ├── languages.min.js
│ ├── languages
│ │ ├── bg.min.js
│ │ ├── cs-CZ.min.js
│ │ ├── da-DK.min.js
│ │ ├── de-AT.min.js
│ │ ├── de-CH.min.js
│ │ ├── de-DE.min.js
│ │ ├── de-LI.min.js
│ │ ├── el.min.js
│ │ ├── en-AU.min.js
│ │ ├── en-GB.min.js
│ │ ├── en-IE.min.js
│ │ ├── en-NZ.min.js
│ │ ├── en-ZA.min.js
│ │ ├── es-AR.min.js
│ │ ├── es-CL.min.js
│ │ ├── es-CO.min.js
│ │ ├── es-CR.min.js
│ │ ├── es-ES.min.js
│ │ ├── es-MX.min.js
│ │ ├── es-NI.min.js
│ │ ├── es-PE.min.js
│ │ ├── es-PR.min.js
│ │ ├── es-SV.min.js
│ │ ├── et-EE.min.js
│ │ ├── fa-IR.min.js
│ │ ├── fi-FI.min.js
│ │ ├── fil-PH.min.js
│ │ ├── fr-CA.min.js
│ │ ├── fr-CH.min.js
│ │ ├── fr-FR.min.js
│ │ ├── he-IL.min.js
│ │ ├── hu-HU.min.js
│ │ ├── id.min.js
│ │ ├── it-CH.min.js
│ │ ├── it-IT.min.js
│ │ ├── ja-JP.min.js
│ │ ├── ko-KR.min.js
│ │ ├── lv-LV.min.js
│ │ ├── nb-NO.min.js
│ │ ├── nb.min.js
│ │ ├── nl-BE.min.js
│ │ ├── nl-NL.min.js
│ │ ├── nn.min.js
│ │ ├── pl-PL.min.js
│ │ ├── pt-BR.min.js
│ │ ├── pt-PT.min.js
│ │ ├── ro-RO.min.js
│ │ ├── ro.min.js
│ │ ├── ru-RU.min.js
│ │ ├── ru-UA.min.js
│ │ ├── sk-SK.min.js
│ │ ├── sl.min.js
│ │ ├── sr-Cyrl-RS.min.js
│ │ ├── sv-SE.min.js
│ │ ├── th-TH.min.js
│ │ ├── tr-TR.min.js
│ │ ├── uk-UA.min.js
│ │ ├── zh-CN.min.js
│ │ ├── zh-MO.min.js
│ │ ├── zh-SG.min.js
│ │ └── zh-TW.min.js
│ └── numbro.js
└── pikaday
│ ├── LICENSE
│ ├── pikaday.css
│ └── pikaday.js
├── handsontable.d.ts
├── handsontable.jquery.json
├── hot.config.js
├── languages
├── all.js
├── de-CH.js
├── de-DE.js
├── en-US.js
├── es-MX.js
├── fr-FR.js
├── index.js
├── it-IT.js
├── ja-JP.js
├── ko-KR.js
├── lv-LV.js
├── nb-NO.js
├── nl-NL.js
├── pl-PL.js
├── pt-BR.js
├── ru-RU.js
├── zh-CN.js
└── zh-TW.js
├── lib
├── SheetClip
│ └── SheetClip.js
├── autoResize
│ └── autoResize.js
└── jsonpatch
│ └── json-patch-duplex.js
├── package.json
├── src
├── 3rdparty
│ └── walkontable
│ │ ├── css
│ │ ├── bootstrap.css
│ │ └── walkontable.css
│ │ ├── package.json
│ │ ├── src
│ │ ├── border.js
│ │ ├── calculator
│ │ │ ├── viewportColumns.js
│ │ │ └── viewportRows.js
│ │ ├── cell
│ │ │ ├── coords.js
│ │ │ └── range.js
│ │ ├── core.js
│ │ ├── event.js
│ │ ├── filter
│ │ │ ├── column.js
│ │ │ └── row.js
│ │ ├── index.js
│ │ ├── overlay
│ │ │ ├── _base.js
│ │ │ ├── bottom.js
│ │ │ ├── bottomLeftCorner.js
│ │ │ ├── debug.js
│ │ │ ├── left.js
│ │ │ ├── top.js
│ │ │ └── topLeftCorner.js
│ │ ├── overlays.js
│ │ ├── scroll.js
│ │ ├── selection.js
│ │ ├── settings.js
│ │ ├── table.js
│ │ ├── tableRenderer.js
│ │ └── viewport.js
│ │ └── test
│ │ ├── SpecRunner.html
│ │ ├── helpers
│ │ ├── common.js
│ │ ├── index.js
│ │ └── jasmine-bridge-reporter.js
│ │ ├── lib
│ │ ├── jquery.min.js
│ │ └── jquery.simulate.js
│ │ └── spec
│ │ ├── border.spec.js
│ │ ├── calculator
│ │ ├── viewportColumns.spec.js
│ │ └── viewportRows.spec.js
│ │ ├── cell
│ │ ├── coords.spec.js
│ │ └── range.spec.js
│ │ ├── core.spec.js
│ │ ├── event.spec.js
│ │ ├── filter
│ │ ├── column.spec.js
│ │ └── row.spec.js
│ │ ├── index.js
│ │ ├── scroll.spec.js
│ │ ├── scrollbar.spec.js
│ │ ├── scrollbarNative.spec.js
│ │ ├── selection.spec.js
│ │ ├── settings
│ │ ├── columnHeaders.spec.js
│ │ ├── preventOverflow.spec.js
│ │ ├── rowHeaders.spec.js
│ │ └── stretchH.spec.js
│ │ └── table.spec.js
├── cellTypes
│ ├── autocompleteType.js
│ ├── checkboxType.js
│ ├── dateType.js
│ ├── dropdownType.js
│ ├── handsontableType.js
│ ├── index.js
│ ├── numericType.js
│ ├── passwordType.js
│ ├── textType.js
│ └── timeType.js
├── core.js
├── css
│ ├── bootstrap.css
│ ├── handsontable.css
│ └── mobile.handsontable.css
├── dataMap.js
├── dataSource.js
├── defaultSettings.js
├── editorManager.js
├── editors
│ ├── _baseEditor.js
│ ├── autocompleteEditor.js
│ ├── checkboxEditor.js
│ ├── dateEditor.js
│ ├── dropdownEditor.js
│ ├── handsontableEditor.js
│ ├── index.js
│ ├── numericEditor.js
│ ├── passwordEditor.js
│ ├── selectEditor.js
│ └── textEditor.js
├── eventManager.js
├── helpers
│ ├── array.js
│ ├── browser.js
│ ├── console.js
│ ├── data.js
│ ├── date.js
│ ├── dom
│ │ ├── element.js
│ │ └── event.js
│ ├── feature.js
│ ├── function.js
│ ├── mixed.js
│ ├── number.js
│ ├── object.js
│ ├── setting.js
│ ├── string.js
│ ├── templateLiteralTag.js
│ ├── unicode.js
│ └── wrappers
│ │ └── jquery.js
├── i18n
│ ├── constants.js
│ ├── dictionariesManager.js
│ ├── index.js
│ ├── languages
│ │ ├── de-CH.js
│ │ ├── de-DE.js
│ │ ├── en-US.js
│ │ ├── es-MX.js
│ │ ├── fr-FR.js
│ │ ├── index.js
│ │ ├── it-IT.js
│ │ ├── ja-JP.js
│ │ ├── ko-KR.js
│ │ ├── lv-LV.js
│ │ ├── nb-NO.js
│ │ ├── nl-NL.js
│ │ ├── pl-PL.js
│ │ ├── pt-BR.js
│ │ ├── ru-RU.js
│ │ ├── zh-CN.js
│ │ └── zh-TW.js
│ ├── phraseFormatters
│ │ ├── index.js
│ │ ├── pluralize.js
│ │ └── substituteVariables.js
│ └── utils.js
├── index.js
├── mixins
│ ├── arrayMapper.js
│ ├── localHooks.js
│ └── stateSaver.js
├── multiMap.js
├── pluginHooks.js
├── plugins.js
├── plugins
│ ├── _base.js
│ ├── autoColumnSize
│ │ ├── autoColumnSize.js
│ │ └── test
│ │ │ └── autoColumnSize.e2e.js
│ ├── autoRowSize
│ │ ├── autoRowSize.js
│ │ └── test
│ │ │ └── autoRowSize.e2e.js
│ ├── autofill
│ │ ├── autofill.js
│ │ ├── test
│ │ │ └── autofill.e2e.js
│ │ └── utils.js
│ ├── columnSorting
│ │ ├── columnSorting.js
│ │ ├── columnStatesManager.js
│ │ ├── domHelpers.js
│ │ ├── rootComparator.js
│ │ ├── rowsMapper.js
│ │ ├── sortFunction
│ │ │ ├── date.js
│ │ │ ├── default.js
│ │ │ └── numeric.js
│ │ ├── sortService
│ │ │ ├── engine.js
│ │ │ ├── index.js
│ │ │ └── registry.js
│ │ ├── test
│ │ │ ├── columnSorting.e2e.js
│ │ │ ├── columnSorting.types.ts
│ │ │ ├── columnStatesManager.unit.js
│ │ │ ├── domHelpers.unit.js
│ │ │ ├── sortFunction
│ │ │ │ ├── date.unit.js
│ │ │ │ ├── default.unit.js
│ │ │ │ └── numeric.unit.js
│ │ │ └── utils.unit.js
│ │ └── utils.js
│ ├── comments
│ │ ├── commentEditor.js
│ │ ├── comments.css
│ │ ├── comments.js
│ │ ├── displaySwitch.js
│ │ └── test
│ │ │ ├── comments.e2e.js
│ │ │ └── displaySwitch.unit.js
│ ├── contextMenu
│ │ ├── commandExecutor.js
│ │ ├── contextMenu.css
│ │ ├── contextMenu.js
│ │ ├── cursor.js
│ │ ├── itemsFactory.js
│ │ ├── menu.js
│ │ ├── predefinedItems.js
│ │ ├── predefinedItems
│ │ │ ├── alignment.js
│ │ │ ├── clearColumn.js
│ │ │ ├── columnLeft.js
│ │ │ ├── columnRight.js
│ │ │ ├── readOnly.js
│ │ │ ├── redo.js
│ │ │ ├── removeColumn.js
│ │ │ ├── removeRow.js
│ │ │ ├── rowAbove.js
│ │ │ ├── rowBelow.js
│ │ │ ├── separator.js
│ │ │ └── undo.js
│ │ ├── test
│ │ │ ├── contextMenu.e2e.js
│ │ │ ├── cursor.unit.js
│ │ │ └── predefinedItems
│ │ │ │ ├── alignment.e2e.js
│ │ │ │ ├── readOnly.e2e.js
│ │ │ │ ├── removeColumn.e2e.js
│ │ │ │ └── removeRow.e2e.js
│ │ └── utils.js
│ ├── copyPaste
│ │ ├── clipboardData.js
│ │ ├── contextMenuItem
│ │ │ ├── copy.js
│ │ │ └── cut.js
│ │ ├── copyPaste.css
│ │ ├── copyPaste.js
│ │ ├── focusableElement.js
│ │ ├── pasteEvent.js
│ │ ├── test
│ │ │ ├── copyPaste.e2e.js
│ │ │ ├── copyPaste.types.ts
│ │ │ └── focusableElement.unit.js
│ │ └── utils.js
│ ├── customBorders
│ │ ├── contextMenuItem
│ │ │ ├── bottom.js
│ │ │ ├── index.js
│ │ │ ├── left.js
│ │ │ ├── noBorders.js
│ │ │ ├── right.js
│ │ │ └── top.js
│ │ ├── customBorders.js
│ │ ├── test
│ │ │ └── customBorders.e2e.js
│ │ └── utils.js
│ ├── dragToScroll
│ │ ├── dragToScroll.js
│ │ └── test
│ │ │ └── dragToScroll.e2e.js
│ ├── index.js
│ ├── manualColumnFreeze
│ │ ├── contextMenuItem
│ │ │ ├── freezeColumn.js
│ │ │ └── unfreezeColumn.js
│ │ ├── manualColumnFreeze.css
│ │ ├── manualColumnFreeze.js
│ │ └── test
│ │ │ └── manualColumnFreeze.e2e.js
│ ├── manualColumnMove
│ │ ├── columnsMapper.js
│ │ ├── manualColumnMove.css
│ │ ├── manualColumnMove.js
│ │ ├── test
│ │ │ ├── columnsMapper.unit.js
│ │ │ ├── manualColumnMove.e2e.js
│ │ │ └── manualColumnMoveUI.e2e.js
│ │ └── ui
│ │ │ ├── _base.js
│ │ │ ├── backlight.js
│ │ │ └── guideline.js
│ ├── manualColumnResize
│ │ ├── manualColumnResize.js
│ │ └── test
│ │ │ └── manualColumnResize.e2e.js
│ ├── manualRowMove
│ │ ├── manualRowMove.css
│ │ ├── manualRowMove.js
│ │ ├── rowsMapper.js
│ │ ├── test
│ │ │ ├── manualRowMove.e2e.js
│ │ │ ├── manualRowMoveUI.e2e.js
│ │ │ └── rowsMapper.unit.js
│ │ └── ui
│ │ │ ├── _base.js
│ │ │ ├── backlight.js
│ │ │ └── guideline.js
│ ├── manualRowResize
│ │ ├── manualRowResize.js
│ │ └── test
│ │ │ └── manualRowResize.e2e.js
│ ├── mergeCells
│ │ ├── calculations
│ │ │ ├── autofill.js
│ │ │ └── selection.js
│ │ ├── cellCoords.js
│ │ ├── cellsCollection.js
│ │ ├── contextMenuItem
│ │ │ └── toggleMerge.js
│ │ ├── mergeCells.css
│ │ ├── mergeCells.js
│ │ ├── test
│ │ │ ├── autofillCalculations.unit.js
│ │ │ ├── cellCoords.unit.js
│ │ │ ├── cellsCollection.unit.js
│ │ │ ├── mergeCells.e2e.js
│ │ │ ├── selection.e2e.js
│ │ │ ├── selection.unit.js
│ │ │ └── utils.unit.js
│ │ └── utils.js
│ ├── multipleSelectionHandles
│ │ └── multipleSelectionHandles.js
│ ├── observeChanges
│ │ ├── dataObserver.js
│ │ ├── observeChanges.js
│ │ ├── test
│ │ │ └── observeChanges.e2e.js
│ │ └── utils.js
│ ├── persistentState
│ │ ├── persistentState.js
│ │ ├── storage.js
│ │ └── test
│ │ │ ├── persistentState.e2e.js
│ │ │ └── storage.unit.js
│ ├── search
│ │ ├── search.js
│ │ └── test
│ │ │ └── search.e2e.js
│ ├── touchScroll
│ │ └── touchScroll.js
│ └── undoRedo
│ │ ├── test
│ │ └── UndoRedo.e2e.js
│ │ └── undoRedo.js
├── renderers
│ ├── _cellDecorator.js
│ ├── autocompleteRenderer.js
│ ├── checkboxRenderer.js
│ ├── htmlRenderer.js
│ ├── index.js
│ ├── numericRenderer.js
│ ├── passwordRenderer.js
│ └── textRenderer.js
├── selection
│ ├── highlight
│ │ ├── highlight.js
│ │ └── types
│ │ │ ├── activeHeader.js
│ │ │ ├── area.js
│ │ │ ├── cell.js
│ │ │ ├── customSelection.js
│ │ │ ├── fill.js
│ │ │ ├── header.js
│ │ │ └── index.js
│ ├── index.js
│ ├── mouseEventHandler.js
│ ├── range.js
│ ├── selection.js
│ ├── transformation.js
│ └── utils.js
├── tableView.js
├── utils
│ ├── dataStructures
│ │ ├── linkedList.js
│ │ ├── queue.js
│ │ └── stack.js
│ ├── ghostTable.js
│ ├── interval.js
│ ├── keyStateObserver.js
│ ├── recordTranslator.js
│ ├── rootInstance.js
│ ├── samplesGenerator.js
│ ├── sortingAlgorithms
│ │ └── mergeSort.js
│ └── staticRegister.js
└── validators
│ ├── autocompleteValidator.js
│ ├── dateValidator.js
│ ├── index.js
│ ├── numericValidator.js
│ └── timeValidator.js
├── test
├── __mocks__
│ └── styleMock.js
├── bootstrap.js
├── e2e
│ ├── ColHeader.spec.js
│ ├── Core_alter.spec.js
│ ├── Core_beforeKeyDown.spec.js
│ ├── Core_beforechange.spec.js
│ ├── Core_count.spec.js
│ ├── Core_countEmptyCols.spec.js
│ ├── Core_countEmptyRows.spec.js
│ ├── Core_dataSchema.spec.js
│ ├── Core_datachange.spec.js
│ ├── Core_destroy.spec.js
│ ├── Core_destroyEditor.spec.js
│ ├── Core_getCellMeta.spec.js
│ ├── Core_getColHeader.spec.js
│ ├── Core_getDataAt.spec.js
│ ├── Core_getDataType.spec.js
│ ├── Core_getRowHeader.spec.js
│ ├── Core_init.spec.js
│ ├── Core_isEmpty.spec.js
│ ├── Core_keepEmptyRows.spec.js
│ ├── Core_listen.spec.js
│ ├── Core_loadData.spec.js
│ ├── Core_navigation.spec.js
│ ├── Core_onKeyDown.spec.js
│ ├── Core_populateFromArray.spec.js
│ ├── Core_reCreate.spec.js
│ ├── Core_removeCellMeta.spec.js
│ ├── Core_render.spec.js
│ ├── Core_selection.spec.js
│ ├── Core_setDataAtCell.spec.js
│ ├── Core_splice.spec.js
│ ├── Core_update.spec.js
│ ├── Core_validate.spec.js
│ ├── Core_view.spec.js
│ ├── Dom.spec.js
│ ├── MemoryLeakTest.js
│ ├── Performance.spec.js
│ ├── PluginHooks.spec.js
│ ├── RowHeader.spec.js
│ ├── cellTypes
│ │ └── index.spec.js
│ ├── core
│ │ ├── colToProp.spec.js
│ │ ├── countSourceCols.spec.js
│ │ ├── emptySelectedCells.spec.js
│ │ ├── getCellMetaAtRow.spec.js
│ │ ├── getCellsMeta.spec.js
│ │ ├── getCopyableData.spec.js
│ │ ├── getCopyableText.spec.js
│ │ ├── getSelected.spec.js
│ │ ├── getSelectedLast.spec.js
│ │ ├── getSelectedRange.spec.js
│ │ ├── getSelectedRangeLast.spec.js
│ │ ├── getSourceDataArray.spec.js
│ │ ├── getSourceDataAtCell.spec.js
│ │ ├── getSourceDataAtCol.spec.js
│ │ ├── propToCol.spec.js
│ │ ├── selectAll.spec.js
│ │ ├── selectCell.spec.js
│ │ ├── selectCells.spec.js
│ │ ├── selectColumns.spec.js
│ │ ├── selectRows.spec.js
│ │ ├── setCellMeta.spec.js
│ │ ├── spliceCellsMeta.spec.js
│ │ ├── spliceCol.spec.js
│ │ ├── spliceRow.spec.js
│ │ ├── toPhysicalColumn.spec.js
│ │ ├── toPhysicalRow.spec.js
│ │ ├── toVisualColumn.spec.js
│ │ └── toVisualRow.spec.js
│ ├── editors
│ │ ├── autocompleteEditor.spec.js
│ │ ├── baseEditor.spec.js
│ │ ├── dateEditor.spec.js
│ │ ├── dropdownEditor.spec.js
│ │ ├── handsontableEditor.spec.js
│ │ ├── index.spec.js
│ │ ├── noEditor.spec.js
│ │ ├── numericEditor.spec.js
│ │ ├── passwordEditor.spec.js
│ │ ├── selectEditor.spec.js
│ │ └── textEditor.spec.js
│ ├── i18n
│ │ └── index.spec.js
│ ├── index.js
│ ├── mobile
│ │ ├── events.spec.js
│ │ ├── index.js
│ │ ├── scroll.spec.js
│ │ └── selection.spec.js
│ ├── publicAPI.spec.js
│ ├── renderers
│ │ ├── autocompleteRenderer.spec.js
│ │ ├── cellDecorator.spec.js
│ │ ├── checkboxRenderer.spec.js
│ │ ├── htmlRenderer.spec.js
│ │ ├── index.spec.js
│ │ ├── numericRenderer.spec.js
│ │ ├── passwordRenderer.spec.js
│ │ └── textRenderer.spec.js
│ ├── settings
│ │ ├── autoWrapCol.spec.js
│ │ ├── autoWrapRow.spec.js
│ │ ├── colWidths.spec.js
│ │ ├── columns.spec.js
│ │ ├── copyable.spec.js
│ │ ├── currentHeaderClassName.spec.js
│ │ ├── currentRowClassName.spec.js
│ │ ├── editor.spec.js
│ │ ├── fixedColumnsLeft.spec.js
│ │ ├── fixedRowsBottom.spec.js
│ │ ├── fixedRowsTop.spec.js
│ │ ├── fragmentSelection.spec.js
│ │ ├── maxCols.spec.js
│ │ ├── maxRows.spec.js
│ │ ├── outsideClickDeselects.spec.js
│ │ ├── renderer.spec.js
│ │ └── tableClassName.spec.js
│ ├── utils
│ │ └── ghostTable.spec.js
│ └── validators
│ │ ├── autocompleteValidator.spec.js
│ │ ├── dateValidator.spec.js
│ │ ├── index.spec.js
│ │ ├── numericValidator.spec.js
│ │ └── timeValidator.spec.js
├── helpers
│ ├── asciiTable.js
│ ├── common.css
│ ├── common.js
│ ├── custom-matchers.js
│ ├── index.js
│ └── jasmine-bridge-reporter.js
├── lib
│ ├── backbone.js
│ ├── jquery.min.js
│ ├── jquery.simulate.js
│ ├── lodash.underscore.js
│ └── normalize.css
├── scripts
│ ├── run-puppeteer.js
│ ├── trigger-hot-builder-tests.sh
│ └── trigger-pro-tests.sh
├── types
│ ├── editors
│ │ ├── password.types.ts
│ │ └── text.types.ts
│ ├── helpers
│ │ ├── dom.types.ts
│ │ └── helpers.types.ts
│ ├── index.types.ts
│ ├── methods.types.ts
│ ├── renderers.types.ts
│ ├── settings.types.ts
│ └── tsconfig.json
└── unit
│ ├── EventManager.spec.js
│ ├── PluginHooks.spec.js
│ ├── helpers
│ ├── Array.spec.js
│ ├── Browser.spec.js
│ ├── Console.spec.js
│ ├── Data.spec.js
│ ├── Date.spec.js
│ ├── Feature.spec.js
│ ├── Function.spec.js
│ ├── Mixed.spec.js
│ ├── Number.spec.js
│ ├── Object.spec.js
│ ├── String.spec.js
│ ├── TemplateLiteralTag.spec.js
│ ├── Unicode.spec.js
│ └── dom
│ │ ├── Element.spec.js
│ │ └── Event.spec.js
│ ├── i18n
│ ├── dictionariesManager.spec.js
│ ├── index.spec.js
│ ├── phraseFormatters
│ │ ├── index.spec.js
│ │ ├── pluralize.spec.js
│ │ └── substituteVariables.spec.js
│ └── utils.spec.js
│ ├── mixins
│ ├── arrayMapper.spec.js
│ └── localHooks.spec.js
│ ├── multiMap.spec.js
│ ├── selection
│ ├── range.spec.js
│ └── utils.spec.js
│ └── utils
│ ├── Interval.spec.js
│ ├── dataStructures
│ └── LinkedList.spec.js
│ ├── keyStateObserver.spec.js
│ ├── recordTranslator.spec.js
│ ├── rootInstance.spec.js
│ ├── samplesGenerator.spec.js
│ ├── sortingAlgorithms
│ └── mergeSort.spec.js
│ └── staticRegister.spec.js
├── update.json
├── webpack.config.js
└── yarn.lock
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "demo/bower_components"
3 | }
--------------------------------------------------------------------------------
/.config/languages-production.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Config responsible for building minified Handsontable `dist/languages/` files.
5 | */
6 | const path = require('path');
7 | const webpack = require('webpack');
8 | const configFactory = require('./languages-development');
9 | const OUTPUT_LANGUAGES_DIRECTORY = 'dist/languages';
10 |
11 | module.exports.create = function create() {
12 | const configs = configFactory.create();
13 |
14 | // Add uglifyJs plugin for each configuration
15 | configs.forEach(function(config) {
16 | config.output.path = path.resolve(__dirname, '../', OUTPUT_LANGUAGES_DIRECTORY);
17 | config.output.filename = '[name].min.js';
18 |
19 | config.plugins = [
20 | new webpack.optimize.UglifyJsPlugin({
21 | compressor: {
22 | pure_getters: true,
23 | unsafe: true,
24 | unsafe_comps: true,
25 | warnings: false,
26 | screw_ie8: true,
27 | },
28 | mangle: {
29 | screw_ie8: true,
30 | },
31 | output: {
32 | comments: /^!|@preserve|@license|@cc_on/i,
33 | screw_ie8: true,
34 | },
35 | })
36 | ];
37 | });
38 |
39 | return [].concat(configs);
40 | };
41 |
--------------------------------------------------------------------------------
/.config/loader/empty-loader.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.cacheable();
3 |
4 | return '';
5 | };
6 |
7 | module.exports.pitch = function() {
8 | this.cacheable();
9 |
10 | return '';
11 | };
12 |
--------------------------------------------------------------------------------
/.config/loader/exports-to-window-loader.js:
--------------------------------------------------------------------------------
1 | var loaderUtils = require('loader-utils');
2 | var FOOTER = '/*** EXPORTS FROM exports-to-window-loader ***/\n';
3 | var alreadyExported = {};
4 |
5 | module.exports = function(content, sourceMap) {
6 | if (this.cacheable) {
7 | this.cacheable();
8 | }
9 | var query = loaderUtils.getOptions(this) || {};
10 | var exports = [];
11 | var keys = Object.keys(query);
12 |
13 | keys.forEach(function(key) {
14 | if (!alreadyExported[key]) {
15 | alreadyExported[key] = true;
16 | exports.push("window['" + key + "'] = require('" + query[key] + "');");
17 | }
18 | });
19 |
20 | if (exports.length) {
21 | content = content + '\n\n' + FOOTER + exports.join('\n');
22 | }
23 |
24 | return content;
25 | }
26 |
--------------------------------------------------------------------------------
/.config/plugin/jasmine-html.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var path = require('path');
3 | var fs = require('fs');
4 | var HtmlWebpackPlugin = require('html-webpack-plugin');
5 | var jasmineCore = require('jasmine-core');
6 |
7 | var jasmineFiles = jasmineCore.files;
8 | var jasminePath = toRelativePath(jasmineFiles.path);
9 | var jasmineBootDir = toRelativePath(jasmineFiles.bootDir);
10 |
11 | function JasmineWebpackPlugin(options) {
12 | options = options || {};
13 |
14 | return new HtmlWebpackPlugin({
15 | inject: true,
16 | filename: options.filename || 'SpecRunner.html',
17 | template: path.join(__dirname, 'template.ejs'),
18 | baseJasminePath: options.baseJasminePath || '',
19 | jasmineJsFiles: toRelativeFiles(jasminePath, jasmineFiles.jsFiles).concat(toRelativeFiles(jasmineBootDir, jasmineFiles.bootFiles)),
20 | jasmineCssFiles: toRelativeFiles(jasminePath, jasmineFiles.cssFiles),
21 | externalJsFiles: options.externalJsFiles || [],
22 | externalCssFiles: options.externalCssFiles || [],
23 | minify: false,
24 | });
25 | }
26 |
27 | function toRelativePath(dirname) {
28 | return dirname.replace(process.cwd(), '').replace(/^\//, '');
29 | }
30 |
31 | function toRelativeFiles(dirname, files) {
32 | return files.map(function(file) {
33 | return path.join(dirname, file);
34 | });
35 | }
36 |
37 | module.exports = JasmineWebpackPlugin;
38 |
--------------------------------------------------------------------------------
/.config/plugin/template.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jasmine Spec Runner
6 | <% for (var i = 0; i < htmlWebpackPlugin.options.jasmineCssFiles.length; i++) { %>
7 |
8 | <% } %>
9 | <% for (var i = 0; i < htmlWebpackPlugin.options.externalCssFiles.length; i++) { %>
10 |
11 | <% } %>
12 | <% for (var i = 0; i < htmlWebpackPlugin.options.jasmineJsFiles.length; i++) { %>
13 |
14 | <% } %>
15 | <% for (var i = 0; i < htmlWebpackPlugin.options.externalJsFiles.length; i++) { %>
16 |
17 | <% } %>
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.config/walkontable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Config responsible for building Walkontable (bundled into `src/3rdparty/walkontable/dist/`):
5 | * - walkontable.js
6 | */
7 | const path = require('path');
8 | const webpack = require('webpack');
9 | const configFactory = require('./base');
10 | const JasmineHtml = require('./plugin/jasmine-html');
11 |
12 | const wotPath = path.resolve(__dirname, '../src/3rdparty/walkontable');
13 |
14 | module.exports.create = function create(envArgs) {
15 | const config = {
16 | devtool: 'cheap-module-source-map',
17 | output: {
18 | library: 'Walkontable',
19 | libraryTarget: 'var',
20 | filename: 'walkontable.js',
21 | path: path.resolve(wotPath, 'dist'),
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.js$/,
27 | loader: 'babel-loader',
28 | exclude: [
29 | /node_modules/,
30 | ],
31 | options: {
32 | cacheDirectory: true,
33 | },
34 | }
35 | ]
36 | },
37 | plugins: [
38 | // This helps ensure the builds are consistent if source code hasn't changed
39 | new webpack.optimize.OccurrenceOrderPlugin(),
40 | ],
41 | };
42 |
43 | return [].concat(config);
44 | }
45 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = false
17 | insert_final_newline = true
18 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | src/3rdparty/walkontable/test/lib/*
3 | Gruntfile.js
4 | dist/*
5 | lib/*
6 | src/3rdparty/walkontable/test/dist/*
7 | src/3rdparty/walkontable/dist/*
8 | test/lib/*
9 | test/dist/*
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=crlf
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 |
4 | ### Steps to reproduce
5 |
6 | 1.
7 | 2.
8 | 3.
9 |
10 | ### Demo
11 |
12 | https://jsfiddle.net/handsoncode/8ffpsqt6/
13 |
14 | ### Your environment
15 | * Handsontable version:
16 | * Browser Name and version:
17 | * Operating System:
18 |
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Context
2 |
3 |
4 | ### How has this been tested?
5 |
6 |
7 | ### Types of changes
8 |
9 | - [ ] Bug fix (non-breaking change which fixes an issue)
10 | - [ ] New feature or improvement (non-breaking change which adds functionality)
11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
12 | - [ ] Additional language file or change to the existing one (translations)
13 |
14 | ### Related issue(s):
15 | 1.
16 | 2.
17 | 3.
18 |
19 | ### Checklist:
20 |
21 |
22 | - [ ] My code follows the code style of this project,
23 | - [ ] My change requires a change to the documentation.
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .grunt
3 | .idea
4 | .vscode
5 | _SpecRunner.html
6 | test/E2ERunner.html
7 | test/UnitRunner.html
8 | test/MobileRunner.html
9 | test/dist/
10 | src/3rdparty/walkontable/test/dist/
11 | src/3rdparty/walkontable/dist/
12 | cars.sqlite
13 | dev*.html
14 | npm-debug.log
15 | !dist/README.md
16 | node_modules/
17 | commonjs/
18 | es/
19 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .grunt
3 | .config
4 | .github
5 | .idea
6 | _SpecRunner.html
7 | cars.sqlite
8 | dev.html
9 | webpack.config.js
10 |
11 | !dist/README.md
12 | !test/
13 | node_modules/
14 |
15 | # this directories are necessary for build PRO package
16 | #demo/
17 | #src/
18 | #lib/
19 | #test/
20 | #tasks/
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | sudo: false
4 |
5 | node_js:
6 | - '8'
7 |
8 | notifications:
9 | email: false
10 | slack:
11 | secure: LDlPC3IbXLbve4yq6kNznoIR5WgMhS2x+/KomgbVRDkUpySzyt27dUtRJqgkAqtkHhDhd0FbpROZNPriQVJI8nLLux0xdl9dngy6Lazl2qVzShrtdN01I3lVxysJlDM3phKHQCPmGhd/1v1qjfTAu45tM9pnYACSnDfdfh46b84=
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | All releases are described at https://github.com/handsontable/handsontable/releases
2 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | handsontable.com
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2012-2014 Marcin Warpechowski
4 | Copyright (c) 2015 Handsoncode sp. z o.o.
5 | Copyright (c) 2019 Rathbone Labs, LLC
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining
8 | a copy of this software and associated documentation files (the
9 | 'Software'), to deal in the Software without restriction, including
10 | without limitation the rights to use, copy, modify, merge, publish,
11 | distribute, sublicense, and/or sell copies of the Software, and to
12 | permit persons to whom the Software is furnished to do so, subject to
13 | the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "handsontable",
3 | "description": "Spreadsheet-like data grid editor that provides copy/paste functionality compatible with Excel/Google Docs",
4 | "main": ["./dist/handsontable.js", "./dist/handsontable.css"],
5 | "homepage": "http://handsontable.com/",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/handsontable/handsontable.git"
9 | },
10 | "authors": [
11 | "Handsoncode", "Handsoncode "
12 | ],
13 | "keywords": [
14 | "data",
15 | "grid",
16 | "table",
17 | "editor",
18 | "grid-editor",
19 | "data-grid",
20 | "data-table",
21 | "spreadsheet",
22 | "excel",
23 | "tabular-data",
24 | "edit-cell",
25 | "editable-table",
26 | "data-spreadsheet"
27 | ],
28 | "ignore": [
29 | "**/.*",
30 | "components",
31 | "demo",
32 | "node_modules",
33 | "src",
34 | "test"
35 | ],
36 | "dependencies": {
37 | "moment": "^2.13.0",
38 | "numbro": "^2.0.6",
39 | "pikaday": "^1.4.0"
40 | },
41 | "devDependencies": {
42 | "chroma-js": "~0.5.6"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/dist/handsontable.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"handsontable.css","sourceRoot":""}
--------------------------------------------------------------------------------
/dist/moment/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) JS Foundation and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/dist/numbro/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Benjamin Van Ryseghem
2 | Copyright (c) 2015-2017 Företagsplatsen
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/dist/numbro/LICENSE-Numeraljs:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Adam Draper
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/dist/numbro/languages/bg.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;((n="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).numbro||(n.numbro={})).bg=e()}}(function(){return function e(n,o,r){function i(f,u){if(!o[f]){if(!n[f]){var d="function"==typeof require&&require;if(!u&&d)return d(f,!0);if(t)return t(f,!0);var l=new Error("Cannot find module '"+f+"'");throw l.code="MODULE_NOT_FOUND",l}var a=o[f]={exports:{}};n[f][0].call(a.exports,function(e){var o=n[f][1][e];return i(o||e)},a,a.exports,e,n,o,r)}return o[f].exports}for(var t="function"==typeof require&&require,f=0;f=20?"ste":"de"},currency:{symbol:"€",position:"postfix",code:"EUR"},currencyFormat:{thousandSeparated:!0,totalLength:4,spaceSeparated:!0,average:!0},formats:{fourDigits:{totalLength:4,spaceSeparated:!0,average:!0},fullWithTwoDecimals:{output:"currency",mantissa:2,spaceSeparated:!0,thousandSeparated:!0},fullWithTwoDecimalsNoCurrency:{mantissa:2,thousandSeparated:!0},fullWithNoDecimals:{output:"currency",spaceSeparated:!0,thousandSeparated:!0,mantissa:0}}}},{}]},{},[1])(1)});
2 | //# sourceMappingURL=nl-BE.min.js.map
3 |
--------------------------------------------------------------------------------
/dist/numbro/languages/nl-NL.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;((t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).numbro||(t.numbro={})).nlNL=e()}}(function(){return function e(t,n,r){function o(i,u){if(!n[i]){if(!t[i]){var f="function"==typeof require&&require;if(!u&&f)return f(i,!0);if(a)return a(i,!0);var s=new Error("Cannot find module '"+i+"'");throw s.code="MODULE_NOT_FOUND",s}var l=n[i]={exports:{}};t[i][0].call(l.exports,function(e){var n=t[i][1][e];return o(n||e)},l,l.exports,e,t,n,r)}return n[i].exports}for(var a="function"==typeof require&&require,i=0;i=20?"ste":"de"},currency:{symbol:"€",position:"prefix",code:"EUR"},currencyFormat:{thousandSeparated:!0,totalLength:4,spaceSeparated:!0,average:!0},formats:{fourDigits:{totalLength:4,spaceSeparated:!0,average:!0},fullWithTwoDecimals:{output:"currency",mantissa:2,spaceSeparated:!0,thousandSeparated:!0},fullWithTwoDecimalsNoCurrency:{mantissa:2,thousandSeparated:!0},fullWithNoDecimals:{output:"currency",spaceSeparated:!0,thousandSeparated:!0,mantissa:0}}}},{}]},{},[1])(1)});
2 | //# sourceMappingURL=nl-NL.min.js.map
3 |
--------------------------------------------------------------------------------
/dist/numbro/languages/nn.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;((n="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).numbro||(n.numbro={})).nn=e()}}(function(){return function e(n,r,o){function i(f,u){if(!r[f]){if(!n[f]){var l="function"==typeof require&&require;if(!u&&l)return l(f,!0);if(t)return t(f,!0);var d=new Error("Cannot find module '"+f+"'");throw d.code="MODULE_NOT_FOUND",d}var a=r[f]={exports:{}};n[f][0].call(a.exports,function(e){var r=n[f][1][e];return i(r||e)},a,a.exports,e,n,r,o)}return r[f].exports}for(var t="function"==typeof require&&require,f=0;f=1.7",
34 | "bootstrap-typeahead.js": "2.1.1",
35 | "contextMenu": ">=1.5.25"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/hot.config.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment');
2 | const packageBody = require('./package.json');
3 |
4 | module.exports = {
5 | HOT_FILENAME: 'handsontable',
6 | HOT_VERSION: packageBody.version,
7 | HOT_BASE_VERSION: '',
8 | HOT_PACKAGE_TYPE: 'ce',
9 | HOT_PACKAGE_NAME: packageBody.name,
10 | HOT_BUILD_DATE: moment().format('DD/MM/YYYY HH:mm:ss'),
11 | HOT_RELEASE_DATE: '19/12/2018',
12 | };
13 |
--------------------------------------------------------------------------------
/src/3rdparty/walkontable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "walkontable",
3 | "description": "Table renderer for Handsontable",
4 | "homepage": "http://handsontable.com/",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/handsontable/handsontable.git"
8 | },
9 | "bugs": {
10 | "url": "https://github.com/handsontable/handsontable/issues"
11 | },
12 | "author": "Handsoncode ",
13 | "version": "0.0.1",
14 | "license": "MIT"
15 | }
16 |
--------------------------------------------------------------------------------
/src/3rdparty/walkontable/src/overlay/debug.js:
--------------------------------------------------------------------------------
1 | import { addClass } from './../../../../helpers/dom/element';
2 | import Overlay from './_base';
3 |
4 | /**
5 | * A overlay that renders ALL available rows & columns positioned on top of the original Walkontable instance and all other overlays.
6 | * Used for debugging purposes to see if the other overlays (that render only part of the rows & columns) are positioned correctly
7 | *
8 | * @class DebugOverlay
9 | */
10 | class DebugOverlay extends Overlay {
11 | /**
12 | * @param {Walkontable} wotInstance
13 | */
14 | constructor(wotInstance) {
15 | super(wotInstance);
16 |
17 | this.clone = this.makeClone(Overlay.CLONE_DEBUG);
18 | this.clone.wtTable.holder.style.opacity = 0.4;
19 | this.clone.wtTable.holder.style.textShadow = '0 0 2px #ff0000';
20 |
21 | addClass(this.clone.wtTable.holder.parentNode, 'wtDebugVisible');
22 | }
23 | }
24 |
25 | Overlay.registerOverlay(Overlay.CLONE_DEBUG, DebugOverlay);
26 |
27 | export default DebugOverlay;
28 |
--------------------------------------------------------------------------------
/src/3rdparty/walkontable/test/SpecRunner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jasmine Spec Runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/3rdparty/walkontable/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-unresolved */
2 | import window from 'window';
3 | import * as common from './common';
4 |
5 | // Export all helpers to the window.
6 | Object.keys(common).forEach((key) => {
7 | window[key] = common[key];
8 | });
9 |
--------------------------------------------------------------------------------
/src/3rdparty/walkontable/test/spec/filter/column.spec.js:
--------------------------------------------------------------------------------
1 | describe('Walkontable.ColumnFilter', () => {
2 | describe('offsettedTH', () => {
3 | it('should do nothing if row header is not visible', () => {
4 | const filter = new Walkontable.ColumnFilter();
5 | filter.countTH = 0;
6 | expect(filter.offsettedTH(1)).toEqual(1);
7 | });
8 |
9 | it('should decrease n by 1 if row header is visible', () => {
10 | const filter = new Walkontable.ColumnFilter();
11 | filter.countTH = 1;
12 | expect(filter.offsettedTH(1)).toEqual(0);
13 | });
14 | });
15 |
16 | describe('unOffsettedTH', () => {
17 | it('should do nothing if row header is not visible', () => {
18 | const filter = new Walkontable.ColumnFilter();
19 | filter.countTH = 0;
20 | expect(filter.unOffsettedTH(1)).toEqual(1);
21 | });
22 |
23 | it('should increase n by 1 if row header is visible', () => {
24 | const filter = new Walkontable.ColumnFilter();
25 | filter.countTH = 1;
26 | expect(filter.unOffsettedTH(0)).toEqual(1);
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/3rdparty/walkontable/test/spec/index.js:
--------------------------------------------------------------------------------
1 | [
2 | require.context('.', true, /\.spec\.js$/),
3 | ].forEach((req) => {
4 | req.keys().forEach((key) => {
5 | req(key);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/src/cellTypes/autocompleteType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 | import { getValidator } from './../validators';
4 |
5 | const CELL_TYPE = 'autocomplete';
6 |
7 | export default {
8 | editor: getEditor(CELL_TYPE),
9 | renderer: getRenderer(CELL_TYPE),
10 | validator: getValidator(CELL_TYPE),
11 | };
12 |
--------------------------------------------------------------------------------
/src/cellTypes/checkboxType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 |
4 | const CELL_TYPE = 'checkbox';
5 |
6 | export default {
7 | editor: getEditor(CELL_TYPE),
8 | renderer: getRenderer(CELL_TYPE),
9 | };
10 |
--------------------------------------------------------------------------------
/src/cellTypes/dateType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 | import { getValidator } from './../validators';
4 |
5 | const CELL_TYPE = 'date';
6 |
7 | export default {
8 | editor: getEditor(CELL_TYPE),
9 | // displays small gray arrow on right side of the cell
10 | renderer: getRenderer('autocomplete'),
11 | validator: getValidator(CELL_TYPE),
12 | };
13 |
--------------------------------------------------------------------------------
/src/cellTypes/dropdownType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 | import { getValidator } from './../validators';
4 |
5 | const CELL_TYPE = 'dropdown';
6 |
7 | export default {
8 | editor: getEditor(CELL_TYPE),
9 | // displays small gray arrow on right side of the cell
10 | renderer: getRenderer('autocomplete'),
11 | validator: getValidator('autocomplete'),
12 | };
13 |
--------------------------------------------------------------------------------
/src/cellTypes/handsontableType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 |
4 | const CELL_TYPE = 'handsontable';
5 |
6 | export default {
7 | editor: getEditor(CELL_TYPE),
8 | // displays small gray arrow on right side of the cell
9 | renderer: getRenderer('autocomplete'),
10 | };
11 |
--------------------------------------------------------------------------------
/src/cellTypes/numericType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 | import { getValidator } from './../validators';
4 |
5 | const CELL_TYPE = 'numeric';
6 |
7 | export default {
8 | editor: getEditor(CELL_TYPE),
9 | renderer: getRenderer(CELL_TYPE),
10 | validator: getValidator(CELL_TYPE),
11 | dataType: 'number',
12 | };
13 |
--------------------------------------------------------------------------------
/src/cellTypes/passwordType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 |
4 | const CELL_TYPE = 'password';
5 |
6 | export default {
7 | editor: getEditor(CELL_TYPE),
8 | renderer: getRenderer(CELL_TYPE),
9 | copyable: false,
10 | };
11 |
--------------------------------------------------------------------------------
/src/cellTypes/textType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 |
4 | const CELL_TYPE = 'text';
5 |
6 | export default {
7 | editor: getEditor(CELL_TYPE),
8 | renderer: getRenderer(CELL_TYPE),
9 | };
10 |
--------------------------------------------------------------------------------
/src/cellTypes/timeType.js:
--------------------------------------------------------------------------------
1 | import { getEditor } from './../editors';
2 | import { getRenderer } from './../renderers';
3 | import { getValidator } from './../validators';
4 |
5 | const CELL_TYPE = 'time';
6 |
7 | export default {
8 | editor: getEditor('text'),
9 | // displays small gray arrow on right side of the cell
10 | renderer: getRenderer('text'),
11 | validator: getValidator(CELL_TYPE),
12 | };
13 |
--------------------------------------------------------------------------------
/src/editors/checkboxEditor.js:
--------------------------------------------------------------------------------
1 | import BaseEditor from './_baseEditor';
2 | import { hasClass } from './../helpers/dom/element';
3 |
4 | /**
5 | * @private
6 | * @editor CheckboxEditor
7 | * @class CheckboxEditor
8 | */
9 | class CheckboxEditor extends BaseEditor {
10 | beginEditing(initialValue, event) {
11 | // Just some events connected with checkbox editor are delegated here. Some `keydown` events like `enter` and `space` key press
12 | // are handled inside `checkboxRenderer`. Some events come here from `editorManager`. Below `if` statement was created by author
13 | // for purpose of handling only `doubleclick` event which may be done on a cell with checkbox.
14 |
15 | if (event && event.type === 'mouseup') {
16 | const checkbox = this.TD.querySelector('input[type="checkbox"]');
17 |
18 | if (!hasClass(checkbox, 'htBadValue')) {
19 | checkbox.click();
20 | }
21 | }
22 | }
23 |
24 | finishEditing() {}
25 | init() {}
26 | open() {}
27 | close() {}
28 | getValue() {}
29 | setValue() {}
30 | focus() {}
31 | }
32 |
33 | export default CheckboxEditor;
34 |
--------------------------------------------------------------------------------
/src/editors/dropdownEditor.js:
--------------------------------------------------------------------------------
1 | import AutocompleteEditor from './autocompleteEditor';
2 | import Hooks from './../pluginHooks';
3 |
4 | /**
5 | * @private
6 | * @editor DropdownEditor
7 | * @class DropdownEditor
8 | * @dependencies AutocompleteEditor
9 | */
10 | class DropdownEditor extends AutocompleteEditor {
11 | prepare(row, col, prop, td, originalValue, cellProperties) {
12 | super.prepare(row, col, prop, td, originalValue, cellProperties);
13 | this.cellProperties.filter = false;
14 | this.cellProperties.strict = true;
15 | }
16 | }
17 |
18 | Hooks.getSingleton().add('beforeValidate', function(value, row, col) {
19 | const cellMeta = this.getCellMeta(row, this.propToCol(col));
20 |
21 | if (cellMeta.editor === DropdownEditor) {
22 | if (cellMeta.strict === void 0) {
23 | cellMeta.filter = false;
24 | cellMeta.strict = true;
25 | }
26 | }
27 | });
28 |
29 | export default DropdownEditor;
30 |
--------------------------------------------------------------------------------
/src/editors/numericEditor.js:
--------------------------------------------------------------------------------
1 | import TextEditor from './textEditor';
2 |
3 | /**
4 | * @private
5 | * @editor NumericEditor
6 | * @class NumericEditor
7 | */
8 | class NumericEditor extends TextEditor {}
9 |
10 | export default NumericEditor;
11 |
--------------------------------------------------------------------------------
/src/editors/passwordEditor.js:
--------------------------------------------------------------------------------
1 | import { empty } from './../helpers/dom/element';
2 | import TextEditor from './textEditor';
3 |
4 | /**
5 | * @private
6 | * @editor PasswordEditor
7 | * @class PasswordEditor
8 | * @dependencies TextEditor
9 | */
10 | class PasswordEditor extends TextEditor {
11 | createElements() {
12 | super.createElements();
13 |
14 | this.TEXTAREA = document.createElement('input');
15 | this.TEXTAREA.setAttribute('type', 'password');
16 | this.TEXTAREA.className = 'handsontableInput';
17 | this.textareaStyle = this.TEXTAREA.style;
18 | this.textareaStyle.width = 0;
19 | this.textareaStyle.height = 0;
20 |
21 | empty(this.TEXTAREA_PARENT);
22 | this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
23 | }
24 | }
25 |
26 | export default PasswordEditor;
27 |
--------------------------------------------------------------------------------
/src/helpers/console.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable no-restricted-globals */
3 |
4 | /**
5 | * "In Internet Explorer 9 (and 8), the console object is only exposed when the developer tools are opened
6 | * for a particular tab."
7 | *
8 | * Source: https://stackoverflow.com/a/5473193
9 | */
10 |
11 | import { isDefined } from './mixed';
12 |
13 | /**
14 | * Logs message to the console if the `console` object is exposed.
15 | *
16 | * @param {...*} args Values which will be logged.
17 | */
18 | export function log(...args) {
19 | if (isDefined(console)) {
20 | console.log(...args);
21 | }
22 | }
23 |
24 | /**
25 | * Logs warn to the console if the `console` object is exposed.
26 | *
27 | * @param {...*} args Values which will be logged.
28 | */
29 | export function warn(...args) {
30 | if (isDefined(console)) {
31 | console.warn(...args);
32 | }
33 | }
34 |
35 | /**
36 | * Logs info to the console if the `console` object is exposed.
37 | *
38 | * @param {...*} args Values which will be logged.
39 | */
40 | export function info(...args) {
41 | if (isDefined(console)) {
42 | console.info(...args);
43 | }
44 | }
45 |
46 | /**
47 | * Logs error to the console if the `console` object is exposed.
48 | *
49 | * @param {...*} args Values which will be logged.
50 | */
51 | export function error(...args) {
52 | if (isDefined(console)) {
53 | console.error(...args);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/helpers/date.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 |
3 | /**
4 | * Get normalized Date object for the ISO formatted date strings.
5 | * Natively, the date object parsed from a ISO 8601 string will be offsetted by the timezone difference, which may result in returning a wrong date.
6 | * See: Github issue #3338.
7 | *
8 | * @param {String} dateString String representing the date.
9 | * @returns {Date} The proper Date object.
10 | */
11 | export function getNormalizedDate(dateString) {
12 | const nativeDate = new Date(dateString);
13 |
14 | // NaN if dateString is not in ISO format
15 | if (!isNaN(new Date(`${dateString}T00:00`).getDate())) {
16 |
17 | // Compensate timezone offset
18 | return new Date(nativeDate.getTime() + (nativeDate.getTimezoneOffset() * 60000));
19 | }
20 |
21 | return nativeDate;
22 | }
23 |
--------------------------------------------------------------------------------
/src/helpers/setting.js:
--------------------------------------------------------------------------------
1 | import { inherit } from './object';
2 | /* eslint-disable import/prefer-default-export */
3 | /**
4 | * Factory for columns constructors.
5 | *
6 | * @param {Object} GridSettings
7 | * @param {Array} conflictList
8 | * @return {Object} ColumnSettings
9 | */
10 | export function columnFactory(GridSettings, conflictList) {
11 | function ColumnSettings() {}
12 |
13 | inherit(ColumnSettings, GridSettings);
14 |
15 | // Clear conflict settings
16 | for (let i = 0, len = conflictList.length; i < len; i++) {
17 | ColumnSettings.prototype[conflictList[i]] = void 0;
18 | }
19 |
20 | return ColumnSettings;
21 | }
22 |
--------------------------------------------------------------------------------
/src/helpers/templateLiteralTag.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import { arrayReduce } from '../helpers/array';
3 |
4 | /**
5 | * Tags a multiline string and return new one without line break characters and following spaces.
6 | *
7 | * @param {Array} strings Parts of the entire string without expressions.
8 | * @param {...String} expressions Expressions converted to strings, which are added to the entire string.
9 | * @returns {String}
10 | */
11 | export function toSingleLine(strings, ...expressions) {
12 | const result = arrayReduce(strings, (previousValue, currentValue, index) => {
13 |
14 | const valueWithoutWhiteSpaces = currentValue.replace(/(?:\r?\n\s+)/g, '');
15 | const expressionForIndex = expressions[index] ? expressions[index] : '';
16 |
17 | return previousValue + valueWithoutWhiteSpaces + expressionForIndex;
18 | }, '');
19 |
20 | return result.trim();
21 | }
22 |
--------------------------------------------------------------------------------
/src/helpers/wrappers/jquery.js:
--------------------------------------------------------------------------------
1 | export default function jQueryWrapper(Handsontable) {
2 | const jQuery = typeof window === 'undefined' ? false : window.jQuery;
3 |
4 | if (!jQuery) {
5 | return;
6 | }
7 |
8 | jQuery.fn.handsontable = function(action, ...args) {
9 | const $this = this.first(); // Use only first element from list
10 | let instance = $this.data('handsontable');
11 |
12 | // Init case
13 | if (typeof action !== 'string') {
14 | const userSettings = action || {};
15 |
16 | if (instance) {
17 | instance.updateSettings(userSettings);
18 |
19 | } else {
20 | instance = new Handsontable.Core($this[0], userSettings);
21 | $this.data('handsontable', instance);
22 | instance.init();
23 | }
24 |
25 | return $this;
26 | }
27 |
28 | let output;
29 |
30 | // Action case
31 | if (instance) {
32 | if (typeof instance[action] !== 'undefined') {
33 | output = instance[action].call(instance, ...args);
34 |
35 | if (action === 'destroy') {
36 | $this.removeData();
37 | }
38 |
39 | } else {
40 | throw new Error(`Handsontable do not provide action: ${action}`);
41 | }
42 | }
43 |
44 | return output;
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/src/i18n/languages/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 |
3 | import deCH from './de-CH';
4 | import deDE from './de-DE';
5 | import enUS from './en-US';
6 | import esMX from './es-MX';
7 | import frFR from './fr-FR';
8 | import itIT from './it-IT';
9 | import jaJP from './ja-JP';
10 | import koKR from './ko-KR';
11 | import lvLV from './lv-LV';
12 | import nbNO from './nb-NO';
13 | import nlNL from './nl-NL';
14 | import plPL from './pl-PL';
15 | import ptBR from './pt-BR';
16 | import ruRU from './ru-RU';
17 | import zhCN from './zh-CN';
18 | import zhTW from './zh-TW';
19 |
20 | export {
21 | deCH,
22 | deDE,
23 | enUS,
24 | esMX,
25 | frFR,
26 | itIT,
27 | jaJP,
28 | koKR,
29 | lvLV,
30 | nbNO,
31 | nlNL,
32 | plPL,
33 | ptBR,
34 | ruRU,
35 | zhCN,
36 | zhTW
37 | };
38 |
--------------------------------------------------------------------------------
/src/i18n/phraseFormatters/index.js:
--------------------------------------------------------------------------------
1 | import staticRegister from './../../utils/staticRegister';
2 | import pluralizeFn from './pluralize';
3 |
4 | const {
5 | register: registerGloballyPhraseFormatter,
6 | getValues: getGlobalPhraseFormatters,
7 | } = staticRegister('phraseFormatters');
8 |
9 | /**
10 | * Register phrase formatter.
11 | *
12 | * @param {String} name Name of formatter.
13 | * @param {Function} formatterFn Function which will be applied on phrase propositions. It will transform them if it's possible.
14 | */
15 | export function register(name, formatterFn) {
16 | registerGloballyPhraseFormatter(name, formatterFn);
17 | }
18 |
19 | /**
20 | * Get all registered previously formatters.
21 | *
22 | * @returns {Array}
23 | */
24 | export function getAll() {
25 | return getGlobalPhraseFormatters();
26 | }
27 |
28 | export {
29 | register as registerPhraseFormatter,
30 | getAll as getPhraseFormatters
31 | };
32 |
33 | register('pluralize', pluralizeFn);
34 |
--------------------------------------------------------------------------------
/src/i18n/phraseFormatters/pluralize.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Try to choose plural form from available phrase propositions.
3 | *
4 | * @param {Array} phrasePropositions List of phrases propositions.
5 | * @param {number} pluralForm Number determining which phrase form should be used.
6 | *
7 | * @returns {String|Array} One particular phrase if it's possible, list of unchanged phrase propositions otherwise.
8 | */
9 | export default function pluralize(phrasePropositions, pluralForm) {
10 | const isPluralizable = Array.isArray(phrasePropositions) && Number.isInteger(pluralForm);
11 |
12 | if (isPluralizable) {
13 | return phrasePropositions[pluralForm];
14 | }
15 |
16 | return phrasePropositions;
17 | }
18 |
--------------------------------------------------------------------------------
/src/i18n/phraseFormatters/substituteVariables.js:
--------------------------------------------------------------------------------
1 | import { substitute } from './../../helpers/string';
2 |
3 | /**
4 | * Try to substitute variable inside phrase propositions.
5 | *
6 | * @param {Array} phrasePropositions List of phrases propositions.
7 | * @param {Object} zippedVariablesAndValues Object containing variables and corresponding values.
8 | *
9 | * @returns {String} Phrases with substituted variables if it's possible, list of unchanged phrase propositions otherwise.
10 | */
11 | export default function substituteVariables(phrasePropositions, zippedVariablesAndValues) {
12 | if (Array.isArray(phrasePropositions)) {
13 | return phrasePropositions.map(phraseProposition => substituteVariables(phraseProposition, zippedVariablesAndValues));
14 | }
15 |
16 | return substitute(phrasePropositions, zippedVariablesAndValues);
17 | }
18 |
--------------------------------------------------------------------------------
/src/mixins/localHooks.js:
--------------------------------------------------------------------------------
1 | import { arrayEach } from './../helpers/array';
2 | import { defineGetter } from './../helpers/object';
3 |
4 | const MIXIN_NAME = 'localHooks';
5 |
6 | /**
7 | * Mixin object to extend objects functionality for local hooks.
8 | *
9 | * @type {Object}
10 | */
11 | const localHooks = {
12 | /**
13 | * Internal hooks storage.
14 | */
15 | _localHooks: Object.create(null),
16 |
17 | /**
18 | * Add hook to the collection.
19 | *
20 | * @param {String} key Hook name.
21 | * @param {Function} callback Hook callback
22 | * @returns {Object}
23 | */
24 | addLocalHook(key, callback) {
25 | if (!this._localHooks[key]) {
26 | this._localHooks[key] = [];
27 | }
28 | this._localHooks[key].push(callback);
29 |
30 | return this;
31 | },
32 |
33 | /**
34 | * Run hooks.
35 | *
36 | * @param {String} key Hook name.
37 | * @param {*} params
38 | */
39 | runLocalHooks(key, ...params) {
40 | if (this._localHooks[key]) {
41 | arrayEach(this._localHooks[key], callback => callback.apply(this, params));
42 | }
43 | },
44 |
45 | /**
46 | * Clear all added hooks.
47 | *
48 | * @returns {Object}
49 | */
50 | clearLocalHooks() {
51 | this._localHooks = {};
52 |
53 | return this;
54 | },
55 | };
56 |
57 | defineGetter(localHooks, 'MIXIN_NAME', MIXIN_NAME, {
58 | writable: false,
59 | enumerable: false,
60 | });
61 |
62 | export default localHooks;
63 |
--------------------------------------------------------------------------------
/src/multiMap.js:
--------------------------------------------------------------------------------
1 | function MultiMap() {
2 | const map = {
3 | arrayMap: [],
4 | weakMap: new WeakMap(),
5 | };
6 |
7 | return {
8 | get(key) {
9 | if (canBeAnArrayMapKey(key)) {
10 | return map.arrayMap[key];
11 | } else if (canBeAWeakMapKey(key)) {
12 | return map.weakMap.get(key);
13 | }
14 | },
15 |
16 | set(key, value) {
17 | if (canBeAnArrayMapKey(key)) {
18 | map.arrayMap[key] = value;
19 | } else if (canBeAWeakMapKey(key)) {
20 | map.weakMap.set(key, value);
21 | } else {
22 | throw new Error('Invalid key type');
23 | }
24 | },
25 |
26 | delete(key) {
27 | if (canBeAnArrayMapKey(key)) {
28 | delete map.arrayMap[key];
29 | } else if (canBeAWeakMapKey(key)) {
30 | map.weakMap.delete(key);
31 | }
32 | },
33 | };
34 |
35 | function canBeAnArrayMapKey(obj) {
36 | return obj !== null && !isNaNSymbol(obj) && (typeof obj === 'string' || typeof obj === 'number');
37 | }
38 |
39 | function canBeAWeakMapKey(obj) {
40 | return obj !== null && (typeof obj === 'object' || typeof obj === 'function');
41 | }
42 |
43 | function isNaNSymbol(obj) {
44 | /* eslint-disable no-self-compare */
45 | return obj !== obj; // NaN === NaN is always false
46 | }
47 | }
48 |
49 | export default MultiMap;
50 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/rowsMapper.js:
--------------------------------------------------------------------------------
1 | import arrayMapper from '../../mixins/arrayMapper';
2 | import { mixin } from '../../helpers/object';
3 | import { rangeEach } from '../../helpers/number';
4 |
5 | /**
6 | * @class RowsMapper
7 | */
8 | class RowsMapper {
9 | /**
10 | * Reset current map array and create new one.
11 | *
12 | * @param {Number} [length] Custom generated map length.
13 | */
14 | createMap(length) {
15 | const originLength = length === void 0 ? this._arrayMap.length : length;
16 |
17 | this._arrayMap.length = 0;
18 |
19 | rangeEach(originLength - 1, (itemIndex) => {
20 | this._arrayMap[itemIndex] = itemIndex;
21 | });
22 | }
23 |
24 | /**
25 | * Destroy class.
26 | */
27 | destroy() {
28 | this._arrayMap = null;
29 | }
30 | }
31 |
32 | mixin(RowsMapper, arrayMapper);
33 |
34 | export default RowsMapper;
35 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/sortService/engine.js:
--------------------------------------------------------------------------------
1 | import mergeSort from '../../../utils/sortingAlgorithms/mergeSort';
2 | import { getRootComparator } from './registry';
3 |
4 | export const DO_NOT_SWAP = 0;
5 | export const FIRST_BEFORE_SECOND = -1;
6 | export const FIRST_AFTER_SECOND = 1;
7 |
8 | export function sort(indexesWithData, rootComparatorId, ...argsForRootComparator) {
9 | const rootComparator = getRootComparator(rootComparatorId);
10 |
11 | mergeSort(indexesWithData, rootComparator(...argsForRootComparator));
12 | }
13 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/sortService/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | registerRootComparator,
3 | getRootComparator,
4 | getCompareFunctionFactory
5 | } from './registry';
6 |
7 | import {
8 | FIRST_AFTER_SECOND,
9 | FIRST_BEFORE_SECOND,
10 | DO_NOT_SWAP,
11 | sort
12 | } from './engine';
13 |
14 | export {
15 | registerRootComparator,
16 | getRootComparator,
17 | getCompareFunctionFactory,
18 | FIRST_AFTER_SECOND,
19 | FIRST_BEFORE_SECOND,
20 | DO_NOT_SWAP,
21 | sort
22 | };
23 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/test/columnSorting.types.ts:
--------------------------------------------------------------------------------
1 | import * as Handsontable from 'handsontable';
2 |
3 | const columnSorting = Handsontable.plugins.ColumnSorting;
4 |
5 | columnSorting.clearSort();
6 |
7 | columnSorting.getSortConfig();
8 | columnSorting.getSortConfig(0);
9 |
10 | const sortConfig0 = columnSorting.getSortConfig(0);
11 |
12 | if (typeof sortConfig0 !== 'undefined') {
13 | sortConfig0.column;
14 | sortConfig0.sortOrder;
15 | }
16 |
17 | const sortConfigs = columnSorting.getSortConfig();
18 |
19 | sortConfigs[0].column;
20 | sortConfigs[0].sortOrder;
21 |
22 | columnSorting.setSortConfig();
23 | columnSorting.setSortConfig({ column: 0, sortOrder: 'asc' });
24 | columnSorting.setSortConfig([{ column: 0, sortOrder: 'asc' }]);
25 | columnSorting.setSortConfig([]);
26 |
27 | columnSorting.isSorted();
28 |
29 | columnSorting.sort();
30 | columnSorting.sort({ column: 0, sortOrder: 'asc' });
31 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/test/sortFunction/date.unit.js:
--------------------------------------------------------------------------------
1 | import { compareFunctionFactory as dateSort } from 'handsontable/plugins/columnSorting/sortFunction/date';
2 |
3 | it('dateSort comparing function shouldn\'t change order when comparing empty string, null and undefined', () => {
4 | expect(dateSort('asc', {}, {})(null, null)).toEqual(0);
5 | expect(dateSort('desc', {}, {})(null, null)).toEqual(0);
6 |
7 | expect(dateSort('asc', {}, {})('', '')).toEqual(0);
8 | expect(dateSort('desc', {}, {})('', '')).toEqual(0);
9 |
10 | expect(dateSort('asc', {}, {})(undefined, undefined)).toEqual(0);
11 | expect(dateSort('desc', {}, {})(undefined, undefined)).toEqual(0);
12 |
13 | expect(dateSort('asc', {}, {})('', null)).toEqual(0);
14 | expect(dateSort('desc', {}, {})('', null)).toEqual(0);
15 | expect(dateSort('asc', {}, {})(null, '')).toEqual(0);
16 | expect(dateSort('desc', {}, {})(null, '')).toEqual(0);
17 |
18 | expect(dateSort('asc', {}, {})('', undefined)).toEqual(0);
19 | expect(dateSort('desc', {}, {})('', undefined)).toEqual(0);
20 | expect(dateSort('asc', {}, {})(undefined, '')).toEqual(0);
21 | expect(dateSort('desc', {}, {})(undefined, '')).toEqual(0);
22 |
23 | expect(dateSort('asc', {}, {})(null, undefined)).toEqual(0);
24 | expect(dateSort('desc', {}, {})(null, undefined)).toEqual(0);
25 | expect(dateSort('asc', {}, {})(undefined, null)).toEqual(0);
26 | expect(dateSort('desc', {}, {})(undefined, null)).toEqual(0);
27 | });
28 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/test/sortFunction/default.unit.js:
--------------------------------------------------------------------------------
1 | import { compareFunctionFactory as defaultSort } from 'handsontable/plugins/columnSorting/sortFunction/default';
2 |
3 | it('defaultSort comparing function shouldn\'t change order when comparing empty string, null and undefined', () => {
4 | expect(defaultSort('asc', {}, {})(null, null)).toEqual(0);
5 | expect(defaultSort('desc', {}, {})(null, null)).toEqual(0);
6 |
7 | expect(defaultSort('asc', {}, {})('', '')).toEqual(0);
8 | expect(defaultSort('desc', {}, {})('', '')).toEqual(0);
9 |
10 | expect(defaultSort('asc', {}, {})(undefined, undefined)).toEqual(0);
11 | expect(defaultSort('desc', {}, {})(undefined, undefined)).toEqual(0);
12 |
13 | expect(defaultSort('asc', {}, {})('', null)).toEqual(0);
14 | expect(defaultSort('desc', {}, {})('', null)).toEqual(0);
15 | expect(defaultSort('asc', {}, {})(null, '')).toEqual(0);
16 | expect(defaultSort('desc', {}, {})(null, '')).toEqual(0);
17 |
18 | expect(defaultSort('asc', {}, {})('', undefined)).toEqual(0);
19 | expect(defaultSort('desc', {}, {})('', undefined)).toEqual(0);
20 | expect(defaultSort('asc', {}, {})(undefined, '')).toEqual(0);
21 | expect(defaultSort('desc', {}, {})(undefined, '')).toEqual(0);
22 |
23 | expect(defaultSort('asc', {}, {})(null, undefined)).toEqual(0);
24 | expect(defaultSort('desc', {}, {})(null, undefined)).toEqual(0);
25 | expect(defaultSort('asc', {}, {})(undefined, null)).toEqual(0);
26 | expect(defaultSort('desc', {}, {})(undefined, null)).toEqual(0);
27 | });
28 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/test/sortFunction/numeric.unit.js:
--------------------------------------------------------------------------------
1 | import { compareFunctionFactory as numericSort } from 'handsontable/plugins/columnSorting/sortFunction/numeric';
2 |
3 | it('numericSort comparing function shouldn\'t change order when comparing empty string, null and undefined', () => {
4 | expect(numericSort('asc', {}, {})(null, null)).toEqual(0);
5 | expect(numericSort('desc', {}, {})(null, null)).toEqual(0);
6 |
7 | expect(numericSort('asc', {}, {})('', '')).toEqual(0);
8 | expect(numericSort('desc', {}, {})('', '')).toEqual(0);
9 |
10 | expect(numericSort('asc', {}, {})(undefined, undefined)).toEqual(0);
11 | expect(numericSort('desc', {}, {})(undefined, undefined)).toEqual(0);
12 |
13 | expect(numericSort('asc', {}, {})('', null)).toEqual(0);
14 | expect(numericSort('desc', {}, {})('', null)).toEqual(0);
15 | expect(numericSort('asc', {}, {})(null, '')).toEqual(0);
16 | expect(numericSort('desc', {}, {})(null, '')).toEqual(0);
17 |
18 | expect(numericSort('asc', {}, {})('', undefined)).toEqual(0);
19 | expect(numericSort('desc', {}, {})('', undefined)).toEqual(0);
20 | expect(numericSort('asc', {}, {})(undefined, '')).toEqual(0);
21 | expect(numericSort('desc', {}, {})(undefined, '')).toEqual(0);
22 |
23 | expect(numericSort('asc', {}, {})(null, undefined)).toEqual(0);
24 | expect(numericSort('desc', {}, {})(null, undefined)).toEqual(0);
25 | expect(numericSort('asc', {}, {})(undefined, null)).toEqual(0);
26 | expect(numericSort('desc', {}, {})(undefined, null)).toEqual(0);
27 | });
28 |
--------------------------------------------------------------------------------
/src/plugins/columnSorting/test/utils.unit.js:
--------------------------------------------------------------------------------
1 | import { areValidSortStates, ASC_SORT_STATE, DESC_SORT_STATE } from 'handsontable/plugins/columnSorting/utils';
2 |
3 | describe('ColumnSorting', () => {
4 | it('areValidSortStates', () => {
5 | expect(areValidSortStates([{}])).toBeFalsy();
6 | expect(areValidSortStates([{ column: 1 }])).toBeFalsy();
7 | expect(areValidSortStates([{ sortOrder: ASC_SORT_STATE }])).toBeFalsy();
8 | expect(areValidSortStates([{ sortOrder: DESC_SORT_STATE }])).toBeFalsy();
9 | expect(areValidSortStates([{ column: 1, sortOrder: DESC_SORT_STATE }, {
10 | column: 1,
11 | sortOrder: DESC_SORT_STATE
12 | }])).toBeFalsy();
13 | expect(areValidSortStates([{ column: 1, sortOrder: DESC_SORT_STATE }])).toBeTruthy();
14 | expect(areValidSortStates([{ column: 1, sortOrder: ASC_SORT_STATE }])).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/plugins/comments/comments.css:
--------------------------------------------------------------------------------
1 | .htCommentCell {
2 | position: relative;
3 | }
4 |
5 | .htCommentCell:after {
6 | content: '';
7 | position: absolute;
8 | top: 0;
9 | right: 0;
10 | border-left: 6px solid transparent;
11 | border-top: 6px solid black;
12 | }
13 |
14 | .htComments {
15 | display: none;
16 | z-index: 1059;
17 | position: absolute;
18 | }
19 |
20 | .htCommentTextArea {
21 | box-shadow: rgba(0, 0, 0, 0.117647) 0 1px 3px, rgba(0, 0, 0, 0.239216) 0 1px 2px;
22 | -webkit-box-sizing: border-box;
23 | -moz-box-sizing: border-box;
24 | box-sizing: border-box;
25 | border: none;
26 | border-left: 3px solid #ccc;
27 | background-color: #fff;
28 | width: 215px;
29 | height: 90px;
30 | font-size: 12px;
31 | padding: 5px;
32 | outline: 0px !important;
33 | -webkit-appearance: none;
34 | }
35 |
36 | .htCommentTextArea:focus {
37 | box-shadow: rgba(0, 0, 0, 0.117647) 0 1px 3px, rgba(0, 0, 0, 0.239216) 0 1px 2px, inset 0 0 0 1px #5292f7;
38 | border-left: 3px solid #5292f7;
39 | }
40 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/clearColumn.js:
--------------------------------------------------------------------------------
1 | import { getValidSelection } from './../utils';
2 | import * as C from './../../../i18n/constants';
3 |
4 | export const KEY = 'clear_column';
5 |
6 | export default function clearColumnItem() {
7 | return {
8 | key: KEY,
9 | name() {
10 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_CLEAR_COLUMN);
11 | },
12 | callback(key, selection) {
13 | const column = selection[0].start.col;
14 |
15 | if (this.countRows()) {
16 | this.populateFromArray(0, column, [[null]], Math.max(selection[0].start.row, selection[0].end.row), column, 'ContextMenu.clearColumn');
17 | }
18 | },
19 | disabled() {
20 | const selected = getValidSelection(this);
21 |
22 | if (!selected) {
23 | return true;
24 | }
25 | const [startRow, startColumn, endRow] = selected[0];
26 | const entireRowSelection = [startRow, 0, endRow, this.countCols() - 1];
27 | const rowSelected = entireRowSelection.join(',') === selected.join(',');
28 |
29 | return startColumn < 0 || this.countCols() >= this.getSettings().maxCols || rowSelected;
30 | }
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/columnLeft.js:
--------------------------------------------------------------------------------
1 | import { getValidSelection } from './../utils';
2 | import * as C from './../../../i18n/constants';
3 |
4 | export const KEY = 'col_left';
5 |
6 | export default function columnLeftItem() {
7 | return {
8 | key: KEY,
9 | name() {
10 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_INSERT_LEFT);
11 | },
12 | callback(key, normalizedSelection) {
13 | const latestSelection = normalizedSelection[Math.max(normalizedSelection.length - 1, 0)];
14 |
15 | this.alter('insert_col', latestSelection.start.col, 1, 'ContextMenu.columnLeft');
16 | },
17 | disabled() {
18 | const selected = getValidSelection(this);
19 |
20 | if (!selected) {
21 | return true;
22 | }
23 | if (!this.isColumnModificationAllowed()) {
24 | return true;
25 | }
26 | const [startRow, startColumn, endRow] = selected[0];
27 | const entireRowSelection = [startRow, 0, endRow, this.countCols() - 1];
28 | const rowSelected = entireRowSelection.join(',') === selected.join(',');
29 | const onlyOneColumn = this.countCols() === 1;
30 |
31 | return startColumn < 0 || this.countCols() >= this.getSettings().maxCols || (!onlyOneColumn && rowSelected);
32 | },
33 | hidden() {
34 | return !this.getSettings().allowInsertColumn;
35 | }
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/columnRight.js:
--------------------------------------------------------------------------------
1 | import { getValidSelection } from './../utils';
2 | import * as C from './../../../i18n/constants';
3 |
4 | export const KEY = 'col_right';
5 |
6 | export default function columnRightItem() {
7 | return {
8 | key: KEY,
9 | name() {
10 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_INSERT_RIGHT);
11 | },
12 | callback(key, normalizedSelection) {
13 | const latestSelection = normalizedSelection[Math.max(normalizedSelection.length - 1, 0)];
14 |
15 | this.alter('insert_col', latestSelection.end.col + 1, 1, 'ContextMenu.columnRight');
16 | },
17 | disabled() {
18 | const selected = getValidSelection(this);
19 |
20 | if (!selected) {
21 | return true;
22 | }
23 | if (!this.isColumnModificationAllowed()) {
24 | return true;
25 | }
26 | const [startRow, startColumn, endRow] = selected[0];
27 | const entireRowSelection = [startRow, 0, endRow, this.countCols() - 1];
28 | const rowSelected = entireRowSelection.join(',') === selected.join(',');
29 | const onlyOneColumn = this.countCols() === 1;
30 |
31 | return startColumn < 0 || this.countCols() >= this.getSettings().maxCols || (!onlyOneColumn && rowSelected);
32 | },
33 | hidden() {
34 | return !this.getSettings().allowInsertColumn;
35 | }
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/readOnly.js:
--------------------------------------------------------------------------------
1 | import { checkSelectionConsistency, markLabelAsSelected } from './../utils';
2 | import { arrayEach } from './../../../helpers/array';
3 | import * as C from './../../../i18n/constants';
4 |
5 | export const KEY = 'make_read_only';
6 |
7 | export default function readOnlyItem() {
8 | return {
9 | key: KEY,
10 | name() {
11 | let label = this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_READ_ONLY);
12 | const atLeastOneReadOnly = checkSelectionConsistency(this.getSelectedRange(), (row, col) => this.getCellMeta(row, col).readOnly);
13 |
14 | if (atLeastOneReadOnly) {
15 | label = markLabelAsSelected(label);
16 | }
17 |
18 | return label;
19 | },
20 | callback() {
21 | const ranges = this.getSelectedRange();
22 | const atLeastOneReadOnly = checkSelectionConsistency(ranges, (row, col) => this.getCellMeta(row, col).readOnly);
23 |
24 | arrayEach(ranges, (range) => {
25 | range.forAll((row, col) => {
26 | this.setCellMeta(row, col, 'readOnly', !atLeastOneReadOnly);
27 | });
28 | });
29 |
30 | this.render();
31 | },
32 | disabled() {
33 | return !(this.getSelectedRange() && !this.selection.isSelectedByCorner());
34 | }
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/redo.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 |
3 | export const KEY = 'redo';
4 |
5 | export default function redoItem() {
6 | return {
7 | key: KEY,
8 | name() {
9 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_REDO);
10 | },
11 | callback() {
12 | this.redo();
13 | },
14 | disabled() {
15 | return this.undoRedo && !this.undoRedo.isRedoAvailable();
16 | }
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/removeColumn.js:
--------------------------------------------------------------------------------
1 | import { getValidSelection } from './../utils';
2 | import { transformSelectionToColumnDistance } from './../../../selection/utils';
3 | import * as C from './../../../i18n/constants';
4 |
5 | export const KEY = 'remove_col';
6 |
7 | export default function removeColumnItem() {
8 | return {
9 | key: KEY,
10 | name() {
11 | const selection = this.getSelected();
12 | let pluralForm = 0;
13 |
14 | if (selection) {
15 | if (selection.length > 1) {
16 | pluralForm = 1;
17 | } else {
18 | const [, fromColumn, , toColumn] = selection[0];
19 |
20 | if (fromColumn - toColumn !== 0) {
21 | pluralForm = 1;
22 | }
23 | }
24 | }
25 |
26 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_REMOVE_COLUMN, pluralForm);
27 | },
28 | callback() {
29 | this.alter('remove_col', transformSelectionToColumnDistance(this.getSelected()), null, 'ContextMenu.removeColumn');
30 | },
31 | disabled() {
32 | const selected = getValidSelection(this);
33 | const totalColumns = this.countCols();
34 |
35 | if (!selected) {
36 | return true;
37 | }
38 |
39 | return this.selection.isSelectedByRowHeader() || this.selection.isSelectedByCorner() ||
40 | !this.isColumnModificationAllowed() || !totalColumns;
41 | },
42 | hidden() {
43 | return !this.getSettings().allowRemoveColumn;
44 | }
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/rowAbove.js:
--------------------------------------------------------------------------------
1 | import { getValidSelection } from './../utils';
2 | import * as C from './../../../i18n/constants';
3 |
4 | export const KEY = 'row_above';
5 |
6 | export default function rowAboveItem() {
7 | return {
8 | key: KEY,
9 | name() {
10 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_ROW_ABOVE);
11 | },
12 | callback(key, normalizedSelection) {
13 | const latestSelection = normalizedSelection[Math.max(normalizedSelection.length - 1, 0)];
14 |
15 | this.alter('insert_row', latestSelection.start.row, 1, 'ContextMenu.rowAbove');
16 | },
17 | disabled() {
18 | const selected = getValidSelection(this);
19 |
20 | if (!selected) {
21 | return true;
22 | }
23 |
24 | return this.selection.isSelectedByColumnHeader() || this.countRows() >= this.getSettings().maxRows;
25 | },
26 | hidden() {
27 | return !this.getSettings().allowInsertRow;
28 | }
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/rowBelow.js:
--------------------------------------------------------------------------------
1 | import { getValidSelection } from './../utils';
2 | import * as C from './../../../i18n/constants';
3 |
4 | export const KEY = 'row_below';
5 |
6 | export default function rowBelowItem() {
7 | return {
8 | key: KEY,
9 | name() {
10 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_ROW_BELOW);
11 | },
12 | callback(key, normalizedSelection) {
13 | const latestSelection = normalizedSelection[Math.max(normalizedSelection.length - 1, 0)];
14 |
15 | this.alter('insert_row', latestSelection.end.row + 1, 1, 'ContextMenu.rowBelow');
16 | },
17 | disabled() {
18 | const selected = getValidSelection(this);
19 |
20 | if (!selected) {
21 | return true;
22 | }
23 |
24 | return this.selection.isSelectedByColumnHeader() || this.countRows() >= this.getSettings().maxRows;
25 | },
26 | hidden() {
27 | return !this.getSettings().allowInsertRow;
28 | }
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/separator.js:
--------------------------------------------------------------------------------
1 | export const KEY = '---------';
2 |
3 | export default function separatorItem() {
4 | return {
5 | name: KEY
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/predefinedItems/undo.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 |
3 | export const KEY = 'undo';
4 |
5 | export default function undoItem() {
6 | return {
7 | key: KEY,
8 | name() {
9 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_UNDO);
10 | },
11 | callback() {
12 | this.undo();
13 | },
14 | disabled() {
15 | return this.undoRedo && !this.undoRedo.isUndoAvailable();
16 | }
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/plugins/contextMenu/test/predefinedItems/readOnly.e2e.js:
--------------------------------------------------------------------------------
1 | describe('ContextMenuReadOnly', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should trigger `afterSetCellMeta` callback after changing cell to read only by context menu', () => {
16 | const afterSetCellMetaCallback = jasmine.createSpy('afterSetCellMetaCallback');
17 | const rows = 5;
18 | const columns = 5;
19 |
20 | handsontable({
21 | data: Handsontable.helper.createSpreadsheetData(rows, columns),
22 | rowHeaders: true,
23 | colHeaders: true,
24 | contextMenu: true,
25 | afterSetCellMeta: afterSetCellMetaCallback
26 | });
27 |
28 | selectCell(2, 3);
29 | contextMenu();
30 |
31 | const changeToReadOnluButton = $('.htItemWrapper').filter(function() {
32 | return $(this).text() === 'Read only';
33 | })[0];
34 |
35 | $(changeToReadOnluButton).simulate('mousedown');
36 | expect(afterSetCellMetaCallback).toHaveBeenCalledWith(2, 3, 'readOnly', true, undefined, undefined);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/src/plugins/copyPaste/clipboardData.js:
--------------------------------------------------------------------------------
1 | export default class ClipboardData {
2 | constructor() {
3 | this.data = {};
4 | }
5 | setData(type, value) {
6 | this.data[type] = value;
7 | }
8 | getData(type) {
9 | return this.data[type] || void 0;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/plugins/copyPaste/contextMenuItem/copy.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 |
3 | export default function copyItem(copyPastePlugin) {
4 | return {
5 | key: 'copy',
6 | name() {
7 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_COPY);
8 | },
9 | callback() {
10 | copyPastePlugin.copy();
11 | },
12 | disabled() {
13 | const selected = this.getSelected();
14 |
15 | if (!selected || selected.length > 1) {
16 | return true;
17 | }
18 |
19 | return false;
20 | },
21 | hidden: false
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/plugins/copyPaste/contextMenuItem/cut.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 |
3 | export default function cutItem(copyPastePlugin) {
4 | return {
5 | key: 'cut',
6 | name() {
7 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_CUT);
8 | },
9 | callback() {
10 | copyPastePlugin.cut();
11 | },
12 | disabled() {
13 | const selected = this.getSelected();
14 |
15 | if (!selected || selected.length > 1) {
16 | return true;
17 | }
18 |
19 | return false;
20 | },
21 | hidden: false
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/plugins/copyPaste/copyPaste.css:
--------------------------------------------------------------------------------
1 | textarea#HandsontableCopyPaste {
2 | position: fixed !important;
3 | top: 0 !important;
4 | right: 100% !important;
5 | overflow: hidden;
6 | opacity: 0;
7 | outline: 0 none !important;
8 | }
9 |
--------------------------------------------------------------------------------
/src/plugins/copyPaste/pasteEvent.js:
--------------------------------------------------------------------------------
1 | import ClipboardData from './clipboardData';
2 |
3 | export default class PasteEvent {
4 | constructor() {
5 | this.clipboardData = new ClipboardData();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/plugins/copyPaste/test/copyPaste.types.ts:
--------------------------------------------------------------------------------
1 | import * as Handsontable from 'handsontable';
2 |
3 | const copyPaste = Handsontable.plugins.CopyPaste;
4 |
5 | copyPaste.columnsLimit = 10;
6 | copyPaste.rowsLimit = 10;
7 | copyPaste.pasteMode = 'overwrite';
8 | copyPaste.pasteMode = 'shift_down';
9 | copyPaste.pasteMode = 'shift_right';
10 |
11 | copyPaste.copy();
12 | copyPaste.copy(true);
13 |
14 | copyPaste.cut();
15 | copyPaste.cut(true);
16 |
17 | copyPaste.paste();
18 | copyPaste.paste(true);
19 |
20 | copyPaste.getRangedData([{ startRow: 1, startCol: 1, endRow: 2, endCol: 2 }]);
21 | copyPaste.getRangedCopyableData([{ startRow: 1, startCol: 1, endRow: 2, endCol: 2 }]);
22 | copyPaste.setCopyableText();
23 |
--------------------------------------------------------------------------------
/src/plugins/customBorders/contextMenuItem/bottom.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 | import { checkSelectionBorders, markSelected } from './../utils';
3 |
4 | export default function bottom(customBordersPlugin) {
5 | return {
6 | key: 'borders:bottom',
7 | name() {
8 | let label = this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_BORDERS_BOTTOM);
9 | const hasBorder = checkSelectionBorders(this, 'bottom');
10 | if (hasBorder) {
11 | label = markSelected(label);
12 | }
13 | return label;
14 | },
15 | callback(key, selected) {
16 | const hasBorder = checkSelectionBorders(this, 'bottom');
17 | customBordersPlugin.prepareBorder(selected, 'bottom', hasBorder);
18 | }
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/plugins/customBorders/contextMenuItem/index.js:
--------------------------------------------------------------------------------
1 | import bottom from './bottom';
2 | import left from './left';
3 | import noBorders from './noBorders';
4 | import right from './right';
5 | import top from './top';
6 |
7 | export {
8 | bottom,
9 | left,
10 | noBorders,
11 | right,
12 | top
13 | };
14 |
--------------------------------------------------------------------------------
/src/plugins/customBorders/contextMenuItem/left.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 | import { checkSelectionBorders, markSelected } from './../utils';
3 |
4 | export default function left(customBordersPlugin) {
5 | return {
6 | key: 'borders:left',
7 | name() {
8 | let label = this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_BORDERS_LEFT);
9 | const hasBorder = checkSelectionBorders(this, 'left');
10 | if (hasBorder) {
11 | label = markSelected(label);
12 | }
13 |
14 | return label;
15 | },
16 | callback(key, selected) {
17 | const hasBorder = checkSelectionBorders(this, 'left');
18 | customBordersPlugin.prepareBorder(selected, 'left', hasBorder);
19 | }
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/plugins/customBorders/contextMenuItem/noBorders.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 | import { checkSelectionBorders } from './../utils';
3 |
4 | export default function noBorders(customBordersPlugin) {
5 | return {
6 | key: 'borders:no_borders',
7 | name() {
8 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_REMOVE_BORDERS);
9 | },
10 | callback(key, selected) {
11 | customBordersPlugin.prepareBorder(selected, 'noBorders');
12 | },
13 | disabled() {
14 | return !checkSelectionBorders(this);
15 | }
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/src/plugins/customBorders/contextMenuItem/right.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 | import { checkSelectionBorders, markSelected } from './../utils';
3 |
4 | export default function right(customBordersPlugin) {
5 | return {
6 | key: 'borders:right',
7 | name() {
8 | let label = this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_BORDERS_RIGHT);
9 | const hasBorder = checkSelectionBorders(this, 'right');
10 | if (hasBorder) {
11 | label = markSelected(label);
12 | }
13 | return label;
14 | },
15 | callback(key, selected) {
16 | const hasBorder = checkSelectionBorders(this, 'right');
17 | customBordersPlugin.prepareBorder(selected, 'right', hasBorder);
18 | }
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/plugins/customBorders/contextMenuItem/top.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 | import { checkSelectionBorders, markSelected } from './../utils';
3 |
4 | export default function top(customBordersPlugin) {
5 | return {
6 | key: 'borders:top',
7 | name() {
8 | let label = this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_BORDERS_TOP);
9 | const hasBorder = checkSelectionBorders(this, 'top');
10 | if (hasBorder) {
11 | label = markSelected(label);
12 | }
13 |
14 | return label;
15 | },
16 | callback(key, selected) {
17 | const hasBorder = checkSelectionBorders(this, 'top');
18 | customBordersPlugin.prepareBorder(selected, 'top', hasBorder);
19 | }
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/plugins/manualColumnFreeze/contextMenuItem/freezeColumn.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 |
3 | export default function freezeColumnItem(manualColumnFreezePlugin) {
4 | return {
5 | key: 'freeze_column',
6 | name() {
7 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_FREEZE_COLUMN);
8 | },
9 | callback(key, selected) {
10 | const [{ start: { col: selectedColumn } }] = selected;
11 |
12 | manualColumnFreezePlugin.freezeColumn(selectedColumn);
13 |
14 | this.render();
15 | this.view.wt.wtOverlays.adjustElementsSize(true);
16 | },
17 | hidden() {
18 | const selection = this.getSelectedRange();
19 | let hide = false;
20 |
21 | if (selection === void 0) {
22 | hide = true;
23 |
24 | } else if (selection.length > 1) {
25 | hide = true;
26 |
27 | } else if ((selection[0].from.col !== selection[0].to.col) || (selection[0].from.col <= this.getSettings().fixedColumnsLeft - 1)) {
28 | hide = true;
29 | }
30 |
31 | return hide;
32 | },
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/plugins/manualColumnFreeze/contextMenuItem/unfreezeColumn.js:
--------------------------------------------------------------------------------
1 | import * as C from './../../../i18n/constants';
2 |
3 | export default function unfreezeColumnItem(manualColumnFreezePlugin) {
4 | return {
5 | key: 'unfreeze_column',
6 | name() {
7 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_UNFREEZE_COLUMN);
8 | },
9 | callback(key, selected) {
10 | const [{ start: { col: selectedColumn } }] = selected;
11 |
12 | manualColumnFreezePlugin.unfreezeColumn(selectedColumn);
13 |
14 | this.render();
15 | this.view.wt.wtOverlays.adjustElementsSize(true);
16 | },
17 | hidden() {
18 | const selection = this.getSelectedRange();
19 | let hide = false;
20 |
21 | if (selection === void 0) {
22 | hide = true;
23 |
24 | } else if (selection.length > 1) {
25 | hide = true;
26 |
27 | } else if ((selection[0].from.col !== selection[0].to.col) || selection[0].from.col >= this.getSettings().fixedColumnsLeft) {
28 | hide = true;
29 | }
30 |
31 | return hide;
32 | },
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/plugins/manualColumnFreeze/manualColumnFreeze.css:
--------------------------------------------------------------------------------
1 | .htRowHeaders .ht_master.innerBorderLeft ~ .ht_clone_top_left_corner th:nth-child(2),
2 | .htRowHeaders .ht_master.innerBorderLeft ~ .ht_clone_left td:first-of-type {
3 | border-left: 0 none;
4 | }
5 |
--------------------------------------------------------------------------------
/src/plugins/manualColumnMove/manualColumnMove.css:
--------------------------------------------------------------------------------
1 | .handsontable .wtHider {
2 | position: relative;
3 | }
4 | .handsontable.ht__manualColumnMove.after-selection--columns thead th.ht__highlight {
5 | cursor: move;
6 | cursor: -moz-grab;
7 | cursor: -webkit-grab;
8 | cursor: grab;
9 | }
10 | .handsontable.ht__manualColumnMove.on-moving--columns,
11 | .handsontable.ht__manualColumnMove.on-moving--columns thead th.ht__highlight {
12 | cursor: move;
13 | cursor: -moz-grabbing;
14 | cursor: -webkit-grabbing;
15 | cursor: grabbing;
16 | }
17 | .handsontable.ht__manualColumnMove.on-moving--columns .manualColumnResizer {
18 | display: none;
19 | }
20 | .handsontable .ht__manualColumnMove--guideline,
21 | .handsontable .ht__manualColumnMove--backlight {
22 | position: absolute;
23 | height: 100%;
24 | display: none;
25 | }
26 | .handsontable .ht__manualColumnMove--guideline {
27 | background: #757575;
28 | width: 2px;
29 | top: 0;
30 | margin-left: -1px;
31 | z-index: 105;
32 | }
33 | .handsontable .ht__manualColumnMove--backlight {
34 | background: #343434;
35 | background: rgba(52, 52, 52, 0.25);
36 | display: none;
37 | z-index: 105;
38 | pointer-events: none;
39 | }
40 | .handsontable.on-moving--columns.show-ui .ht__manualColumnMove--guideline,
41 | .handsontable.on-moving--columns .ht__manualColumnMove--backlight {
42 | display: block;
43 | }
44 |
--------------------------------------------------------------------------------
/src/plugins/manualColumnMove/ui/backlight.js:
--------------------------------------------------------------------------------
1 | import BaseUI from './_base';
2 | import { addClass } from './../../../helpers/dom/element';
3 |
4 | const CSS_CLASSNAME = 'ht__manualColumnMove--backlight';
5 |
6 | /**
7 | * @class BacklightUI
8 | * @util
9 | */
10 | class BacklightUI extends BaseUI {
11 | /**
12 | * Custom className on build process.
13 | */
14 | build() {
15 | super.build();
16 |
17 | addClass(this._element, CSS_CLASSNAME);
18 | }
19 | }
20 |
21 | export default BacklightUI;
22 |
--------------------------------------------------------------------------------
/src/plugins/manualColumnMove/ui/guideline.js:
--------------------------------------------------------------------------------
1 | import BaseUI from './_base';
2 | import { addClass } from './../../../helpers/dom/element';
3 |
4 | const CSS_CLASSNAME = 'ht__manualColumnMove--guideline';
5 |
6 | /**
7 | * @class GuidelineUI
8 | * @util
9 | */
10 | class GuidelineUI extends BaseUI {
11 | /**
12 | * Custom className on build process.
13 | */
14 | build() {
15 | super.build();
16 |
17 | addClass(this._element, CSS_CLASSNAME);
18 | }
19 | }
20 |
21 | export default GuidelineUI;
22 |
--------------------------------------------------------------------------------
/src/plugins/manualRowMove/manualRowMove.css:
--------------------------------------------------------------------------------
1 | .handsontable .wtHider {
2 | position: relative;
3 | }
4 | .handsontable.ht__manualRowMove.after-selection--rows tbody th.ht__highlight {
5 | cursor: move;
6 | cursor: -moz-grab;
7 | cursor: -webkit-grab;
8 | cursor: grab;
9 | }
10 | .handsontable.ht__manualRowMove.on-moving--rows,
11 | .handsontable.ht__manualRowMove.on-moving--rows tbody th.ht__highlight {
12 | cursor: move;
13 | cursor: -moz-grabbing;
14 | cursor: -webkit-grabbing;
15 | cursor: grabbing;
16 | }
17 | .handsontable.ht__manualRowMove.on-moving--rows .manualRowResizer {
18 | display: none;
19 | }
20 | .handsontable .ht__manualRowMove--guideline,
21 | .handsontable .ht__manualRowMove--backlight {
22 | position: absolute;
23 | width: 100%;
24 | display: none;
25 | }
26 | .handsontable .ht__manualRowMove--guideline {
27 | background: #757575;
28 | height: 2px;
29 | left: 0;
30 | margin-top: -1px;
31 | z-index: 105;
32 | }
33 | .handsontable .ht__manualRowMove--backlight {
34 | background: #343434;
35 | background: rgba(52, 52, 52, 0.25);
36 | display: none;
37 | z-index: 105;
38 | pointer-events: none;
39 | }
40 | .handsontable.on-moving--rows.show-ui .ht__manualRowMove--guideline,
41 | .handsontable.on-moving--rows .ht__manualRowMove--backlight {
42 | display: block;
43 | }
44 |
--------------------------------------------------------------------------------
/src/plugins/manualRowMove/ui/backlight.js:
--------------------------------------------------------------------------------
1 | import BaseUI from './_base';
2 | import { addClass } from './../../../helpers/dom/element';
3 |
4 | const CSS_CLASSNAME = 'ht__manualRowMove--backlight';
5 |
6 | /**
7 | * @class BacklightUI
8 | * @util
9 | */
10 | class BacklightUI extends BaseUI {
11 | /**
12 | * Custom className on build process.
13 | */
14 | build() {
15 | super.build();
16 |
17 | addClass(this._element, CSS_CLASSNAME);
18 | }
19 | }
20 |
21 | export default BacklightUI;
22 |
--------------------------------------------------------------------------------
/src/plugins/manualRowMove/ui/guideline.js:
--------------------------------------------------------------------------------
1 | import BaseUI from './_base';
2 | import { addClass } from './../../../helpers/dom/element';
3 |
4 | const CSS_CLASSNAME = 'ht__manualRowMove--guideline';
5 |
6 | /**
7 | * @class GuidelineUI
8 | * @util
9 | */
10 | class GuidelineUI extends BaseUI {
11 | /**
12 | * Custom className on build process.
13 | */
14 | build() {
15 | super.build();
16 |
17 | addClass(this._element, CSS_CLASSNAME);
18 | }
19 | }
20 |
21 | export default GuidelineUI;
22 |
--------------------------------------------------------------------------------
/src/plugins/mergeCells/contextMenuItem/toggleMerge.js:
--------------------------------------------------------------------------------
1 | import * as C from '../../../i18n/constants';
2 | import MergedCellCoords from '../cellCoords';
3 |
4 | export default function toggleMergeItem(plugin) {
5 | return {
6 | key: 'mergeCells',
7 | name() {
8 | const sel = this.getSelectedLast();
9 |
10 | if (sel) {
11 | const info = plugin.mergedCellsCollection.get(sel[0], sel[1]);
12 |
13 | if (info.row === sel[0] && info.col === sel[1] && info.row + info.rowspan - 1 === sel[2] && info.col + info.colspan - 1 === sel[3]) {
14 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_UNMERGE_CELLS);
15 | }
16 | }
17 |
18 | return this.getTranslatedPhrase(C.CONTEXTMENU_ITEMS_MERGE_CELLS);
19 | },
20 | callback() {
21 | plugin.toggleMergeOnSelection();
22 | },
23 | disabled() {
24 | const sel = this.getSelectedLast();
25 |
26 | if (!sel) {
27 | return true;
28 | }
29 |
30 | const isSingleCell = MergedCellCoords.isSingleCell({
31 | row: sel[0],
32 | col: sel[1],
33 | rowspan: sel[2] - sel[0] + 1,
34 | colspan: sel[3] - sel[1] + 1
35 | });
36 |
37 | return isSingleCell || this.selection.isSelectedByCorner();
38 | },
39 | hidden: false
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/src/plugins/mergeCells/mergeCells.css:
--------------------------------------------------------------------------------
1 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"]:not([class*="fullySelectedMergedCell"]):before {
2 | opacity: 0;
3 | }
4 |
5 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-multiple"]:before {
6 | opacity: 0.1;
7 | }
8 |
9 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-0"]:before {
10 | opacity: 0.1;
11 | }
12 |
13 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-1"]:before {
14 | opacity: 0.2;
15 | }
16 |
17 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-2"]:before {
18 | opacity: 0.27;
19 | }
20 |
21 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-3"]:before {
22 | opacity: 0.35;
23 | }
24 |
25 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-4"]:before {
26 | opacity: 0.41;
27 | }
28 |
29 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-5"]:before {
30 | opacity: 0.47;
31 | }
32 |
33 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-6"]:before {
34 | opacity: 0.54;
35 | }
36 |
37 | .handsontable tbody td[rowspan][class*="area"][class*="highlight"][class*="fullySelectedMergedCell-7"]:before {
38 | opacity: 0.58;
39 | }
40 |
--------------------------------------------------------------------------------
/src/plugins/mergeCells/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Apply the `colspan`/`rowspan` properties.
3 | *
4 | * @param {HTMLElement} TD The soon-to-be-modified cell.
5 | * @param {MergedCellCoords} merged cellInfo The merged cell in question.
6 | * @param {Number} row Row index.
7 | * @param {Number} col Column index.
8 | */
9 | // eslint-disable-next-line import/prefer-default-export
10 | export function applySpanProperties(TD, mergedCellInfo, row, col) {
11 | if (mergedCellInfo) {
12 | if (mergedCellInfo.row === row && mergedCellInfo.col === col) {
13 | TD.setAttribute('rowspan', mergedCellInfo.rowspan.toString());
14 | TD.setAttribute('colspan', mergedCellInfo.colspan.toString());
15 |
16 | } else {
17 | TD.removeAttribute('rowspan');
18 | TD.removeAttribute('colspan');
19 |
20 | TD.style.display = 'none';
21 | }
22 |
23 | } else {
24 | TD.removeAttribute('rowspan');
25 | TD.removeAttribute('colspan');
26 |
27 | TD.style.display = '';
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/renderers/_cellDecorator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adds appropriate CSS class to table cell, based on cellProperties
3 | */
4 | import { addClass, removeClass } from './../helpers/dom/element';
5 |
6 | function cellDecorator(instance, TD, row, col, prop, value, cellProperties) {
7 | const classesToAdd = [];
8 | const classesToRemove = [];
9 |
10 | if (cellProperties.className) {
11 | if (TD.className) {
12 | TD.className = `${TD.className} ${cellProperties.className}`;
13 | } else {
14 | TD.className = cellProperties.className;
15 | }
16 | }
17 |
18 | if (cellProperties.readOnly) {
19 | classesToAdd.push(cellProperties.readOnlyCellClassName);
20 | }
21 |
22 | if (cellProperties.valid === false && cellProperties.invalidCellClassName) {
23 | classesToAdd.push(cellProperties.invalidCellClassName);
24 |
25 | } else {
26 | classesToRemove.push(cellProperties.invalidCellClassName);
27 | }
28 |
29 | if (cellProperties.wordWrap === false && cellProperties.noWordWrapClassName) {
30 | classesToAdd.push(cellProperties.noWordWrapClassName);
31 | }
32 |
33 | if (!value && cellProperties.placeholder) {
34 | classesToAdd.push(cellProperties.placeholderCellClassName);
35 | }
36 |
37 | removeClass(TD, classesToRemove);
38 | addClass(TD, classesToAdd);
39 | }
40 |
41 | export default cellDecorator;
42 |
--------------------------------------------------------------------------------
/src/renderers/htmlRenderer.js:
--------------------------------------------------------------------------------
1 | import { fastInnerHTML } from './../helpers/dom/element';
2 | import { getRenderer } from './index';
3 |
4 | /**
5 | * @private
6 | * @renderer HtmlRenderer
7 | * @param instance
8 | * @param TD
9 | * @param row
10 | * @param col
11 | * @param prop
12 | * @param value
13 | * @param cellProperties
14 | */
15 | function htmlRenderer(instance, TD, row, col, prop, value, ...args) {
16 | getRenderer('base').apply(this, [instance, TD, row, col, prop, value, ...args]);
17 |
18 | fastInnerHTML(TD, value === null || value === void 0 ? '' : value);
19 | }
20 |
21 | export default htmlRenderer;
22 |
--------------------------------------------------------------------------------
/src/renderers/index.js:
--------------------------------------------------------------------------------
1 | import staticRegister from './../utils/staticRegister';
2 |
3 | import baseRenderer from './_cellDecorator';
4 | import autocompleteRenderer from './autocompleteRenderer';
5 | import checkboxRenderer from './checkboxRenderer';
6 | import htmlRenderer from './htmlRenderer';
7 | import numericRenderer from './numericRenderer';
8 | import passwordRenderer from './passwordRenderer';
9 | import textRenderer from './textRenderer';
10 |
11 | const {
12 | register,
13 | getItem,
14 | hasItem,
15 | getNames,
16 | getValues,
17 | } = staticRegister('renderers');
18 |
19 | register('base', baseRenderer);
20 | register('autocomplete', autocompleteRenderer);
21 | register('checkbox', checkboxRenderer);
22 | register('html', htmlRenderer);
23 | register('numeric', numericRenderer);
24 | register('password', passwordRenderer);
25 | register('text', textRenderer);
26 |
27 | /**
28 | * Retrieve renderer function.
29 | *
30 | * @param {String} name Renderer identification.
31 | * @returns {Function} Returns renderer function.
32 | */
33 | function _getItem(name) {
34 | if (typeof name === 'function') {
35 | return name;
36 | }
37 | if (!hasItem(name)) {
38 | throw Error(`No registered renderer found under "${name}" name`);
39 | }
40 |
41 | return getItem(name);
42 | }
43 |
44 | export {
45 | register as registerRenderer,
46 | _getItem as getRenderer,
47 | hasItem as hasRenderer,
48 | getNames as getRegisteredRendererNames,
49 | getValues as getRegisteredRenderers,
50 | };
51 |
--------------------------------------------------------------------------------
/src/renderers/passwordRenderer.js:
--------------------------------------------------------------------------------
1 | import { fastInnerHTML } from './../helpers/dom/element';
2 | import { getRenderer } from './index';
3 | import { rangeEach } from './../helpers/number';
4 |
5 | /**
6 | * @private
7 | * @renderer PasswordRenderer
8 | * @param instance
9 | * @param TD
10 | * @param row
11 | * @param col
12 | * @param prop
13 | * @param value
14 | * @param cellProperties
15 | */
16 | function passwordRenderer(instance, TD, row, col, prop, value, cellProperties, ...args) {
17 | getRenderer('text').apply(this, [instance, TD, row, col, prop, value, cellProperties, ...args]);
18 |
19 | const hashLength = cellProperties.hashLength || TD.innerHTML.length;
20 | const hashSymbol = cellProperties.hashSymbol || '*';
21 |
22 | let hash = '';
23 |
24 | rangeEach(hashLength - 1, () => {
25 | hash += hashSymbol;
26 | });
27 | fastInnerHTML(TD, hash);
28 | }
29 |
30 | export default passwordRenderer;
31 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/activeHeader.js:
--------------------------------------------------------------------------------
1 | import { Selection } from './../../../3rdparty/walkontable/src';
2 |
3 | /**
4 | * @return {Selection}
5 | */
6 | function createHighlight({ activeHeaderClassName }) {
7 | const s = new Selection({
8 | highlightHeaderClassName: activeHeaderClassName,
9 | });
10 |
11 | return s;
12 | }
13 |
14 | export default createHighlight;
15 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/area.js:
--------------------------------------------------------------------------------
1 | import { Selection } from './../../../3rdparty/walkontable/src';
2 |
3 | /**
4 | * Creates the new instance of Selection responsible for highlighting area of the selected multiple cells.
5 | *
6 | * @return {Selection}
7 | */
8 | function createHighlight({ layerLevel, areaCornerVisible }) {
9 | const s = new Selection({
10 | className: 'area',
11 | markIntersections: true,
12 | layerLevel: Math.min(layerLevel, 7),
13 | border: {
14 | width: 1,
15 | color: '#4b89ff',
16 | cornerVisible: areaCornerVisible,
17 | },
18 | });
19 |
20 | return s;
21 | }
22 |
23 | export default createHighlight;
24 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/cell.js:
--------------------------------------------------------------------------------
1 | import { Selection } from './../../../3rdparty/walkontable/src';
2 |
3 | /**
4 | * Creates the new instance of Selection responsible for highlighting currently selected cell. This type of selection
5 | * can present on the table only one at the time.
6 | *
7 | * @return {Selection}
8 | */
9 | function createHighlight({ cellCornerVisible }) {
10 | const s = new Selection({
11 | className: 'current',
12 | border: {
13 | width: 2,
14 | color: '#4b89ff',
15 | cornerVisible: cellCornerVisible,
16 | },
17 | });
18 |
19 | return s;
20 | }
21 |
22 | export default createHighlight;
23 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/customSelection.js:
--------------------------------------------------------------------------------
1 | import { Selection } from './../../../3rdparty/walkontable/src';
2 |
3 | /**
4 | * Creates the new instance of Selection responsible for highlighting currently selected cell. This type of selection
5 | * can present on the table only one at the time.
6 | *
7 | * @return {Selection}
8 | */
9 | function createHighlight({ border, cellRange }) {
10 | const s = new Selection(border, cellRange);
11 |
12 | return s;
13 | }
14 |
15 | export default createHighlight;
16 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/fill.js:
--------------------------------------------------------------------------------
1 | import { Selection } from './../../../3rdparty/walkontable/src';
2 |
3 | /**
4 | * Creates the new instance of Selection, responsible for highlighting cells which are covered by fill handle
5 | * functionality. This type of selection can present on the table only one at the time.
6 | *
7 | * @return {Selection}
8 | */
9 | function createHighlight() {
10 | const s = new Selection({
11 | className: 'fill',
12 | border: {
13 | width: 1,
14 | color: '#ff0000',
15 | },
16 | });
17 |
18 | return s;
19 | }
20 |
21 | export default createHighlight;
22 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/header.js:
--------------------------------------------------------------------------------
1 | import { Selection } from './../../../3rdparty/walkontable/src';
2 |
3 | /**
4 | * Creates the new instance of Selection, responsible for highlighting row and column headers. This type of selection
5 | * can occur multiple times.
6 | *
7 | * @return {Selection}
8 | */
9 | function createHighlight({ headerClassName, rowClassName, columnClassName }) {
10 | const s = new Selection({
11 | className: 'highlight',
12 | highlightHeaderClassName: headerClassName,
13 | highlightRowClassName: rowClassName,
14 | highlightColumnClassName: columnClassName,
15 | });
16 |
17 | return s;
18 | }
19 |
20 | export default createHighlight;
21 |
--------------------------------------------------------------------------------
/src/selection/highlight/types/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import staticRegister from './../../../utils/staticRegister';
3 |
4 | import activeHeaderHighlight from './activeHeader';
5 | import areaHighlight from './area';
6 | import cellHighlight from './cell';
7 | import customSelection from './customSelection';
8 | import fillHighlight from './fill';
9 | import headerHighlight from './header';
10 |
11 | const {
12 | register,
13 | getItem,
14 | } = staticRegister('highlight/types');
15 |
16 | register('active-header', activeHeaderHighlight);
17 | register('area', areaHighlight);
18 | register('cell', cellHighlight);
19 | register('custom-selection', customSelection);
20 | register('fill', fillHighlight);
21 | register('header', headerHighlight);
22 |
23 | function createHighlight(highlightType, options) {
24 | return getItem(highlightType)(options);
25 | }
26 |
27 | export {
28 | createHighlight,
29 | };
30 |
--------------------------------------------------------------------------------
/src/selection/index.js:
--------------------------------------------------------------------------------
1 | import Highlight from './highlight/highlight';
2 | import Selection from './selection';
3 | import { handleMouseEvent } from './mouseEventHandler';
4 | import {
5 | detectSelectionType,
6 | normalizeSelectionFactory,
7 | } from './utils';
8 |
9 | export {
10 | handleMouseEvent,
11 | Highlight,
12 | Selection,
13 | detectSelectionType,
14 | normalizeSelectionFactory
15 | };
16 |
--------------------------------------------------------------------------------
/src/utils/dataStructures/queue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class Queue
3 | * @util
4 | */
5 | class Queue {
6 | constructor(initial = []) {
7 | /**
8 | * Items collection.
9 | *
10 | * @type {Array}
11 | */
12 | this.items = initial;
13 | }
14 |
15 | /**
16 | * Add new item or items at the back of the queue.
17 | *
18 | * @param {*} items An item to add.
19 | */
20 | enqueue(...items) {
21 | this.items.push(...items);
22 | }
23 |
24 | /**
25 | * Remove the first element from the queue and returns it.
26 | *
27 | * @returns {*}
28 | */
29 | dequeue() {
30 | return this.items.shift();
31 | }
32 |
33 | /**
34 | * Return the first element from the queue (without modification queue stack).
35 | *
36 | * @returns {*}
37 | */
38 | peek() {
39 | return this.isEmpty() ? void 0 : this.items[0];
40 | }
41 |
42 | /**
43 | * Check if the queue is empty.
44 | *
45 | * @returns {Boolean}
46 | */
47 | isEmpty() {
48 | return !this.size();
49 | }
50 |
51 | /**
52 | * Return number of elements in the queue.
53 | *
54 | * @returns {Number}
55 | */
56 | size() {
57 | return this.items.length;
58 | }
59 | }
60 |
61 | export default Queue;
62 |
--------------------------------------------------------------------------------
/src/utils/dataStructures/stack.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class Stack
3 | * @util
4 | */
5 | class Stack {
6 | constructor(initial = []) {
7 | /**
8 | * Items collection.
9 | *
10 | * @type {Array}
11 | */
12 | this.items = initial;
13 | }
14 |
15 | /**
16 | * Add new item or items at the back of the stack.
17 | *
18 | * @param {*} items An item to add.
19 | */
20 | push(...items) {
21 | this.items.push(...items);
22 | }
23 |
24 | /**
25 | * Remove the last element from the stack and returns it.
26 | *
27 | * @returns {*}
28 | */
29 | pop() {
30 | return this.items.pop();
31 | }
32 |
33 | /**
34 | * Return the last element from the stack (without modification stack).
35 | *
36 | * @returns {*}
37 | */
38 | peek() {
39 | return this.isEmpty() ? void 0 : this.items[this.items.length - 1];
40 | }
41 |
42 | /**
43 | * Check if the stack is empty.
44 | *
45 | * @returns {Boolean}
46 | */
47 | isEmpty() {
48 | return !this.size();
49 | }
50 |
51 | /**
52 | * Return number of elements in the stack.
53 | *
54 | * @returns {Number}
55 | */
56 | size() {
57 | return this.items.length;
58 | }
59 | }
60 |
61 | export default Stack;
62 |
--------------------------------------------------------------------------------
/src/utils/rootInstance.js:
--------------------------------------------------------------------------------
1 | export const holder = new WeakMap();
2 |
3 | export const rootInstanceSymbol = Symbol('rootInstance');
4 |
5 | /**
6 | * Register an object as a root instance.
7 | *
8 | * @param {Object} object An object to associate with root instance flag.
9 | */
10 | export function registerAsRootInstance(object) {
11 | holder.set(object, true);
12 | }
13 |
14 | /**
15 | * Check if the source of the root indication call is valid.
16 | *
17 | * @param {Symbol} rootSymbol A symbol as a source of truth.
18 | * @return {Boolean}
19 | */
20 | export function hasValidParameter(rootSymbol) {
21 | return rootSymbol === rootInstanceSymbol;
22 | }
23 |
24 | /**
25 | * Check if passed an object was flagged as a root instance.
26 | *
27 | * @param {Object} object An object to check.
28 | * @return {Boolean}
29 | */
30 | export function isRootInstance(object) {
31 | return holder.has(object);
32 | }
33 |
--------------------------------------------------------------------------------
/src/validators/autocompleteValidator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Autocomplete cell validator.
3 | *
4 | * @private
5 | * @validator AutocompleteValidator
6 | * @param {*} value - Value of edited cell
7 | * @param {Function} callback - Callback called with validation result
8 | */
9 | export default function autocompleteValidator(value, callback) {
10 | let valueToValidate = value;
11 |
12 | if (valueToValidate === null || valueToValidate === void 0) {
13 | valueToValidate = '';
14 | }
15 |
16 | if (this.allowEmpty && valueToValidate === '') {
17 | callback(true);
18 |
19 | return;
20 | }
21 |
22 | if (this.strict && this.source) {
23 | if (typeof this.source === 'function') {
24 | this.source(valueToValidate, process(valueToValidate, callback));
25 | } else {
26 | process(valueToValidate, callback)(this.source);
27 | }
28 | } else {
29 | callback(true);
30 | }
31 | }
32 |
33 | /**
34 | * Function responsible for validation of autocomplete value.
35 | *
36 | * @param {*} value - Value of edited cell
37 | * @param {Function} callback - Callback called with validation result
38 | */
39 | function process(value, callback) {
40 | const originalVal = value;
41 |
42 | return function(source) {
43 | let found = false;
44 |
45 | for (let s = 0, slen = source.length; s < slen; s++) {
46 | if (originalVal === source[s]) {
47 | found = true; // perfect match
48 | break;
49 | }
50 | }
51 |
52 | callback(found);
53 | };
54 | }
55 |
--------------------------------------------------------------------------------
/src/validators/index.js:
--------------------------------------------------------------------------------
1 | import staticRegister from './../utils/staticRegister';
2 |
3 | import autocompleteValidator from './autocompleteValidator';
4 | import dateValidator from './dateValidator';
5 | import numericValidator from './numericValidator';
6 | import timeValidator from './timeValidator';
7 |
8 | const {
9 | register,
10 | getItem,
11 | hasItem,
12 | getNames,
13 | getValues,
14 | } = staticRegister('validators');
15 |
16 | register('autocomplete', autocompleteValidator);
17 | register('date', dateValidator);
18 | register('numeric', numericValidator);
19 | register('time', timeValidator);
20 |
21 | /**
22 | * Retrieve validator function.
23 | *
24 | * @param {String} name Validator identification.
25 | * @returns {Function} Returns validator function.
26 | */
27 | function _getItem(name) {
28 | if (typeof name === 'function') {
29 | return name;
30 | }
31 | if (!hasItem(name)) {
32 | throw Error(`No registered validator found under "${name}" name`);
33 | }
34 |
35 | return getItem(name);
36 | }
37 |
38 | export {
39 | register as registerValidator,
40 | _getItem as getValidator,
41 | hasItem as hasValidator,
42 | getNames as getRegisteredValidatorNames,
43 | getValues as getRegisteredValidators,
44 | };
45 |
--------------------------------------------------------------------------------
/src/validators/numericValidator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Numeric cell validator
3 | *
4 | * @private
5 | * @validator NumericValidator
6 | * @param {*} value - Value of edited cell
7 | * @param {*} callback - Callback called with validation result
8 | */
9 |
10 | import { isNumeric } from './../helpers/number';
11 |
12 | export default function numericValidator(value, callback) {
13 | let valueToValidate = value;
14 |
15 | if (valueToValidate === null || valueToValidate === void 0) {
16 | valueToValidate = '';
17 | }
18 | if (this.allowEmpty && valueToValidate === '') {
19 | callback(true);
20 |
21 | } else if (valueToValidate === '') {
22 | callback(false);
23 |
24 | } else {
25 | callback(isNumeric(value));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/test/bootstrap.js:
--------------------------------------------------------------------------------
1 | import './helpers/custom-matchers';
2 |
3 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
4 |
5 | beforeEach(() => {
6 | if (document.activeElement && document.activeElement !== document.body) {
7 | document.activeElement.blur();
8 |
9 | } else if (!document.activeElement) { // IE
10 | document.body.focus();
11 | }
12 | });
13 |
14 | afterEach(() => {
15 | /* eslint-disable no-unused-expressions */
16 | (window.scrollTo || window.scrollTo(0, 0));
17 | });
18 |
--------------------------------------------------------------------------------
/test/e2e/Core_reCreate.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core_reCreate', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should correctly re-render corner header when there is multiline content', () => {
16 | const settings = {
17 | rowHeaders: true,
18 | colHeaders(col) {
19 | return `Column
${col}`;
20 | }
21 | };
22 | handsontable(settings);
23 | destroy();
24 | handsontable(settings);
25 |
26 | expect(getTopLeftClone().width()).toBe(54);
27 | expect(getTopLeftClone().height()).toBe(45);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/e2e/MemoryLeakTest.js:
--------------------------------------------------------------------------------
1 | // this file is called MemoryLeakTest.js (not MemoryLeak.spec.js) to make sure it is manually executed as the last suite
2 | describe('MemoryLeakTest', () => {
3 | it('after all Handsontable instances are destroy()\'d, there should be no more active listeners', () => {
4 | expect(Handsontable._getListenersCounter()).toBe(0);
5 | });
6 | });
7 |
--------------------------------------------------------------------------------
/test/e2e/core/colToProp.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.colToProp', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return the property name for the provided column number', () => {
16 | handsontable({
17 | data: [{
18 | id: 1,
19 | firstName: 'Tobias',
20 | lastName: 'Forge'
21 | }]
22 | });
23 |
24 | expect(colToProp(0)).toBe('id');
25 | expect(colToProp(1)).toBe('firstName');
26 | expect(colToProp(2)).toBe('lastName');
27 | });
28 |
29 | it('it should return the provided property name, when the user passes a property name as a column number', () => {
30 | handsontable({
31 | data: [{
32 | id: 1,
33 | sort: true,
34 | length: 2
35 | }]
36 | });
37 |
38 | expect(colToProp('id')).toBe('id');
39 | expect(colToProp('sort')).toBe('sort');
40 | expect(colToProp('length')).toBe('length');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/test/e2e/core/countSourceCols.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.countSourceCols', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return properly index from ', () => {
16 | const hot = handsontable({
17 | data: [['', '', '', '', '', '', '', '', '', '', '', '', '', '', '']],
18 | columns(column) {
19 | return [1, 5, 9].indexOf(column) > -1 ? {} : null;
20 | }
21 | });
22 |
23 | expect(hot.countSourceCols()).toBe(15);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/test/e2e/core/getCellMetaAtRow.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.getCellMetaAtRow', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return a row of cell meta in a form of an array', () => {
16 | handsontable();
17 |
18 | const rowOfMeta = getCellMetaAtRow(0);
19 | expect(rowOfMeta.length).toBe(5);
20 | expect(rowOfMeta[0].row).toBe(0);
21 | expect(rowOfMeta[1].row).toBe(0);
22 | expect(rowOfMeta[2].row).toBe(0);
23 | expect(rowOfMeta[3].row).toBe(0);
24 | expect(rowOfMeta[4].row).toBe(0);
25 | expect(rowOfMeta[0].col).toBe(0);
26 | expect(rowOfMeta[1].col).toBe(1);
27 | expect(rowOfMeta[2].col).toBe(2);
28 | expect(rowOfMeta[3].col).toBe(3);
29 | expect(rowOfMeta[4].col).toBe(4);
30 | expect(rowOfMeta[0].prop).toBe(0);
31 | expect(rowOfMeta[1].prop).toBe(1);
32 | expect(rowOfMeta[2].prop).toBe(2);
33 | expect(rowOfMeta[3].prop).toBe(3);
34 | expect(rowOfMeta[4].prop).toBe(4);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/test/e2e/core/getCellsMeta.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.getCellsMeta', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return all initialized cells meta as flatten array', () => {
16 | handsontable();
17 |
18 | const metas = getCellsMeta();
19 |
20 | expect(metas.length).toBe(25); // default data size
21 | expect(metas[0].row).toBe(0);
22 | expect(metas[0].col).toBe(0);
23 | expect(metas[0].prop).toBe(0);
24 | expect(metas[19].row).toBe(3);
25 | expect(metas[19].col).toBe(4);
26 | expect(metas[19].prop).toBe(4);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/test/e2e/core/getCopyableData.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.getCopyableData', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return copyable data when `copyable` option is enabled', () => {
16 | handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(10, 10),
18 | copyable: true
19 | });
20 |
21 | expect(getCopyableData(0, 0)).toBe('A1');
22 | expect(getCopyableData(1, 1)).toBe('B2');
23 | expect(getCopyableData(5, 1)).toBe('B6');
24 | expect(getCopyableData(8, 9)).toBe('J9');
25 | });
26 |
27 | it('should return empty string as copyable data when `copyable` option is disabled', () => {
28 | handsontable({
29 | data: Handsontable.helper.createSpreadsheetData(10, 10),
30 | copyable: false
31 | });
32 |
33 | expect(getCopyableData(0, 0)).toBe('');
34 | expect(getCopyableData(1, 1)).toBe('');
35 | expect(getCopyableData(5, 1)).toBe('');
36 | expect(getCopyableData(8, 9)).toBe('');
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/e2e/core/getCopyableText.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.getCopyableText', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return copyable string when `copyable` option is enabled', () => {
16 | handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(5, 5),
18 | copyable: true
19 | });
20 |
21 | expect(getCopyableText(0, 0)).toBe('A1');
22 | expect(getCopyableText(0, 0, 1, 2)).toBe('A1\tB1\tC1\nA2\tB2\tC2');
23 | });
24 |
25 | it('should return empty string as copyable data when `copyable` option is disabled', () => {
26 | handsontable({
27 | data: Handsontable.helper.createSpreadsheetData(5, 5),
28 | copyable: false
29 | });
30 |
31 | expect(getCopyableText(0, 0)).toBe('');
32 | expect(getCopyableText(0, 0, 1, 2)).toBe('\t\t\n\t\t');
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/e2e/core/getSourceDataArray.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.getSourceDataArray', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return data as an array when provided data was an array of arrays', () => {
16 | handsontable({
17 | data: [[1, 2, 3], ['a', 'b', 'c']],
18 | copyable: true
19 | });
20 |
21 | expect(getSourceDataArray()).toEqual([[1, 2, 3], ['a', 'b', 'c']]);
22 | expect(getSourceDataArray(0, 1, 1, 2)).toEqual([[2, 3], ['b', 'c']]);
23 | });
24 |
25 | it('should return data as an array when provided data was an array of objects', () => {
26 | handsontable({
27 | data: [{ a: 1, b: 2, c: 3 }, { a: 'a', b: 'b', c: 'c' }],
28 | copyable: true
29 | });
30 |
31 | expect(getSourceDataArray()).toEqual([[1, 2, 3], ['a', 'b', 'c']]);
32 | expect(getSourceDataArray(0, 1, 1, 2)).toEqual([[2, 3], ['b', 'c']]);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/e2e/core/propToCol.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.propToCol', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return valid index for newly added column when manualColumnMove is enabled', () => {
16 | const hot = handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(10, 10),
18 | manualColumnMove: true,
19 | });
20 |
21 | hot.alter('insert_col', 5);
22 |
23 | expect(propToCol(0)).toBe(0);
24 | expect(propToCol(10)).toBe(10);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/test/e2e/core/spliceCellsMeta.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.spliceCellsMeta', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should splice the cell meta array analogously to the native `splice` method', () => {
16 | handsontable();
17 |
18 | let allMeta = getCellsMeta();
19 | expect(allMeta.length).toBe(25);
20 | spliceCellsMeta(3, 1);
21 | allMeta = getCellsMeta();
22 | expect(allMeta.length).toBe(20);
23 |
24 | let metaAtRow = getCellMetaAtRow(2);
25 | expect(metaAtRow[0].row).toEqual(2);
26 | metaAtRow = getCellMetaAtRow(3);
27 | expect(metaAtRow[0].row).toEqual(4);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/e2e/core/toPhysicalColumn.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.toPhysicalColumn', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return valid physical row index', () => {
16 | const hot = handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(10, 10),
18 | modifyCol(column) {
19 | return column + 3;
20 | }
21 | });
22 |
23 | expect(hot.toPhysicalColumn(0)).toBe(3);
24 | expect(hot.toPhysicalColumn(1)).toBe(4);
25 | expect(hot.toPhysicalColumn(2)).toBe(5);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/e2e/core/toPhysicalRow.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.toPhysicalRow', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return valid physical row index', () => {
16 | const hot = handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(10, 10),
18 | modifyRow(row) {
19 | return row + 3;
20 | }
21 | });
22 |
23 | expect(hot.toPhysicalRow(0)).toBe(3);
24 | expect(hot.toPhysicalRow(1)).toBe(4);
25 | expect(hot.toPhysicalRow(2)).toBe(5);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/e2e/core/toVisualColumn.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.toVisualColumn', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return valid visual row index', () => {
16 | const hot = handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(10, 10),
18 | unmodifyCol(column) {
19 | return column + 3;
20 | }
21 | });
22 |
23 | expect(hot.toVisualColumn(0)).toBe(3);
24 | expect(hot.toVisualColumn(1)).toBe(4);
25 | expect(hot.toVisualColumn(2)).toBe(5);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/e2e/core/toVisualRow.spec.js:
--------------------------------------------------------------------------------
1 | describe('Core.toVisualRow', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should return valid visual row index', () => {
16 | const hot = handsontable({
17 | data: Handsontable.helper.createSpreadsheetData(10, 10),
18 | unmodifyRow(row) {
19 | return row + 3;
20 | }
21 | });
22 |
23 | expect(hot.toVisualRow(0)).toBe(3);
24 | expect(hot.toVisualRow(1)).toBe(4);
25 | expect(hot.toVisualRow(2)).toBe(5);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/e2e/index.js:
--------------------------------------------------------------------------------
1 | require('@babel/polyfill/lib/noConflict');
2 | require('jasmine-co').install();
3 |
4 | let testPathRegExp = null;
5 |
6 | if (typeof __ENV_ARGS__ === 'object' && __ENV_ARGS__.testPathPattern) {
7 | // Remove string between % signs. On Windows' machines an empty env variable was visible as '%{variable_name}%' so it must be stripped.
8 | // See https://github.com/handsontable/handsontable/issues/4378).
9 | const pattern = __ENV_ARGS__.testPathPattern.replace(/^%(.*)%$/, '');
10 |
11 | if (pattern) {
12 | testPathRegExp = new RegExp(pattern, 'i');
13 | }
14 | }
15 |
16 | const ignoredE2ETestsPath = './mobile';
17 |
18 | [
19 | require.context('.', true, /\.spec\.js$/),
20 | require.context('./../../src/plugins', true, /\.e2e\.js$/),
21 | ].forEach((req) => {
22 | req.keys().forEach((filePath) => {
23 | if (filePath.includes(ignoredE2ETestsPath) === false) {
24 | if (testPathRegExp === null || (testPathRegExp instanceof RegExp && testPathRegExp.test(filePath))) {
25 | req(filePath);
26 | }
27 | }
28 | });
29 | });
30 |
31 | require('./MemoryLeakTest');
32 |
--------------------------------------------------------------------------------
/test/e2e/mobile/index.js:
--------------------------------------------------------------------------------
1 | require('@babel/polyfill');
2 | require('jasmine-co').install();
3 |
4 | let testPathRegExp = null;
5 |
6 | if (typeof __ENV_ARGS__ === 'object' && __ENV_ARGS__.testPathPattern) {
7 | // Remove string between % signs. On Windows' machines an empty env variable was visible as '%{variable_name}%' so it must be stripped.
8 | // See https://github.com/handsontable/handsontable/issues/4378).
9 | const pattern = __ENV_ARGS__.testPathPattern.replace(/^%(.*)%$/, '');
10 |
11 | if (pattern) {
12 | testPathRegExp = new RegExp(pattern, 'i');
13 | }
14 | }
15 |
16 | [
17 | require.context('.', true, /\.spec\.js$/)
18 | ].forEach((req) => {
19 | req.keys().forEach((filePath) => {
20 | if (testPathRegExp === null || (testPathRegExp instanceof RegExp && testPathRegExp.test(filePath))) {
21 | req(filePath);
22 | }
23 | });
24 | });
25 |
26 | require('../MemoryLeakTest');
27 |
--------------------------------------------------------------------------------
/test/e2e/renderers/htmlRenderer.spec.js:
--------------------------------------------------------------------------------
1 | describe('HTMLRenderer', () => {
2 | const id = 'testContainer';
3 |
4 | beforeEach(function() {
5 | this.$container = $(``).appendTo('body');
6 | });
7 |
8 | afterEach(function() {
9 | if (this.$container) {
10 | destroy();
11 | this.$container.remove();
12 | }
13 | });
14 |
15 | it('should not fill empty rows with null values', () => {
16 | handsontable({
17 | data: [['a', 'b', 'c', 'd', 'e', 'f']],
18 | colHeaders: true,
19 | rowHeaders: true,
20 | minSpareRows: 5,
21 | renderer: 'html'
22 | });
23 |
24 | expect($('.handsontable table tr:last-child td:eq(0)').html()).toEqual('');
25 | expect($('.handsontable table tr:last-child td:eq(1)').html()).toEqual('');
26 | expect($('.handsontable table tr:last-child td:eq(2)').html()).toEqual('');
27 | expect($('.handsontable table tr:last-child td:eq(3)').html()).toEqual('');
28 | expect($('.handsontable table tr:last-child td:eq(4)').html()).toEqual('');
29 | expect($('.handsontable table tr:last-child td:eq(5)').html()).toEqual('');
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/test/helpers/common.css:
--------------------------------------------------------------------------------
1 | .red-background {
2 | background-color: #ff0000 !important;
3 | }
4 |
--------------------------------------------------------------------------------
/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-unresolved */
2 | import window from 'window';
3 | import './../bootstrap';
4 | import * as common from './common';
5 |
6 | const exportToWindow = (helpersHolder) => {
7 | Object.keys(helpersHolder).forEach((key) => {
8 | if (key === '__esModule') {
9 | return;
10 | }
11 |
12 | if (window[key] !== void 0) {
13 | throw Error(`Cannot export "${key}" helper because this name is already assigned.`);
14 | }
15 |
16 | window[key] = helpersHolder[key];
17 | });
18 | };
19 |
20 | // Export all helpers to the window.
21 | exportToWindow(common);
22 |
--------------------------------------------------------------------------------
/test/helpers/jasmine-bridge-reporter.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | if (typeof jasmineStarted === 'undefined') {
3 | return;
4 | }
5 |
6 | function JasmineBridgeReporter() {
7 | this.started = false;
8 | this.finished = false;
9 | this.suites_ = [];
10 | this.results_ = {};
11 | this.buffer = '';
12 | }
13 |
14 | JasmineBridgeReporter.prototype.jasmineStarted = function(metadata) {
15 | this.started = true;
16 | jasmineStarted(metadata);
17 | };
18 |
19 | JasmineBridgeReporter.prototype.specStarted = function(specMetadata) {
20 | specMetadata.startTime = Date.now();
21 | jasmineSpecStarted(specMetadata);
22 | };
23 |
24 | JasmineBridgeReporter.prototype.suiteStarted = function(suiteMetadata) {
25 | suiteMetadata.startTime = Date.now();
26 | jasmineSuiteStarted(suiteMetadata);
27 | };
28 |
29 | JasmineBridgeReporter.prototype.jasmineDone = function() {
30 | this.finished = true;
31 | jasmineDone();
32 | };
33 |
34 | JasmineBridgeReporter.prototype.suiteDone = function(suiteMetadata) {
35 | suiteMetadata.duration = Date.now() - suiteMetadata.startTime;
36 | jasmineSuiteDone(suiteMetadata);
37 | };
38 |
39 | JasmineBridgeReporter.prototype.specDone = function(specMetadata) {
40 | specMetadata.duration = Date.now() - specMetadata.startTime;
41 | this.results_[specMetadata.id] = specMetadata;
42 |
43 | jasmineSpecDone(specMetadata);
44 | };
45 |
46 | jasmine.getEnv().addReporter(new JasmineBridgeReporter());
47 | }());
48 |
--------------------------------------------------------------------------------
/test/scripts/trigger-hot-builder-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const request = require('request');
3 |
4 | const ENDPOINT = 'https://api.travis-ci.org';
5 | const REPO_OWNER = 'handsontable';
6 | const REPO_PROJECT = 'hot-builder';
7 |
8 | const options = {
9 | method: 'POST',
10 | url: `${ENDPOINT}/repo/${REPO_OWNER}%2F${REPO_PROJECT}/requests`,
11 | headers: {
12 | Authorization: `token ${process.env.TCI_TOKEN}`,
13 | Accept: 'application/json',
14 | ContentType: 'application/json',
15 | 'Travis-API-Version': '3',
16 | },
17 | json: {
18 | request: {
19 | message: `Checking triggered from handsontable/handsontable repository (the ${process.env.TRAVIS_BRANCH} branch)`,
20 | // Always check only master branch (release branch) of the hot-builder repository.
21 | branch: 'master',
22 | config: {
23 | env: {
24 | global: [`HOT_BRANCH=${process.env.TRAVIS_BRANCH}`],
25 | },
26 | },
27 | }
28 | },
29 | };
30 |
31 | request(options, (err, res) => {
32 | if (err) {
33 | process.exit(1);
34 | }
35 |
36 | if (res.statusCode >= 400) {
37 | process.exit(1);
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/test/scripts/trigger-pro-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const request = require('request');
3 |
4 | const ENDPOINT = 'https://api.travis-ci.org';
5 | const REPO_OWNER = 'handsontable';
6 | const REPO_PROJECT = 'handsontable-pro';
7 |
8 | const options = {
9 | method: 'POST',
10 | url: `${ENDPOINT}/repo/${REPO_OWNER}%2F${REPO_PROJECT}/requests`,
11 | headers: {
12 | Authorization: `token ${process.env.TCI_TOKEN}`,
13 | Accept: 'application/json',
14 | ContentType: 'application/json',
15 | 'Travis-API-Version': '3',
16 | },
17 | json: {
18 | request: {
19 | message: `Checking triggered from handsontable/handsontable repository (the ${process.env.TRAVIS_BRANCH} branch)`,
20 | branch: 'develop',
21 | config: {
22 | env: {
23 | global: [
24 | `HOT_BRANCH=${process.env.TRAVIS_BRANCH}`,
25 | `HOT_FOREIGN_TRIGGER=true`,
26 | ],
27 | },
28 | },
29 | }
30 | },
31 | };
32 |
33 | request(options, (err, res) => {
34 | if (err) {
35 | process.exit(1);
36 | }
37 |
38 | if (res.statusCode >= 400) {
39 | process.exit(1);
40 | }
41 | });
42 |
--------------------------------------------------------------------------------
/test/types/editors/password.types.ts:
--------------------------------------------------------------------------------
1 | import * as Handsontable from 'handsontable';
2 |
3 | class PasswordEditor extends Handsontable.editors.TextEditor {
4 | createElements() {
5 | // Call the original createElements method
6 | super.createElements.apply(this, arguments);
7 |
8 | // Create password input and update relevant properties
9 | this.TEXTAREA = document.createElement('input');
10 | this.TEXTAREA.setAttribute('type', 'password');
11 | this.TEXTAREA.className = 'handsontableInput';
12 | this.textareaStyle = this.TEXTAREA.style;
13 | this.textareaStyle.width = '0';
14 | this.textareaStyle.height = '0';
15 |
16 | //replace textarea with password input
17 | Handsontable.dom.empty(this.TEXTAREA_PARENT);
18 | this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/types/editors/text.types.ts:
--------------------------------------------------------------------------------
1 | import * as Handsontable from 'handsontable';
2 |
3 | class TextEditor extends Handsontable.editors.TextEditor {
4 | init() {
5 | super.init();
6 | }
7 |
8 | prepare(row: number, col: number, prop: string | number, td: HTMLElement, originalValue: any, cellProperties: Handsontable.GridSettings) {
9 | super.prepare(row, col, prop, td, originalValue, cellProperties);
10 | }
11 |
12 | hideEditableElement() {
13 | super.hideEditableElement();
14 | }
15 |
16 | showEditableElement() {
17 | super.showEditableElement();
18 | }
19 |
20 | getValue() {
21 | super.getValue();
22 | }
23 |
24 | setValue(value: any) {
25 | super.setValue(value);
26 | }
27 |
28 | beginEditing(newInitialValue?: any) {
29 | super.beginEditing(newInitialValue);
30 | }
31 |
32 | open() {
33 | super.open();
34 | }
35 |
36 | close() {
37 | super.close();
38 | }
39 |
40 | focus() {
41 | super.focus();
42 | }
43 |
44 | createElements() {
45 | super.createElements();
46 | }
47 |
48 | getEditedCell() {
49 | const editedCell = super.getEditedCell();
50 |
51 | return editedCell;
52 | }
53 |
54 | refreshValue() {
55 | super.refreshValue();
56 | }
57 |
58 | refreshDimensions(force: boolean = false) {
59 | super.refreshDimensions(force);
60 | }
61 |
62 | bindEvents() {
63 | super.bindEvents();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/types/index.types.ts:
--------------------------------------------------------------------------------
1 | import * as Handsontable from '../../handsontable';
2 |
3 | const baseVersion = Handsontable.baseVersion;
4 | const buildDate = Handsontable.buildDate;
5 | const packageName = Handsontable.packageName;
6 | const version = Handsontable.version;
7 |
--------------------------------------------------------------------------------
/test/types/renderers.types.ts:
--------------------------------------------------------------------------------
1 | import * as Handsontable from 'handsontable';
2 |
3 | const elem = document.createElement('div');
4 | const hot = new Handsontable(elem, {});
5 |
6 | const gridSettings: Handsontable.GridSettings = {
7 | valid: true,
8 | className: 'foo'
9 | };
10 |
11 | Handsontable.renderers.AutocompleteRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
12 | Handsontable.renderers.BaseRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
13 | Handsontable.renderers.CheckboxRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
14 | Handsontable.renderers.HtmlRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
15 | Handsontable.renderers.NumericRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
16 | Handsontable.renderers.PasswordRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
17 | Handsontable.renderers.TextRenderer(hot, new HTMLTableDataCellElement(), 0, 0, 'prop', 1.235, gridSettings);
18 |
--------------------------------------------------------------------------------
/test/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "es6",
5 | "dom"
6 | ],
7 | "module": "commonjs",
8 | "noEmit": true,
9 | "noImplicitAny": true,
10 | "noImplicitThis": true,
11 | "strictNullChecks": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "baseUrl": "",
14 | "paths": {
15 | "handsontable": [ "../../" ]
16 | }
17 | },
18 | "include": [
19 | "../../handsontable.d.ts",
20 | "**/*.types.ts",
21 | "../../src/plugins/**/test/*.types.ts"
22 | ]
23 | }
--------------------------------------------------------------------------------
/test/unit/helpers/Data.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | spreadsheetColumnLabel,
3 | spreadsheetColumnIndex,
4 | } from 'handsontable/helpers/data';
5 |
6 | describe('Data helper', () => {
7 | //
8 | // Handsontable.helper.spreadsheetColumnLabel
9 | //
10 | describe('spreadsheetColumnLabel', () => {
11 | it('should return valid column names based on provided column index', () => {
12 | expect(spreadsheetColumnLabel()).toBe('');
13 | expect(spreadsheetColumnLabel(0)).toBe('A');
14 | expect(spreadsheetColumnLabel(11)).toBe('L');
15 | expect(spreadsheetColumnLabel(113)).toBe('DJ');
16 | expect(spreadsheetColumnLabel(33439273)).toBe('BUDNIX');
17 | });
18 | });
19 |
20 | //
21 | // Handsontable.helper.spreadsheetColumnIndex
22 | //
23 | describe('spreadsheetColumnIndex', () => {
24 | it('should return valid column indexes based on provided column name', () => {
25 | expect(spreadsheetColumnIndex('')).toBe(-1);
26 | expect(spreadsheetColumnIndex('A')).toBe(0);
27 | expect(spreadsheetColumnIndex('L')).toBe(11);
28 | expect(spreadsheetColumnIndex('DJ')).toBe(113);
29 | expect(spreadsheetColumnIndex('BUDNIX')).toBe(33439273);
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/unit/helpers/Date.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | getNormalizedDate,
3 | } from 'handsontable/helpers/date';
4 |
5 | describe('Date helper', () => {
6 | describe('getNormalizedDate', () => {
7 | it('should return a proper date object, with time set to 00:00, when providing it with a date-only string', () => {
8 | const date1 = getNormalizedDate('2016-02-02');
9 | const date2 = getNormalizedDate('2016/02/02');
10 | const date3 = getNormalizedDate('02/02/2016');
11 |
12 | expect(date1.getDate()).toEqual(2);
13 | expect(date2.getDate()).toEqual(2);
14 | expect(date3.getDate()).toEqual(2);
15 |
16 | expect(date1.getMonth()).toEqual(1);
17 | expect(date2.getMonth()).toEqual(1);
18 | expect(date3.getMonth()).toEqual(1);
19 |
20 | expect(date1.getFullYear()).toEqual(2016);
21 | expect(date2.getFullYear()).toEqual(2016);
22 | expect(date3.getFullYear()).toEqual(2016);
23 |
24 | expect(date1.getFullYear()).toEqual(2016);
25 | expect(date2.getFullYear()).toEqual(2016);
26 | expect(date3.getFullYear()).toEqual(2016);
27 |
28 | expect(date1.getHours()).toEqual(0);
29 | expect(date2.getHours()).toEqual(0);
30 | expect(date3.getHours()).toEqual(0);
31 |
32 | expect(date1.getMinutes()).toEqual(0);
33 | expect(date2.getMinutes()).toEqual(0);
34 | expect(date3.getMinutes()).toEqual(0);
35 | });
36 |
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/unit/helpers/Feature.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | getComparisonFunction,
3 | } from 'handsontable/helpers/feature';
4 |
5 | describe('Feature helper', () => {
6 | //
7 | // Handsontable.helper.getComparisonFunction
8 | //
9 | describe('getComparisonFunction', () => {
10 | it('should correct equals strings', () => {
11 | const comparisonFunction = getComparisonFunction();
12 |
13 | expect(comparisonFunction('a', 'b')).toBe(-1);
14 | expect(comparisonFunction('b', 'a')).toBe(1);
15 | expect(comparisonFunction('b', 'b')).toBe(0);
16 | // pl
17 | expect(comparisonFunction('a', 'ł')).toBe(-1);
18 | expect(comparisonFunction('ł', 'a')).toBe(1);
19 | expect(comparisonFunction('Ą', 'A')).toBe(1);
20 | expect(comparisonFunction('Ź', 'Ż')).toBe(-1);
21 | expect(comparisonFunction('Ż', 'Ź')).toBe(1);
22 | expect(comparisonFunction('ą', 'ą')).toBe(0);
23 |
24 | expect(comparisonFunction('1', '10')).toBe(-1);
25 | expect(comparisonFunction('10', '1')).toBe(1);
26 | expect(comparisonFunction('10', '10')).toBe(0);
27 | expect(comparisonFunction(1, 10)).toBe(-1);
28 | expect(comparisonFunction(10, 1)).toBe(1);
29 | expect(comparisonFunction(10, 10)).toBe(0);
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/unit/helpers/TemplateLiteralTag.spec.js:
--------------------------------------------------------------------------------
1 | import { toSingleLine } from 'handsontable/helpers/templateLiteralTag';
2 |
3 | describe('Helpers for template literals', () => {
4 | describe('toSingleLine', () => {
5 | it('should strip two line string (string with whitespace at end of first line and indention at second one)', () => {
6 | const text = toSingleLine`Hello world
7 | Hello world`;
8 |
9 | expect(text).toEqual('Hello world Hello world');
10 | });
11 |
12 | it('should strip two line string (string without whitespace at end of first line and indention at second one)', () => {
13 | const text = toSingleLine`Hello world
14 | Hello world`;
15 |
16 | expect(text).toEqual('Hello worldHello world');
17 | });
18 |
19 | it('should include literals and not remove whitespaces between them without necessary', () => {
20 | const a = 'Hello';
21 | const b = 'world';
22 | const text = toSingleLine`${a} ${b}`;
23 |
24 | expect(text).toEqual('Hello world');
25 | });
26 |
27 | it('should remove whitespaces from both sides of a string.', () => {
28 | const a = ' Hello';
29 | const b = 'world ';
30 | const text = toSingleLine`${a} ${b}`;
31 |
32 | expect(text).toEqual('Hello world');
33 | });
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/unit/i18n/phraseFormatters/index.spec.js:
--------------------------------------------------------------------------------
1 | import { getAll as getAllFormatters, register as registerPhraseFormatter } from 'handsontable/i18n/phraseFormatters';
2 |
3 | describe('i18n phraseFormatters', () => {
4 | it('should register formatters at start', () => {
5 |
6 | // Formatter `substituteVariables` isn't registered at the moment.
7 | expect(getAllFormatters().length).toEqual(1);
8 | });
9 |
10 | it('should register formatter by `register` function', () => {
11 | registerPhraseFormatter('exampleFormatterName', () => {});
12 |
13 | expect(getAllFormatters().length).toEqual(2);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/update.json:
--------------------------------------------------------------------------------
1 | {
2 | "COMMENT": "This is a update.json file used by jsDelivr.com CDN",
3 | "packageManager": "github",
4 | "name": "handsontable",
5 | "repo": "handsontable/handsontable",
6 | "files": {
7 | "include": ["./dist/*.js", "./plugins/**/*.js", "./plugins/**/*.css"],
8 | "exclude": ["./plugins/**/*spec*"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var webpack = require('webpack');
4 |
5 | var env = process.env.NODE_ENV;
6 | var configFactory = require('./.config/' + env);
7 |
8 | // In some cases, npm env variables become rewritten to lower case names. To prevent this it is rewritten to the
9 | // original variable name so the --testPathPattern work in any case.
10 | if (process.env.npm_config_testpathpattern) {
11 | process.env.npm_config_testPathPattern = process.env.npm_config_testpathpattern;
12 | }
13 |
14 | module.exports = function() {
15 | return configFactory.create({
16 | testPathPattern: process.env.npm_config_testPathPattern,
17 | });
18 | };
19 |
--------------------------------------------------------------------------------