Angular组件(二) 分割面板ShrinkSplitter

Angular组件(二) 分割面板ShrinkSplitter

前言

Angular组件(一) 分割面板ShrinkSplitter文章中我们实现了Splitter组件,后来在业务场景中发现在开关右侧容器和底部容器时,使用起来不方便,ngModel绑定的值始终是左侧容器和顶部容器的大小,然而有时我们关注的是右侧容器和底部容器的大小,让左侧自适应。于是修改组件代码,让ngmodel绑定的容器大小和tlColsedMode关联,举例: tlColsedMode = "right",ngModel绑定的值就是右侧容器的大小。

组件Splitter

module.ts

ts 复制代码
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { TlShrinkSplitterComponent } from "./shrink-splitter.component";
import{NzToolTipModule} from "ng-zorro-antd/tooltip"

const COMMENT = [TlShrinkSplitterComponent];

@NgModule({
  declarations: [...COMMENT],
  exports: [...COMMENT],
  imports: [
    CommonModule,
    NzToolTipModule,
  ]
})
export class TlShrinkSplitterModule {}

component.ts

复制代码
import { AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, QueryList, TemplateRef, ViewChild } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
import { TlTemplateDirective } from "topdsm-lib/common"
import { isFalsy } from "topdsm-lib/core/util";
import { off, on } from "./util";

@Component({
    selector: "tl-shrink-splitter",
    templateUrl: "./shrink-splitter.component.html",
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => TlShrinkSplitterComponent),
            multi: true
        }
    ],
    host: {
        class: "tl-shrink-splitter",
        '[class.expand]': 'tlExpand',
        '[class.contract]': '!tlExpand',
        '[class.dragable]': 'tlDragable',
        '[class.contract-left]': 'tlColsedMode === "left"',
        '[class.contract-right]': 'tlColsedMode === "right"',
        '[class.contract-top]': 'tlColsedMode === "top"',
        '[class.contract-bottom]': 'tlColsedMode === "bottom"',
        '[style.z-index]': 'tlZIndex',
    }
})
export class TlShrinkSplitterComponent implements OnInit, AfterContentInit, AfterViewInit, ControlValueAccessor {

    prefix = "tl-shrink-splitter"
    offset = 0
    oldOffset: number | string = 0
    isMoving = false
    initOffset = 0

    _value: number | string = 0.5
    isOpen = true
    viewRender = false

    @Input()
    tlZIndex = 10
    /** 是否展示收起icon */
    @Input()
    tlShowExpandIcon = true

    /** 收起容器模式,上下左右哪一个容器应用收起展开的状态 */
    @Input()
    tlColsedMode: "left" | "right" | "top" | "bottom" = "left"

    @Input()
    tlMin = "40px"

    @Input()
    tlMax = "40px"

    @Input()
    tlExpandTooltipContent = ""

    @Input()
    tlContractTooltipContent = ""

    /** 是否可拖拽调整大小 */
    @Input()
    tlDragable = true

    get value() {
        return this._value
    }

    set value(val: number | string) {
        if(!this.viewRender && !this.tlExpand){           
            this.expandValueCache = val
            val = 0
        }
  
        this._value = val
        this.onChange(val)
        this.computeOffset()

        setTimeout(() => {
            this.viewRender = true
        }, 0);
    }

    expandValueCache: string | number = 0

    /** 展开状态 */
    get tlExpand() {
        return this.isOpen;
    }

    @Input()
    set tlExpand(val: boolean) {
        if (val !== this.isOpen) {
            this.isOpen = val;
            this.tlExpandChange.emit(val);
            this.changeExpand(val)
        }
    }

    /** 容器展开状态切换 */
    changeExpand(status: boolean) {
        if (!status) {
            // 收起
            this.expandValueCache = this.value
            this.value = 0
        } else {
            // 展开
            this.value = this.expandValueCache
            this.expandValueCache = 0
        }
    }

    /** 展开收缩切换事件 */
    @Output() readonly tlExpandChange = new EventEmitter<boolean>();

    @Output() readonly onMoveStart = new EventEmitter();
    @Output() readonly onMoving = new EventEmitter<MouseEvent>();
    @Output() readonly onMoveEnd = new EventEmitter();

    expandChange(e: MouseEvent) {
        e.stopPropagation();
        e.preventDefault()
        this.tlExpand = !this.isOpen
    }


    @ContentChildren(TlTemplateDirective)
    templates?: QueryList<TlTemplateDirective>

    leftTemplate?: TemplateRef<void> | null = null

    rightTemplate?: TemplateRef<void> | null = null

    topTemplate?: TemplateRef<void> | null = null

    bottomTemplate?: TemplateRef<void> | null = null

    @ViewChild('outerWrapper')
    outerWrapper: ElementRef;


    get isHorizontal() {
        return this.tlColsedMode === "left" || this.tlColsedMode === "right"
    }
    get computedMin() {
        return this.getComputedThresholdValue('tlMin');
    }
    get computedMax() {
        return this.getComputedThresholdValue('tlMax');
    }
    get anotherOffset() {
        return 100 - this.offset;
    }

    get valueIsPx() {
        return typeof this.value === 'string';
    }
    get offsetSize() {
        return this.isHorizontal ? 'offsetWidth' : 'offsetHeight';
    }

    get paneClasses() {
        let classes = {}
        classes[`${this.prefix}-pane`] = true
        classes[`${this.prefix}-pane-transition`] = this.viewRender
        classes[`${this.prefix}-pane-moving`] = this.isMoving
        return classes
    }

    /** 展开收起触发器icon */
    get triggrrClass() {
        let classes = {}
        if (this.tlColsedMode === "left" && this.isOpen) {
            classes["icon-caret-left"] = true
        } else if (this.tlColsedMode === "left" && !this.isOpen) {
            classes["icon-caret-right"] = true
        } else if (this.tlColsedMode === "right" && this.isOpen) {
            classes["icon-caret-right"] = true
        } else if (this.tlColsedMode === "right" && !this.isOpen) {
            classes["icon-caret-left"] = true
        } else if (this.tlColsedMode === "top" && this.isOpen) {
            classes["icon-caret-left"] = true
        } else if (this.tlColsedMode === "top" && !this.isOpen) {
            classes["icon-caret-right"] = true
        } else if (this.tlColsedMode === "bottom" && this.isOpen) {
            classes["icon-caret-right"] = true
        } else if (this.tlColsedMode === "bottom" && !this.isOpen) {
            classes["icon-caret-left"] = true
        }
        return classes
    }

    get tooltipPosition() {
        let position = "right"
        if (this.tlColsedMode === "right" && !this.isOpen) {
            position = "left"
        }
        return position
    }

    get tooltipContent() {
        let tooltip = ""
        if (this.tlColsedMode === "left" && this.isOpen) {
            tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起左侧内容" : this.tlExpandTooltipContent
        } else if (this.tlColsedMode === "left" && !this.isOpen) {
            tooltip = isFalsy(this.tlContractTooltipContent) ? "展开左侧内容" : this.tlContractTooltipContent
        } else if (this.tlColsedMode === "right" && this.isOpen) {
            tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起右侧内容" : this.tlExpandTooltipContent
        } else if (this.tlColsedMode === "right" && !this.isOpen) {
            tooltip = isFalsy(this.tlContractTooltipContent) ? "展开右侧内容" : this.tlContractTooltipContent
        } else if (this.tlColsedMode === "top" && this.isOpen) {
            tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起顶部内容" : this.tlExpandTooltipContent
        } else if (this.tlColsedMode === "top" && !this.isOpen) {
            tooltip = isFalsy(this.tlContractTooltipContent) ? "展开顶部内容" : this.tlContractTooltipContent
        } else if (this.tlColsedMode === "bottom" && this.isOpen) {
            tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起底部内容" : this.tlExpandTooltipContent
        } else if (this.tlColsedMode === "bottom" && !this.isOpen) {
            tooltip = isFalsy(this.tlContractTooltipContent) ? "展开底部内容" : this.tlContractTooltipContent
        }
        return tooltip
    }

    px2percent(numerator: string | number, denominator: string | number) {
        return parseFloat(numerator + "") / parseFloat(denominator + "");
    }


    computeOffset() {
        if(this.tlColsedMode === "left" || this.tlColsedMode === "top"){           
            this.offset = (this.valueIsPx ? this.px2percent(this.value as string, this.outerWrapper.nativeElement[this.offsetSize]) : this.value) as number * 10000 / 100
        }else{
            this.offset = (this.valueIsPx ? 1 - this.px2percent(this.value as string, this.outerWrapper.nativeElement[this.offsetSize]) : 1- (this.value as number)) as number * 10000 / 100
        }  
    }

    getComputedThresholdValue(type) {
        let size = this.outerWrapper.nativeElement[this.offsetSize];
        if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type];
        else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type];
    }
    getMin(value1, value2) {
        if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`;
        else return Math.min(value1, value2);
    }
    getMax(value1, value2) {
        if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`;
        else return Math.max(value1, value2);
    }

    getAnotherOffset(value) {
        let res: string | number = 0;
        if (this.valueIsPx) res = `${this.outerWrapper.nativeElement[this.offsetSize] - parseFloat(value)}px`;
        else res = 1 - value;
        return res;
    }

    handleMove = (e) => {
        let pageOffset = this.isHorizontal ? e.pageX : e.pageY;
        let offset = pageOffset - this.initOffset;
        let outerWidth = this.outerWrapper.nativeElement[this.offsetSize];
        let value: string | number = ""
        if (this.valueIsPx) {           
            if(this.tlColsedMode === "left" || this.tlColsedMode === "top"){
                value = `${parseFloat(this.oldOffset as string) + offset}px`
            }else{
                value = `${parseFloat(this.oldOffset as string) - offset}px`
            }
        } else {
            if(this.tlColsedMode === "left" || this.tlColsedMode === "top"){
                value = this.px2percent(outerWidth * (this.oldOffset as number) + offset, outerWidth)
            }else{
                value = this.px2percent(outerWidth * (this.oldOffset as number) - offset, outerWidth)
            }         
        }

        let anotherValue = this.getAnotherOffset(value);
        if (parseFloat(value + "") <= parseFloat(this.computedMin + "")) value = this.getMax(value, this.computedMin);
        if (parseFloat(anotherValue + "") <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax));
        e.atMin = this.value === this.computedMin;
        e.atMax = this.valueIsPx ? this.getAnotherOffset(this.value) === this.computedMax : (this.getAnotherOffset(this.value) as number).toFixed(5) === this.computedMax.toFixed(5);
        
        this.value = value
        this.onMoving.emit(e)
    }

    handleUp = (e) => {
        this.isMoving = false;
        off(document, 'mousemove', this.handleMove);
        off(document, 'mouseup', this.handleUp);
        this.onMoveEnd.emit()
    }

    onTriggerMouseDown(e) {
        if(!this.tlDragable){
            return
        }
        this.initOffset = this.isHorizontal ? e.pageX : e.pageY;
        this.oldOffset = this.value;
        this.isMoving = true;
        on(document, 'mousemove', this.handleMove);
        on(document, 'mouseup', this.handleUp);
        this.onMoveStart.emit()
    }

    constructor(private cdr: ChangeDetectorRef) { }

    ngOnInit(): void {
        console.log("ngOnInit");
    }

    ngAfterViewInit(): void {
        console.log("ngAfterViewInit");
        this.computeOffset()
    }

    ngAfterContentInit() {
        this.templates?.forEach((item) => {
            switch (item.getType()) {
                case 'left':
                    this.leftTemplate = item.template;
                    break;
                case 'right':
                    this.rightTemplate = item.template;
                    break;
                case 'top':
                    this.topTemplate = item.template;
                    break;
                case 'bottom':
                    this.bottomTemplate = item.template;
                    break;
                default:
                    this.leftTemplate = item.template;
                    break;
            }
        });

    }

    // 输入框数据变化时
    onChange: (value: any) => void = () => null;
    onTouched: () => void = () => null;

    writeValue(val: number | string): void {
        if (val !== this.value) {
            this.value = val
            this.computeOffset();
            this.cdr.markForCheck();
        }
    }
    // UI界面值发生更改,调用注册的回调函数
    registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    // 在blur(等失效事件),调用注册的回调函数
    registerOnTouched(fn: any): void {
        this.onTouched = fn;
    }

    // 设置禁用状态
    setDisabledState?(isDisabled: boolean): void {

    }
}

TlTemplateDirective指令实现

ts 复制代码
import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
import { NzSafeAny } from "topdsm-lib/core/types";

@Directive({
  selector: '[tlTemplate]'
})
export class TlTemplateDirective {

  @Input('tlTemplate')
  name: string = "default"

  // @Input()
  // type: string = ""

  constructor(private viewContainer: ViewContainerRef, public template: TemplateRef<NzSafeAny>) {
    //this.template = templateRef;
  }
  ngOnInit(): void {
    this.viewContainer.createEmbeddedView(this.template)
  }
  getType() {
    return this.name;
  }
}

事件绑定、解绑

复制代码
export const on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

export const off = (function() {
    if (document.removeEventListener) {
        return function(element, event, handler) {
            if (element && event) {
                element.removeEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event) {
                element.detachEvent('on' + event, handler);
            }
        };
    }
})();

component.html

html 复制代码
<div [ngClass]="prefix + '-wrapper'" #outerWrapper>
    <div [ngClass]="prefix + '-horizontal'" *ngIf="isHorizontal; else verticalSlot">
        <div class="left-pane" [ngStyle]="{right: anotherOffset + '%'}" [ngClass]="paneClasses">
            <ng-container *ngTemplateOutlet="leftTemplate"></ng-container>
        </div>
        <div [ngClass]="prefix + '-trigger-con'" [ngStyle]="{left: offset + '%'}" (mousedown)="onTriggerMouseDown($event)">
            <div ngClass="tl-shrink-splitter-trigger tl-shrink-splitter-trigger-vertical" >
                <span class="tl-shrink-splitter-trigger-bar-con vertical" [ngClass]="triggrrClass" (mousedown)="expandChange($event)" nz-tooltip [nzTooltipTitle]="tooltipContent" [nzTooltipPlacement]="tooltipPosition" *ngIf="tlShowExpandIcon"></span>
            </div>
        </div>
        <div class="right-pane" [ngStyle]="{left: offset + '%'}" [ngClass]="paneClasses">
            <ng-container *ngTemplateOutlet="rightTemplate"></ng-container>
        </div>
    </div>
    
    <ng-template #verticalSlot>
        <div [ngClass]="prefix + '-vertical'" >
            <div class="top-pane" [ngStyle]="{bottom: anotherOffset + '%'}" [ngClass]="paneClasses">
                <ng-container *ngTemplateOutlet="topTemplate"></ng-container>
            </div>
            <div [ngClass]="prefix + '-trigger-con'" [ngStyle]="{top: offset + '%'}" (mousedown)="onTriggerMouseDown($event)">
                <div ngClass="tl-shrink-splitter-trigger tl-shrink-splitter-trigger-horizontal" >
                    <span class="tl-shrink-splitter-trigger-bar-con horizontal" [ngClass]="triggrrClass" (mousedown)="expandChange($event)" nz-tooltip [nzTooltipTitle]="tooltipContent" [nzTooltipPlacement]="tooltipPosition" *ngIf="tlShowExpandIcon"></span>
                </div>
            </div>
            <div class="bottom-pane" [ngStyle]="{top: offset + '%'}" [ngClass]="paneClasses">
                <ng-container *ngTemplateOutlet="bottomTemplate"></ng-container>
            </div>
        </div>
    </ng-template>
</div>

component.less

less 复制代码
@split-prefix-cls: ~"tl-shrink-splitter";
@trigger-bar-background: rgba(23, 35, 61, 0.25);
@trigger-background: #f3f4f7;
@trigger-width: 8px;
@trigger-bar-width: 4px;
@trigger-bar-offset: (@trigger-width - @trigger-bar-width) / 2;
@trigger-bar-interval: 3px;
@trigger-bar-weight: 1px;
@trigger-bar-con-height: 24px;
@trigger-bar-con-width: 24px;
.tl-shrink-splitter{
    position: relative;
    height: 100%;
    width: 100%;
}
.tl-shrink-splitter-wrapper{
    position: relative;
    height: 100%;
    width: 100%;
}

.@{split-prefix-cls}{
    background-color: #fff;
    border: 1px solid #dee2e6;
    &-pane{
        position: absolute;
        //transition: all .3s ease-in;
        padding: 8px;

        &.tl-shrink-splitter-pane-moving{
            transition: none;
        }
        &.left-pane, &.right-pane {
            top: 0;
            bottom: 0;
        }
        &.left-pane {
            left: 0;
        }
        &.right-pane {
            right: 0;
            padding-left: 16px;
        }
        &.top-pane, &.bottom-pane {
            left: 0;
            right: 0;
        }
        &.top-pane {
            top: 0;
        }
        &.bottom-pane {
            bottom: 0;
            padding-top: 16px;
        }
        &-moving{
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }
        &-transition{
            transition: all .3s ease-in; 
        }
    }

    &-trigger{
        border: 1px solid #dcdee2;
        &-con {
            position: absolute;
            transform: translate(-50%, -50%);
            z-index: 10;
        }
        &-bar-con {
            position: absolute;
            overflow: hidden;
            background-image: linear-gradient(90deg, #dcf3fc, #f8fdff);
            border: 1px solid #76b9d6;
            color: #76b9d6;
            &:hover{
                color: #000 !important;
            }
            &.vertical {
                top: 50%;
                left: -6px;
                width: @trigger-bar-con-width;
                height: @trigger-bar-con-height;
                //background-color: #fff;
                //border: 1px solid #ccc;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                //color: #b2b2b2;
                font-size: 14px;
                cursor: pointer;
            }
            &.horizontal {
                left: 50%;
                top: -4px;
                width: @trigger-bar-con-height;
                height: @trigger-bar-con-width;
                //transform: translate(-50%, 0);
                background-color: #fff;
                border: 1px solid #ccc;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                color: #b2b2b2;
                font-size: 14px;
                cursor: pointer;
            }
        }
        &-vertical {
            width: @trigger-width;
            height: 100%;
            background: @trigger-background;
            border-top: none;
            border-bottom: none;
            //cursor: col-resize;
            .@{split-prefix-cls}-trigger-bar {
                width: @trigger-bar-width;
                height: 1px;
                background: @trigger-bar-background;
                float: left;
                margin-top: @trigger-bar-interval;
            }
        }
        &-horizontal {
            height: @trigger-width;
            width: 100%;
            background: @trigger-background;
            border-left: none;
            border-right: none;
            //cursor: row-resize;
            .@{split-prefix-cls}-trigger-bar {
                height: @trigger-bar-width;
                width: 1px;
                background: @trigger-bar-background;
                float: left;
                margin-right: @trigger-bar-interval;
            }
        }
    }

    &-horizontal {
        .@{split-prefix-cls}-trigger-con {
            top: 50%;
            height: 100%;
            width: 0;
        }
    }
    &-vertical {
        .@{split-prefix-cls}-trigger-con {
            left: 50%;
            height: 0;
            width: 100%;
        }
    }
}

.tl-shrink-splitter.dragable{
    .tl-shrink-splitter-trigger-vertical{
        cursor: col-resize;
    }
    .tl-shrink-splitter-trigger-horizontal{
        cursor: row-resize;
    }
}


.tl-shrink-splitter.contract{
    .tl-shrink-splitter-trigger-vertical{
        width: 0;
        padding-left: 0;
    }
    .tl-shrink-splitter-trigger-horizontal{
        height: 0;
        padding-top: 0;
    }
    .tl-shrink-splitter-trigger{
        border: 0;
    }

    &.contract-left{
        .tl-shrink-splitter-pane.left-pane{
            width: 0;
            padding: 0;
            overflow: hidden;
        }
        .right-pane{
            padding-left: 8px;
        }
    }

    .tl-shrink-splitter-trigger-bar-con{
        &.vertical{
            left: -6px;
        }
    }
    
    &.contract-right{
        .tl-shrink-splitter-trigger-bar-con{
            &.vertical{
                left: -16px;
            }
        }
    }

    &.contract-top{
        .tl-shrink-splitter-pane.top-pane{
            overflow: hidden;
            height: 0;
            padding: 0;
        }
        .bottom-pane{
            padding-top: 8px;
        }
        .tl-shrink-splitter-trigger-bar-con.horizontal{
            transform: rotate(90deg);
        }
    }

    &.contract-bottom{
        .tl-shrink-splitter-trigger-bar-con{
            &.horizontal{
                top: -16px;
            }
        }
        .tl-shrink-splitter-pane.bottom-pane{
            overflow: hidden;
            height: 0;
            padding: 0;
        }
        .top-pane{
            padding-top: 8px;
        }
        .tl-shrink-splitter-trigger-bar-con.horizontal{
            transform: rotate(90deg);
        }
    }
}
.tl-shrink-splitter.expand{
    .tl-shrink-splitter-trigger-bar-con{
        &.vertical{
            left: -8px;
        }
    }

    &.contract-top{
        .tl-shrink-splitter-trigger-bar-con.horizontal{
            transform: rotate(90deg);
        }
    }

    &.contract-bottom{
        .tl-shrink-splitter-trigger-bar-con.horizontal{
            transform: rotate(90deg);
        }
    }
}

页面效果

左右容器和上下容器

demo

左侧容器

ts 复制代码
import { Component } from '@angular/core';

@Component({
  selector: 'tl-demo-shrink-splitter-basic',
  template: `
  <button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button>
    <div class="split-box">
      <tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value" (onMoving)="triggerMoveHnadle($event)">
        <ng-template tlTemplate="left">
          <div>左侧区域自定义</div>
        </ng-template>
        <ng-template tlTemplate="right">
          <div>右侧区域自定义</div>
        </ng-template>
      </tl-shrink-splitter>  
    </div>
  `,
  styles: [
    `
      .split-box{
        height: 200px;
        display: flex;
        position: relative;
        overflow: hidden;
      }
      .split-right{
        margin-left: 10px;
        border: 1px solid #e3e3e3;
        flex:1;
      }
    `
  ]
})
export class TlDemoShrinkSplitterBasicComponent {

  expand = true

  value = 0.3

  expandChange(){
    this.expand = !this.expand
  }

  triggerMoveHnadle(e){
    console.log(e);
    console.log(this.value);  
  }
}

右侧容器

复制代码
import { Component } from '@angular/core';

@Component({
  selector: 'tl-demo-shrink-splitter-right',
  template: `
  <button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button>
    <div class="split-box">
      <tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value" tlColsedMode="right">
        <ng-template tlTemplate="left">
          <div>左侧区域自定义</div>
        </ng-template>
        <ng-template tlTemplate="right">
          <div>右侧区域自定义</div>
        </ng-template>
      </tl-shrink-splitter>  
    </div>
  `,
  styles: [
    `
      .split-box{
        height: 300px;
        display: flex;
        position: relative;
        overflow: hidden;
      }
      .split-right{
        margin-left: 10px;
        border: 1px solid #e3e3e3;
        flex:1;
      }
    `
  ]
})
export class TlDemoShrinkSplitterRightComponent {

  expand = true

  value = "200px"

  expandChange(){
    this.expand = !this.expand
  }
}

顶部容器

复制代码
import { Component } from '@angular/core';

@Component({
  selector: 'tl-demo-shrink-splitter-top',
  template: `
  <button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button>
    <div class="split-box">
      <tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value"  tlColsedMode="top">
        <ng-template tlTemplate="top">
          <div>顶部区域自定义</div>
        </ng-template>
        <ng-template tlTemplate="bottom">
          <div>底部区域自定义</div>
        </ng-template>
      </tl-shrink-splitter>  
    </div>
  `,
  styles: [
    `
      .split-box{
        height: 300px;
        display: flex;
        position: relative;
        overflow: hidden;
      }
      .split-right{
        margin-left: 10px;
        border: 1px solid #e3e3e3;
        flex:1;
      }
    `
  ]
})
export class TlDemoShrinkSplitterTopComponent {

  expand = true

  value = "100px"

  expandChange(){
    this.expand = !this.expand
  }
}

底部容器

复制代码
import { Component } from '@angular/core';

@Component({
    selector: 'tl-demo-shrink-splitter-bottom',
    template: `
    <button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button>
    <div class="split-box">
        <tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value"  tlColsedMode="bottom">
            <ng-template tlTemplate="top">
                <div>顶部区域自定义</div>
            </ng-template>
            <ng-template tlTemplate="bottom">
                <div>底部区域自定义</div>
            </ng-template>
        </tl-shrink-splitter>  
    </div>
  `,
    styles: [
        `
      .split-box{
        height: 300px;
        display: flex;
        position: relative;
        overflow: hidden;
      }
      .split-right{
        margin-left: 10px;
        border: 1px solid #e3e3e3;
        flex:1;
      }
    `
    ]
})
export class TlDemoShrinkSplitterBottomComponent {

    expand = true

    value = "100px"

    expandChange() {
        this.expand = !this.expand
    }
}

API

tl-shrink-splitter

参数 说明 类型 默认值
[ngModel] 面板位置,可以是 0~1 代表百分比,或具体数值的像素,可用 ngModel 双向绑定 `string number`
[tlExpand] 是否展开容器 boolean true
[tlDragable] 是否可以拖拽拖拽容器大小 boolean true
[tlZIndex] 组件z-index number 10
[tlShowExpandIcon] 是否展示收起展开切换状态的按钮icon boolean true
[tlColsedMode] 收起容器模式,上下左右哪一个容器应用收起展开的状态 `'left' 'right'
[tlMin] 最小阈值,类型为number时,数值范围为0 ~ 1 表示宽度百分比 `string number`
[tlMax] 最大阈值,类型为number时,数值范围为0 ~ 1 表示宽度百分比 `string number`
[tlExpandTooltipContent] 展开状态时的tooltip
[tlContractTooltipContent] 收起状态时的tooltip

事件

事件名 参数 描述
tlExpandChange evt(Boolean) 容器展开/收起 change
onMoveStart 拖拽开始
onMoving evt(MouseEvent) 拖拽中
onMoveEnd 拖拽结束
相关推荐
轻语呢喃6 分钟前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端
coding随想19 分钟前
JavaScript中的BOM:Window对象全解析
开发语言·javascript·ecmascript
難釋懷20 分钟前
TypeScript-webpack
javascript·webpack·typescript
Rockson23 分钟前
使用Ruby接入实时行情API教程
javascript·python
前端小巷子2 小时前
Web开发中的文件上传
前端·javascript·面试
上单带刀不带妹2 小时前
手写 Vue 中虚拟 DOM 到真实 DOM 的完整过程
开发语言·前端·javascript·vue.js·前端框架
前端风云志3 小时前
typescript结构化类型应用两例
javascript
gnip4 小时前
总结一期正则表达式
javascript·正则表达式
爱分享的程序员4 小时前
前端面试专栏-算法篇:18. 查找算法(二分查找、哈希查找)
前端·javascript·node.js
翻滚吧键盘4 小时前
vue 条件渲染(v-if v-else-if v-else v-show)
前端·javascript·vue.js