@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,60 @@
1
+ import { type MotionValue } from 'motion';
2
+ import { KeyedSubscriptions } from '@hh.ru/magritte-ui-nav-bar/internal/KeyedSubscriptions';
3
+ export interface PaneData {
4
+ id: string;
5
+ foldable?: boolean;
6
+ motionValue: MotionValue<number>;
7
+ startHeight?: number;
8
+ endHeight?: number;
9
+ top?: number;
10
+ }
11
+ /**
12
+ * Хранилище состояния панели с поддержкой подписок на изменения.
13
+ *
14
+ * Наследует всю подписочную логику от {@link KeyedSubscriptions}, а именно:
15
+ * - `onChange(key, cb)` — подписка на изменения конкретного ключа состояния;
16
+ * - `onDestroy(cb)` — подписка на уничтожение;
17
+ * - батчинг уведомлений (несколько изменений за один цикл объединяются);
18
+ * - дедупликация коллбеков (один коллбек, подписанный на разные ключи, вызовется один раз).
19
+ *
20
+ * Собственная ответственность класса:
21
+ * - Хранение состояния панели в `PaneData`.
22
+ * - Применение частичных обновлений через метод `set(...)`:
23
+ * - сравнивает новые значения с текущими;
24
+ * - обновляет только изменившиеся поля;
25
+ * - помечает изменённые ключи (`markChanged`) и инициирует рассылку (`notify`).
26
+ * - Получение текущего значения по ключу через `get(...)`.
27
+ *
28
+ * Таким образом, `PaneStore` объединяет модель данных панели
29
+ * с реактивными подписками на её изменения.
30
+ */
31
+ export declare class PaneStore extends KeyedSubscriptions<keyof PaneData> {
32
+ data: PaneData;
33
+ /**
34
+ * @param data Начальное состояние панели.
35
+ */
36
+ constructor(data: PaneData);
37
+ /**
38
+ * Частичное обновление состояния панели.
39
+ * Для каждого ключа из `patch` выполняется сравнение и,
40
+ * если значение изменилось, оно записывается в `data`,
41
+ * а ключ помечается как изменённый.
42
+ * После всех обновлений вызывается `notify()` для рассылки подписчикам.
43
+ *
44
+ * @example
45
+ * controller.set({ foldable: true, startHeight: 200 });
46
+ */
47
+ set<K extends keyof PaneData>(patch: Partial<Pick<PaneData, K>>): void;
48
+ /**
49
+ * Получить текущее значение поля состояния.
50
+ *
51
+ * @param key Ключ из {@link PaneData}.
52
+ * @returns Текущее значение по указанному ключу.
53
+ *
54
+ * @example
55
+ * const height = controller.get("startHeight");
56
+ */
57
+ get<K extends keyof PaneData>(key: K): PaneData[K];
58
+ }
59
+ export declare const PaneContext: import("react").Context<PaneStore | null>;
60
+ export declare const usePaneStore: () => PaneStore;
@@ -0,0 +1,102 @@
1
+ import './../index.css';
2
+ import { createContext, useContext, useId, useLayoutEffect } from 'react';
3
+ import { motionValue } from 'motion';
4
+ import { KeyedSubscriptions } from './KeyedSubscriptions.js';
5
+ import { useInitOnce } from './utils.js';
6
+ import { u as useNavBarContext } from '../NavBar-CrD8CEWb.js';
7
+ import 'react/jsx-runtime';
8
+ import 'classnames';
9
+ import 'motion/react';
10
+ import '@hh.ru/magritte-ui-divider';
11
+ import '@hh.ru/magritte-ui-layer';
12
+ import './MetricsProvider.js';
13
+ import './ProgressiveBlur.js';
14
+ import './useAnimationRanges.js';
15
+ import './useBindScrollToAnimationProgress.js';
16
+ import './useDivider.js';
17
+ import './useNavBarMetrics.js';
18
+ import './useResetFocus.js';
19
+ import './useScrollAdapter.js';
20
+ import '@hh.ru/magritte-internal-custom-scroll';
21
+ import './useSnapScroll.js';
22
+ import './useSyncMotionValue.js';
23
+
24
+ /**
25
+ * Хранилище состояния панели с поддержкой подписок на изменения.
26
+ *
27
+ * Наследует всю подписочную логику от {@link KeyedSubscriptions}, а именно:
28
+ * - `onChange(key, cb)` — подписка на изменения конкретного ключа состояния;
29
+ * - `onDestroy(cb)` — подписка на уничтожение;
30
+ * - батчинг уведомлений (несколько изменений за один цикл объединяются);
31
+ * - дедупликация коллбеков (один коллбек, подписанный на разные ключи, вызовется один раз).
32
+ *
33
+ * Собственная ответственность класса:
34
+ * - Хранение состояния панели в `PaneData`.
35
+ * - Применение частичных обновлений через метод `set(...)`:
36
+ * - сравнивает новые значения с текущими;
37
+ * - обновляет только изменившиеся поля;
38
+ * - помечает изменённые ключи (`markChanged`) и инициирует рассылку (`notify`).
39
+ * - Получение текущего значения по ключу через `get(...)`.
40
+ *
41
+ * Таким образом, `PaneStore` объединяет модель данных панели
42
+ * с реактивными подписками на её изменения.
43
+ */
44
+ class PaneStore extends KeyedSubscriptions {
45
+ data;
46
+ /**
47
+ * @param data Начальное состояние панели.
48
+ */
49
+ constructor(data) {
50
+ super();
51
+ this.data = data;
52
+ }
53
+ /**
54
+ * Частичное обновление состояния панели.
55
+ * Для каждого ключа из `patch` выполняется сравнение и,
56
+ * если значение изменилось, оно записывается в `data`,
57
+ * а ключ помечается как изменённый.
58
+ * После всех обновлений вызывается `notify()` для рассылки подписчикам.
59
+ *
60
+ * @example
61
+ * controller.set({ foldable: true, startHeight: 200 });
62
+ */
63
+ set(patch) {
64
+ Object.keys(patch).forEach((key) => {
65
+ const next = patch[key];
66
+ if (next !== this.data[key]) {
67
+ this.data[key] = next;
68
+ this.markChanged(key);
69
+ }
70
+ });
71
+ this.notify();
72
+ }
73
+ /**
74
+ * Получить текущее значение поля состояния.
75
+ *
76
+ * @param key Ключ из {@link PaneData}.
77
+ * @returns Текущее значение по указанному ключу.
78
+ *
79
+ * @example
80
+ * const height = controller.get("startHeight");
81
+ */
82
+ get(key) {
83
+ return this.data[key];
84
+ }
85
+ }
86
+ const PaneContext = createContext(null);
87
+ const usePaneStore = () => {
88
+ const contextStore = useContext(PaneContext);
89
+ const id = useId();
90
+ const registerPaneStore = useNavBarContext();
91
+ const paneStore = useInitOnce(() => contextStore ?? new PaneStore({ motionValue: motionValue(0), id }));
92
+ useLayoutEffect(() => {
93
+ if (!registerPaneStore || contextStore !== null) {
94
+ return void 0;
95
+ }
96
+ return registerPaneStore(contextStore ?? paneStore);
97
+ }, [contextStore, paneStore, registerPaneStore]);
98
+ return contextStore ?? paneStore;
99
+ };
100
+
101
+ export { PaneContext, PaneStore, usePaneStore };
102
+ //# sourceMappingURL=PaneStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaneStore.js","sources":["../../src/internal/PaneStore.ts"],"sourcesContent":["import { createContext, useContext, useId, useLayoutEffect } from 'react';\nimport { motionValue, type MotionValue } from 'motion';\n\nimport { KeyedSubscriptions } from '@hh.ru/magritte-ui-nav-bar/internal/KeyedSubscriptions';\nimport { useInitOnce } from '@hh.ru/magritte-ui-nav-bar/internal/utils';\nimport { useNavBarContext } from '@hh.ru/magritte-ui-nav-bar/public/NavBar';\n\nexport interface PaneData {\n id: string;\n foldable?: boolean;\n motionValue: MotionValue<number>;\n startHeight?: number;\n endHeight?: number;\n top?: number;\n}\n\n/**\n * Хранилище состояния панели с поддержкой подписок на изменения.\n *\n * Наследует всю подписочную логику от {@link KeyedSubscriptions}, а именно:\n * - `onChange(key, cb)` — подписка на изменения конкретного ключа состояния;\n * - `onDestroy(cb)` — подписка на уничтожение;\n * - батчинг уведомлений (несколько изменений за один цикл объединяются);\n * - дедупликация коллбеков (один коллбек, подписанный на разные ключи, вызовется один раз).\n *\n * Собственная ответственность класса:\n * - Хранение состояния панели в `PaneData`.\n * - Применение частичных обновлений через метод `set(...)`:\n * - сравнивает новые значения с текущими;\n * - обновляет только изменившиеся поля;\n * - помечает изменённые ключи (`markChanged`) и инициирует рассылку (`notify`).\n * - Получение текущего значения по ключу через `get(...)`.\n *\n * Таким образом, `PaneStore` объединяет модель данных панели\n * с реактивными подписками на её изменения.\n */\nexport class PaneStore extends KeyedSubscriptions<keyof PaneData> {\n data: PaneData;\n\n /**\n * @param data Начальное состояние панели.\n */\n constructor(data: PaneData) {\n super();\n this.data = data;\n }\n\n /**\n * Частичное обновление состояния панели.\n * Для каждого ключа из `patch` выполняется сравнение и,\n * если значение изменилось, оно записывается в `data`,\n * а ключ помечается как изменённый.\n * После всех обновлений вызывается `notify()` для рассылки подписчикам.\n *\n * @example\n * controller.set({ foldable: true, startHeight: 200 });\n */\n set<K extends keyof PaneData>(patch: Partial<Pick<PaneData, K>>): void {\n (Object.keys(patch) as K[]).forEach((key) => {\n const next = patch[key];\n if (next !== this.data[key]) {\n this.data[key] = next as PaneData[K];\n this.markChanged(key);\n }\n });\n this.notify();\n }\n\n /**\n * Получить текущее значение поля состояния.\n *\n * @param key Ключ из {@link PaneData}.\n * @returns Текущее значение по указанному ключу.\n *\n * @example\n * const height = controller.get(\"startHeight\");\n */\n get<K extends keyof PaneData>(key: K): PaneData[K] {\n return this.data[key];\n }\n}\n\nexport const PaneContext = createContext<PaneStore | null>(null);\nexport const usePaneStore = (): PaneStore => {\n const contextStore = useContext(PaneContext);\n const id = useId();\n const registerPaneStore = useNavBarContext();\n const paneStore = useInitOnce(() => contextStore ?? new PaneStore({ motionValue: motionValue(0), id }));\n useLayoutEffect(() => {\n if (!registerPaneStore || contextStore !== null) {\n return void 0;\n }\n\n return registerPaneStore(contextStore ?? paneStore);\n }, [contextStore, paneStore, registerPaneStore]);\n return contextStore ?? paneStore;\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgBA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,MAAO,SAAU,SAAQ,kBAAkC,CAAA;AAC7D,IAAA,IAAI,CAAW;AAEf;;AAEG;AACH,IAAA,WAAA,CAAY,IAAc,EAAA;AACtB,QAAA,KAAK,EAAE,CAAC;AACR,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;KACpB;AAED;;;;;;;;;AASG;AACH,IAAA,GAAG,CAA2B,KAAiC,EAAA;QAC1D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAS,CAAC,OAAO,CAAC,CAAC,GAAG,KAAI;AACxC,YAAA,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YACxB,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;AACzB,gBAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAmB,CAAC;AACrC,gBAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;aACzB;AACL,SAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,CAAC;KACjB;AAED;;;;;;;;AAQG;AACH,IAAA,GAAG,CAA2B,GAAM,EAAA;AAChC,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;KACzB;AACJ,CAAA;MAEY,WAAW,GAAG,aAAa,CAAmB,IAAI,EAAE;AAC1D,MAAM,YAAY,GAAG,MAAgB;AACxC,IAAA,MAAM,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;AAC7C,IAAA,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;AACnB,IAAA,MAAM,iBAAiB,GAAG,gBAAgB,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,YAAY,IAAI,IAAI,SAAS,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACxG,eAAe,CAAC,MAAK;AACjB,QAAA,IAAI,CAAC,iBAAiB,IAAI,YAAY,KAAK,IAAI,EAAE;YAC7C,OAAO,KAAK,CAAC,CAAC;SACjB;AAED,QAAA,OAAO,iBAAiB,CAAC,YAAY,IAAI,SAAS,CAAC,CAAC;KACvD,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACjD,OAAO,YAAY,IAAI,SAAS,CAAC;AACrC;;;;"}
@@ -0,0 +1,7 @@
1
+ import { type FC } from 'react';
2
+ import { type HTMLMotionProps } from 'motion/react';
3
+ export declare const ProgressiveBlur: FC<{
4
+ detail: number;
5
+ blurAmount: number;
6
+ blurHeight: number;
7
+ } & HTMLMotionProps<'div'>>;
@@ -0,0 +1,43 @@
1
+ import './../index.css';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { useMemo } from 'react';
4
+ import { motion } from 'motion/react';
5
+
6
+ const colorByStopIndex = ['rgba(0, 0, 0, 0)', 'rgb(0, 0, 0)', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 0)'];
7
+ const ProgressiveBlur = ({ detail, blurAmount, blurHeight, ...rest }) => {
8
+ const [maskImageParams, blurAmountParams] = useMemo(() => {
9
+ const maskParams = [];
10
+ const blurAmountParams = [];
11
+ const stops = [0];
12
+ const stepSize = blurHeight / detail;
13
+ for (let i = 0; i < detail; i++) {
14
+ stops.push(stepSize * (i + 1));
15
+ if (stops.length > 4) {
16
+ stops.shift();
17
+ }
18
+ maskParams.push(stops
19
+ .map((percent, index) => {
20
+ const shift = colorByStopIndex.length - stops.length;
21
+ const color = colorByStopIndex[shift + index];
22
+ return `${color} ${percent.toFixed(3)}%`;
23
+ })
24
+ .join(', '));
25
+ }
26
+ let lastBlur = blurAmount;
27
+ for (let i = maskParams.length; i > 0; i--) {
28
+ blurAmountParams[i - 1] = lastBlur;
29
+ lastBlur *= 2;
30
+ }
31
+ return [maskParams, blurAmountParams];
32
+ }, [detail, blurAmount, blurHeight]);
33
+ return (jsx(motion.div, { ...rest, children: maskImageParams.map((maskParams, index) => (jsx("div", { style: {
34
+ position: 'absolute',
35
+ inset: 0,
36
+ zIndex: maskImageParams.length - index,
37
+ backdropFilter: `blur(${blurAmountParams[index]}px)`,
38
+ mask: `linear-gradient(${maskParams})`,
39
+ } }, index))) }));
40
+ };
41
+
42
+ export { ProgressiveBlur };
43
+ //# sourceMappingURL=ProgressiveBlur.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressiveBlur.js","sources":["../../src/internal/ProgressiveBlur.tsx"],"sourcesContent":["import { useMemo, type FC } from 'react';\nimport { type HTMLMotionProps, motion } from 'motion/react';\n\nconst colorByStopIndex = ['rgba(0, 0, 0, 0)', 'rgb(0, 0, 0)', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 0)'];\n\nexport const ProgressiveBlur: FC<\n {\n detail: number;\n blurAmount: number;\n blurHeight: number;\n } & HTMLMotionProps<'div'>\n> = ({ detail, blurAmount, blurHeight, ...rest }) => {\n const [maskImageParams, blurAmountParams] = useMemo(() => {\n const maskParams: string[] = [];\n const blurAmountParams: number[] = [];\n const stops: number[] = [0];\n const stepSize = blurHeight / detail;\n for (let i = 0; i < detail; i++) {\n stops.push(stepSize * (i + 1));\n if (stops.length > 4) {\n stops.shift();\n }\n maskParams.push(\n stops\n .map((percent, index) => {\n const shift = colorByStopIndex.length - stops.length;\n const color = colorByStopIndex[shift + index];\n return `${color} ${percent.toFixed(3)}%`;\n })\n .join(', ')\n );\n }\n let lastBlur = blurAmount;\n for (let i = maskParams.length; i > 0; i--) {\n blurAmountParams[i - 1] = lastBlur;\n lastBlur *= 2;\n }\n\n return [maskParams, blurAmountParams];\n }, [detail, blurAmount, blurHeight]);\n\n return (\n <motion.div {...rest}>\n {maskImageParams.map((maskParams, index) => (\n <div\n key={index}\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: maskImageParams.length - index,\n backdropFilter: `blur(${blurAmountParams[index]}px)`,\n mask: `linear-gradient(${maskParams})`,\n }}\n />\n ))}\n </motion.div>\n );\n};\n"],"names":["_jsx"],"mappings":";;;;AAGA,MAAM,gBAAgB,GAAG,CAAC,kBAAkB,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,CAAC,CAAC;AAErF,MAAA,eAAe,GAMxB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,KAAI;IAChD,MAAM,CAAC,eAAe,EAAE,gBAAgB,CAAC,GAAG,OAAO,CAAC,MAAK;QACrD,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,MAAM,gBAAgB,GAAa,EAAE,CAAC;AACtC,QAAA,MAAM,KAAK,GAAa,CAAC,CAAC,CAAC,CAAC;AAC5B,QAAA,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AACrC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE;YAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAC/B,YAAA,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;gBAClB,KAAK,CAAC,KAAK,EAAE,CAAC;aACjB;YACD,UAAU,CAAC,IAAI,CACX,KAAK;AACA,iBAAA,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,KAAI;gBACpB,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACrD,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;gBAC9C,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA,CAAA,CAAG,CAAC;AAC7C,aAAC,CAAC;AACD,iBAAA,IAAI,CAAC,IAAI,CAAC,CAClB,CAAC;SACL;QACD,IAAI,QAAQ,GAAG,UAAU,CAAC;AAC1B,QAAA,KAAK,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AACxC,YAAA,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;YACnC,QAAQ,IAAI,CAAC,CAAC;SACjB;AAED,QAAA,OAAO,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;KACzC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAErC,QACIA,IAAC,MAAM,CAAC,GAAG,EAAK,EAAA,GAAA,IAAI,EACf,QAAA,EAAA,eAAe,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,KAAK,MACnCA,GAEI,CAAA,KAAA,EAAA,EAAA,KAAK,EAAE;AACH,gBAAA,QAAQ,EAAE,UAAU;AACpB,gBAAA,KAAK,EAAE,CAAC;AACR,gBAAA,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,KAAK;AACtC,gBAAA,cAAc,EAAE,CAAQ,KAAA,EAAA,gBAAgB,CAAC,KAAK,CAAC,CAAK,GAAA,CAAA;gBACpD,IAAI,EAAE,CAAmB,gBAAA,EAAA,UAAU,CAAG,CAAA,CAAA;AACzC,aAAA,EAAA,EAPI,KAAK,CAQZ,CACL,CAAC,EAAA,CACO,EACf;AACN;;;;"}
@@ -0,0 +1,38 @@
1
+ import { type PaneStore } from '@hh.ru/magritte-ui-nav-bar/internal/PaneStore';
2
+ /**
3
+ * Рассчитывает для панелей диапазоны скролла, в течение которого должна проигрываться их анимация.
4
+ *
5
+ * - Каждая панель принадлежит группе, каждая новая группа начинается с foldable панели, и включет её
6
+ * - Для группы суммируется изменение высот её панелей:
7
+ * - для foldable панелей: diff = startHeight (они схлопываются до 0),
8
+ * - для остальных: diff = startHeight − (endHeight ?? startHeight).
9
+ * - Диапазон скролла группы равен [cum, cum + sumDiff], где `cum` — суммарное изменение высот всех предыдущих групп.
10
+ * - Всем панелям группы присваивается один и тот же диапазон.
11
+ *
12
+ * Вход:
13
+ * - Массив панелей в произвольном порядке; внутри функция сортирует их по `top` по убыванию,
14
+ * чтобы идти от нижних к верхним.
15
+ *
16
+ * Выход:
17
+ * - Массив той же длины и в том же порядке, что и `data`.
18
+ * Для каждой панели — кортеж `[start, end]` в пикселях, либо `null`, если диапазон не вычислен.
19
+ *
20
+ * Ожидания по данным:
21
+ * - `id` уникален (используется как ключ).
22
+ * - `startHeight` задан у всех панелей.
23
+ * - `endHeight` опционален (если не задан, изменение высоты для не-foldable панелей считается 0).
24
+ * - `top` задан для корректной сортировки.
25
+ *
26
+ * @param {PaneData[]} data
27
+ * Список панелей. Минимально используемые поля:
28
+ * - id: string
29
+ * - top?: number
30
+ * - foldable?: boolean
31
+ * - startHeight?: number
32
+ * - endHeight?: number
33
+ *
34
+ * @returns {Array<[number, number] | null>}
35
+ * Диапазоны скролла для анимации каждой панели в исходном порядке `data`.
36
+ */
37
+ export type AnimationRanges = Map<string, [number, number]>;
38
+ export declare const useAnimationRanges: (paneRegistry: Set<PaneStore>) => [() => AnimationRanges, VoidFunction];
@@ -0,0 +1,52 @@
1
+ import './../index.css';
2
+ import { useRef } from 'react';
3
+ import { useActualRef, useInitOnce } from './utils.js';
4
+
5
+ const useAnimationRanges = (paneRegistry) => {
6
+ const animationRangesRef = useRef(null);
7
+ const paneRegistryRef = useActualRef(paneRegistry);
8
+ const getAnimationRanges = useInitOnce(() => () => {
9
+ if (!animationRangesRef.current) {
10
+ // Идём снизу вверх: сортируем по top убыванием.
11
+ const panesData = [...paneRegistryRef.current.values()]
12
+ .map((pane) => pane.data)
13
+ .sort((a, b) => (b.top ?? 0) - (a.top ?? 0));
14
+ const scrollRangesById = new Map();
15
+ let groupStartIdx = 0; // индекс последней встреченной foldable (начало текущей группы)
16
+ let totalDuration = 0; // суммарная длительность (в пикселях скролла) всех ранее обработанных групп
17
+ let groupDuration = 0; // суммарная длительность (в пикселях скролла) текущей группы
18
+ for (let i = 0; i <= panesData.length; i++) {
19
+ const pane = panesData[i] ?? null;
20
+ const isLastPane = pane === null;
21
+ const isGroupEnd = isLastPane || pane.foldable;
22
+ // Новая foldable или последняя панель -> завершаем обработку текущей группы, и сохраняем
23
+ // диапазон если известно место ее начала
24
+ if (isGroupEnd && groupStartIdx !== null) {
25
+ const range = [totalDuration, totalDuration + groupDuration];
26
+ for (let j = groupStartIdx; j < i; j++) {
27
+ scrollRangesById.set(panesData[j].id, range);
28
+ }
29
+ totalDuration += groupDuration;
30
+ groupDuration = 0;
31
+ }
32
+ if (!isLastPane) {
33
+ if (pane.foldable) {
34
+ groupStartIdx = i;
35
+ }
36
+ // Дельта высоты панели: вклад в длительность текущей группы
37
+ const deltaHeight = pane.foldable
38
+ ? (pane.startHeight ?? 0) // foldable схлопывается до 0
39
+ : (pane.startHeight ?? 0) - (pane.endHeight ?? pane.startHeight ?? 0); // иначе разница start -> end
40
+ groupDuration += deltaHeight;
41
+ }
42
+ }
43
+ animationRangesRef.current = scrollRangesById;
44
+ }
45
+ return animationRangesRef.current;
46
+ });
47
+ const invalidateAnimationRangesCache = useInitOnce(() => () => (animationRangesRef.current = null));
48
+ return [getAnimationRanges, invalidateAnimationRangesCache];
49
+ };
50
+
51
+ export { useAnimationRanges };
52
+ //# sourceMappingURL=useAnimationRanges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnimationRanges.js","sources":["../../src/internal/useAnimationRanges.ts"],"sourcesContent":["import { type MutableRefObject, useRef } from 'react';\n\nimport { type PaneStore } from '@hh.ru/magritte-ui-nav-bar/internal/PaneStore';\nimport { useActualRef, useInitOnce } from '@hh.ru/magritte-ui-nav-bar/internal/utils';\n\n/**\n * Рассчитывает для панелей диапазоны скролла, в течение которого должна проигрываться их анимация.\n *\n * - Каждая панель принадлежит группе, каждая новая группа начинается с foldable панели, и включет её\n * - Для группы суммируется изменение высот её панелей:\n * - для foldable панелей: diff = startHeight (они схлопываются до 0),\n * - для остальных: diff = startHeight − (endHeight ?? startHeight).\n * - Диапазон скролла группы равен [cum, cum + sumDiff], где `cum` — суммарное изменение высот всех предыдущих групп.\n * - Всем панелям группы присваивается один и тот же диапазон.\n *\n * Вход:\n * - Массив панелей в произвольном порядке; внутри функция сортирует их по `top` по убыванию,\n * чтобы идти от нижних к верхним.\n *\n * Выход:\n * - Массив той же длины и в том же порядке, что и `data`.\n * Для каждой панели — кортеж `[start, end]` в пикселях, либо `null`, если диапазон не вычислен.\n *\n * Ожидания по данным:\n * - `id` уникален (используется как ключ).\n * - `startHeight` задан у всех панелей.\n * - `endHeight` опционален (если не задан, изменение высоты для не-foldable панелей считается 0).\n * - `top` задан для корректной сортировки.\n *\n * @param {PaneData[]} data\n * Список панелей. Минимально используемые поля:\n * - id: string\n * - top?: number\n * - foldable?: boolean\n * - startHeight?: number\n * - endHeight?: number\n *\n * @returns {Array<[number, number] | null>}\n * Диапазоны скролла для анимации каждой панели в исходном порядке `data`.\n */\nexport type AnimationRanges = Map<string, [number, number]>;\nexport const useAnimationRanges = (paneRegistry: Set<PaneStore>): [() => AnimationRanges, VoidFunction] => {\n const animationRangesRef: MutableRefObject<AnimationRanges | null> = useRef(null);\n const paneRegistryRef = useActualRef(paneRegistry);\n\n const getAnimationRanges = useInitOnce(() => () => {\n if (!animationRangesRef.current) {\n // Идём снизу вверх: сортируем по top убыванием.\n const panesData = [...paneRegistryRef.current.values()]\n .map((pane) => pane.data)\n .sort((a, b) => (b.top ?? 0) - (a.top ?? 0));\n\n const scrollRangesById = new Map<string, [number, number]>();\n let groupStartIdx: number | null = 0; // индекс последней встреченной foldable (начало текущей группы)\n let totalDuration = 0; // суммарная длительность (в пикселях скролла) всех ранее обработанных групп\n let groupDuration = 0; // суммарная длительность (в пикселях скролла) текущей группы\n\n for (let i = 0; i <= panesData.length; i++) {\n const pane = panesData[i] ?? null;\n const isLastPane = pane === null;\n const isGroupEnd = isLastPane || pane.foldable;\n // Новая foldable или последняя панель -> завершаем обработку текущей группы, и сохраняем\n // диапазон если известно место ее начала\n if (isGroupEnd && groupStartIdx !== null) {\n const range: [number, number] = [totalDuration, totalDuration + groupDuration];\n for (let j = groupStartIdx; j < i; j++) {\n scrollRangesById.set(panesData[j].id, range);\n }\n totalDuration += groupDuration;\n groupDuration = 0;\n }\n\n if (!isLastPane) {\n if (pane.foldable) {\n groupStartIdx = i;\n }\n\n // Дельта высоты панели: вклад в длительность текущей группы\n const deltaHeight = pane.foldable\n ? (pane.startHeight ?? 0) // foldable схлопывается до 0\n : (pane.startHeight ?? 0) - (pane.endHeight ?? pane.startHeight ?? 0); // иначе разница start -> end\n groupDuration += deltaHeight;\n }\n }\n animationRangesRef.current = scrollRangesById;\n }\n return animationRangesRef.current;\n });\n\n const invalidateAnimationRangesCache = useInitOnce(() => () => (animationRangesRef.current = null));\n\n return [getAnimationRanges, invalidateAnimationRangesCache];\n};\n"],"names":[],"mappings":";;;AAyCa,MAAA,kBAAkB,GAAG,CAAC,YAA4B,KAA2C;AACtG,IAAA,MAAM,kBAAkB,GAA6C,MAAM,CAAC,IAAI,CAAC,CAAC;AAClF,IAAA,MAAM,eAAe,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAEnD,MAAM,kBAAkB,GAAG,WAAW,CAAC,MAAM,MAAK;AAC9C,QAAA,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE;;YAE7B,MAAM,SAAS,GAAG,CAAC,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;iBAClD,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;iBACxB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAEjD,YAAA,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA4B,CAAC;AAC7D,YAAA,IAAI,aAAa,GAAkB,CAAC,CAAC;AACrC,YAAA,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB,YAAA,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB,YAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBACxC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAClC,gBAAA,MAAM,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;AACjC,gBAAA,MAAM,UAAU,GAAG,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC;;;AAG/C,gBAAA,IAAI,UAAU,IAAI,aAAa,KAAK,IAAI,EAAE;oBACtC,MAAM,KAAK,GAAqB,CAAC,aAAa,EAAE,aAAa,GAAG,aAAa,CAAC,CAAC;AAC/E,oBAAA,KAAK,IAAI,CAAC,GAAG,aAAa,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AACpC,wBAAA,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;qBAChD;oBACD,aAAa,IAAI,aAAa,CAAC;oBAC/B,aAAa,GAAG,CAAC,CAAC;iBACrB;gBAED,IAAI,CAAC,UAAU,EAAE;AACb,oBAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;wBACf,aAAa,GAAG,CAAC,CAAC;qBACrB;;AAGD,oBAAA,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ;2BAC1B,IAAI,CAAC,WAAW,IAAI,CAAC;0BACtB,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;oBAC1E,aAAa,IAAI,WAAW,CAAC;iBAChC;aACJ;AACD,YAAA,kBAAkB,CAAC,OAAO,GAAG,gBAAgB,CAAC;SACjD;QACD,OAAO,kBAAkB,CAAC,OAAO,CAAC;AACtC,KAAC,CAAC,CAAC;AAEH,IAAA,MAAM,8BAA8B,GAAG,WAAW,CAAC,MAAM,OAAO,kBAAkB,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;AAEpG,IAAA,OAAO,CAAC,kBAAkB,EAAE,8BAA8B,CAAC,CAAC;AAChE;;;;"}
@@ -0,0 +1,9 @@
1
+ import { type RefObject } from 'react';
2
+ import { type MotionValue } from 'motion';
3
+ import { type AnimationRanges } from '@hh.ru/magritte-ui-nav-bar/internal/useAnimationRanges';
4
+ import { type NavBarMetrics } from '@hh.ru/magritte-ui-nav-bar/internal/useNavBarMetrics';
5
+ import { type ScrollAdapter } from '@hh.ru/magritte-ui-nav-bar/internal/useScrollAdapter';
6
+ export declare const useBindScrollToAnimationProgress: (scrollPosition: MotionValue<number>, getNavBarMetrics: () => NavBarMetrics, getAnimationRanges: () => AnimationRanges, scrollAdapter: ScrollAdapter, startTriggerPosition: "start" | "full-area" | RefObject<HTMLElement>, endTriggerPosition?: RefObject<HTMLElement>) => [VoidFunction, () => {
7
+ top: number;
8
+ bottom: number;
9
+ }, MotionValue<number>];
@@ -0,0 +1,82 @@
1
+ import './../index.css';
2
+ import { useRef } from 'react';
3
+ import { useMotionValue } from 'motion/react';
4
+ import { useActualRef, useInitOnce, getRelativeOffset, remap, clamp } from './utils.js';
5
+
6
+ const createScrollToAnimationProgressMapFn = (getNavBarMetrics, scrollAdapter, animationRanges, startTriggerPosition, endTriggerPosition) => {
7
+ const maxScrollTop = scrollAdapter.getMaxScrollTop();
8
+ const navBarMetrics = getNavBarMetrics();
9
+ if (startTriggerPosition === 'full-area') {
10
+ const { heightDelta, top } = navBarMetrics;
11
+ let currentStart = top;
12
+ const maxProgress = maxScrollTop >= heightDelta + top
13
+ ? 1
14
+ : (animationRanges.find((range) => range[0] * heightDelta <= maxScrollTop - top) ?? [0])[0];
15
+ const mapScrollToAnimationPropgress = (scrollPosition) => {
16
+ if (!heightDelta) {
17
+ return 0;
18
+ }
19
+ const progress = (scrollPosition - currentStart) / heightDelta;
20
+ if (progress > maxProgress) {
21
+ currentStart = scrollPosition - heightDelta * maxProgress;
22
+ }
23
+ else if (progress < 0) {
24
+ currentStart = scrollPosition;
25
+ }
26
+ currentStart = Math.max(currentStart, top);
27
+ return clamp(progress, 0, maxProgress);
28
+ };
29
+ const getClosestStops = () => ({
30
+ bottom: currentStart,
31
+ top: currentStart + heightDelta * maxProgress,
32
+ });
33
+ return [mapScrollToAnimationPropgress, getClosestStops];
34
+ }
35
+ let startPos = startTriggerPosition === 'start' || !startTriggerPosition.current
36
+ ? 0
37
+ : getRelativeOffset(scrollAdapter, startTriggerPosition.current) - navBarMetrics.bottom;
38
+ let endPos = endTriggerPosition?.current
39
+ ? getRelativeOffset(scrollAdapter, endTriggerPosition.current) +
40
+ navBarMetrics.heightDelta -
41
+ navBarMetrics.bottom
42
+ : startPos + navBarMetrics.heightDelta;
43
+ endPos += navBarMetrics.top;
44
+ startPos += navBarMetrics.top;
45
+ let endProgress = 1;
46
+ if (endPos > maxScrollTop) {
47
+ const scrollDelta = endPos - startPos;
48
+ endProgress = (animationRanges.find((range) => range[0] * scrollDelta + startPos < maxScrollTop) ?? [0])[0];
49
+ endPos = startPos + scrollDelta * endProgress;
50
+ }
51
+ return [remap([startPos, endPos], [0, endProgress]), () => ({ bottom: startPos, top: endPos })];
52
+ };
53
+ const useBindScrollToAnimationProgress = (scrollPosition, getNavBarMetrics, getAnimationRanges, scrollAdapter, startTriggerPosition, endTriggerPosition) => {
54
+ const totalAnimationProgress = useMotionValue(0);
55
+ const startTriggerPositionRef = useActualRef(startTriggerPosition);
56
+ const endTriggerPositionRef = useActualRef(endTriggerPosition);
57
+ const getClosestStopsRef = useRef(null);
58
+ const bindScrollToAnimation = useInitOnce(() => {
59
+ let unsubscribe = null;
60
+ return () => {
61
+ unsubscribe?.();
62
+ const { heightDelta } = getNavBarMetrics();
63
+ const animationRanges = [
64
+ ...new Set([...getAnimationRanges().values()].map((range) => [range[0] / heightDelta, range[1] / heightDelta])),
65
+ ].sort((a, b) => b[0] - a[0]);
66
+ const [mapFn, getClosestStops] = createScrollToAnimationProgressMapFn(getNavBarMetrics, scrollAdapter, animationRanges, startTriggerPositionRef.current, endTriggerPositionRef.current);
67
+ getClosestStopsRef.current = getClosestStops;
68
+ unsubscribe = scrollPosition.on('change', (value) => totalAnimationProgress.set(mapFn(value)));
69
+ totalAnimationProgress.set(mapFn(scrollPosition.get()));
70
+ };
71
+ });
72
+ const getClosestStops = useInitOnce(() => () => {
73
+ if (getClosestStopsRef.current) {
74
+ return getClosestStopsRef.current();
75
+ }
76
+ return { bottom: 0, top: getNavBarMetrics().heightDelta };
77
+ });
78
+ return [bindScrollToAnimation, getClosestStops, totalAnimationProgress];
79
+ };
80
+
81
+ export { useBindScrollToAnimationProgress };
82
+ //# sourceMappingURL=useBindScrollToAnimationProgress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBindScrollToAnimationProgress.js","sources":["../../src/internal/useBindScrollToAnimationProgress.ts"],"sourcesContent":["import { type MutableRefObject, type RefObject, useRef } from 'react';\nimport { type MotionValue } from 'motion';\nimport { useMotionValue } from 'motion/react';\n\nimport { type AnimationRanges } from '@hh.ru/magritte-ui-nav-bar/internal/useAnimationRanges';\nimport { type NavBarMetrics } from '@hh.ru/magritte-ui-nav-bar/internal/useNavBarMetrics';\nimport { type ScrollAdapter } from '@hh.ru/magritte-ui-nav-bar/internal/useScrollAdapter';\nimport { remap, clamp, useInitOnce, useActualRef, getRelativeOffset } from '@hh.ru/magritte-ui-nav-bar/internal/utils';\n\nconst createScrollToAnimationProgressMapFn = (\n getNavBarMetrics: () => NavBarMetrics,\n scrollAdapter: ScrollAdapter,\n animationRanges: [number, number][],\n startTriggerPosition: 'start' | 'full-area' | RefObject<HTMLElement>,\n endTriggerPosition?: RefObject<HTMLElement>\n) => {\n const maxScrollTop = scrollAdapter.getMaxScrollTop();\n const navBarMetrics = getNavBarMetrics();\n\n if (startTriggerPosition === 'full-area') {\n const { heightDelta, top } = navBarMetrics;\n\n let currentStart = top;\n const maxProgress =\n maxScrollTop >= heightDelta + top\n ? 1\n : (animationRanges.find((range) => range[0] * heightDelta <= maxScrollTop - top) ?? [0])[0];\n\n const mapScrollToAnimationPropgress = (scrollPosition: number) => {\n if (!heightDelta) {\n return 0;\n }\n const progress = (scrollPosition - currentStart) / heightDelta;\n if (progress > maxProgress) {\n currentStart = scrollPosition - heightDelta * maxProgress;\n } else if (progress < 0) {\n currentStart = scrollPosition;\n }\n currentStart = Math.max(currentStart, top);\n\n return clamp(progress, 0, maxProgress);\n };\n\n const getClosestStops = () => ({\n bottom: currentStart,\n top: currentStart + heightDelta * maxProgress,\n });\n\n return [mapScrollToAnimationPropgress, getClosestStops] as const;\n }\n\n let startPos =\n startTriggerPosition === 'start' || !startTriggerPosition.current\n ? 0\n : getRelativeOffset(scrollAdapter, startTriggerPosition.current) - navBarMetrics.bottom;\n\n let endPos = endTriggerPosition?.current\n ? getRelativeOffset(scrollAdapter, endTriggerPosition.current) +\n navBarMetrics.heightDelta -\n navBarMetrics.bottom\n : startPos + navBarMetrics.heightDelta;\n\n endPos += navBarMetrics.top;\n startPos += navBarMetrics.top;\n\n let endProgress = 1;\n if (endPos > maxScrollTop) {\n const scrollDelta = endPos - startPos;\n endProgress = (animationRanges.find((range) => range[0] * scrollDelta + startPos < maxScrollTop) ?? [0])[0];\n endPos = startPos + scrollDelta * endProgress;\n }\n\n return [remap([startPos, endPos], [0, endProgress]), () => ({ bottom: startPos, top: endPos })] as const;\n};\n\nexport const useBindScrollToAnimationProgress = (\n scrollPosition: MotionValue<number>,\n getNavBarMetrics: () => NavBarMetrics,\n getAnimationRanges: () => AnimationRanges,\n scrollAdapter: ScrollAdapter,\n startTriggerPosition: 'start' | 'full-area' | RefObject<HTMLElement>,\n endTriggerPosition?: RefObject<HTMLElement>\n): [VoidFunction, () => { top: number; bottom: number }, MotionValue<number>] => {\n const totalAnimationProgress = useMotionValue(0);\n const startTriggerPositionRef = useActualRef(startTriggerPosition);\n const endTriggerPositionRef = useActualRef(endTriggerPosition);\n const getClosestStopsRef: MutableRefObject<(() => { top: number; bottom: number }) | null> = useRef(null);\n\n const bindScrollToAnimation = useInitOnce(() => {\n let unsubscribe: VoidFunction | null = null;\n return () => {\n unsubscribe?.();\n const { heightDelta } = getNavBarMetrics();\n\n const animationRanges = [\n ...new Set<[number, number]>(\n [...getAnimationRanges().values()].map((range) => [range[0] / heightDelta, range[1] / heightDelta])\n ),\n ].sort((a, b) => b[0] - a[0]);\n\n const [mapFn, getClosestStops] = createScrollToAnimationProgressMapFn(\n getNavBarMetrics,\n scrollAdapter,\n animationRanges,\n startTriggerPositionRef.current,\n endTriggerPositionRef.current\n );\n getClosestStopsRef.current = getClosestStops;\n unsubscribe = scrollPosition.on('change', (value) => totalAnimationProgress.set(mapFn(value)));\n totalAnimationProgress.set(mapFn(scrollPosition.get()));\n };\n });\n\n const getClosestStops = useInitOnce(() => () => {\n if (getClosestStopsRef.current) {\n return getClosestStopsRef.current();\n }\n\n return { bottom: 0, top: getNavBarMetrics().heightDelta };\n });\n\n return [bindScrollToAnimation, getClosestStops, totalAnimationProgress];\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type MotionStyle, type MotionValue } from 'motion/react';
2
+ import { type ShowDivider } from '@hh.ru/magritte-ui-divider';
3
+ import { type NavBarMetrics } from '@hh.ru/magritte-ui-nav-bar/internal/useNavBarMetrics';
4
+ export declare const useDivider: (scrollPosition: MotionValue<number>, totalAnimationProgress: MotionValue<number>, getNavBarMetrics: () => NavBarMetrics, showDivider: ShowDivider, overlay: boolean) => MotionStyle;
@@ -0,0 +1,38 @@
1
+ import './../index.css';
2
+ import { useEffect } from 'react';
3
+ import { useMotionValue } from 'motion/react';
4
+
5
+ const useDivider = (scrollPosition, totalAnimationProgress, getNavBarMetrics, showDivider, overlay) => {
6
+ const dividerVisibility = useMotionValue(showDivider === 'always' ? 'visible' : 'hidden');
7
+ const dividerOffset = useMotionValue(0);
8
+ useEffect(() => {
9
+ const subscriptions = [];
10
+ if (showDivider === 'with-scroll') {
11
+ const checkDivider = () => {
12
+ const { heightDelta, top } = getNavBarMetrics();
13
+ // из-за накопленных ошибок округления дивайдер может показаться раньше времени
14
+ // добавляем ему "зазор" в три пикселя
15
+ dividerVisibility.set(Math.ceil(heightDelta * totalAnimationProgress.get()) + top < Math.floor(scrollPosition.get()) - 3
16
+ ? 'visible'
17
+ : 'hidden');
18
+ };
19
+ checkDivider();
20
+ subscriptions.push(scrollPosition.on('change', checkDivider));
21
+ }
22
+ dividerVisibility.set(showDivider === 'always' ? 'visible' : 'hidden');
23
+ subscriptions.push(scrollPosition.on('change', () => dividerOffset.set(-getNavBarMetrics().heightDelta * totalAnimationProgress.get())));
24
+ return () => subscriptions.forEach((fn) => fn());
25
+ }, [
26
+ dividerVisibility,
27
+ scrollPosition,
28
+ showDivider,
29
+ getNavBarMetrics,
30
+ totalAnimationProgress,
31
+ overlay,
32
+ dividerOffset,
33
+ ]);
34
+ return { y: dividerOffset, visibility: dividerVisibility };
35
+ };
36
+
37
+ export { useDivider };
38
+ //# sourceMappingURL=useDivider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDivider.js","sources":["../../src/internal/useDivider.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { type MotionStyle, useMotionValue, type MotionValue } from 'motion/react';\n\nimport { type ShowDivider } from '@hh.ru/magritte-ui-divider';\nimport { type NavBarMetrics } from '@hh.ru/magritte-ui-nav-bar/internal/useNavBarMetrics';\n\nexport const useDivider = (\n scrollPosition: MotionValue<number>,\n totalAnimationProgress: MotionValue<number>,\n getNavBarMetrics: () => NavBarMetrics,\n showDivider: ShowDivider,\n overlay: boolean\n): MotionStyle => {\n const dividerVisibility = useMotionValue<'visible' | 'hidden'>(showDivider === 'always' ? 'visible' : 'hidden');\n const dividerOffset = useMotionValue(0);\n\n useEffect(() => {\n const subscriptions: VoidFunction[] = [];\n if (showDivider === 'with-scroll') {\n const checkDivider = () => {\n const { heightDelta, top } = getNavBarMetrics();\n // из-за накопленных ошибок округления дивайдер может показаться раньше времени\n // добавляем ему \"зазор\" в три пикселя\n dividerVisibility.set(\n Math.ceil(heightDelta * totalAnimationProgress.get()) + top < Math.floor(scrollPosition.get()) - 3\n ? 'visible'\n : 'hidden'\n );\n };\n checkDivider();\n subscriptions.push(scrollPosition.on('change', checkDivider));\n }\n\n dividerVisibility.set(showDivider === 'always' ? 'visible' : 'hidden');\n subscriptions.push(\n scrollPosition.on('change', () =>\n dividerOffset.set(-getNavBarMetrics().heightDelta * totalAnimationProgress.get())\n )\n );\n\n return () => subscriptions.forEach((fn) => fn());\n }, [\n dividerVisibility,\n scrollPosition,\n showDivider,\n getNavBarMetrics,\n totalAnimationProgress,\n overlay,\n dividerOffset,\n ]);\n\n return { y: dividerOffset, visibility: dividerVisibility };\n};\n"],"names":[],"mappings":";;;AAMO,MAAM,UAAU,GAAG,CACtB,cAAmC,EACnC,sBAA2C,EAC3C,gBAAqC,EACrC,WAAwB,EACxB,OAAgB,KACH;AACb,IAAA,MAAM,iBAAiB,GAAG,cAAc,CAAuB,WAAW,KAAK,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AAChH,IAAA,MAAM,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAExC,SAAS,CAAC,MAAK;QACX,MAAM,aAAa,GAAmB,EAAE,CAAC;AACzC,QAAA,IAAI,WAAW,KAAK,aAAa,EAAE;YAC/B,MAAM,YAAY,GAAG,MAAK;gBACtB,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,gBAAgB,EAAE,CAAC;;;gBAGhD,iBAAiB,CAAC,GAAG,CACjB,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,sBAAsB,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC;AAC9F,sBAAE,SAAS;sBACT,QAAQ,CACjB,CAAC;AACN,aAAC,CAAC;AACF,YAAA,YAAY,EAAE,CAAC;AACf,YAAA,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;SACjE;AAED,QAAA,iBAAiB,CAAC,GAAG,CAAC,WAAW,KAAK,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AACvE,QAAA,aAAa,CAAC,IAAI,CACd,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MACxB,aAAa,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,CAAC,WAAW,GAAG,sBAAsB,CAAC,GAAG,EAAE,CAAC,CACpF,CACJ,CAAC;AAEF,QAAA,OAAO,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;AACrD,KAAC,EAAE;QACC,iBAAiB;QACjB,cAAc;QACd,WAAW;QACX,gBAAgB;QAChB,sBAAsB;QACtB,OAAO;QACP,aAAa;AAChB,KAAA,CAAC,CAAC;IAEH,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;AAC/D;;;;"}
@@ -0,0 +1,9 @@
1
+ import { RefObject } from 'react';
2
+ import { type PaneStore } from '@hh.ru/magritte-ui-nav-bar/internal/PaneStore';
3
+ import { ScrollAdapter } from '@hh.ru/magritte-ui-nav-bar/internal/useScrollAdapter';
4
+ export interface NavBarMetrics {
5
+ heightDelta: number;
6
+ top: number;
7
+ bottom: number;
8
+ }
9
+ export declare const useNavBarMetrics: (panesRegistry: Set<PaneStore>, rootRef: RefObject<HTMLElement>, scrollAdapter: ScrollAdapter) => [() => NavBarMetrics, VoidFunction];
@@ -0,0 +1,34 @@
1
+ import './../index.css';
2
+ import { useRef } from 'react';
3
+ import { useActualRef, useInitOnce, getRelativeOffset } from './utils.js';
4
+
5
+ const useNavBarMetrics = (panesRegistry, rootRef, scrollAdapter) => {
6
+ const metricsRef = useRef(null);
7
+ const panesRegistryRef = useActualRef(panesRegistry);
8
+ const getNavBarMetrics = useInitOnce(() => () => {
9
+ if (!metricsRef.current) {
10
+ // расстояние от верхнего края NavBar до верхнего края контейнера со скроллом
11
+ const top = getRelativeOffset(scrollAdapter, rootRef.current);
12
+ const panes = [...panesRegistryRef.current.values()].map((pane) => pane.data);
13
+ const heightDelta = panes.reduce((delta, pane) => delta +
14
+ (pane.foldable
15
+ ? (pane.startHeight ?? 0)
16
+ : (pane.startHeight ?? 0) - (pane.endHeight ?? pane.startHeight ?? 0)), 0);
17
+ const positions = panes
18
+ .map((pane) => ({ top: pane.top ?? 0, bottom: (pane.top ?? 0) + (pane.startHeight ?? 0) }))
19
+ .sort((a, b) => a.top - b.top);
20
+ const height = (positions[positions.length - 1]?.bottom ?? 0) - (positions[0]?.top ?? 0);
21
+ metricsRef.current = {
22
+ heightDelta,
23
+ top,
24
+ bottom: top + height,
25
+ };
26
+ }
27
+ return metricsRef.current;
28
+ });
29
+ const invalidateMetricsCache = useInitOnce(() => () => (metricsRef.current = null));
30
+ return [getNavBarMetrics, invalidateMetricsCache];
31
+ };
32
+
33
+ export { useNavBarMetrics };
34
+ //# sourceMappingURL=useNavBarMetrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNavBarMetrics.js","sources":["../../src/internal/useNavBarMetrics.ts"],"sourcesContent":["import { type MutableRefObject, RefObject, useRef } from 'react';\n\nimport { type PaneStore } from '@hh.ru/magritte-ui-nav-bar/internal/PaneStore';\nimport { ScrollAdapter } from '@hh.ru/magritte-ui-nav-bar/internal/useScrollAdapter';\nimport { getRelativeOffset, useActualRef, useInitOnce } from '@hh.ru/magritte-ui-nav-bar/internal/utils';\n\nexport interface NavBarMetrics {\n heightDelta: number;\n top: number;\n bottom: number;\n}\n\nexport const useNavBarMetrics = (\n panesRegistry: Set<PaneStore>,\n rootRef: RefObject<HTMLElement>,\n scrollAdapter: ScrollAdapter\n): [() => NavBarMetrics, VoidFunction] => {\n const metricsRef: MutableRefObject<NavBarMetrics | null> = useRef(null);\n const panesRegistryRef = useActualRef(panesRegistry);\n\n const getNavBarMetrics = useInitOnce(() => () => {\n if (!metricsRef.current) {\n // расстояние от верхнего края NavBar до верхнего края контейнера со скроллом\n const top = getRelativeOffset(scrollAdapter, rootRef.current);\n const panes = [...panesRegistryRef.current.values()].map((pane) => pane.data);\n const heightDelta = panes.reduce(\n (delta, pane) =>\n delta +\n (pane.foldable\n ? (pane.startHeight ?? 0)\n : (pane.startHeight ?? 0) - (pane.endHeight ?? pane.startHeight ?? 0)),\n 0\n );\n const positions = panes\n .map((pane) => ({ top: pane.top ?? 0, bottom: (pane.top ?? 0) + (pane.startHeight ?? 0) }))\n .sort((a, b) => a.top - b.top);\n const height = (positions[positions.length - 1]?.bottom ?? 0) - (positions[0]?.top ?? 0);\n\n metricsRef.current = {\n heightDelta,\n top,\n bottom: top + height,\n };\n }\n\n return metricsRef.current;\n });\n\n const invalidateMetricsCache = useInitOnce(() => () => (metricsRef.current = null));\n\n return [getNavBarMetrics, invalidateMetricsCache];\n};\n"],"names":[],"mappings":";;;AAYa,MAAA,gBAAgB,GAAG,CAC5B,aAA6B,EAC7B,OAA+B,EAC/B,aAA4B,KACS;AACrC,IAAA,MAAM,UAAU,GAA2C,MAAM,CAAC,IAAI,CAAC,CAAC;AACxE,IAAA,MAAM,gBAAgB,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IAErD,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,MAAK;AAC5C,QAAA,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE;;YAErB,MAAM,GAAG,GAAG,iBAAiB,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9D,MAAM,KAAK,GAAG,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9E,YAAA,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAC5B,CAAC,KAAK,EAAE,IAAI,KACR,KAAK;iBACJ,IAAI,CAAC,QAAQ;AACV,uBAAG,IAAI,CAAC,WAAW,IAAI,CAAC;sBACtB,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,EAC9E,CAAC,CACJ,CAAC;YACF,MAAM,SAAS,GAAG,KAAK;AAClB,iBAAA,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;AAC1F,iBAAA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;YAEzF,UAAU,CAAC,OAAO,GAAG;gBACjB,WAAW;gBACX,GAAG;gBACH,MAAM,EAAE,GAAG,GAAG,MAAM;aACvB,CAAC;SACL;QAED,OAAO,UAAU,CAAC,OAAO,CAAC;AAC9B,KAAC,CAAC,CAAC;AAEH,IAAA,MAAM,sBAAsB,GAAG,WAAW,CAAC,MAAM,OAAO,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;AAEpF,IAAA,OAAO,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;AACtD;;;;"}
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from 'react';
2
+ import { type MotionValue } from 'motion/react';
3
+ export declare const useResetFocus: (rootRef: RefObject<HTMLElement>, scrollPosition: MotionValue<number>) => void;
@@ -0,0 +1,31 @@
1
+ import './../index.css';
2
+ import { useRef, useEffect } from 'react';
3
+
4
+ const useResetFocus = (rootRef, scrollPosition) => {
5
+ const hasFocus = useRef(false);
6
+ useEffect(() => {
7
+ if (!rootRef.current) {
8
+ return void 0;
9
+ }
10
+ const focusHandler = () => (hasFocus.current = true);
11
+ const element = rootRef.current;
12
+ element.addEventListener('focusin', focusHandler);
13
+ const unsubscribe = scrollPosition.on('change', () => {
14
+ if (!hasFocus.current || !rootRef.current) {
15
+ return;
16
+ }
17
+ const activeElement = document.activeElement;
18
+ hasFocus.current = false;
19
+ if (activeElement && rootRef.current.contains(activeElement) && typeof activeElement.blur === 'function') {
20
+ activeElement.blur();
21
+ }
22
+ });
23
+ return () => {
24
+ unsubscribe();
25
+ element.removeEventListener('focusin', focusHandler);
26
+ };
27
+ }, [scrollPosition, rootRef]);
28
+ };
29
+
30
+ export { useResetFocus };
31
+ //# sourceMappingURL=useResetFocus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResetFocus.js","sources":["../../src/internal/useResetFocus.ts"],"sourcesContent":["import { type MutableRefObject, useRef, type RefObject, useEffect } from 'react';\nimport { type MotionValue } from 'motion/react';\n\nexport const useResetFocus = (rootRef: RefObject<HTMLElement>, scrollPosition: MotionValue<number>): void => {\n const hasFocus: MutableRefObject<boolean> = useRef(false);\n\n useEffect(() => {\n if (!rootRef.current) {\n return void 0;\n }\n\n const focusHandler = () => (hasFocus.current = true);\n const element = rootRef.current;\n element.addEventListener('focusin', focusHandler);\n const unsubscribe = scrollPosition.on('change', () => {\n if (!hasFocus.current || !rootRef.current) {\n return;\n }\n\n const activeElement = document.activeElement as HTMLElement;\n hasFocus.current = false;\n if (activeElement && rootRef.current.contains(activeElement) && typeof activeElement.blur === 'function') {\n activeElement.blur();\n }\n });\n\n return () => {\n unsubscribe();\n element.removeEventListener('focusin', focusHandler);\n };\n }, [scrollPosition, rootRef]);\n};\n"],"names":[],"mappings":";;MAGa,aAAa,GAAG,CAAC,OAA+B,EAAE,cAAmC,KAAU;AACxG,IAAA,MAAM,QAAQ,GAA8B,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1D,SAAS,CAAC,MAAK;AACX,QAAA,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;YAClB,OAAO,KAAK,CAAC,CAAC;SACjB;AAED,QAAA,MAAM,YAAY,GAAG,OAAO,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AACrD,QAAA,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;AAChC,QAAA,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAClD,MAAM,WAAW,GAAG,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAK;YACjD,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;gBACvC,OAAO;aACV;AAED,YAAA,MAAM,aAAa,GAAG,QAAQ,CAAC,aAA4B,CAAC;AAC5D,YAAA,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAC;AACzB,YAAA,IAAI,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,OAAO,aAAa,CAAC,IAAI,KAAK,UAAU,EAAE;gBACtG,aAAa,CAAC,IAAI,EAAE,CAAC;aACxB;AACL,SAAC,CAAC,CAAC;AAEH,QAAA,OAAO,MAAK;AACR,YAAA,WAAW,EAAE,CAAC;AACd,YAAA,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AACzD,SAAC,CAAC;AACN,KAAC,EAAE,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;AAClC;;;;"}