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">
<form [formGroup]="addFieldForm" (ngSubmit)="addField()">
<div class="form-inline">
<div class="form-group mr-1">
<select class="form-control" formControlName="type">
<option *ngFor="let type of fieldTypes" [ngValue]="type">{{type}}</option>
</select>
<div class="form-group types-group mr-1">
<sqx-dropdown formControlName="type" [items]="fieldTypes">
<ng-template let-type="$implicit">
<i class="field-icon icon-type-{{type}}"></i> <span>{{type}}</span>
</ng-template>
</sqx-dropdown>
</div>
<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;
}
.types-group {
width: 10rem;
}
.schema {
&-edit {
color: $color-border-dark;
@ -32,6 +36,10 @@
}
}
.field-icon {
color: $color-border-dark;
}
.form-check {
margin-top: 1rem;
margin-bottom: -.2rem;

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

@ -56,7 +56,7 @@
<div class="table-items-footer">
<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>
</div>

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

@ -1,5 +1,5 @@
<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"
autocomplete="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;
padding: .3rem 0;
overflow-y: auto;
z-index: 10000;
}
}
.item {
& {
padding: .3rem .8rem;
border: 0;
background: transparent;
cursor: pointer;
}
&.active {
color: $color-dark-foreground;
border: 0;
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_ESCAPE = 27;
const KEY_UP = 38;
const KEY_DOWN = 40;
const NOOP = () => { /* NOOP */ };
@ -59,7 +60,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public writeValue(value: any) {
if (!value) {
this.queryInput.setValue('');
this.resetValue();
} else {
let item: AutocompleteItem | null = null;
@ -101,27 +102,23 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public ngOnInit() {
this.subscription =
this.queryInput.valueChanges
.map(q => <string>q)
.map(q => q ? q.trim() : q)
.map(query => <string>query)
.map(query => query ? query.trim() : query)
.distinctUntilChanged()
.debounceTime(200)
.do(q => {
if (!q) {
.do(query => {
if (!query) {
this.reset();
}
})
.filter(q => !!q && !!this.source)
.switchMap(q => this.source.find(q)).catch(_ => Observable.of([]))
.subscribe(r => {
.filter(query => !!query && !!this.source)
.switchMap(query => this.source.find(query)).catch(_ => Observable.of([]))
.subscribe(items => {
this.reset();
this.items = r || [];
this.items = items || [];
});
}
public markTouched() {
this.touchedCallback();
}
public onKeyDown(event: KeyboardEvent) {
switch (event.keyCode) {
case KEY_UP:
@ -130,6 +127,10 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
case KEY_DOWN:
this.down();
return false;
case KEY_ESCAPE:
this.resetValue();
this.reset();
return false;
case KEY_ENTER:
if (this.items.length > 0) {
this.chooseItem();
@ -139,21 +140,9 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
}
}
private reset() {
this.items = [];
this.itemSelection = -1;
}
public blur() {
this.reset();
}
public up() {
this.selectIndex(this.itemSelection - 1);
}
public down() {
this.selectIndex(this.itemSelection + 1);
this.touchedCallback();
}
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) {
selection = 0;
}
@ -186,4 +187,9 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
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 '_vars';
$form-color: #fff;
:host(.ng-invalid) {
&.ng-dirty {
.form-control {
@ -18,12 +16,6 @@ $form-color: #fff;
}
}
.form-control {
&[readonly] {
background: $form-color;
}
}
.date-group {
& {
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
*/
import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, OnChanges } from '@angular/core';
@Directive({
selector: '[sqxScrollActive]'
})
export class ScrollActiveDirective implements OnChanges {
export class ScrollActiveDirective implements AfterViewInit, OnChanges {
@Input('sqxScrollActive')
public isActive = false;
@ -22,7 +22,15 @@ export class ScrollActiveDirective implements OnChanges {
) {
}
public ngAfterViewInit() {
this.check();
}
public ngOnChanges() {
this.check();
}
private check() {
if (this.isActive && this.container) {
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/date-time-editor.component';
export * from './angular/date-time.pipes';
export * from './angular/dropdown.component';
export * from './angular/file-drop.directive';
export * from './angular/focus-on-change.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/stars.component';
export * from './angular/tag-editor.component';
export * from './angular/template-wrapper.directive';
export * from './angular/title.component';
export * from './angular/toggle.component';
export * from './angular/user-report.component';

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

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

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

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

Loading…
Cancel
Save