ng-zorro-antd-splitter.mjs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import { Directionality } from '@angular/cdk/bidi';
  2. import { coerceCssPixelValue } from '@angular/cdk/coercion';
  3. import { normalizePassiveListenerOptions } from '@angular/cdk/platform';
  4. import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
  5. import * as i0 from '@angular/core';
  6. import { input, output, computed, ChangeDetectionStrategy, ViewEncapsulation, Component, booleanAttribute, viewChild, inject, ElementRef, contentChildren, linkedSignal, signal, NgModule } from '@angular/core';
  7. import { toSignal } from '@angular/core/rxjs-interop';
  8. import { map, Subject, merge, takeUntil } from 'rxjs';
  9. import { startWith, pairwise } from 'rxjs/operators';
  10. import { NzResizeObserver } from 'ng-zorro-antd/cdk/resize-observer';
  11. import { NzDestroyService } from 'ng-zorro-antd/core/services';
  12. import { fromEventOutsideAngular } from 'ng-zorro-antd/core/util';
  13. import { getEventWithPoint } from 'ng-zorro-antd/resizable';
  14. import * as i1 from 'ng-zorro-antd/icon';
  15. import { NzIconModule } from 'ng-zorro-antd/icon';
  16. /**
  17. * Use of this source code is governed by an MIT-style license that can be
  18. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  19. */
  20. class NzSplitterBarComponent {
  21. ariaNow = input.required();
  22. ariaMin = input.required();
  23. ariaMax = input.required();
  24. active = input(false);
  25. resizable = input(true);
  26. vertical = input();
  27. lazy = input(false);
  28. collapsible = input();
  29. constrainedOffset = input();
  30. offsetStart = output();
  31. collapse = output();
  32. previewTransform = computed(() => {
  33. const offset = coerceCssPixelValue(this.constrainedOffset());
  34. return this.vertical() ? `translateY(${offset})` : `translateX(${offset})`;
  35. });
  36. resizeStartEvent(event) {
  37. if (this.resizable()) {
  38. const { pageX, pageY } = getEventWithPoint(event);
  39. this.offsetStart.emit([pageX, pageY]);
  40. }
  41. }
  42. collapseEvent(type) {
  43. this.collapse.emit(type);
  44. }
  45. getValidNumber(num) {
  46. return typeof num === 'number' && !isNaN(num) ? Math.round(num) : 0;
  47. }
  48. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
  49. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.2", type: NzSplitterBarComponent, isStandalone: true, selector: "[nz-splitter-bar]", inputs: { ariaNow: { classPropertyName: "ariaNow", publicName: "ariaNow", isSignal: true, isRequired: true, transformFunction: null }, ariaMin: { classPropertyName: "ariaMin", publicName: "ariaMin", isSignal: true, isRequired: true, transformFunction: null }, ariaMax: { classPropertyName: "ariaMax", publicName: "ariaMax", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, vertical: { classPropertyName: "vertical", publicName: "vertical", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, constrainedOffset: { classPropertyName: "constrainedOffset", publicName: "constrainedOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { offsetStart: "offsetStart", collapse: "collapse" }, host: { attributes: { "role": "separator" }, properties: { "attr.aria-valuenow": "getValidNumber(ariaNow())", "attr.aria-valuemin": "getValidNumber(ariaMin())", "attr.aria-valuemax": "getValidNumber(ariaMax())" }, classAttribute: "ant-splitter-bar" }, ngImport: i0, template: `
  50. @if (lazy()) {
  51. @let preview = active() && !!this.constrainedOffset();
  52. <div
  53. class="ant-splitter-bar-preview"
  54. [class.ant-splitter-bar-preview-active]="preview"
  55. [style.transform]="preview ? previewTransform() : null"
  56. ></div>
  57. }
  58. <div
  59. class="ant-splitter-bar-dragger"
  60. [class.ant-splitter-bar-dragger-disabled]="!resizable()"
  61. [class.ant-splitter-bar-dragger-active]="active()"
  62. (mousedown)="resizeStartEvent($event)"
  63. (touchstart)="resizeStartEvent($event)"
  64. ></div>
  65. @if (collapsible()?.start) {
  66. <div class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start" (click)="collapseEvent('start')">
  67. <nz-icon
  68. [nzType]="vertical() ? 'up' : 'left'"
  69. class="ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
  70. />
  71. </div>
  72. }
  73. @if (collapsible()?.end) {
  74. <div class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end" (click)="collapseEvent('end')">
  75. <nz-icon
  76. [nzType]="vertical() ? 'down' : 'right'"
  77. class="ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
  78. />
  79. </div>
  80. }
  81. `, isInline: true, dependencies: [{ kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i1.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
  82. }
  83. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterBarComponent, decorators: [{
  84. type: Component,
  85. args: [{
  86. selector: '[nz-splitter-bar]',
  87. imports: [NzIconModule],
  88. template: `
  89. @if (lazy()) {
  90. @let preview = active() && !!this.constrainedOffset();
  91. <div
  92. class="ant-splitter-bar-preview"
  93. [class.ant-splitter-bar-preview-active]="preview"
  94. [style.transform]="preview ? previewTransform() : null"
  95. ></div>
  96. }
  97. <div
  98. class="ant-splitter-bar-dragger"
  99. [class.ant-splitter-bar-dragger-disabled]="!resizable()"
  100. [class.ant-splitter-bar-dragger-active]="active()"
  101. (mousedown)="resizeStartEvent($event)"
  102. (touchstart)="resizeStartEvent($event)"
  103. ></div>
  104. @if (collapsible()?.start) {
  105. <div class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start" (click)="collapseEvent('start')">
  106. <nz-icon
  107. [nzType]="vertical() ? 'up' : 'left'"
  108. class="ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
  109. />
  110. </div>
  111. }
  112. @if (collapsible()?.end) {
  113. <div class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end" (click)="collapseEvent('end')">
  114. <nz-icon
  115. [nzType]="vertical() ? 'down' : 'right'"
  116. class="ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
  117. />
  118. </div>
  119. }
  120. `,
  121. host: {
  122. role: 'separator',
  123. class: 'ant-splitter-bar',
  124. '[attr.aria-valuenow]': 'getValidNumber(ariaNow())',
  125. '[attr.aria-valuemin]': 'getValidNumber(ariaMin())',
  126. '[attr.aria-valuemax]': 'getValidNumber(ariaMax())'
  127. },
  128. encapsulation: ViewEncapsulation.None,
  129. changeDetection: ChangeDetectionStrategy.OnPush
  130. }]
  131. }] });
  132. /**
  133. * Use of this source code is governed by an MIT-style license that can be
  134. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  135. */
  136. class NzSplitterPanelComponent {
  137. nzDefaultSize = input();
  138. nzMin = input();
  139. nzMax = input();
  140. nzSize = input();
  141. nzCollapsible = input(false);
  142. nzResizable = input(true, { transform: booleanAttribute });
  143. contentTemplate = viewChild.required('contentTemplate');
  144. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
  145. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.2", type: NzSplitterPanelComponent, isStandalone: true, selector: "nz-splitter-panel", inputs: { nzDefaultSize: { classPropertyName: "nzDefaultSize", publicName: "nzDefaultSize", isSignal: true, isRequired: false, transformFunction: null }, nzMin: { classPropertyName: "nzMin", publicName: "nzMin", isSignal: true, isRequired: false, transformFunction: null }, nzMax: { classPropertyName: "nzMax", publicName: "nzMax", isSignal: true, isRequired: false, transformFunction: null }, nzSize: { classPropertyName: "nzSize", publicName: "nzSize", isSignal: true, isRequired: false, transformFunction: null }, nzCollapsible: { classPropertyName: "nzCollapsible", publicName: "nzCollapsible", isSignal: true, isRequired: false, transformFunction: null }, nzResizable: { classPropertyName: "nzResizable", publicName: "nzResizable", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "contentTemplate", first: true, predicate: ["contentTemplate"], descendants: true, isSignal: true }], exportAs: ["nzSplitterPanel"], ngImport: i0, template: `
  146. <ng-template #contentTemplate>
  147. <ng-content></ng-content>
  148. </ng-template>
  149. `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
  150. }
  151. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterPanelComponent, decorators: [{
  152. type: Component,
  153. args: [{
  154. selector: 'nz-splitter-panel',
  155. exportAs: 'nzSplitterPanel',
  156. template: `
  157. <ng-template #contentTemplate>
  158. <ng-content></ng-content>
  159. </ng-template>
  160. `,
  161. encapsulation: ViewEncapsulation.None,
  162. changeDetection: ChangeDetectionStrategy.OnPush
  163. }]
  164. }] });
  165. /**
  166. * Use of this source code is governed by an MIT-style license that can be
  167. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  168. */
  169. /**
  170. * Get the percentage value of the string. (e.g. '50%' => 0.5)
  171. * @param str
  172. */
  173. function getPercentValue(str) {
  174. return Number(str.slice(0, -1)) / 100;
  175. }
  176. /**
  177. * Check if the size is percentage.
  178. * @param size
  179. */
  180. function isPercent(size) {
  181. return typeof size === 'string' && size.endsWith('%');
  182. }
  183. /**
  184. * Coerce the panel collapsible option to the NzSplitterCollapseOption type.
  185. */
  186. function coerceCollapsible(collapsible) {
  187. if (collapsible && typeof collapsible === 'object') {
  188. return collapsible;
  189. }
  190. const mergedCollapsible = !!collapsible;
  191. return {
  192. start: mergedCollapsible,
  193. end: mergedCollapsible
  194. };
  195. }
  196. /**
  197. * Use of this source code is governed by an MIT-style license that can be
  198. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  199. */
  200. const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
  201. class NzSplitterComponent {
  202. /** ------------------- Props ------------------- */
  203. nzLayout = input('horizontal');
  204. nzLazy = input(false, { transform: booleanAttribute });
  205. nzResizeStart = output();
  206. nzResize = output();
  207. nzResizeEnd = output();
  208. destroy$ = inject(NzDestroyService);
  209. elementRef = inject(ElementRef);
  210. directionality = inject(Directionality);
  211. resizeObserver = inject(NzResizeObserver);
  212. document = inject(DOCUMENT);
  213. dir = toSignal(this.directionality.change, { initialValue: this.directionality.value });
  214. /** ------------------- Panels ------------------- */
  215. // Get all panels from content children
  216. panels = contentChildren(NzSplitterPanelComponent);
  217. // Subscribe to the change of properties
  218. panelProps = computed(() => this.panels().map(panel => ({
  219. defaultSize: panel.nzDefaultSize(),
  220. size: panel.nzSize(),
  221. min: panel.nzMin(),
  222. max: panel.nzMax(),
  223. resizable: panel.nzResizable(),
  224. collapsible: coerceCollapsible(panel.nzCollapsible()),
  225. contentTemplate: panel.contentTemplate()
  226. })));
  227. /** ------------------- Sizes ------------------- */
  228. /**
  229. * Observe the size of the container.
  230. */
  231. containerBox = toSignal(this.resizeObserver.observe(this.elementRef).pipe(map(([item]) => item.target), map(el => ({ width: el.clientWidth, height: el.clientHeight }))), {
  232. initialValue: {
  233. width: this.elementRef.nativeElement.clientWidth || 0,
  234. height: this.elementRef.nativeElement.clientHeight || 0
  235. }
  236. });
  237. /**
  238. * The size of the container, used to calculate the percentage size and flex basis of each panel.
  239. */
  240. containerSize = computed(() => this.nzLayout() === 'horizontal' ? this.containerBox().width : this.containerBox().height);
  241. /**
  242. * Derived from defaultSize of each panel.
  243. * After that it will be updated by the resize event with **real** size in pixels.
  244. */
  245. innerSizes = linkedSignal({
  246. source: this.panelProps,
  247. computation: source => source.map(panel => panel.defaultSize)
  248. });
  249. /**
  250. * Calculate the size of each panel based on the container size and the percentage size.
  251. */
  252. sizes = computed(() => {
  253. let emptyCount = 0;
  254. const containerSize = this.containerSize();
  255. const innerSizes = this.innerSizes();
  256. /**
  257. * Get the calculated size, min and max percentage of each panel
  258. */
  259. const sizes = this.panelProps().map((panel, index) => {
  260. const size = panel.size ?? innerSizes[index];
  261. // Calculate the percentage size of each panel.
  262. let percentage;
  263. if (isPercent(size)) {
  264. percentage = getPercentValue(size);
  265. }
  266. else if (size || size === 0) {
  267. const num = Number(size);
  268. if (!isNaN(num)) {
  269. percentage = num / containerSize;
  270. }
  271. }
  272. else {
  273. percentage = undefined;
  274. emptyCount++;
  275. }
  276. // Calculate the min and max percentage size of each panel.
  277. const minSize = panel.min;
  278. const maxSize = panel.max;
  279. const postPercentMinSize = isPercent(minSize) ? getPercentValue(minSize) : (minSize || 0) / containerSize;
  280. const postPercentMaxSize = isPercent(maxSize)
  281. ? getPercentValue(maxSize)
  282. : (maxSize || containerSize) / containerSize;
  283. return {
  284. size,
  285. percentage,
  286. min: minSize,
  287. max: maxSize,
  288. postPercentMinSize,
  289. postPercentMaxSize
  290. };
  291. });
  292. /**
  293. * Normalize the percentage size of each panel if the total percentage is larger than 1 or smaller than 1.
  294. */
  295. const totalPercentage = sizes.reduce((acc, { percentage }) => acc + (percentage ?? 0), 0);
  296. for (const size of sizes) {
  297. if (totalPercentage > 1 && !emptyCount) {
  298. // If total percentage is larger than 1, we will scale it down.
  299. const scale = 1 / totalPercentage;
  300. size.percentage = size.percentage === undefined ? 0 : size.percentage * scale;
  301. }
  302. else {
  303. // If total percentage is smaller than 1, we will fill the rest.
  304. const averagePercentage = (1 - totalPercentage) / emptyCount;
  305. size.percentage = size.percentage === undefined ? averagePercentage : size.percentage;
  306. }
  307. size.postPxSize = size.percentage * containerSize;
  308. size.size = containerSize ? coerceCssPixelValue(size.postPxSize) : size.size;
  309. }
  310. return sizes;
  311. });
  312. ariaInfos = computed(() => {
  313. const stackSizes = [];
  314. const ariaInfos = [];
  315. const sizes = this.sizes();
  316. let stack = 0;
  317. for (const size of sizes) {
  318. stack += size.percentage;
  319. stackSizes.push(stack);
  320. }
  321. for (let i = 0; i < sizes.length - 1; i += 1) {
  322. const ariaMinStart = (stackSizes[i - 1] || 0) + sizes[i].postPercentMinSize;
  323. const ariaMinEnd = (stackSizes[i + 1] || 100) - sizes[i + 1].postPercentMaxSize;
  324. const ariaMaxStart = (stackSizes[i - 1] || 0) + sizes[i].postPercentMaxSize;
  325. const ariaMaxEnd = (stackSizes[i + 1] || 100) - sizes[i + 1].postPercentMinSize;
  326. ariaInfos.push({
  327. ariaNow: stackSizes[i] * 100,
  328. ariaMin: Math.max(ariaMinStart, ariaMinEnd) * 100,
  329. ariaMax: Math.min(ariaMaxStart, ariaMaxEnd) * 100
  330. });
  331. }
  332. return ariaInfos;
  333. });
  334. getPxSizes() {
  335. return this.sizes().map(s => s.postPxSize);
  336. }
  337. /** ------------------ Resize ------------------ */
  338. /**
  339. * The index of the panel that is being resized.
  340. * @note Mark the moving splitter bar as activated to show the dragging effect even if the mouse is outside the
  341. * splitter container.
  342. */
  343. movingIndex = signal(null);
  344. /**
  345. * The offset of preview position (lazy mode) when dragging the splitter bar.
  346. * Constrained by the min and max size of the target panel.
  347. */
  348. constrainedOffset = signal(0);
  349. /**
  350. * The resizable information of each splitter bar.
  351. */
  352. resizableInfos = computed(() => {
  353. const items = this.panelProps();
  354. const pxSizes = this.getPxSizes();
  355. const resizeInfos = [];
  356. for (let i = 0; i < items.length - 1; i += 1) {
  357. const prevItem = items[i];
  358. const nextItem = items[i + 1];
  359. const prevSize = pxSizes[i];
  360. const nextSize = pxSizes[i + 1];
  361. const { resizable: prevResizable = true, min: prevMin, collapsible: prevCollapsible } = prevItem;
  362. const { resizable: nextResizable = true, min: nextMin, collapsible: nextCollapsible } = nextItem;
  363. const mergedResizable =
  364. // Both need to be resizable
  365. prevResizable &&
  366. nextResizable &&
  367. // Prev is not collapsed and limit min size
  368. (prevSize !== 0 || !prevMin) &&
  369. // Next is not collapsed and limit min size
  370. (nextSize !== 0 || !nextMin);
  371. const startCollapsible =
  372. // Self is collapsible
  373. (prevCollapsible.end && prevSize > 0) ||
  374. // Collapsed and can be collapsed
  375. (nextCollapsible.start && nextSize === 0 && prevSize > 0);
  376. const endCollapsible =
  377. // Self is collapsible
  378. (nextCollapsible.start && nextSize > 0) ||
  379. // Collapsed and can be collapsed
  380. (prevCollapsible.end && prevSize === 0 && nextSize > 0);
  381. resizeInfos[i] = {
  382. resizable: mergedResizable,
  383. collapsible: {
  384. start: !!(this.dir() === 'rtl' ? endCollapsible : startCollapsible),
  385. end: !!(this.dir() === 'rtl' ? startCollapsible : endCollapsible)
  386. }
  387. };
  388. }
  389. return resizeInfos;
  390. });
  391. /**
  392. * Handle the resize start event for the specified panel.
  393. * @param index The index of the panel.
  394. * @param startPos The start position of the resize event.
  395. */
  396. startResize(index, startPos) {
  397. this.movingIndex.set({ index, confirmed: false });
  398. this.nzResizeStart.emit(this.getPxSizes());
  399. const end$ = new Subject();
  400. // Updated constraint calculation
  401. const getConstrainedOffset = (rawOffset) => {
  402. const { percentage, postPercentMinSize, postPercentMaxSize } = this.sizes()[index];
  403. const [ariaNow, ariaMin, ariaMax] = [percentage, postPercentMinSize, postPercentMaxSize].map(p => p * 100);
  404. const containerSize = this.containerSize();
  405. const currentPos = (containerSize * ariaNow) / 100;
  406. const newPos = currentPos + rawOffset;
  407. // Calculate available space
  408. const minAllowed = Math.max(0, (containerSize * ariaMin) / 100);
  409. const maxAllowed = Math.min(containerSize, (containerSize * ariaMax) / 100);
  410. // Constrain new position within bounds
  411. const clampedPos = Math.max(minAllowed, Math.min(maxAllowed, newPos));
  412. return clampedPos - currentPos;
  413. };
  414. const handleLazyMove = (offset) => {
  415. this.constrainedOffset.set(getConstrainedOffset(offset));
  416. };
  417. const handleLazyEnd = () => {
  418. this.updateOffset(index, this.constrainedOffset());
  419. this.constrainedOffset.set(0);
  420. };
  421. // resizing
  422. merge(fromEventOutsideAngular(this.document, 'mousemove', passiveEventListenerOptions), fromEventOutsideAngular(this.document, 'touchmove', passiveEventListenerOptions))
  423. .pipe(map(event => getEventWithPoint(event)), map(({ pageX, pageY }) => (this.nzLayout() === 'horizontal' ? pageX - startPos[0] : pageY - startPos[1])),
  424. // flip the offset if the direction is rtl
  425. map(offset => (this.nzLayout() === 'horizontal' && this.dir() === 'rtl' ? -offset : offset)), startWith(0), pairwise(), takeUntil(merge(end$, this.destroy$)))
  426. .subscribe(([prev, next]) => {
  427. if (this.nzLazy() && next !== 0) {
  428. handleLazyMove(next);
  429. }
  430. else {
  431. const deltaOffset = next - prev;
  432. // filter out the 0 delta offset
  433. if (deltaOffset !== 0) {
  434. this.updateOffset(index, deltaOffset);
  435. }
  436. }
  437. });
  438. // resize end
  439. merge(fromEventOutsideAngular(this.document, 'mouseup'), fromEventOutsideAngular(this.document, 'touchend'))
  440. .pipe(takeUntil(merge(end$, this.destroy$)))
  441. .subscribe(() => {
  442. if (this.nzLazy()) {
  443. handleLazyEnd();
  444. }
  445. this.movingIndex.set(null);
  446. this.nzResizeEnd.emit(this.getPxSizes());
  447. end$.next();
  448. });
  449. }
  450. /**
  451. * Update the sizes of specified panels based on the move offset.
  452. * @param index The index of the panel.
  453. * @param offset The move offset in pixels.
  454. */
  455. updateOffset(index, offset) {
  456. const containerSize = this.containerSize();
  457. const limitSizes = this.sizes().map(p => [p.min, p.max]);
  458. const pxSizes = this.sizes().map(p => p.percentage * containerSize);
  459. const getLimitSize = (size, defaultLimit) => {
  460. if (typeof size === 'string') {
  461. return getPercentValue(size) * containerSize;
  462. }
  463. return size ?? defaultLimit;
  464. };
  465. // First time trigger move index update is not sync in the state
  466. let confirmedIndex = null;
  467. const movingIndex = this.movingIndex();
  468. // we need to know what the real index is
  469. if ((!movingIndex || !movingIndex.confirmed) && offset !== 0) {
  470. // search for the real index
  471. if (offset > 0) {
  472. confirmedIndex = index;
  473. this.movingIndex.set({ index, confirmed: true });
  474. }
  475. else {
  476. for (let i = index; i >= 0; i -= 1) {
  477. if (pxSizes[i] > 0 && this.resizableInfos()[i].resizable) {
  478. confirmedIndex = i;
  479. this.movingIndex.set({ index: i, confirmed: true });
  480. break;
  481. }
  482. }
  483. }
  484. }
  485. const mergedIndex = confirmedIndex ?? index;
  486. const nextIndex = mergedIndex + 1;
  487. // Get boundary
  488. const startMinSize = getLimitSize(limitSizes[mergedIndex][0], 0);
  489. const endMinSize = getLimitSize(limitSizes[nextIndex][0], 0);
  490. const startMaxSize = getLimitSize(limitSizes[mergedIndex][1], containerSize);
  491. const endMaxSize = getLimitSize(limitSizes[nextIndex][1], containerSize);
  492. let mergedOffset = offset;
  493. // Align with the boundary
  494. if (pxSizes[mergedIndex] + mergedOffset < startMinSize) {
  495. mergedOffset = startMinSize - pxSizes[mergedIndex];
  496. }
  497. if (pxSizes[nextIndex] - mergedOffset < endMinSize) {
  498. mergedOffset = pxSizes[nextIndex] - endMinSize;
  499. }
  500. if (pxSizes[mergedIndex] + mergedOffset > startMaxSize) {
  501. mergedOffset = startMaxSize - pxSizes[mergedIndex];
  502. }
  503. if (pxSizes[nextIndex] - mergedOffset > endMaxSize) {
  504. mergedOffset = pxSizes[nextIndex] - endMaxSize;
  505. }
  506. // Do offset
  507. pxSizes[mergedIndex] += mergedOffset;
  508. pxSizes[nextIndex] -= mergedOffset;
  509. this.innerSizes.set(pxSizes);
  510. this.nzResize.emit(pxSizes);
  511. }
  512. /** ------------------ Resize ------------------ */
  513. /**
  514. * Record the original size of the collapsed panel.
  515. * Used to restore the size when the panel is expanded back.
  516. */
  517. cacheCollapsedSize = [];
  518. /**
  519. * Collapse the specified panel.
  520. * @param index The index of the panel to collapse.
  521. * @param type The type of collapse, either `start` or `end`.
  522. */
  523. collapse(index, type) {
  524. const containerSize = this.containerSize();
  525. const limitSizes = this.sizes().map(p => [p.min, p.max]);
  526. const currentSizes = this.sizes().map(p => p.percentage * containerSize);
  527. const adjustedType = this.dir() === 'rtl' ? (type === 'start' ? 'end' : 'start') : type;
  528. const currentIndex = adjustedType === 'start' ? index : index + 1;
  529. const targetIndex = adjustedType == 'start' ? index + 1 : index;
  530. const currentSize = currentSizes[currentIndex];
  531. const targetSize = currentSizes[targetIndex];
  532. const getLimitSize = (size, defaultLimit) => {
  533. if (typeof size === 'string') {
  534. return getPercentValue(size) * containerSize;
  535. }
  536. return size ?? defaultLimit;
  537. };
  538. if (currentSize !== 0 && targetSize !== 0) {
  539. // Collapse directly
  540. currentSizes[currentIndex] = 0;
  541. currentSizes[targetIndex] += currentSize;
  542. this.cacheCollapsedSize[index] = currentSize;
  543. }
  544. else {
  545. const totalSize = currentSize + targetSize;
  546. const currentSizeMin = getLimitSize(limitSizes[currentIndex][0], 0);
  547. const currentSizeMax = getLimitSize(limitSizes[currentIndex][1], containerSize);
  548. const targetSizeMin = getLimitSize(limitSizes[targetIndex][0], 0);
  549. const targetSizeMax = getLimitSize(limitSizes[targetIndex][1], containerSize);
  550. const limitStart = Math.max(currentSizeMin, totalSize - targetSizeMax);
  551. const limitEnd = Math.min(currentSizeMax, totalSize - targetSizeMin);
  552. const halfOffset = (limitEnd - limitStart) / 2;
  553. const targetCacheCollapsedSize = this.cacheCollapsedSize[index];
  554. const currentCacheCollapsedSize = totalSize - targetCacheCollapsedSize;
  555. const shouldUseCache = targetCacheCollapsedSize &&
  556. targetCacheCollapsedSize <= targetSizeMax &&
  557. targetCacheCollapsedSize >= targetSizeMin &&
  558. currentCacheCollapsedSize <= currentSizeMax &&
  559. currentCacheCollapsedSize >= currentSizeMin;
  560. if (shouldUseCache) {
  561. currentSizes[targetIndex] = targetCacheCollapsedSize;
  562. currentSizes[currentIndex] = currentCacheCollapsedSize;
  563. }
  564. else {
  565. currentSizes[currentIndex] -= halfOffset;
  566. currentSizes[targetIndex] += halfOffset;
  567. }
  568. }
  569. this.innerSizes.set(currentSizes);
  570. this.nzResize.emit(currentSizes);
  571. this.nzResizeEnd.emit(currentSizes);
  572. }
  573. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
  574. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.2", type: NzSplitterComponent, isStandalone: true, selector: "nz-splitter", inputs: { nzLayout: { classPropertyName: "nzLayout", publicName: "nzLayout", isSignal: true, isRequired: false, transformFunction: null }, nzLazy: { classPropertyName: "nzLazy", publicName: "nzLazy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nzResizeStart: "nzResizeStart", nzResize: "nzResize", nzResizeEnd: "nzResizeEnd" }, host: { properties: { "class.ant-splitter-horizontal": "nzLayout() === \"horizontal\"", "class.ant-splitter-vertical": "nzLayout() === \"vertical\"", "class.ant-splitter-rtl": "dir() === \"rtl\"" }, classAttribute: "ant-splitter" }, providers: [NzDestroyService], queries: [{ propertyName: "panels", predicate: NzSplitterPanelComponent, isSignal: true }], exportAs: ["nzSplitter"], ngImport: i0, template: `
  575. @for (panel of panelProps(); let i = $index; track i; let last = $last) {
  576. @let size = sizes()[i];
  577. @let flexBasis = !!size.size ? size.size : 'auto';
  578. @let flexGrow = !!size.size ? 0 : 1;
  579. <div
  580. class="ant-splitter-panel"
  581. [class.ant-splitter-panel-hidden]="size.postPxSize === 0"
  582. [style.flex-basis]="flexBasis"
  583. [style.flex-grow]="flexGrow"
  584. >
  585. <ng-container *ngTemplateOutlet="panel.contentTemplate"></ng-container>
  586. </div>
  587. @if (!last) {
  588. @let resizableInfo = resizableInfos()[i];
  589. @let ariaInfo = ariaInfos()[i];
  590. <div
  591. nz-splitter-bar
  592. [ariaNow]="ariaInfo.ariaNow"
  593. [ariaMin]="ariaInfo.ariaMin"
  594. [ariaMax]="ariaInfo.ariaMax"
  595. [resizable]="resizableInfo.resizable"
  596. [collapsible]="resizableInfo.collapsible"
  597. [active]="movingIndex()?.index === i"
  598. [vertical]="nzLayout() === 'vertical'"
  599. [lazy]="nzLazy()"
  600. [constrainedOffset]="constrainedOffset()"
  601. (offsetStart)="startResize(i, $event)"
  602. (collapse)="collapse(i, $event)"
  603. ></div>
  604. }
  605. }
  606. <!-- Fake mask for cursor -->
  607. @if (movingIndex() !== null) {
  608. <div
  609. aria-hidden
  610. class="ant-splitter-mask"
  611. [class.ant-splitter-mask-horizontal]="nzLayout() === 'horizontal'"
  612. [class.ant-splitter-mask-vertical]="nzLayout() === 'vertical'"
  613. ></div>
  614. }
  615. `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NzSplitterBarComponent, selector: "[nz-splitter-bar]", inputs: ["ariaNow", "ariaMin", "ariaMax", "active", "resizable", "vertical", "lazy", "collapsible", "constrainedOffset"], outputs: ["offsetStart", "collapse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
  616. }
  617. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterComponent, decorators: [{
  618. type: Component,
  619. args: [{
  620. selector: 'nz-splitter',
  621. exportAs: 'nzSplitter',
  622. template: `
  623. @for (panel of panelProps(); let i = $index; track i; let last = $last) {
  624. @let size = sizes()[i];
  625. @let flexBasis = !!size.size ? size.size : 'auto';
  626. @let flexGrow = !!size.size ? 0 : 1;
  627. <div
  628. class="ant-splitter-panel"
  629. [class.ant-splitter-panel-hidden]="size.postPxSize === 0"
  630. [style.flex-basis]="flexBasis"
  631. [style.flex-grow]="flexGrow"
  632. >
  633. <ng-container *ngTemplateOutlet="panel.contentTemplate"></ng-container>
  634. </div>
  635. @if (!last) {
  636. @let resizableInfo = resizableInfos()[i];
  637. @let ariaInfo = ariaInfos()[i];
  638. <div
  639. nz-splitter-bar
  640. [ariaNow]="ariaInfo.ariaNow"
  641. [ariaMin]="ariaInfo.ariaMin"
  642. [ariaMax]="ariaInfo.ariaMax"
  643. [resizable]="resizableInfo.resizable"
  644. [collapsible]="resizableInfo.collapsible"
  645. [active]="movingIndex()?.index === i"
  646. [vertical]="nzLayout() === 'vertical'"
  647. [lazy]="nzLazy()"
  648. [constrainedOffset]="constrainedOffset()"
  649. (offsetStart)="startResize(i, $event)"
  650. (collapse)="collapse(i, $event)"
  651. ></div>
  652. }
  653. }
  654. <!-- Fake mask for cursor -->
  655. @if (movingIndex() !== null) {
  656. <div
  657. aria-hidden
  658. class="ant-splitter-mask"
  659. [class.ant-splitter-mask-horizontal]="nzLayout() === 'horizontal'"
  660. [class.ant-splitter-mask-vertical]="nzLayout() === 'vertical'"
  661. ></div>
  662. }
  663. `,
  664. imports: [NgTemplateOutlet, NzSplitterBarComponent],
  665. providers: [NzDestroyService],
  666. host: {
  667. class: 'ant-splitter',
  668. '[class.ant-splitter-horizontal]': 'nzLayout() === "horizontal"',
  669. '[class.ant-splitter-vertical]': 'nzLayout() === "vertical"',
  670. '[class.ant-splitter-rtl]': 'dir() === "rtl"'
  671. },
  672. encapsulation: ViewEncapsulation.None,
  673. changeDetection: ChangeDetectionStrategy.OnPush
  674. }]
  675. }] });
  676. /**
  677. * Use of this source code is governed by an MIT-style license that can be
  678. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  679. */
  680. class NzSplitterModule {
  681. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  682. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterModule, imports: [NzSplitterComponent, NzSplitterPanelComponent], exports: [NzSplitterComponent, NzSplitterPanelComponent] });
  683. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterModule, imports: [NzSplitterComponent] });
  684. }
  685. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzSplitterModule, decorators: [{
  686. type: NgModule,
  687. args: [{
  688. imports: [NzSplitterComponent, NzSplitterPanelComponent],
  689. exports: [NzSplitterComponent, NzSplitterPanelComponent]
  690. }]
  691. }] });
  692. /**
  693. * Use of this source code is governed by an MIT-style license that can be
  694. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  695. */
  696. /**
  697. * Use of this source code is governed by an MIT-style license that can be
  698. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  699. */
  700. /**
  701. * Generated bundle index. Do not edit.
  702. */
  703. export { NzSplitterComponent, NzSplitterModule, NzSplitterPanelComponent };
  704. //# sourceMappingURL=ng-zorro-antd-splitter.mjs.map