@evergis/uilib-gl 1.0.136 → 1.0.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="lodash" />
|
|
2
2
|
import { Component } from "react";
|
|
3
|
-
import { Flip, PopoverPosition, PopoverProps, PopoverPropsDefault } from "./types";
|
|
3
|
+
import { ContentObserver, Flip, PopoverPosition, PopoverProps, PopoverPropsDefault } from "./types";
|
|
4
4
|
export declare class Popover extends Component<PopoverProps> {
|
|
5
5
|
static defaultProps: PopoverPropsDefault;
|
|
6
6
|
anchorNode: HTMLElement | null;
|
|
@@ -8,9 +8,29 @@ export declare class Popover extends Component<PopoverProps> {
|
|
|
8
8
|
popoverAnchorNode: HTMLElement | null;
|
|
9
9
|
flip: Flip | null;
|
|
10
10
|
childrenClicked: boolean;
|
|
11
|
+
contentObservers: ContentObserver[];
|
|
12
|
+
heightSyncFrame: number | null;
|
|
11
13
|
saveAnchor: (ref: HTMLElement | null) => null;
|
|
12
14
|
savePopoverAnchor: (ref: HTMLElement | null) => void;
|
|
13
15
|
saveContainer: (ref: HTMLElement | null) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Содержимое меняет размер и после открытия: форма раскрывает новые поля, список догружается.
|
|
18
|
+
* Обрезку по окну пересчитываем вслед за ним, а не только на открытии и ресайзе окна — иначе
|
|
19
|
+
* выросшее содержимое уезжало бы за край экрана.
|
|
20
|
+
*
|
|
21
|
+
* Размер контейнера ловит ResizeObserver, но под обрезкой он замирает: высота ребёнка задана
|
|
22
|
+
* явно, и рост или сжатие содержимого контейнер уже не меняют. Эти случаи ловит MutationObserver —
|
|
23
|
+
* по вставке и удалению узлов, то есть ровно когда форма меняет свой состав.
|
|
24
|
+
*/
|
|
25
|
+
observeContent: (node: HTMLElement | null) => void;
|
|
26
|
+
disconnectContent: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Пересчёт — в следующем кадре, а не прямо в колбэке: правка высоты внутри колбэка
|
|
29
|
+
* ResizeObserver зациклила бы его («ResizeObserver loop completed…»), а пачка мутаций одного
|
|
30
|
+
* рендера схлопывается в один пересчёт.
|
|
31
|
+
*/
|
|
32
|
+
scheduleHeightSync: () => void;
|
|
33
|
+
syncContainerHeight: () => void;
|
|
14
34
|
getContainerPosition: (anchorNode: HTMLElement, containerNode: HTMLElement) => PopoverPosition;
|
|
15
35
|
setPositions: import("lodash").DebouncedFunc<() => void>;
|
|
16
36
|
closePopover: (e: Event | MouseEvent | TouchEvent) => void;
|
|
@@ -40,3 +40,14 @@ export declare type AnchorProps = {
|
|
|
40
40
|
withTime?: boolean;
|
|
41
41
|
width?: string | number;
|
|
42
42
|
};
|
|
43
|
+
/** Наблюдатель за содержимым поповера — ResizeObserver либо MutationObserver. */
|
|
44
|
+
export interface ContentObserver {
|
|
45
|
+
disconnect: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Конструктор ResizeObserver структурно: сборка (tsdx) идёт своим TypeScript 3.9, в DOM-типах
|
|
49
|
+
* которого ResizeObserver ещё нет, и прямое имя класса валит сборку.
|
|
50
|
+
*/
|
|
51
|
+
export declare type ResizeObserverConstructor = new (callback: () => void) => ContentObserver & {
|
|
52
|
+
observe: (target: Element) => void;
|
|
53
|
+
};
|
|
@@ -7,5 +7,13 @@ export declare const checkFit: (anchorNode: HTMLElement, containerNode: HTMLElem
|
|
|
7
7
|
y: boolean;
|
|
8
8
|
};
|
|
9
9
|
export declare const syncPosition: (anchor: HTMLElement, node: HTMLElement) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Урезает высоту содержимого до места в окне по направлению раскрытия.
|
|
12
|
+
*
|
|
13
|
+
* Прошлая обрезка перед замером снимается: иначе мерилась бы уже урезанная высота, и содержимое,
|
|
14
|
+
* которое с тех пор выросло или сжалось, так и жило бы с устаревшей — выросшее уезжало бы за край
|
|
15
|
+
* окна, сжавшееся оставляло бы пустоту. Своей прокрутки обрезка не даёт: её включает содержимое.
|
|
16
|
+
*/
|
|
17
|
+
export declare const setPopupContainerHeight: (anchorNode: HTMLElement, node: HTMLElement, targetY: number) => void;
|
|
10
18
|
export declare const getOriginPosition: (flip: Flip | null, origin: Origin) => Position;
|
|
11
19
|
export declare const setPopupContainerPosition: (anchorNode: HTMLElement, node: HTMLElement, { anchorOriginPosition: [anchorX, anchorY], targetOriginPosition: [targetX, targetY], paddingSide }: PopoverPosition, animateAppearance: boolean, animateX: boolean, animateY: boolean) => void;
|
package/dist/uilib-gl.esm.js
CHANGED
|
@@ -17667,11 +17667,43 @@ const syncPosition = (anchor, node) => {
|
|
|
17667
17667
|
node.style.top = `${top}px`;
|
|
17668
17668
|
node.style.left = `${newLeft}px`;
|
|
17669
17669
|
};
|
|
17670
|
+
/**
|
|
17671
|
+
* Метки обрезки в data-атрибутах самого ребёнка: что высоту поставил Popover, и какой она была до
|
|
17672
|
+
* него. Обрезку нужно уметь снять, не затерев собственную инлайн-высоту содержимого.
|
|
17673
|
+
*/
|
|
17674
|
+
|
|
17675
|
+
const CLAMPED_HEIGHT_KEY = "popoverClampedHeight";
|
|
17676
|
+
const OWN_HEIGHT_KEY = "popoverOwnHeight";
|
|
17677
|
+
|
|
17678
|
+
const releaseClampedHeight = childNode => {
|
|
17679
|
+
if (childNode.dataset[CLAMPED_HEIGHT_KEY] === undefined) return;
|
|
17680
|
+
childNode.style.height = childNode.dataset[OWN_HEIGHT_KEY] ?? "";
|
|
17681
|
+
delete childNode.dataset[CLAMPED_HEIGHT_KEY];
|
|
17682
|
+
delete childNode.dataset[OWN_HEIGHT_KEY];
|
|
17683
|
+
};
|
|
17684
|
+
|
|
17685
|
+
const clampHeight = (childNode, height) => {
|
|
17686
|
+
if (childNode.dataset[CLAMPED_HEIGHT_KEY] === undefined) {
|
|
17687
|
+
childNode.dataset[OWN_HEIGHT_KEY] = childNode.style.height;
|
|
17688
|
+
childNode.dataset[CLAMPED_HEIGHT_KEY] = "";
|
|
17689
|
+
}
|
|
17690
|
+
|
|
17691
|
+
childNode.style.height = `${height}px`;
|
|
17692
|
+
};
|
|
17693
|
+
/**
|
|
17694
|
+
* Урезает высоту содержимого до места в окне по направлению раскрытия.
|
|
17695
|
+
*
|
|
17696
|
+
* Прошлая обрезка перед замером снимается: иначе мерилась бы уже урезанная высота, и содержимое,
|
|
17697
|
+
* которое с тех пор выросло или сжалось, так и жило бы с устаревшей — выросшее уезжало бы за край
|
|
17698
|
+
* окна, сжавшееся оставляло бы пустоту. Своей прокрутки обрезка не даёт: её включает содержимое.
|
|
17699
|
+
*/
|
|
17700
|
+
|
|
17670
17701
|
|
|
17671
17702
|
const setPopupContainerHeight = (anchorNode, node, targetY) => {
|
|
17672
|
-
if (!node.firstChild) return;
|
|
17703
|
+
if (!(node.firstChild instanceof HTMLElement)) return;
|
|
17673
17704
|
const clientHeight = window.innerHeight;
|
|
17674
17705
|
const childNode = node.firstChild;
|
|
17706
|
+
releaseClampedHeight(childNode);
|
|
17675
17707
|
const childNodeHeight = childNode.offsetHeight;
|
|
17676
17708
|
const anchorBounds = anchorNode.getBoundingClientRect();
|
|
17677
17709
|
const anchorPosY = anchorBounds.top;
|
|
@@ -17680,16 +17712,15 @@ const setPopupContainerHeight = (anchorNode, node, targetY) => {
|
|
|
17680
17712
|
const downDirection = 0;
|
|
17681
17713
|
|
|
17682
17714
|
if (targetY === upDirection && childNodeHeight > anchorPosY) {
|
|
17683
|
-
childNode
|
|
17715
|
+
clampHeight(childNode, anchorPosY);
|
|
17684
17716
|
}
|
|
17685
17717
|
|
|
17686
17718
|
if (targetY === downDirection && childNodeHeight + anchorPosY > clientHeight) {
|
|
17687
17719
|
const heightWithTopIndent = childNodeHeight + anchorPosY;
|
|
17688
17720
|
const delta = heightWithTopIndent - clientHeight;
|
|
17689
|
-
childNode
|
|
17721
|
+
clampHeight(childNode, childNodeHeight - delta - anchorIndent);
|
|
17690
17722
|
}
|
|
17691
17723
|
};
|
|
17692
|
-
|
|
17693
17724
|
const getOriginPosition = (flip, origin) => flip ? flipPosition(flip, positions[origin]) : positions[origin];
|
|
17694
17725
|
const setPopupContainerPosition = (anchorNode, node, _ref6, animateAppearance, animateX, animateY) => {
|
|
17695
17726
|
let {
|
|
@@ -17726,6 +17757,8 @@ class Popover extends Component {
|
|
|
17726
17757
|
this.flip = null; // Click inside react tree on case when children uses Portal
|
|
17727
17758
|
|
|
17728
17759
|
this.childrenClicked = false;
|
|
17760
|
+
this.contentObservers = [];
|
|
17761
|
+
this.heightSyncFrame = null;
|
|
17729
17762
|
|
|
17730
17763
|
this.saveAnchor = ref => {
|
|
17731
17764
|
this.anchorNode = ref;
|
|
@@ -17738,6 +17771,73 @@ class Popover extends Component {
|
|
|
17738
17771
|
|
|
17739
17772
|
this.saveContainer = ref => {
|
|
17740
17773
|
this.containerNode = ref;
|
|
17774
|
+
this.observeContent(ref);
|
|
17775
|
+
};
|
|
17776
|
+
/**
|
|
17777
|
+
* Содержимое меняет размер и после открытия: форма раскрывает новые поля, список догружается.
|
|
17778
|
+
* Обрезку по окну пересчитываем вслед за ним, а не только на открытии и ресайзе окна — иначе
|
|
17779
|
+
* выросшее содержимое уезжало бы за край экрана.
|
|
17780
|
+
*
|
|
17781
|
+
* Размер контейнера ловит ResizeObserver, но под обрезкой он замирает: высота ребёнка задана
|
|
17782
|
+
* явно, и рост или сжатие содержимого контейнер уже не меняют. Эти случаи ловит MutationObserver —
|
|
17783
|
+
* по вставке и удалению узлов, то есть ровно когда форма меняет свой состав.
|
|
17784
|
+
*/
|
|
17785
|
+
|
|
17786
|
+
|
|
17787
|
+
this.observeContent = node => {
|
|
17788
|
+
this.disconnectContent();
|
|
17789
|
+
if (!node) return;
|
|
17790
|
+
const {
|
|
17791
|
+
ResizeObserver: ResizeObserverClass
|
|
17792
|
+
} = window;
|
|
17793
|
+
|
|
17794
|
+
if (ResizeObserverClass) {
|
|
17795
|
+
const resizeObserver = new ResizeObserverClass(this.scheduleHeightSync);
|
|
17796
|
+
resizeObserver.observe(node);
|
|
17797
|
+
this.contentObservers.push(resizeObserver);
|
|
17798
|
+
}
|
|
17799
|
+
|
|
17800
|
+
if (typeof MutationObserver !== "undefined") {
|
|
17801
|
+
const mutationObserver = new MutationObserver(this.scheduleHeightSync);
|
|
17802
|
+
mutationObserver.observe(node, {
|
|
17803
|
+
childList: true,
|
|
17804
|
+
subtree: true
|
|
17805
|
+
});
|
|
17806
|
+
this.contentObservers.push(mutationObserver);
|
|
17807
|
+
}
|
|
17808
|
+
};
|
|
17809
|
+
|
|
17810
|
+
this.disconnectContent = () => {
|
|
17811
|
+
this.contentObservers.forEach(observer => observer.disconnect());
|
|
17812
|
+
this.contentObservers = [];
|
|
17813
|
+
|
|
17814
|
+
if (this.heightSyncFrame !== null) {
|
|
17815
|
+
cancelAnimationFrame(this.heightSyncFrame);
|
|
17816
|
+
this.heightSyncFrame = null;
|
|
17817
|
+
}
|
|
17818
|
+
};
|
|
17819
|
+
/**
|
|
17820
|
+
* Пересчёт — в следующем кадре, а не прямо в колбэке: правка высоты внутри колбэка
|
|
17821
|
+
* ResizeObserver зациклила бы его («ResizeObserver loop completed…»), а пачка мутаций одного
|
|
17822
|
+
* рендера схлопывается в один пересчёт.
|
|
17823
|
+
*/
|
|
17824
|
+
|
|
17825
|
+
|
|
17826
|
+
this.scheduleHeightSync = () => {
|
|
17827
|
+
if (this.heightSyncFrame !== null) return;
|
|
17828
|
+
this.heightSyncFrame = requestAnimationFrame(() => {
|
|
17829
|
+
this.heightSyncFrame = null;
|
|
17830
|
+
this.syncContainerHeight();
|
|
17831
|
+
});
|
|
17832
|
+
}; // Только высота: полный `setPositions` заново проигрывал бы анимацию появления на каждом изменении.
|
|
17833
|
+
|
|
17834
|
+
|
|
17835
|
+
this.syncContainerHeight = () => {
|
|
17836
|
+
if (!this.anchorNode || !this.containerNode) return;
|
|
17837
|
+
const {
|
|
17838
|
+
targetOriginPosition: [, targetY]
|
|
17839
|
+
} = this.getContainerPosition(this.anchorNode, this.containerNode);
|
|
17840
|
+
setPopupContainerHeight(this.anchorNode, this.containerNode, targetY);
|
|
17741
17841
|
};
|
|
17742
17842
|
|
|
17743
17843
|
this.getContainerPosition = (anchorNode, containerNode) => {
|
|
@@ -17840,6 +17940,7 @@ class Popover extends Component {
|
|
|
17840
17940
|
|
|
17841
17941
|
componentWillUnmount() {
|
|
17842
17942
|
this.removeListeners();
|
|
17943
|
+
this.disconnectContent();
|
|
17843
17944
|
}
|
|
17844
17945
|
|
|
17845
17946
|
componentDidUpdate(prevProps) {
|