@hh.ru/magritte-ui-nav-bar 1.0.1
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.
- package/NavBar-CrD8CEWb.js +118 -0
- package/NavBar-CrD8CEWb.js.map +1 -0
- package/index.css +289 -0
- package/index.d.ts +9 -0
- package/index.js +35 -0
- package/index.js.map +1 -0
- package/index.mock.d.ts +17 -0
- package/index.mock.js +47 -0
- package/index.mock.js.map +1 -0
- package/internal/KeyedSubscriptions.d.ts +41 -0
- package/internal/KeyedSubscriptions.js +79 -0
- package/internal/KeyedSubscriptions.js.map +1 -0
- package/internal/MetricsProvider.d.ts +77 -0
- package/internal/MetricsProvider.js +275 -0
- package/internal/MetricsProvider.js.map +1 -0
- package/internal/MorphStore.d.ts +10 -0
- package/internal/MorphStore.js +36 -0
- package/internal/MorphStore.js.map +1 -0
- package/internal/PaneStore.d.ts +60 -0
- package/internal/PaneStore.js +102 -0
- package/internal/PaneStore.js.map +1 -0
- package/internal/ProgressiveBlur.d.ts +7 -0
- package/internal/ProgressiveBlur.js +43 -0
- package/internal/ProgressiveBlur.js.map +1 -0
- package/internal/useAnimationRanges.d.ts +38 -0
- package/internal/useAnimationRanges.js +52 -0
- package/internal/useAnimationRanges.js.map +1 -0
- package/internal/useBindScrollToAnimationProgress.d.ts +9 -0
- package/internal/useBindScrollToAnimationProgress.js +82 -0
- package/internal/useBindScrollToAnimationProgress.js.map +1 -0
- package/internal/useDivider.d.ts +4 -0
- package/internal/useDivider.js +38 -0
- package/internal/useDivider.js.map +1 -0
- package/internal/useNavBarMetrics.d.ts +9 -0
- package/internal/useNavBarMetrics.js +34 -0
- package/internal/useNavBarMetrics.js.map +1 -0
- package/internal/useResetFocus.d.ts +3 -0
- package/internal/useResetFocus.js +31 -0
- package/internal/useResetFocus.js.map +1 -0
- package/internal/useScrollAdapter.d.ts +15 -0
- package/internal/useScrollAdapter.js +116 -0
- package/internal/useScrollAdapter.js.map +1 -0
- package/internal/useSnapScroll.d.ts +6 -0
- package/internal/useSnapScroll.js +148 -0
- package/internal/useSnapScroll.js.map +1 -0
- package/internal/useSyncMotionValue.d.ts +2 -0
- package/internal/useSyncMotionValue.js +9 -0
- package/internal/useSyncMotionValue.js.map +1 -0
- package/internal/utils.d.ts +207 -0
- package/internal/utils.js +359 -0
- package/internal/utils.js.map +1 -0
- package/package.json +38 -0
- package/public/Actions.d.ts +26 -0
- package/public/Actions.js +47 -0
- package/public/Actions.js.map +1 -0
- package/public/EnvironmentFingerprintNode.d.ts +7 -0
- package/public/EnvironmentFingerprintNode.js +70 -0
- package/public/EnvironmentFingerprintNode.js.map +1 -0
- package/public/LayoutMorph.d.ts +32 -0
- package/public/LayoutMorph.js +132 -0
- package/public/LayoutMorph.js.map +1 -0
- package/public/LayoutStage.d.ts +7 -0
- package/public/LayoutStage.js +87 -0
- package/public/LayoutStage.js.map +1 -0
- package/public/Morph.d.ts +28 -0
- package/public/Morph.js +66 -0
- package/public/Morph.js.map +1 -0
- package/public/NavBar.d.ts +57 -0
- package/public/NavBar.js +21 -0
- package/public/NavBar.js.map +1 -0
- package/public/Pane.d.ts +22 -0
- package/public/Pane.js +79 -0
- package/public/Pane.js.map +1 -0
- package/public/Stage.d.ts +10 -0
- package/public/Stage.js +43 -0
- package/public/Stage.js.map +1 -0
- package/public/TitleContainer.d.ts +24 -0
- package/public/TitleContainer.js +34 -0
- package/public/TitleContainer.js.map +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Класс для управления подписками на изменения по ключам с батч-нотификацией.
|
|
3
|
+
*
|
|
4
|
+
* Основные возможности:
|
|
5
|
+
* - Подписка на изменение конкретного ключа (`onChange`).
|
|
6
|
+
* - Подписка на уничтожение (`onDestroy`).
|
|
7
|
+
* - Пометка ключей как изменённых (`markChanged`) и отложенная рассылка уведомлений (`notify`).
|
|
8
|
+
* - Батчинг уведомлений: если в течение одного тика изменилось несколько ключей,
|
|
9
|
+
* подписчики будут вызваны один раз в ближайшей микрозадаче {@link scheduleMicro}.
|
|
10
|
+
* - Дедупликация: один и тот же коллбек, подписанный на несколько ключей, вызывается ровно один раз.
|
|
11
|
+
*
|
|
12
|
+
* Механизм планирования (по умолчанию через микрозадачу) можно переопределить,
|
|
13
|
+
* передав собственную функцию `scheduleFn` в конструктор (например, для тестов).
|
|
14
|
+
*
|
|
15
|
+
* @typeParam K Тип ключей (например, keyof SomeData).
|
|
16
|
+
*/
|
|
17
|
+
export declare class KeyedSubscriptions<K extends PropertyKey> {
|
|
18
|
+
private changeListeners;
|
|
19
|
+
private pendingKeys;
|
|
20
|
+
private readonly runScheduled;
|
|
21
|
+
constructor();
|
|
22
|
+
/**
|
|
23
|
+
* Подписаться на изменения указанного ключа.
|
|
24
|
+
*
|
|
25
|
+
* @param key Ключ, изменения которого отслеживаем.
|
|
26
|
+
* @param cb Коллбек, вызываемый при изменении этого ключа.
|
|
27
|
+
* @returns Функция для отписки.
|
|
28
|
+
*/
|
|
29
|
+
onChange(key: K | K[] | '*', cb: VoidFunction): VoidFunction;
|
|
30
|
+
/**
|
|
31
|
+
* Пометить ключ как изменённый.
|
|
32
|
+
* Уведомления будут отправлены при ближайшем вызове `notify()`.
|
|
33
|
+
*/
|
|
34
|
+
protected markChanged(key: K): void;
|
|
35
|
+
/**
|
|
36
|
+
* Запланировать рассылку уведомлений.
|
|
37
|
+
* Уведомления будут выполнены не более одного раза за цикл планировщика
|
|
38
|
+
* (по умолчанию - за один тик).
|
|
39
|
+
*/
|
|
40
|
+
protected notify(): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import './../index.css';
|
|
2
|
+
import { scheduleMicro } from './utils.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Класс для управления подписками на изменения по ключам с батч-нотификацией.
|
|
7
|
+
*
|
|
8
|
+
* Основные возможности:
|
|
9
|
+
* - Подписка на изменение конкретного ключа (`onChange`).
|
|
10
|
+
* - Подписка на уничтожение (`onDestroy`).
|
|
11
|
+
* - Пометка ключей как изменённых (`markChanged`) и отложенная рассылка уведомлений (`notify`).
|
|
12
|
+
* - Батчинг уведомлений: если в течение одного тика изменилось несколько ключей,
|
|
13
|
+
* подписчики будут вызваны один раз в ближайшей микрозадаче {@link scheduleMicro}.
|
|
14
|
+
* - Дедупликация: один и тот же коллбек, подписанный на несколько ключей, вызывается ровно один раз.
|
|
15
|
+
*
|
|
16
|
+
* Механизм планирования (по умолчанию через микрозадачу) можно переопределить,
|
|
17
|
+
* передав собственную функцию `scheduleFn` в конструктор (например, для тестов).
|
|
18
|
+
*
|
|
19
|
+
* @typeParam K Тип ключей (например, keyof SomeData).
|
|
20
|
+
*/
|
|
21
|
+
class KeyedSubscriptions {
|
|
22
|
+
changeListeners = new Map();
|
|
23
|
+
pendingKeys = new Set();
|
|
24
|
+
runScheduled;
|
|
25
|
+
constructor() {
|
|
26
|
+
const cbSet = new Set();
|
|
27
|
+
this.runScheduled = scheduleMicro(() => {
|
|
28
|
+
// Собираем все коллбеки по ключам, которые были помечены как изменённые
|
|
29
|
+
this.pendingKeys.forEach((key) => {
|
|
30
|
+
this.changeListeners.get(key)?.forEach((cb) => cbSet.add(cb));
|
|
31
|
+
});
|
|
32
|
+
this.changeListeners.get('*')?.forEach((cb) => cbSet.add(cb));
|
|
33
|
+
this.pendingKeys.clear();
|
|
34
|
+
// Вызываем каждый коллбек ровно один раз, даже если он подписан на несколько ключей из набора изменившихся
|
|
35
|
+
for (const cb of cbSet) {
|
|
36
|
+
cb();
|
|
37
|
+
}
|
|
38
|
+
cbSet.clear();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Подписаться на изменения указанного ключа.
|
|
43
|
+
*
|
|
44
|
+
* @param key Ключ, изменения которого отслеживаем.
|
|
45
|
+
* @param cb Коллбек, вызываемый при изменении этого ключа.
|
|
46
|
+
* @returns Функция для отписки.
|
|
47
|
+
*/
|
|
48
|
+
onChange(key, cb) {
|
|
49
|
+
if (Array.isArray(key)) {
|
|
50
|
+
const subscriptions = key.map((key) => this.onChange(key, cb));
|
|
51
|
+
return () => subscriptions.forEach((sub) => sub());
|
|
52
|
+
}
|
|
53
|
+
if (!this.changeListeners.has(key)) {
|
|
54
|
+
this.changeListeners.set(key, new Set());
|
|
55
|
+
}
|
|
56
|
+
this.changeListeners.get(key)?.add(cb);
|
|
57
|
+
return () => this.changeListeners.get(key)?.delete(cb);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Пометить ключ как изменённый.
|
|
61
|
+
* Уведомления будут отправлены при ближайшем вызове `notify()`.
|
|
62
|
+
*/
|
|
63
|
+
markChanged(key) {
|
|
64
|
+
this.pendingKeys.add(key);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Запланировать рассылку уведомлений.
|
|
68
|
+
* Уведомления будут выполнены не более одного раза за цикл планировщика
|
|
69
|
+
* (по умолчанию - за один тик).
|
|
70
|
+
*/
|
|
71
|
+
notify() {
|
|
72
|
+
if (this.pendingKeys.size > 0) {
|
|
73
|
+
this.runScheduled();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { KeyedSubscriptions };
|
|
79
|
+
//# sourceMappingURL=KeyedSubscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyedSubscriptions.js","sources":["../../src/internal/KeyedSubscriptions.ts"],"sourcesContent":["import { scheduleMicro } from '@hh.ru/magritte-ui-nav-bar/internal/utils';\n\n/**\n * Класс для управления подписками на изменения по ключам с батч-нотификацией.\n *\n * Основные возможности:\n * - Подписка на изменение конкретного ключа (`onChange`).\n * - Подписка на уничтожение (`onDestroy`).\n * - Пометка ключей как изменённых (`markChanged`) и отложенная рассылка уведомлений (`notify`).\n * - Батчинг уведомлений: если в течение одного тика изменилось несколько ключей,\n * подписчики будут вызваны один раз в ближайшей микрозадаче {@link scheduleMicro}.\n * - Дедупликация: один и тот же коллбек, подписанный на несколько ключей, вызывается ровно один раз.\n *\n * Механизм планирования (по умолчанию через микрозадачу) можно переопределить,\n * передав собственную функцию `scheduleFn` в конструктор (например, для тестов).\n *\n * @typeParam K Тип ключей (например, keyof SomeData).\n */\nexport class KeyedSubscriptions<K extends PropertyKey> {\n private changeListeners = new Map<K | '*', Set<VoidFunction>>();\n private pendingKeys = new Set<K>();\n private readonly runScheduled: VoidFunction;\n\n constructor() {\n const cbSet = new Set<VoidFunction>();\n this.runScheduled = scheduleMicro(() => {\n // Собираем все коллбеки по ключам, которые были помечены как изменённые\n this.pendingKeys.forEach((key) => {\n this.changeListeners.get(key)?.forEach((cb) => cbSet.add(cb));\n });\n this.changeListeners.get('*')?.forEach((cb) => cbSet.add(cb));\n this.pendingKeys.clear();\n\n // Вызываем каждый коллбек ровно один раз, даже если он подписан на несколько ключей из набора изменившихся\n for (const cb of cbSet) {\n cb();\n }\n cbSet.clear();\n });\n }\n\n /**\n * Подписаться на изменения указанного ключа.\n *\n * @param key Ключ, изменения которого отслеживаем.\n * @param cb Коллбек, вызываемый при изменении этого ключа.\n * @returns Функция для отписки.\n */\n onChange(key: K | K[] | '*', cb: VoidFunction): VoidFunction {\n if (Array.isArray(key)) {\n const subscriptions = key.map((key) => this.onChange(key, cb));\n return () => subscriptions.forEach((sub) => sub());\n }\n if (!this.changeListeners.has(key)) {\n this.changeListeners.set(key, new Set());\n }\n this.changeListeners.get(key)?.add(cb);\n return () => this.changeListeners.get(key)?.delete(cb);\n }\n\n /**\n * Пометить ключ как изменённый.\n * Уведомления будут отправлены при ближайшем вызове `notify()`.\n */\n protected markChanged(key: K): void {\n this.pendingKeys.add(key);\n }\n\n /**\n * Запланировать рассылку уведомлений.\n * Уведомления будут выполнены не более одного раза за цикл планировщика\n * (по умолчанию - за один тик).\n */\n protected notify(): void {\n if (this.pendingKeys.size > 0) {\n this.runScheduled();\n }\n }\n}\n"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;AAeG;MACU,kBAAkB,CAAA;AACnB,IAAA,eAAe,GAAG,IAAI,GAAG,EAA8B,CAAC;AACxD,IAAA,WAAW,GAAG,IAAI,GAAG,EAAK,CAAC;AAClB,IAAA,YAAY,CAAe;AAE5C,IAAA,WAAA,GAAA;AACI,QAAA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;AACtC,QAAA,IAAI,CAAC,YAAY,GAAG,aAAa,CAAC,MAAK;;YAEnC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,KAAI;gBAC7B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAClE,aAAC,CAAC,CAAC;YACH,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9D,YAAA,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;;AAGzB,YAAA,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE;AACpB,gBAAA,EAAE,EAAE,CAAC;aACR;YACD,KAAK,CAAC,KAAK,EAAE,CAAC;AAClB,SAAC,CAAC,CAAC;KACN;AAED;;;;;;AAMG;IACH,QAAQ,CAAC,GAAkB,EAAE,EAAgB,EAAA;AACzC,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACpB,MAAM,aAAa,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/D,YAAA,OAAO,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;SACtD;QACD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAChC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;SAC5C;AACD,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AACvC,QAAA,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;KAC1D;AAED;;;AAGG;AACO,IAAA,WAAW,CAAC,GAAM,EAAA;AACxB,QAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;KAC7B;AAED;;;;AAIG;IACO,MAAM,GAAA;QACZ,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE;YAC3B,IAAI,CAAC,YAAY,EAAE,CAAC;SACvB;KACJ;AACJ;;;;"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type MutableRefObject, type DependencyList, type HTMLAttributes, type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Функция-подписчик, которая получает массив измерений для заранее
|
|
4
|
+
* зарегистрированного набора элементов (порядок соответствует зарегистрированному).
|
|
5
|
+
*/
|
|
6
|
+
type MeasureFn = (elementMetrics: DOMRect) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Хук измерения: подписывает callback на изменения размеров указанных элементов
|
|
9
|
+
* и возвращает функцию ручного «принудительного» переизмерения (remeasure).
|
|
10
|
+
*
|
|
11
|
+
* @param elements Список RefObject’ов элементов, размеры которых нужно измерять.
|
|
12
|
+
* @param measureFn Коллбек, получающий массив DOMRect при измерении.
|
|
13
|
+
* @param deps Зависимости для эффекта подписки (аналогичны deps useEffect).
|
|
14
|
+
* @returns Функция remeasure(), которая помечает элементы «грязными» и запускает измерение.
|
|
15
|
+
*/
|
|
16
|
+
export interface UseMeasure {
|
|
17
|
+
(elements: RefObject<Element | null> | MutableRefObject<Element | null | undefined>, measureFn: MeasureFn, deps?: DependencyList): VoidFunction;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Реестр подписок на измерения размеров элементов.
|
|
21
|
+
* Хранит двунаправные сопоставления «элемент → подписчики» и «подписчик → элементы»,
|
|
22
|
+
* кеширует DOMRect для элементов в текущем тике и поддерживает список «грязных» элементов.
|
|
23
|
+
*
|
|
24
|
+
* Внешний код вызывает:
|
|
25
|
+
* - register/unregister для подписки/отписки,
|
|
26
|
+
* - markDirty/consumeDirty для управления очередью измерений,
|
|
27
|
+
* - subscribersFor/elementFor для получения связей.
|
|
28
|
+
*/
|
|
29
|
+
export declare class MeasureRegistry {
|
|
30
|
+
private elementsToSubs;
|
|
31
|
+
private subsToElements;
|
|
32
|
+
private dirty;
|
|
33
|
+
isDirty: boolean;
|
|
34
|
+
register(element: Element, fn: MeasureFn): boolean;
|
|
35
|
+
unregister(element: Element): void;
|
|
36
|
+
markDirty(element: Element): void;
|
|
37
|
+
markAllDirty(): void;
|
|
38
|
+
consumeDirty(): Element[];
|
|
39
|
+
elementFor(fn: MeasureFn): Element | null;
|
|
40
|
+
subscribersFor(elements: Element[]): Set<MeasureFn>;
|
|
41
|
+
listAllElements(): Element[];
|
|
42
|
+
}
|
|
43
|
+
export interface MetricsContextValue {
|
|
44
|
+
useMeasureAuto: UseMeasure;
|
|
45
|
+
useMeasureManual: UseMeasure;
|
|
46
|
+
useMeasureFingerprint: UseMeasure;
|
|
47
|
+
measureAllManual: VoidFunction;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Провайдер измерений. Оборачивает subtree и предоставляет через контекст:
|
|
51
|
+
* - два хука подписки (авто/ручной),
|
|
52
|
+
* - функцию «переизмерить всё» для ручного реестра.
|
|
53
|
+
*
|
|
54
|
+
* Для корректных измерений при необходимости временно навешивает CSS-класс
|
|
55
|
+
* (measureClassName) на корневой div — полезно для временного отключения
|
|
56
|
+
* transform/overflow и т.п., чтобы getBoundingClientRect() отдавал «чистые» размеры.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* <MetricsProvider measureClassName="no-transform">
|
|
60
|
+
* ...
|
|
61
|
+
* </MetricsProvider>
|
|
62
|
+
*/
|
|
63
|
+
export declare const MetricsProvider: import("react").ForwardRefExoticComponent<{
|
|
64
|
+
measureClassName?: string;
|
|
65
|
+
} & HTMLAttributes<HTMLDivElement> & {
|
|
66
|
+
children?: import("react").ReactNode | undefined;
|
|
67
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
68
|
+
export declare const useMetricsContext: () => MetricsContextValue | null;
|
|
69
|
+
export declare const useMeasureAuto: UseMeasure;
|
|
70
|
+
export declare const useMeasureManual: UseMeasure;
|
|
71
|
+
export declare const useMeasureFingerprint: UseMeasure;
|
|
72
|
+
/**
|
|
73
|
+
* Хук, возвращающий функцию «переизмерить всё» для подписок зарегистрированных с помощью {@link useMeasureManual}.
|
|
74
|
+
* Полезно, когда нужно одномоментно обновить измерения всех зарегистрированных вручную элементов.
|
|
75
|
+
*/
|
|
76
|
+
export declare const useRemeasureAllManual: () => VoidFunction;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import './../index.css';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, forwardRef, useRef, useCallback, useLayoutEffect, useContext } from 'react';
|
|
4
|
+
import { useActualRef, useInitOnce, scheduleMicro, scheduleGatherMetrics } from './utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Реестр подписок на измерения размеров элементов.
|
|
8
|
+
* Хранит двунаправные сопоставления «элемент → подписчики» и «подписчик → элементы»,
|
|
9
|
+
* кеширует DOMRect для элементов в текущем тике и поддерживает список «грязных» элементов.
|
|
10
|
+
*
|
|
11
|
+
* Внешний код вызывает:
|
|
12
|
+
* - register/unregister для подписки/отписки,
|
|
13
|
+
* - markDirty/consumeDirty для управления очередью измерений,
|
|
14
|
+
* - subscribersFor/elementFor для получения связей.
|
|
15
|
+
*/
|
|
16
|
+
class MeasureRegistry {
|
|
17
|
+
elementsToSubs = new Map();
|
|
18
|
+
subsToElements = new Map();
|
|
19
|
+
dirty = new Set();
|
|
20
|
+
isDirty = false;
|
|
21
|
+
register(element, fn) {
|
|
22
|
+
this.subsToElements.set(fn, element);
|
|
23
|
+
const isFirstTimeSub = !this.elementsToSubs.has(element);
|
|
24
|
+
const prevSub = this.elementsToSubs.get(element);
|
|
25
|
+
this.elementsToSubs.set(element, fn);
|
|
26
|
+
if (prevSub && prevSub !== fn) {
|
|
27
|
+
this.subsToElements.delete(prevSub);
|
|
28
|
+
}
|
|
29
|
+
return isFirstTimeSub;
|
|
30
|
+
}
|
|
31
|
+
unregister(element) {
|
|
32
|
+
const fn = this.elementsToSubs.get(element);
|
|
33
|
+
if (fn) {
|
|
34
|
+
this.subsToElements.delete(fn);
|
|
35
|
+
this.elementsToSubs.delete(element);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
markDirty(element) {
|
|
39
|
+
if (this.elementsToSubs.has(element)) {
|
|
40
|
+
this.dirty.add(element);
|
|
41
|
+
this.isDirty = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
markAllDirty() {
|
|
45
|
+
for (const element of this.elementsToSubs.keys()) {
|
|
46
|
+
this.dirty.add(element);
|
|
47
|
+
}
|
|
48
|
+
this.isDirty = true;
|
|
49
|
+
}
|
|
50
|
+
consumeDirty() {
|
|
51
|
+
const list = [...this.dirty];
|
|
52
|
+
this.dirty.clear();
|
|
53
|
+
this.isDirty = false;
|
|
54
|
+
return list;
|
|
55
|
+
}
|
|
56
|
+
elementFor(fn) {
|
|
57
|
+
return this.subsToElements.get(fn) ?? null;
|
|
58
|
+
}
|
|
59
|
+
subscribersFor(elements) {
|
|
60
|
+
const subs = new Set();
|
|
61
|
+
for (const el of elements) {
|
|
62
|
+
const fn = this.elementsToSubs.get(el);
|
|
63
|
+
if (fn) {
|
|
64
|
+
subs.add(fn);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return subs;
|
|
68
|
+
}
|
|
69
|
+
listAllElements() {
|
|
70
|
+
return [...this.elementsToSubs.keys()];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Контекст с API для измерений:
|
|
75
|
+
* - useMeasureAuto: авто-режим (измерения по ResizeObserver + ручные remeasure);
|
|
76
|
+
* - useMeasureManual: ручной режим (только по вызову remeasure);
|
|
77
|
+
* - measureAllManual: принудительное переизмерение всех элементов ручного реестра.
|
|
78
|
+
*/
|
|
79
|
+
const MetricsContext = createContext(null);
|
|
80
|
+
/**
|
|
81
|
+
* Провайдер измерений. Оборачивает subtree и предоставляет через контекст:
|
|
82
|
+
* - два хука подписки (авто/ручной),
|
|
83
|
+
* - функцию «переизмерить всё» для ручного реестра.
|
|
84
|
+
*
|
|
85
|
+
* Для корректных измерений при необходимости временно навешивает CSS-класс
|
|
86
|
+
* (measureClassName) на корневой div — полезно для временного отключения
|
|
87
|
+
* transform/overflow и т.п., чтобы getBoundingClientRect() отдавал «чистые» размеры.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* <MetricsProvider measureClassName="no-transform">
|
|
91
|
+
* ...
|
|
92
|
+
* </MetricsProvider>
|
|
93
|
+
*/
|
|
94
|
+
const MetricsProvider = forwardRef(({ children, measureClassName, ...rest }, ref) => {
|
|
95
|
+
const classNameRef = useActualRef(measureClassName);
|
|
96
|
+
const rootRef = useRef();
|
|
97
|
+
const inMeasureCycleRef = useRef(false);
|
|
98
|
+
const callbacksCallCache = useInitOnce(() => new Set());
|
|
99
|
+
// Пробрасываем внешний ref и локально храним DOM-узел.
|
|
100
|
+
const rootRefCallback = useCallback((element) => {
|
|
101
|
+
rootRef.current = element;
|
|
102
|
+
if (typeof ref === 'function') {
|
|
103
|
+
ref(element);
|
|
104
|
+
}
|
|
105
|
+
else if (ref) {
|
|
106
|
+
ref.current = element;
|
|
107
|
+
}
|
|
108
|
+
}, [ref]);
|
|
109
|
+
// Текущее состояние «включён ли класс для корректного измерения».
|
|
110
|
+
const classStateRef = useRef(false);
|
|
111
|
+
// Реестры: отдельный для авто-подписок (RO) и для ручных подписок.
|
|
112
|
+
const manualSubs = useInitOnce(() => new MeasureRegistry());
|
|
113
|
+
const autoSubs = useInitOnce(() => new MeasureRegistry());
|
|
114
|
+
const fingerprintSubs = useInitOnce(() => new MeasureRegistry());
|
|
115
|
+
/**
|
|
116
|
+
* Включает/выключает CSS-класс для контейнера (например, чтобы на время измерения отключить transform).
|
|
117
|
+
* Если включили — автоматически планируем выключение в конец тика (см. disableLater).
|
|
118
|
+
*/
|
|
119
|
+
const toggle = (on) => {
|
|
120
|
+
if (classStateRef.current === !!on || !rootRef.current || !classNameRef.current) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
rootRef.current.classList.toggle(classNameRef.current, !!on);
|
|
124
|
+
classStateRef.current = !!on;
|
|
125
|
+
// если навесили класс отключающий трансформации - планируем удаление этого класса на конец тика
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
127
|
+
!!on && disableLater();
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Планировщик «снять класс позже»: schedule(toggle) вернёт функцию,
|
|
131
|
+
* которая при вызове поставит toggle(false) в конец очереди микротасок.
|
|
132
|
+
*/
|
|
133
|
+
const disableLater = useInitOnce(() => scheduleMicro(toggle));
|
|
134
|
+
const measureDirtyRegistry = useInitOnce(() => (registry) => {
|
|
135
|
+
if (!registry.isDirty) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const dirty = registry.consumeDirty();
|
|
139
|
+
const measureFns = registry.subscribersFor(dirty);
|
|
140
|
+
measureFns.forEach((fn) => {
|
|
141
|
+
if (callbacksCallCache.has(fn)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
callbacksCallCache.add(fn);
|
|
145
|
+
const element = registry.elementFor(fn);
|
|
146
|
+
if (!element) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
fn(element.getBoundingClientRect());
|
|
150
|
+
});
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
const measureSync = useInitOnce(() => () => {
|
|
154
|
+
/**
|
|
155
|
+
* Во время вызова коллбеков может произойти либо синхронный вызов measureSync, либо асинхронный.
|
|
156
|
+
* Синхронный нужно вызвать сразу, а для асинхронного обычно создается задача в очереди микротасок.
|
|
157
|
+
* Но в случае установки флага inMeasureCycle таска не будет создана, т.к. она не нужна, потому что измерения
|
|
158
|
+
* произойдут в этом же цикле.
|
|
159
|
+
* Чтобы синхронно запущенный вызов не сбросил этот флаг раньше времени мы при вызове функции запоминаем
|
|
160
|
+
* его состояние, и при выходе из него возвращаем его к исходному значению.
|
|
161
|
+
* Получается что при рекурсивном вызове measureSync только самая нижняя в стеке вызовов функция сбросит этот
|
|
162
|
+
* флаг в false.
|
|
163
|
+
*/
|
|
164
|
+
const inCycle = inMeasureCycleRef.current;
|
|
165
|
+
inMeasureCycleRef.current = true;
|
|
166
|
+
toggle(true);
|
|
167
|
+
// фингерпринты обрабатываем только один раз за цикл, т.к. единственный вариант для этих элементов попасть в
|
|
168
|
+
// список на измерение через ResizeObserver, а он не может добавить новые элементы во время цикла измерения
|
|
169
|
+
measureDirtyRegistry(fingerprintSubs);
|
|
170
|
+
// в ручную и авто очередь элементы можно добавить руками с помощью вызова функции, а значит элементы могут
|
|
171
|
+
// добавляться в цикле измерений в коллбеках изменения размера, поэтому обрабатываем их до тех пор пока не
|
|
172
|
+
// перестанут появляться новые элементы
|
|
173
|
+
while (autoSubs.isDirty || manualSubs.isDirty) {
|
|
174
|
+
measureDirtyRegistry(autoSubs);
|
|
175
|
+
measureDirtyRegistry(manualSubs);
|
|
176
|
+
}
|
|
177
|
+
if (!inCycle) {
|
|
178
|
+
callbacksCallCache.clear();
|
|
179
|
+
}
|
|
180
|
+
inMeasureCycleRef.current = inCycle;
|
|
181
|
+
});
|
|
182
|
+
/**
|
|
183
|
+
* Если вызвано асинхронное измерение, то откладываем его до обработки микротасок, чтобы таска на измерения
|
|
184
|
+
* от ResizeObserver и вызванная вручную схлопнулись в одну.
|
|
185
|
+
* Функция не должна вызываться напрямую, только через runMeasure
|
|
186
|
+
*/
|
|
187
|
+
const _scheduledMeasure = useInitOnce(() => scheduleGatherMetrics(measureSync));
|
|
188
|
+
/**
|
|
189
|
+
* Кроме того ручной вызов может произойти в цикле измерений, в этом случае не нужно делать новую таску, достаточно
|
|
190
|
+
* просто пометить инвалидированные размеры, они будут получены (или прочитаны из кеша) в текущем цикле измерений
|
|
191
|
+
*/
|
|
192
|
+
const runMeasure = useInitOnce(() => () => {
|
|
193
|
+
if (inMeasureCycleRef.current) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
_scheduledMeasure();
|
|
197
|
+
});
|
|
198
|
+
/**
|
|
199
|
+
* Общий ResizeObserver: помечает элементы грязными и планирует измерение.
|
|
200
|
+
*/
|
|
201
|
+
const resizeObserver = useInitOnce(() => new ResizeObserver((entries) => {
|
|
202
|
+
const dirtyEntries = entries.map((entry) => entry.target);
|
|
203
|
+
for (const entry of dirtyEntries) {
|
|
204
|
+
autoSubs.markDirty(entry);
|
|
205
|
+
fingerprintSubs.markDirty(entry);
|
|
206
|
+
}
|
|
207
|
+
runMeasure();
|
|
208
|
+
}));
|
|
209
|
+
/** Отписывает подписчика от авто-реестра и снимает наблюдение, если подписчиков больше нет. */
|
|
210
|
+
const unregister = (registry, element, observe = true) => {
|
|
211
|
+
registry.unregister(element);
|
|
212
|
+
observe && resizeObserver.unobserve(element);
|
|
213
|
+
};
|
|
214
|
+
/** Регистрирует подписчика в реестре и опционально подключает наблюдение через ResizeObserver. */
|
|
215
|
+
const register = (registry, element, fn, observe = true) => {
|
|
216
|
+
const isFirstSub = registry.register(element, fn);
|
|
217
|
+
observe && isFirstSub && resizeObserver.observe(element);
|
|
218
|
+
return () => unregister(registry, element, observe);
|
|
219
|
+
};
|
|
220
|
+
const createUseMeasure = useInitOnce(() => (registry, observe) => (ref, callback, deps = []) => {
|
|
221
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
222
|
+
useLayoutEffect(() => (ref.current ? register(registry, ref.current, callback, observe) : void 0),
|
|
223
|
+
// eslint-disable-next-line disable-autofix/react-hooks/exhaustive-deps
|
|
224
|
+
deps);
|
|
225
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
226
|
+
const remeasure = useCallback(() => {
|
|
227
|
+
if (ref.current) {
|
|
228
|
+
registry.markDirty(ref.current);
|
|
229
|
+
runMeasure();
|
|
230
|
+
}
|
|
231
|
+
}, [ref]);
|
|
232
|
+
return remeasure;
|
|
233
|
+
});
|
|
234
|
+
/**
|
|
235
|
+
* Возвращает функцию, которая пометит «грязными» все элементы ручного реестра
|
|
236
|
+
* и выполнит синхронное измерение сразу (без ожидания микротасок).
|
|
237
|
+
*/
|
|
238
|
+
const measureAllManual = useInitOnce(() => () => {
|
|
239
|
+
manualSubs.markAllDirty();
|
|
240
|
+
measureSync();
|
|
241
|
+
});
|
|
242
|
+
const contextValue = useInitOnce(() => ({
|
|
243
|
+
useMeasureAuto: createUseMeasure(autoSubs, true),
|
|
244
|
+
useMeasureManual: createUseMeasure(manualSubs, false),
|
|
245
|
+
useMeasureFingerprint: createUseMeasure(fingerprintSubs, true),
|
|
246
|
+
measureAllManual,
|
|
247
|
+
}));
|
|
248
|
+
return (jsx(MetricsContext.Provider, { value: contextValue, children: jsx("div", { ...rest, ref: rootRefCallback, children: children }) }));
|
|
249
|
+
});
|
|
250
|
+
MetricsProvider.displayName = 'MetricsProvider';
|
|
251
|
+
const useMetricsContext = () => useContext(MetricsContext);
|
|
252
|
+
const createHook = (name) => (...args) => {
|
|
253
|
+
const ctx = useContext(MetricsContext);
|
|
254
|
+
if (!ctx) {
|
|
255
|
+
throw new Error('useMeasure* must be used within <MetricsProvider/>');
|
|
256
|
+
}
|
|
257
|
+
return ctx[name](...args);
|
|
258
|
+
};
|
|
259
|
+
const useMeasureAuto = createHook('useMeasureAuto');
|
|
260
|
+
const useMeasureManual = createHook('useMeasureManual');
|
|
261
|
+
const useMeasureFingerprint = createHook('useMeasureFingerprint');
|
|
262
|
+
/**
|
|
263
|
+
* Хук, возвращающий функцию «переизмерить всё» для подписок зарегистрированных с помощью {@link useMeasureManual}.
|
|
264
|
+
* Полезно, когда нужно одномоментно обновить измерения всех зарегистрированных вручную элементов.
|
|
265
|
+
*/
|
|
266
|
+
const useRemeasureAllManual = () => {
|
|
267
|
+
const ctx = useContext(MetricsContext);
|
|
268
|
+
if (!ctx) {
|
|
269
|
+
throw new Error('useMeasure* must be used within <MetricsProvider/>');
|
|
270
|
+
}
|
|
271
|
+
return ctx.measureAllManual;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export { MeasureRegistry, MetricsProvider, useMeasureAuto, useMeasureFingerprint, useMeasureManual, useMetricsContext, useRemeasureAllManual };
|
|
275
|
+
//# sourceMappingURL=MetricsProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetricsProvider.js","sources":["../../src/internal/MetricsProvider.tsx"],"sourcesContent":["import {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useLayoutEffect,\n useRef,\n type MutableRefObject,\n type DependencyList,\n type HTMLAttributes,\n type PropsWithChildren,\n type RefCallback,\n type RefObject,\n} from 'react';\n\nimport {\n scheduleGatherMetrics,\n scheduleMicro,\n useActualRef,\n useInitOnce,\n} from '@hh.ru/magritte-ui-nav-bar/internal/utils';\n\n/**\n * Функция-подписчик, которая получает массив измерений для заранее\n * зарегистрированного набора элементов (порядок соответствует зарегистрированному).\n */\ntype MeasureFn = (elementMetrics: DOMRect) => void;\n\n/**\n * Хук измерения: подписывает callback на изменения размеров указанных элементов\n * и возвращает функцию ручного «принудительного» переизмерения (remeasure).\n *\n * @param elements Список RefObject’ов элементов, размеры которых нужно измерять.\n * @param measureFn Коллбек, получающий массив DOMRect при измерении.\n * @param deps Зависимости для эффекта подписки (аналогичны deps useEffect).\n * @returns Функция remeasure(), которая помечает элементы «грязными» и запускает измерение.\n */\nexport interface UseMeasure {\n (\n elements: RefObject<Element | null> | MutableRefObject<Element | null | undefined>,\n measureFn: MeasureFn,\n deps?: DependencyList\n ): VoidFunction;\n}\n\n/**\n * Реестр подписок на измерения размеров элементов.\n * Хранит двунаправные сопоставления «элемент → подписчики» и «подписчик → элементы»,\n * кеширует DOMRect для элементов в текущем тике и поддерживает список «грязных» элементов.\n *\n * Внешний код вызывает:\n * - register/unregister для подписки/отписки,\n * - markDirty/consumeDirty для управления очередью измерений,\n * - subscribersFor/elementFor для получения связей.\n */\nexport class MeasureRegistry {\n private elementsToSubs = new Map<Element, MeasureFn>();\n private subsToElements = new Map<MeasureFn, Element>();\n private dirty = new Set<Element>();\n isDirty = false;\n\n register(element: Element, fn: MeasureFn): boolean {\n this.subsToElements.set(fn, element);\n const isFirstTimeSub = !this.elementsToSubs.has(element);\n const prevSub = this.elementsToSubs.get(element);\n this.elementsToSubs.set(element, fn);\n if (prevSub && prevSub !== fn) {\n this.subsToElements.delete(prevSub);\n }\n return isFirstTimeSub;\n }\n\n unregister(element: Element): void {\n const fn = this.elementsToSubs.get(element);\n if (fn) {\n this.subsToElements.delete(fn);\n this.elementsToSubs.delete(element);\n }\n }\n markDirty(element: Element): void {\n if (this.elementsToSubs.has(element)) {\n this.dirty.add(element);\n this.isDirty = true;\n }\n }\n\n markAllDirty(): void {\n for (const element of this.elementsToSubs.keys()) {\n this.dirty.add(element);\n }\n this.isDirty = true;\n }\n\n consumeDirty(): Element[] {\n const list = [...this.dirty];\n this.dirty.clear();\n this.isDirty = false;\n return list;\n }\n\n elementFor(fn: MeasureFn): Element | null {\n return this.subsToElements.get(fn) ?? null;\n }\n\n subscribersFor(elements: Element[]): Set<MeasureFn> {\n const subs = new Set<MeasureFn>();\n for (const el of elements) {\n const fn = this.elementsToSubs.get(el);\n if (fn) {\n subs.add(fn);\n }\n }\n return subs;\n }\n\n listAllElements(): Element[] {\n return [...this.elementsToSubs.keys()];\n }\n}\n\nexport interface MetricsContextValue {\n useMeasureAuto: UseMeasure;\n useMeasureManual: UseMeasure;\n useMeasureFingerprint: UseMeasure;\n measureAllManual: VoidFunction;\n}\n\n/**\n * Контекст с API для измерений:\n * - useMeasureAuto: авто-режим (измерения по ResizeObserver + ручные remeasure);\n * - useMeasureManual: ручной режим (только по вызову remeasure);\n * - measureAllManual: принудительное переизмерение всех элементов ручного реестра.\n */\nconst MetricsContext = createContext<MetricsContextValue | null>(null);\n\n/**\n * Провайдер измерений. Оборачивает subtree и предоставляет через контекст:\n * - два хука подписки (авто/ручной),\n * - функцию «переизмерить всё» для ручного реестра.\n *\n * Для корректных измерений при необходимости временно навешивает CSS-класс\n * (measureClassName) на корневой div — полезно для временного отключения\n * transform/overflow и т.п., чтобы getBoundingClientRect() отдавал «чистые» размеры.\n *\n * @example\n * <MetricsProvider measureClassName=\"no-transform\">\n * ...\n * </MetricsProvider>\n */\nexport const MetricsProvider = forwardRef<\n HTMLDivElement,\n PropsWithChildren<{ measureClassName?: string } & HTMLAttributes<HTMLDivElement>>\n>(({ children, measureClassName, ...rest }, ref) => {\n const classNameRef = useActualRef(measureClassName);\n const rootRef = useRef<HTMLDivElement | null>();\n const inMeasureCycleRef = useRef(false);\n const callbacksCallCache = useInitOnce(() => new Set<MeasureFn>());\n\n // Пробрасываем внешний ref и локально храним DOM-узел.\n const rootRefCallback = useCallback<RefCallback<HTMLDivElement>>(\n (element: HTMLDivElement | null) => {\n rootRef.current = element;\n if (typeof ref === 'function') {\n ref(element);\n } else if (ref) {\n ref.current = element;\n }\n },\n [ref]\n );\n // Текущее состояние «включён ли класс для корректного измерения».\n const classStateRef = useRef(false);\n\n // Реестры: отдельный для авто-подписок (RO) и для ручных подписок.\n const manualSubs = useInitOnce(() => new MeasureRegistry());\n const autoSubs = useInitOnce(() => new MeasureRegistry());\n const fingerprintSubs = useInitOnce(() => new MeasureRegistry());\n\n /**\n * Включает/выключает CSS-класс для контейнера (например, чтобы на время измерения отключить transform).\n * Если включили — автоматически планируем выключение в конец тика (см. disableLater).\n */\n const toggle = (on?: boolean) => {\n if (classStateRef.current === !!on || !rootRef.current || !classNameRef.current) {\n return;\n }\n rootRef.current.classList.toggle(classNameRef.current, !!on);\n classStateRef.current = !!on;\n\n // если навесили класс отключающий трансформации - планируем удаление этого класса на конец тика\n // eslint-disable-next-line @typescript-eslint/no-use-before-define\n !!on && disableLater();\n };\n\n /**\n * Планировщик «снять класс позже»: schedule(toggle) вернёт функцию,\n * которая при вызове поставит toggle(false) в конец очереди микротасок.\n */\n const disableLater = useInitOnce(() => scheduleMicro(toggle));\n const measureDirtyRegistry = useInitOnce(() => (registry: MeasureRegistry) => {\n if (!registry.isDirty) {\n return false;\n }\n\n const dirty = registry.consumeDirty();\n const measureFns = registry.subscribersFor(dirty);\n measureFns.forEach((fn) => {\n if (callbacksCallCache.has(fn)) {\n return;\n }\n callbacksCallCache.add(fn);\n const element = registry.elementFor(fn);\n if (!element) {\n return;\n }\n fn(element.getBoundingClientRect());\n });\n\n return true;\n });\n\n const measureSync = useInitOnce(() => () => {\n /**\n * Во время вызова коллбеков может произойти либо синхронный вызов measureSync, либо асинхронный.\n * Синхронный нужно вызвать сразу, а для асинхронного обычно создается задача в очереди микротасок.\n * Но в случае установки флага inMeasureCycle таска не будет создана, т.к. она не нужна, потому что измерения\n * произойдут в этом же цикле.\n * Чтобы синхронно запущенный вызов не сбросил этот флаг раньше времени мы при вызове функции запоминаем\n * его состояние, и при выходе из него возвращаем его к исходному значению.\n * Получается что при рекурсивном вызове measureSync только самая нижняя в стеке вызовов функция сбросит этот\n * флаг в false.\n */\n const inCycle = inMeasureCycleRef.current;\n inMeasureCycleRef.current = true;\n toggle(true);\n\n // фингерпринты обрабатываем только один раз за цикл, т.к. единственный вариант для этих элементов попасть в\n // список на измерение через ResizeObserver, а он не может добавить новые элементы во время цикла измерения\n measureDirtyRegistry(fingerprintSubs);\n\n // в ручную и авто очередь элементы можно добавить руками с помощью вызова функции, а значит элементы могут\n // добавляться в цикле измерений в коллбеках изменения размера, поэтому обрабатываем их до тех пор пока не\n // перестанут появляться новые элементы\n while (autoSubs.isDirty || manualSubs.isDirty) {\n measureDirtyRegistry(autoSubs);\n measureDirtyRegistry(manualSubs);\n }\n\n if (!inCycle) {\n callbacksCallCache.clear();\n }\n inMeasureCycleRef.current = inCycle;\n });\n\n /**\n * Если вызвано асинхронное измерение, то откладываем его до обработки микротасок, чтобы таска на измерения\n * от ResizeObserver и вызванная вручную схлопнулись в одну.\n * Функция не должна вызываться напрямую, только через runMeasure\n */\n const _scheduledMeasure = useInitOnce(() => scheduleGatherMetrics(measureSync));\n /**\n * Кроме того ручной вызов может произойти в цикле измерений, в этом случае не нужно делать новую таску, достаточно\n * просто пометить инвалидированные размеры, они будут получены (или прочитаны из кеша) в текущем цикле измерений\n */\n const runMeasure = useInitOnce(() => () => {\n if (inMeasureCycleRef.current) {\n return;\n }\n _scheduledMeasure();\n });\n\n /**\n * Общий ResizeObserver: помечает элементы грязными и планирует измерение.\n */\n const resizeObserver = useInitOnce(\n () =>\n new ResizeObserver((entries) => {\n const dirtyEntries = entries.map((entry) => entry.target);\n for (const entry of dirtyEntries) {\n autoSubs.markDirty(entry);\n fingerprintSubs.markDirty(entry);\n }\n runMeasure();\n })\n );\n\n /** Отписывает подписчика от авто-реестра и снимает наблюдение, если подписчиков больше нет. */\n const unregister = (registry: MeasureRegistry, element: Element, observe: boolean = true) => {\n registry.unregister(element);\n observe && resizeObserver.unobserve(element);\n };\n\n /** Регистрирует подписчика в реестре и опционально подключает наблюдение через ResizeObserver. */\n const register = (registry: MeasureRegistry, element: Element, fn: MeasureFn, observe: boolean = true) => {\n const isFirstSub = registry.register(element, fn);\n observe && isFirstSub && resizeObserver.observe(element);\n return () => unregister(registry, element, observe);\n };\n\n const createUseMeasure = useInitOnce(\n () =>\n (registry: MeasureRegistry, observe: boolean): UseMeasure =>\n (ref, callback, deps = []) => {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useLayoutEffect(\n () => (ref.current ? register(registry, ref.current, callback, observe) : void 0),\n // eslint-disable-next-line disable-autofix/react-hooks/exhaustive-deps\n deps\n );\n\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const remeasure: VoidFunction = useCallback(() => {\n if (ref.current) {\n registry.markDirty(ref.current);\n runMeasure();\n }\n }, [ref]);\n\n return remeasure;\n }\n );\n\n /**\n * Возвращает функцию, которая пометит «грязными» все элементы ручного реестра\n * и выполнит синхронное измерение сразу (без ожидания микротасок).\n */\n const measureAllManual = useInitOnce(() => () => {\n manualSubs.markAllDirty();\n measureSync();\n });\n\n const contextValue = useInitOnce(() => ({\n useMeasureAuto: createUseMeasure(autoSubs, true),\n useMeasureManual: createUseMeasure(manualSubs, false),\n useMeasureFingerprint: createUseMeasure(fingerprintSubs, true),\n measureAllManual,\n })) satisfies MetricsContextValue;\n\n return (\n <MetricsContext.Provider value={contextValue}>\n <div {...rest} ref={rootRefCallback}>\n {children}\n </div>\n </MetricsContext.Provider>\n );\n});\n\nMetricsProvider.displayName = 'MetricsProvider';\n\nexport const useMetricsContext = (): MetricsContextValue | null => useContext(MetricsContext);\n\ntype UseMeasureKeys = {\n [K in keyof MetricsContextValue]-?: MetricsContextValue[K] extends UseMeasure ? K : never;\n}[keyof MetricsContextValue];\nconst createHook =\n (name: UseMeasureKeys): UseMeasure =>\n (...args) => {\n const ctx = useContext(MetricsContext);\n if (!ctx) {\n throw new Error('useMeasure* must be used within <MetricsProvider/>');\n }\n return ctx[name](...args);\n };\n\nexport const useMeasureAuto = createHook('useMeasureAuto');\nexport const useMeasureManual = createHook('useMeasureManual');\nexport const useMeasureFingerprint = createHook('useMeasureFingerprint');\n\n/**\n * Хук, возвращающий функцию «переизмерить всё» для подписок зарегистрированных с помощью {@link useMeasureManual}.\n * Полезно, когда нужно одномоментно обновить измерения всех зарегистрированных вручную элементов.\n */\nexport const useRemeasureAllManual: () => VoidFunction = () => {\n const ctx = useContext(MetricsContext);\n if (!ctx) {\n throw new Error('useMeasure* must be used within <MetricsProvider/>');\n }\n return ctx.measureAllManual;\n};\n"],"names":["_jsx"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type FC, type PropsWithChildren } from 'react';
|
|
2
|
+
import { KeyedSubscriptions } from '@hh.ru/magritte-ui-nav-bar/internal/KeyedSubscriptions';
|
|
3
|
+
export declare class MorphStore extends KeyedSubscriptions<string> {
|
|
4
|
+
private morphRectsStore;
|
|
5
|
+
set(key: string, rect: DOMRectReadOnly | null): void;
|
|
6
|
+
get(key: string): DOMRectReadOnly | null;
|
|
7
|
+
}
|
|
8
|
+
export declare const MorphContext: import("react").Context<MorphStore | null>;
|
|
9
|
+
export declare const MorphStoreProvider: FC<PropsWithChildren>;
|
|
10
|
+
export declare const useMorphStore: () => MorphStore;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import './../index.css';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { KeyedSubscriptions } from './KeyedSubscriptions.js';
|
|
5
|
+
import { isDOMRectsEqual, useInitOnce } from './utils.js';
|
|
6
|
+
|
|
7
|
+
class MorphStore extends KeyedSubscriptions {
|
|
8
|
+
morphRectsStore = new Map();
|
|
9
|
+
set(key, rect) {
|
|
10
|
+
const isEqual = isDOMRectsEqual(rect, this.morphRectsStore.get(key) ?? null);
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
12
|
+
rect === null ? this.morphRectsStore.delete(key) : this.morphRectsStore.set(key, rect);
|
|
13
|
+
if (!isEqual) {
|
|
14
|
+
this.markChanged(key);
|
|
15
|
+
this.notify();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
get(key) {
|
|
19
|
+
return this.morphRectsStore.get(key) ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const MorphContext = createContext(null);
|
|
23
|
+
const MorphStoreProvider = ({ children }) => {
|
|
24
|
+
const store = useInitOnce(() => new MorphStore());
|
|
25
|
+
return jsx(MorphContext.Provider, { value: store, children: children });
|
|
26
|
+
};
|
|
27
|
+
const useMorphStore = () => {
|
|
28
|
+
const morphStore = useContext(MorphContext);
|
|
29
|
+
if (!morphStore) {
|
|
30
|
+
throw new Error('useMorphStore must be used within <MorphStoreProvider/>');
|
|
31
|
+
}
|
|
32
|
+
return morphStore;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { MorphContext, MorphStore, MorphStoreProvider, useMorphStore };
|
|
36
|
+
//# sourceMappingURL=MorphStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MorphStore.js","sources":["../../src/internal/MorphStore.tsx"],"sourcesContent":["import { createContext, useContext, type FC, type PropsWithChildren } from 'react';\n\nimport { KeyedSubscriptions } from '@hh.ru/magritte-ui-nav-bar/internal/KeyedSubscriptions';\nimport { isDOMRectsEqual, useInitOnce } from '@hh.ru/magritte-ui-nav-bar/internal/utils';\n\nexport class MorphStore extends KeyedSubscriptions<string> {\n private morphRectsStore = new Map<string, DOMRectReadOnly>();\n\n set(key: string, rect: DOMRectReadOnly | null): void {\n const isEqual = isDOMRectsEqual(rect, this.morphRectsStore.get(key) ?? null);\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n rect === null ? this.morphRectsStore.delete(key) : this.morphRectsStore.set(key, rect);\n if (!isEqual) {\n this.markChanged(key);\n this.notify();\n }\n }\n\n get(key: string): DOMRectReadOnly | null {\n return this.morphRectsStore.get(key) ?? null;\n }\n}\n\nexport const MorphContext = createContext<MorphStore | null>(null);\nexport const MorphStoreProvider: FC<PropsWithChildren> = ({ children }) => {\n const store = useInitOnce(() => new MorphStore());\n\n return <MorphContext.Provider value={store}>{children}</MorphContext.Provider>;\n};\n\nexport const useMorphStore = (): MorphStore => {\n const morphStore = useContext(MorphContext);\n if (!morphStore) {\n throw new Error('useMorphStore must be used within <MorphStoreProvider/>');\n }\n return morphStore;\n};\n"],"names":["_jsx"],"mappings":";;;;;AAKM,MAAO,UAAW,SAAQ,kBAA0B,CAAA;AAC9C,IAAA,eAAe,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE7D,GAAG,CAAC,GAAW,EAAE,IAA4B,EAAA;AACzC,QAAA,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC;;QAE7E,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACvF,IAAI,CAAC,OAAO,EAAE;AACV,YAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,MAAM,EAAE,CAAC;SACjB;KACJ;AAED,IAAA,GAAG,CAAC,GAAW,EAAA;QACX,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;KAChD;AACJ,CAAA;MAEY,YAAY,GAAG,aAAa,CAAoB,IAAI,EAAE;MACtD,kBAAkB,GAA0B,CAAC,EAAE,QAAQ,EAAE,KAAI;IACtE,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC;IAElD,OAAOA,GAAA,CAAC,YAAY,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,KAAK,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAyB,CAAC;AACnF,EAAE;AAEK,MAAM,aAAa,GAAG,MAAiB;AAC1C,IAAA,MAAM,UAAU,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,EAAE;AACb,QAAA,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;KAC9E;AACD,IAAA,OAAO,UAAU,CAAC;AACtB;;;;"}
|