Browse Source

Dropdown improved.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
45b375dfd4
  1. 10
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  2. 8
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss
  3. 2
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  4. 2
      src/Squidex/app/framework/angular/autocomplete.component.html
  5. 3
      src/Squidex/app/framework/angular/autocomplete.component.scss
  6. 60
      src/Squidex/app/framework/angular/autocomplete.component.ts
  7. 8
      src/Squidex/app/framework/angular/date-time-editor.component.scss
  8. 22
      src/Squidex/app/framework/angular/dropdown.component.html
  9. 74
      src/Squidex/app/framework/angular/dropdown.component.scss
  10. 123
      src/Squidex/app/framework/angular/dropdown.component.ts
  11. 12
      src/Squidex/app/framework/angular/scroll-active.directive.ts
  12. 47
      src/Squidex/app/framework/angular/template-wrapper.directive.ts
  13. 2
      src/Squidex/app/framework/declarations.ts
  14. 6
      src/Squidex/app/framework/module.ts
  15. 1
      src/Squidex/app/theme/_vars.scss

10
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -54,10 +54,12 @@
<div class="table-items-footer"> <div class="table-items-footer">
<form [formGroup]="addFieldForm" (ngSubmit)="addField()"> <form [formGroup]="addFieldForm" (ngSubmit)="addField()">
<div class="form-inline"> <div class="form-inline">
<div class="form-group mr-1"> <div class="form-group types-group mr-1">
<select class="form-control" formControlName="type"> <sqx-dropdown formControlName="type" [items]="fieldTypes">
<option *ngFor="let type of fieldTypes" [ngValue]="type">{{type}}</option> <ng-template let-type="$implicit">
</select> <i class="field-icon icon-type-{{type}}"></i> <span>{{type}}</span>
</ng-template>
</sqx-dropdown>
</div> </div>
<div class="form-group mr-1"> <div class="form-group mr-1">

8
src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss

@ -20,6 +20,10 @@
margin-right: 1rem; margin-right: 1rem;
} }
.types-group {
width: 10rem;
}
.schema { .schema {
&-edit { &-edit {
color: $color-border-dark; color: $color-border-dark;
@ -32,6 +36,10 @@
} }
} }
.field-icon {
color: $color-border-dark;
}
.form-check { .form-check {
margin-top: 1rem; margin-top: 1rem;
margin-bottom: -.2rem; margin-bottom: -.2rem;

2
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -56,7 +56,7 @@
<div class="table-items-footer"> <div class="table-items-footer">
<form class="form-inline" [formGroup]="addContributorForm" (ngSubmit)="assignContributor()"> <form class="form-inline" [formGroup]="addContributorForm" (ngSubmit)="assignContributor()">
<div class="form-group mr-3"> <div class="form-group mr-1">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user"></sqx-autocomplete> <sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user"></sqx-autocomplete>
</div> </div>

2
src/Squidex/app/framework/angular/autocomplete.component.html

@ -1,5 +1,5 @@
<span> <span>
<input type="text" class="form-control" (blur)="blur()" [attr.name]="inputName" (keydown)="onKeyDown($event)" (blur)="markTouched()" [attr.placeholder]="placeholder" <input type="text" class="form-control" (blur)="blur()" [attr.name]="inputName" (keydown)="onKeyDown($event)" [attr.placeholder]="placeholder"
[formControl]="queryInput" [formControl]="queryInput"
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"

3
src/Squidex/app/framework/angular/autocomplete.component.scss

@ -18,18 +18,21 @@ $color-input-border: rgba(0, 0, 0, .15);
background: $color-dark-foreground; background: $color-dark-foreground;
padding: .3rem 0; padding: .3rem 0;
overflow-y: auto; overflow-y: auto;
z-index: 10000;
} }
} }
.item { .item {
& { & {
padding: .3rem .8rem; padding: .3rem .8rem;
border: 0;
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
} }
&.active { &.active {
color: $color-dark-foreground; color: $color-dark-foreground;
border: 0;
background: $color-theme-blue; background: $color-theme-blue;
} }

60
src/Squidex/app/framework/angular/autocomplete.component.ts

@ -24,6 +24,7 @@ export class AutocompleteItem {
} }
const KEY_ENTER = 13; const KEY_ENTER = 13;
const KEY_ESCAPE = 27;
const KEY_UP = 38; const KEY_UP = 38;
const KEY_DOWN = 40; const KEY_DOWN = 40;
const NOOP = () => { /* NOOP */ }; const NOOP = () => { /* NOOP */ };
@ -59,7 +60,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public writeValue(value: any) { public writeValue(value: any) {
if (!value) { if (!value) {
this.queryInput.setValue(''); this.resetValue();
} else { } else {
let item: AutocompleteItem | null = null; let item: AutocompleteItem | null = null;
@ -101,27 +102,23 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public ngOnInit() { public ngOnInit() {
this.subscription = this.subscription =
this.queryInput.valueChanges this.queryInput.valueChanges
.map(q => <string>q) .map(query => <string>query)
.map(q => q ? q.trim() : q) .map(query => query ? query.trim() : query)
.distinctUntilChanged() .distinctUntilChanged()
.debounceTime(200) .debounceTime(200)
.do(q => { .do(query => {
if (!q) { if (!query) {
this.reset(); this.reset();
} }
}) })
.filter(q => !!q && !!this.source) .filter(query => !!query && !!this.source)
.switchMap(q => this.source.find(q)).catch(_ => Observable.of([])) .switchMap(query => this.source.find(query)).catch(_ => Observable.of([]))
.subscribe(r => { .subscribe(items => {
this.reset(); this.reset();
this.items = r || []; this.items = items || [];
}); });
} }
public markTouched() {
this.touchedCallback();
}
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
switch (event.keyCode) { switch (event.keyCode) {
case KEY_UP: case KEY_UP:
@ -130,6 +127,10 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
case KEY_DOWN: case KEY_DOWN:
this.down(); this.down();
return false; return false;
case KEY_ESCAPE:
this.resetValue();
this.reset();
return false;
case KEY_ENTER: case KEY_ENTER:
if (this.items.length > 0) { if (this.items.length > 0) {
this.chooseItem(); this.chooseItem();
@ -139,21 +140,9 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} }
} }
private reset() {
this.items = [];
this.itemSelection = -1;
}
public blur() { public blur() {
this.reset(); this.reset();
} this.touchedCallback();
public up() {
this.selectIndex(this.itemSelection - 1);
}
public down() {
this.selectIndex(this.itemSelection + 1);
} }
public chooseItem(selection: AutocompleteItem | null = null) { public chooseItem(selection: AutocompleteItem | null = null) {
@ -175,7 +164,19 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} }
} }
public selectIndex(selection: number) { private up() {
this.selectIndex(this.itemSelection - 1);
}
private down() {
this.selectIndex(this.itemSelection + 1);
}
private resetValue() {
this.queryInput.setValue('');
}
private selectIndex(selection: number) {
if (selection < 0) { if (selection < 0) {
selection = 0; selection = 0;
} }
@ -186,4 +187,9 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.itemSelection = selection; this.itemSelection = selection;
} }
private reset() {
this.items = [];
this.itemSelection = -1;
}
} }

8
src/Squidex/app/framework/angular/date-time-editor.component.scss

@ -1,8 +1,6 @@
@import '_mixins'; @import '_mixins';
@import '_vars'; @import '_vars';
$form-color: #fff;
:host(.ng-invalid) { :host(.ng-invalid) {
&.ng-dirty { &.ng-dirty {
.form-control { .form-control {
@ -18,12 +16,6 @@ $form-color: #fff;
} }
} }
.form-control {
&[readonly] {
background: $form-color;
}
}
.date-group { .date-group {
& { & {
padding-right: .25rem; padding-right: .25rem;

22
src/Squidex/app/framework/angular/dropdown.component.html

@ -0,0 +1,22 @@
<span>
<div class="selection">
<input type="text" class="form-control" [disabled]="isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)"
autocomplete="off"
autocorrect="off"
autocapitalize="off">
<div class="item" *ngIf="selectedItem">
<ng-template [sqxTemplateWrapper]="itemTemplate" [item]="selectedItem"></ng-template>
</div>
<i class="icon-caret-down"></i>
</div>
<div class="items-container">
<div class="items" #container *sqxModalView="modalView">
<div *ngFor="let item of items; let i = index;" class="item selectable" [class.active]="i === selectedIndex" (mousedown)="selectIndexAndClose(i)" [sqxScrollActive]="i === selectedIndex" [container]="container">
<ng-template [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template>
</div>
</div>
</div>
</span>

74
src/Squidex/app/framework/angular/dropdown.component.scss

@ -0,0 +1,74 @@
@import '_mixins';
@import '_vars';
$color-input-border: rgba(0, 0, 0, .15);
$color-input-disabled: #eef1f4;
.form-control {
& {
width: 100%;
}
&[readonly] {
background: $color-input-background;
}
&:disabled {
background: $color-input-disabled;
}
}
.selection {
& {
position: relative;
overflow: hidden;
}
.item {
@include absolute(0, 1rem, 0, 0);
pointer-events: none;
}
.icon-caret-down {
@include absolute(30%, .4rem, auto, auto);
font-size: .9rem;
font-weight: normal;
}
}
.items {
&-container {
position: relative;
}
& {
@include absolute(2px, auto, auto, 0);
@include border-radius(.25em);
@include box-shadow;
max-height: 12rem;
border: 1px solid $color-input-border;
background: $color-dark-foreground;
padding: .3rem 0;
overflow-y: auto;
z-index: 10000;
}
}
.item {
& {
padding: .5rem .75rem;
}
&.selectable {
& {
cursor: pointer;
}
&.active,
&:hover {
color: $color-dark-foreground;
border: 0;
background: $color-theme-blue;
}
}
}

123
src/Squidex/app/framework/angular/dropdown.component.ts

@ -0,0 +1,123 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, ContentChild, forwardRef, Input, TemplateRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const KEY_ENTER = 13;
const KEY_ESCAPE = 27;
const KEY_UP = 38;
const KEY_DOWN = 40;
const NOOP = () => { /* NOOP */ };
import { ModalView } from './../utils/modal-view';
export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true
};
@Component({
selector: 'sqx-dropdown',
styleUrls: ['./dropdown.component.scss'],
templateUrl: './dropdown.component.html',
providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR]
})
export class DropdownComponent implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP;
private touchedCallback: () => void = NOOP;
@Input()
public items: any[] = [];
@ContentChild(TemplateRef)
public itemTemplate: TemplateRef<any>;
public modalView = new ModalView();
public selectedItem: any;
public selectedIndex = -1;
public isDisabled = false;
private get safeItems(): any[] {
return this.items || [];
}
public writeValue(value: any) {
this.selectIndex(this.items && value ? this.items.indexOf(value) : 0);
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
public registerOnChange(fn: any) {
this.changeCallback = fn;
}
public registerOnTouched(fn: any) {
this.touchedCallback = fn;
}
public onKeyDown(event: KeyboardEvent) {
switch (event.keyCode) {
case KEY_UP:
this.up();
return false;
case KEY_DOWN:
this.down();
return false;
case KEY_ESCAPE:
case KEY_ENTER:
this.close();
return false;
}
}
public open() {
this.modalView.show();
this.touchedCallback();
}
public selectIndexAndClose(selectedIndex: number) {
this.selectIndex(selectedIndex);
this.close();
}
private close() {
this.modalView.hide();
}
private up() {
this.selectIndex(this.selectedIndex - 1);
}
private down() {
this.selectIndex(this.selectedIndex + 1);
}
private selectIndex(selectedIndex: number) {
if (selectedIndex < 0) {
selectedIndex = 0;
}
const items = this.items || [];
if (selectedIndex >= items.length) {
selectedIndex = items.length - 1;
}
const value = items[selectedIndex];
if (value !== this.selectedItem) {
this.selectedIndex = selectedIndex;
this.selectedItem = value;
this.changeCallback(value);
}
}
}

12
src/Squidex/app/framework/angular/scroll-active.directive.ts

@ -5,12 +5,12 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, Input, OnChanges } from '@angular/core';
@Directive({ @Directive({
selector: '[sqxScrollActive]' selector: '[sqxScrollActive]'
}) })
export class ScrollActiveDirective implements OnChanges { export class ScrollActiveDirective implements AfterViewInit, OnChanges {
@Input('sqxScrollActive') @Input('sqxScrollActive')
public isActive = false; public isActive = false;
@ -22,7 +22,15 @@ export class ScrollActiveDirective implements OnChanges {
) { ) {
} }
public ngAfterViewInit() {
this.check();
}
public ngOnChanges() { public ngOnChanges() {
this.check();
}
private check() {
if (this.isActive && this.container) { if (this.isActive && this.container) {
this.scrollInView(this.container, this.element.nativeElement); this.scrollInView(this.container, this.element.nativeElement);
} }

47
src/Squidex/app/framework/angular/template-wrapper.directive.ts

@ -0,0 +1,47 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, Input, OnDestroy, OnInit, OnChanges, TemplateRef, ViewContainerRef, EmbeddedViewRef } from '@angular/core';
@Directive({
selector: '[sqxTemplateWrapper]'
})
export class TemplateWrapper implements OnInit, OnDestroy, OnChanges {
@Input()
public item: any;
@Input()
public index: number;
@Input('sqxTemplateWrapper')
public templateRef: TemplateRef<any>;
public view: EmbeddedViewRef<any>;
public constructor(
private viewContainer: ViewContainerRef
) {
}
public ngOnChanges() {
if (this.view) {
this.view.context.$implicit = this.item;
this.view.context.index = this.index;
}
}
public ngOnInit() {
this.view = this.viewContainer.createEmbeddedView(this.templateRef, {
'\$implicit': this.item,
'index': this.index
});
}
public ngOnDestroy() {
this.view.destroy();
}
}

2
src/Squidex/app/framework/declarations.ts

@ -13,6 +13,7 @@ export * from './angular/control-errors.component';
export * from './angular/copy.directive'; export * from './angular/copy.directive';
export * from './angular/date-time-editor.component'; export * from './angular/date-time-editor.component';
export * from './angular/date-time.pipes'; export * from './angular/date-time.pipes';
export * from './angular/dropdown.component';
export * from './angular/file-drop.directive'; export * from './angular/file-drop.directive';
export * from './angular/focus-on-change.directive'; export * from './angular/focus-on-change.directive';
export * from './angular/focus-on-init.directive'; export * from './angular/focus-on-init.directive';
@ -37,6 +38,7 @@ export * from './angular/shortcut.component';
export * from './angular/slider.component'; export * from './angular/slider.component';
export * from './angular/stars.component'; export * from './angular/stars.component';
export * from './angular/tag-editor.component'; export * from './angular/tag-editor.component';
export * from './angular/template-wrapper.directive';
export * from './angular/title.component'; export * from './angular/title.component';
export * from './angular/toggle.component'; export * from './angular/toggle.component';
export * from './angular/user-report.component'; export * from './angular/user-report.component';

6
src/Squidex/app/framework/module.ts

@ -22,6 +22,7 @@ import {
DayOfWeekPipe, DayOfWeekPipe,
DayPipe, DayPipe,
DisplayNamePipe, DisplayNamePipe,
DropdownComponent,
DurationPipe, DurationPipe,
FileDropDirective, FileDropDirective,
FocusOnChangeDirective, FocusOnChangeDirective,
@ -55,6 +56,7 @@ import {
SliderComponent, SliderComponent,
StarsComponent, StarsComponent,
TagEditorComponent, TagEditorComponent,
TemplateWrapper,
TitleService, TitleService,
TitleComponent, TitleComponent,
ToggleComponent, ToggleComponent,
@ -78,6 +80,7 @@ import {
DayOfWeekPipe, DayOfWeekPipe,
DayPipe, DayPipe,
DisplayNamePipe, DisplayNamePipe,
DropdownComponent,
DurationPipe, DurationPipe,
FileDropDirective, FileDropDirective,
FocusOnChangeDirective, FocusOnChangeDirective,
@ -105,6 +108,7 @@ import {
SliderComponent, SliderComponent,
StarsComponent, StarsComponent,
TagEditorComponent, TagEditorComponent,
TemplateWrapper,
TitleComponent, TitleComponent,
ToggleComponent, ToggleComponent,
UserReportComponent UserReportComponent
@ -118,6 +122,7 @@ import {
DayOfWeekPipe, DayOfWeekPipe,
DayPipe, DayPipe,
DisplayNamePipe, DisplayNamePipe,
DropdownComponent,
DurationPipe, DurationPipe,
FileDropDirective, FileDropDirective,
FocusOnChangeDirective, FocusOnChangeDirective,
@ -145,6 +150,7 @@ import {
SliderComponent, SliderComponent,
StarsComponent, StarsComponent,
TagEditorComponent, TagEditorComponent,
TemplateWrapper,
TitleComponent, TitleComponent,
ToggleComponent, ToggleComponent,
UserReportComponent, UserReportComponent,

1
src/Squidex/app/theme/_vars.scss

@ -10,6 +10,7 @@ $color-subtext: $color-text-decent;
$color-empty: $color-text-decent; $color-empty: $color-text-decent;
$color-control: rgba(0, 0, 0, .15); $color-control: rgba(0, 0, 0, .15);
$color-input: #dbe4eb; $color-input: #dbe4eb;
$color-input-background: #fff;
$color-disabled: #eef1f4; $color-disabled: #eef1f4;
$color-extern-google: #d34836; $color-extern-google: #d34836;

Loading…
Cancel
Save