Include paletx core 95/38995/1
authorYuanHu <yuan.hu1@zte.com.cn>
Tue, 27 Mar 2018 09:24:38 +0000 (17:24 +0800)
committerYuanHu <yuan.hu1@zte.com.cn>
Tue, 27 Mar 2018 09:24:38 +0000 (17:24 +0800)
Include paletx core to WF Designer UI.

Issue-ID: SDC-1130,SDC-1131

Change-Id: I9a2591e022b5ff118cccbbc839796be19d70df84
Signed-off-by: YuanHu <yuan.hu1@zte.com.cn>
39 files changed:
sdc-workflow-designer-ui/package-lock.json
sdc-workflow-designer-ui/package.json
sdc-workflow-designer-ui/src/app/paletx/core/boolean-field-value.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/domhandler.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/number-wrapper-parse.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/fullscreen-overlay-container.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/generic-component-type.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/index.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-container.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-directives.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-position-map.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-ref.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-state.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/fake-viewport-ruler.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/free-position-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/global-position-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/overlay-position-builder.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/position-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/block-scroll-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/close-scroll-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/index.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/noop-scroll-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/reposition-scroll-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-strategy-options.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-strategy.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scrollable.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlaypanel/index.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/overlaypanel/overlaypanel.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button-state.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button.directive.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button.module.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/index.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/select.service.ts [new file with mode: 0644]
sdc-workflow-designer-ui/src/app/paletx/core/uuid.ts [new file with mode: 0644]

index bd501c0..a701545 100644 (file)
         "tslib": "1.7.1"
       }
     },
+    "@angular/cdk": {
+      "version": "2.0.0-beta.8",
+      "resolved": "http://registry.npm.taobao.org/@angular/cdk/download/@angular/cdk-2.0.0-beta.8.tgz",
+      "integrity": "sha1-cZYchR376xngheiYv15EYUCPi1c=",
+      "requires": {
+        "tslib": "1.7.1"
+      }
+    },
     "@angular/cli": {
       "version": "1.3.1",
       "resolved": "http://registry.npm.taobao.org/@angular/cli/download/@angular/cli-1.3.1.tgz",
@@ -87,7 +95,6 @@
         "typescript": "2.3.4",
         "url-loader": "0.5.9",
         "walk-sync": "0.3.2",
-        "webpack": "3.4.1",
         "webpack-dev-middleware": "1.12.0",
         "webpack-dev-server": "2.5.1",
         "webpack-merge": "4.1.0",
       "integrity": "sha1-ttiC6kDRjVE/w6A1p5h1Ap/jjwE=",
       "dev": true
     },
+    "@angular/material": {
+      "version": "5.2.4",
+      "resolved": "http://registry.npm.taobao.org/@angular/material/download/@angular/material-5.2.4.tgz",
+      "integrity": "sha1-noI3mDJCg9I+qDkVb6xby3NEPVU=",
+      "requires": {
+        "tslib": "1.7.1"
+      }
+    },
     "@angular/platform-browser": {
       "version": "4.3.5",
       "resolved": "http://registry.npm.taobao.org/@angular/platform-browser/download/@angular/platform-browser-4.3.5.tgz",
         "source-map": "0.5.6"
       }
     },
+    "@ngx-translate/core": {
+      "version": "7.2.2",
+      "resolved": "http://registry.npm.taobao.org/@ngx-translate/core/download/@ngx-translate/core-7.2.2.tgz",
+      "integrity": "sha1-5wBp1e+Og36jNuaJB3N4b177XeU="
+    },
+    "@ngx-translate/http-loader": {
+      "version": "1.1.0",
+      "resolved": "http://registry.npm.taobao.org/@ngx-translate/http-loader/download/@ngx-translate/http-loader-1.1.0.tgz",
+      "integrity": "sha1-opMmDOpvYXhKb/nFWklz6pd43OI="
+    },
     "@types/jasmine": {
       "version": "2.5.53",
       "resolved": "http://registry.npm.taobao.org/@types/jasmine/download/@types/jasmine-2.5.53.tgz",
         }
       }
     },
+    "date-fns": {
+      "version": "1.29.0",
+      "resolved": "http://registry.npm.taobao.org/date-fns/download/date-fns-1.29.0.tgz",
+      "integrity": "sha1-EuYJzcuTUScxHQTTMzTilgoqVOY="
+    },
     "date-now": {
       "version": "0.1.4",
       "resolved": "http://registry.npm.taobao.org/date-now/download/date-now-0.1.4.tgz",
       "resolved": "http://10.75.8.148/repository/npm-pub/jquery/-/jquery-3.2.1.tgz",
       "integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c="
     },
+    "jquery-ui": {
+      "version": "1.12.1",
+      "resolved": "http://registry.npm.taobao.org/jquery-ui/download/jquery-ui-1.12.1.tgz",
+      "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE="
+    },
     "js-base64": {
       "version": "2.1.9",
       "resolved": "http://registry.npm.taobao.org/js-base64/download/js-base64-2.1.9.tgz",
       }
     },
     "webpack": {
-      "version": "3.4.1",
-      "resolved": "http://registry.npm.taobao.org/webpack/download/webpack-3.4.1.tgz",
-      "integrity": "sha1-TD9PP7MYFVpNsMtqNv8FxWl0GPQ=",
+      "version": "3.5.6",
+      "resolved": "http://registry.npm.taobao.org/webpack/download/webpack-3.5.6.tgz",
+      "integrity": "sha1-pJL7bB7X9XOBb5DgDI+7WiDMXDY=",
       "dev": true,
       "requires": {
         "acorn": "5.1.1",
         "mkdirp": "0.5.1",
         "node-libs-browser": "2.0.0",
         "source-map": "0.5.6",
-        "supports-color": "4.2.1",
+        "supports-color": "4.5.0",
         "tapable": "0.2.8",
         "uglifyjs-webpack-plugin": "0.4.6",
         "watchpack": "1.4.0",
           "dev": true
         },
         "supports-color": {
-          "version": "4.2.1",
-          "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-4.2.1.tgz",
-          "integrity": "sha1-ZaS7JjHpDgJCDbpVVMN1pHVLuDY=",
+          "version": "4.5.0",
+          "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-4.5.0.tgz",
+          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
           "dev": true,
           "requires": {
             "has-flag": "2.0.0"
index 561639e..3cd612e 100644 (file)
   "private": true,
   "dependencies": {
     "@angular/animations": "^4.2.4",
+    "@angular/cdk": "2.0.0-beta.8",
     "@angular/common": "^4.2.4",
     "@angular/compiler": "^4.2.4",
     "@angular/core": "^4.2.4",
     "@angular/forms": "^4.2.4",
     "@angular/http": "^4.2.4",
+    "@angular/material": "^5.2.0",
     "@angular/platform-browser": "^4.2.4",
     "@angular/platform-browser-dynamic": "^4.2.4",
     "@angular/router": "^4.2.4",
@@ -26,6 +28,7 @@
     "angular-in-memory-web-api": "^0.3.2",
     "bootstrap": "4.0.0-alpha.6",
     "core-js": "^2.4.1",
+    "date-fns": "^1.29.0",
     "font-awesome": "^4.7.0",
     "jquery": "^3.2.1",
     "jquery-ui": "^1.12.1",
@@ -55,6 +58,7 @@
     "protractor": "~5.1.2",
     "ts-node": "~3.2.0",
     "tslint": "~5.3.2",
-    "typescript": "~2.3.3"
+    "typescript": "~2.3.3",
+    "webpack": "3.5.6"
   }
 }
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/boolean-field-value.ts b/sdc-workflow-designer-ui/src/app/paletx/core/boolean-field-value.ts
new file mode 100644 (file)
index 0000000..dc1f86e
--- /dev/null
@@ -0,0 +1,19 @@
+/* tslint:disable:array-type member-access variable-name */
+function booleanFieldValueFactory() {
+  return function booleanFieldValueMetadata(target: any, key: string): void {
+       const defaultValue = target[key];
+       const localKey = `__ky_private_symbol_${key}`;
+       target[localKey] = defaultValue;
+
+       Object.defineProperty(target, key, {
+               get() {
+               return (this)[localKey];
+               },
+               set(value: boolean) {
+               (this)[localKey] = value !== null && `${value}` !== 'false';
+               }
+       });
+  };
+}
+
+export {booleanFieldValueFactory as BooleanFieldValue};
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/domhandler.ts b/sdc-workflow-designer-ui/src/app/paletx/core/domhandler.ts
new file mode 100644 (file)
index 0000000..fd700a9
--- /dev/null
@@ -0,0 +1,432 @@
+import {Injectable} from '@angular/core';
+
+@Injectable()
+export class DomHandler {
+  static zindex: number = 1000;
+
+  public addClass(element: any, className: string): void {
+       if (element.classList) {
+               element.classList.add(className);
+       } else {
+               element.className += ' ' + className;
+       }
+  }
+
+  public addMultipleClasses(element: any, className: string): void {
+       if (element.classList) {
+               let styles: string[] = className.split(' ');
+      // for (let i = 0; i < styles.length; i++) {
+      //   element.classList.add(styles[i]);
+      // }
+               for (let style of styles) {
+               element.classList.add(style);
+               }
+
+       } else {
+               let styles: string[] = className.split(' ');
+      // for (let i = 0; i < styles.length; i++) {
+      //   element.className += ' ' + styles[i];
+      // }
+               for (let style of styles) {
+               element.className += ' ' + style;
+               }
+       }
+  }
+
+  public removeClass(element: any, className: string): void {
+       if (element.classList) {
+               element.classList.remove(className);
+       } else {
+               element.className = element.className.replace(
+                       new RegExp(
+                               '(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'),
+                       ' ');
+       }
+  }
+
+  public hasClass(element: any, className: string): boolean {
+       if (element.classList) {
+               return element.classList.contains(className);
+       } else {
+               return new RegExp('(^| )' + className + '( |$)', 'gi')
+                       .test(element.className);
+       }
+  }
+
+  public siblings(element: any): any {
+       return Array.prototype.filter.call(
+               element.parentNode.children, (child: any) => {
+                       return child !== element;
+               });
+  }
+
+  public find(element: any, selector: string): any[] {
+       return element.querySelectorAll(selector);
+  }
+
+  public findSingle(element: any, selector: string): any {
+       return element.querySelector(selector);
+  }
+
+  public index(element: any): number {
+       let children = element.parentNode.childNodes;
+       let num = 0;
+    // for (let i = 0; i < children.length; i++) {
+    //   if (children[i] == element) {
+    //     return num;
+    //   }
+    //   if (children[i].nodeType == 1) {
+    //     num++;
+    //   }
+    // }
+       for (let child of children) {
+               if (child === element) {
+               return num;
+               }
+               if (child.nodeType === 1) {
+               num++;
+               }
+       }
+       return -1;
+  }
+
+  public relativePosition(element: any, target: any): void {
+       let elementDimensions = element.offsetParent ?
+               {width: element.outerWidth, height: element.outerHeight} :
+               this.getHiddenElementDimensions(element);
+       let targetHeight = target.offsetHeight;
+       let targetWidth = target.offsetWidth;
+       let targetOffset = target.getBoundingClientRect();
+       let viewport = this.getViewport();
+       let top;
+       let left;
+
+       if ((targetOffset.top + targetHeight + elementDimensions.height) >
+               viewport.height) {
+               top = -1 * (elementDimensions.height);
+       } else {
+               top = targetHeight;
+       }
+
+       if ((targetOffset.left + elementDimensions.width) > viewport.width) {
+               left = targetWidth - elementDimensions.width;
+       } else {
+               left = 0;
+       }
+
+       element.style.top = top + 'px';
+       element.style.left = left + 'px';
+  }
+
+  public absolutePosition(element: any, target: any): void {
+       let elementDimensions = element.offsetParent ?
+               {width: element.offsetWidth, height: element.offsetHeight} :
+               this.getHiddenElementDimensions(element);
+       let elementOuterHeight = elementDimensions.height;
+       let elementOuterWidth = elementDimensions.width;
+       let targetOuterHeight = target.offsetHeight;
+       let targetOuterWidth = target.offsetWidth;
+       let targetOffset = target.getBoundingClientRect();
+       let windowScrollTop = this.getWindowScrollTop();
+       let windowScrollLeft = this.getWindowScrollLeft();
+       let viewport = this.getViewport();
+       let top;
+       let left;
+
+       if (targetOffset.top + targetOuterHeight + elementOuterHeight >
+               viewport.height) {
+               top = targetOffset.top + windowScrollTop - elementOuterHeight;
+               if (top < 0) {
+               top = 0 + windowScrollTop;
+               }
+       } else {
+               top = targetOuterHeight + targetOffset.top + windowScrollTop;
+       }
+
+       if (targetOffset.left + targetOuterWidth + elementOuterWidth >
+               viewport.width) {
+               left = targetOffset.left + windowScrollLeft + targetOuterWidth -
+                       elementOuterWidth;
+       } else {
+               left = targetOffset.left + windowScrollLeft;
+       }
+
+       element.style.top = top + 'px';
+       element.style.left = left + 'px';
+  }
+
+  public getHiddenElementOuterHeight(element: any): number {
+       element.style.visibility = 'hidden';
+       element.style.display = 'block';
+       let elementHeight = element.offsetHeight;
+       element.style.display = 'none';
+       element.style.visibility = 'visible';
+
+       return elementHeight;
+  }
+
+  public getHiddenElementOuterWidth(element: any): number {
+       element.style.visibility = 'hidden';
+       element.style.display = 'block';
+       let elementWidth = element.offsetWidth;
+       element.style.display = 'none';
+       element.style.visibility = 'visible';
+
+       return elementWidth;
+  }
+
+  public getHiddenElementDimensions(element: any): any {
+       let dimensions: any = {};
+       element.style.visibility = 'hidden';
+       element.style.display = 'block';
+       dimensions.width = element.offsetWidth;
+       dimensions.height = element.offsetHeight;
+       element.style.display = 'none';
+       element.style.visibility = 'visible';
+
+       return dimensions;
+  }
+
+  public scrollInView(container: any, item: any) {
+       let borderTopValue: string =
+               getComputedStyle(container).getPropertyValue('borderTopWidth');
+       let borderTop: number = borderTopValue ? parseFloat(borderTopValue) : 0;
+       let paddingTopValue: string =
+               getComputedStyle(container).getPropertyValue('paddingTop');
+       let paddingTop: number = paddingTopValue ? parseFloat(paddingTopValue) : 0;
+       let containerRect = container.getBoundingClientRect();
+       let itemRect = item.getBoundingClientRect();
+       let offset = (itemRect.top + document.body.scrollTop) -
+               (containerRect.top + document.body.scrollTop) - borderTop - paddingTop;
+       let scroll = container.scrollTop;
+       let elementHeight = container.clientHeight;
+       let itemHeight = this.getOuterHeight(item);
+
+       if (offset < 0) {
+               container.scrollTop = scroll + offset;
+       } else if ((offset + itemHeight) > elementHeight) {
+               container.scrollTop = scroll + offset - elementHeight + itemHeight;
+       }
+  }
+
+  public fadeIn(element: any, duration: number): void {
+       element.style.opacity = 0;
+
+       let last = +new Date();
+       let opacity = 0;
+       let tick = () => {
+               opacity =
+                       +element.style.opacity + (new Date().getTime() - last) / duration;
+               element.style.opacity = opacity;
+               last = +new Date();
+
+               if (+opacity < 1) {
+               if (!window.requestAnimationFrame || !requestAnimationFrame(tick)) {
+                       setTimeout(tick, 16);
+               }
+
+        /*(window.requestAnimationFrame && requestAnimationFrame(tick)) ||
+            setTimeout(tick, 16);*/
+               }
+       };
+
+       tick();
+  }
+
+  public fadeOut(element: any, ms: any) {
+       let opacity = 1;
+       let interval = 50;
+       let duration = ms;
+       let gap = interval / duration;
+
+       let fading = setInterval(() => {
+               opacity = opacity - gap;
+
+               if (opacity <= 0) {
+               opacity = 0;
+               clearInterval(fading);
+               }
+
+               element.style.opacity = opacity;
+       }, interval);
+  }
+
+  public getWindowScrollTop(): number {
+       let doc = document.documentElement;
+       return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+  }
+
+  public getWindowScrollLeft(): number {
+       let doc = document.documentElement;
+       return (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+  }
+
+  public matches(element: any, selector: string): boolean {
+       let p: any = Element.prototype;
+       let f: any = p['matches'] || p.webkitMatchesSelector ||
+               p['mozMatchesSelector'] || p.msMatchesSelector || function(s: any) {
+                       return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
+               };
+       return f.call(element, selector);
+  }
+
+  public getOuterWidth(el: any, margin?: any) {
+       let width = el.offsetWidth;
+
+       if (margin) {
+               let style = getComputedStyle(el);
+               width += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+       }
+
+       return width;
+  }
+
+  public getHorizontalPadding(el: any) {
+       let style = getComputedStyle(el);
+       return parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
+  }
+
+  public getHorizontalMargin(el: any) {
+       let style = getComputedStyle(el);
+       return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+  }
+
+  public innerWidth(el: any) {
+       let width = el.offsetWidth;
+       let style = getComputedStyle(el);
+
+       width += parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
+       return width;
+  }
+
+  public width(el: any) {
+       let width = el.offsetWidth;
+       let style = getComputedStyle(el);
+
+       width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
+       return width;
+  }
+
+  public getOuterHeight(el: any, margin?: any) {
+       let height = el.offsetHeight;
+
+       if (margin) {
+               let style = getComputedStyle(el);
+               height += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+       }
+
+       return height;
+  }
+
+  public getHeight(el: any): number {
+       let height = el.offsetHeight;
+       let style = getComputedStyle(el);
+
+       height -= parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) +
+               parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
+
+       return height;
+  }
+
+  public getWidth(el: any): number {
+       let width = el.offsetWidth;
+       let style = getComputedStyle(el);
+
+       width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) +
+               parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
+
+       return width;
+  }
+
+  public getViewport(): any {
+       let win = window;
+       let d = document;
+       let e = d.documentElement;
+       let g = d.getElementsByTagName('body')[0];
+       let w = win.innerWidth || e.clientWidth || g.clientWidth;
+       let h = win.innerHeight || e.clientHeight || g.clientHeight;
+
+       return {width: w, height: h};
+  }
+
+  public getOffset(el: any) {
+       let x = el.offsetLeft;
+       let y = el.offsetTop;
+
+       while (el = el.offsetParent) {
+               x += el.offsetLeft;
+               y += el.offsetTop;
+       }
+
+       return {left: x, top: y};
+  }
+
+  public getUserAgent(): string {
+       return navigator.userAgent;
+  }
+
+  public isIE() {
+       let ua = window.navigator.userAgent;
+
+       let msie = ua.indexOf('MSIE ');
+       if (msie > 0) {
+      // IE 10 or older => return version number
+               return true;
+       }
+
+       let trident = ua.indexOf('Trident/');
+       if (trident > 0) {
+      // IE 11 => return version number
+      /* let rv = ua.indexOf('rv:');*/
+               return true;
+       }
+
+       let edge = ua.indexOf('Edge/');
+       if (edge > 0) {
+      // Edge (IE 12+) => return version number
+               return true;
+       }
+
+    // other browser
+       return false;
+  }
+
+  public appendChild(element: any, target: any) {
+       if (this.isElement(target)) {
+               target.appendChild(element);
+       } else if (target.el && target.el.nativeElement) {
+               target.el.nativeElement.appendChild(element);
+       } else {
+               throw 'Cannot append ' + target + ' to ' + element;
+       }
+  }
+
+  public removeChild(element: any, target: any) {
+       if (this.isElement(target)) {
+               target.removeChild(element);
+       } else if (target.el && target.el.nativeElement) {
+               target.el.nativeElement.removeChild(element);
+       } else {
+               throw 'Cannot remove ' + element + ' from ' + target;
+       }
+  }
+
+  public isElement(obj: any) {
+       return (
+               typeof HTMLElement === 'object' ?
+                       obj instanceof HTMLElement :
+                       obj && typeof obj === 'object' && obj !== null &&
+                               obj.nodeType === 1 && typeof obj.nodeName === 'string');
+  }
+
+  public calculateScrollbarWidth(): number {
+       let scrollDiv = document.createElement('div');
+       scrollDiv.className = 'ui-scrollbar-measure';
+       document.body.appendChild(scrollDiv);
+
+       let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+       document.body.removeChild(scrollDiv);
+
+       return scrollbarWidth;
+  }
+}
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/number-wrapper-parse.ts b/sdc-workflow-designer-ui/src/app/paletx/core/number-wrapper-parse.ts
new file mode 100644 (file)
index 0000000..ceccd92
--- /dev/null
@@ -0,0 +1,10 @@
+/* tslint:disable:array-type member-access variable-name */
+export function NumberWrapperParseFloat(text: any) {
+  if (/^(\-|\+)?[0-9]+$/.test(text)) {
+       return parseInt(text);
+  } else if (/^(\-|\+)?[0-9]+\.[0-9]+$/.test(text)) {
+       return parseFloat(text);
+  } else {
+       return 0;
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/fullscreen-overlay-container.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/fullscreen-overlay-container.ts
new file mode 100644 (file)
index 0000000..0eca202
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Injectable} from '@angular/core';
+import {OverlayContainer} from './overlay-container';
+
+/**
+ * The FullscreenOverlayContainer is the alternative to OverlayContainer
+ * that supports correct displaying of overlay elements in Fullscreen mode
+ * https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen
+ * It should be provided in the root component that way:
+ * providers: [
+ *   {provide: OverlayContainer, useClass: FullscreenOverlayContainer}
+ * ],
+ */
+@Injectable()
+export class FullscreenOverlayContainer extends OverlayContainer {
+  protected _createContainer(): void {
+       super._createContainer();
+       this._adjustParentForFullscreenChange();
+       this._addFullscreenChangeListener(
+               () => this._adjustParentForFullscreenChange());
+  }
+
+  private _adjustParentForFullscreenChange(): void {
+       if (!this._containerElement) {
+               return;
+       }
+       const fullscreenElement = this.getFullscreenElement();
+       const parent = fullscreenElement || document.body;
+       parent.appendChild(this._containerElement);
+  }
+
+  private _addFullscreenChangeListener(fn: () => void) {
+       if (document.fullscreenEnabled) {
+               document.addEventListener('fullscreenchange', fn);
+       } else if (document.webkitFullscreenEnabled) {
+               document.addEventListener('webkitfullscreenchange', fn);
+       } else if ((document as any).mozFullScreenEnabled) {
+               document.addEventListener('mozfullscreenchange', fn);
+       } else if ((document as any).msFullscreenEnabled) {
+               document.addEventListener('MSFullscreenChange', fn);
+       }
+  }
+
+  /**
+   * When the page is put into fullscreen mode, a specific element is specified.
+   * Only that element and its children are visible when in fullscreen mode.
+   */
+  getFullscreenElement(): Element {
+       return document.fullscreenElement || document.webkitFullscreenElement ||
+               (document as any).mozFullScreenElement ||
+               (document as any).msFullscreenElement || null;
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/generic-component-type.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/generic-component-type.ts
new file mode 100644 (file)
index 0000000..523bd42
--- /dev/null
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export interface ComponentType<T> { new(...args: any[]): T; }
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/index.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/index.ts
new file mode 100644 (file)
index 0000000..e02bc3c
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {NgModule, Provider} from '@angular/core';
+
+import {Overlay} from './overlay';
+import {OVERLAY_CONTAINER_PROVIDER} from './overlay-container';
+import {ConnectedOverlayDirective, OverlayOrigin} from './overlay-directives';
+import {OverlayPositionBuilder} from './position/overlay-position-builder';
+import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler';
+import {ScrollDispatchModule} from './scroll/index';
+
+
+export const OVERLAY_PROVIDERS: Provider[] = [
+  Overlay,
+  OverlayPositionBuilder,
+  VIEWPORT_RULER_PROVIDER,
+  OVERLAY_CONTAINER_PROVIDER,
+];
+
+@NgModule({
+  imports: [ScrollDispatchModule],
+  exports: [ConnectedOverlayDirective, OverlayOrigin, ScrollDispatchModule],
+  declarations: [ConnectedOverlayDirective, OverlayOrigin],
+  providers: [OVERLAY_PROVIDERS],
+})
+export class OverlayModule {
+}
+
+
+export {Overlay} from './overlay';
+export {OverlayContainer} from './overlay-container';
+export {FullscreenOverlayContainer} from './fullscreen-overlay-container';
+export {OverlayRef} from './overlay-ref';
+export {OverlayState} from './overlay-state';
+export {ConnectedOverlayDirective, OverlayOrigin} from './overlay-directives';
+export {ViewportRuler} from './position/viewport-ruler';
+
+export * from './position/connected-position';
+export * from './scroll/index';
+
+// Export pre-defined position strategies and interface to build custom ones.
+export {PositionStrategy} from './position/position-strategy';
+export {GlobalPositionStrategy} from './position/global-position-strategy';
+export {ConnectedPositionStrategy} from './position/connected-position-strategy';
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-container.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-container.ts
new file mode 100644 (file)
index 0000000..fbb37c7
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Injectable, Optional, SkipSelf} from '@angular/core';
+
+
+/**
+ * The OverlayContainer is the container in which all overlays will load.
+ * It should be provided in the root component to ensure it is properly shared.
+ */
+@Injectable()
+export class OverlayContainer {
+  protected _containerElement: HTMLElement;
+
+  private _themeClass: string;
+
+  /**
+   * Base theme to be applied to all overlay-based components.
+   */
+  get themeClass(): string {
+       return this._themeClass;
+  }
+  set themeClass(value: string) {
+       if (this._containerElement) {
+               this._containerElement.classList.remove(this._themeClass);
+
+               if (value) {
+               this._containerElement.classList.add(value);
+               }
+       }
+
+       this._themeClass = value;
+  }
+
+  /**
+   * This method returns the overlay container element.  It will lazily
+   * create the element the first time  it is called to facilitate using
+   * the container in non-browser environments.
+   * @returns the container element
+   */
+  getContainerElement(): HTMLElement {
+       if (!this._containerElement) {
+               this._createContainer();
+       }
+       return this._containerElement;
+  }
+
+  /**
+   * Create the overlay container element, which is simply a div
+   * with the 'cdk-overlay-container' class on the document body.
+   */
+  protected _createContainer(): void {
+       const container = document.createElement('div');
+       container.classList.add('nz-overlay-container');
+
+       if (this._themeClass) {
+               container.classList.add(this._themeClass);
+       }
+
+       document.body.appendChild(container);
+       this._containerElement = container;
+  }
+}
+
+export function OVERLAY_CONTAINER_PROVIDER_FACTORY(
+       parentContainer: OverlayContainer) {
+  return parentContainer || new OverlayContainer();
+}
+
+export const OVERLAY_CONTAINER_PROVIDER = {
+  // If there is already an OverlayContainer available, use that. Otherwise,
+  // provide a new one.
+  provide: OverlayContainer,
+  deps: [[new Optional(), new SkipSelf(), OverlayContainer]],
+  useFactory: OVERLAY_CONTAINER_PROVIDER_FACTORY
+};
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-directives.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-directives.ts
new file mode 100644 (file)
index 0000000..5b8c162
--- /dev/null
@@ -0,0 +1,329 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+import {TemplatePortal} from '@angular/cdk';
+import {Direction, Directionality} from '@angular/cdk';
+import {ESCAPE} from '@angular/cdk';
+import {Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Optional, Output, Renderer2, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
+import {Subscription} from 'rxjs/Subscription';
+
+import {Overlay} from './overlay';
+import {OverlayRef} from './overlay-ref';
+import {OverlayState} from './overlay-state';
+import {ConnectedOverlayPositionChange, ConnectionPositionPair} from './position/connected-position';
+import {ConnectedPositionStrategy} from './position/connected-position-strategy';
+import {ScrollStrategy} from './scroll/scroll-strategy';
+
+/** Coerces a data-bound value (typically a string) to a boolean. */
+export function coerceBooleanProperty(value: any): boolean {
+  return value  !== null && `${value}` !== 'false';
+}
+
+
+/** Default set of positions for the overlay. Follows the behavior of a
+ * dropdown. */
+const defaultPositionList = [
+  new ConnectionPositionPair(
+               {originX: 'start', originY: 'bottom'},
+               {overlayX: 'start', overlayY: 'top'}),
+  new ConnectionPositionPair(
+               {originX: 'start', originY: 'top'},
+               {overlayX: 'start', overlayY: 'bottom'}),
+];
+
+
+/**
+ * Directive applied to an element to make it usable as an origin for an Overlay
+ * using a ConnectedPositionStrategy.
+ */
+@Directive({
+  selector: '[nz-overlay-origin]',
+  exportAs: 'nzOverlayOrigin',
+})
+export class OverlayOrigin {
+  constructor(public elementRef: ElementRef) {}
+}
+
+
+/**
+ * Directive to facilitate declarative creation of an Overlay using a
+ * ConnectedPositionStrategy.
+ */
+@Directive({selector: '[nz-connected-overlay]', exportAs: 'nzConnectedOverlay'})
+export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
+  private _overlayRef: OverlayRef;
+  private _templatePortal: TemplatePortal;
+  private _hasBackdrop = false;
+  private _backdropSubscription: Subscription|null;
+  private _positionSubscription: Subscription;
+  private _offsetX = 0;
+  private _offsetY = 0;
+  private _position: ConnectedPositionStrategy;
+  private _escapeListener: Function;
+
+  /** Origin for the connected overlay. */
+  @Input() origin: OverlayOrigin;
+
+  /** Registered connected position pairs. */
+  @Input() positions: ConnectionPositionPair[];
+
+  /** The offset in pixels for the overlay connection point on the x-axis */
+  @Input()
+  get offsetX(): number {
+       return this._offsetX;
+  }
+
+  set offsetX(offsetX: number) {
+       this._offsetX = offsetX;
+       if (this._position) {
+               this._position.withOffsetX(offsetX);
+       }
+  }
+
+  /** The offset in pixels for the overlay connection point on the y-axis */
+  @Input()
+  get offsetY() {
+       return this._offsetY;
+  }
+
+  set offsetY(offsetY: number) {
+       this._offsetY = offsetY;
+       if (this._position) {
+               this._position.withOffsetY(offsetY);
+       }
+  }
+
+  /** The width of the overlay panel. */
+  @Input() width: number|string;
+
+  /** The height of the overlay panel. */
+  @Input() height: number|string;
+
+  /** The min width of the overlay panel. */
+  @Input() minWidth: number|string;
+
+  /** The min height of the overlay panel. */
+  @Input() minHeight: number|string;
+
+  /** The custom class to be set on the backdrop element. */
+  @Input() backdropClass: string;
+
+  /** The custom class to be set on the pane element. */
+  @Input() paneClass: string;
+
+  /** Strategy to be used when handling scroll events while the overlay is open.
+   */
+  @Input()
+  scrollStrategy: ScrollStrategy = this._overlay.scrollStrategies.reposition();
+
+  /** Whether the overlay is open. */
+  @Input() open = false;
+
+  /** Whether or not the overlay should attach a backdrop. */
+  @Input()
+  get hasBackdrop() {
+       return this._hasBackdrop;
+  }
+
+  set hasBackdrop(value: any) {
+       this._hasBackdrop = coerceBooleanProperty(value);
+  }
+
+  /** Event emitted when the backdrop is clicked. */
+  @Output() backdropClick = new EventEmitter<void>();
+
+  /** Event emitted when the position has changed. */
+  @Output() positionChange = new EventEmitter<ConnectedOverlayPositionChange>();
+
+  /** Event emitted when the overlay has been attached. */
+  @Output() attach = new EventEmitter<void>();
+
+  /** Event emitted when the overlay has been detached. */
+  @Output() detach = new EventEmitter<void>();
+
+  // TODO(jelbourn): inputs for size, scroll behavior, animation, etc.
+
+  constructor(
+               private _overlay: Overlay, private _renderer: Renderer2,
+               templateRef: TemplateRef<any>, viewContainerRef: ViewContainerRef,
+               @Optional() private _dir: Directionality) {
+       this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
+  }
+
+  /** The associated overlay reference. */
+  get overlayRef(): OverlayRef {
+       return this._overlayRef;
+  }
+
+  /** The element's layout direction. */
+  get dir(): Direction {
+       return this._dir ? this._dir.value : 'ltr';
+  }
+
+  ngOnDestroy() {
+       this._destroyOverlay();
+  }
+
+  ngOnChanges(changes: SimpleChanges) {
+       if (changes['open']) {
+               this.open ? this._attachOverlay() : this._detachOverlay();
+       }
+  }
+
+  /** Creates an overlay */
+  private _createOverlay() {
+       if (!this.positions || !this.positions.length) {
+               this.positions = defaultPositionList;
+       }
+
+       this._overlayRef =
+               this._overlay.create(this._buildConfig(), this.paneClass);
+  }
+
+  /** Builds the overlay config based on the directive's inputs */
+  private _buildConfig(): OverlayState {
+       const overlayConfig = new OverlayState();
+
+       if (this.width || this.width === 0) {
+               overlayConfig.width = this.width;
+       }
+
+       if (this.height || this.height === 0) {
+               overlayConfig.height = this.height;
+       }
+
+       if (this.minWidth || this.minWidth === 0) {
+               overlayConfig.minWidth = this.minWidth;
+       }
+
+       if (this.minHeight || this.minHeight === 0) {
+               overlayConfig.minHeight = this.minHeight;
+       }
+
+       overlayConfig.hasBackdrop = this.hasBackdrop;
+
+       if (this.backdropClass) {
+               overlayConfig.backdropClass = this.backdropClass;
+       }
+
+       this._position =
+               this._createPositionStrategy() as ConnectedPositionStrategy;
+       overlayConfig.positionStrategy = this._position;
+       overlayConfig.scrollStrategy = this.scrollStrategy;
+
+       return overlayConfig;
+  }
+
+  /** Returns the position strategy of the overlay to be set on the overlay
+   * config */
+  private _createPositionStrategy(): ConnectedPositionStrategy {
+       const pos = this.positions[0];
+       const originPoint = {originX: pos.originX, originY: pos.originY};
+       const overlayPoint = {overlayX: pos.overlayX, overlayY: pos.overlayY};
+
+       const strategy =
+               this._overlay.position()
+                       .connectedTo(this.origin.elementRef, originPoint, overlayPoint)
+                       .withOffsetX(this.offsetX)
+                       .withOffsetY(this.offsetY);
+
+       this._handlePositionChanges(strategy);
+
+       return strategy;
+  }
+
+  private _handlePositionChanges(strategy: ConnectedPositionStrategy): void {
+       for (let i = 1; i < this.positions.length; i++) {
+               strategy.withFallbackPosition(
+                       {
+                       originX: this.positions[i].originX,
+                       originY: this.positions[i].originY
+                       },
+                       {
+                       overlayX: this.positions[i].overlayX,
+                       overlayY: this.positions[i].overlayY
+                       });
+       }
+
+       this._positionSubscription = strategy.onPositionChange.subscribe(
+               pos => this.positionChange.emit(pos));
+  }
+
+  /** Attaches the overlay and subscribes to backdrop clicks if backdrop exists
+   */
+  private _attachOverlay() {
+       if (!this._overlayRef) {
+               this._createOverlay();
+       }
+
+       this._position.withDirection(this.dir);
+       this._overlayRef.getState().direction = this.dir;
+       this._initEscapeListener();
+
+       if (!this._overlayRef.hasAttached()) {
+               this._overlayRef.attach(this._templatePortal);
+               this.attach.emit();
+       }
+
+       if (this.hasBackdrop) {
+               this._backdropSubscription =
+                       this._overlayRef.backdropClick().subscribe(() => {
+                       this.backdropClick.emit();
+                       });
+       }
+  }
+
+  /** Detaches the overlay and unsubscribes to backdrop clicks if backdrop
+   * exists */
+  private _detachOverlay() {
+       if (this._overlayRef) {
+               this._overlayRef.detach();
+               this.detach.emit();
+       }
+
+       if (this._backdropSubscription) {
+               this._backdropSubscription.unsubscribe();
+               this._backdropSubscription = null;
+       }
+
+       if (this._escapeListener) {
+               this._escapeListener();
+       }
+  }
+
+  /** Destroys the overlay created by this directive. */
+  private _destroyOverlay() {
+       if (this._overlayRef) {
+               this._overlayRef.dispose();
+       }
+
+       if (this._backdropSubscription) {
+               this._backdropSubscription.unsubscribe();
+       }
+
+       if (this._positionSubscription) {
+               this._positionSubscription.unsubscribe();
+       }
+
+       if (this._escapeListener) {
+               this._escapeListener();
+       }
+  }
+
+  /** Sets the event listener that closes the overlay when pressing Escape. */
+  private _initEscapeListener() {
+       this._escapeListener =
+               this._renderer.listen('document', 'keydown', (event: KeyboardEvent) => {
+                       if (event.keyCode === ESCAPE) {
+                       this._detachOverlay();
+                       }
+               });
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-position-map.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-position-map.ts
new file mode 100644 (file)
index 0000000..8ce5385
--- /dev/null
@@ -0,0 +1,124 @@
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector one-variable-per-declaration
+ no-attribute-parameter-decorator*/
+import {ConnectionPositionPair} from './index';
+
+export const POSITION_MAP: any = {
+  'top': {
+       originX: 'center',
+       originY: 'top',
+       overlayX: 'center',
+       overlayY: 'bottom'
+  },
+  'topCenter': {
+       originX: 'center',
+       originY: 'top',
+       overlayX: 'center',
+       overlayY: 'bottom'
+  },
+  'topLeft':
+               {originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom'},
+  'topRight':
+               {originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom'},
+  'right': {
+       originX: 'end',
+       originY: 'center',
+       overlayX: 'start',
+       overlayY: 'center',
+  },
+  'rightTop': {
+       originX: 'end',
+       originY: 'top',
+       overlayX: 'start',
+       overlayY: 'top',
+  },
+  'rightBottom': {
+       originX: 'end',
+       originY: 'bottom',
+       overlayX: 'start',
+       overlayY: 'bottom',
+  },
+  'bottom': {
+       originX: 'center',
+       originY: 'bottom',
+       overlayX: 'center',
+       overlayY: 'top',
+  },
+  'bottomCenter': {
+       originX: 'center',
+       originY: 'bottom',
+       overlayX: 'center',
+       overlayY: 'top',
+  },
+  'bottomLeft': {
+       originX: 'start',
+       originY: 'bottom',
+       overlayX: 'start',
+       overlayY: 'top',
+  },
+  'bottomRight': {
+       originX: 'end',
+       originY: 'bottom',
+       overlayX: 'end',
+       overlayY: 'top',
+  },
+  'left': {
+       originX: 'start',
+       originY: 'center',
+       overlayX: 'end',
+       overlayY: 'center',
+  },
+  'leftTop': {
+       originX: 'start',
+       originY: 'top',
+       overlayX: 'end',
+       overlayY: 'top',
+  },
+  'leftBottom': {
+       originX: 'start',
+       originY: 'bottom',
+       overlayX: 'end',
+       overlayY: 'bottom',
+  },
+};
+export const DEFAULT_4_POSITIONS = _objectValues([
+  POSITION_MAP['top'], POSITION_MAP['right'], POSITION_MAP['bottom'],
+  POSITION_MAP['left']
+]);
+export const DEFAULT_DROPDOWN_POSITIONS =
+       _objectValues([POSITION_MAP['bottomLeft'], POSITION_MAP['topLeft']]);
+export const DEFAULT_DATEPICKER_POSITIONS = [
+  {
+       originX: 'start',
+       originY: 'top',
+       overlayX: 'start',
+       overlayY: 'top',
+  },
+  {
+       originX: 'start',
+       originY: 'bottom',
+       overlayX: 'start',
+       overlayY: 'bottom',
+  }
+] as ConnectionPositionPair[];
+
+function arrayMap(array: any, iteratee: any) {
+  let index = -1;
+  const length = array === null ? 0 : array.length, result = Array(length);
+
+  while (++index < length) {
+       result[index] = iteratee(array[index], index, array);
+  }
+  return result;
+}
+
+function baseValues(object: any, props: any) {
+  return arrayMap(props, function(key: any) {
+       return object[key];
+  });
+}
+
+function _objectValues(object: any) {
+  return object === null ? [] : baseValues(object, Object.keys(object));
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-ref.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-ref.ts
new file mode 100644 (file)
index 0000000..03c8c2b
--- /dev/null
@@ -0,0 +1,271 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens
+ promise-function-async*/
+import {Portal, PortalHost} from '@angular/cdk';
+import {NgZone} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Subject} from 'rxjs/Subject';
+
+import {OverlayState} from './overlay-state';
+import {ScrollStrategy} from './scroll/scroll-strategy';
+
+
+/**
+ * Reference to an overlay that has been created with the Overlay service.
+ * Used to manipulate or dispose of said overlay.
+ */
+export class OverlayRef implements PortalHost {
+  private _backdropElement: HTMLElement|null = null;
+  private _backdropClick: Subject<any> = new Subject();
+  private _attachments = new Subject<void>();
+  private _detachments = new Subject<void>();
+
+  constructor(
+               private _portalHost: PortalHost, private _pane: HTMLElement,
+               private _state: OverlayState, private _scrollStrategy: ScrollStrategy,
+               private _ngZone: NgZone) {
+       _scrollStrategy.attach(this);
+  }
+
+  /** The overlay's HTML element */
+  get overlayElement(): HTMLElement {
+       return this._pane;
+  }
+
+  /**
+   * Attaches the overlay to a portal instance and adds the backdrop.
+   * @param portal Portal instance to which to attach the overlay.
+   * @returns The portal attachment result.
+   */
+  attach(portal: Portal<any>): any {
+       const attachResult = this._portalHost.attach(portal);
+
+    // Update the pane element with the given state configuration.
+       this._updateStackingOrder();
+       this.updateSize();
+       this.updateDirection();
+       this.updatePosition();
+       this._scrollStrategy.enable();
+
+    // Enable pointer events for the overlay pane element.
+       this._togglePointerEvents(true);
+
+       if (this._state.hasBackdrop) {
+               this._attachBackdrop();
+       }
+
+       if (this._state.panelClass) {
+               this._pane.classList.add(this._state.panelClass);
+       }
+
+    // Only emit the `attachments` event once all other setup is done.
+       this._attachments.next();
+
+       return attachResult;
+  }
+
+  /**
+   * Detaches an overlay from a portal.
+   * @returns Resolves when the overlay has been detached.
+   */
+  detach(): Promise<any> {
+       this.detachBackdrop();
+
+    // When the overlay is detached, the pane element should disable pointer
+    // events. This is necessary because otherwise the pane element will cover
+    // the page and disable pointer events therefore. Depends on the position
+    // strategy and the applied pane boundaries.
+       this._togglePointerEvents(false);
+       this._scrollStrategy.disable();
+
+       const detachmentResult = this._portalHost.detach();
+
+    // Only emit after everything is detached.
+       this._detachments.next();
+
+       return detachmentResult;
+  }
+
+  /**
+   * Cleans up the overlay from the DOM.
+   */
+  dispose(): void {
+       if (this._state.positionStrategy) {
+               this._state.positionStrategy.dispose();
+       }
+
+       if (this._scrollStrategy) {
+               this._scrollStrategy.disable();
+       }
+
+       this.detachBackdrop();
+       this._portalHost.dispose();
+       this._attachments.complete();
+       this._backdropClick.complete();
+       this._detachments.next();
+       this._detachments.complete();
+  }
+
+  /**
+   * Checks whether the overlay has been attached.
+   */
+  hasAttached(): boolean {
+       return this._portalHost.hasAttached();
+  }
+
+  /**
+   * Returns an observable that emits when the backdrop has been clicked.
+   */
+  backdropClick(): Observable<void> {
+       return this._backdropClick.asObservable();
+  }
+
+  /** Returns an observable that emits when the overlay has been attached. */
+  attachments(): Observable<void> {
+       return this._attachments.asObservable();
+  }
+
+  /** Returns an observable that emits when the overlay has been detached. */
+  detachments(): Observable<void> {
+       return this._detachments.asObservable();
+  }
+
+  /**
+   * Gets the current state config of the overlay.
+   */
+  getState(): OverlayState {
+       return this._state;
+  }
+
+  /** Updates the position of the overlay based on the position strategy. */
+  updatePosition() {
+       if (this._state.positionStrategy) {
+               this._state.positionStrategy.apply(this._pane);
+       }
+  }
+
+  /** Updates the text direction of the overlay panel. */
+  private updateDirection() {
+       this._pane.setAttribute('dir', this._state.direction);
+  }
+
+
+  /** Updates the size of the overlay based on the overlay config. */
+  updateSize() {
+       if (this._state.width || this._state.width === 0) {
+               this._pane.style.width = formatCssUnit(this._state.width);
+       }
+
+       if (this._state.height || this._state.height === 0) {
+               this._pane.style.height = formatCssUnit(this._state.height);
+       }
+
+       if (this._state.minWidth || this._state.minWidth === 0) {
+               this._pane.style.minWidth = formatCssUnit(this._state.minWidth);
+       }
+
+       if (this._state.minHeight || this._state.minHeight === 0) {
+               this._pane.style.minHeight = formatCssUnit(this._state.minHeight);
+       }
+  }
+
+  /** Toggles the pointer events for the overlay pane element. */
+  private _togglePointerEvents(enablePointer: boolean) {
+       this._pane.style.pointerEvents = enablePointer ? 'auto' : 'none';
+  }
+
+  /** Attaches a backdrop for this overlay. */
+  private _attachBackdrop() {
+       this._backdropElement = document.createElement('div');
+       this._backdropElement.classList.add('nz-overlay-backdrop');
+
+       if (this._state.backdropClass) {
+               this._backdropElement.classList.add(this._state.backdropClass);
+       }
+
+    // Insert the backdrop before the pane in the DOM order,
+    // in order to handle stacked overlays properly.
+       this._pane.parentElement.insertBefore(this._backdropElement, this._pane);
+
+    // Forward backdrop clicks such that the consumer of the overlay can perform
+    // whatever action desired when such a click occurs (usually closing the
+    // overlay).
+       this._backdropElement.addEventListener(
+               'click', () => this._backdropClick.next(null));
+
+    // Add class to fade-in the backdrop after one frame.
+       requestAnimationFrame(() => {
+               if (this._backdropElement) {
+               this._backdropElement.classList.add('nz-overlay-backdrop-showing');
+               }
+       });
+  }
+
+  /**
+   * Updates the stacking order of the element, moving it to the top if
+   * necessary. This is required in cases where one overlay was detached, while
+   * another one, that should be behind it, was destroyed. The next time both of
+   * them are opened, the stacking will be wrong, because the detached element's
+   * pane will still be in its original DOM position.
+   */
+  private _updateStackingOrder() {
+       if (this._pane.nextSibling) {
+               this._pane.parentNode.appendChild(this._pane);
+       }
+  }
+
+  /** Detaches the backdrop (if any) associated with the overlay. */
+  detachBackdrop(): void {
+       const backdropToDetach = this._backdropElement;
+
+       if (backdropToDetach) {
+               const finishDetach = () => {
+        // It may not be attached to anything in certain cases (e.g. unit
+        // tests).
+               if (backdropToDetach && backdropToDetach.parentNode) {
+                       backdropToDetach.parentNode.removeChild(backdropToDetach);
+               }
+
+        // It is possible that a new portal has been attached to this overlay
+        // since we started removing the backdrop. If that is the case, only
+        // clear the backdrop reference if it is still the same instance that we
+        // started to remove.
+               if (this._backdropElement === backdropToDetach) {
+                       this._backdropElement = null;
+               }
+               };
+
+               backdropToDetach.classList.remove('nz-overlay-backdrop-showing');
+
+               if (this._state.backdropClass) {
+               backdropToDetach.classList.remove(this._state.backdropClass);
+               }
+
+               backdropToDetach.addEventListener('transitionend', finishDetach);
+
+      // If the backdrop doesn't have a transition, the `transitionend` event
+      // won't fire. In this case we make it unclickable and we try to remove it
+      // after a delay.
+               backdropToDetach.style.pointerEvents = 'none';
+
+      // Run this outside the Angular zone because there's nothing that Angular
+      // cares about. If it were to run inside the Angular zone, every test that
+      // used Overlay would have to be either async or fakeAsync.
+               this._ngZone.runOutsideAngular(() => {
+               setTimeout(finishDetach, 500);
+               });
+       }
+  }
+}
+
+function formatCssUnit(value: number|string) {
+  return typeof value === 'string' ? value as string : `${value}px`;
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-state.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay-state.ts
new file mode 100644 (file)
index 0000000..73d6b54
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Direction} from '@angular/cdk';
+
+import {PositionStrategy} from './position/position-strategy';
+import {ScrollStrategy} from './scroll/scroll-strategy';
+
+
+/**
+ * OverlayState is a bag of values for either the initial configuration or
+ * current state of an overlay.
+ */
+export class OverlayState {
+  /** Strategy with which to position the overlay. */
+  positionStrategy: PositionStrategy;
+
+  /** Strategy to be used when handling scroll events while the overlay is open.
+   */
+  scrollStrategy: ScrollStrategy;
+
+  /** Custom class to add to the overlay pane. */
+  panelClass = '';
+
+  /** Whether the overlay has a backdrop. */
+  hasBackdrop = false;
+
+  /** Custom class to add to the backdrop */
+  backdropClass = 'cdk-overlay-dark-backdrop';
+
+  /** The width of the overlay panel. If a number is provided, pixel units are
+   * assumed. */
+  width?: number|string;
+
+  /** The height of the overlay panel. If a number is provided, pixel units are
+   * assumed. */
+  height?: number|string;
+
+  /** The min-width of the overlay panel. If a number is provided, pixel units
+   * are assumed. */
+  minWidth?: number|string;
+
+  /** The min-height of the overlay panel. If a number is provided, pixel units
+   * are assumed. */
+  minHeight?: number|string;
+
+  /** The direction of the text in the overlay panel. */
+  direction?: Direction = 'ltr';
+
+  // TODO(jelbourn): configuration still to add
+  // - focus trap
+  // - disable pointer events
+  // - z-index
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/overlay.ts
new file mode 100644 (file)
index 0000000..5995201
--- /dev/null
@@ -0,0 +1,109 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+import {DomPortalHost} from '@angular/cdk';
+import {ApplicationRef, ComponentFactoryResolver, Injectable, Injector, NgZone} from '@angular/core';
+
+import {OverlayContainer} from './overlay-container';
+import {OverlayRef} from './overlay-ref';
+import {OverlayState} from './overlay-state';
+import {OverlayPositionBuilder} from './position/overlay-position-builder';
+import {ScrollStrategyOptions} from './scroll/index';
+
+
+/** Next overlay unique ID. */
+let nextUniqueId = 0;
+
+/** The default state for newly created overlays. */
+const defaultState = new OverlayState();
+
+
+/**
+ * Service to create Overlays. Overlays are dynamically added pieces of floating
+ * UI, meant to be used as a low-level building building block for other
+ * components. Dialogs, tooltips, menus, selects, etc. can all be built using
+ * overlays. The service should primarily be used by authors of re-usable
+ * components rather than developers building end-user applications.
+ *
+ * An overlay *is* a PortalHost, so any kind of Portal can be loaded into one.
+ */
+@Injectable()
+export class Overlay {
+  constructor(
+               public scrollStrategies: ScrollStrategyOptions,
+               private _overlayContainer: OverlayContainer,
+               private _componentFactoryResolver: ComponentFactoryResolver,
+               private _positionBuilder: OverlayPositionBuilder,
+               private _appRef: ApplicationRef, private _injector: Injector,
+               private _ngZone: NgZone) {}
+
+  /**
+   * Creates an overlay.
+   * @param state State to apply to the overlay.
+   * @returns Reference to the created overlay.
+   */
+  create(state: OverlayState = defaultState, paneClassName?: string):
+               OverlayRef {
+       return this._createOverlayRef(
+               this._createPaneElement(paneClassName), state);
+  }
+
+  /**
+   * Returns a position builder that can be used, via fluent API,
+   * to construct and configure a position strategy.
+   */
+  position(): OverlayPositionBuilder {
+       return this._positionBuilder;
+  }
+
+  /**
+   * Creates the DOM element for an overlay and appends it to the overlay
+   * container.
+   * @returns Newly-created pane element
+   */
+  private _createPaneElement(className?: string): HTMLElement {
+       const pane = document.createElement('div');
+
+       pane.id = `nz-overlay-${nextUniqueId++}`;
+       pane.classList.add('nz-overlay-pane');
+       if (className) {
+               const classList = className.split(' ');
+               classList.forEach(c => {
+               pane.classList.add(c);
+               });
+       }
+       this._overlayContainer.getContainerElement().appendChild(pane);
+
+       return pane;
+  }
+
+  /**
+   * Create a DomPortalHost into which the overlay content can be loaded.
+   * @param pane The DOM element to turn into a portal host.
+   * @returns A portal host for the given DOM element.
+   */
+  private _createPortalHost(pane: HTMLElement): DomPortalHost {
+       return new DomPortalHost(
+               pane, this._componentFactoryResolver, this._appRef, this._injector);
+  }
+
+  /**
+   * Creates an OverlayRef for an overlay in the given DOM element.
+   * @param pane DOM element for the overlay
+   * @param state
+   */
+  private _createOverlayRef(pane: HTMLElement, state: OverlayState):
+               OverlayRef {
+       const scrollStrategy = state.scrollStrategy || this.scrollStrategies.noop();
+       const portalHost = this._createPortalHost(pane);
+       return new OverlayRef(
+               portalHost, pane, state, scrollStrategy, this._ngZone);
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position-strategy.ts
new file mode 100644 (file)
index 0000000..d144c81
--- /dev/null
@@ -0,0 +1,478 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens
+ no-unused-variable*/
+import {ElementRef} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Subject} from 'rxjs/Subject';
+
+import {Scrollable} from '../scroll/scrollable';
+
+import {ConnectedOverlayPositionChange, ConnectionPositionPair, OriginConnectionPosition, OverlayConnectionPosition, ScrollableViewProperties} from './connected-position';
+import {PositionStrategy} from './position-strategy';
+import {ViewportRuler} from './viewport-ruler';
+
+/**
+ * Container to hold the bounding positions of a particular element with respect
+ * to the viewport, where top and bottom are the y-axis coordinates of the
+ * bounding rectangle and left and right are the x-axis coordinates.
+ */
+interface ElementBoundingPositions {
+  top: number;
+  right: number;
+  bottom: number;
+  left: number;
+}
+
+/**
+ * A strategy for positioning overlays. Using this strategy, an overlay is given
+ * an implicit position relative some origin element. The relative position is
+ * defined in terms of a point on the origin element that is connected to a
+ * point on the overlay element. For example, a basic dropdown is connecting the
+ * bottom-left corner of the origin to the top-left corner of the overlay.
+ */
+export class ConnectedPositionStrategy implements PositionStrategy {
+  private _dir = 'ltr';
+
+  /** The offset in pixels for the overlay connection point on the x-axis */
+  private _offsetX = 0;
+
+  /** The offset in pixels for the overlay connection point on the y-axis */
+  private _offsetY = 0;
+
+  /** The Scrollable containers used to check scrollable view properties on
+   * position change. */
+  private scrollables: Scrollable[] = [];
+
+  /** Whether the we're dealing with an RTL context */
+  get _isRtl() {
+       return this._dir === 'rtl';
+  }
+
+  /** Ordered list of preferred positions, from most to least desirable. */
+  _preferredPositions: ConnectionPositionPair[] = [];
+
+  /** The origin element against which the overlay will be positioned. */
+  private _origin: HTMLElement;
+
+  /** The overlay pane element. */
+  private _pane: HTMLElement;
+
+  /** The last position to have been calculated as the best fit position. */
+  private _lastConnectedPosition: ConnectionPositionPair;
+
+  _onPositionChange: Subject<ConnectedOverlayPositionChange> =
+               new Subject<ConnectedOverlayPositionChange>();
+
+  /** Emits an event when the connection point changes. */
+  get onPositionChange(): Observable<ConnectedOverlayPositionChange> {
+       return this._onPositionChange.asObservable();
+  }
+
+  constructor(
+               private _connectedTo: ElementRef,
+               private _originPos: OriginConnectionPosition,
+               private _overlayPos: OverlayConnectionPosition,
+               private _viewportRuler: ViewportRuler) {
+       this._origin = this._connectedTo.nativeElement;
+       this.withFallbackPosition(this._originPos, this._overlayPos);
+  }
+
+  /** Ordered list of preferred positions, from most to least desirable. */
+  get positions() {
+       return this._preferredPositions;
+  }
+
+  /**
+   * To be used to for any cleanup after the element gets destroyed.
+   */
+  dispose() {
+    //
+  }
+
+  /**
+   * Updates the position of the overlay element, using whichever preferred
+   * position relative to the origin fits on-screen.
+   * @docs-private
+   *
+   * @param element Element to which to apply the CSS styles.
+   * @returns Resolves when the styles have been applied.
+   */
+  apply(element: HTMLElement): void {
+    // Cache the overlay pane element in case re-calculating position is
+    // necessary
+       this._pane = element;
+
+    // We need the bounding rects for the origin and the overlay to determine
+    // how to position the overlay relative to the origin.
+       const originRect = this._origin.getBoundingClientRect();
+       const overlayRect = element.getBoundingClientRect();
+
+    // We use the viewport rect to determine whether a position would go
+    // off-screen.
+       const viewportRect = this._viewportRuler.getViewportRect();
+
+    // Fallback point if none of the fallbacks fit into the viewport.
+       let fallbackPoint: OverlayPoint|undefined;
+       let fallbackPosition: ConnectionPositionPair|undefined;
+
+    // We want to place the overlay in the first of the preferred positions such
+    // that the overlay fits on-screen.
+       for (const pos of this._preferredPositions) {
+      // Get the (x, y) point of connection on the origin, and then use that to
+      // get the (top, left) coordinate for the overlay at `pos`.
+               const originPoint = this._getOriginConnectionPoint(originRect, pos);
+               const overlayPoint =
+                       this._getOverlayPoint(originPoint, overlayRect, viewportRect, pos);
+
+      // If the overlay in the calculated position fits on-screen, put it there
+      // and we're done.
+               if (overlayPoint.fitsInViewport) {
+               this._setElementPosition(element, overlayRect, overlayPoint, pos);
+
+        // Save the last connected position in case the position needs to be
+        // re-calculated.
+               this._lastConnectedPosition = pos;
+
+        // Notify that the position has been changed along with its change
+        // properties.
+               const scrollableViewProperties =
+                       this.getScrollableViewProperties(element);
+               const positionChange =
+                       new ConnectedOverlayPositionChange(pos, scrollableViewProperties);
+               this._onPositionChange.next(positionChange);
+
+               return;
+               } else if (
+                       !fallbackPoint ||
+                       fallbackPoint.visibleArea < overlayPoint.visibleArea) {
+               fallbackPoint = overlayPoint;
+               fallbackPosition = pos;
+               }
+       }
+
+    // If none of the preferred positions were in the viewport, take the one
+    // with the largest visible area.
+       this._setElementPosition(
+               element, overlayRect, fallbackPoint, fallbackPosition);
+  }
+
+  /**
+   * This re-aligns the overlay element with the trigger in its last calculated
+   * position, even if a position higher in the "preferred positions" list would
+   * now fit. This allows one to re-align the panel without changing the
+   * orientation of the panel.
+   */
+  recalculateLastPosition(): void {
+       const originRect = this._origin.getBoundingClientRect();
+       const overlayRect = this._pane.getBoundingClientRect();
+       const viewportRect = this._viewportRuler.getViewportRect();
+       const lastPosition =
+               this._lastConnectedPosition || this._preferredPositions[0];
+
+       const originPoint =
+               this._getOriginConnectionPoint(originRect, lastPosition);
+       const overlayPoint = this._getOverlayPoint(
+               originPoint, overlayRect, viewportRect, lastPosition);
+       this._setElementPosition(
+               this._pane, overlayRect, overlayPoint, lastPosition);
+  }
+
+  /**
+   * Sets the list of Scrollable containers that host the origin element so that
+   * on reposition we can evaluate if it or the overlay has been clipped or
+   * outside view. Every Scrollable must be an ancestor element of the
+   * strategy's origin element.
+   */
+  withScrollableContainers(scrollables: Scrollable[]) {
+       this.scrollables = scrollables;
+  }
+
+  /**
+   * Adds a new preferred fallback position.
+   * @param originPos
+   * @param overlayPos
+   */
+  withFallbackPosition(
+               originPos: OriginConnectionPosition,
+               overlayPos: OverlayConnectionPosition): this {
+       this._preferredPositions.push(
+               new ConnectionPositionPair(originPos, overlayPos));
+       return this;
+  }
+
+  /**
+   * Sets the layout direction so the overlay's position can be adjusted to
+   * match.
+   * @param dir New layout direction.
+   */
+  withDirection(dir: 'ltr'|'rtl'): this {
+       this._dir = dir;
+       return this;
+  }
+
+  /**
+   * Sets an offset for the overlay's connection point on the x-axis
+   * @param offset New offset in the X axis.
+   */
+  withOffsetX(offset: number): this {
+       this._offsetX = offset;
+       return this;
+  }
+
+  /**
+   * Sets an offset for the overlay's connection point on the y-axis
+   * @param  offset New offset in the Y axis.
+   */
+  withOffsetY(offset: number): this {
+       this._offsetY = offset;
+       return this;
+  }
+
+  /**
+   * Gets the horizontal (x) "start" dimension based on whether the overlay is
+   * in an RTL context.
+   * @param rect
+   */
+  private _getStartX(rect: ClientRect): number {
+       return this._isRtl ? rect.right : rect.left;
+  }
+
+  /**
+   * Gets the horizontal (x) "end" dimension based on whether the overlay is in
+   * an RTL context.
+   * @param rect
+   */
+  private _getEndX(rect: ClientRect): number {
+       return this._isRtl ? rect.left : rect.right;
+  }
+
+
+  /**
+   * Gets the (x, y) coordinate of a connection point on the origin based on a
+   * relative position.
+   * @param originRect
+   * @param pos
+   */
+  private _getOriginConnectionPoint(
+               originRect: ClientRect, pos: ConnectionPositionPair): Point {
+       const originStartX = this._getStartX(originRect);
+       const originEndX = this._getEndX(originRect);
+
+       let x: number;
+       if (pos.originX === 'center') {
+               x = originStartX + (originRect.width / 2);
+       } else {
+               x = pos.originX === 'start' ? originStartX : originEndX;
+       }
+
+       let y: number;
+       if (pos.originY === 'center') {
+               y = originRect.top + (originRect.height / 2);
+       } else {
+               y = pos.originY === 'top' ? originRect.top : originRect.bottom;
+       }
+
+       return {x, y};
+  }
+
+
+  /**
+   * Gets the (x, y) coordinate of the top-left corner of the overlay given a
+   * given position and origin point to which the overlay should be connected,
+   * as well as how much of the element would be inside the viewport at that
+   * position.
+   */
+  private _getOverlayPoint(
+               originPoint: Point, overlayRect: ClientRect, viewportRect: ClientRect,
+               pos: ConnectionPositionPair): OverlayPoint {
+    // Calculate the (overlayStartX, overlayStartY), the start of the potential
+    // overlay position relative to the origin point.
+       let overlayStartX: number;
+       if (pos.overlayX === 'center') {
+               overlayStartX = -overlayRect.width / 2;
+       } else if (pos.overlayX === 'start') {
+               overlayStartX = this._isRtl ? -overlayRect.width : 0;
+       } else {
+               overlayStartX = this._isRtl ? 0 : -overlayRect.width;
+       }
+
+       let overlayStartY: number;
+       if (pos.overlayY === 'center') {
+               overlayStartY = -overlayRect.height / 2;
+       } else {
+               overlayStartY = pos.overlayY === 'top' ? 0 : -overlayRect.height;
+       }
+
+    // The (x, y) coordinates of the overlay.
+       const x = originPoint.x + overlayStartX + this._offsetX;
+       const y = originPoint.y + overlayStartY + this._offsetY;
+
+    // How much the overlay would overflow at this position, on each side.
+       const leftOverflow = 0 - x;
+       const rightOverflow = (x + overlayRect.width) - viewportRect.width;
+       const topOverflow = 0 - y;
+       const bottomOverflow = (y + overlayRect.height) - viewportRect.height;
+
+    // Visible parts of the element on each axis.
+       const visibleWidth =
+               this._subtractOverflows(overlayRect.width, leftOverflow, rightOverflow);
+       const visibleHeight = this._subtractOverflows(
+               overlayRect.height, topOverflow, bottomOverflow);
+
+    // The area of the element that's within the viewport.
+       const visibleArea = visibleWidth * visibleHeight;
+       const fitsInViewport =
+               (overlayRect.width * overlayRect.height) === visibleArea;
+
+       return {x, y, fitsInViewport, visibleArea};
+  }
+
+  /**
+   * Gets the view properties of the trigger and overlay, including whether they
+   * are clipped or completely outside the view of any of the strategy's
+   * scrollables.
+   */
+  private getScrollableViewProperties(overlay: HTMLElement):
+               ScrollableViewProperties {
+       const originBounds = this._getElementBounds(this._origin);
+       const overlayBounds = this._getElementBounds(overlay);
+       const scrollContainerBounds =
+               this.scrollables.map((scrollable: Scrollable) => {
+                       return this._getElementBounds(
+                               scrollable.getElementRef().nativeElement);
+               });
+
+       return {
+               isOriginClipped:
+                       this.isElementClipped(originBounds, scrollContainerBounds),
+               isOriginOutsideView:
+                       this.isElementOutsideView(originBounds, scrollContainerBounds),
+               isOverlayClipped:
+                       this.isElementClipped(overlayBounds, scrollContainerBounds),
+               isOverlayOutsideView:
+                       this.isElementOutsideView(overlayBounds, scrollContainerBounds),
+       };
+  }
+
+  /** Whether the element is completely out of the view of any of the
+   * containers. */
+  private isElementOutsideView(
+               elementBounds: ElementBoundingPositions,
+               containersBounds: ElementBoundingPositions[]): boolean {
+       return containersBounds.some(
+               (containerBounds: ElementBoundingPositions) => {
+                       const outsideAbove = elementBounds.bottom < containerBounds.top;
+                       const outsideBelow = elementBounds.top > containerBounds.bottom;
+                       const outsideLeft = elementBounds.right < containerBounds.left;
+                       const outsideRight = elementBounds.left > containerBounds.right;
+
+                       return outsideAbove || outsideBelow || outsideLeft || outsideRight;
+               });
+  }
+
+  /** Whether the element is clipped by any of the containers. */
+  private isElementClipped(
+               elementBounds: ElementBoundingPositions,
+               containersBounds: ElementBoundingPositions[]): boolean {
+       return containersBounds.some(
+               (containerBounds: ElementBoundingPositions) => {
+                       const clippedAbove = elementBounds.top < containerBounds.top;
+                       const clippedBelow = elementBounds.bottom > containerBounds.bottom;
+                       const clippedLeft = elementBounds.left < containerBounds.left;
+                       const clippedRight = elementBounds.right > containerBounds.right;
+
+                       return clippedAbove || clippedBelow || clippedLeft || clippedRight;
+               });
+  }
+
+  /** Physically positions the overlay element to the given coordinate. */
+  private _setElementPosition(
+               element: HTMLElement, overlayRect: ClientRect, overlayPoint: Point,
+               pos: ConnectionPositionPair) {
+    // We want to set either `top` or `bottom` based on whether the overlay
+    // wants to appear above or below the origin and the direction in which the
+    // element will expand.
+       const verticalStyleProperty = pos.overlayY === 'bottom' ? 'bottom' : 'top';
+
+    // When using `bottom`, we adjust the y position such that it is the
+    // distance from the bottom of the viewport rather than the top.
+       const y = verticalStyleProperty === 'top' ?
+               overlayPoint.y :
+               document.documentElement.clientHeight -
+                       (overlayPoint.y + overlayRect.height);
+
+    // We want to set either `left` or `right` based on whether the overlay
+    // wants to appear "before" or "after" the origin, which determines the
+    // direction in which the element will expand. For the horizontal axis, the
+    // meaning of "before" and "after" change based on whether the page is in
+    // RTL or LTR.
+       let horizontalStyleProperty: any;
+       if (this._dir === 'rtl') {
+               horizontalStyleProperty = pos.overlayX === 'end' ? 'left' : 'right';
+       } else {
+               horizontalStyleProperty = pos.overlayX === 'end' ? 'right' : 'left';
+       }
+
+    // When we're setting `right`, we adjust the x position such that it is the
+    // distance from the right edge of the viewport rather than the left edge.
+       const x = horizontalStyleProperty === 'left' ?
+               overlayPoint.x :
+               document.documentElement.clientWidth -
+                       (overlayPoint.x + overlayRect.width);
+
+
+    // Reset any existing styles. This is necessary in case the preferred
+    // position has changed since the last `apply`.
+       ['top', 'bottom', 'left', 'right'].forEach(
+               (p: any) => element.style[p] = null);
+
+       element.style[verticalStyleProperty] = `${y}px`;
+       element.style[horizontalStyleProperty] = `${x}px`;
+  }
+
+  /** Returns the bounding positions of the provided element with respect to the
+   * viewport. */
+  private _getElementBounds(element: HTMLElement): ElementBoundingPositions {
+       const boundingClientRect = element.getBoundingClientRect();
+       return {
+               top: boundingClientRect.top,
+               right: boundingClientRect.left + boundingClientRect.width,
+               bottom: boundingClientRect.top + boundingClientRect.height,
+               left: boundingClientRect.left
+       };
+  }
+
+  /**
+   * Subtracts the amount that an element is overflowing on an axis from it's
+   * length.
+   */
+  private _subtractOverflows(length: number, ...overflows: number[]): number {
+       return overflows.reduce((currentValue: number, currentOverflow: number) => {
+               return currentValue - Math.max(currentOverflow, 0);
+       }, length);
+  }
+}
+
+/** A simple (x, y) coordinate. */
+interface Point {
+  x: number;
+  y: number;
+}
+
+/**
+ * Expands the simple (x, y) coordinate by adding info about whether the
+ * element would fit inside the viewport at that position, as well as
+ * how much of the element would be visible.
+ */
+interface OverlayPoint extends Point {
+  visibleArea: number;
+  fitsInViewport: boolean;
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/connected-position.ts
new file mode 100644 (file)
index 0000000..dad3f04
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+/** Horizontal dimension of a connection point on the perimeter of the origin or
+ * overlay element. */
+import {Optional} from '@angular/core';
+export type HorizontalConnectionPos = 'start' | 'center' | 'end';
+
+/** Vertical dimension of a connection point on the perimeter of the origin or
+ * overlay element. */
+export type VerticalConnectionPos = 'top' | 'center' | 'bottom';
+
+
+/** A connection point on the origin element. */
+export interface OriginConnectionPosition {
+  originX: HorizontalConnectionPos;
+  originY: VerticalConnectionPos;
+}
+
+/** A connection point on the overlay element. */
+export interface OverlayConnectionPosition {
+  overlayX: HorizontalConnectionPos;
+  overlayY: VerticalConnectionPos;
+}
+
+/** The points of the origin element and the overlay element to connect. */
+export class ConnectionPositionPair {
+  originX: HorizontalConnectionPos;
+  originY: VerticalConnectionPos;
+  overlayX: HorizontalConnectionPos;
+  overlayY: VerticalConnectionPos;
+
+  constructor(
+               origin: OriginConnectionPosition, overlay: OverlayConnectionPosition) {
+       this.originX = origin.originX;
+       this.originY = origin.originY;
+       this.overlayX = overlay.overlayX;
+       this.overlayY = overlay.overlayY;
+  }
+}
+
+/**
+ * Set of properties regarding the position of the origin and overlay relative
+ * to the viewport with respect to the containing Scrollable elements.
+ *
+ * The overlay and origin are clipped if any part of their bounding client
+ * rectangle exceeds the bounds of any one of the strategy's Scrollable's
+ * bounding client rectangle.
+ *
+ * The overlay and origin are outside view if there is no overlap between their
+ * bounding client rectangle and any one of the strategy's Scrollable's bounding
+ * client rectangle.
+ *
+ *       -----------                    -----------
+ *       | outside |                    | clipped |
+ *       |  view   |              --------------------------
+ *       |         |              |     |         |        |
+ *       ----------               |     -----------        |
+ *  --------------------------    |                        |
+ *  |                        |    |      Scrollable        |
+ *  |                        |    |                        |
+ *  |                        |     --------------------------
+ *  |      Scrollable        |
+ *  |                        |
+ *  --------------------------
+ */
+export class ScrollableViewProperties {
+  isOriginClipped: boolean;
+  isOriginOutsideView: boolean;
+  isOverlayClipped: boolean;
+  isOverlayOutsideView: boolean;
+}
+
+/** The change event emitted by the strategy when a fallback position is used.
+ */
+export class ConnectedOverlayPositionChange {
+  constructor(
+               public connectionPair: ConnectionPositionPair,
+               @Optional() public scrollableViewProperties: ScrollableViewProperties) {}
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/fake-viewport-ruler.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/fake-viewport-ruler.ts
new file mode 100644 (file)
index 0000000..698aed1
--- /dev/null
@@ -0,0 +1,25 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/** @docs-private */
+export class FakeViewportRuler {
+  getViewportRect() {
+       return {
+               left: 0,
+               top: 0,
+               width: 1014,
+               height: 686,
+               bottom: 686,
+               right: 1014
+       };
+  }
+
+  getViewportScrollPosition() {
+       return {top: 0, left: 0};
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/free-position-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/free-position-strategy.ts
new file mode 100644 (file)
index 0000000..bd33e40
--- /dev/null
@@ -0,0 +1,83 @@
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+import {PositionStrategy} from './position-strategy';
+
+/**
+ * Free position strategy for overlay without origin
+ * @author lingyi.zcs
+ */
+export class FreePositionStrategy implements PositionStrategy {
+  private _wrapper: HTMLElement;
+  // private _cssPosition: string = '';
+  // private _top: string = '';
+  // private _left: string = '';
+  // private _width: string = '';
+  // private _height: string = '';
+
+  // cssPosition(value: string) {
+  //   this._cssPosition = value;
+  //   return this;
+  // }
+
+  // top(value: number | string): this {
+  //   this._top = this._toCssValue(value);
+  //   return this;
+  // }
+
+  // left(value: number | string): this {
+  //   this._left = this._toCssValue(value);
+  //   return this;
+  // }
+
+  // width(value: number | string): this {
+  //   this._width = this._toCssValue(value);
+  //   return this;
+  // }
+
+  // height(value: number | string): this {
+  //   this._height = this._toCssValue(value);
+  //   return this;
+  // }
+
+  /**
+   * Apply the position to the element. (NOTE: normally will triggered by
+   * scrolling)
+   * @docs-private
+   *
+   * @param element Element to which to apply the CSS.
+   * @returns Resolved when the styles have been applied.
+   */
+  apply(element: HTMLElement): void {
+       if (!this._wrapper) {
+               this._wrapper = document.createElement('div');
+               this._wrapper.classList.add('cdk-free-overlay-wrapper');
+               element.parentNode.insertBefore(this._wrapper, element);
+               this._wrapper.appendChild(element);
+
+      // // Initialized style once
+      // const style = element.style;
+      // style.position = this._cssPosition;
+      // style.top = this._top;
+      // style.left = this._left;
+      // style.width = this._width;
+      // style.height = this._height;
+       }
+
+    // TODO: do somethings while triggered (eg. by scrolling)
+  }
+
+  /**
+   * Removes the wrapper element from the DOM.
+   */
+  dispose(): void {
+       if (this._wrapper && this._wrapper.parentNode) {
+               this._wrapper.parentNode.removeChild(this._wrapper);
+               this._wrapper = null;
+       }
+  }
+
+  // private _toCssValue(value: number | string) {
+  //   return typeof value === 'number' ? value + 'px' : value;
+  // }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/global-position-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/global-position-strategy.ts
new file mode 100644 (file)
index 0000000..8e3204a
--- /dev/null
@@ -0,0 +1,178 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+import {PositionStrategy} from './position-strategy';
+
+
+/**
+ * A strategy for positioning overlays. Using this strategy, an overlay is given
+ * an explicit position relative to the browser's viewport. We use flexbox,
+ * instead of transforms, in order to avoid issues with subpixel rendering which
+ * can cause the element to become blurry.
+ */
+export class GlobalPositionStrategy implements PositionStrategy {
+  private _cssPosition = 'static';
+  private _topOffset = '';
+  private _bottomOffset = '';
+  private _leftOffset = '';
+  private _rightOffset = '';
+  private _alignItems = '';
+  private _justifyContent = '';
+  private _width = '';
+  private _height = '';
+
+  /* A lazily-created wrapper for the overlay element that is used as a flex
+   * container.  */
+  private _wrapper: HTMLElement|null = null;
+
+  /**
+   * Sets the top position of the overlay. Clears any previously set vertical
+   * position.
+   * @param value New top offset.
+   */
+  top(value = ''): this {
+       this._bottomOffset = '';
+       this._topOffset = value;
+       this._alignItems = 'flex-start';
+       return this;
+  }
+
+  /**
+   * Sets the left position of the overlay. Clears any previously set horizontal
+   * position.
+   * @param value New left offset.
+   */
+  left(value = ''): this {
+       this._rightOffset = '';
+       this._leftOffset = value;
+       this._justifyContent = 'flex-start';
+       return this;
+  }
+
+  /**
+   * Sets the bottom position of the overlay. Clears any previously set vertical
+   * position.
+   * @param value New bottom offset.
+   */
+  bottom(value = ''): this {
+       this._topOffset = '';
+       this._bottomOffset = value;
+       this._alignItems = 'flex-end';
+       return this;
+  }
+
+  /**
+   * Sets the right position of the overlay. Clears any previously set
+   * horizontal position.
+   * @param value New right offset.
+   */
+  right(value = ''): this {
+       this._leftOffset = '';
+       this._rightOffset = value;
+       this._justifyContent = 'flex-end';
+       return this;
+  }
+
+  /**
+   * Sets the overlay width and clears any previously set width.
+   * @param value New width for the overlay
+   */
+  width(value = ''): this {
+       this._width = value;
+
+    // When the width is 100%, we should reset the `left` and the offset,
+    // in order to ensure that the element is flush against the viewport edge.
+       if (value === '100%') {
+               this.left('0px');
+       }
+
+       return this;
+  }
+
+  /**
+   * Sets the overlay height and clears any previously set height.
+   * @param value New height for the overlay
+   */
+  height(value = ''): this {
+       this._height = value;
+
+    // When the height is 100%, we should reset the `top` and the offset,
+    // in order to ensure that the element is flush against the viewport edge.
+       if (value === '100%') {
+               this.top('0px');
+       }
+
+       return this;
+  }
+
+  /**
+   * Centers the overlay horizontally with an optional offset.
+   * Clears any previously set horizontal position.
+   *
+   * @param offset Overlay offset from the horizontal center.
+   */
+  centerHorizontally(offset = ''): this {
+       this.left(offset);
+       this._justifyContent = 'center';
+       return this;
+  }
+
+  /**
+   * Centers the overlay vertically with an optional offset.
+   * Clears any previously set vertical position.
+   *
+   * @param offset Overlay offset from the vertical center.
+   */
+  centerVertically(offset = ''): this {
+       this.top(offset);
+       this._alignItems = 'center';
+       return this;
+  }
+
+  /**
+   * Apply the position to the element.
+   * @docs-private
+   *
+   * @param element Element to which to apply the CSS.
+   * @returns Resolved when the styles have been applied.
+   */
+  apply(element: HTMLElement): void {
+       if (!this._wrapper && element.parentNode) {
+               this._wrapper = document.createElement('div');
+               this._wrapper.classList.add('cdk-global-overlay-wrapper');
+               element.parentNode.insertBefore(this._wrapper, element);
+               this._wrapper.appendChild(element);
+       }
+
+       const styles = element.style;
+       const parentStyles = (element.parentNode as HTMLElement).style;
+
+       styles.position = this._cssPosition;
+       styles.marginTop = this._topOffset;
+       styles.marginLeft = this._leftOffset;
+       styles.marginBottom = this._bottomOffset;
+       styles.marginRight = this._rightOffset;
+       styles.width = this._width;
+       styles.height = this._height;
+
+       parentStyles.justifyContent = this._justifyContent;
+       parentStyles.alignItems = this._alignItems;
+  }
+
+  /**
+   * Removes the wrapper element from the DOM.
+   */
+  dispose(): void {
+       if (this._wrapper && this._wrapper.parentNode) {
+               this._wrapper.parentNode.removeChild(this._wrapper);
+               this._wrapper = null;
+       }
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/overlay-position-builder.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/overlay-position-builder.ts
new file mode 100644 (file)
index 0000000..0f6735e
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+import {ElementRef, Injectable} from '@angular/core';
+
+import {OriginConnectionPosition, OverlayConnectionPosition} from './connected-position';
+import {ConnectedPositionStrategy} from './connected-position-strategy';
+import {FreePositionStrategy} from './free-position-strategy';
+import {GlobalPositionStrategy} from './global-position-strategy';
+import {ViewportRuler} from './viewport-ruler';
+
+
+/** Builder for overlay position strategy. */
+@Injectable()
+export class OverlayPositionBuilder {
+  constructor(private _viewportRuler: ViewportRuler) {}
+
+  /**
+   * Creates a free position strategy
+   */
+  free(): FreePositionStrategy {
+       return new FreePositionStrategy();
+  }
+
+  /**
+   * Creates a global position strategy.
+   */
+  global(): GlobalPositionStrategy {
+       return new GlobalPositionStrategy();
+  }
+
+  /**
+   * Creates a relative position strategy.
+   * @param elementRef
+   * @param originPos
+   * @param overlayPos
+   */
+  connectedTo(
+               elementRef: ElementRef, originPos: OriginConnectionPosition,
+               overlayPos: OverlayConnectionPosition): ConnectedPositionStrategy {
+       return new ConnectedPositionStrategy(
+               elementRef, originPos, overlayPos, this._viewportRuler);
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/position-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/position-strategy.ts
new file mode 100644 (file)
index 0000000..21bd0fa
--- /dev/null
@@ -0,0 +1,17 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/** Strategy for setting the position on an overlay. */
+export interface PositionStrategy {
+  /** Updates the position of the overlay element. */
+  apply(element: Element): void;
+
+  /** Cleans up any DOM modifications made by the position strategy, if
+   * necessary. */
+  dispose(): void;
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts
new file mode 100644 (file)
index 0000000..298cd64
--- /dev/null
@@ -0,0 +1,110 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unnecessary-type-assertion arrow-parens*/
+import {Injectable, Optional, SkipSelf} from '@angular/core';
+import {ScrollDispatcher} from '../scroll/scroll-dispatcher';
+
+
+/**
+ * Simple utility for getting the bounds of the browser viewport.
+ * @docs-private
+ */
+@Injectable()
+export class ViewportRuler {
+  /** Cached document client rectangle. */
+  private _documentRect?: ClientRect;
+
+  constructor(scrollDispatcher: ScrollDispatcher) {
+    // Subscribe to scroll and resize events and update the document rectangle
+    // on changes.
+       scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry());
+  }
+
+  /** Gets a ClientRect for the viewport's bounds. */
+  getViewportRect(documentRect = this._documentRect): ClientRect {
+    // Cache the document bounding rect so that we don't recompute it for
+    // multiple calls.
+       if (!documentRect) {
+               this._cacheViewportGeometry();
+               documentRect = this._documentRect;
+       }
+
+    // Use the document element's bounding rect rather than the window scroll
+    // properties (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE
+    // where window scroll properties and client coordinates
+    // (boundingClientRect, clientX/Y, etc.) are in different conceptual
+    // viewports. Under most circumstances these viewports are equivalent, but
+    // they can disagree when the page is pinch-zoomed (on devices that support
+    // touch). See
+    // https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4 We use
+    // the documentElement instead of the body because, by default (without a
+    // css reset) browsers typically give the document body an 8px margin, which
+    // is not included in getBoundingClientRect().
+       const scrollPosition = this.getViewportScrollPosition(documentRect);
+       const height = window.innerHeight;
+       const width = window.innerWidth;
+
+       return {
+               top: scrollPosition.top,
+               left: scrollPosition.left,
+               bottom: scrollPosition.top + height,
+               right: scrollPosition.left + width,
+               height,
+               width,
+       };
+  }
+
+
+  /**
+   * Gets the (top, left) scroll position of the viewport.
+   * @param documentRect
+   */
+  getViewportScrollPosition(documentRect = this._documentRect) {
+    // Cache the document bounding rect so that we don't recompute it for
+    // multiple calls.
+       if (!documentRect) {
+               this._cacheViewportGeometry();
+               documentRect = this._documentRect;
+       }
+
+    // The top-left-corner of the viewport is determined by the scroll position
+    // of the document body, normally just (scrollLeft, scrollTop). However,
+    // Chrome and Firefox disagree about whether `document.body` or
+    // `document.documentElement` is the scrolled element, so reading
+    // `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding
+    // rect of `document.documentElement` works consistently, where the `top`
+    // and `left` values will equal negative the scroll position.
+       const top = -documentRect.top || document.body.scrollTop ||
+               window.scrollY || document.documentElement.scrollTop || 0;
+
+       const left = -documentRect.left || document.body.scrollLeft ||
+               window.scrollX || document.documentElement.scrollLeft || 0;
+
+       return {top, left};
+  }
+
+  /** Caches the latest client rectangle of the document element. */
+  _cacheViewportGeometry() {
+       this._documentRect = document.documentElement.getBoundingClientRect();
+  }
+}
+
+export function VIEWPORT_RULER_PROVIDER_FACTORY(
+       parentRuler: ViewportRuler, scrollDispatcher: ScrollDispatcher) {
+  return parentRuler || new ViewportRuler(scrollDispatcher);
+}
+
+export const VIEWPORT_RULER_PROVIDER = {
+  // If there is already a ViewportRuler available, use that. Otherwise, provide
+  // a new one.
+  provide: ViewportRuler,
+  deps: [[new Optional(), new SkipSelf(), ViewportRuler], ScrollDispatcher],
+  useFactory: VIEWPORT_RULER_PROVIDER_FACTORY
+};
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/block-scroll-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/block-scroll-strategy.ts
new file mode 100644 (file)
index 0000000..d1c1d40
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {ViewportRuler} from '../position/viewport-ruler';
+
+import {ScrollStrategy} from './scroll-strategy';
+
+/**
+ * Strategy that will prevent the user from scrolling while the overlay is
+ * visible.
+ */
+export class BlockScrollStrategy implements ScrollStrategy {
+  private _previousHTMLStyles = {top: '', left: ''};
+  private _previousScrollPosition: {top: number, left: number};
+  private _isEnabled = false;
+
+  constructor(private _viewportRuler: ViewportRuler) {}
+
+  attach() {
+    //
+  }
+
+  enable() {
+       if (this._canBeEnabled()) {
+               const root = document.documentElement;
+
+               this._previousScrollPosition =
+                       this._viewportRuler.getViewportScrollPosition();
+
+      // Cache the previous inline styles in case the user had set them.
+               this._previousHTMLStyles.left = root.style.left || '';
+               this._previousHTMLStyles.top = root.style.top || '';
+
+      // Note: we're using the `html` node, instead of the `body`, because the
+      // `body` may have the user agent margin, whereas the `html` is guaranteed
+      // not to have one.
+               root.style.left = `${- this._previousScrollPosition.left}px`;
+               root.style.top = `${- this._previousScrollPosition.top}px`;
+               root.classList.add('cdk-global-scrollblock');
+               this._isEnabled = true;
+       }
+  }
+
+  disable() {
+       if (this._isEnabled) {
+               this._isEnabled = false;
+               document.documentElement.style.left = this._previousHTMLStyles.left;
+               document.documentElement.style.top = this._previousHTMLStyles.top;
+               document.documentElement.classList.remove('cdk-global-scrollblock');
+               window.scroll(
+                       this._previousScrollPosition.left, this._previousScrollPosition.top);
+       }
+  }
+
+  private _canBeEnabled(): boolean {
+    // Since the scroll strategies can't be singletons, we have to use a global
+    // CSS class
+    // (`cdk-global-scrollblock`) to make sure that we don't try to disable
+    // global scrolling multiple times.
+       if (document.documentElement.classList.contains('cdk-global-scrollblock') ||
+               this._isEnabled) {
+               return false;
+       }
+
+       const body = document.body;
+       const viewport = this._viewportRuler.getViewportRect();
+       return body.scrollHeight > viewport.height ||
+               body.scrollWidth > viewport.width;
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/close-scroll-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/close-scroll-strategy.ts
new file mode 100644 (file)
index 0000000..51189dc
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Subscription} from 'rxjs/Subscription';
+
+import {OverlayRef} from '../overlay-ref';
+
+import {ScrollDispatcher} from './scroll-dispatcher';
+import {getMdScrollStrategyAlreadyAttachedError, ScrollStrategy} from './scroll-strategy';
+
+
+/**
+ * Strategy that will close the overlay as soon as the user starts scrolling.
+ */
+export class CloseScrollStrategy implements ScrollStrategy {
+  private _scrollSubscription: Subscription|null = null;
+  private _overlayRef: OverlayRef;
+
+  constructor(private _scrollDispatcher: ScrollDispatcher) {}
+
+  attach(overlayRef: OverlayRef) {
+       if (this._overlayRef) {
+               throw getMdScrollStrategyAlreadyAttachedError();
+       }
+
+       this._overlayRef = overlayRef;
+  }
+
+  enable() {
+       if (!this._scrollSubscription) {
+               this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => {
+               if (this._overlayRef.hasAttached()) {
+                       this._overlayRef.detach();
+               }
+
+               this.disable();
+               });
+       }
+  }
+
+  disable() {
+       if (this._scrollSubscription) {
+               this._scrollSubscription.unsubscribe();
+               this._scrollSubscription = null;
+       }
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/index.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/index.ts
new file mode 100644 (file)
index 0000000..e386770
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {PlatformModule} from '@angular/cdk';
+import {NgModule} from '@angular/core';
+
+import {SCROLL_DISPATCHER_PROVIDER} from './scroll-dispatcher';
+import {ScrollStrategyOptions} from './scroll-strategy-options';
+import {Scrollable} from './scrollable';
+
+export {BlockScrollStrategy} from './block-scroll-strategy';
+export {CloseScrollStrategy} from './close-scroll-strategy';
+export {NoopScrollStrategy} from './noop-scroll-strategy';
+export {RepositionScrollStrategy} from './reposition-scroll-strategy';
+export {ScrollDispatcher} from './scroll-dispatcher';
+// Export pre-defined scroll strategies and interface to build custom ones.
+export {ScrollStrategy} from './scroll-strategy';
+export {ScrollStrategyOptions} from './scroll-strategy-options';
+export {Scrollable} from './scrollable';
+
+@NgModule({
+  imports: [PlatformModule],
+  exports: [Scrollable],
+  declarations: [Scrollable],
+  providers: [SCROLL_DISPATCHER_PROVIDER, ScrollStrategyOptions],
+})
+export class ScrollDispatchModule {
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/noop-scroll-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/noop-scroll-strategy.ts
new file mode 100644 (file)
index 0000000..9b92ab4
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {ScrollStrategy} from './scroll-strategy';
+
+/**
+ * Scroll strategy that doesn't do anything.
+ */
+export class NoopScrollStrategy implements ScrollStrategy {
+  enable() {
+    //
+  }
+  disable() {
+    //
+  }
+  attach() {
+    //
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/reposition-scroll-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/reposition-scroll-strategy.ts
new file mode 100644 (file)
index 0000000..b15d5de
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Subscription} from 'rxjs/Subscription';
+
+import {OverlayRef} from '../overlay-ref';
+
+import {ScrollDispatcher} from './scroll-dispatcher';
+import {getMdScrollStrategyAlreadyAttachedError, ScrollStrategy} from './scroll-strategy';
+
+/**
+ * Config options for the RepositionScrollStrategy.
+ */
+export interface RepositionScrollStrategyConfig { scrollThrottle?: number; }
+
+/**
+ * Strategy that will update the element position as the user is scrolling.
+ */
+export class RepositionScrollStrategy implements ScrollStrategy {
+  private _scrollSubscription: Subscription|null = null;
+  private _overlayRef: OverlayRef;
+
+  constructor(
+               private _scrollDispatcher: ScrollDispatcher,
+               private _config?: RepositionScrollStrategyConfig) {}
+
+  attach(overlayRef: OverlayRef) {
+       if (this._overlayRef) {
+               throw getMdScrollStrategyAlreadyAttachedError();
+       }
+
+       this._overlayRef = overlayRef;
+  }
+
+  enable() {
+       if (!this._scrollSubscription) {
+               const throttle = this._config ? this._config.scrollThrottle : 0;
+
+               this._scrollSubscription =
+                       this._scrollDispatcher.scrolled(throttle, () => {
+                       this._overlayRef.updatePosition();
+                       });
+       }
+  }
+
+  disable() {
+       if (this._scrollSubscription) {
+               this._scrollSubscription.unsubscribe();
+               this._scrollSubscription = null;
+       }
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts
new file mode 100644 (file)
index 0000000..2c145af
--- /dev/null
@@ -0,0 +1,174 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Platform} from '@angular/cdk';
+import {auditTime} from 'rxjs/operator/auditTime';
+import {ElementRef, Injectable, NgZone, Optional, SkipSelf} from '@angular/core';
+import {fromEvent} from 'rxjs/observable/fromEvent';
+import {merge} from 'rxjs/observable/merge';
+import {Subject} from 'rxjs/Subject';
+import {Subscription} from 'rxjs/Subscription';
+
+import {Scrollable} from './scrollable';
+
+
+/** Time in ms to throttle the scrolling events by default. */
+export const DEFAULT_SCROLL_TIME = 20;
+
+/**
+ * Service contained all registered Scrollable references and emits an event
+ * when any one of the Scrollable references emit a scrolled event.
+ */
+@Injectable()
+export class ScrollDispatcher {
+  /** Subject for notifying that a registered scrollable reference element has
+   * been scrolled. */
+  _scrolled: Subject<void> = new Subject<void>();
+
+  /** Keeps track of the global `scroll` and `resize` subscriptions. */
+  _globalSubscription: Subscription|null = null;
+
+  /** Keeps track of the amount of subscriptions to `scrolled`. Used for
+   * cleaning up afterwards. */
+  private _scrolledCount = 0;
+
+  /**
+   * Map of all the scrollable references that are registered with the service
+   * and their scroll event subscriptions.
+   */
+  scrollableReferences: Map<Scrollable, Subscription> = new Map();
+
+  constructor(private _ngZone: NgZone, private _platform: Platform) {}
+
+  /**
+   * Registers a Scrollable with the service and listens for its scrolled
+   * events. When the scrollable is scrolled, the service emits the event in its
+   * scrolled observable.
+   * @param scrollable Scrollable instance to be registered.
+   */
+  register(scrollable: Scrollable): void {
+       const scrollSubscription =
+               scrollable.elementScrolled().subscribe(() => this._notify());
+
+       this.scrollableReferences.set(scrollable, scrollSubscription);
+  }
+
+  /**
+   * Deregisters a Scrollable reference and unsubscribes from its scroll event
+   * observable.
+   * @param scrollable Scrollable instance to be deregistered.
+   */
+  deregister(scrollable: Scrollable): void {
+       const scrollableReference = this.scrollableReferences.get(scrollable);
+
+       if (scrollableReference) {
+               scrollableReference.unsubscribe();
+               this.scrollableReferences.delete(scrollable);
+       }
+  }
+
+  /**
+   * Subscribes to an observable that emits an event whenever any of the
+   * registered Scrollable references (or window, document, or body) fire a
+   * scrolled event. Can provide a time in ms to override the default "throttle"
+   * time.
+   */
+  scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME, callback: () => any):
+               Subscription {
+    // Scroll events can only happen on the browser, so do nothing if we're not
+    // on the browser.
+       if (!this._platform.isBrowser) {
+               return Subscription.EMPTY;
+       }
+
+    // In the case of a 0ms delay, use an observable without auditTime
+    // since it does add a perceptible delay in processing overhead.
+       const observable = auditTimeInMs > 0 ?
+               auditTime.call(this._scrolled.asObservable(), auditTimeInMs) :
+               this._scrolled.asObservable();
+
+       this._scrolledCount++;
+
+       if (!this._globalSubscription) {
+               this._globalSubscription = this._ngZone.runOutsideAngular(() => {
+               return merge(
+                                       fromEvent(window.document, 'scroll'),
+                                       fromEvent(window, 'resize'))
+                       .subscribe(() => this._notify());
+               });
+       }
+
+    // Note that we need to do the subscribing from here, in order to be able to
+    // remove the global event listeners once there are no more subscriptions.
+       const subscription = observable.subscribe(callback);
+
+       subscription.add(() => {
+               this._scrolledCount--;
+
+               if (this._globalSubscription && !this.scrollableReferences.size &&
+                       !this._scrolledCount) {
+               this._globalSubscription.unsubscribe();
+               this._globalSubscription = null;
+               }
+       });
+
+       return subscription;
+  }
+
+  /** Returns all registered Scrollables that contain the provided element. */
+  getScrollContainers(elementRef: ElementRef): Scrollable[] {
+       const scrollingContainers: Scrollable[] = [];
+
+       this.scrollableReferences.forEach(
+               (_subscription: Subscription, scrollable: Scrollable) => {
+                       if (this.scrollableContainsElement(scrollable, elementRef)) {
+                       scrollingContainers.push(scrollable);
+                       }
+               });
+
+       return scrollingContainers;
+  }
+
+  /** Returns true if the element is contained within the provided Scrollable.
+   */
+  scrollableContainsElement(scrollable: Scrollable, elementRef: ElementRef):
+               boolean {
+       let element = elementRef.nativeElement;
+       const scrollableElement = scrollable.getElementRef().nativeElement;
+
+    // Traverse through the element parents until we reach null, checking if any
+    // of the elements are the scrollable's element.
+       do {
+               if (element === scrollableElement) {
+               return true;
+               }
+       } while (element = element.parentElement);
+
+       return false;
+  }
+
+  /** Sends a notification that a scroll event has been fired. */
+  _notify() {
+       this._scrolled.next();
+  }
+}
+
+export function SCROLL_DISPATCHER_PROVIDER_FACTORY(
+       parentDispatcher: ScrollDispatcher, ngZone: NgZone, platform: Platform) {
+  return parentDispatcher || new ScrollDispatcher(ngZone, platform);
+}
+
+export const SCROLL_DISPATCHER_PROVIDER = {
+  // If there is already a ScrollDispatcher available, use that. Otherwise,
+  // provide a new one.
+  provide: ScrollDispatcher,
+  deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone, Platform],
+  useFactory: SCROLL_DISPATCHER_PROVIDER_FACTORY
+};
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-strategy-options.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-strategy-options.ts
new file mode 100644 (file)
index 0000000..f627038
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector no-unused-expression*/
+import {Injectable} from '@angular/core';
+
+import {ViewportRuler} from '../position/viewport-ruler';
+
+import {BlockScrollStrategy} from './block-scroll-strategy';
+import {CloseScrollStrategy} from './close-scroll-strategy';
+import {NoopScrollStrategy} from './noop-scroll-strategy';
+import {RepositionScrollStrategy, RepositionScrollStrategyConfig} from './reposition-scroll-strategy';
+import {ScrollDispatcher} from './scroll-dispatcher';
+// import {ScrollStrategy} from './scroll-strategy';
+
+
+/**
+ * Options for how an overlay will handle scrolling.
+ *
+ * Users can provide a custom value for `ScrollStrategyOptions` to replace the
+ * default behaviors. This class primarily acts as a factory for ScrollStrategy
+ * instances.
+ */
+@Injectable()
+export class ScrollStrategyOptions {
+  constructor(
+               private _scrollDispatcher: ScrollDispatcher,
+               private _viewportRuler: ViewportRuler) {}
+
+  /** Do nothing on scroll. */
+  noop = () => new NoopScrollStrategy();
+
+  /** Close the overlay as soon as the user scrolls. */
+  close = () => new CloseScrollStrategy(this._scrollDispatcher);
+
+  /** Block scrolling. */
+  block = () => new BlockScrollStrategy(this._viewportRuler);
+
+  /**
+   * Update the overlay's position on scroll.
+   * @param config Configuration to be used inside the scroll strategy.
+   * Allows debouncing the reposition calls.
+   */
+  reposition = (config?: RepositionScrollStrategyConfig) =>
+               new RepositionScrollStrategy(this._scrollDispatcher, config)
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-strategy.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-strategy.ts
new file mode 100644 (file)
index 0000000..d59651a
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {OverlayRef} from '../overlay-ref';
+
+/**
+ * Describes a strategy that will be used by an overlay
+ * to handle scroll events while it is open.
+ */
+export abstract class ScrollStrategy {
+  enable: () => void;
+  disable: () => void;
+  attach: (overlayRef: OverlayRef) => void;
+}
+
+/**
+ * Returns an error to be thrown when attempting to attach an already-attached
+ * scroll strategy.
+ */
+export function getMdScrollStrategyAlreadyAttachedError(): Error {
+  return Error(`Scroll strategy has already been attached.`);
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scrollable.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scrollable.ts
new file mode 100644 (file)
index 0000000..fe7b041
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+/* tslint:disable:array-type member-access variable-name typedef
+ only-arrow-functions directive-class-suffix component-class-suffix
+ component-selector*/
+import {Directive, ElementRef, NgZone, OnDestroy, OnInit, Renderer2} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Subject} from 'rxjs/Subject';
+
+import {ScrollDispatcher} from './scroll-dispatcher';
+
+
+/**
+ * Sends an event when the directive's element is scrolled. Registers itself
+ * with the ScrollDispatcher service to include itself as part of its collection
+ * of scrolling events that it can be listened to through the service.
+ */
+@Directive({selector: '[cdk-scrollable], [cdkScrollable]'})
+export class Scrollable implements OnInit, OnDestroy {
+  private _elementScrolled: Subject<Event> = new Subject();
+  private _scrollListener: Function|null;
+
+  constructor(
+               private _elementRef: ElementRef, private _scroll: ScrollDispatcher,
+               private _ngZone: NgZone, private _renderer: Renderer2) {}
+
+  ngOnInit() {
+       this._scrollListener = this._ngZone.runOutsideAngular(() => {
+               return this._renderer.listen(
+                       this.getElementRef().nativeElement, 'scroll', (event: Event) => {
+                       this._elementScrolled.next(event);
+                       });
+       });
+
+       this._scroll.register(this);
+  }
+
+  ngOnDestroy() {
+       this._scroll.deregister(this);
+
+       if (this._scrollListener) {
+               this._scrollListener();
+               this._scrollListener = null;
+       }
+  }
+
+  /**
+   * Returns observable that emits when a scroll event is fired on the host
+   * element.
+   */
+  elementScrolled(): Observable<any> {
+       return this._elementScrolled.asObservable();
+  }
+
+  getElementRef(): ElementRef {
+       return this._elementRef;
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlaypanel/index.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlaypanel/index.ts
new file mode 100644 (file)
index 0000000..4ad2b4a
--- /dev/null
@@ -0,0 +1 @@
+export * from './overlaypanel';
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlaypanel/overlaypanel.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlaypanel/overlaypanel.ts
new file mode 100644 (file)
index 0000000..ee529c5
--- /dev/null
@@ -0,0 +1,163 @@
+import {CommonModule} from '@angular/common';
+import {AfterViewInit, Component, ElementRef, EventEmitter, Input, NgModule, OnDestroy, OnInit, Output, Renderer} from '@angular/core';
+
+import {DomHandler} from '../domhandler';
+
+@Component({
+  selector: 'plx-overlay-panel',
+  template: `
+        <div [ngClass]="'ui-overlaypanel ui-widget ui-widget-content ui-corner-all ui-shadow'" [ngStyle]="style" [class]="styleClass"
+            [style.display]="visible ? 'block' : 'none'" (click)="onPanelClick()">
+            <div class="ui-overlaypanel-content">
+                <ng-content></ng-content>
+            </div>
+            <a href="#" *ngIf="showCloseIcon" class="ui-overlaypanel-close ui-state-default" (click)="onCloseClick($event)">
+                <span class="fa fa-fw fa-close"></span>
+            </a>
+        </div>
+    `,
+  providers: [DomHandler]
+})
+export class PlxOverlayPanelComponent implements OnInit, AfterViewInit,
+                                                                                               OnDestroy {
+  @Input() dismissable: boolean = true;
+
+  @Input() showCloseIcon: boolean;
+
+  @Input() style: any;
+
+  @Input() styleClass: string;
+
+  @Input() appendTo: any;
+
+  @Output() onBeforeShow: EventEmitter<any> = new EventEmitter();
+
+  @Output() onAfterShow: EventEmitter<any> = new EventEmitter();
+
+  @Output() onBeforeHide: EventEmitter<any> = new EventEmitter();
+
+  @Output() onAfterHide: EventEmitter<any> = new EventEmitter();
+
+  container: any;
+
+  visible: boolean = false;
+
+  documentClickListener: any;
+
+  selfClick: boolean;
+
+  targetEvent: boolean;
+
+  target: any;
+
+  constructor(
+               public el: ElementRef, public domHandler: DomHandler,
+               public renderer: Renderer) {}
+
+  ngOnInit() {
+       if (this.dismissable) {
+               this.documentClickListener =
+                       this.renderer.listenGlobal('body', 'click', () => {
+                       if (!this.selfClick && !this.targetEvent) {
+                               this.hide();
+                       }
+                       this.selfClick = false;
+                       this.targetEvent = false;
+                       });
+       }
+  }
+
+  ngAfterViewInit() {
+       this.container = this.el.nativeElement.children[0];
+       if (this.appendTo) {
+               if (this.appendTo === 'body') {
+               document.body.appendChild(this.container);
+               } else {
+               this.domHandler.appendChild(this.container, this.appendTo);
+               }
+       }
+  }
+
+  toggle(event: any, target?: any) {
+       let currentTarget = (target || event.currentTarget || event.target);
+
+       if (!this.target || this.target === currentTarget) {
+               if (this.visible) {
+               this.hide();
+               } else {
+               this.show(event, target);
+               }
+       } else {
+               this.show(event, target);
+       }
+
+       if (this.dismissable) {
+               this.targetEvent = true;
+       }
+
+       this.target = currentTarget;
+  }
+
+  show(event: any, target?: any) {
+       if (this.dismissable) {
+               this.targetEvent = true;
+       }
+
+       this.onBeforeShow.emit(null);
+       let elementTarget = target || event.currentTarget || event.target;
+       this.container.style.zIndex = ++DomHandler.zindex;
+
+       if (this.visible) {
+               this.domHandler.absolutePosition(this.container, elementTarget);
+       } else {
+               this.visible = true;
+               this.domHandler.absolutePosition(this.container, elementTarget);
+               this.domHandler.fadeIn(this.container, 250);
+       }
+       this.onAfterShow.emit(null);
+  }
+
+  hide() {
+       if (this.visible) {
+               this.onBeforeHide.emit(null);
+               this.visible = false;
+               this.onAfterHide.emit(null);
+       }
+  }
+
+  onPanelClick() {
+       if (this.dismissable) {
+               this.selfClick = true;
+       }
+  }
+
+  onCloseClick(event: any) {
+       this.hide();
+
+       if (this.dismissable) {
+               this.selfClick = true;
+       }
+
+       event.preventDefault();
+  }
+
+  ngOnDestroy() {
+       if (this.documentClickListener) {
+               this.documentClickListener();
+       }
+
+       if (this.appendTo) {
+               this.el.nativeElement.appendChild(this.container);
+       }
+
+       this.target = null;
+  }
+}
+
+@NgModule({
+  imports: [CommonModule],
+  exports: [PlxOverlayPanelComponent],
+  declarations: [PlxOverlayPanelComponent]
+})
+export class PlxOverlayPanelModule {
+}
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button-state.ts b/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button-state.ts
new file mode 100644 (file)
index 0000000..2f1f73b
--- /dev/null
@@ -0,0 +1,6 @@
+export enum PlxButtonState {
+  IDLE,
+  DOING,
+  SUCCESS,
+  FAILURE
+}
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button.directive.ts b/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button.directive.ts
new file mode 100644 (file)
index 0000000..842b9fb
--- /dev/null
@@ -0,0 +1,178 @@
+import {AfterViewInit, Directive, ElementRef, Input, OnDestroy} from '@angular/core';
+
+import {DomHandler} from '../domhandler';
+
+import {PlxButtonState} from './button-state';
+
+@Directive({selector: '[pxButton]', providers: [DomHandler]})
+export class PlxButtonDirective implements AfterViewInit, OnDestroy {
+  @Input() iconPos: string = 'left';
+
+  @Input() cornerStyleClass: string = 'ui-corner-all';
+
+  _label: string;
+
+  _loadinglabel: string;
+
+  _icon: string;
+
+  _state: number;
+
+  initialized: boolean;
+
+  constructor(
+               public el: ElementRef, public domHandler: DomHandler) {}
+
+  ngAfterViewInit() {
+       this.domHandler.addMultipleClasses(
+               this.el.nativeElement, this.getStyleClass());
+       if (this.icon) {
+               let iconElement = document.createElement('span');
+               let iconPosClass = (this.iconPos === 'right') ? 'ui-button-icon-right' :
+                                                                                                               'ui-button-icon-left';
+               iconElement.className =
+                       iconPosClass + ' ui-c iconfont plx-icon-' + this.icon;
+               this.el.nativeElement.appendChild(iconElement);
+       }
+
+       let iconAnimationElement = document.createElement('span');
+       iconAnimationElement.className =
+               'ui-button-icon-left ui-c iconfont plx-icon-circle-o-notch plx-spin';
+       iconAnimationElement.style.display = 'none';
+       this.el.nativeElement.appendChild(iconAnimationElement);
+
+       let labelElement = document.createElement('span');
+       labelElement.className = 'ui-button-text ui-c';
+       labelElement.appendChild(document.createTextNode(this.label || ''));
+       this.el.nativeElement.appendChild(labelElement);
+
+       if (this.state) {
+               let spanElement =
+                       this.domHandler.findSingle(this.el.nativeElement, '.ui-button-text');
+               if (this.state === PlxButtonState.DOING) {
+               if (spanElement) {
+                       spanElement.innerText = this.loadinglabel || 'loading';
+               }
+               this.el.nativeElement.disabled = true;
+               this.setIconELement(true);
+               } else {
+               spanElement.innerText = this.label || '';
+               this.el.nativeElement.disabled = false;
+               this.setIconELement(false);
+               }
+       }
+
+       this.initialized = true;
+  }
+
+  getStyleClass(): string {
+       let styleClass =
+               'ui-button ui-widget ui-state-default ' + this.cornerStyleClass;
+       if (this.icon) {
+               if (this.label  !== null && this.label  !== undefined) {
+               if (this.iconPos === 'left') {
+                       styleClass = styleClass + ' ui-button-text-icon-left';
+               } else {
+                       styleClass = styleClass + ' ui-button-text-icon-right';
+               }
+               } else {
+               styleClass = styleClass + ' ui-button-icon-only';
+               }
+       } else {
+               styleClass = styleClass + ' ui-button-text-only';
+       }
+
+       return styleClass;
+  }
+
+  setIconELement(isShowAnimation: boolean) {
+       let iconLeftElement = this.domHandler.findSingle(
+               this.el.nativeElement, '.ui-button-icon-left.iconfont');
+       if (iconLeftElement) {
+               iconLeftElement.style.display = isShowAnimation ? 'none' : 'inline-block';
+       }
+       let iconRightElement = this.domHandler.findSingle(
+               this.el.nativeElement, '.ui-button-icon-left.iconfont');
+       if (iconRightElement) {
+               iconRightElement.style.display =
+                       isShowAnimation ? 'none' : 'inline-block';
+       }
+       let iconAnimationElement = this.domHandler.findSingle(
+               this.el.nativeElement, '.iconfont.plx-icon-circle-o-notch.plx-spin');
+       if (iconAnimationElement) {
+               iconAnimationElement.style.display =
+                       isShowAnimation ? 'inline-block' : 'none';
+       }
+  }
+
+  @Input()
+  get label(): string {
+       return this._label;
+  }
+
+  set label(val: string) {
+       this._label = val;
+
+       if (this.initialized) {
+               this.domHandler.findSingle(this.el.nativeElement, '.ui-button-text')
+                       .textContent = this._label;
+       }
+  }
+
+  @Input()
+  get loadinglabel(): string {
+       return this._loadinglabel;
+  }
+
+  set loadinglabel(val: string) {
+       this._loadinglabel = val;
+  }
+
+  @Input()
+  get icon(): string {
+       return this._icon;
+  }
+
+  set icon(val: string) {
+       this._icon = val;
+
+       if (this.initialized) {
+               let iconPosClass = (this.iconPos === 'right') ? 'ui-button-icon-right' :
+                                                                                                               'ui-button-icon-left';
+               this.domHandler.findSingle(this.el.nativeElement, '.iconfont').className =
+                       iconPosClass + ' ui-c iconfont plx-icon-' + this.icon;
+       }
+  }
+
+  @Input()
+  get state(): number {
+       return this._state;
+  }
+
+  set state(val: number) {
+       this._state = val;
+       if (this.initialized) {
+               let spanElement =
+                       this.domHandler.findSingle(this.el.nativeElement, '.ui-button-text');
+               if (this.state === PlxButtonState.DOING) {
+               if (spanElement) {
+                       spanElement.innerText = this.loadinglabel || 'loading';
+               }
+               this.el.nativeElement.disabled = true;
+               this.setIconELement(true);
+               } else {
+               spanElement.innerText = this.label || '';
+               this.el.nativeElement.disabled = false;
+               this.setIconELement(false);
+               }
+       }
+  }
+
+  ngOnDestroy() {
+       while (this.el.nativeElement.hasChildNodes()) {
+               this.el.nativeElement.removeChild(this.el.nativeElement.lastChild);
+       }
+
+       this.initialized = false;
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button.module.ts b/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/button.module.ts
new file mode 100644 (file)
index 0000000..0197329
--- /dev/null
@@ -0,0 +1,14 @@
+/**
+ * Created by 10190264 on 2016/12/15.
+ */
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {PlxButtonDirective} from './button.directive';
+
+@NgModule({
+  imports: [CommonModule],
+  exports: [PlxButtonDirective],
+  declarations: [PlxButtonDirective]
+})
+export class PlxButtonModule {
+}
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/index.ts b/sdc-workflow-designer-ui/src/app/paletx/core/pxbutton/index.ts
new file mode 100644 (file)
index 0000000..f435b24
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './button.module';
+export * from './button-state';
\ No newline at end of file
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/select.service.ts b/sdc-workflow-designer-ui/src/app/paletx/core/select.service.ts
new file mode 100644 (file)
index 0000000..ba6f579
--- /dev/null
@@ -0,0 +1,57 @@
+/* tslint:disable:array-type member-access variable-name */
+import {Injectable} from '@angular/core';
+
+@Injectable()
+export class SelectService {
+  selection: string[] = [];
+
+  selected(indexName: string): boolean {
+       if (this.selection === undefined || this.selection === []) {
+               return null;
+       }
+
+       for (let item of this.selection) {
+               if (item === indexName) {
+               return true;
+               }
+       }
+       return false;
+  }
+
+  handleSingleSelect(optionIndex: string) {
+       this.selection = [];
+       this.selection.push(optionIndex);
+       return this.selection;
+  }
+
+  handleMutipleSelect(optionIndex: string) {
+       if (this.selected(optionIndex)) {
+               this.selection = this.handleSecondSelect(optionIndex);
+       } else {
+               this.selection.push(optionIndex);
+       }
+       return this.selection;
+  }
+
+  handleSecondSelect(optionIndex: string) {
+       let selectedOption = [];
+       for (let option of this.selection) {
+               if (option !== optionIndex) {
+               selectedOption.push(option);
+               }
+       }
+       return selectedOption;
+  }
+
+  select(optionIndex: string, isMutiple: boolean): string[] {
+       if (!isMutiple) {
+               return this.handleSingleSelect(optionIndex);
+       } else {
+               return this.handleMutipleSelect(optionIndex);
+       }
+  }
+
+  deselect() {
+       this.selection = [];
+  }
+}
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/uuid.ts b/sdc-workflow-designer-ui/src/app/paletx/core/uuid.ts
new file mode 100644 (file)
index 0000000..58756b6
--- /dev/null
@@ -0,0 +1,36 @@
+/* tslint:disable:array-type member-access variable-name */
+export class UUID {
+  constructor() {
+      //
+  }
+
+  static UUID(): string {
+       if (typeof(window.crypto) !== 'undefined' &&
+               typeof(window.crypto.getRandomValues) !== 'undefined') {
+      // If we have a cryptographically secure PRNG, use that
+               let buf: Uint16Array = new Uint16Array(8);
+               window.crypto.getRandomValues(buf);
+               return (
+                       this.pad4(buf[0]) + this.pad4(buf[1]) + '-' + this.pad4(buf[2]) +
+                       '-' + this.pad4(buf[3]) + '-' + this.pad4(buf[4]) + '-' +
+                       this.pad4(buf[5]) + this.pad4(buf[6]) + this.pad4(buf[7]));
+       } else {
+      // Otherwise, just use Math.random
+               return this.random4() + this.random4() + '-' + this.random4() + '-' +
+                       this.random4() + '-' + this.random4() + '-' + this.random4() +
+                       this.random4() + this.random4();
+       }
+  }
+
+  private static pad4(num: number): string {
+       let ret: string = num.toString(16);
+       while (ret.length < 4) {
+               ret = '0' + ret;
+       }
+       return ret;
+  }
+
+  private static random4(): string {
+       return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+  }
+}