From ea9647d1ef5333b5dee62712ac09cb997cf698c9 Mon Sep 17 00:00:00 2001 From: david limkys Date: Sat, 15 Jul 2017 06:22:29 +0300 Subject: [PATCH 1/4] Added a range component. --- src/modules/range/components/range.ts | 158 ++++++++++++++++++++++++++ src/modules/range/index.ts | 3 + src/modules/range/public.ts | 3 + src/modules/range/range.module.ts | 18 +++ 4 files changed, 182 insertions(+) create mode 100644 src/modules/range/components/range.ts create mode 100644 src/modules/range/index.ts create mode 100644 src/modules/range/public.ts create mode 100644 src/modules/range/range.module.ts diff --git a/src/modules/range/components/range.ts b/src/modules/range/components/range.ts new file mode 100644 index 000000000..e02ad8193 --- /dev/null +++ b/src/modules/range/components/range.ts @@ -0,0 +1,158 @@ +import { Component, Input, Renderer2, ViewChild, EventEmitter, Output, OnInit, ElementRef } from "@angular/core"; + +@Component({ + selector: "sui-range", + template: ` +
+
+
+
+
+
+
` +}) +export class SuiRange implements OnInit { + @Input() public max:number = 100; + @Input() public min:number = 0; + @Input() public step:number = 1; + @Input() public isReadonly:boolean = false; + + @Input() + set value(value:number) { + let finalValue = value; + if (this.isReadonly) { + return; + } + + if (value < this.min) { + finalValue = this.min; + } else if (value > this.max) { + finalValue = this.max; + } + + this._value = finalValue; + this._position = this.determinePosition(this._value); + this.valueChange.emit(this._value); + } + + @Output() + public valueChange:EventEmitter; + + private _left:number; + private _right:number; + private _pageX:number; + private _value:number; + private _precision:number; + private _listenersToRemove:(() => void)[]; + private readonly _offset:number = 10; + + private set _position(position:number) { + let finalPosition:number | string = position; + + if (position < 0) { + finalPosition = 0; + } + + finalPosition = `${finalPosition}px`; + + this._renderer.setStyle(this._thumb.nativeElement, "left", finalPosition); + this._renderer.setStyle(this._trackFill.nativeElement, "width", finalPosition); + } + + @ViewChild("thumb") + private readonly _thumb:ElementRef; + @ViewChild("inner") + private readonly _inner:ElementRef; + @ViewChild("trackFill") + private readonly _trackFill:ElementRef; + + + constructor(private _renderer:Renderer2) { + this.valueChange = new EventEmitter(); + } + + private static getPageX(eventData:MouseEvent & TouchEvent, isTouch:boolean):number { + return isTouch ? eventData.touches[0].pageX : eventData.pageX; + } + + public ngOnInit():void { + this._precision = this.determinePrecision(); + } + + public onDown(eventData:MouseEvent & TouchEvent, isTouch:boolean):void { + this.checkPosition(eventData, isTouch); + + this.setValueByPosition(this.determineValue(this._left, this._right, this._pageX)); + + this._listenersToRemove = this.bindToEvents(isTouch); + } + + public onUp():void { + this._listenersToRemove.forEach(removeListener => removeListener()); + } + + private onMove(eventData:MouseEvent & TouchEvent, isTouch:boolean):void { + this.checkPosition(eventData, isTouch); + + this.setValueByPosition(this.determineValue(this._left, this._right, this._pageX)); + } + + private determinePrecision():number { + const split = String(this.step).split("."); + let decimalPlaces; + if (split.length === 2) { + decimalPlaces = split[1].length; + } else { + decimalPlaces = 0; + } + + return Math.pow(10, decimalPlaces); + } + + private determinePosition(value:number):number { + const ratio = (value - this.min) / (this.max - this.min); + + return Math.round(ratio * this._inner.nativeElement.offsetWidth) - this._offset; + } + + private checkPosition(eventData:MouseEvent&TouchEvent, isTouch:boolean):void { + const boundingClientRect = this._inner.nativeElement.getBoundingClientRect(); + + this._left = boundingClientRect.left; + // This is a false positive. + // tslint:disable-next-line:restrict-plus-operands + this._right = this._left + boundingClientRect.width; + this._pageX = SuiRange.getPageX(eventData, isTouch); + } + + private bindToEvents(isTouch:boolean):(() => void)[] { + const upEventName = isTouch ? "touchend" : "mouseup"; + const moveEventName = isTouch ? "touchmove" : "mousemove"; + + const removeOnUp = this._renderer.listen("window", upEventName, () => this.onUp()); + const removeOnMove = this._renderer.listen("window", moveEventName, e => this.onMove(e, isTouch)); + + return [removeOnUp, removeOnMove]; + + } + + private determineValue(startPos:number, endPos:number, currentPos:number):number { + const ratio = (currentPos - startPos) / (endPos - startPos); + const range = this.max - this.min; + let difference = Math.round(ratio * range / this.step) * this.step; + + difference = Math.round(difference * this._precision) / this._precision; + + return difference + this.min; + } + + private setValueByPosition(value:number):void { + if (this._pageX >= this._left && this._pageX <= this._right) { + if (value >= this.min && value <= this.max) { + this._value = value; + this._position = this._pageX - this._left - this._offset; + this.valueChange.emit(this._value); + } + } + } +} diff --git a/src/modules/range/index.ts b/src/modules/range/index.ts new file mode 100644 index 000000000..7e110a1c7 --- /dev/null +++ b/src/modules/range/index.ts @@ -0,0 +1,3 @@ +export * from "./components/range"; + +export * from "./range.module"; diff --git a/src/modules/range/public.ts b/src/modules/range/public.ts new file mode 100644 index 000000000..b10f5e948 --- /dev/null +++ b/src/modules/range/public.ts @@ -0,0 +1,3 @@ +export { + SuiRangeModule +} from "./index"; diff --git a/src/modules/range/range.module.ts b/src/modules/range/range.module.ts new file mode 100644 index 000000000..6fe846b22 --- /dev/null +++ b/src/modules/range/range.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { FormsModule } from "@angular/forms"; +import { SuiRange } from "./components/range"; + +@NgModule({ + imports: [ + FormsModule, + CommonModule + ], + declarations: [ + SuiRange + ], + exports: [ + SuiRange + ] +}) +export class SuiRangeModule {} From 85ed319350f8459b68dbf6f931517b8907e694a0 Mon Sep 17 00:00:00 2001 From: david limkys Date: Sat, 15 Jul 2017 06:22:51 +0300 Subject: [PATCH 2/4] Added suiRange module. --- src/sui.module.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/sui.module.ts b/src/sui.module.ts index 651b09f07..1e7854c4c 100644 --- a/src/sui.module.ts +++ b/src/sui.module.ts @@ -1,12 +1,5 @@ import { NgModule } from "@angular/core"; - -// Collections -import { - SuiMessageModule, - SuiPaginationModule -} from "./collections"; - -// Modules +import { SuiMessageModule, SuiPaginationModule } from "./collections"; import { SuiAccordionModule, SuiCheckboxModule, @@ -17,6 +10,7 @@ import { SuiModalModule, SuiPopupModule, SuiProgressModule, + SuiRangeModule, SuiRatingModule, SuiSearchModule, SuiSidebarModule, @@ -24,16 +18,16 @@ import { SuiSelectModule, SuiTransitionModule } from "./modules"; +import { SuiLocalizationModule } from "./behaviors"; +import { SuiUtilityModule } from "./misc"; + +// Collections + +// Modules // Behaviors -import { - SuiLocalizationModule -} from "./behaviors"; // Misc -import { - SuiUtilityModule -} from "./misc"; @NgModule({ exports: [ @@ -51,6 +45,7 @@ import { SuiModalModule, SuiPopupModule, SuiProgressModule, + SuiRangeModule, SuiRatingModule, SuiSearchModule, SuiSelectModule, From 8539162d081aed836ce19419761bdef2015fd594 Mon Sep 17 00:00:00 2001 From: david limkys Date: Sat, 15 Jul 2017 06:23:27 +0300 Subject: [PATCH 3/4] Now including the range css link. --- demo/src/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo/src/index.html b/demo/src/index.html index cb1e5c693..77b5112a3 100644 --- a/demo/src/index.html +++ b/demo/src/index.html @@ -15,6 +15,8 @@ + + From 1b023f4299c1ca20fb1b0405d9b72bbf3e26341c Mon Sep 17 00:00:00 2001 From: david limkys Date: Sat, 15 Jul 2017 06:23:58 +0300 Subject: [PATCH 4/4] Added a range example page. --- demo/src/app/app.routing.ts | 17 +- .../components/sidebar/sidebar.component.html | 3 +- demo/src/app/pages/demo-pages.module.ts | 15 +- .../app/pages/modules/range/range.page.html | 43 +++++ .../src/app/pages/modules/range/range.page.ts | 149 ++++++++++++++++++ src/modules/index.ts | 1 + 6 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 demo/src/app/pages/modules/range/range.page.html create mode 100644 demo/src/app/pages/modules/range/range.page.ts diff --git a/demo/src/app/app.routing.ts b/demo/src/app/app.routing.ts index 06b35bbd2..0cc0cf3bd 100644 --- a/demo/src/app/app.routing.ts +++ b/demo/src/app/app.routing.ts @@ -1,12 +1,8 @@ import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { GettingStartedPage } from "./pages/getting-started/getting-started.page"; - -// Collections import { MessagePage } from "./pages/collections/message/message.page"; import { PaginationPage } from "./pages/collections/pagination/pagination.page"; - -// Modules import { AccordionPage } from "./pages/modules/accordion/accordion.page"; import { CheckboxPage } from "./pages/modules/checkbox/checkbox.page"; import { CollapsePage } from "./pages/modules/collapse/collapse.page"; @@ -16,18 +12,23 @@ import { DropdownPage } from "./pages/modules/dropdown/dropdown.page"; import { ModalPage } from "./pages/modules/modal/modal.page"; import { PopupPage } from "./pages/modules/popup/popup.page"; import { ProgressPage } from "./pages/modules/progress/progress.page"; +import { RangePage } from "./pages/modules/range/range.page"; import { RatingPage } from "./pages/modules/rating/rating.page"; import { SearchPage } from "./pages/modules/search/search.page"; import { SelectPage } from "./pages/modules/select/select.page"; import { SidebarPage } from "./pages/modules/sidebar/sidebar.page"; import { TabsPage } from "./pages/modules/tabs/tabs.page"; import { TransitionPage } from "./pages/modules/transition/transition.page"; +import { LocalizationPage } from "./pages/behaviors/localization/localization.page"; +import { TestPage } from "./pages/development/test/test.page"; + +// Collections + +// Modules // Behaviors -import { LocalizationPage } from "./pages/behaviors/localization/localization.page"; // Development -import { TestPage } from "./pages/development/test/test.page"; const appRoutes:Routes = [ { @@ -82,6 +83,10 @@ const appRoutes:Routes = [ path: "modules/progress", component: ProgressPage }, + { + path: "modules/range", + component: RangePage + }, { path: "modules/rating", component: RatingPage diff --git a/demo/src/app/components/sidebar/sidebar.component.html b/demo/src/app/components/sidebar/sidebar.component.html index d0179001e..aca74ae18 100644 --- a/demo/src/app/components/sidebar/sidebar.component.html +++ b/demo/src/app/components/sidebar/sidebar.component.html @@ -29,6 +29,7 @@ Modal Popup Progress + Range Rating Search Select @@ -48,4 +49,4 @@ - \ No newline at end of file + diff --git a/demo/src/app/pages/demo-pages.module.ts b/demo/src/app/pages/demo-pages.module.ts index c51c2cafd..54602e2b9 100644 --- a/demo/src/app/pages/demo-pages.module.ts +++ b/demo/src/app/pages/demo-pages.module.ts @@ -4,14 +4,9 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { RouterModule } from "@angular/router"; import { SuiModule } from "../../../../src"; import { DemoComponentsModule } from "../components/demo-components.module"; - import { GettingStartedPage } from "./getting-started/getting-started.page"; - -// Collections import { MessagePageComponents } from "./collections/message/message.page"; import { PaginationPageComponents } from "./collections/pagination/pagination.page"; - -// Modules import { AccordionPageComponents } from "./modules/accordion/accordion.page"; import { CheckboxPageComponents } from "./modules/checkbox/checkbox.page"; import { CollapsePageComponents } from "./modules/collapse/collapse.page"; @@ -21,18 +16,23 @@ import { DropdownPageComponents } from "./modules/dropdown/dropdown.page"; import { ModalPageComponents, ConfirmModalComponent } from "./modules/modal/modal.page"; import { PopupPageComponents } from "./modules/popup/popup.page"; import { ProgressPageComponents } from "./modules/progress/progress.page"; +import { RangePageComponents } from "./modules/range/range.page"; import { RatingPageComponents } from "./modules/rating/rating.page"; import { SearchPageComponents } from "./modules/search/search.page"; import { SelectPageComponents } from "./modules/select/select.page"; import { SidebarPageComponents } from "./modules/sidebar/sidebar.page"; import { TabsPageComponents } from "./modules/tabs/tabs.page"; import { TransitionPageComponents } from "./modules/transition/transition.page"; +import { LocalizationPageComponents } from "./behaviors/localization/localization.page"; +import { TestPage } from "./development/test/test.page"; + +// Collections + +// Modules // Behaviors -import { LocalizationPageComponents } from "./behaviors/localization/localization.page"; // Development -import { TestPage } from "./development/test/test.page"; @NgModule({ imports: [ @@ -60,6 +60,7 @@ import { TestPage } from "./development/test/test.page"; ModalPageComponents, PopupPageComponents, ProgressPageComponents, + RangePageComponents, RatingPageComponents, SearchPageComponents, SelectPageComponents, diff --git a/demo/src/app/pages/modules/range/range.page.html b/demo/src/app/pages/modules/range/range.page.html new file mode 100644 index 000000000..30e67eea2 --- /dev/null +++ b/demo/src/app/pages/modules/range/range.page.html @@ -0,0 +1,43 @@ + +
Rating
+
+

A rating indicates user interest in content

+
+
+ + +
Important Note
+

+ The range is a port of Semantic-UI-Range, + an external module for Semantic UI. If you'd like to use the range in your app, you must include the module's + CSS alongside Semantic UI's. +

+
+ +
+
+

Examples

+ +
+

Range

+

A basic range

+
+ +
+ +
+

Range

+

Range with min max

+
+ +
+ +
+

Range

+

Range that skips 5 values with each step

+
+ +
+

API

+ +
diff --git a/demo/src/app/pages/modules/range/range.page.ts b/demo/src/app/pages/modules/range/range.page.ts new file mode 100644 index 000000000..6829e42d8 --- /dev/null +++ b/demo/src/app/pages/modules/range/range.page.ts @@ -0,0 +1,149 @@ +import { Component } from "@angular/core"; +import { ApiDefinition } from "../../../components/api/api.component"; + +const exampleStandardTemplate = ` +
+
+ + +
+
+ +
+ + + + +
+
+
+ Read Only? +
+
+`; + +const exampleMinMaxTemplate = ` +
+
+ + +
+
+ +
+ + + + +
+
+
+`; + +const exampleStepTemplate = ` +
+
+ + +
+
+ +
+ + + + +
+
+
+`; + +@Component({ + selector: "demo-page-range", + templateUrl: "./range.page.html" +}) +export class RangePage { + public api:ApiDefinition = [ + { + selector: "", + properties: [ + { + name: "min", + type: "number", + description: "The minimum value of the range.", + defaultValue: "0" + }, + { + name: "max", + type: "number", + description: "The maximum value of the range.", + defaultValue: "100" + }, + { + name: "step", + type: "number", + description: "The amount each step skips in value, for step 2 the value will skip like so: 0, 2, 4...", + defaultValue: "100" + }, + { + name: "isReadonly", + type: "boolean", + description: "Sets whether or not the value is read-only. ", + defaultValue: "false" + } + ], + events: [ + { + name: "valueChange", + type: "number", + description: "Fires whenever the value value is changed." + } + ] + } + ]; + public exampleStandardTemplate:string = exampleStandardTemplate; + public exampleMinMaxTemplate:string = exampleMinMaxTemplate; + public exampleStepTemplate:string = exampleStepTemplate; + public cssInclude:string = ``; +} + +@Component({ + selector: "example-range-standard", + template: exampleStandardTemplate +}) +export class RangeExampleStandard { + public value:number = 3; + public readonly:boolean; +} + +@Component({ + selector: "example-range-min-max", + template: exampleMinMaxTemplate +}) +export class RangeExampleMinMax { + public value:number = 3; + public readonly:boolean; +} + +@Component({ + selector: "example-range-step", + template: exampleStepTemplate +}) +export class RangeExampleStep { + public value:number = 5; + public readonly:boolean; +} + +export const RangePageComponents = [RangePage, RangeExampleStandard, RangeExampleMinMax, RangeExampleStep]; diff --git a/src/modules/index.ts b/src/modules/index.ts index e00c024dc..493db24b3 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -7,6 +7,7 @@ export * from "./dropdown"; export * from "./modal"; export * from "./popup"; export * from "./progress"; +export * from "./range"; export * from "./rating"; export * from "./search"; export * from "./select";