Эх сурвалжийг харах

stepVal is set as optional.

Ievgen Naida 2 жил өмнө
parent
commit
56bccf115f

+ 2 - 1
.eslintignore

@@ -9,4 +9,5 @@ tests/src
 ./tests/tests/
 tests/tests/
 tests/*.js
-tests/*.js.map
+tests/*.js.map
+karma.conf.js

+ 15 - 16
.eslintrc.js

@@ -1,16 +1,15 @@
-module.exports = {
-    parser: "@typescript-eslint/parser", // Specifies the ESLint parser
-    parserOptions: {
-        ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
-        sourceType: "module" // Allows for the use of imports
-    },
-    extends: [
-        "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
-        "plugin:prettier/recommended" 
-
-    ],
-    rules: {
-        // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
-        // e.g. "@typescript-eslint/explicit-function-return-type": "off",
-    }
-};
+module.exports = {
+  parser: '@typescript-eslint/parser', // Specifies the ESLint parser
+  parserOptions: {
+    ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
+    sourceType: 'module', // Allows for the use of imports
+  },
+  extends: [
+    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
+    'plugin:prettier/recommended',
+  ],
+  rules: {
+    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
+    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
+  },
+};

+ 6 - 6
.prettierrc.js

@@ -1,8 +1,8 @@
 module.exports = {
-	semi: true,
-	trailingComma: 'all',
-	singleQuote: true,
-	printWidth: 200,
-	tabWidth: 2,
-	useTabs: false,
+  semi: true,
+  trailingComma: 'all',
+  singleQuote: true,
+  printWidth: 200,
+  tabWidth: 2,
+  useTabs: false,
 };

+ 16 - 9
README.md

@@ -131,7 +131,9 @@ See the [live demo](https://ievgennaida.github.io/animation-timeline-control/)
 ## Model
 
 Keyframes model is used to pass keyframes and rows to be visualized.
-Component is not changing passed model and it's used only for the visual purpose. It also means that any attached metadata can be passed and it will be preserved (Use case: you can attach additional data for each keyframe).
+Component is using passed model for the visualization purpose and has no method to manage tracks or keyframes.
+It also means that any attached metadata can be passed and it will be preserved
+(Use case: you can attach additional data for each keyframe).
 
 Read only and defined by the interfaces:
 
@@ -209,13 +211,11 @@ Styles can be applied on a few levels:
 
 Styles are applied by a global settings and can be overridden by a row or keyframe style.
 
-## Debug component
+## Changes
 
-VsCode configuration is included to the configuration,
-so you can navigate to the debug window and click 'Launch Debug File'.
-Put breakpoint in any typescript file and trigger function from the browser.
+## 2.2.1
 
-## Changes
+ TypeScript fixes, updated build packages.
 
 ## > 2.0
 
@@ -244,7 +244,16 @@ Run next command to pack JavaScript as a bundle:
 
 ### Debug
 
-VSCode is used as IDE.
+VSCode is used as IDE and configuration is included to the project sources.
+
+To debug project you should run command once files are changed:
+
+```cmd
+npm run build
+```
+
+Then navigate to the debug window and click 'Launch Debug File'.
+Put breakpoint in any typescript file and trigger function from the browser.
 
 Recommended extensions:
 
@@ -252,8 +261,6 @@ Recommended extensions:
 - ESLint
 - esbenp.prettier-vscode
 
-Click 'debug start' and ensure that unminified version of the file is used.
-
 ### Dev Dependencies
 
 Component has no production dependencies when built.

+ 1 - 1
cspell.json

@@ -1,5 +1,5 @@
 {
-  "version": "0.1",
+  "version": "0.2",
   "words": [
     "NESW",
     "NWSE",

+ 25 - 25
karma.conf.js

@@ -1,25 +1,25 @@
-// Karma is a tool to start server and test component in the real or headless browser.
-const {createDefaultConfig} = require('@open-wc/testing-karma');
-const merge = require('deepmerge');
-
-module.exports = config => {
-    config.set(
-        merge(createDefaultConfig(config), {
-            frameworks: ['mocha', 'chai'],
-            client: {
-                mocha: {ui: 'bdd'}
-            },
-            files: [
-                {
-                    pattern: config.grep ? config.grep : 'test/**/*.test.js',
-                    type: 'module'
-                },
-            ],
-            esm: {
-                // if you are using 'bare module imports' you will need this option
-                nodeResolve: true,
-            },
-        }),
-    );
-    return config;
-}
+// Karma is a tool to start server and test component in the real or headless browser.
+const { createDefaultConfig } = require('@open-wc/testing-karma');
+const merge = require('deepmerge');
+
+module.exports = (config) => {
+  config.set(
+    merge(createDefaultConfig(config), {
+      frameworks: ['mocha', 'chai'],
+      client: {
+        mocha: { ui: 'bdd' },
+      },
+      files: [
+        {
+          pattern: config.grep ? config.grep : 'test/**/*.test.js',
+          type: 'module',
+        },
+      ],
+      esm: {
+        // if you are using 'bare module imports' you will need this option
+        nodeResolve: true,
+      },
+    }),
+  );
+  return config;
+};

+ 83 - 7
lib/animation-timeline.js

@@ -49,7 +49,28 @@ __webpack_require__.r(__webpack_exports__);
 
 // EXPORTS
 __webpack_require__.d(__webpack_exports__, {
-  "Timeline": () => (/* binding */ Timeline)
+  "Timeline": () => (/* reexport */ Timeline),
+  "TimelineCapShape": () => (/* reexport */ TimelineCapShape),
+  "TimelineClickEvent": () => (/* reexport */ TimelineClickEvent),
+  "TimelineCursorType": () => (/* reexport */ TimelineCursorType),
+  "TimelineDragEvent": () => (/* reexport */ TimelineDragEvent),
+  "TimelineElementType": () => (/* reexport */ TimelineElementType),
+  "TimelineEventSource": () => (/* reexport */ TimelineEventSource),
+  "TimelineEvents": () => (/* reexport */ TimelineEvents),
+  "TimelineEventsEmitter": () => (/* reexport */ TimelineEventsEmitter),
+  "TimelineInteractionMode": () => (/* reexport */ TimelineInteractionMode),
+  "TimelineKeyframeChangedEvent": () => (/* reexport */ TimelineKeyframeChangedEvent),
+  "TimelineKeyframeShape": () => (/* reexport */ TimelineKeyframeShape),
+  "TimelineSelectedEvent": () => (/* reexport */ TimelineSelectedEvent),
+  "TimelineSelectionMode": () => (/* reexport */ TimelineSelectionMode),
+  "TimelineStyleUtils": () => (/* reexport */ TimelineStyleUtils),
+  "TimelineTimeChangedEvent": () => (/* reexport */ TimelineTimeChangedEvent),
+  "TimelineUtils": () => (/* reexport */ TimelineUtils),
+  "defaultTimelineConsts": () => (/* reexport */ defaultTimelineConsts),
+  "defaultTimelineKeyframeStyle": () => (/* reexport */ defaultTimelineKeyframeStyle),
+  "defaultTimelineOptions": () => (/* reexport */ defaultTimelineOptions),
+  "defaultTimelineRowStyle": () => (/* reexport */ defaultTimelineRowStyle),
+  "defaultTimelineStyle": () => (/* reexport */ defaultTimelineStyle)
 });
 
 ;// CONCATENATED MODULE: ./src/timelineEventsEmitter.ts
@@ -994,7 +1015,7 @@ var Timeline = /*#__PURE__*/function (_TimelineEventsEmitte) {
    */
 
   /**
-   * Dynamically generated event.
+   * Dynamically generated canvas inside of the container.
    */
 
   /**
@@ -2156,7 +2177,7 @@ var Timeline = /*#__PURE__*/function (_TimelineEventsEmitte) {
         min = 0;
       }
       min *= this._currentZoom || 1;
-      var steps = this._options.stepVal * this._currentZoom || 1;
+      var steps = (this._options.stepVal || 0) * this._currentZoom || 1;
       var val = min + px / this._options.stepPx * steps;
       return val;
     }
@@ -2191,7 +2212,7 @@ var Timeline = /*#__PURE__*/function (_TimelineEventsEmitte) {
         min = 0;
       }
       min *= this._currentZoom || 1;
-      var steps = this._options.stepVal * this._currentZoom || 1;
+      var steps = (this._options.stepVal || 0) * this._currentZoom || 1;
       return (-min + val) * (this._options.stepPx / steps);
     }
 
@@ -2945,7 +2966,7 @@ var Timeline = /*#__PURE__*/function (_TimelineEventsEmitte) {
     value: function _setOptions(toSet) {
       this._options = this._mergeOptions(toSet);
       // Normalize and validate spans per value.
-      this._options.snapStep = TimelineUtils.keepInBounds(this._options.snapStep, 0, this._options.stepVal);
+      this._options.snapStep = TimelineUtils.keepInBounds(this._options.snapStep, 0, this._options.stepVal || 0);
       this._currentZoom = this._setZoom(this._options.zoom, this._options.zoomMin, this._options.zoomMax);
       this._options.min = TimelineUtils.isNumber(this._options.min) ? this._options.min : 0;
       this._options.max = TimelineUtils.isNumber(this._options.max) ? this._options.max : Number.MAX_VALUE;
@@ -3018,8 +3039,8 @@ var Timeline = /*#__PURE__*/function (_TimelineEventsEmitte) {
   }, {
     key: "_updateCanvasScale",
     value: function _updateCanvasScale() {
-      if (!this._scrollContainer || !this._ctx) {
-        console.log('control should be initialized first');
+      if (!this._scrollContainer || !this._container || !this._ctx) {
+        console.log('Component should be initialized first');
         return;
       }
       var changed = false;
@@ -3438,6 +3459,61 @@ var Timeline = /*#__PURE__*/function (_TimelineEventsEmitte) {
   }]);
   return Timeline;
 }(TimelineEventsEmitter);
+;// CONCATENATED MODULE: ./src/animation-timeline.ts
+// bundle entry point
+
+
+
+
+
+
+
+
+
+// @public styles
+
+
+
+
+
+
+
+
+// @private helper containers.
+
+
+
+
+
+
+
+
+// @private virtual model
+
+
+
+
+
+
+// @public events
+
+
+
+
+
+
+
+// @public enums
+
+
+
+
+
+
+
+
+// @private defaults are exposed:
+
 /******/ 	return __webpack_exports__;
 /******/ })()
 ;

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
lib/animation-timeline.js.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
lib/animation-timeline.min.js


+ 1 - 1
lib/settings/timelineOptions.d.ts

@@ -21,7 +21,7 @@ export interface TimelineOptions extends TimelineRanged {
     /**
      * Number of points that should fit into the one stepPx.
      */
-    stepVal: number;
+    stepVal?: number;
     stepSmallPx?: number;
     /**
      * Snap step in units. from 0 to stepVal

+ 2 - 2
lib/timeline.d.ts

@@ -8,7 +8,7 @@ import { TimelineElement } from './utils/timelineElement';
 import { TimelineCutBoundsRectResults } from './utils/timelineCutBoundsRectResults';
 import { TimelineSelectionResults } from './utils/timelineSelectionResults';
 import { TimelineMouseData } from './utils/timelineMouseData';
-import { TimelineElementDragState } from './utils/TimelineElementDragState';
+import { TimelineElementDragState } from './utils/timelineElementDragState';
 import { TimelineDraggableData } from './utils/timelineDraggableData';
 import { TimelineModelCalcResults } from './utils/timelineModelCalcResults';
 import { TimelineCalculatedRow } from './utils/timelineCalculatedRow';
@@ -28,7 +28,7 @@ export declare class Timeline extends TimelineEventsEmitter {
      */
     _container: HTMLElement | null;
     /**
-     * Dynamically generated event.
+     * Dynamically generated canvas inside of the container.
      */
     _canvas: HTMLCanvasElement | null;
     /**

+ 0 - 3
lib/utils/TimelineElementDragState.js

@@ -1,3 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-//# sourceMappingURL=TimelineElementDragState.js.map

+ 0 - 1
lib/utils/TimelineElementDragState.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"TimelineElementDragState.js","sourceRoot":"","sources":["../../src/utils/TimelineElementDragState.ts"],"names":[],"mappings":""}

+ 0 - 3
lib/utils/timelineCalculated.js

@@ -1,3 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-//# sourceMappingURL=timelineCalculated.js.map

+ 0 - 1
lib/utils/timelineCalculated.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"timelineCalculated.js","sourceRoot":"","sources":["../../src/utils/timelineCalculated.ts"],"names":[],"mappings":""}

+ 0 - 3
lib/utils/timelineCalculatedGroup.js

@@ -1,3 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-//# sourceMappingURL=timelineCalculatedGroup.js.map

+ 0 - 1
lib/utils/timelineCalculatedGroup.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"timelineCalculatedGroup.js","sourceRoot":"","sources":["../../src/utils/timelineCalculatedGroup.ts"],"names":[],"mappings":""}

+ 0 - 3
lib/utils/timelineCalculatedKeyframe.js

@@ -1,3 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-//# sourceMappingURL=timelineCalculatedKeyframe.js.map

+ 0 - 1
lib/utils/timelineCalculatedKeyframe.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"timelineCalculatedKeyframe.js","sourceRoot":"","sources":["../../src/utils/timelineCalculatedKeyframe.ts"],"names":[],"mappings":""}

+ 0 - 3
lib/utils/timelineCalculatedRow.js

@@ -1,3 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-//# sourceMappingURL=timelineCalculatedRow.js.map

+ 0 - 1
lib/utils/timelineCalculatedRow.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"timelineCalculatedRow.js","sourceRoot":"","sources":["../../src/utils/timelineCalculatedRow.ts"],"names":[],"mappings":""}

+ 6 - 13
package-lock.json

@@ -1525,12 +1525,6 @@
         "eslint-visitor-keys": "^3.3.0"
       }
     },
-    "@ungap/promise-all-settled": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
-      "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
-      "dev": true
-    },
     "@webassemblyjs/ast": {
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -1955,9 +1949,9 @@
       "dev": true
     },
     "caniuse-lite": {
-      "version": "1.0.30001419",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz",
-      "integrity": "sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==",
+      "version": "1.0.30001420",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
+      "integrity": "sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==",
       "dev": true
     },
     "chai": {
@@ -3192,12 +3186,11 @@
       }
     },
     "mocha": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz",
-      "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==",
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
+      "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
       "dev": true,
       "requires": {
-        "@ungap/promise-all-settled": "1.1.2",
         "ansi-colors": "4.1.1",
         "browser-stdout": "1.3.1",
         "chokidar": "3.5.3",

+ 3 - 2
package.json

@@ -19,7 +19,7 @@
     "eslint": "8.25.0",
     "eslint-config-prettier": "8.5.0",
     "eslint-plugin-prettier": "4.2.1",
-    "mocha": "10.0.0",
+    "mocha": "10.1.0",
     "prettier": "2.7.1",
     "ts-loader": "9.4.1",
     "typescript": "4.8.4",
@@ -27,7 +27,8 @@
     "webpack-cli": "4.10.0"
   },
   "scripts": {
-    "test": "echo \"Run tests/unittest.html explicitly to execute tests\" && exit 1",
+    "start": "echo \"Run index.html in your browser. Build after files are changed.\" && exit 1",
+    "test": "echo \"Run tests/unittest.html explicitly to execute tests. Build after files are changed.\" && exit 1",
     "build-ts-def": "tsc -emitDeclarationOnly",
     "build": "webpack && npm run build-tests && npm run build-ts-def",
     "lint": "eslint --ext .ts,.html src --ignore-path .gitignore && prettier \"src/*.ts\" --check --ignore-path .gitignore",

+ 1 - 1
src/settings/timelineOptions.ts

@@ -22,7 +22,7 @@ export interface TimelineOptions extends TimelineRanged {
   /**
    * Number of points that should fit into the one stepPx.
    */
-  stepVal: number;
+  stepVal?: number;
   stepSmallPx?: number;
   /**
    * Snap step in units. from 0 to stepVal

+ 7 - 7
src/timeline.ts

@@ -19,7 +19,7 @@ import { TimelineElement } from './utils/timelineElement';
 import { TimelineCutBoundsRectResults } from './utils/timelineCutBoundsRectResults';
 import { TimelineSelectionResults } from './utils/timelineSelectionResults';
 import { TimelineMouseData } from './utils/timelineMouseData';
-import { TimelineElementDragState } from './utils/TimelineElementDragState';
+import { TimelineElementDragState } from './utils/timelineElementDragState';
 import { TimelineDraggableData } from './utils/timelineDraggableData';
 
 // @private virtual model
@@ -54,7 +54,7 @@ export class Timeline extends TimelineEventsEmitter {
    */
   _container: HTMLElement | null = null;
   /**
-   * Dynamically generated event.
+   * Dynamically generated canvas inside of the container.
    */
   _canvas: HTMLCanvasElement | null = null;
   /**
@@ -1176,7 +1176,7 @@ export class Timeline extends TimelineEventsEmitter {
       min = 0;
     }
     min *= this._currentZoom || 1;
-    const steps = this._options.stepVal * this._currentZoom || 1;
+    const steps = (this._options.stepVal || 0) * this._currentZoom || 1;
     const val = min + (px / this._options.stepPx) * steps;
     return val;
   }
@@ -1205,7 +1205,7 @@ export class Timeline extends TimelineEventsEmitter {
       min = 0;
     }
     min *= this._currentZoom || 1;
-    const steps = this._options.stepVal * this._currentZoom || 1;
+    const steps = (this._options.stepVal || 0) * this._currentZoom || 1;
     return (-min + val) * (this._options.stepPx / steps);
   }
 
@@ -1981,7 +1981,7 @@ export class Timeline extends TimelineEventsEmitter {
   _setOptions(toSet: TimelineOptions): TimelineOptions {
     this._options = this._mergeOptions(toSet);
     // Normalize and validate spans per value.
-    this._options.snapStep = TimelineUtils.keepInBounds(this._options.snapStep, 0, this._options.stepVal);
+    this._options.snapStep = TimelineUtils.keepInBounds(this._options.snapStep, 0, this._options.stepVal || 0);
     this._currentZoom = this._setZoom(this._options.zoom, this._options.zoomMin, this._options.zoomMax);
     this._options.min = TimelineUtils.isNumber(this._options.min) ? this._options.min : 0;
     this._options.max = TimelineUtils.isNumber(this._options.max) ? this._options.max : Number.MAX_VALUE;
@@ -2046,8 +2046,8 @@ export class Timeline extends TimelineEventsEmitter {
    * Apply container div size to the container on changes detected.
    */
   _updateCanvasScale(): boolean {
-    if (!this._scrollContainer || !this._ctx) {
-      console.log('control should be initialized first');
+    if (!this._scrollContainer || !this._container || !this._ctx) {
+      console.log('Component should be initialized first');
       return;
     }
     let changed = false;

+ 24 - 0
tests/README.md

@@ -0,0 +1,24 @@
+# Run Tests
+
+Build
+
+```CMD
+npm run build
+```
+
+open unittests.html in any browser.
+
+Note: js folder is autogenerated. To add a new test *.ts files should be used.
+
+## Debug
+
+VSCode is used as IDE and configuration is included to the project sources.
+
+To debug project you should run command once files are changed:
+
+```cmd
+npm run build
+```
+
+Then navigate to the debug window and click 'Launch Test File'.
+Put breakpoint in any typescript tests file to and refresh page or test to run debugger.

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 7 - 5
tests/js/settingsTests.test.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 4 - 2
tests/js/styleTests.test.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 210 - 97
tests/js/timelineTests.test.js


+ 5 - 5
tests/settingsTests.test.ts

@@ -23,7 +23,7 @@ describe('_mergeOptions', function () {
     const merged = timeline._mergeOptions(options);
     chai.expect(merged.id).equal(options.id);
     chai.expect(!!merged.rowsStyle).equal(true, 'Row style cannot be null');
-    chai.expect(!!merged.rowsStyle.keyframesStyle).equal(true, 'Keyframes style cannot be null');
+    chai.expect(!!merged.rowsStyle?.keyframesStyle).equal(true, 'Keyframes style cannot be null');
   });
 
   it('Deep styles are merged', function () {
@@ -44,11 +44,11 @@ describe('_mergeOptions', function () {
     const merged = timeline._mergeOptions(options);
     chai.expect(merged.id).equal('new id');
     chai.expect(merged.headerHeight).equal(44);
-    chai.expect(merged.rowsStyle.height).equal(100);
-    chai.expect(merged.rowsStyle.keyframesStyle.hidden).equal(true);
-    chai.expect(merged.rowsStyle.keyframesStyle.draggable).equal(false);
+    chai.expect(merged.rowsStyle?.height).equal(100);
+    chai.expect(merged.rowsStyle?.keyframesStyle?.hidden).equal(true);
+    chai.expect(merged.rowsStyle?.keyframesStyle?.draggable).equal(false);
     const defOptions = defaultTimelineOptions as TimelineOptions;
-    chai.expect(merged.rowsStyle.keyframesStyle.shape, defOptions.rowsStyle.keyframesStyle.shape);
+    chai.expect(merged.rowsStyle?.keyframesStyle?.shape, defOptions.rowsStyle?.keyframesStyle?.shape);
   });
   it('Original options are not affected', function () {
     const timeline = new Timeline();

+ 2 - 2
tests/styleTests.test.ts

@@ -141,7 +141,7 @@ describe('TimelineStyleUtils', function () {
       } as TimelineOptions;
 
       const rowsStyle = {} as TimelineRowStyle;
-      chai.expect(TimelineStyleUtils.getRowHeight(rowsStyle, globalStyle)).equal(globalStyle.rowsStyle.height);
+      chai.expect(TimelineStyleUtils.getRowHeight(rowsStyle, globalStyle)).equal(globalStyle.rowsStyle?.height);
     });
     it('Margin bottom is taken from global settings', function () {
       const globalStyle = {
@@ -153,7 +153,7 @@ describe('TimelineStyleUtils', function () {
       } as TimelineOptions;
 
       const rowsStyle = {} as TimelineRowStyle;
-      chai.expect(TimelineStyleUtils.getRowMarginBottom(rowsStyle, globalStyle)).equal(globalStyle.rowsStyle.marginBottom);
+      chai.expect(TimelineStyleUtils.getRowMarginBottom(rowsStyle, globalStyle)).equal(globalStyle.rowsStyle?.marginBottom);
     });
     it('Margin bottom is taken from row settings', function () {
       const globalStyle = {

+ 188 - 96
tests/timelineTests.test.ts

@@ -219,10 +219,14 @@ describe('Timeline', function () {
       chai.expect(element.selectionChanged).equal(true);
       let changed = 0;
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          chai.expect(keyframe.selected).equal(true);
-          changed++;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row && row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            chai.expect(keyframe.selected).equal(true);
+            changed++;
+          });
+        }
       });
 
       chai.expect(element.selected.length).equal(changed);
@@ -235,14 +239,18 @@ describe('Timeline', function () {
       let changed = 0;
       let selectable = 0;
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          keyframe.selected = false;
-          keyframe.selectable = changed % 2 === 0;
-          if (keyframe.selectable) {
-            selectable++;
-          }
-          changed++;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            keyframe.selected = false;
+            keyframe.selectable = changed % 2 === 0;
+            if (keyframe.selectable) {
+              selectable++;
+            }
+            changed++;
+          });
+        }
       });
       const selectionResults = timeline.select(element);
       chai.expect(selectionResults.changed.length).equal(selectable);
@@ -252,19 +260,27 @@ describe('Timeline', function () {
       const timeline = new Timeline();
       timeline._model = model;
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          keyframe.selectable = true;
-          keyframe.selected = true;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            keyframe.selectable = true;
+            keyframe.selected = true;
+          });
+        }
       });
       // deselect all
       const element = timeline.deselectAll();
       chai.expect(element.selectionChanged).equal(true);
       chai.expect(element.selected.length).equal(0);
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          chai.expect(keyframe.selected).equal(false);
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            chai.expect(keyframe.selected).equal(false);
+          });
+        }
       });
     });
     it('Select one and deselect other all', function () {
@@ -273,29 +289,42 @@ describe('Timeline', function () {
       let expectedChanged = 0;
       // Select all
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          keyframe.selectable = true;
-          keyframe.selected = true;
-          expectedChanged++;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            keyframe.selectable = true;
+            keyframe.selected = true;
+            expectedChanged++;
+          });
+        }
       });
 
       // select one will deselect other
-      const toSelect = model.rows[1].keyframes[0];
-
+      const rowToSelect = model.rows[1];
+      chai.expect(!!rowToSelect.keyframes).equal(true);
+      if (!rowToSelect.keyframes) {
+        return;
+      }
+      // toggle selection
+      const toSelect = rowToSelect.keyframes[0];
       const element = timeline.select(toSelect);
       chai.expect(element.selectionChanged).equal(true);
       chai.expect(element.selected.length).equal(1);
       chai.expect(element.changed.length).equal(expectedChanged - 1);
 
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          if (toSelect == keyframe) {
-            chai.expect(keyframe.selected).equal(true);
-          } else {
-            chai.expect(keyframe.selected).equal(false);
-          }
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            if (toSelect == keyframe) {
+              chai.expect(keyframe.selected).equal(true);
+            } else {
+              chai.expect(keyframe.selected).equal(false);
+            }
+          });
+        }
       });
     });
     it('Revert selection (Toggle)', function () {
@@ -304,15 +333,25 @@ describe('Timeline', function () {
       let totalKeyframes = 0;
       // Select all
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          keyframe.selectable = true;
-          keyframe.selected = true;
-          totalKeyframes++;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            keyframe.selectable = true;
+            keyframe.selected = true;
+            totalKeyframes++;
+          });
+        }
       });
 
+      const rowToSelect = model.rows[1];
+      chai.expect(!!rowToSelect.keyframes).equal(true);
+      if (!rowToSelect.keyframes) {
+        return;
+      }
       // toggle selection
-      const toSelect = model.rows[1].keyframes[0];
+      const toSelect = rowToSelect.keyframes[0];
+
       // item is selected, should be reverted
       const element = timeline.select(toSelect, TimelineSelectionMode.Revert);
       chai.expect(element.selectionChanged).equal(true);
@@ -320,13 +359,16 @@ describe('Timeline', function () {
       chai.expect(element.changed.length).equal(1);
 
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          if (toSelect == keyframe) {
-            chai.expect(keyframe.selected).equal(false);
-          } else {
-            chai.expect(keyframe.selected).equal(true);
-          }
-        });
+        chai.expect((row.keyframes?.length || 0) > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            if (toSelect == keyframe) {
+              chai.expect(keyframe.selected).equal(false);
+            } else {
+              chai.expect(keyframe.selected).equal(true);
+            }
+          });
+        }
       });
     });
     it('Select full row', function () {
@@ -334,14 +376,22 @@ describe('Timeline', function () {
       timeline._model = model;
       // Deselect all
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          keyframe.selectable = true;
-          keyframe.selected = false;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            keyframe.selectable = true;
+            keyframe.selected = false;
+          });
+        }
       });
 
       // select one will deselect other
       const rowToSelect = model.rows[1];
+      chai.expect(!!rowToSelect.keyframes).equal(true);
+      if (!rowToSelect.keyframes) {
+        return;
+      }
       const element = timeline.select(rowToSelect.keyframes);
       chai.expect(element.selectionChanged).equal(true);
       chai.expect(element.selected.length).equal(rowToSelect.keyframes.length);
@@ -349,13 +399,21 @@ describe('Timeline', function () {
 
       model.rows.forEach((row: TimelineRow) => {
         if (rowToSelect === row) {
-          rowToSelect.keyframes.forEach((keyframe: TimelineKeyframe) => {
-            chai.expect(keyframe.selected).equal(true);
-          });
+          const len = row?.keyframes?.length || 0;
+          chai.expect(len > 0).equal(true);
+          if (rowToSelect.keyframes) {
+            rowToSelect.keyframes.forEach((keyframe: TimelineKeyframe) => {
+              chai.expect(keyframe.selected).equal(true);
+            });
+          }
         } else {
-          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-            chai.expect(keyframe.selected).equal(false);
-          });
+          const len = row?.keyframes?.length || 0;
+          chai.expect(len > 0).equal(true);
+          if (row.keyframes) {
+            row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+              chai.expect(keyframe.selected).equal(false);
+            });
+          }
         }
       });
     });
@@ -364,34 +422,48 @@ describe('Timeline', function () {
       timeline._model = model;
       // Deselect all
       model.rows.forEach((row: TimelineRow) => {
-        row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-          keyframe.selected = false;
-        });
+        const len = row?.keyframes?.length || 0;
+        chai.expect(len > 0).equal(true);
+        if (row.keyframes) {
+          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+            keyframe.selected = false;
+          });
+        }
       });
 
       // select one row (array of the keyframes)
       const rowToSelect = model.rows[1];
+      chai.expect(!!rowToSelect.keyframes).equal(true);
+      if (!rowToSelect.keyframes) {
+        return;
+      }
       let results = timeline.select(rowToSelect.keyframes);
       chai.expect(results.selectionChanged).equal(true);
-      chai.expect(results.selected.length).equal(rowToSelect.keyframes.length);
-      chai.expect(results.changed.length).equal(rowToSelect.keyframes.length);
+      chai.expect(results.selected.length).equal(rowToSelect.keyframes?.length);
+      chai.expect(results.changed.length).equal(rowToSelect.keyframes?.length);
 
       // (array of the keyframes)
       const rowToSelect2 = model.rows[2];
-      results = timeline.select(rowToSelect2.keyframes, TimelineSelectionMode.Append);
+      results = timeline.select(rowToSelect2?.keyframes || [], TimelineSelectionMode.Append);
       chai.expect(results.selectionChanged).equal(true);
-      chai.expect(results.selected.length).equal(rowToSelect.keyframes.length + rowToSelect2.keyframes.length);
-      chai.expect(results.changed.length).equal(rowToSelect2.keyframes.length);
+      chai.expect(results.selected.length).equal((rowToSelect?.keyframes?.length || 0) + (rowToSelect2?.keyframes?.length || 0));
+      chai.expect(results.changed.length).equal(rowToSelect2?.keyframes?.length);
 
       model.rows.forEach((row: TimelineRow) => {
         if (rowToSelect === row || rowToSelect2 === row) {
-          rowToSelect.keyframes.forEach((keyframe: TimelineKeyframe) => {
-            chai.expect(keyframe.selected).equal(true);
-          });
+          chai.expect((rowToSelect.keyframes?.length || 0) > 0).equal(true);
+          if (rowToSelect.keyframes) {
+            rowToSelect.keyframes.forEach((keyframe: TimelineKeyframe) => {
+              chai.expect(keyframe.selected).equal(true);
+            });
+          }
         } else {
-          row.keyframes.forEach((keyframe: TimelineKeyframe) => {
-            chai.expect(keyframe.selected).equal(false);
-          });
+          chai.expect((row.keyframes?.length || 0) > 0).equal(true);
+          if (row.keyframes) {
+            row.keyframes.forEach((keyframe: TimelineKeyframe) => {
+              chai.expect(keyframe.selected).equal(false);
+            });
+          }
         }
       });
     });
@@ -559,20 +631,30 @@ describe('Timeline', function () {
       } as TimelineModel;
       timeline._model = model;
       const move = -50;
+      const row = model.rows[0];
+      chai.expect(!!row).equal(true);
+      if (!row) {
+        return;
+      }
+      const keyframes = row.keyframes;
+      chai.expect(!!keyframes).equal(true);
+      if (!keyframes) {
+        return;
+      }
       const movedOffset = timeline._moveElements(move, [
         {
-          keyframe: model.rows[0].keyframes[1],
-          row: model.rows[0],
+          keyframe: keyframes[1],
+          row: row,
         } as TimelineElementDragState,
         {
-          keyframe: model.rows[0].keyframes[0],
-          row: model.rows[0],
+          keyframe: keyframes[0],
+          row: row,
         } as TimelineElementDragState,
       ]);
 
       chai.expect(movedOffset).equal(move);
-      chai.expect(model.rows[0].keyframes[0].val).equal(item1 + move);
-      chai.expect(model.rows[0].keyframes[1].val).equal(item2 + move);
+      chai.expect(keyframes[0]?.val).equal(item1 + move);
+      chai.expect(keyframes[1]?.val).equal(item2 + move);
     });
     it('move right', function () {
       const timeline = new Timeline();
@@ -583,20 +665,30 @@ describe('Timeline', function () {
       } as TimelineModel;
       timeline._model = model;
       const move = 100;
+      const row = model.rows[0];
+      chai.expect(!!row).equal(true);
+      if (!row) {
+        return;
+      }
+      const keyframes = row.keyframes;
+      chai.expect(!!keyframes).equal(true);
+      if (!keyframes) {
+        return;
+      }
       const movedOffset = timeline._moveElements(move, [
         {
-          keyframe: model.rows[0].keyframes[1],
-          row: model.rows[0],
+          keyframe: keyframes[1],
+          row: row,
         } as TimelineElementDragState,
         {
-          keyframe: model.rows[0].keyframes[0],
-          row: model.rows[0],
+          keyframe: keyframes[0],
+          row: row,
         } as TimelineElementDragState,
       ]);
 
       chai.expect(movedOffset).equal(move);
-      chai.expect(model.rows[0].keyframes[0].val).equal(item1 + move);
-      chai.expect(model.rows[0].keyframes[1].val).equal(item2 + move);
+      chai.expect(keyframes[0]?.val).equal(item1 + move);
+      chai.expect(keyframes[1]?.val).equal(item2 + move);
     });
     it('move left limited by min', function () {
       const timeline = new Timeline();
@@ -615,8 +707,8 @@ describe('Timeline', function () {
       const movedOffset = timeline._moveElements(move, elementsToMove);
 
       chai.expect(movedOffset).equal(move / 2);
-      chai.expect(elementsToMove[0].keyframe.val).equal(0);
-      chai.expect(elementsToMove[1].keyframe.val).equal(25);
+      chai.expect(elementsToMove[0].keyframe?.val).equal(0);
+      chai.expect(elementsToMove[1].keyframe?.val).equal(25);
     });
 
     it('move right limited by max', function () {
@@ -636,8 +728,8 @@ describe('Timeline', function () {
       const movedOffset = timeline._moveElements(move, elementsToMove);
 
       chai.expect(movedOffset).equal(move / 2);
-      chai.expect(elementsToMove[0].keyframe.val).equal(item1 + 50);
-      chai.expect(elementsToMove[1].keyframe.val).equal(item2 + 50);
+      chai.expect(elementsToMove[0].keyframe?.val).equal(item1 + 50);
+      chai.expect(elementsToMove[1].keyframe?.val).equal(item2 + 50);
     });
     it('move right limited by max negative', function () {
       const timeline = new Timeline();
@@ -656,8 +748,8 @@ describe('Timeline', function () {
       const movedOffset = timeline._moveElements(move, elementsToMove);
 
       chai.expect(movedOffset).equal(25);
-      chai.expect(elementsToMove[0].keyframe.val).equal(item1 + 25);
-      chai.expect(elementsToMove[1].keyframe.val).equal(item2 + 25);
+      chai.expect(elementsToMove[0].keyframe?.val).equal(item1 + 25);
+      chai.expect(elementsToMove[1].keyframe?.val).equal(item2 + 25);
     });
     it('move right limited by max negative when other row out of the bounds', function () {
       const timeline = new Timeline();
@@ -686,10 +778,10 @@ describe('Timeline', function () {
       const movedOffset = timeline._moveElements(move, elementsToMove);
       const moved = move / 2;
       chai.expect(movedOffset).equal(moved);
-      chai.expect(elementsToMove[0].keyframe.val).equal(100 + moved);
-      chai.expect(elementsToMove[1].keyframe.val).equal(400 + moved);
-      chai.expect(elementsToMove[2].keyframe.val).equal(200 + moved);
-      chai.expect(elementsToMove[3].keyframe.val).equal(300 + moved);
+      chai.expect(elementsToMove[0].keyframe?.val).equal(100 + moved);
+      chai.expect(elementsToMove[1].keyframe?.val).equal(400 + moved);
+      chai.expect(elementsToMove[2].keyframe?.val).equal(200 + moved);
+      chai.expect(elementsToMove[3].keyframe?.val).equal(300 + moved);
     });
     it('move left limited by min negative', function () {
       const timeline = new Timeline();
@@ -708,8 +800,8 @@ describe('Timeline', function () {
       const movedOffset = timeline._moveElements(move, elementsToMove);
 
       chai.expect(movedOffset).equal(move / 2);
-      chai.expect(elementsToMove[0].keyframe.val).equal(item1 - 50);
-      chai.expect(elementsToMove[1].keyframe.val).equal(item2 - 50);
+      chai.expect(elementsToMove[0].keyframe?.val).equal(item1 - 50);
+      chai.expect(elementsToMove[1].keyframe?.val).equal(item2 - 50);
     });
     it('move left only one keyframe is limited', function () {
       const timeline = new Timeline();
@@ -728,8 +820,8 @@ describe('Timeline', function () {
       const movedOffset = timeline._moveElements(move, elementsToMove);
 
       chai.expect(movedOffset).equal(move / 2);
-      chai.expect(elementsToMove[0].keyframe.val).equal(25 + 50);
-      chai.expect(elementsToMove[1].keyframe.val).equal(50 + 50);
+      chai.expect(elementsToMove[0].keyframe?.val).equal(25 + 50);
+      chai.expect(elementsToMove[1].keyframe?.val).equal(50 + 50);
     });
   });
 });

+ 1 - 2
tests/unittests.html

@@ -12,7 +12,7 @@
     <div>
         <a href="../index.html">Back</a>
         <p>
-            Build tests first and then tests can be executed here.
+            Build tests using the npm command first and then tests can be executed here.
             Results:
         </p>
     </div>
@@ -25,7 +25,6 @@
             window.exports = {};
         }
     </script>
-    <script src="./js/asserts.js"></script>
     <script>if (!window.require) {
             window.require = function (moduleName) {
                 if (moduleName.indexOf('assert') > 0) {

+ 2 - 2
webpack.config.js

@@ -12,7 +12,7 @@ module.exports = [
     mode: 'production',
     name: 'minimized-prod',
     entry: {
-      'animation-timeline': './src/timeline.ts',
+      'animation-timeline': './src/animation-timeline.ts',
     },
     module: {
       rules: [
@@ -39,7 +39,7 @@ module.exports = [
     mode: 'production',
     name: 'unminimized',
     entry: {
-      'animation-timeline': './src/timeline.ts',
+      'animation-timeline': './src/animation-timeline.ts',
     },
     module: {
       rules: [

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно