From e700e5e6eaa4046eaebfe33c49ed0800422eca6d Mon Sep 17 00:00:00 2001 From: Andrey Churkin Date: Thu, 28 May 2020 22:15:31 +0300 Subject: [PATCH] Merge renovation (#13214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * preact button * add generator * Template * add generation to build task * get generator from npm * update generator * Add the elementAttr property * Add the type property * skip known errors * update generator * update elementAttr * change type for elementAttr * remove test-list, test-button-group * fix test-button jQuery wrapper * fix memory leaks tests * elements on refresh test increase timeout * Update Button * move declarations/component-declaration from js * increase tick in elementsOnRefresh test * update generator * Rollback elementsOnRefresh and refix * Update devextreme-internal-tools * Revert "move declarations/component-declaration from js" This reverts commit df2c2ec4dfdad96a071928ea2fb263710cbdb4c7. * add babel-plugin-transform-object-assign, remove unused packages * remove extra line * Add tests for new components * Add node env to eslint config * Fix merge * Rollback jQuery playground * Fix warnings on generation * Remove extra tsconfig * fix lint errors * Fix lint in themebuilder * fix gulp tasks * use full reporter in tsProject * remove generated file * ignore generated files * move eslint node env setting to testing/renovation * add empty line * remove comment * fix lint * Update js/ui/test-button.tsx Co-Authored-By: Alexander Ziborov <1420883+San4es@users.noreply.github.com> * small refactoring * fix warning * small refactoring * update generator * Add BaseWidget component * Remove TestDomComponent file * update generator * add Effect decorator * Start adding tests for TestWidget * Fix hot code transpiling in test environment (#11476) * Add some tests to TestWidget * Support aria attrs * Add tests * Add native events * Add dxClick and dxHover events * Add `activeStateUnit` private property * Use TestWidget inside the TestButton * Add the active event * Fix tests * Add keyboard support to button * Add aria support * Add undefiend fields check to avoid test crash * Update generator * Add test for ARIA * Add test for 'disabled' * Extend widget props * Temporary workaround for Widget JSX Constructor (#11662) * Temporary workaround for Widget JSX Constructor * add empty line to the end of jsx.d.ts * fix after review * update package.json (#11675) * Renovation: add Widget.accessKey tests (#11643) * Renovation: add accessKey tests * tests refactoring * test-widget refactoring * fix tests * Run jest test on GitHub CI (#11697) * Run jest test on GitHub CI * add test_jest in travis and drone matrix * JQuery widget: fix removing container attributes (#11690) * JQuery widget fix removing container attributes * fix onClick in button * Renovation: add async qunit tests (#11713) * Renovation: add async qunit tests * fix remark * fix tests * Renovation: make renovation folder (#11696) * Renovation: implement Button.stylingMode (#11735) * Renovation: implement Button.stylingMode * fix tests * refactoring * fix remarks * fix remark * Renovation: fix elementAttr property (#11739) * Renovation: Implement default button behavior (#11743) * Renovation: jest tests refactoring (#11788) * Renovation: jest tests refactoring * fix tests * fix tests * rewrite few tests * Renovation: add keyboard tests (#11806) * Renovation: add keyboard tests * fix remarks * Renovation: add hoverStateEnabled property tests (#11771) * Renovation: add jest eslint rules (#11809) * Renovation: add jest eslint rules * use recommended rules * fix ci * Update .drone.yml #11721 * Renovation: add Button's text tests (#11827) * Renovation: add Button's integration tabIndex tests (#11826) * Renovation: fix tests tsx compilation (#11822) * Renovation: fix tests tsx compilation * fix lint errors * Update package.json https://github.com/facebook/jest/issues/3254#issuecomment-488434727 * Renovation: implement Button.useSubmitBehavior (#11845) * Renovation: implement Button.useSubmitBehavior * fix remarks * Move components input to separate class (#11872) * Move components input to separate class * fix 'Cannot find name 'h'.ts(2304)' in declarations * fix error TS2345 * update generator * remove component_declaration module * Merge branch 'preact-button' of https://github.com/DevExpress/DevExtreme into new-design # Conflicts: # js/renovation/button.tsx # js/renovation/widget.tsx * Renovation: move click and key handlers into Button declaration (#11894) * Renovation: move click and key handlers into Button declaration * fix remarks * Renovation: Rename declaration decorators (#11920) * Renovation: inkRipple refactoring (#11916) * Renovation: implement Button.useInkRipple (#11932) * Renovation: Button.icon refactoring (#11948) * Renovation: use tslint before commit (#11975) * Merge 20_1 into preact_button (#11978) * DataGrid: Fix scrolling if max-height of the widget container is set to a non-integer number of pixels (T849902) (#11632) * TabPanel - Fix active tab switching on focusIn (T852689) (#11583) * Fix active tab switching on focusIn (T852689) * Tests for fix T852689 * Small refactoring * Small fix * Tests improvements * Small refactoring * Test refactorings * Tests improvements * fix tests for non jquery enviroment * Small fix * Small test refactoring * Fix test for mobile devices * Fix focusing for mobile devices * Fix focusing logic * Small refactoring * Small fixes * Pull request feedback * Tets fix * Small refactoring * Small fix * Refactoring * Small tests refactoring * Small tests refactoring * Small test refactoring * Tests refactorings * Rid of unexpected borders for the DataGrid selected rows in generic light and dark themes (T853231) (#11665) * Fix mock of window for server side tests (#11681) * PivotGrid: Fix texts.emptyValue was not working (T852897) (#11671) * Fix environment preparation for gesture cover tests (#11672) * Fix environment preparation for gesture cover tests * refactor * Comment unstable assert in test (#11688) * Fix DropDownEditor Popup position depending the "rtlEnabled" option value (T856114) (#11687) * Fix Popup position of the DropDownEditor depending on "rtlEnabled" option value (T856114) * refactoring * Support date serialization formats for DateBox with list picker type (T854579) (#11695) * DataGrid - Setting the isHighlighted option in the onFocusedCellChanging event handler does not work when the end user uses the Tab key to navigate between data cells (T853599) (#11667) * Renovation: odata/mixins es6 refactoring (#11444) * Renovation: Data Source refactoring (#11479) * DataGrid: Fix onRowClick call on "Save" click when edit mode is "form" (T848729) (#11615) * DataGrid: Fix onRowClick call on "Save" click when edit mode is "form" (T848729) * Fix test * Gantt: fix incorrect key value on assignment deleting (T850951) (#11704) * UpdateCI - Functional tests - Added testCafe quarantineMode parameter and applied it for the Scheduler (#11640) * UpdateCI - Functional tests - Added testCafe quarantineMode parameter and applied it for scheduler. * A bit of refactoring * A bit of refactoring * DataGrid - Focused row should not being reseted after begin edit row if form edit mode (T851400) (#11620) * DataGrid - Contrast Theme - Deleted records are not visible in Batch Editing Mode ( T856115) (#11702) * Fix recurrence part position and size after daylights saving change (T804886, first step) (#11657) * dxScheduler - fix recurrent appointment rendering with workWeek view (T853629) (#11660) * Scheduler, remove skipTimezone checking after T834428 fixing (#11680) * ExcelJS - changing 'wrapText' setting according grid.wordWrapEnabled option (#11701) * ExcelJS - changing default value for 'autoFilterEnabled' property to false (#11700) * Refactor some functions in Scheduler subscribes (#11712) * Draft (#11715) * Fix Scheduler tests in Tokyo and Australia timezones (#11722) * Scheduler - Reduce test speed in unstable functional tests (#11706) * Fix hiding tooltip by click issue (T850217) (#11720) * Add missing tests on events for Popup and Overlay (#11719) * Fix Lookup styles for compact themes (T856794) (#11725) * Drone CI: colorized logs (#11721) * Less refactoring for scss generation (icons, typography, button, buttonGroup, scrollView) (#11629) * File Manager - Directory Chooser dialog text change (#11732) * Draft * Next iteration * Implement dialogManager and add localizations constants * Remove redundant code * Fix up test * Add confirmation dialog draft and move dialog inits to ctor * Fix uncaught exception when a legend is hidden (T854736) (#11737) * File Manager - Notification popup configuration (#11741) * Draft * Reset text alignment * Option rename * DateBox should not raise any errors when useMaskBehavior is enabled and locale digits are different to arabic digits (T851630) (#11678) * Remove some code for deprecated formatWidthCalculator and closeOnValueChange DateBox options (#11736) * File Manager - Change icon of filesView 'ParentFolder' item (#11634) * Icon changed * Breadcrumbs icon revert and filesView parentFolder icon change * List (Sortable) - Fix item dragging to the top position when allowReordering is false (T856292) (#11729) * List (Sortable) - Fix item dragging to the top position when allowReordering is false (T856292) * Fix lint * Do not use real clean-css module in some integration tests. (#11749) * TreeView - fix docs for the rootValue (T854356) (#11718) * Fix docs for the rootValue of the treeView (T854356) * Small tests refactoring * File Manager - 'File actions' button shape (#11746) * File Manager - Thumbnails View - adjust icons size * Next version * Draft * Revert "Next version" This reverts commit cd99f05fc03c8d0d0b385d0722a66610757335b2. * Next one draft * Buttons and icons now are of default theme size * Looks like not bad case * Change focused icon color * Remove comment * Revert "File Manager - Thumbnails View - adjust icons size" This reverts commit 92a82e828bfda3540da86e9c41e30b9cb494a27c. * Change list icon in dark material theme (T857017) (#11752) * SelectBox: add some event tests (#11756) * Renovation: odata/store es6 refactoring (#11391) * dxPieChart: Fix triggering onLegendClick for legend icon (T854491) (#11760) * Add missing tests for a DateBox widget (#11733) * Use bash arrays for Chrome args (docker-ci) (#11764) * Scss generator (#11750) * Drone CI - Add time zone argument to the test matrix (#11761) * Drone CI - Add time zone argument to the test matrix * Add TZ for Travis, Shippable * QUnit: Rework API for ignoring uncleared timers (#11765) * QUnit: Refactor to use one way for ignoring uncleared timers * QUnit: Get rid of timerIgnoringCheckers.applyUnregister method * QUnit: timerIgnoringCheckers -> timersDetector.ignoreRules * QUnit: ignoreRules.needSkip -> ignoreRules.shouldIgnore * QUnit: Get rid of ignoreAngularBrowserDeferTimer duplication * QUnit: Refactor isThirdPartyLibraryTimer * QUnit: Get rid of normalizedTimerInfo * QUnit: Refactor spyWindowMethods * QUnit: Consolidate all ignore rules for angular in one place * QUnit: Log test failure when uncleared timers detected instead of global one * Less: change icon mixin from 'selector' to 'parametrized' (#11763) * Update README.txt Sync with https://github.com/DevExpress/DevExtreme/pull/10897 * Remove advanceCaret DateBox option (#11768) * Scrollable - check scroll position after update() (T848870) (#11766) * DataGrid - column headers do not align with cells if showScrollbar is 'always' (#11758) * Add missing NumberBox test (#11769) * DataGrid: Fix selected checkboxes were not checked after page refresh if stateStoring is enabled and renderAsync is true (T857205) (#11773) * Scss generator: additional widgets (#11782) * Add bundled tests for "onInitialized" and "onDisposing" events (#11762) * Add bundled tests for "onInitialized" and "onDisposing" events * add LayoutManager to tests Co-authored-by: AlekseyMartynov * Drone CI: reduce dotnet cache footprint (#11783) * ExcelJS - set vertical alignment for cell is 'top' as default (#11778) * Add noClean option for devextreme-themebuilder (#11790) (#11799) * Gantt: Add time intervals (#11776) * DataGrid: The onFocusedRowChanged event firing refactoring (#11724) * Fix passing of the event that caused the value to change when clicking the List item of the DateBox time picker (T858107) (#11800) * DropDownButton: add some events tests (#11775) * DropDownButton: add some events tests * Fix widget behavior - Possible problems with SSR (depends on specific config) has been resolved - ContentReady event has been normalized - OptionChanged issues has been resolver * add one more test * remove comma * fix typo Co-authored-by: Dmitry Levkovskiy * Diagram redesign (#11812) * Diagram - UI changes (toolbox) + rename some files * Fix linter errors * QUnit: Enable errors for 'qunit/no-ok-equality' rule (#11816) * Fix "Initialized and Disposing events" tests for IE (#11814) * HtmlEditor: Ignore Quill timers in tests (#11818) * Add missing TagBox tests (#11810) * QUnit: Make extensions compatible with IE (#11843) * Scss: add new widgets to generator (#11831) * DataGrid: Fix cell was not highlighted after editing another cell and click (refix T836391) (#11659) Co-authored-by: AlekseyMartynov * DOTNET_USE_POLLING_FILE_WATCHER=true (docker-ci) (#11855) * FileManager - Update result type for file provider's methods (#11853) * Scss: add new widgets to generator (#11868) * ThemeBuilder tests refactoring (#11801) * DataGrid: Fix group cell that has the dx-datagrid-hidden-column class when the hidingPriority property is specified for a grid column (T857506) (#11833) * DataGrid: Fix group cell that has the dx-datagrid-hidden-column class when the hidingPriority property is specified for a grid column (T857506) * Fix lint * Support ScrollView reachBottom event for dropDownLists keyboard navigation if the browser is zoomed (T858013) (#11851) * dxScheduler - fix appointment popup on closing (T854500) (#11754) * Fix angular typescript hack (#11884) * Fix FF test after #11724 (#11876) * Widgets: Eliminate global object pollution (#11887) * CollectionWidget: Remove globals in tests * Tooltip: Do not set 'aria-describedby' to window * TreeList: Fix values were not assigned by e.promise in onInitNewRow (T857405) (#11794) * Fix Calendar accessibility issues (#11846) * Fix Calendar accessibility issues * Calendar views refactoring * base view refactoring * remove code duplication * Update cache before callBase * Support zero value as a new selected item key for the DropDownButton (T858013) (#11880) * DataGrid: Remove globals in tests (#11888) * FileManager - Add public constructor for FileManagerItem class (#11885) * TreeList: Add node property for a detail adaptive row (#11871) * ExcelJS - add doс comments for arguments of the 'exportDataGrid' function (#11807) * TreeList: Fix navigateTo to the same page with row expanding after #11724 (#11895) * Don't hang if QUnit runner is broken (docker-ci) (#11897) * Update docker-ci.sh Quotes * Revert "ExcelJS - add doс comments for arguments of the 'exportDataGrid' function (#11807)" (#11898) * Merge js/events.d.ts into js/events/index.d.ts (#11901) * DataGrid - KeyboardNavigation - Replace pointerUp with pointerDown (#11900) * DataGrid - KeyboardNavigation - Replace pointerUp with pointerDown * Fix fileManager tests * Fix treeList tests * Fix tests in FF * Fix IE test * FileManager - Update public API (#11905) * File Manager - UploadPanel items appearance (#11907) * RadioGroup: add some event tests (#11866) * TreeList: Fix getSelectedRowsData method when calling navigateToRow in the onNodesInitialized event (T858312) (#11904) * ExcelJS - add doс comments for arguments of the 'exportDataGrid' function (#11912) * Diagram - fix server-side rendering test (#11913) * DataGrid: Fix onFocusedRowChanged firing on scroll if autoNavigateToFocusedRow is false after #11724 (#11910) * Fix zero-time appointment width in month & timelineMonth views (T858496) (#11860) * Fix zero-time appointment width in month & timelineMonth views (T858496) * rename index * Refactor * Drone CI - Fix setting timezone (#11906) * Drone CI - Fix setting timezone * Drone CI - Correct timezones * List: Fix incorrect dragged item render if RTL is enabled (T859557) (#11918) * Fix "event" argument of the Switch "valueChanged" event when value changed by gesture (T860005) (#11922) * File Manager - ProgressPanel close button alignment (#11926) * File Manager - ProgressPanel close button alignment * Now it's more aligned * Update docker-ci.sh * run_$TARGET convention (docker-ci) * Update ja.json (#11879) * Update ja.json Added localizations for Max and Min * Update ja.json localized dxDataGrid-summaryAvg as well * FileManager - Update FileSystemProviderBase.getItems() method signature (#11933) * Update localization.js Give names to a bunch of tasks * ExcelJS - rename cellsRange to cellRange (#11936) * DataGrid: fix column header's sort icon remains after grouping by this column if showWhenGrouped (T859208) (#11914) * dxLookup - Fix field paddings in the Material theme * DataGrid - Click by command select cell should not highlight focus if editing is enabled (refix T836391) (#11937) * CI: QUnit runner watchdog (#11938) * ExcelJS - change doc comments (#11935) * Don't build all themes in TEST_CI mode (#11939) * Update aspnet.tests.js spy.restore() * File Manager - ProgressPanel file icons size (#11950) * FileManager - Progress panel close button alignment (#11951) * File Manager - ProgressPanel close button alignment * Now it's more aligned * File Manager - ProgressPanel close button alignment * Diagram - redesign (add history toolbar and view toolbar) (#11944) * Diagram - add history toolbar + viewSettings toolbar - first commit * Diagram - commands refactoring * Diagram - add an ability to check context menu items * Diagram - context menus refactoring * Diagram - context menu icons fix * Diagram - fix select box commands * Diagram - toolbars refactoring + tests * Fix tests * Fix tests * Fix tests * Fix the knokout binding with the observable array in the Form (#11923) (#11954) * FileManager - Update CustomFileSystemProviderOptions declarations (#11952) * FileManager - Update event / callback function signatures (#11953) * Draggable: fix clone had incorrect direction if rtlEnabled (T859557 refix) (#11928) * Fix label and bar point overlapping on the edge of pane (T856746) (#11955) * Fix double min-width rule in the CSS bundle (#11960) * ASP.NET: warning about the alternate template syntax (#11934) * Limit DropDownLists popup height if the container is larger than a window (T859133) (#11949) * FileManager - Add upload.chunkSize option (#11966) * Fix after merge Co-authored-by: Konstantin Volnyagin Co-authored-by: Sergey Novikov <57402891+novsstation@users.noreply.github.com> Co-authored-by: kotov.alexander Co-authored-by: Andrey Ignatovskiy <43685423+LazyLahtak@users.noreply.github.com> Co-authored-by: Jaan Toming Co-authored-by: Dmitry Levkovskiy Co-authored-by: Alexey Kamyshin Co-authored-by: Igor Maltsev Co-authored-by: Alyar Co-authored-by: Alexander Zelevinskiy <16476188+zelik88@users.noreply.github.com> Co-authored-by: Anton Sermyazhko Co-authored-by: Yana Yarovaya Co-authored-by: Smirnova Yuliya Co-authored-by: EugeniyKiyashko Co-authored-by: Stanislav Klesarev Co-authored-by: polosatov.alexander Co-authored-by: AlekseyMartynov Co-authored-by: Alexey Babich Co-authored-by: Anton Kuznetsov Co-authored-by: Alexander Bezborodov Co-authored-by: Alexander Ziborov <1420883+San4es@users.noreply.github.com> Co-authored-by: Stepan Co-authored-by: Roman Rodin Co-authored-by: Vladimir Kovalev <47112293+vladkovl@users.noreply.github.com> Co-authored-by: ilya.kharchenko <14272298+IlyaKhD@users.noreply.github.com> Co-authored-by: AlisherAmonulloev Co-authored-by: groshenkovamarina Co-authored-by: Dmitry Semenov Co-authored-by: pavelgruba * Renovation: implement button's iconPosition (#11757) * Renovation: implement base widget focus and add icon test (#11711) * Renovation: get rid of Actions (#11962) * Renovation: add onVisibilityChange callback functionality (#11830) * Renovation: pass click event into Button's click (#12054) * Renovation: implement Button.template (#11899) * Update docker-ci.sh This should resolve "npm ERR! Cannot read property 'match' of undefined" * Move icon to dedicated component (#12135) * Renovation: add old tests for renovation button (#12140) * Fix button ARIA functionality (#12151) * Renovation: correct template tests for new button (#12153) * Renovation: fix IE errors when using template wrapper (#12154) * Renovation: fix template wrapper with text node child (#12159) * Default option rules (#12158) * Renovetion: Implement defaultOptionRules * use jest mocks instead of sinon * remove sinon * fix after review * DroneCI - DataGrid - Fix tests for the latest FF (#12106) * Prepare tests for build image update (#12162) * Update Drone CI environment * DataGrid - Wrong freeSpace row height calculation in FF 70.0+ (#11983) * DataGrid - Wrong freeSpace row height calculation in FF 70.0+ * Extract height correction logic * Simplify height correction logic * Fix tests * Improve tests * Renovation: pass event on keyboard press (#12136) * Renovation: add onContentReady property to button and widget (#12174) * Renovation: Move viewModel to class with component declaration (#12188) * Move viewModel to class * Update generator version * fix review remarks * fix regression * Move template's 'model' param to jQuery widget (#12223) * Renovation: refactor actions in button (#12274) * Removation: implement validationGroup (#12280) * Renovation: implement submit behavior (#12299) * Renovation: code coverage for jest tests (#12328) Co-authored-by: Andrey Churkin * Renovation: jest tests refactoring (#12346) Co-authored-by: Andrey Churkin * Renovation: remove unstable button qunit tests (#12347) * Add Angular and React generators (#12352) * add generate-angular, generate-react gulp tasks * import click and hover * add angular and react apps to playground * add readme and tsconfig * fix support import in pointer module * update generator-version * rollback extra change * rename parameter * extract base tsconfig * fix remark * add eslintignore for react and angular * rework ignoring eslint rules for playground * Skip default options generation for Icon and widget components (#12360) * Skip generating defaultOptions for icon and widget * set coverege threshold 100 * Do not build scripts before jest test * update generator version * generate components before jest test * Renovation: refactor template render (#12371) * Add the ErrorMessage component (#12357) * Add validation declaration component * Edit tests * Remove excess comments * Fix tests * Fix test code coverage * fix merge * Fix merge#2 * fix merge#3 * Fix IE test * Fix events declaration (#12515) * Refactor icon condition in button (#12478) * Renovation: Preparations for jQuery generation (#12527) * Renovation: add SSR for Button (#12488) * Renovation: save custom classes on type changes (#12547) * Renovation: provide custom sizes from styles (#12580) * Renovation: add anonymous template (#12599) * Renovation: render or remove Ink Ripple on useInkRipple change (#12554) * Renovation: merge 20_1 into preact-button (#12705) * Renovation: add checking of parent node on render (#12676) * Renovation: add setAria method into preact wrapper (#12703) * Renovation: implement registerKeyHandler (#12748) * Renovation: rename onKeyPress property to onKeyDown (#12759) * Prepare to JQuery generation (#12750) * Renovation: add gulp tasks to build react and angular (#12777) * add @types/enzyme (#12789) * Renovation: Add the ability to test declaration, fix react warnings (#12755) * Renovation: Add the ability to test declaration, fix react warnings * fix tests, rename Input --> Props Co-authored-by: Andrey Churkin * Add capability to spread rest attributes (#12455) Changed `hasClass` to `includes` because I removed `className` property. See documentation of [hasClass](https://enzymejs.github.io/enzyme/docs/api/ShallowWrapper/hasClass.html) > Returns whether or not the wrapped node has a `className` prop including the passed in class name. * Renovation. Turn on jQuery generator (#12793) * Fix missed extend issue (#12807) * jQuery based selectbox and numberbox (#12794) * Remove using $ and onValueChanged (#12847) * Remove restAttributes prop from widget (#12840) * jQuery generator. Add Actions for Events (#12884) * Renovation: extract common props from the widget component (#12886) * Create base component for legacy button (#12914) * Renovation: Get rid of tslint (#12897) * get rid of tslint * merge * fix linter * fix jest tests * fix jest tests * fix button.d.ts Co-authored-by: Andrey Churkin * use eslint-config-devextreme package (#12946) Co-authored-by: Andrey Churkin * use global event mock (#12955) * Fix lint warnings in the jest tests (#12951) Co-authored-by: Andrey Churkin * App vue playground (#13107) * Add vue app into playground * Fix gulp file * update readme * disable eslint * Renovation: Pass original event into onClick (#13127) * Renovation: Add CRA in playground (#13159) * remove system js app * Add react cra in playground * Update generator * Fix tests * try to disable eslint * Merge with 20_2 (#13189) * Scss generator: fix regex and task name (#12650) * Resizable: add some event tests (#12606) * dxPieChart: fix resolving to the overlapping point labels (T877200) (#12658) * File Manager - Fix custom items visibility in file toolbar (#12657) * File Manager - Fix custom items visibility in file toolbar * Code refactored * Diagram - update core version (#12662) * Fix Scheduler rendering when rtlEnabled = true (T850771) (#12628) * DataGrid: Fix data loading on scrolling after deleting several rows if scrolling mode is infinite and refreshMode is repaint (T862268) (#12638) * FileManager - Fix selection clearing in single selection and focused item updating during navigation (#12661) * FileManager - Fix selection clearing in single selection and focused item updating during navigation * update test - support focused item * add test * NumberBox: it should be possible to set negative value when min is null (T876378) (#12653) * DataGrid - Fixes SelectAll state when all items in the header filter are deselected (T875471) (#12627) * Diagram - rename API (childrenExpr => containerChildrenExpr) (#12667) * dxGantt: Fix editing and validation API (#12670) * DataGrid - Fixes the expand state changing of a group row when the Enter Key is pressed (T869799) (#12652) * Update jQuery to version 3.5.0 (#12672) * chore(package): update jquery to version 3.5.0 * Fix unit tests * fix export test Co-authored-by: greenkeeper[bot] <23040076+greenkeeper[bot]@users.noreply.github.com> Co-authored-by: EugeniyKiyashko * revert test (#12659) * FileManager - Fix customizeDetailColumns argument type (#12678) * DataGrid: Fix loading all items for 'anyOf' operation if headerFilter.dataSource is used (T876959) (#12671) DataGrid: Fix loading all items for 'anyOf' operation if headerFilter.dataSource is used (T876959) * dxViz: Fix font opacity for tooltip (T879069) (#12684) * Fix the scrolling and display the navigation buttons when the asynchronous templates are used (T875754) (#12663) (#12681) * DataGrid - The custom button template content is not rendered if renderAsync is true (T876950) (#12668) * Drawer - Add 'minSize' tests (#12673) * Add 'minSize' tests * Remove checks in invisible state * lint: change var to let * Correct getBoundingClientRect usage: the returned object should not be modified. * HtmlEditor - Allow to delete selected images (T878203) (#12687) * DataGrid: Fix incorrect cell focus after editing bool column with virtual row rendering (T872126) (#12636) * Revert "revert test (#12659)" (#12700) This reverts commit 64bd346bba40b1dcb953d291f26efb5ce6f1a520. * Get rid of redundant code in Intl integration (#12699) * Diagram - UI/UX fixes (#12682) * Diagram - mobile UI - hide toolbox when text editing is started * Diagram - update toolbox size after filtering * Diagram - fix that it's not possible to hide toolbar submenu on touch devices without click on its subitem * Diagram - update core package * Diagram - force restart tests * Diagram - update core version * dxDiagram - hide viewpanel while mobile toolbox is visible, fix adaptive calcs (#12704) * ExcelJS - mark the old export options as deprecated (#12696) * Revert ssr test (#12690) * Fix Scheduler tests in Edge (#12688) * FileManager - Fix missing column properties (#12711) * Update the "dropDownOptions" option type for the Lookup (#12712) * Create stale.yml * Update the "dropDownOptions" option type for Lookup * Delete stale.yml * dxChart - fix labels overlapping for small bars (T869506) (#12686) * PivotGrid: Fix sorting icon is positioned incorrectly when header text was wrapped to the next line (T878428) (#12708) * DataGrid: Fix incorrect rowIndexes in cellTemplate if row rendering is virtual (T878343) (#12694) * Fix appointment-tail calculation in Scheduler (T854740) (#12691) * Fix Scheduler long-appointment overlapping (T864456) (#12692) * DataGrid - Fixes focusing row when a command button is clicked (T879627) (#12706) * DropDownList and Lookup: get rid of qunit-fixture styling (#12697) * Autocomplete: add some event tests (#12715) * dxChart - fix tooltip behavior on touch devices (T856700) (#12718) * Fix Scheduler tests in Edge (#12722) * FileManager - Fix selection clearing during refresh (#12721) * FileManager - Fix method return type for documentation (#12734) * FileManager - Update toolbar clear selection command name (#12731) * FileManager - Update API for obtaining the FileSystemItem extension (#12732) * FileManager - Fix allowedFileExtensions option default value (#12738) * ContextMenu - Fix changing visible option (T879766) (#12693) * Bugfix T879766 * Pull request feedback * PR feedback * dxGantt: Add icons and hints to bars (#12737) * File manager - Fix toolbar separators calculation (#12698) * Fix problem with item location * Fix problem with empty group * fix compact mode issue * Fix issue with custom toolbar items in compact mode * Code refactor * Remove redundant argument * Apply workaround for regression with item render in menu after first load * Code refactor * Refactor a bit * Update test * Diagram - UI/UX fixes (#12727) * Diagram - fix show/hide a toolbar submenus * Diagram - refactor context menus + fix JS error on fullscreen toggle * Diagram - fix z-order of panels(toolbox/properties panel) and context menu/toolbox + refactoring * Diagram - fix toolbars context menu incorrect showing + refactoring * Fix tests * Fix tests on Touch devices * dxGantt: Fix toolbar items (#12744) * File Manager - Fix toolbar items default location (#12745) * File Manager - Fix toolbar items default location * Code refactor * File manager - Fix possibility to specify toolbar items by option full name (#12749) * Add test * Add fix * Refactor * Diagram - update core package (#12746) * Diagram - update core package * Bump DevExtreme version (#12752) * dxToolbar - Fix internal menu styles (#12714) * File manager - Support compact mode for custom toolbar items (#12754) * Add test * Add fix * Refactor * Now compact only if it's button with icon & text * Refactor * FileManager - Fix directory renaming when allowedFileExtensions specified (#12761) * DataGrid - Fixes highlighting unmodified invalid cells in the Batch editing mode (T880238) (#12739) * FileManager - Fix selection changed event for select all operation (#12764) * Sortable: Fix reordering of items in nested sortable (T875890) (#12720) * File Manager - Fix native scroll in thumbnails view mode (#12769) * Add scrollView to thumbnailsView * All views must keep scroll position for sync focused item * Fix tests for mobile devices * Refactored a bit * Add keyboard navigation test for thumbnails view * Temprorary remove test * Editor should validate the current value before the "valueChanged" event occurs (T878544) (#12741) * DataGrid - Fixes expandRow (T880769) (#12765) * Drawer - Mobile keyboard causes overlapping drawer to be positioned incorrectly(T865716) (#12753) * DataGrid: Fix columnChooser scrolled up after item selection (T880276) (#12756) * DataGrid: Rework selectionFilter simplification if deferred selection is used (T814753, T874992) (#12728) * FileUploader - The uploadChunk function does not work with the native Promise objects (T881508) (#12782) * ESlint: Use "qunit/recommended" and "qunit/two" presets (#12772) * ESlint: Use "qunit/recommended" preset * ESlint: Use "qunit/two" preset * ESLint: Remove rules included in presets * File Manager - Fix custom thumbnails size (#12786) * FileManager - Fix IE tests (#12790) * FileManager - Fix IE tests * fix test * Drawer - deprecate the 'target' option (T863881) (#12760) * Toolbar - deprecate height option (#12766) * Deprecase height in toolbar * Fix deprecated text * Test for deprecated option * Update js/ui/toolbar.js Co-Authored-By: RomanTsukanov * Fix test text * Pull request feedback Co-authored-by: RomanTsukanov * DataGrid: Fix incorrect row render after scroll if scrolling and row rendering are virtual (T750279) (#12762) * Add config for the Stale bot (#12733) * dxViz: Refactoring (es6) (#12796) * File manager - Fix progress panel 'no operations' text (#12797) * Add tests * Add fix * Fix Scheduler timelineWeek rendering with groupByDate & crossScrolling (T853642) (#12735) * Diagram - add ability to set up initial settings for items (#12798) * Diagram - add ability to set up initial settings for items * Rename options * DataGrid: Fix row dragging if remote data with slow connection is used (T867087) (#12799) * DataGrid - Adds a test for (T878218) (#12781) * TextEditor - Prevent custom buttons overlapping in ie11 (T879885) (#12795) * FileManager - Fix thumbnails view single selection synchronization with focused item (#12806) * FileManager - Fix thumbnails view single selection synchronization with focused item * fix selecteAll() in multiselection mode * ESLint: Enable errors for "qunit/no-commented-tests" rule (#12771) * ESLint: Enable errors for "qunit/no-commented-tests" rule * Uncomment and skip tests * DataGrid: Fix new row's cell value was not displayed for lookup column (T879946) (#12802) * Color swatch will use the same typography settings as the main theme (#12780) * Color swatch will use the same typography settings as the main theme * Update themebuilder/modules/less-template-loader.js Co-Authored-By: Sergey Zvyagin * remove unnecessary escape * simplify regex Co-authored-by: Sergey Zvyagin * dxGantt: Fix error on adding an item next to the root one (#12811) * DataGrid: Fix wrong rowspan for band columns if showWhenGrouped (T881055) (#12791) * DataGrid: Move test modules to the modern syntax (Part 2) (#12808) * DataGrid - Columns become out of sync (T844512) (#12804) * Fix prefer-const eslint warning for TreeList and PivotGrid (#12817) * Diagram - prevent unexpected cancel text editing if the toolbox or properties panel is visible (#12822) * Revert "TextEditor - Prevent custom buttons overlapping in ie11 (T879885) (#12795)" (#12824) This reverts commit 489796991c1187c88149b350a838804a6be248f4. * Fix drawing image annotations when customizeAnnotation used (T881143) (#12827) * Fix drawing image annotations when customizeAnnotation used (T881143) * dxGantt: Fix page hanging problem (T856646) (#12829) * Fix prefer-const warning for DataGrid (#12825) * Diagram - update core package (style objects can accept css text) (#12832) * FileManager for Knockout - ViewModel binding doesn't work (T861302) (#12833) * Scss ThemeBuilder: metadata generator, base compiler (#12775) * DateBox: validation callback should be called only once when value changes (T879881) (#12770) * Autocomplete: dataSource should load new items only when searchTimeout is up (T880996) (#12820) (#12837) * Viz: Fix prefer-const ESLint warnings (#12841) * Fix prefer-const ESLint warnings (#12843) * ESLint: Enable errors for the "prefer-const" rule (#12842) * Tune date parts selection behavior of the DateBox with useMaskBehavior (T882025) (#12839) * Set release versions for dxGantt and dxDiagram (#12866) Co-authored-by: kruglikov.stepan * DataGrid: Fix different behavior between tree and list header filters (T881628) (#12831) * dxViz: Fix behavior of holding (T880908) (#12855) * Diagram - the onOptionChanged event isn't raised for the "hasChanges" option if the control is bound (T883021) (#12864) * Diagram - the onOptionChanged event isn't raised for the "hasChanges" option if the control is bound (T883021) * Fix test * DataGrid - Fixes freeSpace row resizing on virtual row rendering (T881439) (#12848) * File Manager - Update error texts (#12873) * Make a change * update tests * Fix the min-width CSS style for the ResponsiveBox item in the row direction (T878630) (#12846) * fix minWidth of box item by row direction * fix mobile tests * fix IE tests * remove tests * File Manager - Add server rendering tests (#12872) * Add first test, works well * Move test to another file, improve it, update existing tests * Move scrollView tests, add some new tests * kinda not-working test * Temporarily remove excess test * Trying fix default render test * Fix IE test * Refactor * DataGrid - The group panel does not display correctly when dragging columns into it (T880880) (#12858) * DataGrid - The group panel does not display correctly when dragging columns into it (T880880) * Add rough comparing for IE test * File Manager - Fix select all in thumbnails view (#12883) * Add test * First approach * Revert "First approach" This reverts commit 233702c679bd3f920bc7b2d18f5c87655402db38. * Improve test * Fix * Split up test into 2 parts * DataGrid - Fix filtering with an incomplete "between" filter value (T882759) (#12878) * DataGrid - Fix filtering with an incomplete "between" filter value * Fix test * DataGrid: Fix pageIndex updating during virtual scroll to last page if last page size less than viewport size (T866890) (#12851) * TagBox - mark selected items in filtered results (T880346) (#12887) * TextEditor - Prevent custom buttons overlapping in ie11 (T879885) (#12890) * DataGrid: Fix focus overlay was visible during column resizing (T882682) (#12849) * DateBox: fix unstable tests (#12885) (#12893) * FileUploader - Accessibility - The file input element does not have the aria-* attributes and does not allow to set one (T876739) (#12894) * Diagram - fix initial options and core synchronization (#12891) * Diagram - fix initial options and core synchronization * Add autoZoomMode test + tests refactoring * Diagram - update core package * dxChart - add option to make tooltip interactive (#12896) * Sortable: Fix drag and drop in RTL mode when itemOrientation is horizontal (T877953) (#12813) * Sortable: Fix drag and drop in RTL mode when itemOrientation is horizontal (T877953) * Fix lint * Fix unstable charts' tests (#12902) * Fix dxList memory leak in material theme (T882408) (#12907) * Skip test for T882682 (#12905) * FileManager - Fix FileSystemProvider doc-comments (#12910) * DataGrid - Fixes duplicated characters in Firefox (T882996) (#12875) * ExcelJS - specify direct alternative for exportToExcel(selectionOnly) as exportDataGrid (#12898) * Scheduler - Recurrence appointments should have correct time if BYMONTHDAY rule (Refix T868401) (#12901) Co-authored-by: Anton Sermyazhko * Fix grouped agenda rendering in Scheduler (T683374) (#12863) * Fix grouped agenda rendering in Scheduler, material theme and rtl (T683374)(#12920) * Scheduler - Month view should have default height in the generic themes (T882020) (#12857) * Scheduler - Month view should have default height in the generic themes (T882020) * Take two * Correct tests for FF, ie11 * Declare month view rows count as constant. * Refix - Set "min-height" for scrollable container instead of height. Extend tests. * Optimization of using the test wrapper * Fix timeline group header cell height + extend tests * Correct timeline data cell height Co-authored-by: Anton Sermyazhko * DataGrid: Fixed test for T882682 (#12912) * ScrollView - The scrolling operation is interrupted on touch devices (T886654) (#12917) * DropDownBox should consider `popupPosition` (T884469, stage 1) (#12931) * DropDownButton should consider `popupPosition`(T884469, stage 1) * Fix test for IE11 * TabPanel - fix borders ( T879157) (#12747) * Gantt: update core IModelChangesListener signature (T848234) (#12943) * TabPanel - multiview style should be applied only to the widget (#12944) * DataGrid - Fixes focusing command cells on Tab (T884646) (#12922) * Change export of ODataContext (T886254) (#12924) * dxChart: Add the 'position' option to axis' label (#12932) * Throw a warning message for toolbar.height and drawer.target options only once if widget was initialized with using these options (#12945) * Custom File Provider - Declaration for the uploadFileChunk method misses the destinationDirectory parameter (T887298) (#12941) * Custom File Provider - Declaration for the uploadFileChunk method misses the destinationDirectory parameter (T887298) * fix ts build * FileUploader - The format of arguments is incorrect in the uploadFile method (T887206) (#12938) * DateBox: valueChanged event should be fired after input clearing undo (T878918) (#12915) * Fix Scheduler recurrenceEditor readOnly state for disabled appointments (T880614) (#12918) * DataGrid: Fix column widths after virtual scrolling in Safari if rowTemplate is defined (T878862) (#12928) * DataGrid: Fix column updating during resize (T881314) (#12899) * Remove unexpected DateBox warnings after rendering (T887580) (#12950) * Remove unexpected DateBox warnings after rendering (T887580) * Update default options and tests * TextEditor - collapse input container in ie11 only if the container has enough width (#12911) * DropDownBox - call displayValueFormatter only once on init (T883129) (#12952) * Throw a warning message for toolbar.height and drawer.target options only once if widget was initialized with using these options (#12954) * Fix Scheduler agenda in material theme (#12942) * Bugfix colors of the marks in the form (T882067) (#12927) * Github CI: Replace sleep to runner response waiting (#12961) * Button - Fixes submitting a form when AsyncRule is passed (T887207) (#12934) * Scheduler - Rename 'allowEditingTimeZones' -> 'allowTimeZoneEditing' (#12935) * Scheduler - Rename 'allowEditingTimeZones' -> 'allowTimeZoneEditing' * Fix using 'allowEditingTimeZones' option * Update TS declarations Co-authored-by: Anton Sermyazhko * Github Actions CI: Write runner results to stdout (#12962) * TextEditor - revert input container collapsing in ie11 (T879885) (#12966) * dxVectorMap: fix custom store load with raw option (T885056) (#12959) * Gantt: fix custom fields int the autoUpdateParentsMode (T887279) (#12963) * Gantt: fix custom fields int the autoUpdateParentsMode (T887279) * Gantt: fix custom fields int the autoUpdateParentsMode step 2 (T887279) * Gantt: fix custom fields int the autoUpdateParentsMode step 3 (T887279) * Rollback 'export default' for ODataContext and ODataStore (#12969) * fix validation summary when validation rules are changed dynamically (#12964) (#12975) * Tabs - fix icon alignment, if text is not defined (T885520) (#12947) * Bugfix icon alignment (T885520) * Fix tests * dxVectorMap - fix dataSource doc (T887537) (#12977) * DataGrid: Fix columns without dataField were not hidden (T885383) (#12948) * dxViz: Fix axis' label alignment for default (by theme) cases (#12980) * Diagram - It's possible to modify the background color via code in read only mode (T887099) (#12976) * GitHub Actions CI: Get QUnit runner port from ports.json config (#12996) * VectorMap. Implement drawing polygon&multipolygon elements from one source (#12983) * dxLookup - Fix behavior if turn on the fullScreen option (T885130) * DataGrid - Prevents onSelectionChanged from firing when deferred selection and state storing are enabled (T885777) (#12984) * Update localization (#12973) * Update jQuery to version 3.5.1 (#12998) * Log jQuery version * Update jQuery to version 3.5.1 * Revert "Log jQuery version" This reverts commit 9e965bea136083c18e919747f36e7ea3fe9fc34e. * Fix lost aspnet.createComponent calls, part II (fixes T886572) (#12979) * Add swatches support for scss Themebuilder (#12965) * DataGrid - Fixes applying pager options when repaintChangesOnly is enabled (T886628) (#13000) * FileManager - A context menu is re-created after changing the disabled property of a specific item (T887308) (#12989) * ESLint: Update to v7.0 (#12995) * Shceduler - Deprecate 'allowEditingTimeZones' (#13002) * Fix Scheduler localization in appointment popup, first step (T887054) (#13008) * Fix Scheduler shader position for small screen (T886366) (#13013) * PivotGrid: Fix uneccessary render on expand header item if store is not defined (T887002) (#12981) * DataGrid: Fix invalid editor refocusing without timeout on document click in ios (T837043) (#12956) * update versions 20_2 (#13019) * ESLint: Add Node plugin with recommended preset for /build folder (#12994) (#13020) * ESLint: Add node plugin with recommended preset * ESLint: Disable "node/no-unpublished-require" rule * ESLint: Specify Node version for "no-unsupported-features/node-builtins" rule * ESLint: Configure "shebang" rule * Use strict * new Buffer(string) -> Buffer.from(string) * Ignore remaining lines * Disable doubtful rules * Fix - Scheduler should not ignore BYSETPOS=-1 in recurrent rule if FREQ is lower than date range in the current view (T886991) (#13025) Co-authored-by: Anton Sermyazhko * Refactor DOM utils (#13021) * Fix drawing of empty text as categories(T888028) (#13035) * Fix drawing of empty text as categories(T888028) * Small fix in Scheduler shader position (#13017) (#13026) * Diagram - move to development version(20.2) of the diagram core package (#13030) * Fix unexpected data request by RadioGroup without initial value (T887090) (#13024) (#13037) * Fix unexpected data request by RadioGroup without initial value (T887090 ) * Complex item with null value should be selectable * Drone CI - Add QUARANTINE_MODE for TestCafe tests (#13047) Co-authored-by: Anton Sermyazhko * Make events engine to not fall with SVG (#13045) (#13052) * DataGrid - The "Export all data" toolbar item is rendered incorrectly (T886693 ) (#12991) (#13051) * Use screenshots tests instead of qunit (#13023) (#13063) * Diagram - publish getNodeDataSource and getEdgeDataSource methods (#13065) * Fix German and Japanese localization (#13068) * DataGrid - Prevents the content from scrolling on refresh (T884308) (#13056) * DataGrid: Fix ts definitions for 'anyof'/'noneof' filter operations (T885463) (#13069) * dxScheduler - remove customizeStoreLoadOptionsHandler parameter (#12266) (#13074) * Remove customizeStoreLoadOptionsHandler method * Remove test * Separate test (#12779) (#13075) * dxScheduler - Recurrence appointment date should be displayed equal to the targetedAppointmentData date in appointment popup from form(T882652) (#13072) * dxScheduler - Recurrence appointment date should be displayed equal to the targetedAppointmentData date in appointment popup from form(T882652) (#12870) * Fix * create _getCurrentAppointmentData method * Add test * fix stylelint * dxTabPanel - extra borders on the tabs element in Rich editor (#13029) (#13031) * Move dx-tools to tag stable v20.2 (#13076) * DataGrid - Prevents focusing hidden cells (T887014) (#13050) * Fix dxDataGridMethods.exportToExcel deprecation (#13084) * Update README (#13018) * Update README * Update README * Update README.md Co-authored-by: arminal Co-authored-by: arminal * Fix task crash on npm ls error (#13085) * DataGrid - Fixes the searchPanel.text value when state storing is enabled (T887758) (#13079) * Tabpanel - change priority in styles for width of Tab (#13090) * Override base event parameters for treeview (treeview doesn't provide info about added items and removed items) (#13044) * Extract `data-options` logic from dom utils (#13082) * TreeView - change visibility item logic (T888410) (#13032) (#13093) * Change visibility logic. Node must have invisible class if item's visibility is set to false (T888410) * More test cases * More test cases * Simplified tests * PR feedback * Fix test * Test naming * dropDownButton - change horizontal paddings if the widget has no dropdown arrow (T888866) (#13081) * Lookup: remove deprecates from tests (#13073) (#13102) * Toolbar - button group support (#13119) * Toolbar - support for button group (#12838) * Toolbar now supports button group widget * Fix rtl issue * Fix icon size issue for material theme * fix toolbar icons issues * Fix less gulp task processing https://devextreme-ci.devexpress.com/DevExpress/DevExtreme/31311/126 * Try to fix gulp task * Try to determine why sass test is failed * Fix regular expression for valid translation to sass * Fix translations to sass * dirty realization of focus navigation * Fix keyboard navigation logic * Remove custom keyboard navigation logic * Fix layout in case of multiple button groups * Simple test for toolbar * Fix test in case if open menu method is broken * Toolbar - fix rtl for button group inside menu (#13098) * Fix rtl for button group in toolbar menu * Correct fix of rtl mode for button group * dxChart: Fix unnecessary margin if the axis has a title and labels are positioned inside the chart (T891064) (#13117) * Drawer - panel z-index clear immediatly when closeOnOutsideClick was fired (#13108) * FIx appointments overlapping in Scheduler when browser is zoomed(T885595) (#13087) * Add integrity-validator run (#13092) * Remove old deprecated APIs (#13097) * Fix "ports" for 20.2 (#13121) * Fix "ports" for 20.2 * Update ports.json * Update ports.json Co-authored-by: Anton Sermyazhko * dxChart - fix axis custom position (T889092) (#13122) * dxChart - fix tests for edge after fix T889092 (#13130) * Lookup: add some event tests (#13078) (#13111) * Scss generator: format files (#13128) (#13145) * Make domUtils.contains not to fall with SVG in IE (#13135) * dxViz: Fix tooltip's background showing in Firefox when using a material theme (T891490) (#13148) * Scheduler - Add loadPanel for popupEditForm (T885300) (#13115) * Scheduler - Add loadPanel for popupEditForm (T885300) * Fix test for IE * Add "locker" for the "appointmentPopup.saveChanges" method Co-authored-by: Anton Sermyazhko * DataGrid: Fix column resizing when columnResizingMode is widget and rtlEnabled is true (T837344) (#13060) * DataGrid: Fix column resizing when columnResizingMode is widget and rtlEnabled is true (T837344) * fix test * Eliminated the faults noted in the remarks * Eliminated the faults noted in the remarks (part 2) * dxViz: Fix tooltip's shadow filter (#13162) * cherry pick (#13166) * Add linter to the scss themebuilder (#13036) (#13169) * DataGrid: Fix loading data on scroll after deleting several rows if scrolilng mode is infinite, rowRenderingMode is virtual and refreshMode is repaint (T862268) (#13104) * DataGrid (Sortable) - Add promise argument for onReorder event (T887897) (#13140) * Simplify style compiler code (scss) (#13168) * DataGrid - Hides the 'between' operation for a lookup column (T889066) (#13156) * DataGrid: Fix column separator height was wrong during reordering after resizing (T889787) (#13134) * Diagram for Core/MVC - It's not possible to add both custom and default commands to the context menu (T891295) (#13167) * Lookup: subscription by 'on' method should work correct for onPageLoading and onPullRefresh events (#13099) (#13114) * List: click on nextButton should raise pageLoading event (T892010) (#13174) * File manager - Show navigation errors in notification panel (#13182) * File Manager - Show navigation errors in notification panel * Refactored a bit * Lint generated localization files with client config (#13180) * dxScheduler - update scheduler test wrapper (#13177) * cherry pick * Update scheduler wrapper * Fix tests * fix test Co-authored-by: Alexey Babich Co-authored-by: Anton Kuznetsov Co-authored-by: Alexander Bezborodov Co-authored-by: Stanislav Klesarev Co-authored-by: Roman Rodin Co-authored-by: Yana Yarovaya Co-authored-by: Konstantin Volnyagin Co-authored-by: Vladimir Kovalev <47112293+vladkovl@users.noreply.github.com> Co-authored-by: Nick Mitrokhin Co-authored-by: Stepan Co-authored-by: Alexander Ziborov <1420883+San4es@users.noreply.github.com> Co-authored-by: greenkeeper[bot] <23040076+greenkeeper[bot]@users.noreply.github.com> Co-authored-by: EugeniyKiyashko Co-authored-by: Andrew Makarov Co-authored-by: MikeVitik Co-authored-by: Dmitry Semenov Co-authored-by: Igor Maltsev Co-authored-by: Ignatov Dan Co-authored-by: kotov.alexander Co-authored-by: Jaan Toming Co-authored-by: zhavoronkov.evgeny Co-authored-by: Roman Resh Co-authored-by: EugeniyKiyashko Co-authored-by: Dmitry Levkovskiy Co-authored-by: Smirnova Yuliya Co-authored-by: Sergey Novikov <57402891+novsstation@users.noreply.github.com> Co-authored-by: dxrobot Co-authored-by: groshenkovamarina Co-authored-by: Alyar Co-authored-by: RomanTsukanov Co-authored-by: Sergey Zvyagin Co-authored-by: polosatov.alexander Co-authored-by: kruglikov.stepan Co-authored-by: Anton Sermyazhko Co-authored-by: Anton Sermyazhko Co-authored-by: Alexander Zelevinskiy <16476188+zelik88@users.noreply.github.com> Co-authored-by: Andrey Ignatovskiy <43685423+LazyLahtak@users.noreply.github.com> Co-authored-by: Andrey Bykiev Co-authored-by: AlekseyMartynov Co-authored-by: AlisherAmonulloev Co-authored-by: Oleg Kipchatov <35765711+OlegKipchatov@users.noreply.github.com> Co-authored-by: ilya.kharchenko <14272298+IlyaKhD@users.noreply.github.com> Co-authored-by: arminal Co-authored-by: Andrey Churkin * Renovation: use global eslint config for qunit tests (#13206) Co-authored-by: Andrey Churkin * Use babel-eslint for js files (#13209) * fix remarks * fix qunit tests * remove extra css rules * Update README.md * Update README.md * Update README.md * Update package.json * update playground readme : Co-authored-by: roman-simionov Co-authored-by: Pavel Gruba Co-authored-by: maximkudriavtsev Co-authored-by: Roman Simionov Co-authored-by: Alexander Ziborov <1420883+San4es@users.noreply.github.com> Co-authored-by: Andrey Ignatovskiy Co-authored-by: Andrey Ignatovskiy <43685423+LazyLahtak@users.noreply.github.com> Co-authored-by: AlekseyMartynov Co-authored-by: Konstantin Volnyagin Co-authored-by: Sergey Novikov <57402891+novsstation@users.noreply.github.com> Co-authored-by: kotov.alexander Co-authored-by: Jaan Toming Co-authored-by: Dmitry Levkovskiy Co-authored-by: Alexey Kamyshin Co-authored-by: Igor Maltsev Co-authored-by: Alyar Co-authored-by: Alexander Zelevinskiy <16476188+zelik88@users.noreply.github.com> Co-authored-by: Anton Sermyazhko Co-authored-by: Yana Yarovaya Co-authored-by: Smirnova Yuliya Co-authored-by: EugeniyKiyashko Co-authored-by: Stanislav Klesarev Co-authored-by: polosatov.alexander Co-authored-by: Alexey Babich Co-authored-by: Anton Kuznetsov Co-authored-by: Alexander Bezborodov Co-authored-by: Stepan Co-authored-by: Roman Rodin Co-authored-by: Vladimir Kovalev <47112293+vladkovl@users.noreply.github.com> Co-authored-by: ilya.kharchenko <14272298+IlyaKhD@users.noreply.github.com> Co-authored-by: AlisherAmonulloev Co-authored-by: groshenkovamarina Co-authored-by: Dmitry Semenov Co-authored-by: Andrey Churkin Co-authored-by: MikeVitik Co-authored-by: Andrey Churkin Co-authored-by: Maxim Kudryavtsev Co-authored-by: Roman Rodin Co-authored-by: Nick Mitrokhin Co-authored-by: greenkeeper[bot] <23040076+greenkeeper[bot]@users.noreply.github.com> Co-authored-by: EugeniyKiyashko Co-authored-by: Andrew Makarov Co-authored-by: Ignatov Dan Co-authored-by: zhavoronkov.evgeny Co-authored-by: Roman Resh Co-authored-by: dxrobot Co-authored-by: RomanTsukanov Co-authored-by: Sergey Zvyagin Co-authored-by: kruglikov.stepan Co-authored-by: Anton Sermyazhko Co-authored-by: Andrey Bykiev Co-authored-by: Oleg Kipchatov <35765711+OlegKipchatov@users.noreply.github.com> Co-authored-by: arminal --- .babelrc | 11 +- .drone.yml | 3 + .eslintignore | 6 + .eslintrc | 452 +----------- .github/workflows/CI.yml | 2 +- .gitignore | 4 + .lintstagedrc | 3 +- .travis.yml | 3 + .vscode/launch.json | 22 + build/gulp/generator/gulpfile.js | 153 ++++ .../ts-configs/angular.tsconfig.json | 6 + .../generator/ts-configs/preact.tsconfig.json | 6 + .../generator/ts-configs/react.tsconfig.json | 6 + build/gulp/generator/ts-configs/tsconfig.json | 15 + build/gulp/transpile.js | 7 +- build/gulp/ts.js | 1 + build/gulp/tsconfig.json | 6 + build/gulp/vendor.js | 11 + docker-ci.sh | 8 + gulpfile.js | 3 +- jest.config.js | 39 + js/.eslintrc | 4 + js/bundles/modules/parts/widgets-base.js | 5 + js/core/dom_component.js | 1 + js/core/options/utils.js | 4 + js/core/utils/icon.js | 7 +- js/events/pointer.js | 2 +- js/events/short.js | 15 +- js/renovation/.eslintrc | 5 + js/renovation/button.tsx | 266 +++++++ js/renovation/error-message.tsx | 25 + js/renovation/icon.tsx | 34 + js/renovation/ink-ripple.tsx | 38 + js/renovation/number-box.tsx | 56 ++ js/renovation/preact-wrapper/button.d.ts | 13 + js/renovation/preact-wrapper/button.js | 59 ++ js/renovation/preact-wrapper/component.d.ts | 38 + js/renovation/preact-wrapper/component.js | 190 +++++ js/renovation/preact-wrapper/utils.js | 40 + js/renovation/select-box.tsx | 41 ++ js/renovation/utils/base-props.tsx | 39 + js/renovation/utils/noop.js | 1 + js/renovation/widget.tsx | 366 ++++++++++ js/ui/gantt.d.ts | 4 +- js/ui/grid_core/ui.grid_core.editing.js | 2 +- .../ui.hierarchical_collection_widget.js | 4 +- js/ui/scheduler/ui.scheduler.js | 1 - js/ui/widget/ui.widget.js | 2 +- js/ui/widget/utils.ink_ripple.js | 35 +- package.json | 42 +- playground/angular/README.md | 27 + playground/angular/app/app.component.html | 7 + playground/angular/app/app.component.ts | 27 + playground/angular/config.js | 58 ++ playground/angular/index.html | 32 + playground/angular/tsconfig.json | 29 + playground/angular/tslint.json | 83 +++ playground/react/.gitignore | 1 + playground/react/README.md | 31 + playground/react/config/env.js | 101 +++ playground/react/config/getHttpsConfig.js | 66 ++ playground/react/config/jest/cssTransform.js | 14 + playground/react/config/jest/fileTransform.js | 40 + playground/react/config/modules.js | 141 ++++ playground/react/config/paths.js | 72 ++ playground/react/config/pnpTs.js | 35 + playground/react/config/webpack.config.js | 669 +++++++++++++++++ .../react/config/webpackDevServer.config.js | 130 ++++ playground/react/package.json | 81 +++ playground/react/public/index.html | 15 + playground/react/scripts/start.js | 166 +++++ playground/react/src/App.tsx | 14 + playground/react/src/index.tsx | 13 + playground/react/src/react-app-env.d.ts | 66 ++ playground/react/src/serviceWorker.js | 141 ++++ playground/react/tsconfig.json | 34 + playground/vue/.gitignore | 22 + playground/vue/README.md | 27 + playground/vue/babel.config.js | 10 + playground/vue/package.json | 32 + playground/vue/public/favicon.ico | Bin 0 -> 4286 bytes playground/vue/public/index.html | 17 + playground/vue/src/App.vue | 35 + playground/vue/src/assets/logo.png | Bin 0 -> 6849 bytes playground/vue/src/main.js | 10 + playground/vue/tsconfig.json | 5 + shippable.yml | 3 + styles/widgets/common/button.less | 8 +- testing/.eslintrc | 28 - testing/.gitignore | 1 + testing/functional/model/dataGrid.ts | 18 +- .../tests/dataGrid/keyboardNavigation.ts | 79 +- testing/helpers/.eslintrc | 5 + testing/helpers/qunitExtensions.js | 3 + testing/jest/.eslintrc | 8 + testing/jest/button.tests.tsx | 687 ++++++++++++++++++ testing/jest/error-message.tests.tsx | 29 + testing/jest/icon.tests.tsx | 70 ++ testing/jest/setup-enzyme.ts | 4 + testing/jest/tsconfig.json | 8 + testing/jest/utils/events-mock.ts | 108 +++ testing/jest/widget.tests.tsx | 554 ++++++++++++++ testing/launch | 47 +- testing/runner/Tools/UIModelHelper.cs | 2 +- testing/runner/Views/Main/RunSuite.cshtml | 3 + testing/tests/.eslintrc | 5 + .../DevExpress.serverSide/renovation.tests.js | 1 + .../fieldChooser.tests.js | 1 - .../DevExpress.utils/utils.inkRipple.tests.js | 39 +- .../elementsOnRefresh_bundled.tests.js | 1 + .../eventsOnRefresh_bundled.tests.js | 4 +- testing/tests/Renovation/__meta.json | 5 + .../tests/Renovation/button.markup.tests.js | 263 +++++++ testing/tests/Renovation/button.tests.js | 652 +++++++++++++++++ testing/tests/Renovation/widget.tests.js | 127 ++++ .../data/scss/widgets/generic/_colors.scss | 1 - .../tests/metadata/generator.test.ts | 8 +- themebuilder/.eslintrc | 5 + tsconfig.json | 47 ++ webpack.config.dev.js | 17 +- 120 files changed, 6573 insertions(+), 625 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 build/gulp/generator/gulpfile.js create mode 100644 build/gulp/generator/ts-configs/angular.tsconfig.json create mode 100644 build/gulp/generator/ts-configs/preact.tsconfig.json create mode 100644 build/gulp/generator/ts-configs/react.tsconfig.json create mode 100644 build/gulp/generator/ts-configs/tsconfig.json create mode 100644 build/gulp/tsconfig.json create mode 100644 jest.config.js create mode 100644 js/renovation/.eslintrc create mode 100644 js/renovation/button.tsx create mode 100644 js/renovation/error-message.tsx create mode 100644 js/renovation/icon.tsx create mode 100644 js/renovation/ink-ripple.tsx create mode 100644 js/renovation/number-box.tsx create mode 100644 js/renovation/preact-wrapper/button.d.ts create mode 100644 js/renovation/preact-wrapper/button.js create mode 100644 js/renovation/preact-wrapper/component.d.ts create mode 100644 js/renovation/preact-wrapper/component.js create mode 100644 js/renovation/preact-wrapper/utils.js create mode 100644 js/renovation/select-box.tsx create mode 100644 js/renovation/utils/base-props.tsx create mode 100644 js/renovation/utils/noop.js create mode 100644 js/renovation/widget.tsx create mode 100644 playground/angular/README.md create mode 100644 playground/angular/app/app.component.html create mode 100644 playground/angular/app/app.component.ts create mode 100644 playground/angular/config.js create mode 100644 playground/angular/index.html create mode 100644 playground/angular/tsconfig.json create mode 100644 playground/angular/tslint.json create mode 100644 playground/react/.gitignore create mode 100644 playground/react/README.md create mode 100644 playground/react/config/env.js create mode 100644 playground/react/config/getHttpsConfig.js create mode 100644 playground/react/config/jest/cssTransform.js create mode 100644 playground/react/config/jest/fileTransform.js create mode 100644 playground/react/config/modules.js create mode 100644 playground/react/config/paths.js create mode 100644 playground/react/config/pnpTs.js create mode 100644 playground/react/config/webpack.config.js create mode 100644 playground/react/config/webpackDevServer.config.js create mode 100644 playground/react/package.json create mode 100644 playground/react/public/index.html create mode 100644 playground/react/scripts/start.js create mode 100644 playground/react/src/App.tsx create mode 100644 playground/react/src/index.tsx create mode 100644 playground/react/src/react-app-env.d.ts create mode 100644 playground/react/src/serviceWorker.js create mode 100644 playground/react/tsconfig.json create mode 100644 playground/vue/.gitignore create mode 100644 playground/vue/README.md create mode 100644 playground/vue/babel.config.js create mode 100644 playground/vue/package.json create mode 100644 playground/vue/public/favicon.ico create mode 100644 playground/vue/public/index.html create mode 100644 playground/vue/src/App.vue create mode 100644 playground/vue/src/assets/logo.png create mode 100644 playground/vue/src/main.js create mode 100644 playground/vue/tsconfig.json delete mode 100644 testing/.eslintrc create mode 100644 testing/helpers/.eslintrc create mode 100644 testing/jest/.eslintrc create mode 100644 testing/jest/button.tests.tsx create mode 100644 testing/jest/error-message.tests.tsx create mode 100644 testing/jest/icon.tests.tsx create mode 100644 testing/jest/setup-enzyme.ts create mode 100644 testing/jest/tsconfig.json create mode 100644 testing/jest/utils/events-mock.ts create mode 100644 testing/jest/widget.tests.tsx create mode 100644 testing/tests/.eslintrc create mode 100644 testing/tests/DevExpress.serverSide/renovation.tests.js create mode 100644 testing/tests/Renovation/__meta.json create mode 100644 testing/tests/Renovation/button.markup.tests.js create mode 100644 testing/tests/Renovation/button.tests.js create mode 100644 testing/tests/Renovation/widget.tests.js create mode 100644 themebuilder/.eslintrc create mode 100644 tsconfig.json diff --git a/.babelrc b/.babelrc index 7230e2b9857e..a415eedc217c 100644 --- a/.babelrc +++ b/.babelrc @@ -1,7 +1,12 @@ { "presets": ["@babel/preset-env"], - "plugins": ["transform-es2015-modules-commonjs", "add-module-exports", - "@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-optional-chaining" + "plugins": [ + "transform-es2015-modules-commonjs", + "add-module-exports", + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-optional-chaining", + ["transform-react-jsx", { "pragma": "Preact.h" }], + "transform-object-assign" ], "ignore": ["**/*.json", "**/sinon.js"] -} \ No newline at end of file +} diff --git a/.drone.yml b/.drone.yml index 2266e4e1efb3..20d8eee69ddd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -43,6 +43,7 @@ matrix: - { TARGET: test, CONSTEL: ui.scheduler, TZ: 'Japan' } - { TARGET: test, CONSTEL: ui.scheduler, TZ: 'Australia/ACT' } - { TARGET: test, CONSTEL: viz } + - { TARGET: test, CONSTEL: renovation } - { TARGET: test, PERF: true, JQUERY: true, NO_HEADLESS: true } - { TARGET: test, MOBILE_UA: ios9, CONSTEL: ui } - { TARGET: test, MOBILE_UA: ios9, CONSTEL: ui.editors, NO_HEADLESS: true } @@ -61,9 +62,11 @@ matrix: - { TARGET: test, BROWSER: firefox, JQUERY: true, CONSTEL: ui.grid } - { TARGET: test, BROWSER: firefox, JQUERY: true, CONSTEL: ui.scheduler } - { TARGET: test, BROWSER: firefox, JQUERY: true, CONSTEL: viz } + - { TARGET: test, BROWSER: firefox, JQUERY: true, CONSTEL: renovation } - { TARGET: test_functional, COMPONENT: dataGrid, QUARANTINE_MODE: true } - { TARGET: test_functional, COMPONENT: scheduler, QUARANTINE_MODE: true } - { TARGET: test_functional, COMPONENT: editors } - { TARGET: test_functional, COMPONENT: navigation } - { TARGET: test_themebuilder } + - { TARGET: test_jest } - { TARGET: test_scss } diff --git a/.eslintignore b/.eslintignore index 22face29318a..f0b7ca327162 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,5 +2,11 @@ artifacts/* js/viz/docs/* node_modules/* testing/helpers/sinon/* +*.p.js +*.j.js +*.p.d.ts +*.j.d.ts +playground/* themebuilder/data/metadata/* themebuilder-scss/**/* +js/bundles/dx.custom.js diff --git a/.eslintrc b/.eslintrc index 250e1850a88c..d7a17480456f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,17 +2,17 @@ "env": { "es6": true }, - "parser": "babel-eslint", + "parser": "@typescript-eslint/parser", "parserOptions": { + "createDefaultProgram": true, + "project": "./tsconfig.json", "ecmaVersion": 6, "sourceType": "module", "ecmaFeatures": { - "globalReturn": true + "globalReturn": true, + "jsx": true } }, - "plugins": [ - "spellcheck" - ], "globals": { "setInterval": true, "setTimeout": true, @@ -22,7 +22,7 @@ "module": true, "exports": true }, - "extends": "eslint:recommended", + "extends": ["eslint:recommended", "devextreme/spell-check"], "rules": { "block-spacing": "error", "comma-spacing": "error", @@ -51,7 +51,7 @@ "no-new-func": "error", "no-eval": "error", "no-undef-init": "error", - "no-unused-vars": ["error", { "args": "none" }], + "no-unused-vars": ["error", { "args": "none", "ignoreRestSiblings": true }], "no-extend-native": "error", "no-alert": "error", "no-console": "error", @@ -89,436 +89,10 @@ } } ], - "quotes": ["error", "single"], - "spellcheck/spell-checker": [ - "error", - { - "lang": "en_US", - "comments": false, - "strings": false, - "identifiers": true, - "templates": false, - "skipIfMatch": [ - "^\\$?..$" - ], - "skipWords": [ - "dx", // DevExpress - "el", // Element - "fn", // Function - "fx", // Effects - "jq", // jQuery - "js", // JavaScript - "ko", // Knockout - "ln", // Math - "na", // Special case for NaN - "ng", // Angular - "ok", // OK - "px", // Pixel - "tz", // Timezone - "ua", // User-agent - "ui", // User Interface - "un", // "Un-Escape" - "xs", // extra small - "xy", // XY-diagram - "vm", // view-model - - "amd", // AMD modules - "bing", - "browserslist", // auto-prefixer browsers list - "cldr", // Unicode CLDR Project - "cssom", // cssom parser - "cwd", // current working directory - "edm", // Entity Data Model - "eol", // end of line - "etag", // HTTP header - "eula", // EULA - "globals", // jest settings - "jsrender", // JsRender template engine - "hsl", // HSL color - "hsv", // HSV color - "iana", // IANA (time-zone database) - "ie", - "ie11", - "ios", - "ipad", - "iphone", - "linejoin", // SVG "stroke-linejoin" - "linux", - "ltr", // Left-to-Right - "mdx", // OLAP Multi-dimensional expressions - "mercator", // Map term - "microsoft", - "moz", // Vendor prefix - "mozilla", - "mvc", - "firefox", - "fmt", - "msie", - "odata", // OData - "readonly", - "rebase", // clean-css option - "rtl", // Right-to-Left - "scss", - "semver", // The semantic versioner for npm - "sinon", // JS library - "tspan", // SVG element - "tspans", - "uglify", // UglifyJS - "untils", // Time-zone term - "viapoint", // Geo term - "webkit", - "webpack", - "xmla", // XML for Analysis - "ldml", // LOCALE DATA MARKUP LANGUAGE - - "png", - "jpg", - "svg", - - "API", - "accessor", - "accessors", - "acos", - "activedescendant", - "adaptivity", - "addons", - "affine", - "aggregator", - "aggregators", - "ajax", - "ampm", - "anim", - "appt", - "appts", - "arabic", - "arg", - "argc", - "args", - "argv", - "asc", - "ascii", - "asin", - "aspnet", - "async", - "atan", - "attr", - "attributor", - "attrs", - "autocomplete", - "autocompletion", - "backend", - "backends", - "basename", - "bezier", - "bindable", - "bool", - "buf", - "calc", - "camelize", - "cancelable", - "captionize", - "ceil", - "centroid", - "checkbox", - "checkboxes", - "codomain", - "coef", - "coefs", - "coeff", - "coeffs", - "colgroup", - "colgroups", - "colorizer", - "colorizers", - "colspan", - "colspans", - "concat", - "cond", - "configs", - "configurator", - "configurators", - "const", - "consts", - "conv", - "coord", - "coords", - "cordova", - "cpus", - "crit", - "crosshair", - "ctor", - "ctors", - "ctrl", - "ctx", - "dasherize", - "dataset", - "datetime", - "dblclick", - "deactivator", - "dec", - "decrement", - "deferreds", - "defs", - "del", - "dels", - "denormalize", - "deps", - "desc", - "deserialization", - "deserialize", - "dest", - "dev", - "devtool", - "dir", - "dirname", - "dom", - "donut", - "downloader", - "draggable", - "draggables", - "drilldown", - "droppable", - "durations", - "eigen", - "elems", - "enctype", - "enqueue", - "enum", - "esc", - "etalon", - "exceedings", - "exchanger", - "expander", - "expando", - "expr", - "exprs", - "extname", - "extremum", - "fieldset", - "fieldsets", - "filename", - "focusable", - "focusin", - "focusout", - "foreach", - "formatter", - "formatters", - "fullscreen", - "func", - "funcs", - "gantt", - "gaussian", - "geo", - "geocode", - "geocoded", - "geocoder", - "getter", - "getters", - "gregorian", - "guid", - "gte", - "haspopup", - "hideable", - "historyless", - "hor", - "horz", - "hostname", - "hoverable", - "href", - "html", - "http", - "idx", - "img", - "impl", - "inflector", - "infobox", - "infos", - "init", - "inited", - "intervalize", - "invertible", - "invoker", - "iri", - "iso", - "iter", - "jsonp", - "keydown", - "len", - "lng", - "localizable", - "lookups", - "marginate", - "matcher", - "matchers", - "metadata", - "minify", - "mixin", - "mixins", - "multiline", - "multipane", - "multitouch", - "namespace", - "namespaced", - "namespaces", - "nav", - "navbar", - "noop", - "normalizer", - "num", - "observables", - "overline", - "paddings", - "param", - "params", - "parsers", - "patcher", - "pathname", - "pdf", - "penult", - "polyfill", - "polyline", - "polymorph", - "polynom", - "popout", - "popup", - "pos", - "postfix", - "postfixes", - "postprocess", - "pre", - "preload", - "prepend", - "prerender", - "prev", - "proj", - "proto", - "proxied", - "queryable", - "radian", - "radians", - "radiuses", - "readdir", - "rect", - "rects", - "registrator", - "reinit", - "rels", - "renderer", - "renderers", - "reposition", - "resample", - "resampled", - "resizable", - "resizables", - "resize", - "resized", - "resizer", - "resizing", - "resolvers", - "rgb", - "rgba", - "roadmap", - "rowspan", - "rowspans", - "sankey", - "scalebar", - "scrollable", - "scrollbar", - "scroller", - "scrollers", - "seg", - "selectable", - "semidiscrete", - "serializers", - "shader", - "sortable", - "sparkline", - "sparklines", - "splitter", - "sqrt", - "squarified", - "squarify", - "src", - "str", - "strikethrough", - "stringify", - "struct", - "stylesheets", - "sublevel", - "submenu", - "submenus", - "substr", - "substring", - "substrings", - "subtags", - "subvalue", - "subvalues", - "sugiyama", - "svg", - "swipeable", - "synchronizable", - "synchronizer", - "tabbable", - "tabindex", - "tbody", - "templated", - "thead", - "timeline", - "timestamp", - "timezones", - "titleize", - "tfoot", - "tmp", - "tmpl", - "toolbars", - "tooltip", - "tooltips", - "transclude", - "transcluded", - "treeview", - "turndown", - "uid", - "uint", - "unary", - "undelete", - "ungroup", - "ungrouping", - "unicode", - "unlink", - "unmap", - "unmerge", - "unmerged", - "unmocked", - "unproject", - "unregister", - "unselect", - "unselected", - "unshift", - "untranslate", - "updatable", - "uploader", - "uri", - "utc", - "utils", - "validator", - "validators", - "vals", - "ver", - "vert", - "viewport", - "vml", - "waypoint", - "waypoints", - "whitelist", - "winloss", - "workspace", - "writeable", - "xhr", - "xlsx", - "xml", - "xmlns" - ] - } - ] - } + "quotes": ["error", "single"] + }, + "overrides": [{ + "files": ["*.js"], + "parser": "babel-eslint" + }] } diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d3bf435e6fda..cbad98bd4887 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,7 +6,7 @@ jobs: test: strategy: matrix: - CONSTEL: [ export, misc, ui, ui.editors, ui.grid, ui.scheduler, viz ] + CONSTEL: [ export, misc, ui, ui.editors, ui.grid, ui.scheduler, viz, renovation ] runs-on: windows-latest timeout-minutes: 60 diff --git a/.gitignore b/.gitignore index e4dd4339f8be..4ebc2489bfe8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ /js/bundles/dx.custom.js /js/localization/default_messages.js /js/localization/cldr-data +/js/renovation/**/*.p.d.ts +/js/renovation/**/*.p.js +/js/renovation/**/*.j.d.ts +/js/renovation/**/*.j.js /themebuilder/data/less /themebuilder/data/metadata /scss diff --git a/.lintstagedrc b/.lintstagedrc index 26dfe75fb834..30ffecb9b207 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,4 +1,5 @@ { "*.js": ["eslint"], - "*.{css,less}": ["stylelint"] + "*.{css,less}": ["stylelint"], + "*.{ts,tsx}": ["npm run lint-ts"] } diff --git a/.travis.yml b/.travis.yml index 7f558a6a4111..1bbb0256931f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ env: - TARGET=test CONSTEL=ui.scheduler TZ='Japan' - TARGET=test CONSTEL=ui.scheduler TZ='Australia/ACT' - TARGET=test CONSTEL=viz + - TARGET=test CONSTEL=renovation - TARGET=test PERF=true JQUERY=true NO_HEADLESS=true - TARGET=test MOBILE_UA=ios9 CONSTEL=ui - TARGET=test MOBILE_UA=ios9 CONSTEL=ui.editors NO_HEADLESS=true @@ -42,11 +43,13 @@ env: - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=ui.grid - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=ui.scheduler - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=viz + - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=renovation - TARGET=test_functional COMPONENT=dataGrid QUARANTINE_MODE=true - TARGET=test_functional COMPONENT=scheduler QUARANTINE_MODE=true - TARGET=test_functional COMPONENT=editors - TARGET=test_functional COMPONENT=navigation - TARGET=test_themebuilder + - TARGET=test_jest - TARGET=test_scss cache: diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000000..be8c07099b83 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible Node.js debug attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Jest Tests", + "program": "${workspaceRoot}/node_modules/jest/bin/jest.js", + "args": [ + "--runInBand" + ], + "runtimeArgs": [ + "--harmony" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}" + }, + ] +} diff --git a/build/gulp/generator/gulpfile.js b/build/gulp/generator/gulpfile.js new file mode 100644 index 000000000000..7358e9f9a03c --- /dev/null +++ b/build/gulp/generator/gulpfile.js @@ -0,0 +1,153 @@ +'use strict'; + +const gulp = require('gulp'); +const { generateComponents } = require('devextreme-generator/component-compiler'); +const generator = require('devextreme-generator/preact-generator').default; +const ts = require('gulp-typescript'); +const lint = require('gulp-eslint'); +const plumber = require('gulp-plumber'); +const gulpIf = require('gulp-if'); +const babel = require('gulp-babel'); +const notify = require('gulp-notify'); +const watch = require('gulp-watch'); + +const SRC = ['js/renovation/**/*.tsx']; +const DEST = 'js/renovation/'; + +const COMMON_SRC = ['js/**/*.*', `!${SRC}`]; + +const knownErrors = [ + 'Cannot find module \'preact\'.', + 'Cannot find module \'preact/hooks\'.', + 'Cannot find module \'preact/compat\'.' +]; + +gulp.task('generate-components', function() { + const tsProject = ts.createProject('build/gulp/generator/ts-configs/preact.tsconfig.json'); + generator.defaultOptionsModule = 'js/core/options/utils'; + generator.jqueryComponentRegistratorModule = 'js/core/component_registrator'; + generator.jqueryBaseComponentModule = 'js/renovation/preact-wrapper/component'; + + return gulp.src(SRC) + .pipe(generateComponents(generator)) + .pipe(plumber(()=>null)) + .pipe(tsProject({ + error(e) { + if(!knownErrors.some(i => e.message.endsWith(i))) { + console.log(e.message); + } + }, + finish() {} + })) + .pipe(gulpIf(file => file.extname === '.js', + lint({ + quiet: true, + fix: true, + useEslintrc: true + }) + )) + .pipe(lint.format()) + .pipe(gulp.dest(DEST)); +}); + +function addGenerationTask( + frameworkName, + knownErrors = [], + compileTs = true, + copyArtifacts = false, + babelGeneratedFiles = true +) { + const frameworkDest = `artifacts/${frameworkName}`; + const generator = require(`devextreme-generator/${frameworkName}-generator`).default; + let tsProject = () => () => { }; + if(compileTs) { + tsProject = ts.createProject(`build/gulp/generator/ts-configs/${frameworkName}.tsconfig.json`); + } + + generator.defaultOptionsModule = 'js/core/options/utils'; + + gulp.task(`generate-${frameworkName}-declaration-only`, function() { + return gulp.src('js/**/*.tsx') + .pipe(generateComponents(generator)) + .pipe(plumber(() => null)) + .pipe(gulpIf(compileTs, tsProject({ + error(e) { + if(!knownErrors.some(i => e.message.endsWith(i))) { + console.log(e.message); + } + }, + finish() { } + }))) + .pipe(gulpIf(babelGeneratedFiles, babel())) + .pipe(gulp.dest(frameworkDest)); + }); + + const artifactsSrc = ['./artifacts/css/**/*', `./artifacts/${frameworkName}/**/*`]; + + const generateSeries = [ + `generate-${frameworkName}-declaration-only`, + function() { + return gulp.src(COMMON_SRC) + .pipe( + gulpIf( + file => file.extname === '.js', + babel() + ) + ) + .pipe(gulp.dest(frameworkDest)); + }]; + + if(copyArtifacts) { + generateSeries.push(function copyArtifacts() { + return gulp.src(artifactsSrc, { base: './artifacts/' }) + .pipe(gulp.dest(`./playground/${frameworkName}/src/artifacts`)); + }); + } + + gulp.task(`generate-${frameworkName}`, gulp.series(...generateSeries)); + + const watchTasks = [ + function() { + watch(COMMON_SRC) + .pipe(plumber({ + errorHandler: notify.onError('Error: <%= error.message %>') + .bind() // bind call is necessary to prevent firing 'end' event in notify.onError implementation + })) + .pipe( + gulpIf( + file => file.extname === '.js', + babel() + ) + ) + .pipe(gulp.dest(frameworkDest)); + }, + function declarationBuild() { + gulp.watch(SRC, gulp.series(`generate-${frameworkName}-declaration-only`)); + } + ]; + + if(copyArtifacts) { + watchTasks.push(function copyArtifacts() { + return gulp.src(artifactsSrc, { base: './artifacts/' }) + .pipe(watch(artifactsSrc, { base: './artifacts/', readDelay: 1000 })) + .pipe(gulp.dest(`./playground/${frameworkName}/src/artifacts`)); + }); + } + + gulp.task(`generate-${frameworkName}-watch`, gulp.series( + `generate-${frameworkName}`, + gulp.parallel(...watchTasks) + )); +} + +addGenerationTask('react', ['Cannot find module \'csstype\'.'], false, true, false); +addGenerationTask('angular', [ + 'Cannot find module \'@angular/core\'.', + 'Cannot find module \'@angular/common\'.' +]); + +addGenerationTask('vue', [], false, true, false); + +gulp.task('generate-components-watch', gulp.series('generate-components', function() { + gulp.watch(SRC, gulp.series('generate-components')); +})); diff --git a/build/gulp/generator/ts-configs/angular.tsconfig.json b/build/gulp/generator/ts-configs/angular.tsconfig.json new file mode 100644 index 000000000000..2dedc812db31 --- /dev/null +++ b/build/gulp/generator/ts-configs/angular.tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "experimentalDecorators": true + } +} diff --git a/build/gulp/generator/ts-configs/preact.tsconfig.json b/build/gulp/generator/ts-configs/preact.tsconfig.json new file mode 100644 index 000000000000..b21bba7e312f --- /dev/null +++ b/build/gulp/generator/ts-configs/preact.tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsxFactory": "Preact.h" + } +} diff --git a/build/gulp/generator/ts-configs/react.tsconfig.json b/build/gulp/generator/ts-configs/react.tsconfig.json new file mode 100644 index 000000000000..08be024c5d52 --- /dev/null +++ b/build/gulp/generator/ts-configs/react.tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowSyntheticDefaultImports": true + } +} diff --git a/build/gulp/generator/ts-configs/tsconfig.json b/build/gulp/generator/ts-configs/tsconfig.json new file mode 100644 index 000000000000..f7e064c71382 --- /dev/null +++ b/build/gulp/generator/ts-configs/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "strict": false, + "esModuleInterop": false, + "typeRoots": ["node_modules/@types"], + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "declaration": true + }, + "exclude": [ + "node_modules" + ] +} diff --git a/build/gulp/transpile.js b/build/gulp/transpile.js index e586f613fe70..448403e11a28 100644 --- a/build/gulp/transpile.js +++ b/build/gulp/transpile.js @@ -10,15 +10,16 @@ const notify = require('gulp-notify'); const context = require('./context.js'); +require('./generator/gulpfile'); + const GLOB_TS = require('./ts').GLOB_TS; -const SRC = ['js/**/*.*', '!' + GLOB_TS]; +const SRC = ['js/**/*.*', '!' + GLOB_TS, '!js/**/*.tsx']; const TESTS_PATH = 'testing'; const TESTS_SRC = TESTS_PATH + '/**/*.js'; const VERSION_FILE_PATH = 'core/version.js'; - -gulp.task('transpile', gulp.series('bundler-config', function() { +gulp.task('transpile', gulp.series('generate-components', 'bundler-config', function() { return gulp.src(SRC) .pipe(babel()) .pipe(gulp.dest(context.TRANSPILED_PATH)); diff --git a/build/gulp/ts.js b/build/gulp/ts.js index 23c4737660b3..fbaf6f5b8161 100644 --- a/build/gulp/ts.js +++ b/build/gulp/ts.js @@ -52,6 +52,7 @@ gulp.task('ts-bundle', gulp.series( gulp.task('ts-jquery-check', gulp.series('ts-bundle', function checkJQueryAugmentations() { let content = `/// \n`; + content += 'import * as $ from \'jquery\';'; content += MODULES .map(function(moduleMeta) { diff --git a/build/gulp/tsconfig.json b/build/gulp/tsconfig.json new file mode 100644 index 000000000000..9f756e497164 --- /dev/null +++ b/build/gulp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "typeRoots": ["node_modules/@types"], + "noEmitOnError": true + } +} diff --git a/build/gulp/vendor.js b/build/gulp/vendor.js index ac315bae31d8..6b3ffca7df76 100644 --- a/build/gulp/vendor.js +++ b/build/gulp/vendor.js @@ -13,6 +13,17 @@ const JS_VENDORS = [ { path: '/angular/angular.js' }, + { + path: '/preact/dist/preact.js' + }, + { + path: '/preact/hooks/dist/hooks.js', + noUglyFile: true + }, + { + path: '/preact/compat/dist/compat.js', + noUglyFile: true + }, { path: '/jquery/dist/jquery.js' }, diff --git a/docker-ci.sh b/docker-ci.sh index b4201b361298..f7d08aebc7dc 100755 --- a/docker-ci.sh +++ b/docker-ci.sh @@ -189,6 +189,14 @@ function run_test_functional { npm run test-functional -- $args } +function run_test_jest { + export DEVEXTREME_TEST_CI=true + + npm i + npx gulp generate-components + npm run test-jest +} + function run_test_scss { npm i npx gulp generate-scss diff --git a/gulpfile.js b/gulpfile.js index 5ed1e5ff6cc5..0bb5d29bea1a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,6 +21,7 @@ require('./build/gulp/vendor'); require('./build/gulp/ts'); require('./build/gulp/localization'); require('./build/gulp/style-compiler'); +require('./build/gulp/generator/gulpfile'); require('./build/gulp/scss/tasks'); const TEST_CI = Boolean(process.env['DEVEXTREME_TEST_CI']); @@ -69,4 +70,4 @@ gulp.task('style-compiler-batch', createStyleCompilerBatch()); gulp.task('default', createDefaultBatch()); -gulp.task('dev', gulp.parallel('bundler-config-dev', 'js-bundles-dev', 'style-compiler-themes-dev')); +gulp.task('dev', gulp.parallel('bundler-config-dev', 'generate-components-watch', 'js-bundles-dev', 'style-compiler-themes-dev')); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000000..6fecc04644c9 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,39 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html +const path = require('path'); +const resolve = require('resolve'); + +module.exports = { + 'globals': { + 'ts-jest': { + tsConfig: './testing/jest/tsconfig.json', + diagnostics: false, // set to true to enable type checking + } + }, + collectCoverage: true, + collectCoverageFrom: [ + './js/renovation/**/*.p.js', + '!./js/renovation/number-box.p.js', + '!./js/renovation/select-box.p.js', + ], + coverageDirectory: './testing/jest/code_coverage', + coverageThreshold: { + './js/renovation/**/*.p.js': { + functions: 100, + statements: 100, + lines: 100, + branches: 100 + } + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + preset: 'ts-jest', + setupFiles: [ + path.join(path.resolve('.'), './testing/jest/setup-enzyme.ts'), + ], + testMatch: [ + path.join(path.resolve('.'), './testing/jest/**/*.tests.[jt]s?(x)') + ], + transform: { + '.(js|jsx|ts|tsx)': resolve.sync('ts-jest') + } +}; diff --git a/js/.eslintrc b/js/.eslintrc index 251a6c8d0139..82bd686698cc 100644 --- a/js/.eslintrc +++ b/js/.eslintrc @@ -1,4 +1,8 @@ { + "env": { + "es6": true, + "node": false + }, "parserOptions": { "sourceType": "module" }, diff --git a/js/bundles/modules/parts/widgets-base.js b/js/bundles/modules/parts/widgets-base.js index 956caccba6d7..daddceea96ce 100644 --- a/js/bundles/modules/parts/widgets-base.js +++ b/js/bundles/modules/parts/widgets-base.js @@ -89,4 +89,9 @@ ui.CollectionWidget = require('../../../ui/collection/ui.collection_widget.edit' ui.dxDropDownEditor = require('../../../ui/drop_down_editor/ui.drop_down_editor'); // Reports +// Renovation +ui.Button = require('../../../renovation/button.j').default; +ui.Widget = require('../../../renovation/widget.j').default; +// Renovation + module.exports = ui; diff --git a/js/core/dom_component.js b/js/core/dom_component.js index f3344bd6a2fc..01f55cbffbfe 100644 --- a/js/core/dom_component.js +++ b/js/core/dom_component.js @@ -437,6 +437,7 @@ const DOMComponent = Component.inherit({ if(anonymousTemplateMeta.name && !anonymousTemplate) { this._options.silent(`integrationOptions.templates.${anonymousTemplateMeta.name}`, anonymousTemplateMeta.template); + this._options.silent('_hasAnonymousTemplateContent', true); } }, diff --git a/js/core/options/utils.js b/js/core/options/utils.js index 84df374783ca..12c0cb8af957 100644 --- a/js/core/options/utils.js +++ b/js/core/options/utils.js @@ -35,3 +35,7 @@ export const getNestedOptionValue = function(optionsObject, name) { cachedGetters[name] = cachedGetters[name] || compileGetter(name); return cachedGetters[name](optionsObject, { functionsAsIs: true }); }; + +export default function createDefaultOptionRules(options = []) { + return options; +} diff --git a/js/core/utils/icon.js b/js/core/utils/icon.js index ef0175f29986..20c39f4767cd 100644 --- a/js/core/utils/icon.js +++ b/js/core/utils/icon.js @@ -3,7 +3,7 @@ import $ from '../../core/renderer'; const ICON_CLASS = 'dx-icon'; const SVG_ICON_CLASS = 'dx-svg-icon'; -const getImageSourceType = (source) => { +export const getImageSourceType = (source) => { if(!source || typeof source !== 'string') { return false; } @@ -27,7 +27,7 @@ const getImageSourceType = (source) => { return false; }; -const getImageContainer = (source) => { +export const getImageContainer = (source) => { switch(getImageSourceType(source)) { case 'image': return $('').attr('src', source).addClass(ICON_CLASS); @@ -41,6 +41,3 @@ const getImageContainer = (source) => { return null; } }; - -exports.getImageSourceType = getImageSourceType; -exports.getImageContainer = getImageContainer; diff --git a/js/events/pointer.js b/js/events/pointer.js index 54e58b03967b..6d4fc9a8d207 100644 --- a/js/events/pointer.js +++ b/js/events/pointer.js @@ -1,4 +1,4 @@ -import support from '../core/utils/support'; +import * as support from '../core/utils/support'; import { each } from '../core/utils/iterator'; import browser from '../core/utils/browser'; import devices from '../core/devices'; diff --git a/js/events/short.js b/js/events/short.js index 22c8d72a4e39..b4662be5e0a6 100644 --- a/js/events/short.js +++ b/js/events/short.js @@ -7,15 +7,19 @@ function addNamespace(event, namespace) { return namespace ? pureAddNamespace(event, namespace) : event; } +function executeAction(action, args) { + return typeof action === 'function' ? action(args) : action.execute(args); +} + export const active = { on: ($el, active, inactive, opts) => { const { selector, showTimeout, hideTimeout, namespace } = opts; eventsEngine.on($el, addNamespace('dxactive', namespace), selector, { timeout: showTimeout }, - event => active.execute({ event, element: event.currentTarget }) + event => executeAction(active, { event, element: event.currentTarget }) ); eventsEngine.on($el, addNamespace('dxinactive', namespace), selector, { timeout: hideTimeout }, - event => inactive.execute({ event, element: event.currentTarget }) + event => executeAction(inactive, { event, element: event.currentTarget }) ); }, @@ -37,9 +41,8 @@ export const resize = { export const hover = { on: ($el, start, end, { selector, namespace }) => { eventsEngine.on($el, addNamespace('dxhoverend', namespace), selector, event => end(event)); - eventsEngine.on($el, addNamespace('dxhoverstart', namespace), selector, event => { - start.execute({ element: event.target, event }); - }); + eventsEngine.on($el, addNamespace('dxhoverstart', namespace), selector, + event => executeAction(start, { element: event.target, event })); }, off: ($el, { selector, namespace }) => { @@ -67,7 +70,7 @@ export const focus = { if(domAdapter.hasDocumentProperty('onbeforeactivate')) { eventsEngine.on($el, addNamespace('beforeactivate', namespace), - e => isFocusable(e.target) || e.preventDefault() + e => isFocusable(null, e.target) || e.preventDefault() ); } }, diff --git a/js/renovation/.eslintrc b/js/renovation/.eslintrc new file mode 100644 index 000000000000..b25516aa9f13 --- /dev/null +++ b/js/renovation/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "devextreme/renovation-declarations" + ] +} diff --git a/js/renovation/button.tsx b/js/renovation/button.tsx new file mode 100644 index 000000000000..6f7ddd5903b5 --- /dev/null +++ b/js/renovation/button.tsx @@ -0,0 +1,266 @@ +import { + Component, + ComponentBindings, + Effect, + Event, + JSXComponent, + Method, + OneWay, + Ref, + Template, +} from 'devextreme-generator/component_declaration/common'; +import createDefaultOptionRules from '../core/options/utils'; +import devices from '../core/devices'; +import noop from './utils/noop'; +import themes from '../ui/themes'; +import { click } from '../events/short'; +import { getImageSourceType } from '../core/utils/icon'; +import Icon from './icon'; +import InkRipple from './ink-ripple'; +import Widget from './widget'; +import { BaseWidgetProps } from './utils/base-props'; +import BaseComponent from './preact-wrapper/button'; + +const stylingModes = ['outlined', 'text', 'contained']; + +const getInkRippleConfig = ({ text, icon, type }: ButtonProps) => { + const isOnlyIconButton = (!text && icon) || (type === 'back'); + const config: any = isOnlyIconButton ? { + isCentered: true, + useHoldAnimation: false, + waveSizeCoefficient: 1, + } : {}; + + return config; +}; + +const getCssClasses = (model: ButtonProps) => { + const { + text, icon, stylingMode, type, iconPosition, + } = model; + const classNames = ['dx-button']; + const isValidStylingMode = stylingMode && stylingModes.indexOf(stylingMode) !== -1; + + classNames.push(`dx-button-mode-${isValidStylingMode ? stylingMode : 'contained'}`); + classNames.push(`dx-button-${type || 'normal'}`); + + text && classNames.push('dx-button-has-text'); + icon && classNames.push('dx-button-has-icon'); + iconPosition !== 'left' && classNames.push('dx-button-icon-right'); + + return classNames.join(' '); +}; + +const getAriaLabel = (text, icon) => { + let label = (text && text.trim()) || icon; + + if (!text && getImageSourceType(icon) === 'image') { + label = icon.indexOf('base64') === -1 ? icon.replace(/.+\/([^.]+)\..+$/, '$1') : 'Base64'; + } + + return label ? { label } : {}; +}; + +export const viewFunction = (viewModel: Button) => { + const { + icon, iconPosition, template, text, + } = viewModel.props; + const renderText = !template && text; + const isIconLeft = iconPosition === 'left'; + const iconComponent = !template && viewModel.iconSource + && ; + + return ( + +
+ {template + && ( + + )} + {isIconLeft && iconComponent} + {renderText + && {text}} + {!isIconLeft && iconComponent} + {viewModel.props.useSubmitBehavior + && } + {viewModel.props.useInkRipple + && } +
+
+ ); +}; + +@ComponentBindings() +export class ButtonProps extends BaseWidgetProps { + @OneWay() activeStateEnabled?: boolean = true; + + @OneWay() hoverStateEnabled?: boolean = true; + + @OneWay() icon?: string = ''; + + @OneWay() iconPosition?: string = 'left'; + + @Event({ + actionConfig: { excludeValidators: ['readOnly'] }, + }) + onClick?: (e: any) => any = noop; + + @Event() onSubmit?: (e: any) => any = noop; + + @OneWay() pressed?: boolean; + + @OneWay() stylingMode?: 'outlined' | 'text' | 'contained'; + + @Template({ canBeAnonymous: true }) template?: any = ''; + + @OneWay() text?: string = ''; + + @OneWay() type?: string; + + @OneWay() useInkRipple?: boolean = false; + + @OneWay() useSubmitBehavior?: boolean = false; + + @OneWay() validationGroup?: string = undefined; +} + +const defaultOptionRules = createDefaultOptionRules([{ + device: () => devices.real().deviceType === 'desktop' && !(devices as any).isSimulator(), + options: { focusStateEnabled: true }, +}, { + device: () => (themes as any).isMaterial(themes.current()), + options: { useInkRipple: true }, +}]); +@Component({ + defaultOptionRules, + jQuery: { + component: BaseComponent, + register: true, + }, + view: viewFunction, +}) + +export default class Button extends JSXComponent { + @Ref() contentRef!: HTMLDivElement; + + @Ref() inkRippleRef!: InkRipple; + + @Ref() submitInputRef!: HTMLInputElement; + + @Ref() widgetRef!: Widget; + + @Effect() + contentReadyEffect() { + // NOTE: we should trigger this effect on change each + // property upon which onContentReady depends + // (for example, text, icon, etc) + const { onContentReady } = this.props; + + onContentReady!({ element: this.contentRef.parentNode }); + } + + @Method() + focus() { + this.widgetRef.focus(); + } + + onActive(event: Event) { + const { useInkRipple } = this.props; + + useInkRipple && this.inkRippleRef.showWave({ element: this.contentRef, event }); + } + + onInactive(event: Event) { + const { useInkRipple } = this.props; + + useInkRipple && this.inkRippleRef.hideWave({ element: this.contentRef, event }); + } + + onWidgetClick(event: Event) { + const { onClick, useSubmitBehavior, validationGroup } = this.props; + + onClick!({ event, validationGroup }); + useSubmitBehavior && this.submitInputRef.click(); + } + + onWidgetKeyDown(event: Event, options) { + const { onKeyDown } = this.props; + const { keyName, which } = options; + + const result = onKeyDown?.(event, options); + if (result?.cancel) { + return result; + } + + if (keyName === 'space' || which === 'space' || keyName === 'enter' || which === 'enter') { + event.preventDefault(); + this.onWidgetClick(event); + } + + return undefined; + } + + @Effect() + submitEffect() { + const namespace = 'UIFeedback'; + const { useSubmitBehavior, onSubmit } = this.props; + + if (useSubmitBehavior) { + click.on(this.submitInputRef, + (event) => onSubmit!({ event, submitInput: this.submitInputRef }), + { namespace }); + + return () => click.off(this.submitInputRef, { namespace }); + } + + return undefined; + } + + get aria() { + return getAriaLabel(this.props.text, this.props.icon); + } + + get cssClasses(): string { + return getCssClasses(this.props); + } + + get elementAttr() { + return { ...this.props.elementAttr, role: 'button' }; + } + + get iconSource(): string { + const { icon, type } = this.props; + + return (icon || type === 'back') ? (icon || 'back') : ''; + } + + get inkRippleConfig() { + return getInkRippleConfig(this.props); + } +} diff --git a/js/renovation/error-message.tsx b/js/renovation/error-message.tsx new file mode 100644 index 000000000000..2cdab02380ad --- /dev/null +++ b/js/renovation/error-message.tsx @@ -0,0 +1,25 @@ +import { + Component, ComponentBindings, JSXComponent, OneWay, +} from 'devextreme-generator/component_declaration/common'; + +export const viewFunction = ({ props: { message, className }, restAttributes }: ErrorMessage) => ( +
+ {message} +
+); + +@ComponentBindings() +export class ErrorMessageProps { + @OneWay() className?: string = ''; + + @OneWay() message?: string = ''; +} + +@Component({ + defaultOptionRules: null, + view: viewFunction, +}) +export default class ErrorMessage extends JSXComponent {} diff --git a/js/renovation/icon.tsx b/js/renovation/icon.tsx new file mode 100644 index 000000000000..454d8fdeef93 --- /dev/null +++ b/js/renovation/icon.tsx @@ -0,0 +1,34 @@ +import { + Component, ComponentBindings, JSXComponent, OneWay, Fragment, +} from 'devextreme-generator/component_declaration/common'; +import { getImageSourceType } from '../core/utils/icon'; + +export const viewFunction = ({ sourceType, cssClass, props: { source } }: Icon) => ( + + {sourceType === 'dxIcon' && } + {sourceType === 'fontIcon' && } + {sourceType === 'image' && } + {sourceType === 'svg' && {source}} + +); + +@ComponentBindings() +export class IconProps { + @OneWay() position?: string = 'left'; + + @OneWay() source?: string = ''; +} + +@Component({ + defaultOptionRules: null, + view: viewFunction, +}) +export default class Icon extends JSXComponent { + get sourceType() { + return getImageSourceType(this.props.source); + } + + get cssClass() { + return this.props.position !== 'left' ? 'dx-icon-right' : ''; + } +} diff --git a/js/renovation/ink-ripple.tsx b/js/renovation/ink-ripple.tsx new file mode 100644 index 000000000000..1d2e980f5365 --- /dev/null +++ b/js/renovation/ink-ripple.tsx @@ -0,0 +1,38 @@ +import { + Component, ComponentBindings, JSXComponent, OneWay, Method, +} from 'devextreme-generator/component_declaration/common'; +import { initConfig, showWave, hideWave } from '../ui/widget/utils.ink_ripple'; + +// TODO: remake old ink ripple in new JSX component +export const viewFunction = (model: InkRipple) => ( +
+); + +@ComponentBindings() +export class InkRippleProps { + @OneWay() config?: any = {}; +} + +@Component({ + defaultOptionRules: null, + view: viewFunction, +}) +export default class InkRipple extends JSXComponent { + @Method() + hideWave(event) { + hideWave(this.getConfig, event); + } + + @Method() + showWave(event) { + showWave(this.getConfig, event); + } + + get getConfig() { + const { config } = this.props; + return initConfig(config); + } +} diff --git a/js/renovation/number-box.tsx b/js/renovation/number-box.tsx new file mode 100644 index 000000000000..c6fcb2129ee0 --- /dev/null +++ b/js/renovation/number-box.tsx @@ -0,0 +1,56 @@ +import { + Ref, Effect, Component, ComponentBindings, JSXComponent, OneWay, Event, TwoWay, +} from 'devextreme-generator/component_declaration/common'; +import DxNumberBox from '../ui/number_box'; +import { WidgetProps } from './widget'; + +export const viewFunction = ({ widgetRef }: NumberBox) => (
); + +@ComponentBindings() +export class NumberBoxProps extends WidgetProps { + // props was copied from js\ui\number_box.d.ts + + // buttons?: Array<'clear' | 'spins' | dxTextEditorButton>; + // format?: format; + @OneWay() invalidValueMessage?: string; + + @OneWay() max?: number; + + @OneWay() min?: number; + + @OneWay() mode?: 'number' | 'text' | 'tel'; + + // Needed only for jQuery. Should be auto-generated + // onValueChanged?: ((e: { component?: T, element?: dxElement, model?: any, + // value?: any, previousValue?: any, event?: event }) => any); + @OneWay() showSpinButtons?: boolean; + + @OneWay() step?: number; + + @OneWay() useLargeSpinButtons?: boolean; + + @TwoWay() value?: number; + + @Event() valueChange?: ((value: number) => void) = () => {}; +} + +@Component({ + defaultOptionRules: null, + view: viewFunction, +}) +export default class NumberBox extends JSXComponent { + @Ref() + widgetRef!: HTMLDivElement; + + @Effect() + setupWidget() { + const { valueChange } = this.props; + + new DxNumberBox(this.widgetRef, { // eslint-disable-line no-new + ...this.props as any, + onValueChanged: (e) => { + valueChange!(e.value); + }, + }); + } +} diff --git a/js/renovation/preact-wrapper/button.d.ts b/js/renovation/preact-wrapper/button.d.ts new file mode 100644 index 000000000000..7b0968779962 --- /dev/null +++ b/js/renovation/preact-wrapper/button.d.ts @@ -0,0 +1,13 @@ +/* eslint-disable no-underscore-dangle */ +import Component from './component'; + +export default class Button extends Component { + getAllProps(isFirstRender: boolean): any; + + _init(): any; + + _getSubmitAction(): any; + + _findGroup(): any; +} +/* eslint-enable no-underscore-dangle */ diff --git a/js/renovation/preact-wrapper/button.js b/js/renovation/preact-wrapper/button.js new file mode 100644 index 000000000000..fab660e39eb0 --- /dev/null +++ b/js/renovation/preact-wrapper/button.js @@ -0,0 +1,59 @@ +/* eslint-disable */ +import ValidationEngine from '../../ui/validation_engine'; +import Component from './component'; + +export default class Button extends Component { + _init() { + super._init(); + this._addAction('onSubmit', this._getSubmitAction()); + } + + getAllProps(isFirstRender) { + const props = super.getAllProps(isFirstRender); + props.validationGroup = this._validationGroupConfig; + return props; + } + + _getSubmitAction() { + let needValidate = true; + let validationStatus = 'valid'; + + return this._createAction(({ event, submitInput }) => { + if(needValidate) { + const validationGroup = this._validationGroupConfig; + + if(validationGroup) { + const { status, complete } = validationGroup.validate(); + + validationStatus = status; + + if(status === 'pending') { + needValidate = false; + this.option('disabled', true); + + complete.then(({ status }) => { + needValidate = true; + this.option('disabled', false); + + validationStatus = status; + validationStatus === 'valid' && submitInput.click(); + }); + } + } + } + + validationStatus !== 'valid' && event.preventDefault(); + event.stopPropagation(); + }); + } + + get _validationGroupConfig() { + return ValidationEngine.getGroupConfig(this._findGroup()); + } + + _findGroup() { + const $element = this.$element(); + return this.option('validationGroup') || ValidationEngine.findGroup($element, this._modelByElement($element)); + } +} +/* eslint-enable */ diff --git a/js/renovation/preact-wrapper/component.d.ts b/js/renovation/preact-wrapper/component.d.ts new file mode 100644 index 000000000000..e09972a4876a --- /dev/null +++ b/js/renovation/preact-wrapper/component.d.ts @@ -0,0 +1,38 @@ +/* eslint-disable no-underscore-dangle */ +import DOMComponent from '../../core/dom_component'; + +export default class PreactWrapper extends DOMComponent { + viewRef: any; + + getInstance(): this; + + _initMarkup(): void; + + _render(): void; + + getAllProps(isFirstRender: boolean): any; + + _getActionsMap(): any; + + _init(): any; + + _createViewRef(): any; + + _optionChanged(option: any): any; + + _addAction(name: string, config: any): any; + + _stateChange(name: string): (value: any) => void; + + _createTemplateComponent(props: any, templateOption: any, canBeAnonymous: boolean): any; + + _wrapKeyDownHandler(handler: (event: any, options: any) => any): any; + + // Public API + repaint(): any; + + registerKeyHandler(key: string, handler: (e: any) => any): void; + + setAria(): any; +} +/* eslint-enable no-underscore-dangle */ diff --git a/js/renovation/preact-wrapper/component.js b/js/renovation/preact-wrapper/component.js new file mode 100644 index 000000000000..946305223145 --- /dev/null +++ b/js/renovation/preact-wrapper/component.js @@ -0,0 +1,190 @@ +/* eslint-disable */ +import $ from '../../core/renderer'; +import DOMComponent from '../../core/dom_component'; +import * as Preact from 'preact'; +import { isEmpty } from '../../core/utils/string'; +import { wrapElement, removeDifferentElements } from '../preact-wrapper/utils'; +import { useLayoutEffect } from 'preact/hooks'; +import { getPublicElement } from '../../core/element'; + +const TEMPLATE_WRAPPER_CLASS = 'dx-template-wrapper'; + +export default class PreactWrapper extends DOMComponent { + getInstance() { + return this; + } + + _initMarkup() { + const isFirstRender = this.$element().children().length === 0; + const hasParent = this.$element().parent().length > 0; + const container = isFirstRender && hasParent ? this.$element().get(0) : undefined; + + Preact.render(Preact.h(this._viewComponent, this.getAllProps(isFirstRender)), this.$element().get(0), container); + } + + _render() { + // NOTE: see ui.widget + // this._renderContent(); + } + // _renderContent() { } + + getAllProps(isFirstRender) { + const options = { ...this.option() }; + const attributes = this.$element()[0].attributes; + const { width, height } = this.$element()[0].style; + + if(isFirstRender) { + options.elementAttr = { + ...Object.keys(attributes).reduce((a, key) => { + if(attributes[key].specified) { + a[attributes[key].name] = attributes[key].value; + } + return a; + }, {}), + ...options.elementAttr + }; + } else { + if(attributes.id) { + // NOTE: workaround to save container id + options.elementAttr = { + [attributes.id.name]: attributes.id.value, + ...options.elementAttr + }; + } + if(attributes.class) { + // NOTE: workaround to save custom classes on type changes + const customClass = attributes.class.value + .split(' ') + .filter(name => name.indexOf('dx-') < 0) + .join(' '); + const classes = options.elementAttr.class ? options.elementAttr.class.concat(customClass) : customClass; + options.elementAttr = { class: classes, ...options.elementAttr }; + } + } + if(!isEmpty(width)) { + options.width = width; + } + if(!isEmpty(height)) { + options.height = height; + } + + if(this.viewRef) { + options.ref = this.viewRef; + } + + Object.keys(this._actionsMap).forEach(name => { + options[name] = this._actionsMap[name]; + }); + + return this.getProps && this.getProps(options) || options; + } + + _getActionConfigs() { return {}; } + + _init() { + super._init(); + this._actionsMap = {}; + + Object.keys(this._getActionConfigs()).forEach(name => this._addAction(name)); + + this._initWidget && this._initWidget(); + this._supportedKeys = () => ({}); + } + + _addAction(event, action) { + this._actionsMap[event] = action || this._createActionByOption(event, this._getActionConfigs()[event]); + } + + _createViewRef() { + this.viewRef = Preact.createRef(); + } + + _optionChanged(option) { + const { name } = option || {}; + if(name && this._getActionConfigs()[name]) { + this._addAction(name); + } + + super._optionChanged(option); + this._invalidate(); + } + + _stateChange(name) { + return (value) => this.option(name, value); + } + + _createTemplateComponent(props, templateOption, canBeAnonymous) { + if(!templateOption && this.option('_hasAnonymousTemplateContent') && canBeAnonymous) { + templateOption = this._templateManager.anonymousTemplateName; + } + if(!templateOption) { + return; + } + + const template = this._getTemplate(templateOption); + return ({ parentRef, ...restProps }) => { + useLayoutEffect(() => { + const $parent = $(parentRef.current); + const $children = $parent.contents(); + + let $template = $(template.render({ + container: getPublicElement($parent), + model: restProps, + transclude: canBeAnonymous && templateOption === this._templateManager.anonymousTemplateName, + // TODO index + })); + + if($template.hasClass(TEMPLATE_WRAPPER_CLASS)) { + $template = wrapElement($parent, $template); + } + const $newChildren = $parent.contents(); + + return () => { + // NOTE: order is important + removeDifferentElements($children, $newChildren); + }; + }, Object.keys(props).map(key => props[key])); + + return (); + }; + } + + _wrapKeyDownHandler(handler) { + return (event, options) => { + const { originalEvent, keyName, which } = options; + const keys = this._supportedKeys(); + const func = keys[keyName] || keys[which]; + + // NOTE: registered handler has more priority + if(func !== undefined) { + const handler = func.bind(this); + const result = handler(originalEvent, options); + + if(!result) { + event.cancel = true; + return event; + } + } + + // NOTE: make it possible to pass onKeyDown property + return handler?.(event, options); + }; + } + + // Public API + repaint() { + this._refresh(); + } + + registerKeyHandler(key, handler) { + const currentKeys = this._supportedKeys(); + this._supportedKeys = () => ({ ...currentKeys, [key]: handler }); + } + + // NOTE: this method will be deprecated + // aria changes should be defined in declaration or passed through property + setAria() { + throw new Error('"setAria" method is deprecated, use "aria" property instead'); + } +} +/* eslint-enable */ diff --git a/js/renovation/preact-wrapper/utils.js b/js/renovation/preact-wrapper/utils.js new file mode 100644 index 000000000000..3ff12ea8c6ee --- /dev/null +++ b/js/renovation/preact-wrapper/utils.js @@ -0,0 +1,40 @@ +import { each } from '../../core/utils/iterator'; + +const addAttributes = ($element, attributes) => { + each(attributes, (_, { name, value }) => { + if (name === 'class') { + $element.addClass(value); + } else { + $element.attr(name, value); + } + }); +}; + +// NOTE: function for jQuery templates +export const wrapElement = ($element, $wrapper) => { + const { attributes } = $wrapper.get(0); + const children = $wrapper.contents(); + + addAttributes($element, attributes); + + $wrapper.remove(); + each(children, (_, child) => { + $element.append(child); + }); + + return children; +}; + +export const removeDifferentElements = ($children, $newChildren) => { + each($newChildren, (__, element) => { + let hasComponent = false; + each($children, (_, oldElement) => { + if (element === oldElement) { + hasComponent = true; + } + }); + if (!hasComponent) { + element.parentNode.removeChild(element); + } + }); +}; diff --git a/js/renovation/select-box.tsx b/js/renovation/select-box.tsx new file mode 100644 index 000000000000..78d3cb751016 --- /dev/null +++ b/js/renovation/select-box.tsx @@ -0,0 +1,41 @@ +import { + Ref, Effect, Component, ComponentBindings, JSXComponent, Event, OneWay, TwoWay, +} from 'devextreme-generator/component_declaration/common'; +import { WidgetProps } from './widget'; +import DataSource, { DataSourceOptions } from '../data/data_source'; +import DxSelectBox from '../ui/select_box'; + +export const viewFunction = ({ widgetRef }: SelectBox) => (
); + +@ComponentBindings() +export class SelectBoxProps extends WidgetProps { + @OneWay() dataSource?: string | Array | DataSource | DataSourceOptions; + + @OneWay() displayExpr?: string; + + @TwoWay() value?: number; + + @OneWay() valueExpr?: string; + + @Event() valueChange?: ((value: number) => void) = () => {}; +} +@Component({ + defaultOptionRules: null, + view: viewFunction, +}) +export default class SelectBox extends JSXComponent { + @Ref() + widgetRef!: HTMLDivElement; + + @Effect() + setupWidget() { + const { valueChange } = this.props; + + new DxSelectBox(this.widgetRef, { // eslint-disable-line no-new + ...this.props as any, + onValueChanged: (e) => { + valueChange!(e.value); + }, + }); + } +} diff --git a/js/renovation/utils/base-props.tsx b/js/renovation/utils/base-props.tsx new file mode 100644 index 000000000000..07c768943c95 --- /dev/null +++ b/js/renovation/utils/base-props.tsx @@ -0,0 +1,39 @@ +import { + Event, OneWay, ComponentBindings, +} from 'devextreme-generator/component_declaration/common'; +import config from '../../core/config'; + +@ComponentBindings() +export class BaseWidgetProps { // eslint-disable-line import/prefer-default-export + @OneWay() accessKey?: string | null = null; + + @OneWay() activeStateEnabled?: boolean = false; + + @OneWay() disabled?: boolean = false; + + @OneWay() elementAttr?: { [name: string]: any }; + + @OneWay() focusStateEnabled?: boolean = false; + + @OneWay() height?: string | number | null = null; + + @OneWay() hint?: string; + + @OneWay() hoverStateEnabled?: boolean = false; + + @Event() onClick?: (e: any) => void; + + @Event({ + actionConfig: { excludeValidators: ['disabled', 'readOnly'] }, + }) onContentReady?: (e: any) => any = (() => {}); + + @Event() onKeyDown?: (e: any, options: any) => any; + + @OneWay() rtlEnabled?: boolean = config().rtlEnabled; + + @OneWay() tabIndex?: number = 0; + + @OneWay() visible?: boolean = true; + + @OneWay() width?: string | number | null = null; +} diff --git a/js/renovation/utils/noop.js b/js/renovation/utils/noop.js new file mode 100644 index 000000000000..5ff8d71cb4e4 --- /dev/null +++ b/js/renovation/utils/noop.js @@ -0,0 +1 @@ +export default () => undefined; diff --git a/js/renovation/widget.tsx b/js/renovation/widget.tsx new file mode 100644 index 000000000000..b7e4e321f696 --- /dev/null +++ b/js/renovation/widget.tsx @@ -0,0 +1,366 @@ +import { + Component, + ComponentBindings, + Effect, + Event, + InternalState, + JSXComponent, + Method, + OneWay, + Ref, + Slot, +} from 'devextreme-generator/component_declaration/common'; +import '../events/click'; +import '../events/hover'; + +import { + active, dxClick, focus, hover, keyboard, resize, visibility, +} from '../events/short'; +import { each } from '../core/utils/iterator'; +import { extend } from '../core/utils/extend'; +import { focusable } from '../ui/widget/selectors'; +import { isFakeClickEvent } from '../events/utils'; +import { BaseWidgetProps } from './utils/base-props'; + +const getStyles = ({ width, height, style }) => { + const computedWidth = typeof width === 'function' ? width() : width; + const computedHeight = typeof height === 'function' ? height() : height; + + return { + height: computedHeight ?? undefined, + width: computedWidth ?? undefined, + ...style, + }; +}; + +const setAttribute = (name, value) => { + const result = {}; + + if (value) { + const attrName = (name === 'role' || name === 'id') ? name : `aria-${name}`; + + result[attrName] = String(value); + } + + return result; +}; + +const getAria = (args) => { + let attrs = {}; + + each(args, (name, value) => { + attrs = { ...attrs, ...setAttribute(name, value) }; + }); + + return attrs; +}; + +const getAttributes = ({ elementAttr, accessKey }) => { + const attrs = extend({}, elementAttr, accessKey && { accessKey }); + + delete attrs.class; + + return attrs; +}; + +const getCssClasses = (model: Partial & Partial) => { + const className = ['dx-widget']; + const isFocusable = model.focusStateEnabled && !model.disabled; + const isHoverable = model.hoverStateEnabled && !model.disabled; + + model.classes && className.push(model.classes); + model.className && className.push(model.className); + model.disabled && className.push('dx-state-disabled'); + !model.visible && className.push('dx-state-invisible'); + model.focused && isFocusable && className.push('dx-state-focused'); + model.active && className.push('dx-state-active'); + model.hovered && isHoverable && !model.active && className.push('dx-state-hover'); + model.rtlEnabled && className.push('dx-rtl'); + model.onVisibilityChange && className.push('dx-visibility-change-handler'); + model.elementAttr?.class && className.push(model.elementAttr.class); + + return className.join(' '); +}; + +export const viewFunction = (viewModel: Widget) => ( + +); + +@ComponentBindings() +export class WidgetProps extends BaseWidgetProps { + @OneWay() _feedbackHideTimeout?: number = 400; + + @OneWay() _feedbackShowTimeout?: number = 30; + + @OneWay() activeStateUnit?: string; + + @OneWay() aria?: any = {}; + + @Slot() children?: any; + + @OneWay() classes?: string | undefined = ''; + + @OneWay() className?: string = ''; + + @OneWay() name?: string = ''; + + @Event() onActive?: (e: any) => any; + + @Event() onDimensionChanged?: () => any; + + @Event() onInactive?: (e: any) => any; + + @Event() onKeyboardHandled?: (args: any) => any | undefined; + + @Event() onVisibilityChange?: (args: boolean) => undefined; + + @OneWay() style?: { [name: string]: any }; +} + +@Component({ + defaultOptionRules: null, + jQuery: { + register: true, + }, + view: viewFunction, +}) + +export default class Widget extends JSXComponent { + @InternalState() active = false; + + @InternalState() focused = false; + + @InternalState() hovered = false; + + @Ref() + widgetRef!: HTMLDivElement; + + @Effect() + accessKeyEffect() { + const namespace = 'UIFeedback'; + const { accessKey, focusStateEnabled, disabled } = this.props; + const isFocusable = focusStateEnabled && !disabled; + const canBeFocusedByKey = isFocusable && accessKey; + + if (canBeFocusedByKey) { + dxClick.on(this.widgetRef, (e) => { + if (isFakeClickEvent(e)) { + e.stopImmediatePropagation(); + this.focused = true; + } + }, { namespace }); + + return () => dxClick.off(this.widgetRef, { namespace }); + } + + return undefined; + } + + @Effect() + activeEffect() { + const { + activeStateEnabled, activeStateUnit, disabled, onInactive, + _feedbackShowTimeout, _feedbackHideTimeout, onActive, + } = this.props; + const selector = activeStateUnit; + const namespace = 'UIFeedback'; + + if (activeStateEnabled && !disabled) { + active.on(this.widgetRef, + ({ event }) => { + this.active = true; + onActive?.(event); + }, + ({ event }) => { + this.active = false; + onInactive?.(event); + }, { + hideTimeout: _feedbackHideTimeout, + namespace, + selector, + showTimeout: _feedbackShowTimeout, + }); + + return () => active.off(this.widgetRef, { selector, namespace }); + } + + return undefined; + } + + @Effect() + clickEffect() { + const { name, onClick } = this.props; + const namespace = name; + + if (onClick) { + dxClick.on(this.widgetRef, + (e) => onClick(e), + { namespace }); + + return () => dxClick.off(this.widgetRef, { namespace }); + } + + return undefined; + } + + @Method() + focus() { + focus.trigger(this.widgetRef); + } + + @Effect() + focusEffect() { + const { disabled, focusStateEnabled, name } = this.props; + const namespace = `${name}Focus`; + const isFocusable = focusStateEnabled && !disabled; + + if (isFocusable) { + focus.on(this.widgetRef, + (e) => { !e.isDefaultPrevented() && (this.focused = true); }, + (e) => { !e.isDefaultPrevented() && (this.focused = false); }, + { + isFocusable: focusable, + namespace, + }); + + return () => focus.off(this.widgetRef, { namespace }); + } + + return undefined; + } + + @Effect() + hoverEffect() { + const namespace = 'UIFeedback'; + const { activeStateUnit, hoverStateEnabled, disabled } = this.props; + const selector = activeStateUnit; + const isHoverable = hoverStateEnabled && !disabled; + + if (isHoverable) { + hover.on(this.widgetRef, + () => { !this.active && (this.hovered = true); }, + () => { this.hovered = false; }, + { selector, namespace }); + + return () => hover.off(this.widgetRef, { selector, namespace }); + } + + return undefined; + } + + @Effect() + keyboardEffect() { + const { focusStateEnabled, onKeyDown } = this.props; + + if (focusStateEnabled || onKeyDown) { + const id = keyboard.on(this.widgetRef, this.widgetRef, + (options) => onKeyDown!(options.originalEvent, options)); + + return () => keyboard.off(id); + } + + return undefined; + } + + @Effect() + resizeEffect() { + const namespace = `${this.props.name}VisibilityChange`; + const { onDimensionChanged } = this.props; + + if (onDimensionChanged) { + resize.on(this.widgetRef, onDimensionChanged, { namespace }); + + return () => resize.off(this.widgetRef, { namespace }); + } + + return undefined; + } + + @Effect() + visibilityEffect() { + const { name, onVisibilityChange } = this.props; + const namespace = `${name}VisibilityChange`; + + if (onVisibilityChange) { + visibility.on(this.widgetRef, + () => onVisibilityChange!(true), + () => onVisibilityChange!(false), + { namespace }); + + return () => visibility.off(this.widgetRef, { namespace }); + } + + return undefined; + } + + get attributes() { + const { + accessKey, + aria, + disabled, + elementAttr, + focusStateEnabled, + visible, + } = this.props; + + const arias = getAria({ ...aria, disabled, hidden: !visible }); + const attrsWithoutClass = getAttributes({ + accessKey: focusStateEnabled && !disabled && accessKey, + elementAttr, + }); + + return { ...attrsWithoutClass, ...arias }; + } + + get styles() { + const { width, height, style } = this.props; + + return getStyles({ width, height, style }); + } + + get cssClasses() { + const { + classes, + className, + disabled, + elementAttr, + focusStateEnabled, + hoverStateEnabled, + onVisibilityChange, + rtlEnabled, + visible, + } = this.props; + + return getCssClasses({ + active: this.active, + focused: this.focused, + hovered: this.hovered, + className, + classes, + disabled, + elementAttr, + focusStateEnabled, + hoverStateEnabled, + onVisibilityChange, + rtlEnabled, + visible, + }); + } + + get tabIndex() { + const { focusStateEnabled, disabled } = this.props; + + return focusStateEnabled && !disabled && this.props.tabIndex; + } +} diff --git a/js/ui/gantt.d.ts b/js/ui/gantt.d.ts index a8d1cc9752ff..10ab5715e99b 100644 --- a/js/ui/gantt.d.ts +++ b/js/ui/gantt.d.ts @@ -238,7 +238,7 @@ export interface dxGanttStripLine { declare global { interface JQuery { dxGantt(): JQuery; - dxGantt(options: "instance"): dxGantt; + dxGantt(options: 'instance'): dxGantt; dxGantt(options: string): any; dxGantt(options: string, ...params: any[]): any; dxGantt(options: dxGanttOptions): JQuery; @@ -247,4 +247,4 @@ interface JQuery { export type Options = dxGanttOptions; /** @deprecated use Options instead */ -export type IOptions = dxGanttOptions; \ No newline at end of file +export type IOptions = dxGanttOptions; diff --git a/js/ui/grid_core/ui.grid_core.editing.js b/js/ui/grid_core/ui.grid_core.editing.js index caea082edab1..776f8c73d1a9 100644 --- a/js/ui/grid_core/ui.grid_core.editing.js +++ b/js/ui/grid_core/ui.grid_core.editing.js @@ -22,7 +22,7 @@ import Form from '../form'; import holdEvent from '../../events/hold'; import { when, Deferred, fromPromise } from '../../core/utils/deferred'; import commonUtils from '../../core/utils/common'; -import iconUtils from '../../core/utils/icon'; +import * as iconUtils from '../../core/utils/icon'; import Scrollable from '../scroll_view/ui.scrollable'; import deferredUtils from '../../core/utils/deferred'; diff --git a/js/ui/hierarchical_collection/ui.hierarchical_collection_widget.js b/js/ui/hierarchical_collection/ui.hierarchical_collection_widget.js index 8e960f24ea2e..ab91cb399866 100644 --- a/js/ui/hierarchical_collection/ui.hierarchical_collection_widget.js +++ b/js/ui/hierarchical_collection/ui.hierarchical_collection_widget.js @@ -3,7 +3,7 @@ import { compileGetter, compileSetter } from '../../core/utils/data'; import { extend } from '../../core/utils/extend'; import { each } from '../../core/utils/iterator'; import devices from '../../core/devices'; -import iconUtils from '../../core/utils/icon'; +import { getImageContainer } from '../../core/utils/icon'; import HierarchicalDataAdapter from './ui.data_adapter'; import CollectionWidget from '../collection/ui.collection_widget.edit'; import { BindableTemplate } from '../../core/templates/bindable_template'; @@ -93,7 +93,7 @@ const HierarchicalCollectionWidget = CollectionWidget.inherit({ }, _getIconContainer: function(itemData) { - return itemData.icon ? iconUtils.getImageContainer(itemData.icon) : undefined; + return itemData.icon ? getImageContainer(itemData.icon) : undefined; }, _getTextContainer: function(itemData) { diff --git a/js/ui/scheduler/ui.scheduler.js b/js/ui/scheduler/ui.scheduler.js index a15efab4de91..098b6723474e 100644 --- a/js/ui/scheduler/ui.scheduler.js +++ b/js/ui/scheduler/ui.scheduler.js @@ -50,7 +50,6 @@ import SchedulerWorkSpaceMonth from './workspaces/ui.scheduler.work_space_month' import SchedulerWorkSpaceWeek from './workspaces/ui.scheduler.work_space_week'; import SchedulerWorkSpaceWorkWeek from './workspaces/ui.scheduler.work_space_work_week'; - const when = deferredUtils.when; const Deferred = deferredUtils.Deferred; diff --git a/js/ui/widget/ui.widget.js b/js/ui/widget/ui.widget.js index e9057f0e0056..247c50986928 100644 --- a/js/ui/widget/ui.widget.js +++ b/js/ui/widget/ui.widget.js @@ -419,7 +419,7 @@ const Widget = DOMComponent.inherit({ e => this._focusInHandler(e), e => this._focusOutHandler(e), { namespace: `${this.NAME}Focus`, - isFocusable: el => $(el).is(focusableSelector) + isFocusable: (index, el) => $(el).is(focusableSelector) } ); }, diff --git a/js/ui/widget/utils.ink_ripple.js b/js/ui/widget/utils.ink_ripple.js index 24f2ee1d0385..588e631f0bae 100644 --- a/js/ui/widget/utils.ink_ripple.js +++ b/js/ui/widget/utils.ink_ripple.js @@ -10,19 +10,15 @@ const ANIMATION_DURATION = 300; const HOLD_ANIMATION_DURATION = 1000; const DEFAULT_WAVE_INDEX = 0; -const render = function(args) { - args = args || {}; - - if(args.useHoldAnimation === undefined) { - args.useHoldAnimation = true; - } +const initConfig = ({ useHoldAnimation, waveSizeCoefficient, isCentered, wavesNumber } = {}) => ({ + waveSizeCoefficient: waveSizeCoefficient || DEFAULT_WAVE_SIZE_COEFFICIENT, + isCentered: isCentered || false, + wavesNumber: wavesNumber || 1, + durations: getDurations(useHoldAnimation ?? true) +}); - const config = { - waveSizeCoefficient: args.waveSizeCoefficient || DEFAULT_WAVE_SIZE_COEFFICIENT, - isCentered: args.isCentered || false, - wavesNumber: args.wavesNumber || 1, - durations: getDurations(args.useHoldAnimation) - }; +const render = function(args) { + const config = initConfig(args); return { showWave: showWave.bind(this, config), @@ -43,7 +39,7 @@ const getInkRipple = function(element) { }; const getWaves = function(element, wavesNumber) { - const inkRipple = getInkRipple(element); + const inkRipple = getInkRipple($(element)); const result = inkRipple.children('.' + INKRIPPLE_WAVE_CLASS).toArray(); for(let i = result.length; i < wavesNumber; i++) { @@ -58,7 +54,7 @@ const getWaves = function(element, wavesNumber) { }; const getWaveStyleConfig = function(args, config) { - const element = config.element; + const element = $(config.element); const elementWidth = element.outerWidth(); const elementHeight = element.outerHeight(); const elementDiagonal = parseInt(Math.sqrt(elementWidth * elementWidth + elementHeight * elementHeight)); @@ -71,7 +67,7 @@ const getWaveStyleConfig = function(args, config) { top = (elementHeight - waveSize) / 2; } else { const event = config.event; - const position = config.element.offset(); + const position = element.offset(); const x = event.pageX - position.left; const y = event.pageY - position.top; @@ -80,8 +76,8 @@ const getWaveStyleConfig = function(args, config) { } return { - left: left, - top: top, + left, + top, height: waveSize, width: waveSize }; @@ -135,6 +131,9 @@ function hideWave(args, config) { } module.exports = { - render: render + initConfig, + hideWave, + render, + showWave }; diff --git a/package.json b/package.json index 31b7fd8f87ef..7caf12776ff3 100644 --- a/package.json +++ b/package.json @@ -40,13 +40,19 @@ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-proposal-optional-chaining": "^7.8.3", "@babel/preset-env": "^7.8.7", + "@types/enzyme": "^3.10.5", + "@types/jest": "^24.0.24", "@types/jquery": "^2.0.34", + "@types/react": "16.9.16", + "@typescript-eslint/eslint-plugin": "^2.29.0", "angular": "^1.6.10", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.0.3", "babel-loader": "^8.1.0", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", + "babel-plugin-transform-object-assign": "^6.22.0", + "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-env": "^1.6.1", "bootstrap": "^4.3.1", "cldr-core": "latest", @@ -55,10 +61,20 @@ "cssom": "^0.4.4", "del": "^2.2.2", "devextreme-cldr-data": "^1.0.2", + "devextreme-generator": "1.0.63", "devextreme-internal-tools": "stable", - "eslint": "^7.0.0", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^2.2.0", + "eslint": "^7.1.0", + "eslint-config-airbnb-typescript": "^7.2.1", + "eslint-config-devextreme": "^0.1.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jest": "^23.6.0", + "eslint-plugin-jest-formatting": "^1.2.0", + "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-qunit": "^4.0.0", + "eslint-plugin-react": "^7.18.0", "eslint-plugin-spellcheck": "0.0.11", "exceljs": "3.3.1", "fibers": "^5.0.0", @@ -91,6 +107,7 @@ "handlebars": "^4.7.3", "hogan.js": "3.0.2", "intl": "^1.2.5", + "jest": "^24.9.0", "jquery": "^3.5.1", "jquery.1": "^1.0.0", "jquery.2": "^1.0.0", @@ -108,7 +125,11 @@ "nconf": "^0.10.0", "npm-run-all": "^4.1.5", "pre-commit": "^1.2.2", + "preact": "^10.0.1", "qunitjs": "^2.0.1", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "react-test-renderer": "^16.12.0", "run-sequence": "^1.1.5", "sass": "^1.26.5", "shelljs": "^0.8.3", @@ -122,7 +143,8 @@ "systemjs-plugin-text": "0.0.8", "testcafe": "^1.2.0", "through2": "^2.0.1", - "typescript": "^2.0.3", + "ts-jest": "^24.2.0", + "typescript": "^3.7.2", "underscore": "^1.9.2", "vinyl-named": "^1.1.0", "webpack": "^3.10.0", @@ -136,19 +158,28 @@ "scripts": { "lint": "npm-run-all -p -c lint-js lint-css", "lint-js": "eslint .", + "lint-ts": "eslint ./js/renovation/*.{ts,tsx} ./js/renovation/**/*.{ts,tsx} ./testing/jest/**/*.{ts,tsx}", "lint-css": "stylelint styles", "lint-staged": "lint-staged", "build": "dotnet build build/build-dotnet.sln && gulp default", "build-dist": "dotnet build build/build-dotnet.sln && gulp default --uglify", "build-themes": "gulp style-compiler-themes", "build-themebuilder-assets": "gulp style-compiler-tb-assets", + "build:react": "gulp generate-react", + "build:react:watch": "gulp generate-react-watch", + "build:angular": "gulp generate-angular", + "build:angular:watch": "gulp generate-angular-watch", + "build:vue": "gulp generate-vue", + "build:vue:watch": "gulp generate-vue-watch", "dev": "gulp dev", "transpile-tests": "gulp transpile-tests", "test-env": "node testing/launch", "update-ts": "dx-tools update-ts-bundle --output=./ts/dx.all.d.ts", "internal-tool": "dx-tools", "validate-declarations": "dx-tools validate-declarations", - "test-functional": "node ./testing/functional/runner" + "test-functional": "node ./testing/functional/runner", + "test-jest": "jest", + "test-jest:watch": "jest --watch" }, "browserslist": [ "last 2 versions", @@ -156,5 +187,10 @@ "ie > 10", "> 1%" ], + "jest": { + "modulePathIgnorePatterns": [ + "node_modules" + ] + }, "pre-commit": "lint-staged" } diff --git a/playground/angular/README.md b/playground/angular/README.md new file mode 100644 index 000000000000..1bc90142af0b --- /dev/null +++ b/playground/angular/README.md @@ -0,0 +1,27 @@ +# DevExtreme Angular Playground + +## Run Examples + +1. Install npm packages: + + ```bash + npm install + ``` + +2. Generate components and build scripts: + + ```bash + npm run build + npm run build:angular + ``` + or + + ```bash + npm run build:angular:watch + ``` + +3. Set up a local web server and launch the application in it: + + ```bash + TODO: Add the command + ``` diff --git a/playground/angular/app/app.component.html b/playground/angular/app/app.component.html new file mode 100644 index 000000000000..a257b1fb71bd --- /dev/null +++ b/playground/angular/app/app.component.html @@ -0,0 +1,7 @@ + + diff --git a/playground/angular/app/app.component.ts b/playground/angular/app/app.component.ts new file mode 100644 index 000000000000..3ec70bb38012 --- /dev/null +++ b/playground/angular/app/app.component.ts @@ -0,0 +1,27 @@ +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { DxButtonModule } from 'devextreme/renovation/button'; + +@Component({ + providers: [], + selector: 'demo-app', + styleUrls: [], + templateUrl: './app/app.component.html', +}) +export class AppComponent { + onClick() { + alert('clicked'); + } +} +@NgModule({ + imports: [ + BrowserModule, + DxButtonModule, + ], + declarations: [AppComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/playground/angular/config.js b/playground/angular/config.js new file mode 100644 index 000000000000..e83fa630bddc --- /dev/null +++ b/playground/angular/config.js @@ -0,0 +1,58 @@ +/* eslint-disable */ + +System.config({ + transpiler: 'ts', + typescriptOptions: { + module: 'commonjs', + emitDecoratorMetadata: true, + experimentalDecorators: true + }, + meta: { + 'typescript': { + 'exports': 'ts' + } + }, + paths: { + 'npm:': 'https://unpkg.com/' + }, + map: { + 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', + 'typescript': 'npm:typescript@3.4.5/lib/typescript.js', + + '@angular/core': 'npm:@angular/core@8.0.0/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common@8.0.0/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler@8.0.0/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser@8.0.0/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@8.0.0/bundles/platform-browser-dynamic.umd.js', + '@angular/router': 'npm:@angular/router@8.0.0/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms@8.0.0/bundles/forms.umd.js', + '@angular/common/http': 'npm:@angular/common@8.0.0/bundles/common-http.umd.js', + 'tslib': 'npm:tslib/tslib.js', + + 'rxjs': 'npm:rxjs@6.3.3', + 'rxjs/operators': 'npm:rxjs@6.3.3/operators', + + 'jszip': 'npm:jszip@3.1.3/dist/jszip.min.js', + 'quill': 'npm:quill@1.3.6/dist/quill.js', + 'devextreme': '../../artifacts/angular' + }, + packages: { + 'app': { + main: './app.component.ts', + defaultExtension: 'ts' + }, + 'devextreme': { + defaultExtension: 'js', + }, + 'devextreme/events': { + defaultExtension: 'js', + main: 'index.js' + }, + 'devextreme/events/utils': { + defaultExtension: 'js', + main: 'index.js' + }, + 'rxjs': { main: 'index.js', defaultExtension: 'js' }, + 'rxjs/operators': { main: 'index.js', defaultExtension: 'js' }, + } +}); diff --git a/playground/angular/index.html b/playground/angular/index.html new file mode 100644 index 000000000000..ff5488eb409c --- /dev/null +++ b/playground/angular/index.html @@ -0,0 +1,32 @@ + + + + + DevExtreme Demo + + + + + + + + + + + + + + + + + + + +
+ Loading... +
+ + + diff --git a/playground/angular/tsconfig.json b/playground/angular/tsconfig.json new file mode 100644 index 000000000000..aed9b644f22b --- /dev/null +++ b/playground/angular/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "module": "esnext", + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2018", + "dom" + ] + }, + "include": [ + "app/**/*.ts" + ], + "angularCompilerOptions": { + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true + } + } diff --git a/playground/angular/tslint.json b/playground/angular/tslint.json new file mode 100644 index 000000000000..2f1da5abad49 --- /dev/null +++ b/playground/angular/tslint.json @@ -0,0 +1,83 @@ +{ + "extends": "tslint:recommended", + "rules": { + "array-type": false, + "arrow-parens": false, + "deprecation": { + "severity": "warning" + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "interface-name": false, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-consecutive-blank-lines": false, + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "object-literal-sort-keys": false, + "ordered-imports": false, + "quotemark": [ + true, + "single" + ], + "trailing-comma": false, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + }, + "rulesDirectory": [ + "codelyzer" + ] + } \ No newline at end of file diff --git a/playground/react/.gitignore b/playground/react/.gitignore new file mode 100644 index 000000000000..7912fc48c7d7 --- /dev/null +++ b/playground/react/.gitignore @@ -0,0 +1 @@ +/src/artifacts diff --git a/playground/react/README.md b/playground/react/README.md new file mode 100644 index 000000000000..f11f8ba1998f --- /dev/null +++ b/playground/react/README.md @@ -0,0 +1,31 @@ +# README + +## Run Example + +Install packages using the following command: + +```bash + npm install +``` + +After installation generate components and build scripts + +```bash + npm run build + npm run build:react +``` + +or + +```bash + npm run build:react:watch +``` + + +### Compiles and hot-reloads for development + +```bash +cd ./playground/react +npm install +npm start +``` diff --git a/playground/react/config/env.js b/playground/react/config/env.js new file mode 100644 index 000000000000..09ec03c5bd6a --- /dev/null +++ b/playground/react/config/env.js @@ -0,0 +1,101 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')]; + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +const dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv, +].filter(Boolean); + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + // We support configuring the sockjs pathname during development. + // These settings let a developer run multiple simultaneous projects. + // They are used as the connection `hostname`, `pathname` and `port` + // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` + // and `sockPort` options in webpack-dev-server. + WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, + WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, + WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, + } + ); + // Stringify all values so we can feed into webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/playground/react/config/getHttpsConfig.js b/playground/react/config/getHttpsConfig.js new file mode 100644 index 000000000000..013d493c1bbe --- /dev/null +++ b/playground/react/config/getHttpsConfig.js @@ -0,0 +1,66 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const chalk = require('react-dev-utils/chalk'); +const paths = require('./paths'); + +// Ensure the certificate and key provided are valid and if not +// throw an easy to debug error +function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { + let encrypted; + try { + // publicEncrypt will throw an error with an invalid cert + encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); + } catch (err) { + throw new Error( + `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` + ); + } + + try { + // privateDecrypt will throw an error with an invalid key + crypto.privateDecrypt(key, encrypted); + } catch (err) { + throw new Error( + `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ + err.message + }` + ); + } +} + +// Read file and throw an error if it doesn't exist +function readEnvFile(file, type) { + if (!fs.existsSync(file)) { + throw new Error( + `You specified ${chalk.cyan( + type + )} in your env, but the file "${chalk.yellow(file)}" can't be found.` + ); + } + return fs.readFileSync(file); +} + +// Get the https config +// Return cert files if provided in env, otherwise just true or false +function getHttpsConfig() { + const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; + const isHttps = HTTPS === 'true'; + + if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { + const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); + const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); + const config = { + cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), + key: readEnvFile(keyFile, 'SSL_KEY_FILE'), + }; + + validateKeyAndCerts({ ...config, keyFile, crtFile }); + return config; + } + return isHttps; +} + +module.exports = getHttpsConfig; diff --git a/playground/react/config/jest/cssTransform.js b/playground/react/config/jest/cssTransform.js new file mode 100644 index 000000000000..8f65114812a4 --- /dev/null +++ b/playground/react/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/playground/react/config/jest/fileTransform.js b/playground/react/config/jest/fileTransform.js new file mode 100644 index 000000000000..aab67618c38b --- /dev/null +++ b/playground/react/config/jest/fileTransform.js @@ -0,0 +1,40 @@ +'use strict'; + +const path = require('path'); +const camelcase = require('camelcase'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + // Based on how SVGR generates a component name: + // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 + const pascalCaseFilename = camelcase(path.parse(filename).name, { + pascalCase: true, + }); + const componentName = `Svg${pascalCaseFilename}`; + return `const React = require('react'); + module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: React.forwardRef(function ${componentName}(props, ref) { + return { + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: ref, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }; + }), + };`; + } + + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/playground/react/config/modules.js b/playground/react/config/modules.js new file mode 100644 index 000000000000..c8efd0dd0b33 --- /dev/null +++ b/playground/react/config/modules.js @@ -0,0 +1,141 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); +const chalk = require('react-dev-utils/chalk'); +const resolve = require('resolve'); + +/** + * Get additional module paths based on the baseUrl of a compilerOptions object. + * + * @param {Object} options + */ +function getAdditionalModulePaths(options = {}) { + const baseUrl = options.baseUrl; + + // We need to explicitly check for null and undefined (and not a falsy value) because + // TypeScript treats an empty string as `.`. + if (baseUrl == null) { + // If there's no baseUrl set we respect NODE_PATH + // Note that NODE_PATH is deprecated and will be removed + // in the next major release of create-react-app. + + const nodePath = process.env.NODE_PATH || ''; + return nodePath.split(path.delimiter).filter(Boolean); + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + // We don't need to do anything if `baseUrl` is set to `node_modules`. This is + // the default behavior. + if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { + return null; + } + + // Allow the user set the `baseUrl` to `appSrc`. + if (path.relative(paths.appSrc, baseUrlResolved) === '') { + return [paths.appSrc]; + } + + // If the path is equal to the root directory we ignore it here. + // We don't want to allow importing from the root directly as source files are + // not transpiled outside of `src`. We do allow importing them with the + // absolute path (e.g. `src/Components/Button.js`) but we set that up with + // an alias. + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return null; + } + + // Otherwise, throw an error. + throw new Error( + chalk.red.bold( + "Your project's `baseUrl` can only be set to `src` or `node_modules`." + + ' Create React App does not support other values at this time.' + ) + ); +} + +/** + * Get webpack aliases based on the baseUrl of a compilerOptions object. + * + * @param {*} options + */ +function getWebpackAliases(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return {}; + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return { + src: paths.appSrc, + }; + } +} + +/** + * Get jest aliases based on the baseUrl of a compilerOptions object. + * + * @param {*} options + */ +function getJestAliases(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return {}; + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return { + '^src/(.*)$': '/src/$1', + }; + } +} + +function getModules() { + // Check if TypeScript is setup + const hasTsConfig = fs.existsSync(paths.appTsConfig); + const hasJsConfig = fs.existsSync(paths.appJsConfig); + + if (hasTsConfig && hasJsConfig) { + throw new Error( + 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' + ); + } + + let config; + + // If there's a tsconfig.json we assume it's a + // TypeScript project and set up the config + // based on tsconfig.json + if (hasTsConfig) { + const ts = require(resolve.sync('typescript', { + basedir: paths.appNodeModules, + })); + config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; + // Otherwise we'll check if there is jsconfig.json + // for non TS projects. + } else if (hasJsConfig) { + config = require(paths.appJsConfig); + } + + config = config || {}; + const options = config.compilerOptions || {}; + + const additionalModulePaths = getAdditionalModulePaths(options); + + return { + additionalModulePaths: additionalModulePaths, + webpackAliases: getWebpackAliases(options), + jestAliases: getJestAliases(options), + hasTsConfig, + }; +} + +module.exports = getModules(); diff --git a/playground/react/config/paths.js b/playground/react/config/paths.js new file mode 100644 index 000000000000..b3fd764aecb3 --- /dev/null +++ b/playground/react/config/paths.js @@ -0,0 +1,72 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// webpack needs to know it to put the right + + + + diff --git a/playground/vue/src/assets/logo.png b/playground/vue/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d2503fc2a44b5053b0837ebea6e87a2d339a43 GIT binary patch literal 6849 zcmaKRcUV(fvo}bjDT-7nLI_nlK}sT_69H+`qzVWDA|yaU?}j417wLi^B1KB1SLsC& zL0ag7$U(XW5YR7p&Ux?sP$d4lvMt8C^+TcQu4F zQqv!UF!I+kw)c0jhd6+g6oCr9P?7)?!qX1ui*iL{p}sKCAGuJ{{W)0z1pLF|=>h}& zt(2Lr0Z`2ig8<5i%Zk}cO5Fm=LByqGWaS`oqChZdEFmc`0hSb#gg|Aap^{+WKOYcj zHjINK)KDG%&s?Mt4CL(T=?;~U@bU2x_mLKN!#GJuK_CzbNw5SMEJorG!}_5;?R>@1 zSl)jns3WlU7^J%=(hUtfmuUCU&C3%8B5C^f5>W2Cy8jW3#{Od{lF1}|?c61##3dzA zsPlFG;l_FzBK}8>|H_Ru_H#!_7$UH4UKo3lKOA}g1(R&|e@}GINYVzX?q=_WLZCgh z)L|eJMce`D0EIwgRaNETDsr+?vQknSGAi=7H00r`QnI%oQnFxm`G2umXso9l+8*&Q z7WqF|$p49js$mdzo^BXpH#gURy=UO;=IMrYc5?@+sR4y_?d*~0^YP7d+y0{}0)zBM zIKVM(DBvICK#~7N0a+PY6)7;u=dutmNqK3AlsrUU9U`d;msiucB_|8|2kY=(7XA;G zwDA8AR)VCA#JOkxm#6oHNS^YVuOU;8p$N)2{`;oF|rQ?B~K$%rHDxXs+_G zF5|-uqHZvSzq}L;5Kcy_P+x0${33}Ofb6+TX&=y;;PkEOpz%+_bCw_{<&~ zeLV|!bP%l1qxywfVr9Z9JI+++EO^x>ZuCK);=$VIG1`kxK8F2M8AdC$iOe3cj1fo(ce4l-9 z7*zKy3={MixvUk=enQE;ED~7tv%qh&3lR<0m??@w{ILF|e#QOyPkFYK!&Up7xWNtL zOW%1QMC<3o;G9_S1;NkPB6bqbCOjeztEc6TsBM<(q9((JKiH{01+Ud=uw9B@{;(JJ z-DxI2*{pMq`q1RQc;V8@gYAY44Z!%#W~M9pRxI(R?SJ7sy7em=Z5DbuDlr@*q|25V)($-f}9c#?D%dU^RS<(wz?{P zFFHtCab*!rl(~j@0(Nadvwg8q|4!}L^>d?0al6}Rrv9$0M#^&@zjbfJy_n!%mVHK4 z6pLRIQ^Uq~dnyy$`ay51Us6WaP%&O;@49m&{G3z7xV3dLtt1VTOMYl3UW~Rm{Eq4m zF?Zl_v;?7EFx1_+#WFUXxcK78IV)FO>42@cm@}2I%pVbZqQ}3;p;sDIm&knay03a^ zn$5}Q$G!@fTwD$e(x-~aWP0h+4NRz$KlnO_H2c< z(XX#lPuW_%H#Q+c&(nRyX1-IadKR-%$4FYC0fsCmL9ky3 zKpxyjd^JFR+vg2!=HWf}2Z?@Td`0EG`kU?{8zKrvtsm)|7>pPk9nu@2^z96aU2<#` z2QhvH5w&V;wER?mopu+nqu*n8p~(%QkwSs&*0eJwa zMXR05`OSFpfyRb!Y_+H@O%Y z0=K^y6B8Gcbl?SA)qMP3Z+=C(?8zL@=74R=EVnE?vY!1BQy2@q*RUgRx4yJ$k}MnL zs!?74QciNb-LcG*&o<9=DSL>1n}ZNd)w1z3-0Pd^4ED1{qd=9|!!N?xnXjM!EuylY z5=!H>&hSofh8V?Jofyd!h`xDI1fYAuV(sZwwN~{$a}MX^=+0TH*SFp$vyxmUv7C*W zv^3Gl0+eTFgBi3FVD;$nhcp)ka*4gSskYIqQ&+M}xP9yLAkWzBI^I%zR^l1e?bW_6 zIn{mo{dD=)9@V?s^fa55jh78rP*Ze<3`tRCN4*mpO$@7a^*2B*7N_|A(Ve2VB|)_o z$=#_=aBkhe(ifX}MLT()@5?OV+~7cXC3r!%{QJxriXo9I%*3q4KT4Xxzyd{ z9;_%=W%q!Vw$Z7F3lUnY+1HZ*lO;4;VR2+i4+D(m#01OYq|L_fbnT;KN<^dkkCwtd zF7n+O7KvAw8c`JUh6LmeIrk4`F3o|AagKSMK3))_5Cv~y2Bb2!Ibg9BO7Vkz?pAYX zoI=B}+$R22&IL`NCYUYjrdhwjnMx_v=-Qcx-jmtN>!Zqf|n1^SWrHy zK|MwJ?Z#^>)rfT5YSY{qjZ&`Fjd;^vv&gF-Yj6$9-Dy$<6zeP4s+78gS2|t%Z309b z0^fp~ue_}i`U9j!<|qF92_3oB09NqgAoehQ`)<)dSfKoJl_A6Ec#*Mx9Cpd-p#$Ez z={AM*r-bQs6*z$!*VA4|QE7bf@-4vb?Q+pPKLkY2{yKsw{&udv_2v8{Dbd zm~8VAv!G~s)`O3|Q6vFUV%8%+?ZSVUa(;fhPNg#vab@J*9XE4#D%)$UU-T5`fwjz! z6&gA^`OGu6aUk{l*h9eB?opVdrHK>Q@U>&JQ_2pR%}TyOXGq_6s56_`U(WoOaAb+K zXQr#6H}>a-GYs9^bGP2Y&hSP5gEtW+GVC4=wy0wQk=~%CSXj=GH6q z-T#s!BV`xZVxm{~jr_ezYRpqqIcXC=Oq`b{lu`Rt(IYr4B91hhVC?yg{ol4WUr3v9 zOAk2LG>CIECZ-WIs0$N}F#eoIUEtZudc7DPYIjzGqDLWk_A4#(LgacooD z2K4IWs@N`Bddm-{%oy}!k0^i6Yh)uJ1S*90>|bm3TOZxcV|ywHUb(+CeX-o1|LTZM zwU>dY3R&U)T(}5#Neh?-CWT~@{6Ke@sI)uSuzoah8COy)w)B)aslJmp`WUcjdia-0 zl2Y}&L~XfA`uYQboAJ1;J{XLhYjH){cObH3FDva+^8ioOQy%Z=xyjGLmWMrzfFoH; zEi3AG`_v+%)&lDJE;iJWJDI@-X9K5O)LD~j*PBe(wu+|%ar~C+LK1+-+lK=t# z+Xc+J7qp~5q=B~rD!x78)?1+KUIbYr^5rcl&tB-cTtj+e%{gpZZ4G~6r15+d|J(ky zjg@@UzMW0k9@S#W(1H{u;Nq(7llJbq;;4t$awM;l&(2s+$l!Ay9^Ge|34CVhr7|BG z?dAR83smef^frq9V(OH+a+ki#q&-7TkWfFM=5bsGbU(8mC;>QTCWL5ydz9s6k@?+V zcjiH`VI=59P-(-DWXZ~5DH>B^_H~;4$)KUhnmGo*G!Tq8^LjfUDO)lASN*=#AY_yS zqW9UX(VOCO&p@kHdUUgsBO0KhXxn1sprK5h8}+>IhX(nSXZKwlNsjk^M|RAaqmCZB zHBolOHYBas@&{PT=R+?d8pZu zUHfyucQ`(umXSW7o?HQ3H21M`ZJal+%*)SH1B1j6rxTlG3hx1IGJN^M7{$j(9V;MZ zRKybgVuxKo#XVM+?*yTy{W+XHaU5Jbt-UG33x{u(N-2wmw;zzPH&4DE103HV@ER86 z|FZEmQb|&1s5#`$4!Cm}&`^{(4V}OP$bk`}v6q6rm;P!H)W|2i^e{7lTk2W@jo_9q z*aw|U7#+g59Fv(5qI`#O-qPj#@_P>PC#I(GSp3DLv7x-dmYK=C7lPF8a)bxb=@)B1 zUZ`EqpXV2dR}B&r`uM}N(TS99ZT0UB%IN|0H%DcVO#T%L_chrgn#m6%x4KE*IMfjX zJ%4veCEqbXZ`H`F_+fELMC@wuy_ch%t*+Z+1I}wN#C+dRrf2X{1C8=yZ_%Pt6wL_~ zZ2NN-hXOT4P4n$QFO7yYHS-4wF1Xfr-meG9Pn;uK51?hfel`d38k{W)F*|gJLT2#T z<~>spMu4(mul-8Q3*pf=N4DcI)zzjqAgbE2eOT7~&f1W3VsdD44Ffe;3mJp-V@8UC z)|qnPc12o~$X-+U@L_lWqv-RtvB~%hLF($%Ew5w>^NR82qC_0FB z)=hP1-OEx?lLi#jnLzH}a;Nvr@JDO-zQWd}#k^an$Kwml;MrD&)sC5b`s0ZkVyPkb zt}-jOq^%_9>YZe7Y}PhW{a)c39G`kg(P4@kxjcYfgB4XOOcmezdUI7j-!gs7oAo2o zx(Ph{G+YZ`a%~kzK!HTAA5NXE-7vOFRr5oqY$rH>WI6SFvWmahFav!CfRMM3%8J&c z*p+%|-fNS_@QrFr(at!JY9jCg9F-%5{nb5Bo~z@Y9m&SHYV`49GAJjA5h~h4(G!Se zZmK{Bo7ivCfvl}@A-ptkFGcWXAzj3xfl{evi-OG(TaCn1FAHxRc{}B|x+Ua1D=I6M z!C^ZIvK6aS_c&(=OQDZfm>O`Nxsw{ta&yiYPA~@e#c%N>>#rq)k6Aru-qD4(D^v)y z*>Rs;YUbD1S8^D(ps6Jbj0K3wJw>L4m)0e(6Pee3Y?gy9i0^bZO?$*sv+xKV?WBlh zAp*;v6w!a8;A7sLB*g-^<$Z4L7|5jXxxP1}hQZ<55f9<^KJ>^mKlWSGaLcO0=$jem zWyZkRwe~u{{tU63DlCaS9$Y4CP4f?+wwa(&1ou)b>72ydrFvm`Rj-0`kBJgK@nd(*Eh!(NC{F-@=FnF&Y!q`7){YsLLHf0_B6aHc# z>WIuHTyJwIH{BJ4)2RtEauC7Yq7Cytc|S)4^*t8Va3HR zg=~sN^tp9re@w=GTx$;zOWMjcg-7X3Wk^N$n;&Kf1RgVG2}2L-(0o)54C509C&77i zrjSi{X*WV=%C17((N^6R4Ya*4#6s_L99RtQ>m(%#nQ#wrRC8Y%yxkH;d!MdY+Tw@r zjpSnK`;C-U{ATcgaxoEpP0Gf+tx);buOMlK=01D|J+ROu37qc*rD(w`#O=3*O*w9?biwNoq3WN1`&Wp8TvKj3C z3HR9ssH7a&Vr<6waJrU zdLg!ieYz%U^bmpn%;(V%%ugMk92&?_XX1K@mwnVSE6!&%P%Wdi7_h`CpScvspMx?N zQUR>oadnG17#hNc$pkTp+9lW+MBKHRZ~74XWUryd)4yd zj98$%XmIL4(9OnoeO5Fnyn&fpQ9b0h4e6EHHw*l68j;>(ya`g^S&y2{O8U>1*>4zR zq*WSI_2o$CHQ?x0!wl9bpx|Cm2+kFMR)oMud1%n2=qn5nE&t@Fgr#=Zv2?}wtEz^T z9rrj=?IH*qI5{G@Rn&}^Z{+TW}mQeb9=8b<_a`&Cm#n%n~ zU47MvCBsdXFB1+adOO)03+nczfWa#vwk#r{o{dF)QWya9v2nv43Zp3%Ps}($lA02*_g25t;|T{A5snSY?3A zrRQ~(Ygh_ebltHo1VCbJb*eOAr;4cnlXLvI>*$-#AVsGg6B1r7@;g^L zFlJ_th0vxO7;-opU@WAFe;<}?!2q?RBrFK5U{*ai@NLKZ^};Ul}beukveh?TQn;$%9=R+DX07m82gP$=}Uo_%&ngV`}Hyv8g{u z3SWzTGV|cwQuFIs7ZDOqO_fGf8Q`8MwL}eUp>q?4eqCmOTcwQuXtQckPy|4F1on8l zP*h>d+cH#XQf|+6c|S{7SF(Lg>bR~l(0uY?O{OEVlaxa5@e%T&xju=o1`=OD#qc16 zSvyH*my(dcp6~VqR;o(#@m44Lug@~_qw+HA=mS#Z^4reBy8iV?H~I;{LQWk3aKK8$bLRyt$g?- h(App), +}).$mount('#app'); +/* eslint-enable */ diff --git a/playground/vue/tsconfig.json b/playground/vue/tsconfig.json new file mode 100644 index 000000000000..08c824a2e6b1 --- /dev/null +++ b/playground/vue/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "allowJs": true + } +} diff --git a/shippable.yml b/shippable.yml index 0bb2ab8e7161..62f351bc62bd 100644 --- a/shippable.yml +++ b/shippable.yml @@ -20,6 +20,7 @@ env: - TARGET=test CONSTEL=ui.scheduler TZ='Japan' - TARGET=test CONSTEL=ui.scheduler TZ='Australia/ACT' - TARGET=test CONSTEL=viz + - TARGET=test CONSTEL=renovation - TARGET=test PERF=true JQUERY=true NO_HEADLESS=true - TARGET=test MOBILE_UA=ios9 CONSTEL=ui - TARGET=test MOBILE_UA=ios9 CONSTEL=ui.editors NO_HEADLESS=true @@ -38,11 +39,13 @@ env: - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=ui.grid - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=ui.scheduler - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=viz + - TARGET=test BROWSER=firefox JQUERY=true CONSTEL=renovation - TARGET=test_functional COMPONENT=dataGrid QUARANTINE_MODE=true - TARGET=test_functional COMPONENT=scheduler QUARANTINE_MODE=true - TARGET=test_functional COMPONENT=editors - TARGET=test_functional COMPONENT=navigation - TARGET=test_themebuilder + - TARGET=test_jest - TARGET=test_scss build: diff --git a/styles/widgets/common/button.less b/styles/widgets/common/button.less index dbce30d39b67..630ea389ac5d 100644 --- a/styles/widgets/common/button.less +++ b/styles/widgets/common/button.less @@ -55,13 +55,7 @@ } .dx-button-submit-input { - padding: 0; - margin: 0; - border: 0; - height: 0; - width: 0; - font-size: 0; - opacity: 0; + display: none; } .dx-state-disabled { diff --git a/testing/.eslintrc b/testing/.eslintrc deleted file mode 100644 index 07d11e3f3122..000000000000 --- a/testing/.eslintrc +++ /dev/null @@ -1,28 +0,0 @@ -{ - "env": { - "qunit": true, - "browser": true - }, - "globals": { - "define": true, - "Promise": true, - "SystemJS": true, - "DevExpress": true, - "sinon": true - }, - "plugins": [ - "qunit" - ], - "extends": [ - "plugin:qunit/recommended", - "plugin:qunit/two" - ], - "rules": { - "qunit/no-arrow-tests": "error", - "qunit/no-commented-tests": "error", - "qunit/no-identical-names": "warn", - "qunit/no-global-module-test": "off", - "qunit/require-expect": "off", - "qunit/resolve-async": "off" - } -} diff --git a/testing/.gitignore b/testing/.gitignore index e3820ee3572e..d644ace2da4d 100644 --- a/testing/.gitignore +++ b/testing/.gitignore @@ -1 +1,2 @@ /Results.xml +/jest/code_coverage diff --git a/testing/functional/model/dataGrid.ts b/testing/functional/model/dataGrid.ts index 9c2c86b43e8d..44ecb51be4a5 100644 --- a/testing/functional/model/dataGrid.ts +++ b/testing/functional/model/dataGrid.ts @@ -1,5 +1,5 @@ -import { ClientFunction, Selector } from "testcafe"; -import Widget from "./internal/widget"; +import { ClientFunction, Selector } from 'testcafe'; +import Widget from './internal/widget'; const CLASS = { headers: 'headers', @@ -137,7 +137,7 @@ class HeaderCell extends DxElement { } getFilterIcon(): Selector { - return this.element.find(`.dx-column-indicators > .dx-header-filter`); + return this.element.find('.dx-column-indicators > .dx-header-filter'); } } @@ -247,7 +247,7 @@ class GroupRow extends DxElement { this.widgetName = widgetName; this.isFocusedRow = this.element.hasClass(CLASS.focusedRow); this.isFocused = this.element.hasClass(CLASS.focused); - this.isExpanded = this.element.find(`.${CLASS.commandExpand} .${addWidgetPrefix(this.widgetName, CLASS.groupExpanded)}`).exists + this.isExpanded = this.element.find(`.${CLASS.commandExpand} .${addWidgetPrefix(this.widgetName, CLASS.groupExpanded)}`).exists; } getCell(index: number): DataCell { @@ -315,7 +315,7 @@ export class EditForm extends DxElement { } getItem(id): Selector { - return this.form.find(`.${CLASS.textEditorInput}[id*=_${id}]`) + return this.form.find(`.${CLASS.textEditorInput}[id*=_${id}]`); } getInvalids(): Selector { @@ -329,7 +329,7 @@ export default class DataGrid extends Widget { name: string; - constructor(id: string, name='dxDataGrid') { + constructor(id: string, name = 'dxDataGrid') { super(id); this.name = name; @@ -339,7 +339,7 @@ export default class DataGrid extends Widget { this.getGridInstance = ClientFunction( () => $(grid())[`${name}`]('instance'), - { dependencies: { grid, name }} + { dependencies: { grid, name } } ); } @@ -404,7 +404,7 @@ export default class DataGrid extends Widget { getEditForm(): EditForm { const editFormRowClass = this.addWidgetPrefix(CLASS.editFormRow); - const element = this.element ? this.element.find(`.${editFormRowClass}`) : Selector(`.${editFormRowClass}`); + const element = this.element ? this.element.find(`.${editFormRowClass}`) : Selector(`.${editFormRowClass}`); const buttons = element.find(`.${this.addWidgetPrefix(CLASS.formButtonsContainer)} .${CLASS.button}`); return new EditForm(element, buttons); @@ -478,7 +478,7 @@ export default class DataGrid extends Widget { const getGridInstance: any = this.getGridInstance; return ClientFunction(() => { const dataGrid = getGridInstance(); - const result = dataGrid.getController('validating').getCellValidationResult({ rowKey : dataGrid.getKeyByRowIndex(rowIndex), columnIndex }); + const result = dataGrid.getController('validating').getCellValidationResult({ rowKey: dataGrid.getKeyByRowIndex(rowIndex), columnIndex }); return result ? result.status : null; }, { dependencies: { getGridInstance, rowIndex, columnIndex } } )(); diff --git a/testing/functional/tests/dataGrid/keyboardNavigation.ts b/testing/functional/tests/dataGrid/keyboardNavigation.ts index b9a8f5bbc95c..d57d8d26df8f 100644 --- a/testing/functional/tests/dataGrid/keyboardNavigation.ts +++ b/testing/functional/tests/dataGrid/keyboardNavigation.ts @@ -7,7 +7,7 @@ fixture `Keyboard Navigation` .page(url(__dirname, '../container.html')); test('Cell should not highlighted after editing another cell when startEditAction: dblClick and editing.mode: batch', async t => { -const dataGrid = new DataGrid('#container'); + const dataGrid = new DataGrid('#container'); await t .expect(dataGrid.getDataCell(0, 1).isFocused).notOk() @@ -34,7 +34,7 @@ const dataGrid = new DataGrid('#container'); { name: 'Alex', phone: '555555', room: 1 }, { name: 'Dan', phone: '553355', room: 2 } ], - columns:['name','phone','room'], + columns: ['name', 'phone', 'room'], editing: { mode: 'batch', allowUpdating: true, @@ -71,7 +71,7 @@ test('Cell should highlighted after editing another cell when startEditAction is { name: 'Alex', phone: '555555', room: 1 }, { name: 'Dan', phone: '553355', room: 2 } ], - columns:['name','phone','room'], + columns: ['name', 'phone', 'room'], editing: { mode: 'cell', allowUpdating: true, @@ -117,10 +117,10 @@ test('Cell should be focused after Enter key press if enterKeyDirection is "none })); test('TextArea should be focused on editing start', async t => { - const dataGrid = new DataGrid('#container'), - commandCell = dataGrid.getDataCell(1, 3).element, - dataCell = dataGrid.getDataCell(1, 0), - getTextArea = () => dataCell.element.find('.text-area-1'); + const dataGrid = new DataGrid('#container'); + const commandCell = dataGrid.getDataCell(1, 3).element; + const dataCell = dataGrid.getDataCell(1, 0); + const getTextArea = () => dataCell.element.find('.text-area-1'); await t // act, assert @@ -366,7 +366,7 @@ test('Navigation through views using Tab, Shift+Tab', async t => { .pressKey('shift+tab') .expect(dataGrid.getDataRow(0).getCommandCell(0).getSelectCheckBox().focused).notOk() .expect(dataGrid.getDataRow(0).getCommandCell(0).element.focused).ok() - .expect(dataGrid.getDataRow(0).getCommandCell(0).isFocused).ok() + .expect(dataGrid.getDataRow(0).getCommandCell(0).isFocused).ok(); // filter row await t @@ -414,7 +414,7 @@ test('Navigation through views using Tab, Shift+Tab', async t => { .pressKey('shift+tab') .expect(Selector('BODY').focused).ok(); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { width: 300, dataSource: [ @@ -454,7 +454,7 @@ test('Select - The first command cell should be focused using Tab (T884646)', as const headerRow = dataGrid.getHeaders().getHeaderRow(0); const dataRow = dataGrid.getDataRow(0); - //header row + // header row await t .pressKey('tab') .expect(headerRow.getCommandCell(0).element.focused).notOk() @@ -463,7 +463,7 @@ test('Select - The first command cell should be focused using Tab (T884646)', as .pressKey('tab') .expect(headerRow.getHeaderCell(1).element.focused).ok(); - //data row + // data row await t .pressKey('tab') .expect(dataRow.getCommandCell(0).isFocused).ok() @@ -488,7 +488,7 @@ test('Select - The first command cell should be focused using Tab (T884646)', as .expect(dataRow.getCommandCell(0).getSelectCheckBox().focused).notOk(); - //header row + // header row await t .pressKey('shift+tab') .expect(headerRow.getHeaderCell(1).element.focused).ok() @@ -502,7 +502,7 @@ test('Select - The first command cell should be focused using Tab (T884646)', as .pressKey('shift+tab') .expect(Selector('BODY').focused).ok(); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { width: 300, dataSource: [ @@ -521,12 +521,12 @@ test('Edit - The first command cell should be focused using Tab (T884646)', asyn const headerRow = dataGrid.getHeaders().getHeaderRow(0); const dataRow = dataGrid.getDataRow(0); - //header row + // header row await t .pressKey('tab') .expect(headerRow.getHeaderCell(1).element.focused).ok(); - //data row + // data row await t .pressKey('tab') .expect(dataRow.getCommandCell(0).isFocused).ok() @@ -551,17 +551,17 @@ test('Edit - The first command cell should be focused using Tab (T884646)', asyn .expect(dataRow.getCommandCell(0).getButton(0).focused).notOk(); - //header row + // header row await t .pressKey('shift+tab') - .expect(headerRow.getHeaderCell(1).element.focused).ok() + .expect(headerRow.getHeaderCell(1).element.focused).ok(); // focus BODY await t .pressKey('shift+tab') .expect(Selector('BODY').focused).ok(); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { width: 300, dataSource: [ @@ -584,12 +584,12 @@ test('Detail - The first command cell should be focused using Tab (T884646)', as const headerRow = dataGrid.getHeaders().getHeaderRow(0); const dataRow = dataGrid.getDataRow(0); - //header row + // header row await t .pressKey('tab') .expect(headerRow.getHeaderCell(1).element.focused).ok(); - //data row + // data row await t .pressKey('tab') .expect(dataRow.getCommandCell(0).isFocused).ok() @@ -603,17 +603,17 @@ test('Detail - The first command cell should be focused using Tab (T884646)', as .expect(dataRow.getCommandCell(0).isFocused).ok() .expect(dataRow.getCommandCell(0).element.focused).ok(); - //header row + // header row await t .pressKey('shift+tab') - .expect(headerRow.getHeaderCell(1).element.focused).ok() + .expect(headerRow.getHeaderCell(1).element.focused).ok(); // focus BODY await t .pressKey('shift+tab') .expect(Selector('BODY').focused).ok(); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { width: 300, dataSource: [ @@ -631,7 +631,7 @@ test('Adaptive - Hidden cells should not be focused using Tab (T887014)', async const headerRow = dataGrid.getHeaders().getHeaderRow(0); const dataRow = dataGrid.getDataRow(0); - //header row + // header row await t .pressKey('tab') .expect(headerRow.getHeaderCell(1).element.focused).ok() @@ -643,7 +643,7 @@ test('Adaptive - Hidden cells should not be focused using Tab (T887014)', async .expect(headerRow.getHeaderCell(3).element.focused).ok() .expect(headerRow.getHeaderCell(3).element.hasAttribute('tabindex')).ok(); - //data row + // data row await t .pressKey('tab') .expect(dataRow.getCommandCell(0).isFocused).ok() @@ -669,7 +669,7 @@ test('Adaptive - Hidden cells should not be focused using Tab (T887014)', async .expect(dataRow.getCommandCell(0).isFocused).ok() .expect(dataRow.getCommandCell(0).element.focused).ok(); - //header row + // header row await t .pressKey('shift+tab') .expect(headerRow.getHeaderCell(3).element.focused).ok() @@ -679,14 +679,14 @@ test('Adaptive - Hidden cells should not be focused using Tab (T887014)', async .expect(headerRow.getHeaderCell(2).isHidden).ok() .expect(headerRow.getHeaderCell(2).element.hasAttribute('tabindex')).notOk('the third header cell does not have tabindex') .expect(headerRow.getHeaderCell(1).element.focused).ok() - .expect(headerRow.getHeaderCell(1).element.hasAttribute('tabindex')).ok() + .expect(headerRow.getHeaderCell(1).element.hasAttribute('tabindex')).ok(); // focus BODY await t .pressKey('shift+tab') .expect(Selector('BODY').focused).ok(); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { keyExpr: 'name', dataSource: [ @@ -753,7 +753,7 @@ test('Select views by Ctrl+Up, Ctrl+Down keys', async t => { .pressKey('ctrl+up') .expect(headers.hasFocusedState).ok('headers has focused state') .expect(headerRow.getHeaderCell(0).element.focused).ok('focused header cell[0, 0]'); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { width: 300, dataSource: [ @@ -795,7 +795,7 @@ test('DataGrid - Scroll bars should not appear when updating edge cell focus ove .pressKey('tab') .expect(dataGrid.getDataCell(1, 0).isFocused).ok() .expect(dataGrid.getScrollbarWidth(false)).eql(0); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { height: 150, width: 200, @@ -831,7 +831,7 @@ test('Tab key on the focused group row should be handled by default behavior (T8 .pressKey('tab') .expect(groupRow.hasHiddenFocusState).notOk() .expect(pagerPage0.element.focused).ok(); -}).before(async () => { +}).before(async() => { await createWidget('dxDataGrid', { width: 400, dataSource: [ @@ -886,8 +886,8 @@ test('Row should not be focused by "focusedRowIndex" after change "pageIndex" by } })); -test("Cell should be highlighted after editing another cell when startEditAction is 'dblClick' and 'batch' edit mode if isHighlighted is set to true in onFocusedCellChanging (T836391)", async t => { - const dataGrid = new DataGrid("#container"); +test('Cell should be highlighted after editing another cell when startEditAction is \'dblClick\' and \'batch\' edit mode if isHighlighted is set to true in onFocusedCellChanging (T836391)', async t => { + const dataGrid = new DataGrid('#container'); const cell0 = dataGrid.getDataCell(0, 0); const cell1 = dataGrid.getDataCell(0, 1); @@ -903,12 +903,12 @@ test("Cell should be highlighted after editing another cell when startEditAction .expect(cell1.isFocused).ok() .expect(cell0.isFocused).notOk() .expect(cell0.isEditCell).notOk(); -}).before(() => createWidget("dxDataGrid", { +}).before(() => createWidget('dxDataGrid', { dataSource: [ { name: 'Alex', phone: '555555', room: 1 }, { name: 'Dan', phone: '553355', room: 2 } ], - columns:['name','phone','room'], + columns: ['name', 'phone', 'room'], editing: { mode: 'batch', allowUpdating: true, @@ -926,7 +926,7 @@ test('Previous navigation elements should not have "tabindex" if navigation acti await t .click(cell.element) .expect(cell.element.focused).ok(`cell[${rowIndex}, ${colIndex}] is focused`) - .expect(cell.element.getAttribute('tabindex')).eql('111', `cell[${rowIndex}, ${colIndex}] has tabindex`) + .expect(cell.element.getAttribute('tabindex')).eql('111', `cell[${rowIndex}, ${colIndex}] has tabindex`); } } }).before(() => createWidget('dxDataGrid', { @@ -950,7 +950,7 @@ test('Previous navigation elements should not have "tabindex" if navigation acti await t .expect(cell.element.focused).ok(`cell[${rowIndex}, ${colIndex}] is focused`) - .expect(cell.element.getAttribute('tabindex')).eql('111', `cell[${rowIndex}, ${colIndex}] has tabindex`) + .expect(cell.element.getAttribute('tabindex')).eql('111', `cell[${rowIndex}, ${colIndex}] has tabindex`); await t.pressKey('tab'); } @@ -977,13 +977,13 @@ test('The first group row should be expanded when the Enter key is pressed (T869 .pressKey('enter') - .expect(firstGroupRow.isExpanded).ok() + .expect(firstGroupRow.isExpanded).ok(); }).before(() => createWidget('dxDataGrid', { dataSource: [ { name: 'Alex', phone: '555555' } ], - columns:[{ + columns: [{ dataField: 'name', groupIndex: 0 }, 'phone'], @@ -991,4 +991,3 @@ test('The first group row should be expanded when the Enter key is pressed (T869 autoExpandAll: false } })); - diff --git a/testing/helpers/.eslintrc b/testing/helpers/.eslintrc new file mode 100644 index 000000000000..50600f0bfd79 --- /dev/null +++ b/testing/helpers/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "devextreme/qunit" + ] +} diff --git a/testing/helpers/qunitExtensions.js b/testing/helpers/qunitExtensions.js index 33fd2d49e5ec..f87330cc3f18 100644 --- a/testing/helpers/qunitExtensions.js +++ b/testing/helpers/qunitExtensions.js @@ -424,6 +424,9 @@ return true; } } + + if(callback.match(/function\(\)\{clearTimeout\(\w+\),cancelAnimationFrame\(\w+\),setTimeout\(\w+\)\}/)) return true; // NOTE: Preact hooks + if(callback.match(/\.__H\.\w+\.forEach\(/)) return true; // NOTE: Preact hooks }); const logTestFailure = function(timerInfo) { diff --git a/testing/jest/.eslintrc b/testing/jest/.eslintrc new file mode 100644 index 000000000000..72d208766563 --- /dev/null +++ b/testing/jest/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "devextreme/jest" + ], + "settings": { + "react": { "pragma": "h" } + } +} diff --git a/testing/jest/button.tests.tsx b/testing/jest/button.tests.tsx new file mode 100644 index 000000000000..57ef06d24fb0 --- /dev/null +++ b/testing/jest/button.tests.tsx @@ -0,0 +1,687 @@ + +import { h, createRef } from 'preact'; +import { mount, ReactWrapper } from 'enzyme'; +import { JSXInternal } from 'preact/src/jsx'; +import devices from '../../js/core/devices'; +import themes from '../../js/ui/themes'; +import { + clear as clearEventHandlers, + defaultEvent, + emit, + emitKeyboard, + getEventHandlers, + fakeClickEvent, + EVENT, + KEY, +} from './utils/events-mock'; +import Button, { defaultOptions } from '../../js/renovation/button.p'; +import type ButtonRef from '../../js/renovation/button.p'; +import Icon from '../../js/renovation/icon.p'; +import Widget from '../../js/renovation/widget.p'; +import type { WidgetProps } from '../../js/renovation/widget'; +import type { ButtonProps } from '../../js/renovation/button'; + +type Mock = jest.Mock; + +jest.mock('../../js/core/devices', () => { + const actualDevices = require.requireActual('../../js/core/devices'); + const isSimulator = actualDevices.isSimulator.bind(actualDevices); + const real = actualDevices.real.bind(actualDevices); + + actualDevices.isSimulator = jest.fn(isSimulator); + actualDevices.real = jest.fn(real); + + return actualDevices; +}); + +jest.mock('../../js/ui/themes', () => ({ + ...require.requireActual('../../js/ui/themes'), + current: jest.fn(() => 'generic'), +})); + +describe('Button', () => { + const render = (props = {}): ReactWrapper => mount(