From 233774535909a19c1b04962c20de51557a402b6f Mon Sep 17 00:00:00 2001 From: Harpush Date: Mon, 20 Nov 2023 23:13:50 +0200 Subject: [PATCH] feat: support custom gutter template --- cypress/e2e/5.style.cy.js | 52 +++- .../src/lib/component/split.component.ts | 47 ++- .../src/lib/gutter/gutter-num-token.ts | 8 + .../split-gutter-drag-handle.directive.ts | 22 ++ ...split-gutter-dynamic-injector.directive.ts | 44 +++ ...plit-gutter-exclude-from-drag.directive.ts | 22 ++ .../src/lib/gutter/split-gutter.directive.ts | 97 ++++++ projects/angular-split/src/lib/interface.ts | 2 +- projects/angular-split/src/lib/module.ts | 22 +- projects/angular-split/src/public_api.ts | 3 + .../documentation.component.html | 63 +++- .../documentation/documentation.component.ts | 36 +++ .../custom-gutter-style.component.scss | 278 +++++++++--------- .../custom-gutter-style.component.ts | 82 +++++- 14 files changed, 609 insertions(+), 169 deletions(-) create mode 100644 projects/angular-split/src/lib/gutter/gutter-num-token.ts create mode 100644 projects/angular-split/src/lib/gutter/split-gutter-drag-handle.directive.ts create mode 100644 projects/angular-split/src/lib/gutter/split-gutter-dynamic-injector.directive.ts create mode 100644 projects/angular-split/src/lib/gutter/split-gutter-exclude-from-drag.directive.ts create mode 100644 projects/angular-split/src/lib/gutter/split-gutter.directive.ts diff --git a/cypress/e2e/5.style.cy.js b/cypress/e2e/5.style.cy.js index 2b716464..d42e0875 100644 --- a/cypress/e2e/5.style.cy.js +++ b/cypress/e2e/5.style.cy.js @@ -3,29 +3,53 @@ import { moveGutterByMouse, checkSplitDirAndSizes } from '../support/splitUtils' context('Custom split style example page tests', () => { - const W = 1070 + const W = 1076 const H = 300 - const GUTTER = 35 - const COLOR = 'rgb(255, 255, 0)' - const IMGH = `url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAjCAYAAABl/XGVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANkSURBVEiJrddfaJdlFAfwzztda6tdZNo/pZstWlmhSy8siiwhGfQP3IWRIGUEJRgFQXTbZRBeFBhlBv25iFD6s4zCqI0GaWXEhNK5tTCUsJGUWQtOF++zeH7vfnO/3/JcPef7nOd8z3ne5znPeWlCgi3BeHAieDoomlnfDNGy4HBwabAoGAieOReOW4Lu4IIMuyd4LdMXBceCJY36bamXAb7C2/g+eC5YgFPonLYr+DXZ3J/WtQZ9wfrgvEazejHYmsbtwa7g1eDC4OegPbNdGXwdtAXDwVvBm8Fg0DpnZliMsRT9n9iCbmzAPtybZfeNMutnMVawsSgz/QmbGsnsoeD1CrYs+DHYFuytzD0RTAWPZtiGYGcjZK3BgaC/gvcFI+nYL60EEsGTGbYq+CRYF7wQPBWcPxthV8qkq4K/FJwJNmdYWyLbnmGXBKeD74L7gpeDV86WYX+wP2jLsDuS460V26lgT6YXiWxV0tuDk/UOCCjKYz2OuzN4EL+ho2L+Oy6qXe54wYGkT1H/NOayG32Zh7/xkez4JzmN/Zl+PX7J9CsxMRfZXtwZtUG9X4fsD3yW6bfgaKZ3YfSsZKlKjEp7n2RA9h2TnMJQpt+sNtPuOcky5+uzAE5ipGIzVjCZ6V3YQXlY8DDea4RsBFdVsOHpQdriLzL9Guwqyq2lvK/HC4YEPcGHwQdR7nWNBL3BpxWsJRsvqLwOj0/XxTR3KOidnvw42BTcFoxWCYOlwaEGdmDaviMbb47yCv0HTGaRrA2+rCy+Lvi2UbJs3ZJgLOipIasYHQmuyPT+4I15kO0OtlXBo8HFFaPbg4XBAym6m+ZBdiTK56oG3BusyfTH0kcdD3YGy5slSn5ORPXFDrbnVTxhq/Ns50HUGeUDWiML8QOuzsGi9vbPR1aoc4JblIV1Y6TG5RzJGtnFr5H02g4HO2bs8zwk2BOsO5tBWyIbCi7/H0RFlP1kZyPGD6brMKN0NUjWE2Xf2fCCG1N0i+e2nrH2keD5enN1q35RRvYu7mqWDLfi84bJkgxi9TzIenGwWbIJWX/YhFyGY82SjeHaqNOzzybBSkymxqg5SaXsYJQt+fIo+/p8viNYkd6td4KJYO1s/ub8c0wVvx83KJ/8FuXF/wdncFhZmgawr+Cv2Xz9Cy1ParntgE8FAAAAAElFTkSuQmCC")` - const IMGV = `url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAdCAYAAAAgqdWEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAOxSURBVEiJtddtzNdTGAfwz7+6y91qKpaGF0UjY8JmrIw1sdj0YFMeMzYLJWaUmac1bfQiw0hkkjErTy+whrARm0zztHmYp3mayjyFkny9+J3bfv7uf3e3/p03v3Od872u8z3Xua7rnB9tamFQmB1eDhvC7+G7sCZcHw5u11o9ETkmPBBODgeGjtBZ+qeEZWFjeCKM2Z1ExodN4eKwPBzdAtc/XB6+DbPbsfCwcEI5jqXhvfBLeCacFfrvhI2R4f1wW2jU5xotFPrhMIwt38PLd198ibexHi9hbYM/ermp4ViD1Q2ubgU6LjwafgwJH4cHw5XhxDCsN4v2QGifEuDTmic6wpJC4KewIBzSwkiPR9ELQqeH7/+1ybCoEFka9qqNd4ZTwx0lZW8Pg9tFpqzxdLilSzggbA831gATwqrC+vkwP4xuJ4naWhPD5jBIuDG8HfqGGaW/KkwJA5sURxV822pFaISPwpmNsBq/YgS2YVaDD+tgnIQ5GIXzG7y1iwQGNNhak+9Dp/BpiZcVoW8NMDjMDR+UenJuSfldbs0ZFC4K65XsWd+1UBgabi3p/UqYXC9O4YhwXbghdPxPMvPCkTV5fPhFqaCTyuBp4ZvwQji2ycDk8E54N8xpjqdekrkszKrJR4Ytwlep7o1zwp+p7o+6J4aEx8I9qarwLrfimcU1eVL4vB9+wBDcjXkNbq+B9sMjuKJRlf92tYH4syaPwzrhs+KNN5o8MrSk+fFtJNFle3mYX/qdJYlmCJ8UQjObFFaEle0mUmx/HMaFPqneQe+GDiVYE/avgSeEv8IRO2m8IxwVLgn3hyk7wB4Tfk715Fib6lV4aNfkveHzJoVnw3M9LD41LA6vht/Khr4MN4XOFnqN2ua/CHemepb8A7gmrK3JI4pXprYwODJV+V4TFtZInB36tNpA0V0YtpQC+t+3VJge3qzJ0wrrvt1gO8K6cs57hNfDi6nd9C1IDC0xuK3VJruAo4snpoc9w5Ph2hbY21JdDQPCXeWI9tiB7WHF8xtSVfTWRGpKTxV3p7hxeDeYS0tsjC1BuDHs08LeQYXs5lTPkEWtsN0pd4bVhczD3cxflao6n1fkh8KCbnBjUv2ObE91r81sFcw9Edq7kJlaGxseVoat4cLa+DvN6ZvqV2VLOcaJvSbQDaGvS0BOS3UrbyqZM74J91q4uSZfUDby+I5iqLdkJqSqyNtTPSvmhgHd4OaWzFiWqtZsLTVjh6m9W1qqMr6kBPRH4Yx22P0bNcolauCjiTYAAAAASUVORK5CYII=")` beforeEach(() => { cy.visit('/examples/custom-gutter-style') }) - it('Verify gutter size color and horizontal image', () => { - checkSplitDirAndSizes('.ex-a as-split', 'horizontal', W, H, GUTTER, [312.296875, 728.6875]) + // ----- EXAMPLE A - cy.get('.ex-a as-split > .as-split-gutter').should('have.css', 'background-color', COLOR) - cy.get('.ex-a as-split > .as-split-gutter > .as-split-gutter-icon').should('have.css', 'background-image', IMGH) + it('should display initial state for example a', () => { + checkSplitDirAndSizes('.ex-a > as-split', 'horizontal', W, H, 35, [301.796875, 402.390625, 301.8125]) }) - xit('Change direction', () => { - cy.get('.btns > .btn').click() - checkSplitDirAndSizes('.ex-a as-split', 'vertical', W, H, GUTTER, [79.5, 185.5]) + it('should not move from non handle for example a', () => { + moveGutterByMouse('.ex-a .as-split-gutter', 0, 280, 0) + checkSplitDirAndSizes('.ex-a > as-split', 'horizontal', W, H, 35, [301.796875, 402.390625, 301.8125]) + }) + + it('should move from handle for example a', () => { + moveGutterByMouse('.ex-a .as-split-gutter .custom-hand-gutter-icon', 0, 280, 0) + checkSplitDirAndSizes('.ex-a > as-split', 'horizontal', W, H, 35, [581.796875, 122.390625, 301.8125]) + }) + + // ----- EXAMPLE B + + it('should display initial state for example b', () => { + checkSplitDirAndSizes('.ex-b > as-split', 'horizontal', W, H, 1, [322.1875, 537, 214.796875]) + }) + + // ----- EXAMPLE C + + it('should display initial state for example c', () => { + checkSplitDirAndSizes('.ex-c > as-split', 'horizontal', W, H, 25, [300.296875, 100.09375, 400.390625, 200.1875]) + }) + + it('should not move from collapse button for example c', () => { + moveGutterByMouse('.ex-c .as-split-gutter .custom-collapse-gutter-header div', 0, 50, 0) + checkSplitDirAndSizes('.ex-c > as-split', 'horizontal', W, H, 25, [300.296875, 100.09375, 400.390625, 200.1875]) + }) + + it('should move from anywhere other than buttons for example c', () => { + moveGutterByMouse('.ex-c .as-split-gutter', 0, 50, 0) + checkSplitDirAndSizes('.ex-c > as-split', 'horizontal', W, H, 25, [350.296875, 50.09375, 400.390625, 200.1875]) + }) - cy.get('.ex-a as-split > .as-split-gutter').should('have.css', 'background-color', COLOR) - cy.get('.ex-a as-split > .as-split-gutter > .as-split-gutter-icon').should('have.css', 'background-image', IMGV) + it('should collapse left area on collapse button click', () => { + cy.get('.ex-c .as-split-gutter:first-of-type .custom-collapse-gutter-header div:first-of-type').click() + checkSplitDirAndSizes('.ex-c > as-split', 'horizontal', W, H, 25, [0, 400.390625, 400.390625, 200.1875]) }) }) diff --git a/projects/angular-split/src/lib/component/split.component.ts b/projects/angular-split/src/lib/component/split.component.ts index b00afe15..bc4a89fa 100644 --- a/projects/angular-split/src/lib/component/split.component.ts +++ b/projects/angular-split/src/lib/component/split.component.ts @@ -15,6 +15,7 @@ import { ViewEncapsulation, Inject, Optional, + ContentChild, } from '@angular/core' import { Observable, Subscriber, Subject } from 'rxjs' import { debounceTime } from 'rxjs/operators' @@ -46,6 +47,7 @@ import { getKeyboardEndpoint, } from '../utils' import { ANGULAR_SPLIT_DEFAULT_OPTIONS } from '../angular-split-config.token' +import { SplitGutterDirective } from '../gutter/split-gutter.directive' /** * angular-split @@ -83,13 +85,21 @@ import { ANGULAR_SPLIT_DEFAULT_OPTIONS } from '../angular-split-config.token' changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: [`./split.component.scss`], template: ` - + `, encapsulation: ViewEncapsulation.Emulated, }) export class SplitComponent implements AfterViewInit, OnDestroy { + @ContentChild(SplitGutterDirective) customGutter: SplitGutterDirective @Input() set direction(v: ISplitDirection) { this._direction = v === 'vertical' ? 'vertical' : 'horizontal' @@ -288,6 +319,7 @@ export class SplitComponent implements AfterViewInit, OnDestroy { @ViewChildren('gutterEls') private gutterEls: QueryList _clickTimeout: number | null = null + draggedGutterNum: number = undefined public ngAfterViewInit() { this.ngZone.runOutsideAngular(() => { @@ -584,6 +616,10 @@ export class SplitComponent implements AfterViewInit, OnDestroy { } public startMouseDrag(event: MouseEvent | TouchEvent, gutterOrder: number, gutterNum: number): void { + if (this.customGutter && !this.customGutter.canStartDragging(event.target as HTMLElement, gutterNum)) { + return + } + event.preventDefault() event.stopPropagation() @@ -709,7 +745,8 @@ export class SplitComponent implements AfterViewInit, OnDestroy { this.isWaitingInitialMove = false this.renderer.addClass(this.elRef.nativeElement, 'as-dragging') - this.renderer.addClass(this.gutterEls.toArray()[this.snapshot.gutterNum - 1].nativeElement, 'as-dragged') + this.draggedGutterNum = this.snapshot.gutterNum + this.cdRef.markForCheck() this.notify('start', this.snapshot.gutterNum) }) @@ -848,7 +885,9 @@ export class SplitComponent implements AfterViewInit, OnDestroy { } this.renderer.removeClass(this.elRef.nativeElement, 'as-dragging') - this.renderer.removeClass(this.gutterEls.toArray()[this.snapshot.gutterNum - 1].nativeElement, 'as-dragged') + this.draggedGutterNum = undefined + this.cdRef.markForCheck() + this.snapshot = null this.isWaitingClear = true diff --git a/projects/angular-split/src/lib/gutter/gutter-num-token.ts b/projects/angular-split/src/lib/gutter/gutter-num-token.ts new file mode 100644 index 00000000..8ffff0f2 --- /dev/null +++ b/projects/angular-split/src/lib/gutter/gutter-num-token.ts @@ -0,0 +1,8 @@ +import { InjectionToken } from '@angular/core' + +/** + * Identifies the gutter by number through DI + * to allow SplitGutterDragHandleDirective and SplitGutterExcludeFromDragDirective to know + * the gutter template context without inputs + */ +export const GUTTER_NUM_TOKEN = new InjectionToken('Gutter num') diff --git a/projects/angular-split/src/lib/gutter/split-gutter-drag-handle.directive.ts b/projects/angular-split/src/lib/gutter/split-gutter-drag-handle.directive.ts new file mode 100644 index 00000000..a73364cd --- /dev/null +++ b/projects/angular-split/src/lib/gutter/split-gutter-drag-handle.directive.ts @@ -0,0 +1,22 @@ +import { Directive, OnInit, OnDestroy, Inject, ElementRef } from '@angular/core' +import { SplitGutterDirective } from './split-gutter.directive' +import { GUTTER_NUM_TOKEN } from './gutter-num-token' + +@Directive({ + selector: '[asSplitGutterDragHandle]', +}) +export class SplitGutterDragHandleDirective implements OnInit, OnDestroy { + constructor( + @Inject(GUTTER_NUM_TOKEN) private gutterNum: number, + private elementRef: ElementRef, + private gutterDir: SplitGutterDirective, + ) {} + + ngOnInit(): void { + this.gutterDir.addToMap(this.gutterDir.gutterToHandleElementMap, this.gutterNum, this.elementRef) + } + + ngOnDestroy(): void { + this.gutterDir.removedFromMap(this.gutterDir.gutterToHandleElementMap, this.gutterNum, this.elementRef) + } +} diff --git a/projects/angular-split/src/lib/gutter/split-gutter-dynamic-injector.directive.ts b/projects/angular-split/src/lib/gutter/split-gutter-dynamic-injector.directive.ts new file mode 100644 index 00000000..1d8de996 --- /dev/null +++ b/projects/angular-split/src/lib/gutter/split-gutter-dynamic-injector.directive.ts @@ -0,0 +1,44 @@ +import { Injector, Directive, Input, ViewContainerRef, TemplateRef } from '@angular/core' +import { GUTTER_NUM_TOKEN } from './gutter-num-token' + +interface SplitGutterDynamicInjectorTemplateContext { + $implicit: Injector +} + +/** + * This directive allows creating a dynamic injector inside ngFor + * with dynamic gutter num and expose the injector for ngTemplateOutlet usage + */ +@Directive({ + selector: '[asSplitGutterDynamicInjector]', +}) +export class SplitGutterDynamicInjectorDirective { + @Input('asSplitGutterDynamicInjector') + public set gutterNum(value: number) { + this.vcr.clear() + + const injector = Injector.create({ + providers: [ + { + provide: GUTTER_NUM_TOKEN, + useValue: value, + }, + ], + parent: this.vcr.injector, + }) + + this.vcr.createEmbeddedView(this.templateRef, { $implicit: injector }) + } + + constructor( + private vcr: ViewContainerRef, + private templateRef: TemplateRef, + ) {} + + static ngTemplateContextGuard( + dir: SplitGutterDynamicInjectorDirective, + ctx: unknown, + ): ctx is SplitGutterDynamicInjectorTemplateContext { + return true + } +} diff --git a/projects/angular-split/src/lib/gutter/split-gutter-exclude-from-drag.directive.ts b/projects/angular-split/src/lib/gutter/split-gutter-exclude-from-drag.directive.ts new file mode 100644 index 00000000..24a1c324 --- /dev/null +++ b/projects/angular-split/src/lib/gutter/split-gutter-exclude-from-drag.directive.ts @@ -0,0 +1,22 @@ +import { Directive, OnInit, OnDestroy, Inject, ElementRef } from '@angular/core' +import { SplitGutterDirective } from './split-gutter.directive' +import { GUTTER_NUM_TOKEN } from './gutter-num-token' + +@Directive({ + selector: '[asSplitGutterExcludeFromDrag]', +}) +export class SplitGutterExcludeFromDragDirective implements OnInit, OnDestroy { + constructor( + @Inject(GUTTER_NUM_TOKEN) private gutterNum: number, + private elementRef: ElementRef, + private gutterDir: SplitGutterDirective, + ) {} + + ngOnInit(): void { + this.gutterDir.addToMap(this.gutterDir.gutterToExcludeDragElementMap, this.gutterNum, this.elementRef) + } + + ngOnDestroy(): void { + this.gutterDir.removedFromMap(this.gutterDir.gutterToExcludeDragElementMap, this.gutterNum, this.elementRef) + } +} diff --git a/projects/angular-split/src/lib/gutter/split-gutter.directive.ts b/projects/angular-split/src/lib/gutter/split-gutter.directive.ts new file mode 100644 index 00000000..8876a2a4 --- /dev/null +++ b/projects/angular-split/src/lib/gutter/split-gutter.directive.ts @@ -0,0 +1,97 @@ +import { Directive, ElementRef, TemplateRef } from '@angular/core' +import { IArea } from '../interface' + +export interface SplitGutterTemplateContext { + /** + * The area before the gutter. + * In RTL the right area and in LTR the left area + */ + areaBefore: IArea + /** + * The area after the gutter. + * In RTL the left area and in LTR the right area + */ + areaAfter: IArea + /** + * The absolute number of the gutter based on direction (RTL and LTR). + * First gutter is 1, second is 2, etc... + */ + gutterNum: number + /** + * Whether this is the first gutter. + * In RTL the most right area and in LTR the most left area + */ + first: boolean + /** + * Whether this is the last gutter. + * In RTL the most left area and in LTR the most right area + */ + last: boolean + /** + * Whether the gutter is being dragged now + */ + isDragged: boolean +} + +@Directive({ + selector: '[asSplitGutter]', +}) +export class SplitGutterDirective { + /** + * The map holds reference to the drag handle elements inside instances + * of the provided template. + */ + public gutterToHandleElementMap = new Map[]>() + /** + * The map holds reference to the excluded drag elements inside instances + * of the provided template. + */ + public gutterToExcludeDragElementMap = new Map[]>() + + constructor(public template: TemplateRef) {} + + public canStartDragging(originElement: HTMLElement, gutterNum: number) { + if (this.gutterToExcludeDragElementMap.has(gutterNum)) { + const isInsideExclude = this.gutterToExcludeDragElementMap + .get(gutterNum) + .some((gutterExcludeElement) => gutterExcludeElement.nativeElement.contains(originElement)) + + if (isInsideExclude) { + return false + } + } + + if (this.gutterToHandleElementMap.has(gutterNum)) { + return this.gutterToHandleElementMap + .get(gutterNum) + .some((gutterHandleElement) => gutterHandleElement.nativeElement.contains(originElement)) + } + + return true + } + + public addToMap(map: Map[]>, gutterNum: number, elementRef: ElementRef) { + if (map.has(gutterNum)) { + map.get(gutterNum).push(elementRef) + } else { + map.set(gutterNum, [elementRef]) + } + } + + public removedFromMap( + map: Map[]>, + gutterNum: number, + elementRef: ElementRef, + ) { + const elements = map.get(gutterNum) + elements.splice(elements.indexOf(elementRef), 1) + + if (elements.length === 0) { + map.delete(gutterNum) + } + } + + static ngTemplateContextGuard(dir: SplitGutterDirective, ctx: unknown): ctx is SplitGutterTemplateContext { + return true + } +} diff --git a/projects/angular-split/src/lib/interface.ts b/projects/angular-split/src/lib/interface.ts index 44fdcbf6..f079109f 100644 --- a/projects/angular-split/src/lib/interface.ts +++ b/projects/angular-split/src/lib/interface.ts @@ -1,4 +1,4 @@ -import { SplitAreaDirective } from './directive/split-area.directive' +import type { SplitAreaDirective } from './directive/split-area.directive' export type ISplitDirection = 'horizontal' | 'vertical' diff --git a/projects/angular-split/src/lib/module.ts b/projects/angular-split/src/lib/module.ts index 61997c52..db90ee92 100644 --- a/projects/angular-split/src/lib/module.ts +++ b/projects/angular-split/src/lib/module.ts @@ -1,12 +1,28 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' - import { SplitComponent } from './component/split.component' import { SplitAreaDirective } from './directive/split-area.directive' +import { SplitGutterDirective } from './gutter/split-gutter.directive' +import { SplitGutterDragHandleDirective } from './gutter/split-gutter-drag-handle.directive' +import { SplitGutterDynamicInjectorDirective } from './gutter/split-gutter-dynamic-injector.directive' +import { SplitGutterExcludeFromDragDirective } from './gutter/split-gutter-exclude-from-drag.directive' @NgModule({ imports: [CommonModule], - declarations: [SplitComponent, SplitAreaDirective], - exports: [SplitComponent, SplitAreaDirective], + declarations: [ + SplitComponent, + SplitAreaDirective, + SplitGutterDirective, + SplitGutterDragHandleDirective, + SplitGutterDynamicInjectorDirective, + SplitGutterExcludeFromDragDirective, + ], + exports: [ + SplitComponent, + SplitAreaDirective, + SplitGutterDirective, + SplitGutterDragHandleDirective, + SplitGutterExcludeFromDragDirective, + ], }) export class AngularSplitModule {} diff --git a/projects/angular-split/src/public_api.ts b/projects/angular-split/src/public_api.ts index 45465dc8..9fe5caad 100644 --- a/projects/angular-split/src/public_api.ts +++ b/projects/angular-split/src/public_api.ts @@ -4,6 +4,9 @@ export { AngularSplitModule } from './lib/module' export { SplitComponent } from './lib/component/split.component' +export { SplitGutterDirective, SplitGutterTemplateContext } from './lib/gutter/split-gutter.directive' +export { SplitGutterDragHandleDirective } from './lib/gutter/split-gutter-drag-handle.directive' +export { SplitGutterExcludeFromDragDirective } from './lib/gutter/split-gutter-exclude-from-drag.directive' export { SplitAreaDirective } from './lib/directive/split-area.directive' export { ANGULAR_SPLIT_DEFAULT_OPTIONS } from './lib/angular-split-config.token' export * from './lib/interface' diff --git a/src/app/documentation/documentation.component.html b/src/app/documentation/documentation.component.html index b1c84ced..9cdae522 100644 --- a/src/app/documentation/documentation.component.html +++ b/src/app/documentation/documentation.component.html @@ -1,9 +1,7 @@

- SplitComponent (selector: 'as-split' / exportAs: 'asSplit') + SplitComponent (selector: 'as-split' / exportAs: + 'asSplit')

@@ -64,11 +62,8 @@



- SplitAreaDirective (selector: 'as-split-area, [as-split-area]' / exportAs: 'asSplitArea') + SplitAreaDirective (selector: 'as-split-area, + [as-split-area]' / exportAs: 'asSplitArea')

@@ -92,6 +87,54 @@



+

+ SplitGutterDirective (selector: '[asSplitGutter] + ') +

+

Used as structural directive (*asSplitGutter) to customize the gutter.

+

Mostly requires width: 100% and height: 100% in order to fill the whole gutter.

+

If the custom gutter is wider than the declared gutter size (using gutterSize input) the areas will overflow too. + So attention is required to match the size of the gutter.

+ + + + + + + + + + + + + + + +
Template context propertyTypeDetails
{{ prop.name }}{{ prop.type }}
+

+ +

+ SplitGutterDragHandleDirective (selector: '[asSplitGutterDragHandle] + ') +

+

Used to declare an area inside asSplitGutter to be a drag handle

+

Can be declared on multiple elements which means all of them are drag handles.

+

+ +

+ SplitGutterExcludeFromDragDirective (selector: '[asSplitGutterExcludeFromDrag] + ') +

+

Used to declare an area inside asSplitGutter to be excluded from drag

+

Can be declared on multiple elements which means all of them are excluded from drag.

+

asSplitGutterExcludeFromDrag inside asSplitGutterDragHandle works as expected (all of the element is a handle + except the excluded area)

+

asSplitGutterDragHandle inside asSplitGutterExcludeFromDrag is ignored and the whole element marked with exclude is + excluded.

+

+

CSS classes

@@ -141,4 +184,4 @@

CSS classes

-
+ \ No newline at end of file diff --git a/src/app/documentation/documentation.component.ts b/src/app/documentation/documentation.component.ts index 19775714..9a67fce2 100644 --- a/src/app/documentation/documentation.component.ts +++ b/src/app/documentation/documentation.component.ts @@ -187,6 +187,42 @@ export class DocumentationComponent { ], } + readonly splitGutterDoc = { + templateContext: [ + { + name: 'areaBefore', + type: 'IArea', + details: 'The area before the gutter. In RTL the right area and in LTR the left area', + }, + { + name: 'areaAfter', + type: 'IArea', + details: 'The area after the gutter. In RTL the left area and in LTR the right area', + }, + { + name: 'gutterNum', + type: 'number', + details: + 'The absolute number of the gutter based on direction (RTL and LTR). First gutter is 1, second is 2, etc...', + }, + { + name: 'first', + type: 'boolean', + details: 'Whether this is the first gutter. In RTL the most right area and in LTR the most left area', + }, + { + name: 'last', + type: 'boolean', + details: 'Whether this is the last gutter. In RTL the most left area and in LTR the most right area', + }, + { + name: 'isDragged', + type: 'boolean', + details: 'Whether the gutter is being dragged now', + }, + ], + } + readonly cssClasses = { split: [ { name: 'as-init', details: 'Added after component initialization.' }, diff --git a/src/app/examples/custom-gutter-style/custom-gutter-style.component.scss b/src/app/examples/custom-gutter-style/custom-gutter-style.component.scss index 3d46676b..6c318054 100644 --- a/src/app/examples/custom-gutter-style/custom-gutter-style.component.scss +++ b/src/app/examples/custom-gutter-style/custom-gutter-style.component.scss @@ -1,151 +1,159 @@ -:host { - .ex-a as-split { - overflow: inherit; - - ::ng-deep .as-split-gutter { - background-color: yellow !important; - align-items: flex-end !important; - pointer-events: none; - - & > .as-split-gutter-icon { - pointer-events: all; - transition: all 0.3s; - opacity: 0.6; - - &:hover { - opacity: 1; - } - } - } +// #region example a - &.as-horizontal ::ng-deep .as-split-gutter-icon { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAjCAYAAABl/XGVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANkSURBVEiJrddfaJdlFAfwzztda6tdZNo/pZstWlmhSy8siiwhGfQP3IWRIGUEJRgFQXTbZRBeFBhlBv25iFD6s4zCqI0GaWXEhNK5tTCUsJGUWQtOF++zeH7vfnO/3/JcPef7nOd8z3ne5znPeWlCgi3BeHAieDoomlnfDNGy4HBwabAoGAieOReOW4Lu4IIMuyd4LdMXBceCJY36bamXAb7C2/g+eC5YgFPonLYr+DXZ3J/WtQZ9wfrgvEazejHYmsbtwa7g1eDC4OegPbNdGXwdtAXDwVvBm8Fg0DpnZliMsRT9n9iCbmzAPtybZfeNMutnMVawsSgz/QmbGsnsoeD1CrYs+DHYFuytzD0RTAWPZtiGYGcjZK3BgaC/gvcFI+nYL60EEsGTGbYq+CRYF7wQPBWcPxthV8qkq4K/FJwJNmdYWyLbnmGXBKeD74L7gpeDV86WYX+wP2jLsDuS460V26lgT6YXiWxV0tuDk/UOCCjKYz2OuzN4EL+ho2L+Oy6qXe54wYGkT1H/NOayG32Zh7/xkez4JzmN/Zl+PX7J9CsxMRfZXtwZtUG9X4fsD3yW6bfgaKZ3YfSsZKlKjEp7n2RA9h2TnMJQpt+sNtPuOcky5+uzAE5ipGIzVjCZ6V3YQXlY8DDea4RsBFdVsOHpQdriLzL9Guwqyq2lvK/HC4YEPcGHwQdR7nWNBL3BpxWsJRsvqLwOj0/XxTR3KOidnvw42BTcFoxWCYOlwaEGdmDaviMbb47yCv0HTGaRrA2+rCy+Lvi2UbJs3ZJgLOipIasYHQmuyPT+4I15kO0OtlXBo8HFFaPbg4XBAym6m+ZBdiTK56oG3BusyfTH0kcdD3YGy5slSn5ORPXFDrbnVTxhq/Ns50HUGeUDWiML8QOuzsGi9vbPR1aoc4JblIV1Y6TG5RzJGtnFr5H02g4HO2bs8zwk2BOsO5tBWyIbCi7/H0RFlP1kZyPGD6brMKN0NUjWE2Xf2fCCG1N0i+e2nrH2keD5enN1q35RRvYu7mqWDLfi84bJkgxi9TzIenGwWbIJWX/YhFyGY82SjeHaqNOzzybBSkymxqg5SaXsYJQt+fIo+/p8viNYkd6td4KJYO1s/ub8c0wVvx83KJ/8FuXF/wdncFhZmgawr+Cv2Xz9Cy1ParntgE8FAAAAAElFTkSuQmCC') !important; - width: 27px !important; - height: 35px !important; - cursor: grab; - margin-bottom: 5px; - } +.custom-hand-gutter { + width: 100%; + height: 100%; + display: flex; + background-color: #ff0; + cursor: default; - &.as-vertical ::ng-deep .as-split-gutter-icon { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAdCAYAAAAgqdWEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAOxSURBVEiJtddtzNdTGAfwz7+6y91qKpaGF0UjY8JmrIw1sdj0YFMeMzYLJWaUmac1bfQiw0hkkjErTy+whrARm0zztHmYp3mayjyFkny9+J3bfv7uf3e3/p03v3Od872u8z3Xua7rnB9tamFQmB1eDhvC7+G7sCZcHw5u11o9ETkmPBBODgeGjtBZ+qeEZWFjeCKM2Z1ExodN4eKwPBzdAtc/XB6+DbPbsfCwcEI5jqXhvfBLeCacFfrvhI2R4f1wW2jU5xotFPrhMIwt38PLd198ibexHi9hbYM/ermp4ViD1Q2ubgU6LjwafgwJH4cHw5XhxDCsN4v2QGifEuDTmic6wpJC4KewIBzSwkiPR9ELQqeH7/+1ybCoEFka9qqNd4ZTwx0lZW8Pg9tFpqzxdLilSzggbA831gATwqrC+vkwP4xuJ4naWhPD5jBIuDG8HfqGGaW/KkwJA5sURxV822pFaISPwpmNsBq/YgS2YVaDD+tgnIQ5GIXzG7y1iwQGNNhak+9Dp/BpiZcVoW8NMDjMDR+UenJuSfldbs0ZFC4K65XsWd+1UBgabi3p/UqYXC9O4YhwXbghdPxPMvPCkTV5fPhFqaCTyuBp4ZvwQji2ycDk8E54N8xpjqdekrkszKrJR4Ytwlep7o1zwp+p7o+6J4aEx8I9qarwLrfimcU1eVL4vB9+wBDcjXkNbq+B9sMjuKJRlf92tYH4syaPwzrhs+KNN5o8MrSk+fFtJNFle3mYX/qdJYlmCJ8UQjObFFaEle0mUmx/HMaFPqneQe+GDiVYE/avgSeEv8IRO2m8IxwVLgn3hyk7wB4Tfk715Fib6lV4aNfkveHzJoVnw3M9LD41LA6vht/Khr4MN4XOFnqN2ua/CHemepb8A7gmrK3JI4pXprYwODJV+V4TFtZInB36tNpA0V0YtpQC+t+3VJge3qzJ0wrrvt1gO8K6cs57hNfDi6nd9C1IDC0xuK3VJruAo4snpoc9w5Ph2hbY21JdDQPCXeWI9tiB7WHF8xtSVfTWRGpKTxV3p7hxeDeYS0tsjC1BuDHs08LeQYXs5lTPkEWtsN0pd4bVhczD3cxflao6n1fkh8KCbnBjUv2ObE91r81sFcw9Edq7kJlaGxseVoat4cLa+DvN6ZvqV2VLOcaJvSbQDaGvS0BOS3UrbyqZM74J91q4uSZfUDby+I5iqLdkJqSqyNtTPSvmhgHd4OaWzFiWqtZsLTVjh6m9W1qqMr6kBPRH4Yx22P0bNcolauCjiTYAAAAASUVORK5CYII=') !important; - width: 35px !important; - height: 29px !important; - cursor: grab; - margin-right: 5px; - } + .as-horizontal & { + align-items: flex-end; + justify-content: center; + } - /* Code by Chris Bracco (https://chrisbracco.com/a-simple-css-tooltip/) */ - - ::ng-deep .as-split-gutter > .as-split-gutter-icon { - position: relative; - z-index: 2; - - /* Hide the tooltip content by default */ - &:before, - &:after { - visibility: hidden; - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)'; - filter: 'progid: DXImageTransform.Microsoft.Alpha(Opacity=0)'; - opacity: 0; - pointer-events: none; - } - - /* Position tooltip above the element */ - &:before { - position: absolute; - bottom: 110%; - left: 50%; - margin-bottom: 5px; - margin-left: -80px; - padding: 7px; - width: 160px; - border-radius: 3px; - background-color: hsla(0, 0%, 20%, 0.9); - color: #fff; - content: 'Drag me if you can!'; - text-align: center; - font-size: 14px; - line-height: 1.2; - } - - /* Triangle hack to make tooltip look like a speech bubble */ - &:after { - position: absolute; - bottom: 110%; - left: 50%; - margin-left: -5px; - width: 0; - border-top: 5px solid hsla(0, 0%, 20%, 0.9); - border-right: 5px solid transparent; - border-left: 5px solid transparent; - content: ' '; - font-size: 0; - line-height: 0; - } - } + .as-vertical & { + align-items: center; + justify-content: flex-end; + } +} - /* Show tooltip content on hover */ - ::ng-deep .as-split-gutter.as-dragged > .as-split-gutter-icon, - ::ng-deep .as-split-gutter > .as-split-gutter-icon:hover { - opacity: 1; - - &:before, - &:after { - visibility: visible; - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; - filter: 'progid: DXImageTransform.Microsoft.Alpha(Opacity=100)'; - opacity: 1; - } - } +.custom-hand-gutter-icon { + opacity: 0.6; + transition: opacity 0.3s; + cursor: grab; + + .as-horizontal & { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAjCAYAAABl/XGVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANkSURBVEiJrddfaJdlFAfwzztda6tdZNo/pZstWlmhSy8siiwhGfQP3IWRIGUEJRgFQXTbZRBeFBhlBv25iFD6s4zCqI0GaWXEhNK5tTCUsJGUWQtOF++zeH7vfnO/3/JcPef7nOd8z3ne5znPeWlCgi3BeHAieDoomlnfDNGy4HBwabAoGAieOReOW4Lu4IIMuyd4LdMXBceCJY36bamXAb7C2/g+eC5YgFPonLYr+DXZ3J/WtQZ9wfrgvEazejHYmsbtwa7g1eDC4OegPbNdGXwdtAXDwVvBm8Fg0DpnZliMsRT9n9iCbmzAPtybZfeNMutnMVawsSgz/QmbGsnsoeD1CrYs+DHYFuytzD0RTAWPZtiGYGcjZK3BgaC/gvcFI+nYL60EEsGTGbYq+CRYF7wQPBWcPxthV8qkq4K/FJwJNmdYWyLbnmGXBKeD74L7gpeDV86WYX+wP2jLsDuS460V26lgT6YXiWxV0tuDk/UOCCjKYz2OuzN4EL+ho2L+Oy6qXe54wYGkT1H/NOayG32Zh7/xkez4JzmN/Zl+PX7J9CsxMRfZXtwZtUG9X4fsD3yW6bfgaKZ3YfSsZKlKjEp7n2RA9h2TnMJQpt+sNtPuOcky5+uzAE5ipGIzVjCZ6V3YQXlY8DDea4RsBFdVsOHpQdriLzL9Guwqyq2lvK/HC4YEPcGHwQdR7nWNBL3BpxWsJRsvqLwOj0/XxTR3KOidnvw42BTcFoxWCYOlwaEGdmDaviMbb47yCv0HTGaRrA2+rCy+Lvi2UbJs3ZJgLOipIasYHQmuyPT+4I15kO0OtlXBo8HFFaPbg4XBAym6m+ZBdiTK56oG3BusyfTH0kcdD3YGy5slSn5ORPXFDrbnVTxhq/Ns50HUGeUDWiML8QOuzsGi9vbPR1aoc4JblIV1Y6TG5RzJGtnFr5H02g4HO2bs8zwk2BOsO5tBWyIbCi7/H0RFlP1kZyPGD6brMKN0NUjWE2Xf2fCCG1N0i+e2nrH2keD5enN1q35RRvYu7mqWDLfi84bJkgxi9TzIenGwWbIJWX/YhFyGY82SjeHaqNOzzybBSkymxqg5SaXsYJQt+fIo+/p8viNYkd6td4KJYO1s/ub8c0wVvx83KJ/8FuXF/wdncFhZmgawr+Cv2Xz9Cy1ParntgE8FAAAAAElFTkSuQmCC') !important; + width: 27px; + height: 35px; + margin-bottom: 5px; } - .ex-b as-split { - .as-split-area { - background-color: lightgrey; + .as-vertical & { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAdCAYAAAAgqdWEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAOxSURBVEiJtddtzNdTGAfwz7+6y91qKpaGF0UjY8JmrIw1sdj0YFMeMzYLJWaUmac1bfQiw0hkkjErTy+whrARm0zztHmYp3mayjyFkny9+J3bfv7uf3e3/p03v3Od872u8z3Xua7rnB9tamFQmB1eDhvC7+G7sCZcHw5u11o9ETkmPBBODgeGjtBZ+qeEZWFjeCKM2Z1ExodN4eKwPBzdAtc/XB6+DbPbsfCwcEI5jqXhvfBLeCacFfrvhI2R4f1wW2jU5xotFPrhMIwt38PLd198ibexHi9hbYM/ermp4ViD1Q2ubgU6LjwafgwJH4cHw5XhxDCsN4v2QGifEuDTmic6wpJC4KewIBzSwkiPR9ELQqeH7/+1ybCoEFka9qqNd4ZTwx0lZW8Pg9tFpqzxdLilSzggbA831gATwqrC+vkwP4xuJ4naWhPD5jBIuDG8HfqGGaW/KkwJA5sURxV822pFaISPwpmNsBq/YgS2YVaDD+tgnIQ5GIXzG7y1iwQGNNhak+9Dp/BpiZcVoW8NMDjMDR+UenJuSfldbs0ZFC4K65XsWd+1UBgabi3p/UqYXC9O4YhwXbghdPxPMvPCkTV5fPhFqaCTyuBp4ZvwQji2ycDk8E54N8xpjqdekrkszKrJR4Ytwlep7o1zwp+p7o+6J4aEx8I9qarwLrfimcU1eVL4vB9+wBDcjXkNbq+B9sMjuKJRlf92tYH4syaPwzrhs+KNN5o8MrSk+fFtJNFle3mYX/qdJYlmCJ8UQjObFFaEle0mUmx/HMaFPqneQe+GDiVYE/avgSeEv8IRO2m8IxwVLgn3hyk7wB4Tfk715Fib6lV4aNfkveHzJoVnw3M9LD41LA6vht/Khr4MN4XOFnqN2ua/CHemepb8A7gmrK3JI4pXprYwODJV+V4TFtZInB36tNpA0V0YtpQC+t+3VJge3qzJ0wrrvt1gO8K6cs57hNfDi6nd9C1IDC0xuK3VJruAo4snpoc9w5Ph2hbY21JdDQPCXeWI9tiB7WHF8xtSVfTWRGpKTxV3p7hxeDeYS0tsjC1BuDHs08LeQYXs5lTPkEWtsN0pd4bVhczD3cxflao6n1fkh8KCbnBjUv2ObE91r81sFcw9Edq7kJlaGxseVoat4cLa+DvN6ZvqV2VLOcaJvSbQDaGvS0BOS3UrbyqZM74J91q4uSZfUDby+I5iqLdkJqSqyNtTPSvmhgHd4OaWzFiWqtZsLTVjh6m9W1qqMr6kBPRH4Yx22P0bNcolauCjiTYAAAAASUVORK5CYII=') !important; + width: 35px; + height: 29px; + margin-right: 5px; + } - & > p { - padding: 5px; - } - } + &:hover { + opacity: 1; + } +} - ::ng-deep .as-split-gutter { - background-color: grey !important; - position: relative; - } +// #endregion - ::ng-deep .as-split-gutter-icon { - background-image: none !important; - background-color: black; - transition: opacity 0.3s; - opacity: 0; - position: absolute; - } +// #region example b - ::ng-deep .as-split-gutter.as-dragged .as-split-gutter-icon, - ::ng-deep .as-split-gutter-icon:hover { - opacity: 0.1; - } +.ex-b { + as-split-area { + background-color: #d3d3d3; - &.as-horizontal { - ::ng-deep .as-split-gutter-icon { - width: 17px !important; - left: -8px; - right: 8px; - } - } - &.as-vertical { - ::ng-deep .as-split-gutter-icon { - height: 17px !important; - top: -8px; - bottom: 8px; - } + p { + padding: 10px; } } +} - .btns { - display: flex; - justify-content: center; - align-items: center; - flex-wrap: wrap; +.custom-shade-gutter { + width: 100%; + height: 100%; + background-color: grey; + position: relative; +} + +.custom-shade-gutter-icon { + height: 100%; + width: 100%; + background-color: black; + transition: opacity 0.3s; + opacity: 0; + position: absolute; + + .dragged &, + &:hover { + opacity: 0.1; + } + + $shadeSize: 8px; + + .as-horizontal & { + width: 1 + ($shadeSize * 2); + left: -$shadeSize; + right: $shadeSize; + } + + .as-vertical & { + height: 1 + ($shadeSize * 2); + top: -$shadeSize; + bottom: $shadeSize; + } +} + +// #endregion + +// #region example c + +.custom-collapse-gutter { + display: flex; + justify-content: space-between; + align-items: center; + height: 100%; + width: 100%; + + .as-vertical & { + flex-direction: row; + } + + .as-horizontal & { + flex-direction: column; + } +} + +.custom-collapse-gutter-header { + display: flex; + flex: 1; + + .as-vertical & { + flex-direction: row; + } + + .as-horizontal & { + flex-direction: column; + } + + & > div { + cursor: pointer; + } +} + +.custom-collapse-gutter-icon { + background-position: center center; + background-repeat: no-repeat; + + .as-vertical & { + width: 30px; + height: 5px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFCAMAAABl/6zIAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAABRJREFUeAFjYGRkwIMJSeMHlBkOABP7AEGzSuPKAAAAAElFTkSuQmCC'); } + + .as-horizontal & { + width: 5px; + height: 30px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + } +} + +.custom-collapse-gutter-ghost { + flex: 1; +} + +// #endregion + +.btns { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; } diff --git a/src/app/examples/custom-gutter-style/custom-gutter-style.component.ts b/src/app/examples/custom-gutter-style/custom-gutter-style.component.ts index c43cda9f..95cb3c22 100644 --- a/src/app/examples/custom-gutter-style/custom-gutter-style.component.ts +++ b/src/app/examples/custom-gutter-style/custom-gutter-style.component.ts @@ -1,6 +1,6 @@ import { Component, ChangeDetectionStrategy } from '@angular/core' import { AComponent } from '../../ui/components/AComponent' -import { ISplitDirection } from 'angular-split' +import { IAreaSize, IOutputData, ISplitDirection } from 'angular-split' @Component({ selector: 'sp-ex-custom-gutter-style', @@ -15,6 +15,9 @@ import { ISplitDirection } from 'angular-split'
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tiam, quis nostrud exercitation @@ -22,7 +25,14 @@ import { ISplitDirection } from 'angular-split' voluptate velit esse cillum dolore eu fugiat nulla pariatur.

- + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tiam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in + voluptate velit esse cillum dolore eu fugiat nulla pariatur. +

+
+

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt @@ -38,6 +48,9 @@ import { ISplitDirection } from 'angular-split'

+
+
+

A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A @@ -55,6 +68,58 @@ import { ISplitDirection } from 'angular-split' >

+
+ +
+
+
+ {{ direction === 'horizontal' ? '◀' : '🔼' }} +
+
+ {{ direction === 'horizontal' ? '▶' : '🔽' }} +
+
+
+
+
+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tiam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in + voluptate velit esse cillum dolore eu fugiat nulla pariatur. +

+
+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tiam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in + voluptate velit esse cillum dolore eu fugiat nulla pariatur. +

+
+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tiam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in + voluptate velit esse cillum dolore eu fugiat nulla pariatur. +

+
+ +

+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam + rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt + explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia + consequuntur magni dolores eodolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi + tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis + nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis + autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel + illum qui dolorem eum fugiat quo voluptas nulla pariatur? +

+
+
+