@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.
Files changed (79) hide show
  1. package/NavBar-CrD8CEWb.js +118 -0
  2. package/NavBar-CrD8CEWb.js.map +1 -0
  3. package/index.css +289 -0
  4. package/index.d.ts +9 -0
  5. package/index.js +35 -0
  6. package/index.js.map +1 -0
  7. package/index.mock.d.ts +17 -0
  8. package/index.mock.js +47 -0
  9. package/index.mock.js.map +1 -0
  10. package/internal/KeyedSubscriptions.d.ts +41 -0
  11. package/internal/KeyedSubscriptions.js +79 -0
  12. package/internal/KeyedSubscriptions.js.map +1 -0
  13. package/internal/MetricsProvider.d.ts +77 -0
  14. package/internal/MetricsProvider.js +275 -0
  15. package/internal/MetricsProvider.js.map +1 -0
  16. package/internal/MorphStore.d.ts +10 -0
  17. package/internal/MorphStore.js +36 -0
  18. package/internal/MorphStore.js.map +1 -0
  19. package/internal/PaneStore.d.ts +60 -0
  20. package/internal/PaneStore.js +102 -0
  21. package/internal/PaneStore.js.map +1 -0
  22. package/internal/ProgressiveBlur.d.ts +7 -0
  23. package/internal/ProgressiveBlur.js +43 -0
  24. package/internal/ProgressiveBlur.js.map +1 -0
  25. package/internal/useAnimationRanges.d.ts +38 -0
  26. package/internal/useAnimationRanges.js +52 -0
  27. package/internal/useAnimationRanges.js.map +1 -0
  28. package/internal/useBindScrollToAnimationProgress.d.ts +9 -0
  29. package/internal/useBindScrollToAnimationProgress.js +82 -0
  30. package/internal/useBindScrollToAnimationProgress.js.map +1 -0
  31. package/internal/useDivider.d.ts +4 -0
  32. package/internal/useDivider.js +38 -0
  33. package/internal/useDivider.js.map +1 -0
  34. package/internal/useNavBarMetrics.d.ts +9 -0
  35. package/internal/useNavBarMetrics.js +34 -0
  36. package/internal/useNavBarMetrics.js.map +1 -0
  37. package/internal/useResetFocus.d.ts +3 -0
  38. package/internal/useResetFocus.js +31 -0
  39. package/internal/useResetFocus.js.map +1 -0
  40. package/internal/useScrollAdapter.d.ts +15 -0
  41. package/internal/useScrollAdapter.js +116 -0
  42. package/internal/useScrollAdapter.js.map +1 -0
  43. package/internal/useSnapScroll.d.ts +6 -0
  44. package/internal/useSnapScroll.js +148 -0
  45. package/internal/useSnapScroll.js.map +1 -0
  46. package/internal/useSyncMotionValue.d.ts +2 -0
  47. package/internal/useSyncMotionValue.js +9 -0
  48. package/internal/useSyncMotionValue.js.map +1 -0
  49. package/internal/utils.d.ts +207 -0
  50. package/internal/utils.js +359 -0
  51. package/internal/utils.js.map +1 -0
  52. package/package.json +38 -0
  53. package/public/Actions.d.ts +26 -0
  54. package/public/Actions.js +47 -0
  55. package/public/Actions.js.map +1 -0
  56. package/public/EnvironmentFingerprintNode.d.ts +7 -0
  57. package/public/EnvironmentFingerprintNode.js +70 -0
  58. package/public/EnvironmentFingerprintNode.js.map +1 -0
  59. package/public/LayoutMorph.d.ts +32 -0
  60. package/public/LayoutMorph.js +132 -0
  61. package/public/LayoutMorph.js.map +1 -0
  62. package/public/LayoutStage.d.ts +7 -0
  63. package/public/LayoutStage.js +87 -0
  64. package/public/LayoutStage.js.map +1 -0
  65. package/public/Morph.d.ts +28 -0
  66. package/public/Morph.js +66 -0
  67. package/public/Morph.js.map +1 -0
  68. package/public/NavBar.d.ts +57 -0
  69. package/public/NavBar.js +21 -0
  70. package/public/NavBar.js.map +1 -0
  71. package/public/Pane.d.ts +22 -0
  72. package/public/Pane.js +79 -0
  73. package/public/Pane.js.map +1 -0
  74. package/public/Stage.d.ts +10 -0
  75. package/public/Stage.js +43 -0
  76. package/public/Stage.js.map +1 -0
  77. package/public/TitleContainer.d.ts +24 -0
  78. package/public/TitleContainer.js +34 -0
  79. package/public/TitleContainer.js.map +1 -0
@@ -0,0 +1,359 @@
1
+ import './../index.css';
2
+ import { useRef, useLayoutEffect } from 'react';
3
+
4
+ const lerp = (from, to, progress) => from + (to - from) * progress;
5
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
6
+ /**
7
+ * Создаёт функцию преобразования значений из одного числового интервала (домен) в другой (образ).
8
+ *
9
+ * @param domain Интервал входных значений [min, max].
10
+ * @param image Интервал выходных значений [min, max].
11
+ *
12
+ * @returns Функция, принимающая число из диапазона `domain`, преобразующая его в диапазон `image`
13
+ * с линейной интерполяцией.
14
+ */
15
+ const remap = (domain, image) => {
16
+ const domainRange = domain[1] - domain[0];
17
+ const imageRange = image[1] - image[0];
18
+ const min = Math.min(...image);
19
+ const max = Math.max(...image);
20
+ if (domainRange === 0) {
21
+ return () => image[0];
22
+ }
23
+ const a = imageRange / domainRange;
24
+ const b = image[0] - a * domain[0];
25
+ return (value) => clamp(a * value + b, min, max);
26
+ };
27
+ const useInitOnce = (fn) => {
28
+ const ref = useRef(null);
29
+ if (ref.current === null) {
30
+ ref.current = fn();
31
+ }
32
+ return ref.current;
33
+ };
34
+ /**
35
+ * Возвращает «одноразовый» планировщик, который выполнит `fn` в следующем
36
+ * тике **микрозадач** (через `Promise.resolve().then(...)`). Повторные вызовы
37
+ * до момента выполнения схлопываются в один запуск.
38
+ *
39
+ * Семантика:
40
+ * - Первый вызов ставит `fn` в очередь микрозадач; последующие вызовы до
41
+ * выполнения игнорируются.
42
+ * - После выполнения `fn` «засов» снимается — можно планировать снова.
43
+ * - Ошибки из `fn` перебрасываются в следующий макротик (через `setTimeout`),
44
+ * чтобы они были видны в DevTools и не превращались в «unhandled rejection».
45
+ *
46
+ * Когда использовать:
47
+ * - Нужно объединить множество синхронных триггеров в одно действие,
48
+ * выполнив его **раньше таймеров/`requestAnimationFrame`**.
49
+ * - Не требуется ожидать стабилизации DOM/лейаута (микрозадачи идут до кадра).
50
+ *
51
+ * @param fn — колбэк, который будет выполнен в следующем тике микрозадач.
52
+ * @returns Функция-триггер: вызов ставит `fn` в очередь 1 раз на тик микрозадач.
53
+ *
54
+ * @example
55
+ * const trigger = scheduleMicro(recompute);
56
+ * trigger(); // запланирован один запуск
57
+ * trigger(); // проигнорирован (уже запланировано)
58
+ * // `recompute` выполнится один раз на следующем тике микрозадач
59
+ */
60
+ const scheduleMicro = (fn) => {
61
+ let scheduled = false;
62
+ return () => {
63
+ if (scheduled) {
64
+ return;
65
+ }
66
+ scheduled = true;
67
+ Promise.resolve()
68
+ .then(() => {
69
+ scheduled = false;
70
+ fn();
71
+ })
72
+ .catch((err) => setTimeout(() => {
73
+ throw err;
74
+ }, 0));
75
+ };
76
+ };
77
+ /**
78
+ * Возвращает «одноразовый» планировщик, который выполнит `fn` в следующем
79
+ * тике **макрозадач** (через `setTimeout(0)`). Повторные вызовы до момента
80
+ * выполнения схлопываются в один запуск.
81
+ *
82
+ * Семантика:
83
+ * - Первый вызов ставит `fn` в `setTimeout`; последующие вызовы до выполнения
84
+ * игнорируются.
85
+ * - После выполнения `fn` «засов» снимается — можно планировать снова.
86
+ * - Ошибки из `fn` всплывут как непойманные ошибки из обработчика таймера.
87
+ *
88
+ * Когда использовать:
89
+ * - Нужно отложить выполнение **после** опустошения очереди микрозадач
90
+ * (т.е. позже, чем `scheduleMicro`), часто позволяя браузеру между делом
91
+ * подготовить кадр/перерисовку.
92
+ *
93
+ * @param fn — колбэк, который будет выполнен в следующем тике макрозадач.
94
+ * @returns Функция-триггер: вызов ставит `fn` в очередь 1 раз на тик таймера.
95
+ *
96
+ * @example
97
+ * const trigger = scheduleMacro(flushQueue);
98
+ * trigger(); // запланирован один таймер
99
+ * trigger(); // проигнорирован до выполнения `flushQueue`
100
+ */
101
+ const scheduleMacro = (fn) => {
102
+ let scheduled = false;
103
+ const scheduledFn = () => {
104
+ if (scheduled) {
105
+ return;
106
+ }
107
+ scheduled = true;
108
+ setTimeout(() => {
109
+ scheduled = false;
110
+ fn();
111
+ });
112
+ };
113
+ return scheduledFn;
114
+ };
115
+ /**
116
+ * Планировщик для «сбора метрик» на основе микрозадач: откладывает выполнение `fn`
117
+ * до момента, когда серия частых вызовов завершится, и запускает `fn` ровно один раз.
118
+ *
119
+ * Идея:
120
+ * - Первый вызов добавляет «запас» из двух тиков микрозадач; каждый следующий
121
+ * в ту же серию добавляет ещё +1.
122
+ * - Единый «дренирующий» цикл (один на серию) уменьшает счётчик на каждом тике
123
+ * микрозадач; когда счётчик достигает нуля — выполняется `fn`.
124
+ * - Повторные вызовы не создают параллельных циклов; `fn` не вызывается несколько раз.
125
+ * - Ошибки из `fn` перебрасываются в макрозадачу (`setTimeout`), чтобы их было видно
126
+ * в DevTools и они не становились unhandled rejection.
127
+ *
128
+ * Когда использовать:
129
+ * - Нужна коалесценция множественных триггеров (Promises, ResizeObserver и т.п.)
130
+ * с запуском «после того, как всё успокоилось», чтобы измерить DOM/пересчитать
131
+ * лейаут/агрегировать состояние один раз.
132
+ *
133
+ * Гарантии и нюансы:
134
+ * - Гарантируется минимум два тика микрозадач после первого вызова (даёт времени
135
+ * промисам/микроочереди завершиться).
136
+ * - Работает как эвристика «дождаться затишья» на уровне микрозадач, без перехода
137
+ * в макрозадачи/таймеры (сам `fn` всё равно выполняется в микрозадаче).
138
+ *
139
+ * @param fn — колбэк, который будет выполнен один раз после завершения серии вызовов.
140
+ * @returns Триггер; его можно вызывать многократно — `fn` запустится один раз,
141
+ * когда внутренний счётчик дойдёт до нуля.
142
+ */
143
+ const scheduleGatherMetrics = (fn) => {
144
+ let callsCount = 0;
145
+ let draining = false;
146
+ const step = () => Promise.resolve()
147
+ .then(() => {
148
+ if (callsCount > 0) {
149
+ callsCount -= 1;
150
+ if (callsCount > 0) {
151
+ void step();
152
+ return;
153
+ }
154
+ draining = false;
155
+ fn();
156
+ return;
157
+ }
158
+ draining = false;
159
+ })
160
+ .catch((err) => setTimeout(() => {
161
+ throw err;
162
+ }, 0));
163
+ return () => {
164
+ callsCount += callsCount === 0 ? 2 : 1;
165
+ if (!draining) {
166
+ draining = true;
167
+ void step();
168
+ }
169
+ };
170
+ };
171
+ /**
172
+ * Хук для получения "живой" ссылки (`ref`) на актуальное значение.
173
+ *
174
+ * В отличие от обычного `useRef(initialValue)`, где `.current` инициализируется
175
+ * только один раз и далее меняется вручную, `useActualRef` автоматически
176
+ * обновляет `.current` при каждом рендере на основе переданного `value`.
177
+ *
178
+ * Зачем это нужно:
179
+ * - Когда в коллбэках или подписках нужно иметь доступ к самому свежему значению
180
+ * пропса или состояния, но при этом не хочется пересоздавать замыкания.
181
+ * - Позволяет избежать проблем со "старыми" значениями внутри `useCallback`,
182
+ * `useEffect` и обработчиков событий.
183
+ *
184
+ * @param value Актуальное значение, которое должно быть доступно через `.current`.
185
+ *
186
+ * @returns `ref`-объект (`MutableRefObject<T>`), чьё свойство `.current` всегда
187
+ * указывает на последнее переданное `value`.
188
+ */
189
+ const useActualRef = (value) => {
190
+ const valueRef = useRef(value);
191
+ valueRef.current = value;
192
+ return valueRef;
193
+ };
194
+ /**
195
+ * Подписывает трансформацию на изменения исходного `MotionValue` **и** внешнего хранилища ключевых подписок.
196
+ *
197
+ * В отличие от `useTransform` из motion, этот хук не создает новый `MotionValue`.
198
+ * Вместо этого он вызывает пользовательскую `transformFn(arg, value)` при любом изменении:
199
+ * - исходного `originalValue`, и/или
200
+ * - значений во внешнем хранилище `store` по указанным `keys`.
201
+ *
202
+ * `transformFn` обычно внутри делает `.set(...)` у одного или нескольких целевых `MotionValue`.
203
+ *
204
+ * @template T Тип значения исходного `MotionValue`.
205
+ * @template K Тип ключей внешнего хранилища (строка | число | символ).
206
+ * @template V Тип агрегированного значения, извлекаемого из хранилища и передаваемого в `transformFn`.
207
+ *
208
+ * @param {MotionValue<T>} originalValue
209
+ * Исходный `MotionValue`, изменения которого инициируют трансформацию.
210
+ *
211
+ * @param {KeyedSubscriptions<K> | (() => KeyedSubscriptions<K>)} store
212
+ * Внешнее хранилище с подписками. Может быть самим объектом или фабрикой.
213
+ * Если передана функция, хук пересоздает подписки при событии `onDestroy`.
214
+ *
215
+ * @param {K[]} keys
216
+ * Набор ключей хранилища, на изменения которых нужно реагировать.
217
+ * Равенство `keys` проверяется по количеству и составу; изменение порядка не важно.
218
+ * Изменение этого набора вызывает пересоздание подписок подписок.
219
+ *
220
+ * @param {() => V} valueExtractor
221
+ * Функция, синхронно извлекающая/агрегирующая данные из хранилища
222
+ * для передачи первым аргументом в `transformFn`.
223
+ *
224
+ * @param {(arg: V, value: T) => void} transformFn
225
+ * Функция-трансформация. Вызывается при каждом триггере с
226
+ * `arg = valueExtractor()` и `value = originalValue.get()`. Обычно внутри вызывает `someMotionValue.set(...)`.
227
+ *
228
+ * @example
229
+ * ```tsx
230
+ * import { motion, useMotionValue } from "motion/react";
231
+ *
232
+ * const store: KeyedSubscriptions<"width" | "height"> = createKeyedStore();
233
+ *
234
+ * export function Example() {
235
+ * const x = useMotionValue(0); // исходный драйвер
236
+ * const y = useMotionValue(0); // целевой MotionValue
237
+ *
238
+ * useStoreSyncedTransform(
239
+ * x,
240
+ * store,
241
+ * ["width", "height"],
242
+ * () => ({
243
+ * w: store.get("width"),
244
+ * h: store.get("height"),
245
+ * }),
246
+ * ({ w, h }, xVal) => {
247
+ * // пример простой зависимости
248
+ * const mapped = (xVal / Math.max(w, 1)) * h;
249
+ * y.set(mapped);
250
+ * }
251
+ * );
252
+ *
253
+ * return <motion.div style={{ x, y }} />;
254
+ * }
255
+ * ```
256
+ */
257
+ const useStoreSyncedTransform = (originalValue, store, keys, valueExtractor, transformFn) => {
258
+ const valueExtractorRef = useActualRef(valueExtractor);
259
+ const extractedValueRef = useActualRef(valueExtractor());
260
+ const transformFnRef = useActualRef(transformFn);
261
+ const originalValueRef = useActualRef(originalValue);
262
+ const transform = useInitOnce(() => () => transformFnRef.current(extractedValueRef.current, originalValueRef.current.get()));
263
+ const observedKeysRef = useRef(null);
264
+ let observedKeys = observedKeysRef.current;
265
+ const isKeysChanged = observedKeys === null || keys.length !== observedKeys.size || keys.some((k) => !observedKeys?.has(k));
266
+ if (isKeysChanged) {
267
+ observedKeys = new Set(keys);
268
+ observedKeysRef.current = observedKeys;
269
+ }
270
+ useLayoutEffect(() => {
271
+ const unsubscribeStore = store.onChange(observedKeys ? [...observedKeys] : [], () => {
272
+ extractedValueRef.current = valueExtractorRef.current();
273
+ transform();
274
+ });
275
+ const unsubscribeValue = originalValue.on('change', transform);
276
+ return () => {
277
+ unsubscribeStore();
278
+ unsubscribeValue();
279
+ };
280
+ }, [store, observedKeys, originalValue, extractedValueRef, valueExtractorRef, transform]);
281
+ useLayoutEffect(() => {
282
+ transform();
283
+ }, [valueExtractor, extractedValueRef, transformFn, transform]);
284
+ };
285
+ const SCROLLABLE = ['auto', 'scroll'];
286
+ const findScrollContainer = (element) => {
287
+ let parentElement = element.parentElement;
288
+ while (parentElement !== null) {
289
+ const { overflowY } = window.getComputedStyle(parentElement);
290
+ if (SCROLLABLE.includes(overflowY)) {
291
+ return { eventsProvider: parentElement, infoProvider: parentElement, mode: 'element' };
292
+ }
293
+ parentElement = parentElement.parentElement;
294
+ }
295
+ return {
296
+ eventsProvider: window,
297
+ infoProvider: document.scrollingElement || document.documentElement,
298
+ mode: 'window',
299
+ };
300
+ };
301
+ const isDOMRectsEqual = (a, b) => a === b || (a !== null && b !== null && a.x === b.x && a.y === b.y && a.height === b.height && a.width === b.width);
302
+ const MAX_SCALE_RATIO = 2;
303
+ const AXIS_SIZE_MULTIPLIER = {
304
+ left: 0,
305
+ center: 0.5,
306
+ right: 1,
307
+ top: 0,
308
+ bottom: 1,
309
+ };
310
+ const calcMorphParams = (start, end, sizeAxis, horizontalPositionAlign, verticalPositionAlign) => {
311
+ if (start === null || end === null) {
312
+ return {
313
+ deltaX: 0,
314
+ deltaY: 0,
315
+ scaleX: 1,
316
+ scaleY: 1,
317
+ };
318
+ }
319
+ let axis = sizeAxis;
320
+ let scaleX = end.width / start.width;
321
+ let scaleY = end.height / start.height;
322
+ if (sizeAxis === 'auto') {
323
+ const ratio = Math.max(scaleX, scaleY) / Math.min(scaleX, scaleY);
324
+ if (ratio > MAX_SCALE_RATIO) {
325
+ const absScaleX = scaleX < 1 ? 1 / scaleX : scaleX;
326
+ const absScaleY = scaleY < 1 ? 1 / scaleY : scaleY;
327
+ axis = absScaleX < absScaleY ? 'horizontal' : 'vertical';
328
+ }
329
+ }
330
+ scaleX = axis === 'vertical' ? scaleY : scaleX;
331
+ scaleY = axis === 'horizontal' ? scaleX : scaleY;
332
+ const startX = start.x + start.width * AXIS_SIZE_MULTIPLIER[horizontalPositionAlign];
333
+ const endX = end.x + end.width * AXIS_SIZE_MULTIPLIER[horizontalPositionAlign];
334
+ const startY = start.y + start.height * AXIS_SIZE_MULTIPLIER[verticalPositionAlign];
335
+ const endY = end.y + end.height * AXIS_SIZE_MULTIPLIER[verticalPositionAlign];
336
+ const deltaX = endX - startX;
337
+ const deltaY = endY - startY;
338
+ return {
339
+ deltaX,
340
+ deltaY,
341
+ scaleX,
342
+ scaleY,
343
+ };
344
+ };
345
+ const getRelativeOffset = (scrollAdapter, element) => {
346
+ if (!scrollAdapter.scrollContainer.current || !element) {
347
+ return 0;
348
+ }
349
+ const container = scrollAdapter.scrollContainer.current;
350
+ const containerTop = container === document.documentElement ? 0 : container.getBoundingClientRect().top;
351
+ const scroll = scrollAdapter.getScrollTop();
352
+ scrollAdapter.setScrollTop(0);
353
+ const elementTop = element.getBoundingClientRect().top;
354
+ scrollAdapter.setScrollTop(scroll);
355
+ return elementTop - containerTop;
356
+ };
357
+
358
+ export { calcMorphParams, clamp, findScrollContainer, getRelativeOffset, isDOMRectsEqual, lerp, remap, scheduleGatherMetrics, scheduleMacro, scheduleMicro, useActualRef, useInitOnce, useStoreSyncedTransform };
359
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../src/internal/utils.ts"],"sourcesContent":["import { type MutableRefObject, useLayoutEffect, useRef } from 'react';\nimport type { MotionValue } from 'motion';\n\nimport { KeyedSubscriptions } from '@hh.ru/magritte-ui-nav-bar/internal/KeyedSubscriptions';\nimport { ScrollAdapter } from '@hh.ru/magritte-ui-nav-bar/internal/useScrollAdapter';\n\nexport const lerp = (from: number, to: number, progress: number): number => from + (to - from) * progress;\nexport const clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\n\n/**\n * Создаёт функцию преобразования значений из одного числового интервала (домен) в другой (образ).\n *\n * @param domain Интервал входных значений [min, max].\n * @param image Интервал выходных значений [min, max].\n *\n * @returns Функция, принимающая число из диапазона `domain`, преобразующая его в диапазон `image`\n * с линейной интерполяцией.\n */\nexport const remap = (domain: [number, number], image: [number, number]) => {\n const domainRange = domain[1] - domain[0];\n const imageRange = image[1] - image[0];\n const min = Math.min(...image);\n const max = Math.max(...image);\n\n if (domainRange === 0) {\n return () => image[0];\n }\n\n const a = imageRange / domainRange;\n const b = image[0] - a * domain[0];\n\n return (value: number): number => clamp(a * value + b, min, max);\n};\n\nexport const useInitOnce = <T>(fn: () => T): T => {\n const ref = useRef<T | null>(null);\n if (ref.current === null) {\n ref.current = fn();\n }\n return ref.current;\n};\n\n/**\n * Возвращает «одноразовый» планировщик, который выполнит `fn` в следующем\n * тике **микрозадач** (через `Promise.resolve().then(...)`). Повторные вызовы\n * до момента выполнения схлопываются в один запуск.\n *\n * Семантика:\n * - Первый вызов ставит `fn` в очередь микрозадач; последующие вызовы до\n * выполнения игнорируются.\n * - После выполнения `fn` «засов» снимается — можно планировать снова.\n * - Ошибки из `fn` перебрасываются в следующий макротик (через `setTimeout`),\n * чтобы они были видны в DevTools и не превращались в «unhandled rejection».\n *\n * Когда использовать:\n * - Нужно объединить множество синхронных триггеров в одно действие,\n * выполнив его **раньше таймеров/`requestAnimationFrame`**.\n * - Не требуется ожидать стабилизации DOM/лейаута (микрозадачи идут до кадра).\n *\n * @param fn — колбэк, который будет выполнен в следующем тике микрозадач.\n * @returns Функция-триггер: вызов ставит `fn` в очередь 1 раз на тик микрозадач.\n *\n * @example\n * const trigger = scheduleMicro(recompute);\n * trigger(); // запланирован один запуск\n * trigger(); // проигнорирован (уже запланировано)\n * // `recompute` выполнится один раз на следующем тике микрозадач\n */\nexport const scheduleMicro = (fn: VoidFunction) => {\n let scheduled = false;\n return (): void => {\n if (scheduled) {\n return;\n }\n scheduled = true;\n Promise.resolve()\n .then(() => {\n scheduled = false;\n fn();\n })\n .catch((err) =>\n setTimeout(() => {\n throw err;\n }, 0)\n );\n };\n};\n\n/**\n * Возвращает «одноразовый» планировщик, который выполнит `fn` в следующем\n * тике **макрозадач** (через `setTimeout(0)`). Повторные вызовы до момента\n * выполнения схлопываются в один запуск.\n *\n * Семантика:\n * - Первый вызов ставит `fn` в `setTimeout`; последующие вызовы до выполнения\n * игнорируются.\n * - После выполнения `fn` «засов» снимается — можно планировать снова.\n * - Ошибки из `fn` всплывут как непойманные ошибки из обработчика таймера.\n *\n * Когда использовать:\n * - Нужно отложить выполнение **после** опустошения очереди микрозадач\n * (т.е. позже, чем `scheduleMicro`), часто позволяя браузеру между делом\n * подготовить кадр/перерисовку.\n *\n * @param fn — колбэк, который будет выполнен в следующем тике макрозадач.\n * @returns Функция-триггер: вызов ставит `fn` в очередь 1 раз на тик таймера.\n *\n * @example\n * const trigger = scheduleMacro(flushQueue);\n * trigger(); // запланирован один таймер\n * trigger(); // проигнорирован до выполнения `flushQueue`\n */\nexport const scheduleMacro = (fn: VoidFunction): VoidFunction => {\n let scheduled = false;\n const scheduledFn = () => {\n if (scheduled) {\n return;\n }\n scheduled = true;\n setTimeout(() => {\n scheduled = false;\n fn();\n });\n };\n return scheduledFn;\n};\n\n/**\n * Планировщик для «сбора метрик» на основе микрозадач: откладывает выполнение `fn`\n * до момента, когда серия частых вызовов завершится, и запускает `fn` ровно один раз.\n *\n * Идея:\n * - Первый вызов добавляет «запас» из двух тиков микрозадач; каждый следующий\n * в ту же серию добавляет ещё +1.\n * - Единый «дренирующий» цикл (один на серию) уменьшает счётчик на каждом тике\n * микрозадач; когда счётчик достигает нуля — выполняется `fn`.\n * - Повторные вызовы не создают параллельных циклов; `fn` не вызывается несколько раз.\n * - Ошибки из `fn` перебрасываются в макрозадачу (`setTimeout`), чтобы их было видно\n * в DevTools и они не становились unhandled rejection.\n *\n * Когда использовать:\n * - Нужна коалесценция множественных триггеров (Promises, ResizeObserver и т.п.)\n * с запуском «после того, как всё успокоилось», чтобы измерить DOM/пересчитать\n * лейаут/агрегировать состояние один раз.\n *\n * Гарантии и нюансы:\n * - Гарантируется минимум два тика микрозадач после первого вызова (даёт времени\n * промисам/микроочереди завершиться).\n * - Работает как эвристика «дождаться затишья» на уровне микрозадач, без перехода\n * в макрозадачи/таймеры (сам `fn` всё равно выполняется в микрозадаче).\n *\n * @param fn — колбэк, который будет выполнен один раз после завершения серии вызовов.\n * @returns Триггер; его можно вызывать многократно — `fn` запустится один раз,\n * когда внутренний счётчик дойдёт до нуля.\n */\nexport const scheduleGatherMetrics = (fn: VoidFunction): VoidFunction => {\n let callsCount = 0;\n let draining = false;\n\n const step = () =>\n Promise.resolve()\n .then(() => {\n if (callsCount > 0) {\n callsCount -= 1;\n if (callsCount > 0) {\n void step();\n return;\n }\n draining = false;\n fn();\n return;\n }\n draining = false;\n })\n .catch((err) =>\n setTimeout(() => {\n throw err;\n }, 0)\n );\n\n return () => {\n callsCount += callsCount === 0 ? 2 : 1;\n if (!draining) {\n draining = true;\n void step();\n }\n };\n};\n\n/**\n * Хук для получения \"живой\" ссылки (`ref`) на актуальное значение.\n *\n * В отличие от обычного `useRef(initialValue)`, где `.current` инициализируется\n * только один раз и далее меняется вручную, `useActualRef` автоматически\n * обновляет `.current` при каждом рендере на основе переданного `value`.\n *\n * Зачем это нужно:\n * - Когда в коллбэках или подписках нужно иметь доступ к самому свежему значению\n * пропса или состояния, но при этом не хочется пересоздавать замыкания.\n * - Позволяет избежать проблем со \"старыми\" значениями внутри `useCallback`,\n * `useEffect` и обработчиков событий.\n *\n * @param value Актуальное значение, которое должно быть доступно через `.current`.\n *\n * @returns `ref`-объект (`MutableRefObject<T>`), чьё свойство `.current` всегда\n * указывает на последнее переданное `value`.\n */\nexport const useActualRef = <T>(value: T): MutableRefObject<T> => {\n const valueRef = useRef(value);\n valueRef.current = value;\n return valueRef;\n};\n\n/**\n * Подписывает трансформацию на изменения исходного `MotionValue` **и** внешнего хранилища ключевых подписок.\n *\n * В отличие от `useTransform` из motion, этот хук не создает новый `MotionValue`.\n * Вместо этого он вызывает пользовательскую `transformFn(arg, value)` при любом изменении:\n * - исходного `originalValue`, и/или\n * - значений во внешнем хранилище `store` по указанным `keys`.\n *\n * `transformFn` обычно внутри делает `.set(...)` у одного или нескольких целевых `MotionValue`.\n *\n * @template T Тип значения исходного `MotionValue`.\n * @template K Тип ключей внешнего хранилища (строка | число | символ).\n * @template V Тип агрегированного значения, извлекаемого из хранилища и передаваемого в `transformFn`.\n *\n * @param {MotionValue<T>} originalValue\n * Исходный `MotionValue`, изменения которого инициируют трансформацию.\n *\n * @param {KeyedSubscriptions<K> | (() => KeyedSubscriptions<K>)} store\n * Внешнее хранилище с подписками. Может быть самим объектом или фабрикой.\n * Если передана функция, хук пересоздает подписки при событии `onDestroy`.\n *\n * @param {K[]} keys\n * Набор ключей хранилища, на изменения которых нужно реагировать.\n * Равенство `keys` проверяется по количеству и составу; изменение порядка не важно.\n * Изменение этого набора вызывает пересоздание подписок подписок.\n *\n * @param {() => V} valueExtractor\n * Функция, синхронно извлекающая/агрегирующая данные из хранилища\n * для передачи первым аргументом в `transformFn`.\n *\n * @param {(arg: V, value: T) => void} transformFn\n * Функция-трансформация. Вызывается при каждом триггере с\n * `arg = valueExtractor()` и `value = originalValue.get()`. Обычно внутри вызывает `someMotionValue.set(...)`.\n *\n * @example\n * ```tsx\n * import { motion, useMotionValue } from \"motion/react\";\n *\n * const store: KeyedSubscriptions<\"width\" | \"height\"> = createKeyedStore();\n *\n * export function Example() {\n * const x = useMotionValue(0); // исходный драйвер\n * const y = useMotionValue(0); // целевой MotionValue\n *\n * useStoreSyncedTransform(\n * x,\n * store,\n * [\"width\", \"height\"],\n * () => ({\n * w: store.get(\"width\"),\n * h: store.get(\"height\"),\n * }),\n * ({ w, h }, xVal) => {\n * // пример простой зависимости\n * const mapped = (xVal / Math.max(w, 1)) * h;\n * y.set(mapped);\n * }\n * );\n *\n * return <motion.div style={{ x, y }} />;\n * }\n * ```\n */\nexport const useStoreSyncedTransform = <T, K extends PropertyKey, V>(\n originalValue: MotionValue<T>,\n store: KeyedSubscriptions<K>,\n keys: K[],\n valueExtractor: () => V,\n transformFn: (arg: V, value: T) => void\n): void => {\n const valueExtractorRef = useActualRef(valueExtractor);\n const extractedValueRef = useActualRef(valueExtractor());\n const transformFnRef = useActualRef(transformFn);\n const originalValueRef = useActualRef(originalValue);\n const transform = useInitOnce(\n () => () => transformFnRef.current(extractedValueRef.current, originalValueRef.current.get())\n );\n\n const observedKeysRef = useRef<Set<K> | null>(null);\n let observedKeys = observedKeysRef.current;\n const isKeysChanged =\n observedKeys === null || keys.length !== observedKeys.size || keys.some((k) => !observedKeys?.has(k));\n\n if (isKeysChanged) {\n observedKeys = new Set(keys);\n observedKeysRef.current = observedKeys;\n }\n\n useLayoutEffect(() => {\n const unsubscribeStore = store.onChange(observedKeys ? [...observedKeys] : [], () => {\n extractedValueRef.current = valueExtractorRef.current();\n transform();\n });\n const unsubscribeValue = originalValue.on('change', transform);\n\n return () => {\n unsubscribeStore();\n unsubscribeValue();\n };\n }, [store, observedKeys, originalValue, extractedValueRef, valueExtractorRef, transform]);\n\n useLayoutEffect(() => {\n transform();\n }, [valueExtractor, extractedValueRef, transformFn, transform]);\n};\n\nconst SCROLLABLE = ['auto', 'scroll'];\nexport const findScrollContainer = (\n element: HTMLElement\n):\n | { eventsProvider: HTMLElement; infoProvider: HTMLElement; mode: 'element' }\n | { eventsProvider: Window; infoProvider: Element; mode: 'window' } => {\n let parentElement = element.parentElement;\n while (parentElement !== null) {\n const { overflowY } = window.getComputedStyle(parentElement);\n if (SCROLLABLE.includes(overflowY)) {\n return { eventsProvider: parentElement, infoProvider: parentElement, mode: 'element' };\n }\n parentElement = parentElement.parentElement;\n }\n\n return {\n eventsProvider: window,\n infoProvider: document.scrollingElement || document.documentElement,\n mode: 'window',\n };\n};\n\nexport const isDOMRectsEqual = (a: DOMRectReadOnly | null, b: DOMRectReadOnly | null): boolean =>\n a === b || (a !== null && b !== null && a.x === b.x && a.y === b.y && a.height === b.height && a.width === b.width);\n\nexport interface MorphSetup {\n start: DOMRectReadOnly;\n end: DOMRectReadOnly;\n containerStart: DOMRectReadOnly;\n containerEnd: DOMRectReadOnly;\n}\n\nexport type SizeAxis = 'vertical' | 'horizontal' | 'both' | 'auto';\nexport type HorizontalAlign = 'left' | 'right' | 'center';\nexport type VerticalAlign = 'top' | 'bottom' | 'center';\n\nconst MAX_SCALE_RATIO = 2;\nconst AXIS_SIZE_MULTIPLIER = {\n left: 0,\n center: 0.5,\n right: 1,\n top: 0,\n bottom: 1,\n} as const;\n\nexport const calcMorphParams = (\n start: DOMRectReadOnly | null,\n end: DOMRectReadOnly | null,\n sizeAxis: SizeAxis,\n horizontalPositionAlign: HorizontalAlign,\n verticalPositionAlign: VerticalAlign\n): { deltaX: number; deltaY: number; scaleX: number; scaleY: number } => {\n if (start === null || end === null) {\n return {\n deltaX: 0,\n deltaY: 0,\n scaleX: 1,\n scaleY: 1,\n };\n }\n\n let axis = sizeAxis;\n let scaleX = end.width / start.width;\n let scaleY = end.height / start.height;\n\n if (sizeAxis === 'auto') {\n const ratio = Math.max(scaleX, scaleY) / Math.min(scaleX, scaleY);\n if (ratio > MAX_SCALE_RATIO) {\n const absScaleX = scaleX < 1 ? 1 / scaleX : scaleX;\n const absScaleY = scaleY < 1 ? 1 / scaleY : scaleY;\n axis = absScaleX < absScaleY ? 'horizontal' : 'vertical';\n }\n }\n\n scaleX = axis === 'vertical' ? scaleY : scaleX;\n scaleY = axis === 'horizontal' ? scaleX : scaleY;\n\n const startX = start.x + start.width * AXIS_SIZE_MULTIPLIER[horizontalPositionAlign];\n const endX = end.x + end.width * AXIS_SIZE_MULTIPLIER[horizontalPositionAlign];\n const startY = start.y + start.height * AXIS_SIZE_MULTIPLIER[verticalPositionAlign];\n const endY = end.y + end.height * AXIS_SIZE_MULTIPLIER[verticalPositionAlign];\n\n const deltaX = endX - startX;\n const deltaY = endY - startY;\n\n return {\n deltaX,\n deltaY,\n scaleX,\n scaleY,\n };\n};\n\nexport const getRelativeOffset = (scrollAdapter: ScrollAdapter, element: HTMLElement | null): number => {\n if (!scrollAdapter.scrollContainer.current || !element) {\n return 0;\n }\n\n const container = scrollAdapter.scrollContainer.current;\n const containerTop = container === document.documentElement ? 0 : container.getBoundingClientRect().top;\n const scroll = scrollAdapter.getScrollTop();\n scrollAdapter.setScrollTop(0);\n const elementTop = element.getBoundingClientRect().top;\n scrollAdapter.setScrollTop(scroll);\n\n return elementTop - containerTop;\n};\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@hh.ru/magritte-ui-nav-bar",
3
+ "version": "1.0.1",
4
+ "main": "index.js",
5
+ "types": "index.d.ts",
6
+ "sideEffects": [
7
+ "index.css"
8
+ ],
9
+ "scripts": {
10
+ "build": "yarn root:build $(pwd)",
11
+ "build-test-branch": "yarn root:build-test-branch $(pwd)",
12
+ "prepack": "yarn root:prepack $(pwd)",
13
+ "postpublish": "yarn root:postpublish $(pwd)",
14
+ "stylelint-check": "yarn root:stylelint-check $(pwd)",
15
+ "eslint-check": "yarn root:eslint-check $(pwd)",
16
+ "ts-config": "yarn root:ts-config $(pwd)",
17
+ "ts-check": "yarn root:ts-check $(pwd)",
18
+ "test": "yarn root:test $(pwd)",
19
+ "watch": "yarn root:watch $(pwd)"
20
+ },
21
+ "peerDependencies": {
22
+ "classnames": ">=2.3.2",
23
+ "motion": "^12.23.12",
24
+ "react": ">=18.2.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@hh.ru/magritte-common-use-when-font-loaded": "1.0.12",
31
+ "@hh.ru/magritte-design-tokens": "23.2.2",
32
+ "@hh.ru/magritte-internal-custom-scroll": "2.0.0",
33
+ "@hh.ru/magritte-ui-divider": "3.0.6",
34
+ "@hh.ru/magritte-ui-icon": "13.1.0",
35
+ "@hh.ru/magritte-ui-layer": "3.0.4"
36
+ },
37
+ "gitHead": "b01f070fc5e24a45a987ba451165c2f90733a37e"
38
+ }
@@ -0,0 +1,26 @@
1
+ import { type PropsWithChildren, type FC, type ReactNode } from 'react';
2
+ export interface ActionsProps {
3
+ /**
4
+ * Контент левого слота
5
+ */
6
+ left?: ReactNode | ReactNode[];
7
+ /**
8
+ * Контент правого слота
9
+ */
10
+ right?: ReactNode | ReactNode[];
11
+ /**
12
+ * Контент центрального слота
13
+ */
14
+ children?: ReactNode;
15
+ /**
16
+ * Включает центрирование контента центрального слота относительно всего компонента.
17
+ */
18
+ centered?: boolean;
19
+ /**
20
+ * Управляет автоматическим оборачиванием всего контента в компоненты <Morph />.
21
+ * По умолчанию включен, но может быть отключен если требуется более сложное описание анимации
22
+ * чем используется по умолчанию.
23
+ */
24
+ autoMorph?: boolean;
25
+ }
26
+ export declare const Actions: FC<PropsWithChildren<ActionsProps>>;
@@ -0,0 +1,47 @@
1
+ import './../index.css';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { isValidElement, Fragment, Children } from 'react';
4
+ import classNames from 'classnames';
5
+ import { isIconElement } from '@hh.ru/magritte-ui-icon';
6
+ import { EnvironmentFingerprintNode } from './EnvironmentFingerprintNode.js';
7
+ import { Morph } from './Morph.js';
8
+ import { useAnimationStage } from './Stage.js';
9
+ import { s as styles } from '../NavBar-CrD8CEWb.js';
10
+ import '../internal/MetricsProvider.js';
11
+ import '../internal/utils.js';
12
+ import 'motion/react';
13
+ import '../internal/MorphStore.js';
14
+ import '../internal/KeyedSubscriptions.js';
15
+ import '../internal/PaneStore.js';
16
+ import 'motion';
17
+ import '@hh.ru/magritte-ui-divider';
18
+ import '@hh.ru/magritte-ui-layer';
19
+ import '../internal/ProgressiveBlur.js';
20
+ import '../internal/useAnimationRanges.js';
21
+ import '../internal/useBindScrollToAnimationProgress.js';
22
+ import '../internal/useDivider.js';
23
+ import '../internal/useNavBarMetrics.js';
24
+ import '../internal/useResetFocus.js';
25
+ import '../internal/useScrollAdapter.js';
26
+ import '@hh.ru/magritte-internal-custom-scroll';
27
+ import '../internal/useSnapScroll.js';
28
+ import '../internal/useSyncMotionValue.js';
29
+
30
+ const wrapToMorph = (node, isRight) => {
31
+ const arr = isValidElement(node) && node.type === Fragment
32
+ ? Children.toArray(node.props.children)
33
+ : Children.toArray(node);
34
+ return arr.map((node, index) => (jsx(Morph, { className: isIconElement(node) ? styles.actionsIconMorph : '', id: `actions-component-${isRight ? 'right' : 'left'}-slot-item-${isRight ? arr.length - index : index}`, children: node }, index)));
35
+ };
36
+ const Actions = ({ children, left, right, centered = false, autoMorph = true, }) => {
37
+ const stage = useAnimationStage();
38
+ return (jsxs("div", { className: classNames(styles.actionsContainer, {
39
+ [styles.actionsStartStage]: stage === 'start',
40
+ [styles.actionsEndStage]: stage === 'end',
41
+ [styles.actionsOnlyStage]: stage === 'only',
42
+ [styles.actionsNoChildren]: !children,
43
+ }), children: [!!left && (jsxs("div", { className: styles.actionsLeftSlot, children: [jsx(EnvironmentFingerprintNode, { className: styles.actionsSideSlotContent, children: autoMorph && stage !== 'only' ? wrapToMorph(left, false) : left }), centered && (jsx("div", { className: styles.actionsSideSlotContentClone, "aria-hidden": "true", children: right }))] })), (!!children || !left || !right) && (jsx("div", { className: classNames(styles.actionsCenterSlot, { [styles.actionsCenterSlotCentered]: centered }), children: children })), !!right && (jsxs("div", { className: styles.actionsRightSlot, children: [jsx(EnvironmentFingerprintNode, { className: styles.actionsSideSlotContent, children: autoMorph && stage !== 'only' ? wrapToMorph(right, true) : right }), centered && (jsx("div", { className: styles.actionsSideSlotContentClone, "aria-hidden": "true", children: left }))] }))] }));
44
+ };
45
+
46
+ export { Actions };
47
+ //# sourceMappingURL=Actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Actions.js","sources":["../../src/public/Actions.tsx"],"sourcesContent":["import {\n Children,\n Fragment,\n isValidElement,\n type JSXElementConstructor,\n type PropsWithChildren,\n type ReactElement,\n type FC,\n type ReactNode,\n} from 'react';\nimport classNames from 'classnames';\n\nimport { isIconElement } from '@hh.ru/magritte-ui-icon';\nimport { EnvironmentFingerprintNode } from '@hh.ru/magritte-ui-nav-bar/public/EnvironmentFingerprintNode';\nimport { Morph } from '@hh.ru/magritte-ui-nav-bar/public/Morph';\nimport { useAnimationStage } from '@hh.ru/magritte-ui-nav-bar/public/Stage';\n\nimport styles from './nav-bar.less';\n\nexport interface ActionsProps {\n /**\n * Контент левого слота\n */\n left?: ReactNode | ReactNode[];\n /**\n * Контент правого слота\n */\n right?: ReactNode | ReactNode[];\n /**\n * Контент центрального слота\n */\n children?: ReactNode;\n /**\n * Включает центрирование контента центрального слота относительно всего компонента.\n */\n centered?: boolean;\n /**\n * Управляет автоматическим оборачиванием всего контента в компоненты <Morph />.\n * По умолчанию включен, но может быть отключен если требуется более сложное описание анимации\n * чем используется по умолчанию.\n */\n autoMorph?: boolean;\n}\n\nconst wrapToMorph = (node: ReactNode | ReactNode[], isRight: boolean) => {\n const arr =\n isValidElement(node) && node.type === Fragment\n ? Children.toArray(\n (node as ReactElement<PropsWithChildren, JSXElementConstructor<PropsWithChildren>>).props.children\n )\n : Children.toArray(node);\n\n return arr.map((node, index) => (\n <Morph\n className={isIconElement(node) ? styles.actionsIconMorph : ''}\n id={`actions-component-${isRight ? 'right' : 'left'}-slot-item-${isRight ? arr.length - index : index}`}\n key={index}\n >\n {node}\n </Morph>\n ));\n};\n\nexport const Actions: FC<PropsWithChildren<ActionsProps>> = ({\n children,\n left,\n right,\n centered = false,\n autoMorph = true,\n}) => {\n const stage = useAnimationStage();\n return (\n <div\n className={classNames(styles.actionsContainer, {\n [styles.actionsStartStage]: stage === 'start',\n [styles.actionsEndStage]: stage === 'end',\n [styles.actionsOnlyStage]: stage === 'only',\n [styles.actionsNoChildren]: !children,\n })}\n >\n {!!left && (\n <div className={styles.actionsLeftSlot}>\n <EnvironmentFingerprintNode className={styles.actionsSideSlotContent}>\n {autoMorph && stage !== 'only' ? wrapToMorph(left, false) : left}\n </EnvironmentFingerprintNode>\n {centered && (\n <div className={styles.actionsSideSlotContentClone} aria-hidden=\"true\">\n {right}\n </div>\n )}\n </div>\n )}\n\n {(!!children || !left || !right) && (\n <div className={classNames(styles.actionsCenterSlot, { [styles.actionsCenterSlotCentered]: centered })}>\n {children}\n </div>\n )}\n\n {!!right && (\n <div className={styles.actionsRightSlot}>\n <EnvironmentFingerprintNode className={styles.actionsSideSlotContent}>\n {autoMorph && stage !== 'only' ? wrapToMorph(right, true) : right}\n </EnvironmentFingerprintNode>\n {centered && (\n <div className={styles.actionsSideSlotContentClone} aria-hidden=\"true\">\n {left}\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type HTMLAttributes, type PropsWithChildren, type FC } from 'react';
2
+ export type EnvironmentFingerprintNodeProps = PropsWithChildren<HTMLAttributes<HTMLDivElement>>;
3
+ export declare const EnvironmentFingerprintNode: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
4
+ children?: import("react").ReactNode | undefined;
5
+ } & import("react").RefAttributes<HTMLDivElement>>;
6
+ export declare const EnvironmentFingerprintProvider: FC<PropsWithChildren>;
7
+ export declare const useEnvironmentFingerprint: (onChange: VoidFunction) => void;
@@ -0,0 +1,70 @@
1
+ import './../index.css';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { createContext, forwardRef, useContext, useRef, useCallback, useLayoutEffect } from 'react';
4
+ import { useMeasureFingerprint } from '../internal/MetricsProvider.js';
5
+ import { isDOMRectsEqual, useInitOnce, useActualRef, scheduleMicro } from '../internal/utils.js';
6
+
7
+ const FingerprintContext = createContext(null);
8
+ const EnvironmentFingerprintNode = forwardRef(({ children, ...rest }, ref) => {
9
+ const ctx = useContext(FingerprintContext);
10
+ if (!ctx) {
11
+ throw new Error('EnvironmentFingerprintNode must be used inside EnvironmentFingerprintProvider');
12
+ }
13
+ const rootRef = useRef();
14
+ const rootRefCallback = useCallback((element) => {
15
+ rootRef.current = element;
16
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
17
+ !!ref && (typeof ref === 'function' ? ref(element) : (ref.current = element));
18
+ }, [ref]);
19
+ const rectRef = useRef(null);
20
+ useMeasureFingerprint(rootRef, (rect) => {
21
+ if (!isDOMRectsEqual(rect, rectRef.current)) {
22
+ rectRef.current = rect;
23
+ ctx.notify();
24
+ }
25
+ });
26
+ return (jsx("div", { ref: rootRefCallback, ...rest, children: children }));
27
+ });
28
+ EnvironmentFingerprintNode.displayName = 'EnvironmentFingerprintNode';
29
+ const EnvironmentFingerprintProvider = ({ children }) => {
30
+ const contextValue = useInitOnce(() => {
31
+ const callbacks = new Set();
32
+ // Ожидаем что изменения по фингерпринтам могут прилетать не чаще одного раза за тик
33
+ // (потому что они генерятся только ResizeObserver). При этом элементов может измениться сразу несколько,
34
+ // каждый коллбек нам нужно вызвать только один раз. Есть два варианта этого добиться:
35
+ // - обернуть вызов коллбеков в schedule, но тогда они вызовутся не синхронно, а в следующей микротаске
36
+ // - добавить флаг который предотвращает повторную нотификацию в тике и вызвать коллбеки синхронно
37
+ // Синхронный вызов коллбеков позволяет эффективнее снимать метрики в одном цикле измерений,
38
+ // поэтому выбран этот вариант - синхронизация через флаг.
39
+ let notified = false;
40
+ const resetNotified = scheduleMicro(() => (notified = false));
41
+ return {
42
+ registerCallback: (cb) => {
43
+ callbacks.add(cb);
44
+ return () => callbacks.delete(cb);
45
+ },
46
+ notify: () => {
47
+ if (notified) {
48
+ return;
49
+ }
50
+ notified = true;
51
+ resetNotified();
52
+ callbacks.forEach((cb) => cb());
53
+ },
54
+ };
55
+ });
56
+ return jsx(FingerprintContext.Provider, { value: contextValue, children: children });
57
+ };
58
+ EnvironmentFingerprintProvider.displayName = 'EnvironmentFingerprintProvider';
59
+ const useEnvironmentFingerprint = (onChange) => {
60
+ const ctx = useContext(FingerprintContext);
61
+ const onChangeRef = useActualRef(onChange);
62
+ const callback = useInitOnce(() => () => onChangeRef.current());
63
+ if (!ctx) {
64
+ throw new Error('useEnvironmentFingerprint must be used inside EnvironmentFingerprintProvider');
65
+ }
66
+ useLayoutEffect(() => ctx.registerCallback(callback), [ctx, callback]);
67
+ };
68
+
69
+ export { EnvironmentFingerprintNode, EnvironmentFingerprintProvider, useEnvironmentFingerprint };
70
+ //# sourceMappingURL=EnvironmentFingerprintNode.js.map