-
Notifications
You must be signed in to change notification settings - Fork 156
/
Copy pathsplitter.ts
2871 lines (2688 loc) · 127 KB
/
splitter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { Component, Property, setStyleAttribute, ChildProperty, compile } from '@syncfusion/ej2-base';
import { NotifyPropertyChanges, addClass, Collection, isNullOrUndefined } from '@syncfusion/ej2-base';
import { Event, EmitType, EventHandler, selectAll, removeClass, select, Browser, detach, formatUnit } from '@syncfusion/ej2-base';
import { SanitizeHtmlHelper, extend } from '@syncfusion/ej2-base';
import { SplitterModel, PanePropertiesModel } from './splitter-model';
const ROOT: string = 'e-splitter';
const HORIZONTAL_PANE: string = 'e-splitter-horizontal';
const VERTICAL_PANE: string = 'e-splitter-vertical';
const PANE: string = 'e-pane';
const SPLIT_H_PANE: string = 'e-pane-horizontal';
const SPLIT_V_PANE: string = 'e-pane-vertical';
const SPLIT_BAR: string = 'e-split-bar';
const SPLIT_H_BAR: string = 'e-split-bar-horizontal';
const SPLIT_V_BAR: string = 'e-split-bar-vertical';
const STATIC_PANE: string = 'e-static-pane';
const SCROLL_PANE: string = 'e-scrollable';
const RESIZE_BAR: string = 'e-resize-handler';
const RESIZABLE_BAR: string = 'e-resizable-split-bar';
const SPLIT_BAR_HOVER: string = 'e-split-bar-hover';
const SPLIT_BAR_ACTIVE: string = 'e-split-bar-active';
const HIDE_HANDLER: string = 'e-hide-handler';
const SPLIT_TOUCH: string = 'e-splitter-touch';
const DISABLED: string = 'e-disabled';
const RTL: string = 'e-rtl';
const E_ICONS: string = 'e-icons';
const COLLAPSIBLE: string = 'e-collapsible';
const NAVIGATE_ARROW: string = 'e-navigate-arrow';
const ARROW_RIGHT: string = 'e-arrow-right';
const ARROW_LEFT: string = 'e-arrow-left';
const ARROW_UP: string = 'e-arrow-up';
const ARROW_DOWN: string = 'e-arrow-down';
const HIDE_ICON: string = 'e-icon-hidden';
const EXPAND_PANE: string = 'e-expanded';
const COLLAPSE_PANE: string = 'e-collapsed';
const PANE_HIDDEN: string = 'e-pane-hidden';
const RESIZABLE_PANE: string = 'e-resizable';
const LAST_BAR: string = 'e-last-bar';
const BAR_SIZE_DEFAULT: number = 1;
/**
* Interface to configure pane properties such as its content, size, min, max, resizable, collapsed and collapsible.
*/
export class PaneProperties extends ChildProperty<PaneProperties> {
/**
* Configures the properties for each pane.
*
* @default ''
*/
@Property()
public size: string;
/**
* Specifies whether a pane is collapsible or not collapsible.
*
* {% codeBlock src='splitter/collapsible/index.md' %}{% endcodeBlock %}
*
* @default false
*/
@Property(false)
public collapsible: boolean;
/**
* Specifies whether a pane is collapsed or not collapsed at the initial rendering of splitter.
*
* {% codeBlock src='splitter/collapsed/index.md' %}{% endcodeBlock %}
*
* @default false
*/
@Property(false)
public collapsed: boolean;
/**
* Specifies the value whether a pane is resizable. By default, the Splitter is resizable in all panes.
* You can disable this for any specific panes using this property.
*
* @default true
*/
@Property(true)
public resizable: boolean;
/**
* Specifies the minimum size of a pane. The pane cannot be resized if it is less than the specified minimum size.
*
* @default null
*/
@Property(null)
public min: string;
/**
* Specifies the maximum size of a pane. The pane cannot be resized if it is more than the specified maximum limit.
*
* @default null
*/
@Property(null)
public max: string;
/**
* Specifies the content of split pane as plain text, HTML markup, or any other JavaScript controls.
*
* @default ''
* @blazorType string
*/
@Property()
public content: string | HTMLElement;
/**
* Specifies the CSS class names that defines specific user-defined
* styles and themes to be appended on corresponding pane of the Splitter.
* It is used to customize the Splitter control panes.
* One or more custom CSS classes can be specified to the Splitter panes.
*
* @default ''
*/
@Property('')
public cssClass: string;
}
/**
* Provides information about a SanitizeSelectors.
*/
export interface SanitizeSelectors {
/** Returns the tags. */
tags?: string[]
/** Returns the attributes. */
attributes?: SanitizeRemoveAttrs[]
}
/**
* Provides information about a BeforeSanitizeHtml event.
*/
export interface BeforeSanitizeHtmlArgs {
/** Illustrates whether the current action needs to be prevented or not. */
cancel?: boolean
/** It is a callback function and executed it before our inbuilt action. It should return HTML as a string.
*
* @function
* @param {string} value - Returns the value.
* @returns {string}
*/
helper?: Function
/** Returns the selectors object which carrying both tags and attributes selectors to block list of cross-site scripting attack.
* Also possible to modify the block list in this event.
*/
selectors?: SanitizeSelectors
}
/**
* Provides information about a SanitizeRemoveAttributes.
*/
export interface SanitizeRemoveAttrs {
/** Defines the attribute name to sanitize */
attribute?: string
/** Defines the selector that sanitize the specified attributes within the selector */
selector?: string
}
/**
* Specifies a value that indicates whether to align the split panes horizontally or vertically.
*/
export type Orientation = 'Horizontal' | 'Vertical';
/**
* Splitter is a layout user interface (UI) control that has resizable and collapsible split panes.
* The container can be split into multiple panes, which are oriented horizontally or vertically.
* The separator (divider) splits the panes and resizes and expands/collapses the panes.
* The splitter is placed inside the split pane to make a nested layout user interface.
*
* ```html
* <div id="splitter">
* <div> Left Pane </div>
* <div> Center Pane </div>
* <div> Right Pane </div>
* </div>
* ```
* ```typescript
* <script>
* var splitterObj = new Splitter({ width: '300px', height: '200px'});
* splitterObj.appendTo('#splitter');
* </script>
* ```
*/
@NotifyPropertyChanges
export class Splitter extends Component<HTMLElement> {
private onReportWindowSize: EventListenerOrEventListenerObject;
private onMouseMoveHandler: EventListenerOrEventListenerObject;
private onMouseUpHandler: EventListenerOrEventListenerObject;
private onTouchMoveHandler: EventListenerOrEventListenerObject;
private onTouchEndHandler: EventListenerOrEventListenerObject;
private allPanes: HTMLElement[];
private paneOrder: number[];
private separatorOrder: number[];
private currentSeparator: HTMLElement;
private allBars: HTMLElement[];
private previousCoordinates: Coordinates;
private currentCoordinates: Coordinates;
private totalWidth: number;
private totalPercent: number;
private order: number;
private previousPane: HTMLElement;
private nextPane: HTMLElement;
private prevPaneIndex: number;
private previousPaneHeightWidth: string | number;
private updatePrePaneInPercentage: boolean;
private updateNextPaneInPercentage: boolean;
private prePaneDimenson: number;
private nextPaneDimension: number;
private panesDimensions: number[];
private border: number;
private wrapper: HTMLElement;
private wrapperParent: HTMLElement;
private sizeFlag: boolean;
private prevPaneCurrentWidth: any;
private nextPaneCurrentWidth: any;
private nextPaneIndex: number;
private nextPaneHeightWidth: string | number;
private validDataAttributes: string[];
private validElementAttributes: string[];
private arrow: string;
private currentBarIndex: number;
private prevBar: HTMLElement;
private nextBar: HTMLElement;
private splitInstance: PaneDetails;
private leftArrow: string;
private rightArrow: string;
private iconsDelay: number;
private templateElement: HTMLElement[];
private collapseFlag: boolean;
private expandFlag: boolean;
/**
* Specifies the height of the Splitter component that accepts both string and number values.
*
* @default '100%'
*/
@Property('100%')
public height: string;
/**
* Specifies the value whether splitter panes are reordered or not .
*
* @default true
*/
@Property(false)
public enableReversePanes: boolean;
/**
* Specifies the width of the Splitter control, which accepts both string and number values as width.
* The string value can be either in pixel or percentage format.
*
* @default '100%'
*/
@Property('100%')
public width: string;
/**
* Enables or disables the persisting component's state between page reloads.
*
* @default false
*/
@Property(false)
public enablePersistence: boolean;
/**
* Configures the individual pane behaviors such as content, size, resizable, minimum, maximum validation, collapsible and collapsed.
*
* {% codeBlock src='splitter/panesettings/index.md' %}{% endcodeBlock %}
*
* @default []
*/
@Collection<PanePropertiesModel>([], PaneProperties)
public paneSettings: PanePropertiesModel[];
/**
* Specifies a value that indicates whether to align the split panes horizontally or vertically.
* * Set the orientation property as "Horizontal" to create a horizontal splitter that aligns the panes left-to-right.
* * Set the orientation property as "Vertical" to create a vertical splitter that aligns the panes top-to-bottom.
*
* {% codeBlock src='splitter/orientation/index.md' %}{% endcodeBlock %}
*
* @default Horizontal
*/
@Property('Horizontal')
public orientation: Orientation;
/**
* Specifies the CSS class names that defines specific user-defined
* styles and themes to be appended on the root element of the Splitter.
* It is used to customize the Splitter control.
* One or more custom CSS classes can be specified to the Splitter.
*
* @default ''
*/
@Property('')
public cssClass: string;
/**
* Specifies boolean value that indicates whether the component is enabled or disabled.
* The Splitter component does not allow to interact when this property is disabled.
*
* @default true
*/
@Property(true)
public enabled: boolean;
/**
* Defines whether to allow the cross-scripting site or not.
*
* @default true
*/
@Property(true)
public enableHtmlSanitizer: boolean;
/**
* Specifies the size of the separator line for both horizontal or vertical orientation.
* The separator is used to separate the panes by lines.
*
* @default null
*/
@Property(null)
public separatorSize: number;
/**
* Event triggers before sanitize the value.
*
* @event 'event'
* @blazorProperty 'OnSanitizeHtml'
*/
@Event()
public beforeSanitizeHtml: EmitType<BeforeSanitizeHtmlArgs>;
/**
* Triggers after creating the splitter component with its panes.
*
* @event 'event'
* @blazorProperty 'Created'
*/
@Event()
public created: EmitType<Object>;
/* eslint-enable */
/**
* Triggers when the split pane is started to resize.
*
* @event 'event'
* @blazorProperty 'OnResizeStart'
*/
@Event()
public resizeStart: EmitType<ResizeEventArgs>;
/**
* Triggers when a split pane is being resized.
*
* @event 'event'
* @blazorProperty 'Resizing'
*/
@Event()
public resizing: EmitType<ResizingEventArgs>;
/**
* Triggers when the resizing of split pane is stopped.
*
* @event 'event'
* @blazorProperty 'OnResizeStop'
*/
@Event()
public resizeStop: EmitType<ResizingEventArgs>;
/**
* Triggers when before panes get collapsed.
*
* @event 'event'
* @blazorProperty 'OnCollapse'
*/
@Event()
public beforeCollapse: EmitType<BeforeExpandEventArgs>;
/**
* Triggers when before panes get expanded.
*
* @event 'event'
* @blazorProperty 'OnExpand'
*/
@Event()
public beforeExpand: EmitType<BeforeExpandEventArgs>;
/**
* Triggers when after panes get collapsed.
*
* @event 'event'
* @blazorProperty 'Collapsed'
*/
@Event()
public collapsed: EmitType<ExpandedEventArgs>;
protected needsID: boolean;
/**
* Triggers when after panes get expanded.
*
* @event 'event'
* @blazorProperty 'Expanded'
*/
@Event()
public expanded: EmitType<ExpandedEventArgs>;
/**
* Initializes a new instance of the Splitter class.
*
* @param options - Specifies Splitter model properties as options.
* @param element - Specifies the element that is rendered as an Splitter.
*/
public constructor(options?: SplitterModel, element?: string | HTMLElement) {
super(options, element);
this.needsID = true;
}
/**
* Gets called when the model property changes.The data that describes the old and new values of the property that changed.
*
* @param {SplitterModel} newProp - specifies the new property
* @param {SplitterModel} oldProp - specifies the old property
* @returns {void}
* @private
*/
public onPropertyChanged(newProp: SplitterModel, oldProp: SplitterModel): void {
if (!this.element.classList.contains(ROOT)) {
return;
}
for (const prop of Object.keys(newProp)) {
switch (prop) {
case 'height':
this.setSplitterSize(this.element, newProp.height, 'height');
break;
case 'width':
this.setSplitterSize(this.element, newProp.width, 'width');
break;
case 'cssClass':
this.setCssClass(this.element, newProp.cssClass);
break;
case 'enabled':
this.isEnabled(this.enabled);
break;
case 'enableReversePanes':
this.setReversePane();
break;
case 'separatorSize':
this.setSeparatorSize(newProp.separatorSize);
break;
case 'orientation':
this.changeOrientation(newProp.orientation);
break;
case 'paneSettings': {
if (!(newProp.paneSettings instanceof Array && oldProp.paneSettings instanceof Array)) {
const paneCounts: Object[] = Object.keys(newProp.paneSettings);
const isPaneContentChanged: boolean = paneCounts.some((count: number) =>
!isNullOrUndefined(newProp.paneSettings[count as number].content));
if ((this as any).isReact && isPaneContentChanged) {
let cPaneCount: number = 0;
for ( let k: number = 0; k < this.paneSettings.length; k++ ){
if (typeof(this.paneSettings[k as number].content) === 'function'){
cPaneCount = cPaneCount + 1;
}
}
const hasAllContent: boolean = cPaneCount === this.paneSettings.length;
if (hasAllContent){
this.clearTemplate();
}
}
for (let i: number = 0; i < paneCounts.length; i++) {
const index: number = parseInt(Object.keys(newProp.paneSettings)[i as number], 10);
const changedPropsCount: number = Object.keys(newProp.paneSettings[index as number]).length;
for (let j: number = 0; j < changedPropsCount; j++) {
const property: string = Object.keys(newProp.paneSettings[index as number])[j as number];
switch (property) {
case 'content': {
const newValue: string = Object(newProp.paneSettings[index as number])[`${property}`];
if (!isNullOrUndefined(newValue)) {
this.allPanes[index as number].innerHTML = '';
this.setTemplate(newValue, this.allPanes[index as number]);
}
break;
}
case 'resizable': {
const newVal: boolean = Object(newProp.paneSettings[index as number])[`${property}`];
this.resizableModel(index, newVal);
break;
}
case 'collapsible':
this.collapsibleModelUpdate(index);
break;
case 'collapsed':
if (newProp.paneSettings[index as number].collapsed) {
this.isCollapsed(index);
} else {
this.collapsedOnchange(index);
}
break;
case 'cssClass':
this.setCssClass(this.allPanes[index as number] as HTMLElement,
newProp.paneSettings[index as number].cssClass);
break;
case 'size': {
const newValSize: string = Object(newProp.paneSettings[index as number])[`${property}`];
if (newValSize !== '' && !isNullOrUndefined(newValSize)) {
this.updatePaneSize(newValSize, index);
}
break;
}
}
}
}
} else {
this.clearTemplate();
this.destroyPaneSettings();
this.allBars = [];
this.allPanes = [];
this.createSplitPane(this.element);
this.addSeparator(this.element);
this.getPanesDimensions();
this.setRTL(this.enableRtl);
this.isCollapsed();
}
break;
}
case 'enableRtl':
this.setRTL(newProp.enableRtl);
break;
}
}
}
private updatePaneSize(newValSize: string, index: number): void {
this.allPanes[index as number].style.flexBasis = newValSize;
const flexPaneIndexes: number[] = [];
let staticPaneWidth: number;
let flexCount: number = 0;
for (let i: number = 0; i < this.allPanes.length; i++) {
if (!this.paneSettings[i as number].size && !(this.allPanes[i as number].innerText === '')) {
flexPaneIndexes[flexCount as number] = i;
flexCount++;
} else if (this.paneSettings[i as number].size) {
staticPaneWidth = this.orientation === 'Horizontal' ? this.allPanes[index as number].offsetWidth : this.allPanes[index as number].offsetHeight;
}
}
staticPaneWidth = this.orientation === 'Horizontal' ? (this.allBars[0].offsetWidth * this.allBars.length) + staticPaneWidth :
(this.allBars[0].offsetHeight * this.allBars.length) + staticPaneWidth;
const flexPaneWidth: number = (this.orientation === 'Horizontal' ? this.element.offsetWidth : this.element.offsetHeight)
- staticPaneWidth - (this.border * 2);
const avgDiffWidth: number = flexPaneWidth / flexPaneIndexes.length;
for (let j: number = 0; j < flexPaneIndexes.length; j++) {
if (this.allPanes[flexPaneIndexes[j as number]].style.flexBasis !== '') {
this.allPanes[flexPaneIndexes[j as number]].style.flexBasis = avgDiffWidth + 'px';
}
}
this.allPanes[index as number].classList.add(STATIC_PANE);
}
protected initializeValues(): void {
this.allPanes = [];
this.paneOrder = [];
this.separatorOrder = [];
this.allBars = [];
this.previousCoordinates = {};
this.currentCoordinates = {};
this.updatePrePaneInPercentage = false;
this.updateNextPaneInPercentage = false;
this.panesDimensions = [];
this.border = 0;
this.validDataAttributes = ['data-size', 'data-min', 'data-max', 'data-collapsible',
'data-resizable', 'data-content', 'data-collapsed'];
this.validElementAttributes = ['data-orientation', 'data-width', 'data-height'];
this.iconsDelay = 300;
this.templateElement = [];
this.collapseFlag = false;
this.expandFlag = true;
}
protected preRender(): void {
this.initializeValues();
this.onReportWindowSize = this.reportWindowSize.bind(this);
this.onMouseMoveHandler = this.onMouseMove.bind(this);
this.onMouseUpHandler = this.onMouseUp.bind(this);
this.onTouchMoveHandler = this.onMouseMove.bind(this);
this.onTouchEndHandler = this.onMouseUp.bind(this);
this.wrapper = this.element.cloneNode(true) as HTMLElement;
this.wrapperParent = this.element.parentElement;
removeClass([this.wrapper], ['e-control', 'e-lib', ROOT]);
const orientation: string = this.orientation === 'Horizontal' ? HORIZONTAL_PANE : VERTICAL_PANE;
addClass([this.element], orientation);
const name: string = Browser.info.name;
const css: string = (name === 'msie') ? 'e-ie' : '';
this.setCssClass(this.element, css);
if (Browser.isDevice) {
addClass([this.element], SPLIT_TOUCH);
}
}
protected getPersistData(): string {
return this.addOnPersist(['paneSettings']);
}
/**
* Returns the current module name.
*
* @returns {string} - returns the string value
* @private
*/
protected getModuleName(): string {
return 'splitter';
}
/**
* To Initialize the control rendering
*
* @returns {void}
* @private
*/
public render(): void {
this.checkDataAttributes();
this.setCssClass(this.element, this.cssClass);
this.isEnabled(this.enabled);
this.setDimension(this.getHeight(this.element), this.getWidth(this.element));
this.createSplitPane(this.element);
this.addSeparator(this.element);
this.getPanesDimensions();
this.setPaneSettings();
this.setRTL(this.enableRtl);
if (this.enableReversePanes) {
this.setReversePane();
}
this.collapseFlag = true;
this.isCollapsed();
this.collapseFlag = false;
EventHandler.add(document, 'touchstart click', this.onDocumentClick, this);
this.renderComplete();
this.element.ownerDocument.defaultView.addEventListener('resize', this.onReportWindowSize, true);
EventHandler.add(this.element, 'keydown', this.onMove, this);
}
private onDocumentClick(e: Event | MouseEvent): void {
if (!(<HTMLElement>e.target).classList.contains(SPLIT_BAR) && !isNullOrUndefined(this.currentSeparator)) {
this.currentSeparator.classList.remove(SPLIT_BAR_HOVER);
this.currentSeparator.classList.remove(SPLIT_BAR_ACTIVE);
}
}
private checkPaneSize(e: MouseEvent | TouchEvent | PointerEvent | KeyboardEvent): void {
const prePaneSize: number = this.orientation === 'Horizontal' ? this.previousPane.offsetWidth : this.previousPane.offsetHeight;
const nextPaneSize: number = this.orientation === 'Horizontal' ? this.nextPane.offsetWidth : this.nextPane.offsetHeight;
const splitBarSize: number = isNullOrUndefined(this.separatorSize) ? BAR_SIZE_DEFAULT : this.separatorSize;
if ((this.previousPane.style.flexBasis.indexOf('%') > 0 || this.previousPane.style.flexBasis.indexOf('p') > 0 || this.nextPane.style.flexBasis.indexOf('%') > 0)) {
const previousFlexBasis: number = this.updatePaneFlexBasis(this.previousPane);
const nextFlexBasis: number = this.updatePaneFlexBasis(this.nextPane);
this.totalPercent = previousFlexBasis + nextFlexBasis;
this.totalWidth = this.convertPercentageToPixel(this.totalPercent + '%');
if (e.type === 'keydown' && (!isNullOrUndefined((<KeyboardEvent>e).keyCode))) {
if (((<KeyboardEvent>e).keyCode === 39 || ((<KeyboardEvent>e).keyCode === 40)) && nextPaneSize > 0 &&
(this.getMinInPixel(this.paneSettings[this.nextPaneIndex].min) <
this.convertPercentageToPixel((nextFlexBasis - 1) + '%'))) {
this.previousPane.style.flexBasis = (previousFlexBasis + 1) + '%';
this.nextPane.style.flexBasis = (nextFlexBasis - 1) + '%';
} else if (((<KeyboardEvent>e).keyCode === 37 || ((<KeyboardEvent>e).keyCode === 38)) && prePaneSize > 0 &&
(this.getMinInPixel(this.paneSettings[this.prevPaneIndex].min) <
this.convertPercentageToPixel((previousFlexBasis - 1) + '%'))) {
this.previousPane.style.flexBasis = (previousFlexBasis - 1) + '%';
this.nextPane.style.flexBasis = (nextFlexBasis + 1) + '%';
}
}
} else {
this.totalWidth = (this.orientation === 'Horizontal') ? this.previousPane.offsetWidth + this.nextPane.offsetWidth :
this.previousPane.offsetHeight + this.nextPane.offsetHeight;
if (e.type === 'keydown' && (!isNullOrUndefined((<KeyboardEvent>e).keyCode))) {
if (((<KeyboardEvent>e).keyCode === 39 || ((<KeyboardEvent>e).keyCode === 40)) && nextPaneSize > 0 &&
(this.getMinInPixel(this.paneSettings[this.nextPaneIndex].min) < (nextPaneSize + splitBarSize))) {
this.addStaticPaneClass();
this.previousPane.style.flexBasis = (prePaneSize + splitBarSize) + 'px';
this.nextPane.style.flexBasis = (nextPaneSize < splitBarSize) ? '0px' :
(nextPaneSize - splitBarSize) + 'px';
} else if (((<KeyboardEvent>e).keyCode === 37 || ((<KeyboardEvent>e).keyCode === 38)) && prePaneSize > 0 &&
(this.getMinInPixel(this.paneSettings[this.prevPaneIndex].min) < (prePaneSize - splitBarSize))) {
this.addStaticPaneClass();
this.previousPane.style.flexBasis = (prePaneSize < splitBarSize) ? '0px' :
(prePaneSize - splitBarSize) + 'px';
this.nextPane.style.flexBasis = (nextPaneSize + splitBarSize) + 'px';
}
}
}
}
private onMove(event: KeyboardEvent): void {
if (this.allPanes.length > 1) {
const index: number = this.getSeparatorIndex(this.currentSeparator);
const isPrevpaneCollapsed: boolean = this.previousPane.classList.contains(COLLAPSE_PANE);
const isPrevpaneExpanded: boolean = this.previousPane.classList.contains(EXPAND_PANE);
const isNextpaneCollapsed: boolean = this.nextPane.classList.contains(COLLAPSE_PANE);
if (((this.orientation !== 'Horizontal' && event.keyCode === 38) || (this.orientation === 'Horizontal' &&
event.keyCode === 39) ||
(this.orientation === 'Horizontal' && event.keyCode === 37) || (this.orientation !== 'Horizontal' && event.keyCode === 40))
&& (!isPrevpaneExpanded && !isNextpaneCollapsed && !isPrevpaneCollapsed || (isPrevpaneExpanded) && !isNextpaneCollapsed) &&
document.activeElement.classList.contains(SPLIT_BAR) && (this.paneSettings[index as number].resizable &&
this.paneSettings[index + 1].resizable)) {
event.preventDefault();
this.checkPaneSize(event);
this.triggerResizing(event);
} else if (event.keyCode === 13 && this.paneSettings[index as number].collapsible &&
document.activeElement.classList.contains(SPLIT_BAR) && this.currentSeparator.classList.contains(SPLIT_BAR_ACTIVE)) {
if (!this.previousPane.classList.contains(COLLAPSE_PANE)) {
this.collapse(index);
addClass([this.currentSeparator], SPLIT_BAR_ACTIVE);
} else {
this.expand(index);
addClass([this.currentSeparator], SPLIT_BAR_ACTIVE);
}
}
}
}
private getMinInPixel(minValue: string): number {
if (isNullOrUndefined(minValue)) { return 0; }
let paneMinRange: number = this.convertPixelToNumber(minValue.toString());
if (minValue.indexOf('%') > 0) {
paneMinRange = this.convertPercentageToPixel(minValue);
}
const min: number = this.convertPixelToNumber((paneMinRange).toString());
return min;
}
/**
* @param {string} value - specifies the string value
* @returns {string} returns the string
* @hidden
*/
public sanitizeHelper(value: string): string {
if (this.enableHtmlSanitizer) {
const item: BeforeSanitizeHtmlArgs = SanitizeHtmlHelper.beforeSanitize();
const beforeEvent: BeforeSanitizeHtmlArgs = {
cancel: false,
helper: null
};
extend(item, item, beforeEvent);
this.trigger('beforeSanitizeHtml', item);
if (item.cancel && !isNullOrUndefined(item.helper)) {
value = item.helper(value);
} else if (!item.cancel) {
value = SanitizeHtmlHelper.serializeValue(item, value);
}
}
return value;
}
private checkDataAttributes(): void {
let api: string;
let value: string | boolean;
// Element values
for (let dataIndex: number = 0; dataIndex < this.validElementAttributes.length; dataIndex++) {
value = this.element.getAttribute(this.validElementAttributes[dataIndex as number]);
if (!isNullOrUndefined(value)) {
api = this.removeDataPrefix(this.validElementAttributes[dataIndex as number]);
// eslint-disable-next-line
(this as any)[api] = value;
}
}
// Pane values
for (let paneIndex: number = 0; paneIndex < this.element.children.length; paneIndex++) {
for (let dataAttr: number = 0; dataAttr < this.validDataAttributes.length; dataAttr++) {
value = this.element.children[paneIndex as number].getAttribute(this.validDataAttributes[dataAttr as number]);
if (!isNullOrUndefined(value)) {
api = this.removeDataPrefix(this.validDataAttributes[dataAttr as number]);
value = (api === 'collapsible' || api === 'resizable') ? (value === 'true') : value;
if (isNullOrUndefined(this.paneSettings[paneIndex as number])) {
this.paneSettings[paneIndex as number] = {
size: '',
min: null,
max: null,
content: '',
resizable: true,
collapsible: false,
collapsed: false
};
}
// eslint-disable-next-line
let paneAPI: PanePropertiesModel = (this.paneSettings[paneIndex] as any)[api];
if (api === 'resizable' || api === 'collapsible' || api === 'collapsed') {
// eslint-disable-next-line
(this.paneSettings[paneIndex] as any)[api] = value;
}
if (isNullOrUndefined(paneAPI) || paneAPI === '') {
// eslint-disable-next-line
(this.paneSettings[paneIndex] as any)[api] = value;
}
}
}
}
}
private destroyPaneSettings(): void {
[].slice.call(this.element.children).forEach((el: HTMLElement) => {
detach(el);
});
this.restoreElem();
}
private setPaneSettings(): void {
const childCount: number = this.allPanes.length;
const paneCollection: PanePropertiesModel[] = [];
const paneValue: PanePropertiesModel = {
size: '',
min: null,
max: null,
content: '',
resizable: true,
collapsed: false,
collapsible: false,
cssClass: ''
};
for (let i: number = 0; i < childCount; i++) {
if (isNullOrUndefined(this.paneSettings[i as number])) {
paneCollection[i as number] = paneValue;
} else {
paneCollection[i as number] = this.paneSettings[i as number];
}
}
this.setProperties({ 'paneSettings': paneCollection }, true);
}
private checkArrow(paneIndex: number, targetArrow: string): HTMLElement {
return (this.allBars[paneIndex as number].querySelector('.' + NAVIGATE_ARROW + '.' + targetArrow));
}
private removeDataPrefix(attribute: string): string {
return attribute.slice(attribute.lastIndexOf('-') + 1);
}
private setRTL(rtl: boolean): void {
if (rtl) {
addClass([this.element], RTL);
} else {
removeClass([this.element], RTL);
}
}
private setReversePane(): void {
this.allPanes = this.allPanes.reverse();
this.allBars = this.allBars.reverse();
addClass([this.allBars[this.allBars.length - 1]], LAST_BAR);
removeClass([this.allBars[0]], LAST_BAR);
this.setProperties({ 'paneSettings': this.paneSettings.reverse() }, true);
if (this.enableReversePanes) {
this.element.setAttribute('dir', 'rtl');
} else {
this.element.removeAttribute('dir');
}
}
private setSplitterSize(element: HTMLElement, size: string, property: string): void {
const style: { [key: string]: Object } = property === 'width' ? { 'width': formatUnit(size) } : { 'height': formatUnit(size) };
setStyleAttribute(element, style);
}
private getPanesDimensions(): void {
for (let index: number = 0; index < this.allPanes.length; index++) {
if (this.orientation === 'Horizontal') {
this.panesDimensions.push(this.allPanes[index as number].getBoundingClientRect().width);
} else {
this.panesDimensions.push(this.allPanes[index as number].getBoundingClientRect().height);
}
}
}
private setCssClass(element: HTMLElement, className: string): void {
if (className) {
addClass([element], className.split(className.indexOf(',') > -1 ? ',' : ' '));
}
}
private hideResizer(target: HTMLElement): void {
addClass([select('.' + RESIZE_BAR, target)], HIDE_HANDLER);
}
private showResizer(target: HTMLElement): void {
if (!isNullOrUndefined(this.previousPane) && this.previousPane.classList.contains(RESIZABLE_PANE) &&
!isNullOrUndefined(this.nextPane) && this.nextPane.classList.contains(RESIZABLE_PANE)) {
removeClass([select('.' + RESIZE_BAR, target)], HIDE_HANDLER);
}
}
private resizableModel(index: number, newVal: boolean): void {
const paneIndex: number = (index === (this.allBars.length)) ? (index - 1) : index;
const i: number = index;
EventHandler.remove(this.allBars[paneIndex as number], 'mousedown', this.onMouseDown);
if (newVal) {
EventHandler.add(this.allBars[paneIndex as number], 'mousedown', this.onMouseDown, this);
if (this.isResizable()) {
this.showResizer(this.allBars[paneIndex as number]);
removeClass([select('.' + RESIZE_BAR, this.allBars[paneIndex as number])], HIDE_HANDLER);
this.allBars[paneIndex as number].classList.add(RESIZABLE_BAR);
if (index === (this.allBars.length)) {
this.allPanes[index as number].classList.add(RESIZABLE_PANE);
} else {
this.allPanes[paneIndex as number].classList.add(RESIZABLE_PANE);
}
this.updateResizablePanes(i);
}
} else {
this.updateResizablePanes(i);
this.hideResizer(this.allBars[paneIndex as number]);
this.allBars[paneIndex as number].classList.remove(RESIZABLE_BAR);
if (index === (this.allBars.length)) {
this.allPanes[index as number].classList.remove(RESIZABLE_PANE);
} else {
this.allPanes[paneIndex as number].classList.remove(RESIZABLE_PANE);
}
}
}
private collapsibleModelUpdate(index: number): void {
const paneIndex: number = index === (this.allBars.length) ? (index - 1) : index;
const arrow2: HTMLElement = (this.orientation === 'Horizontal')
? this.checkArrow(paneIndex, ARROW_LEFT) : this.checkArrow(paneIndex, ARROW_UP);
const arrow1: HTMLElement = (this.orientation === 'Horizontal')
? this.checkArrow(paneIndex, ARROW_RIGHT) : this.checkArrow(paneIndex, ARROW_DOWN);
this.paneCollapsible(this.allPanes[index as number], index);
this.updateCollapseIcons(paneIndex, arrow1, arrow2);
}
private collapseArrow(barIndex: number, arrow: string): HTMLElement {
return selectAll('.' + arrow, this.allBars[barIndex as number])[0];
}
private updateIsCollapsed(index: number, collapseArrow: string, lastBarArrow: string): void {
if (!isNullOrUndefined(index)) {
let targetEle: HTMLElement;
const lastBarIndex: boolean = (index === this.allBars.length);
const barIndex: number = lastBarIndex ? index - 1 : index;
if (!lastBarIndex && this.allPanes[index + 1].classList.contains(COLLAPSE_PANE) && index !== 0) {
targetEle = this.collapseArrow(barIndex - 1, lastBarArrow);
} else {
targetEle = (lastBarIndex) ? this.collapseArrow(barIndex, lastBarArrow) : this.collapseArrow(barIndex, collapseArrow);
}
targetEle.click();
}
}
private isCollapsed(index?: number): void {
if (!isNullOrUndefined(index) && this.paneSettings[index as number].collapsed
&& isNullOrUndefined(this.allPanes[index as number].classList.contains(COLLAPSE_PANE))) {
return;
}
this.expandFlag = false;
if (!isNullOrUndefined(index)) {
this.collapseFlag = true;
let targetEle: HTMLElement;
const lastBarIndex: boolean = (index === this.allBars.length);
const barIndex: number = lastBarIndex ? index - 1 : index;
if (!lastBarIndex && this.allPanes[index + 1].classList.contains(COLLAPSE_PANE) && index !== 0) {
targetEle = this.collapseArrow(barIndex - 1, this.targetArrows().lastBarArrow);
} else {
targetEle = (lastBarIndex) ? this.collapseArrow(barIndex, this.targetArrows().lastBarArrow) :
this.collapseArrow(barIndex, this.targetArrows().collapseArrow);
}
const event: Object = { target: targetEle };
const eventArgs: BeforeExpandEventArgs = this.beforeAction(event as Event);
this.trigger('beforeCollapse', eventArgs, (beforeCollapseArgs: BeforeExpandEventArgs) => {
if (!beforeCollapseArgs.cancel) {
let collapsedindex: number[] = [];
collapsedindex[0] = index;
let j: number = 1;
for (let i: number = 0; i < this.allPanes.length; i++) {
if (this.allPanes[i as number].classList.contains(COLLAPSE_PANE)) {
collapsedindex[j as number] = i;
j++;
}
}
collapsedindex = collapsedindex.sort();
this.updateIsCollapsed(index, this.targetArrows().collapseArrow, this.targetArrows().lastBarArrow);
for (let i: number = 0; i < collapsedindex.length; i++) {
if (!this.allPanes[collapsedindex[i as number]].classList.contains(COLLAPSE_PANE)) {
this.updateIsCollapsed(collapsedindex[i as number],
this.targetArrows().collapseArrow, this.targetArrows().lastBarArrow);
}
}
for (let i: number = collapsedindex.length; i > 0; i--) {
if (!this.allPanes[collapsedindex[i - 1]].classList.contains(COLLAPSE_PANE)) {
const targetArrow: { [key: string]: string } = this.targetArrows();
this.updateIsCollapsed(collapsedindex[i - 1], targetArrow.collapseArrow, targetArrow.lastBarArrow);
}
}
const collapseEventArgs: ExpandedEventArgs = this.afterAction(event as Event);
this.trigger('collapsed', collapseEventArgs);
this.collapseFlag = false;
}
});
} else {