@astral/ui 4.103.2 → 4.104.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 (55) hide show
  1. package/components/Onboarding/ui/Overlay/Overlay.d.ts +1 -5
  2. package/components/Onboarding/ui/Overlay/Overlay.js +2 -5
  3. package/components/Onboarding/ui/Overlay/constants.d.ts +0 -1
  4. package/components/Onboarding/ui/Overlay/constants.js +0 -1
  5. package/components/Onboarding/ui/Overlay/styles.js +0 -11
  6. package/components/Onboarding/ui/StepStrategy/StepStrategy.js +2 -2
  7. package/components/Onboarding/ui/StepStrategy/useLogic/useLogic.d.ts +0 -4
  8. package/components/Onboarding/ui/StepStrategy/useLogic/useLogic.js +14 -5
  9. package/components/Onboarding/ui/hooks/useAnimatedRect/index.d.ts +1 -0
  10. package/components/Onboarding/ui/hooks/useAnimatedRect/index.js +1 -0
  11. package/components/Onboarding/ui/hooks/useAnimatedRect/useAnimatedRect.d.ts +21 -0
  12. package/components/Onboarding/ui/hooks/useAnimatedRect/useAnimatedRect.js +66 -0
  13. package/components/Onboarding/ui/hooks/useStepTransition/useStepTransition.d.ts +3 -17
  14. package/components/Onboarding/ui/hooks/useStepTransition/useStepTransition.js +8 -23
  15. package/components/Onboarding/ui/hooks/useTargetAnchor/useTargetAnchor.js +3 -6
  16. package/components/Onboarding/utils/geometry/rect/index.d.ts +1 -1
  17. package/components/Onboarding/utils/geometry/rect/index.js +1 -1
  18. package/components/Onboarding/utils/geometry/rect/rect.d.ts +4 -0
  19. package/components/Onboarding/utils/geometry/rect/rect.js +9 -0
  20. package/components/Onboarding/utils/scrollIntoView/scrollIntoView.d.ts +5 -6
  21. package/components/Onboarding/utils/scrollIntoView/scrollIntoView.js +19 -49
  22. package/components/Select/Select.js +1 -1
  23. package/components/Select/useLogic/useLogic.js +2 -2
  24. package/components/Select/useLogic/utils/isShowingPlaceholder/index.d.ts +1 -0
  25. package/components/Select/useLogic/utils/isShowingPlaceholder/index.js +1 -0
  26. package/components/Select/useLogic/utils/isShowingPlaceholder/isShowingPlaceholder.d.ts +1 -0
  27. package/components/Select/useLogic/utils/isShowingPlaceholder/isShowingPlaceholder.js +12 -0
  28. package/node/components/Onboarding/ui/Overlay/Overlay.d.ts +1 -5
  29. package/node/components/Onboarding/ui/Overlay/Overlay.js +2 -5
  30. package/node/components/Onboarding/ui/Overlay/constants.d.ts +0 -1
  31. package/node/components/Onboarding/ui/Overlay/constants.js +0 -1
  32. package/node/components/Onboarding/ui/Overlay/styles.js +0 -11
  33. package/node/components/Onboarding/ui/StepStrategy/StepStrategy.js +2 -2
  34. package/node/components/Onboarding/ui/StepStrategy/useLogic/useLogic.d.ts +0 -4
  35. package/node/components/Onboarding/ui/StepStrategy/useLogic/useLogic.js +14 -5
  36. package/node/components/Onboarding/ui/hooks/useAnimatedRect/index.d.ts +1 -0
  37. package/node/components/Onboarding/ui/hooks/useAnimatedRect/index.js +5 -0
  38. package/node/components/Onboarding/ui/hooks/useAnimatedRect/useAnimatedRect.d.ts +21 -0
  39. package/node/components/Onboarding/ui/hooks/useAnimatedRect/useAnimatedRect.js +70 -0
  40. package/node/components/Onboarding/ui/hooks/useStepTransition/useStepTransition.d.ts +3 -17
  41. package/node/components/Onboarding/ui/hooks/useStepTransition/useStepTransition.js +8 -23
  42. package/node/components/Onboarding/ui/hooks/useTargetAnchor/useTargetAnchor.js +3 -6
  43. package/node/components/Onboarding/utils/geometry/rect/index.d.ts +1 -1
  44. package/node/components/Onboarding/utils/geometry/rect/index.js +2 -1
  45. package/node/components/Onboarding/utils/geometry/rect/rect.d.ts +4 -0
  46. package/node/components/Onboarding/utils/geometry/rect/rect.js +11 -1
  47. package/node/components/Onboarding/utils/scrollIntoView/scrollIntoView.d.ts +5 -6
  48. package/node/components/Onboarding/utils/scrollIntoView/scrollIntoView.js +19 -49
  49. package/node/components/Select/Select.js +1 -1
  50. package/node/components/Select/useLogic/useLogic.js +2 -2
  51. package/node/components/Select/useLogic/utils/isShowingPlaceholder/index.d.ts +1 -0
  52. package/node/components/Select/useLogic/utils/isShowingPlaceholder/index.js +17 -0
  53. package/node/components/Select/useLogic/utils/isShowingPlaceholder/isShowingPlaceholder.d.ts +1 -0
  54. package/node/components/Select/useLogic/utils/isShowingPlaceholder/isShowingPlaceholder.js +16 -0
  55. package/package.json +1 -1
@@ -38,11 +38,6 @@ exports.Backdrop = styled_1.styled.path `
38
38
  cursor: pointer;
39
39
 
40
40
  fill: ${({ theme }) => theme.palette.background.modalShadow};
41
-
42
- .${constants_2.overlayClassnames.holeMoving} & {
43
- transition: d ${({ theme }) => theme.transitions.duration.standard}ms
44
- ${({ theme }) => theme.transitions.easing.easeInOut};
45
- }
46
41
  `;
47
42
  /**
48
43
  * Блокатор кликов для неинтерактивных шагов.
@@ -51,10 +46,4 @@ exports.HoleBlocker = styled_1.styled.rect `
51
46
  pointer-events: auto;
52
47
 
53
48
  fill: transparent;
54
-
55
- .${constants_2.overlayClassnames.holeMoving} & {
56
- transition-timing-function: ${({ theme }) => theme.transitions.easing.easeInOut};
57
- transition-duration: ${({ theme }) => theme.transitions.duration.standard}ms;
58
- transition-property: x, y, width, height, rx;
59
- }
60
49
  `;
@@ -14,8 +14,8 @@ const STEP_VIEWS = {
14
14
  * Стратегия отрисовки по типу шага, единый overlay для всего тура.
15
15
  */
16
16
  const StepStrategy = ({ activeStep, isClosing }) => {
17
- const { geometry, shouldShowOverlay, isVisible, isHoleMoving, onOverlayClose, } = (0, useLogic_1.useLogic)(activeStep, isClosing);
17
+ const { geometry, shouldShowOverlay, isVisible, onOverlayClose } = (0, useLogic_1.useLogic)(activeStep, isClosing);
18
18
  const StepView = STEP_VIEWS[activeStep.step.type];
19
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [shouldShowOverlay && ((0, jsx_runtime_1.jsx)(Overlay_1.Overlay, { hole: geometry.hole, isInteractive: activeStep.isInteractive, isVisible: isVisible, isHoleMoving: isHoleMoving, onClose: onOverlayClose })), (0, jsx_runtime_1.jsx)(StepView, { activeStep: activeStep, isClosing: isClosing, geometry: geometry })] }));
19
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [shouldShowOverlay && ((0, jsx_runtime_1.jsx)(Overlay_1.Overlay, { hole: geometry.hole, isInteractive: activeStep.isInteractive, isVisible: isVisible, onClose: onOverlayClose })), (0, jsx_runtime_1.jsx)(StepView, { activeStep: activeStep, isClosing: isClosing, geometry: geometry })] }));
20
20
  };
21
21
  exports.StepStrategy = StepStrategy;
@@ -32,10 +32,6 @@ export type UseLogicResult = {
32
32
  * Overlay виден.
33
33
  */
34
34
  isVisible: boolean;
35
- /**
36
- * Hole анимируется.
37
- */
38
- isHoleMoving: boolean;
39
35
  /**
40
36
  * Клик по overlay — запрос выхода.
41
37
  */
@@ -5,6 +5,7 @@ const react_1 = require("react");
5
5
  const useTheme_1 = require("../../../../theme/hooks/useTheme");
6
6
  const createVirtualAnchor_1 = require("../../../utils/createVirtualAnchor");
7
7
  const geometry_1 = require("../../../utils/geometry");
8
+ const useAnimatedRect_1 = require("../../hooks/useAnimatedRect");
8
9
  const useStepTransition_1 = require("../../hooks/useStepTransition");
9
10
  const useTargetAnchor_1 = require("../../hooks/useTargetAnchor");
10
11
  /**
@@ -22,12 +23,21 @@ const useLogic = (activeStep, isClosing) => {
22
23
  timeout: step.waitForTimeout,
23
24
  });
24
25
  const padding = (0, geometry_1.parseOffset)(step.highlightOffset);
25
- const hole = (0, react_1.useMemo)(() => (rect ? (0, geometry_1.inflateRect)(rect, padding) : null), [rect, padding]);
26
+ // Отступ учитываем до переезда: иначе он подставляется сразу от нового шага,
27
+ // и область скачком меняет размер вместо плавного.
28
+ const targetHole = (0, react_1.useMemo)(() => (rect ? (0, geometry_1.inflateRect)(rect, padding) : null), [rect, padding]);
29
+ // Переезд hole между шагами: цель шага может ехать сама (шторка, прокрутка),
30
+ // поэтому область анимируется здесь, а не css-переходом.
31
+ const hole = (0, useAnimatedRect_1.useAnimatedRect)(targetHole, {
32
+ stepKey: step.name,
33
+ durationMs: theme.transitions.duration.short,
34
+ // Шаг без hole: область гаснет сразу, а не едет к отсутствующей цели.
35
+ hasHole: isHighlight,
36
+ });
26
37
  const anchorEl = (0, react_1.useMemo)(() => (hole ? (0, createVirtualAnchor_1.createVirtualAnchor)(hole) : null), [hole]);
27
- const { isPopoverHidden, isHoleMoving } = (0, useStepTransition_1.useStepTransition)(step.name, {
28
- // Popover скрыт пока hole движется.
38
+ const isPopoverHidden = (0, useStepTransition_1.useStepTransition)(step.name, {
39
+ // Popover скрыт пока hole едет к новой цели.
29
40
  popoverHiddenMs: theme.transitions.duration.short,
30
- holeMovingMs: theme.transitions.duration.short,
31
41
  hasHole: isHighlight,
32
42
  });
33
43
  const onOverlayClose = (0, react_1.useCallback)(() => {
@@ -48,7 +58,6 @@ const useLogic = (activeStep, isClosing) => {
48
58
  },
49
59
  shouldShowOverlay: hasShownOverlayRef.current,
50
60
  isVisible: !isClosing,
51
- isHoleMoving,
52
61
  onOverlayClose,
53
62
  };
54
63
  };
@@ -0,0 +1 @@
1
+ export { useAnimatedRect } from './useAnimatedRect';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAnimatedRect = void 0;
4
+ var useAnimatedRect_1 = require("./useAnimatedRect");
5
+ Object.defineProperty(exports, "useAnimatedRect", { enumerable: true, get: function () { return useAnimatedRect_1.useAnimatedRect; } });
@@ -0,0 +1,21 @@
1
+ import { type Rect } from '../../../utils/geometry';
2
+ export type AnimatedRectOptions = {
3
+ /**
4
+ * Ключ шага: его смена запускает переезд hole на новую цель.
5
+ */
6
+ stepKey: string;
7
+ /**
8
+ * Длительность переезда (мс).
9
+ */
10
+ durationMs: number;
11
+ /**
12
+ * Есть ли hole в текущем шаге (highlight). У шага без hole область гаснет
13
+ * сразу: ехать некуда.
14
+ * @default true
15
+ */
16
+ hasHole?: boolean;
17
+ };
18
+ /**
19
+ * Плавный переход области hole от цели прошлого шага к цели нового.
20
+ */
21
+ export declare const useAnimatedRect: (target: Rect | null, { stepKey, durationMs, hasHole }: AnimatedRectOptions) => Rect | null;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAnimatedRect = void 0;
4
+ const react_1 = require("react");
5
+ const geometry_1 = require("../../../utils/geometry");
6
+ /**
7
+ * Сглаживание переезда, аналог css-функции `ease-in-out`.
8
+ */
9
+ const easeInOut = (progress) => progress < 0.5 ? 4 * progress ** 3 : 1 - (-2 * progress + 2) ** 3 / 2;
10
+ /**
11
+ * Плавный переход области hole от цели прошлого шага к цели нового.
12
+ */
13
+ const useAnimatedRect = (target, { stepKey, durationMs, hasHole = true }) => {
14
+ const [travelRect, setTravelRect] = (0, react_1.useState)(null);
15
+ const fromRect = (0, react_1.useRef)(null);
16
+ const targetRect = (0, react_1.useRef)(target);
17
+ const shownRect = (0, react_1.useRef)(target);
18
+ const prevStepKey = (0, react_1.useRef)(stepKey);
19
+ const [travelId, setTravelId] = (0, react_1.useState)(0);
20
+ targetRect.current = target;
21
+ // Стартуем синхронно во время render, иначе виден кадр с hole на новой цели.
22
+ if (prevStepKey.current !== stepKey) {
23
+ prevStepKey.current = stepKey;
24
+ if (!hasHole) {
25
+ // Шаг без hole (standalone): гасим область и отменяем начатый переезд.
26
+ fromRect.current = null;
27
+ setTravelRect(null);
28
+ setTravelId((id) => id + 1);
29
+ }
30
+ else if (shownRect.current) {
31
+ // Область уже показана — едем от неё; иначе (первый hole в туре) цель
32
+ // показывается сразу, без переезда.
33
+ fromRect.current = shownRect.current;
34
+ setTravelRect(shownRect.current);
35
+ setTravelId((id) => id + 1);
36
+ }
37
+ }
38
+ (0, react_1.useEffect)(() => {
39
+ // Первый render (mount) — переезд не запускаем.
40
+ if (travelId === 0) {
41
+ return;
42
+ }
43
+ const from = fromRect.current;
44
+ if (!from) {
45
+ return;
46
+ }
47
+ let frame = 0;
48
+ let startTime = 0;
49
+ const travel = (time) => {
50
+ startTime = startTime || time;
51
+ const progress = Math.min((time - startTime) / durationMs, 1);
52
+ const to = targetRect.current;
53
+ // Цель ещё не готова (элемент не смонтирован, идёт прокрутка) — держим
54
+ // прежнюю область, чтобы hole не гас на время ожидания.
55
+ setTravelRect(to ? (0, geometry_1.interpolateRect)(from, to, easeInOut(progress)) : from);
56
+ if (progress < 1) {
57
+ frame = requestAnimationFrame(travel);
58
+ return;
59
+ }
60
+ // Переезд закончен, дальше область ведёт трекинг цели.
61
+ setTravelRect(null);
62
+ };
63
+ frame = requestAnimationFrame(travel);
64
+ return () => cancelAnimationFrame(frame);
65
+ }, [travelId, durationMs]);
66
+ const rect = hasHole ? (travelRect ?? target) : null;
67
+ shownRect.current = rect;
68
+ return rect;
69
+ };
70
+ exports.useAnimatedRect = useAnimatedRect;
@@ -1,12 +1,8 @@
1
1
  export type StepTransitionDurations = {
2
2
  /**
3
- * Время скрытия popover при смене шага (не меньше `holeMovingMs`).
3
+ * Время скрытия popover при смене шага.
4
4
  */
5
5
  popoverHiddenMs: number;
6
- /**
7
- * Время движения hole к новому элементу.
8
- */
9
- holeMovingMs: number;
10
6
  /**
11
7
  * Есть ли hole в текущем шаге (highlight). Скрытие popover нужно только для
12
8
  * переезда hole (highlight → highlight).
@@ -14,17 +10,7 @@ export type StepTransitionDurations = {
14
10
  */
15
11
  hasHole?: boolean;
16
12
  };
17
- export type StepTransitionPhase = {
18
- /**
19
- * Popover скрыт, пока hole движется.
20
- */
21
- isPopoverHidden: boolean;
22
- /**
23
- * Идёт анимированный переезд hole.
24
- */
25
- isHoleMoving: boolean;
26
- };
27
13
  /**
28
- * Координирует скрытие popover и движение hole при переходе между highlight-шагами.
14
+ * Прячет popover на время переезда hole между highlight-шагами.
29
15
  */
30
- export declare const useStepTransition: (stepKey: string, { popoverHiddenMs, holeMovingMs, hasHole }: StepTransitionDurations) => StepTransitionPhase;
16
+ export declare const useStepTransition: (stepKey: string, { popoverHiddenMs, hasHole }: StepTransitionDurations) => boolean;
@@ -2,19 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useStepTransition = void 0;
4
4
  const react_1 = require("react");
5
- const IDLE_PHASE = {
6
- isPopoverHidden: false,
7
- isHoleMoving: false,
8
- };
9
- const HIDDEN_PHASE = {
10
- isPopoverHidden: true,
11
- isHoleMoving: true,
12
- };
13
5
  /**
14
- * Координирует скрытие popover и движение hole при переходе между highlight-шагами.
6
+ * Прячет popover на время переезда hole между highlight-шагами.
15
7
  */
16
- const useStepTransition = (stepKey, { popoverHiddenMs, holeMovingMs, hasHole = true }) => {
17
- const [phase, setPhase] = (0, react_1.useState)(IDLE_PHASE);
8
+ const useStepTransition = (stepKey, { popoverHiddenMs, hasHole = true }) => {
9
+ const [isPopoverHidden, setIsPopoverHidden] = (0, react_1.useState)(false);
18
10
  const prevKey = (0, react_1.useRef)(stepKey);
19
11
  const prevHadHole = (0, react_1.useRef)(hasHole);
20
12
  // Триггер для эффекта: `prevKey` мутируется во время render, поэтому в эффекте не сравнить.
@@ -25,26 +17,19 @@ const useStepTransition = (stepKey, { popoverHiddenMs, holeMovingMs, hasHole = t
25
17
  const isHoleTravel = prevHadHole.current && hasHole;
26
18
  prevKey.current = stepKey;
27
19
  prevHadHole.current = hasHole;
20
+ setIsPopoverHidden(isHoleTravel);
28
21
  if (isHoleTravel) {
29
- setPhase(HIDDEN_PHASE);
30
22
  setTransitionId((id) => id + 1);
31
23
  }
32
- else {
33
- setPhase(IDLE_PHASE);
34
- }
35
24
  }
36
25
  (0, react_1.useEffect)(() => {
37
26
  // Первый render (mount) — переход не запускаем.
38
27
  if (transitionId === 0) {
39
28
  return;
40
29
  }
41
- const popoverTimer = setTimeout(() => setPhase((prev) => ({ ...prev, isPopoverHidden: false })), popoverHiddenMs);
42
- const holeTimer = setTimeout(() => setPhase((prev) => ({ ...prev, isHoleMoving: false })), holeMovingMs);
43
- return () => {
44
- clearTimeout(popoverTimer);
45
- clearTimeout(holeTimer);
46
- };
47
- }, [transitionId, popoverHiddenMs, holeMovingMs]);
48
- return phase;
30
+ const popoverTimer = setTimeout(() => setIsPopoverHidden(false), popoverHiddenMs);
31
+ return () => clearTimeout(popoverTimer);
32
+ }, [transitionId, popoverHiddenMs]);
33
+ return isPopoverHidden;
49
34
  };
50
35
  exports.useStepTransition = useStepTransition;
@@ -24,17 +24,14 @@ const useTargetAnchor = (targetName, { onError, shouldTrack = true, scrollOnEnte
24
24
  // Устанавливаем найденный элемент сразу, без задержки.
25
25
  setElement((0, getTargetElement_1.getTargetElement)(targetName));
26
26
  (0, waitForElement_1.waitForElement)(targetName, { timeout })
27
- .then(async (target) => {
27
+ .then((target) => {
28
28
  if (isCancelled) {
29
29
  return;
30
30
  }
31
+ setElement(target);
31
32
  if (scrollOnEnter) {
32
- await (0, scrollIntoView_1.scrollIntoView)(target);
33
+ (0, scrollIntoView_1.scrollIntoView)(target);
33
34
  }
34
- if (isCancelled) {
35
- return;
36
- }
37
- setElement(target);
38
35
  stopRemovalWatch = (0, observeRemoval_1.observeRemoval)(target, () => {
39
36
  if (!isCancelled) {
40
37
  setElement(null);
@@ -1,2 +1,2 @@
1
- export { clampToScrollParents, clampToViewport, inflateRect, isSameRect, rectFromDomRect, } from './rect';
1
+ export { clampToScrollParents, clampToViewport, inflateRect, interpolateRect, isSameRect, rectFromDomRect, } from './rect';
2
2
  export type { Rect } from './rect';
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rectFromDomRect = exports.isSameRect = exports.inflateRect = exports.clampToViewport = exports.clampToScrollParents = void 0;
3
+ exports.rectFromDomRect = exports.isSameRect = exports.interpolateRect = exports.inflateRect = exports.clampToViewport = exports.clampToScrollParents = void 0;
4
4
  var rect_1 = require("./rect");
5
5
  Object.defineProperty(exports, "clampToScrollParents", { enumerable: true, get: function () { return rect_1.clampToScrollParents; } });
6
6
  Object.defineProperty(exports, "clampToViewport", { enumerable: true, get: function () { return rect_1.clampToViewport; } });
7
7
  Object.defineProperty(exports, "inflateRect", { enumerable: true, get: function () { return rect_1.inflateRect; } });
8
+ Object.defineProperty(exports, "interpolateRect", { enumerable: true, get: function () { return rect_1.interpolateRect; } });
8
9
  Object.defineProperty(exports, "isSameRect", { enumerable: true, get: function () { return rect_1.isSameRect; } });
9
10
  Object.defineProperty(exports, "rectFromDomRect", { enumerable: true, get: function () { return rect_1.rectFromDomRect; } });
@@ -20,6 +20,10 @@ export declare const isSameRect: (a: Rect | null, b: Rect) => boolean;
20
20
  * Расширяет область на padding во все стороны.
21
21
  */
22
22
  export declare const inflateRect: (rect: Rect, padding: number) => Rect;
23
+ /**
24
+ * Область между `from` и `to` по доле переезда `progress` (0..1).
25
+ */
26
+ export declare const interpolateRect: (from: Rect, to: Rect, progress: number) => Rect;
23
27
  /**
24
28
  * Обрезает область hole по границам viewport.
25
29
  */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.clampToScrollParents = exports.isViewportSizedRect = exports.clampToViewport = exports.inflateRect = exports.isSameRect = exports.rectFromDomRect = void 0;
3
+ exports.clampToScrollParents = exports.isViewportSizedRect = exports.clampToViewport = exports.interpolateRect = exports.inflateRect = exports.isSameRect = exports.rectFromDomRect = void 0;
4
4
  /**
5
5
  * Приводит нативный `DOMRect` к нашему `Rect` (только позиция и размеры).
6
6
  */
@@ -30,6 +30,16 @@ const inflateRect = (rect, padding) => ({
30
30
  height: rect.height + padding * 2,
31
31
  });
32
32
  exports.inflateRect = inflateRect;
33
+ /**
34
+ * Область между `from` и `to` по доле переезда `progress` (0..1).
35
+ */
36
+ const interpolateRect = (from, to, progress) => ({
37
+ x: from.x + (to.x - from.x) * progress,
38
+ y: from.y + (to.y - from.y) * progress,
39
+ width: from.width + (to.width - from.width) * progress,
40
+ height: from.height + (to.height - from.height) * progress,
41
+ });
42
+ exports.interpolateRect = interpolateRect;
33
43
  /**
34
44
  * Пересечение двух областей. Не пересекаются — ширина/высота 0.
35
45
  */
@@ -1,13 +1,12 @@
1
1
  /**
2
- * Доводит элемент highlight-шага до центра viewport и ждёт, пока прокрутка
3
- * реально доедет.
2
+ * Доводит элемент highlight-шага до центра viewport.
4
3
  *
5
4
  * Зачем: цель может быть за пределами экрана или внутри прокрученного
6
5
  * контейнера — её надо показать, прежде чем подсвечивать. Нативный
7
6
  * `scrollIntoView` сам прокручивает всю цепочку вложенных scroll-контейнеров.
8
7
  *
9
- * Почему ждём: прокрутка плавная (анимированная), позиция элемента меняется
10
- * кадр за кадром. Дожидаемся её стабилизации, чтобы hole overlay и popover встали
11
- * по финальной позиции, а не по промежуточной «едущей».
8
+ * Окончания прокрутки не ждём: hole и popover привязаны к элементу, а
9
+ * `useTrackedRect` ведёт их за ним покадрово — к финальной позиции они приедут
10
+ * вместе с ним.
12
11
  */
13
- export declare const scrollIntoView: (element: HTMLElement, behavior?: ScrollBehavior) => Promise<void>;
12
+ export declare const scrollIntoView: (element: HTMLElement, behavior?: ScrollBehavior) => void;
@@ -5,51 +5,23 @@ const constants_1 = require("../../constants");
5
5
  const geometry_1 = require("../geometry");
6
6
  const getScrollParents_1 = require("../getScrollParents");
7
7
  /**
8
- * Сколько кадров подряд позиция не меняется, чтобы счесть прокрутку стабилизировавшейся.
8
+ * Позиционирование, при котором элемент не двигается прокруткой страницы.
9
9
  */
10
- const STABLE_FRAMES = 3;
10
+ const PINNED_POSITIONS = ['fixed', 'sticky'];
11
11
  /**
12
- * Предохранитель: если позиция так и не стабилизировалась (постоянные
13
- * layout-сдвиги), делаем resolve по тайм-ауту, чтобы Promise не завис и тур не застрял.
12
+ * Элемент закреплён на экране: лежит внутри шторки, модалки или липкой шапки.
13
+ * Прокрутка такой элемент не подвинет, звать её незачем.
14
14
  */
15
- const SETTLE_TIMEOUT_MS = 1000;
16
- /**
17
- * Дожидается окончания плавной прокрутки.
18
- *
19
- * У браузера нет надёжного сигнала «плавная прокрутка доехала».
20
- * Поэтому отслеживаем позицию элемента покадрово и считаем прокрутку
21
- * законченной, когда `top` перестаёт меняться 3 кадра
22
- * подряд — либо когда срабатывает предохранитель 1000мс.
23
- */
24
- const waitForScrollSettle = (element) => new Promise((resolve) => {
25
- let raf = 0;
26
- let stableFrames = 0;
27
- let prevTop = Number.NaN;
28
- const timer = setTimeout(() => {
29
- cancelAnimationFrame(raf);
30
- resolve();
31
- }, SETTLE_TIMEOUT_MS);
32
- const tick = () => {
33
- const { top } = element.getBoundingClientRect();
34
- // Позиция не изменилась за кадр — копим «стабильные» кадры; дёрнулась —
35
- // сбрасываем счётчик и запоминаем новую позицию.
36
- if (top === prevTop) {
37
- stableFrames += 1;
38
- }
39
- else {
40
- stableFrames = 0;
41
- prevTop = top;
15
+ const checkIsPinned = (element) => {
16
+ let node = element;
17
+ while (node) {
18
+ if (PINNED_POSITIONS.includes(getComputedStyle(node).position)) {
19
+ return true;
42
20
  }
43
- // Достаточно кадров без движения → анимация закончилась.
44
- if (stableFrames >= STABLE_FRAMES) {
45
- clearTimeout(timer);
46
- resolve();
47
- return;
48
- }
49
- raf = requestAnimationFrame(tick);
50
- };
51
- raf = requestAnimationFrame(tick);
52
- });
21
+ node = node.parentElement;
22
+ }
23
+ return false;
24
+ };
53
25
  /**
54
26
  * Элемент целиком видим: помещается и во viewport, и в рамки каждого своего
55
27
  * scroll-предка.
@@ -68,22 +40,20 @@ const isFullyVisible = (element) => {
68
40
  rect.right <= frame.right);
69
41
  };
70
42
  /**
71
- * Доводит элемент highlight-шага до центра viewport и ждёт, пока прокрутка
72
- * реально доедет.
43
+ * Доводит элемент highlight-шага до центра viewport.
73
44
  *
74
45
  * Зачем: цель может быть за пределами экрана или внутри прокрученного
75
46
  * контейнера — её надо показать, прежде чем подсвечивать. Нативный
76
47
  * `scrollIntoView` сам прокручивает всю цепочку вложенных scroll-контейнеров.
77
48
  *
78
- * Почему ждём: прокрутка плавная (анимированная), позиция элемента меняется
79
- * кадр за кадром. Дожидаемся её стабилизации, чтобы hole overlay и popover встали
80
- * по финальной позиции, а не по промежуточной «едущей».
49
+ * Окончания прокрутки не ждём: hole и popover привязаны к элементу, а
50
+ * `useTrackedRect` ведёт их за ним покадрово — к финальной позиции они приедут
51
+ * вместе с ним.
81
52
  */
82
- const scrollIntoView = async (element, behavior = constants_1.SCROLL_BEHAVIOR) => {
83
- if (isFullyVisible(element)) {
53
+ const scrollIntoView = (element, behavior = constants_1.SCROLL_BEHAVIOR) => {
54
+ if (checkIsPinned(element) || isFullyVisible(element)) {
84
55
  return;
85
56
  }
86
57
  element.scrollIntoView({ behavior, block: 'center', inline: 'center' });
87
- await waitForScrollSettle(element);
88
58
  };
89
59
  exports.scrollIntoView = scrollIntoView;
@@ -53,6 +53,6 @@ const SelectInner = ({ required, getOptionLabel, placeholder, helperText, hideHe
53
53
  }
54
54
  return findMenuItemsLabel(children, selectedOptions);
55
55
  };
56
- return ((0, jsx_runtime_1.jsxs)(FormControl_1.default, { error: error, fullWidth: fullWidth, children: [label && ((0, jsx_runtime_1.jsx)(InputLabel_1.default, { htmlFor: "grouped-select", required: required, disabled: disabled, children: label })), (0, jsx_runtime_1.jsxs)(styles_1.StyledSelect, { ...props, value: value === null ? '' : value, open: isOpened, disabled: disabled, onOpen: openSelect, onClose: closeSelect, renderValue: renderValue, IconComponent: () => null, displayEmpty: true, ref: ref, fullWidth: fullWidth, onChange: handleChangeOption, "$isShowingPlaceholder": isShowingPlaceholder, endAdornment: (0, jsx_runtime_1.jsxs)(styles_1.EndAdornmentWrapper, { children: [loading && (0, jsx_runtime_1.jsx)(Loader_1.Loader, { color: "primary", size: "small" }), !loading && isShowingClearButton && ((0, jsx_runtime_1.jsx)(ClearButton, { disabled: disabled, ref: resetButtonRef, onClick: onClearAll })), !loading && ((0, jsx_runtime_1.jsx)(IconButton_1.IconButton, { variant: "text", disabled: disabled, onClick: openSelect, children: !isOpened ? (0, jsx_runtime_1.jsx)(DownOutlineMd_1.DownOutlineMd, {}) : (0, jsx_runtime_1.jsx)(UpOutlineMd_1.UpOutlineMd, {}) }))] }), children: [(0, jsx_runtime_1.jsx)(styles_1.Placeholder, { value: "", children: placeholder }), !loading && isShowingSelectAll && ((0, jsx_runtime_1.jsxs)(MenuItem_1.MenuItem, { className: selectAllClassName, selected: isSelectedAll, value: constants_1.SELECT_ALL_VALUE, children: [(0, jsx_runtime_1.jsx)(ListItemIcon_1.default, { children: (0, jsx_runtime_1.jsx)(Checkbox_1.Checkbox, { checked: isSelectedAll, indeterminate: isIndeterminate }) }), "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435"] })), !loading && children, !loading && isNoData && (0, jsx_runtime_1.jsx)(styles_1.StateWrapper, { children: "\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445" })] }), (0, jsx_runtime_1.jsx)(FormHelperText_1.FormHelperText, { error: error, success: success, hideHelperText: hideHelperText, children: helperText })] }));
56
+ return ((0, jsx_runtime_1.jsxs)(FormControl_1.default, { error: error, fullWidth: fullWidth, children: [label && ((0, jsx_runtime_1.jsx)(InputLabel_1.default, { htmlFor: "grouped-select", required: required, disabled: disabled, children: label })), (0, jsx_runtime_1.jsxs)(styles_1.StyledSelect, { ...props, value: value === null || value === undefined ? '' : value, open: isOpened, disabled: disabled, onOpen: openSelect, onClose: closeSelect, renderValue: renderValue, IconComponent: () => null, displayEmpty: true, ref: ref, fullWidth: fullWidth, onChange: handleChangeOption, "$isShowingPlaceholder": isShowingPlaceholder, endAdornment: (0, jsx_runtime_1.jsxs)(styles_1.EndAdornmentWrapper, { children: [loading && (0, jsx_runtime_1.jsx)(Loader_1.Loader, { color: "primary", size: "small" }), !loading && isShowingClearButton && ((0, jsx_runtime_1.jsx)(ClearButton, { disabled: disabled, ref: resetButtonRef, onClick: onClearAll })), !loading && ((0, jsx_runtime_1.jsx)(IconButton_1.IconButton, { variant: "text", disabled: disabled, onClick: openSelect, children: !isOpened ? (0, jsx_runtime_1.jsx)(DownOutlineMd_1.DownOutlineMd, {}) : (0, jsx_runtime_1.jsx)(UpOutlineMd_1.UpOutlineMd, {}) }))] }), children: [(0, jsx_runtime_1.jsx)(styles_1.Placeholder, { value: "", children: placeholder }), !loading && isShowingSelectAll && ((0, jsx_runtime_1.jsxs)(MenuItem_1.MenuItem, { className: selectAllClassName, selected: isSelectedAll, value: constants_1.SELECT_ALL_VALUE, children: [(0, jsx_runtime_1.jsx)(ListItemIcon_1.default, { children: (0, jsx_runtime_1.jsx)(Checkbox_1.Checkbox, { checked: isSelectedAll, indeterminate: isIndeterminate }) }), "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435"] })), !loading && children, !loading && isNoData && (0, jsx_runtime_1.jsx)(styles_1.StateWrapper, { children: "\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445" })] }), (0, jsx_runtime_1.jsx)(FormHelperText_1.FormHelperText, { error: error, success: success, hideHelperText: hideHelperText, children: helperText })] }));
57
57
  };
58
58
  exports.Select = (0, forwardRefWithGeneric_1.forwardRefWithGeneric)(SelectInner);
@@ -7,6 +7,7 @@ const MenuItem_2 = require("../../MenuItem");
7
7
  const getNodeText_1 = require("../../utils/getNodeText");
8
8
  const isSyncReactElement_1 = require("../../utils/isSyncReactElement");
9
9
  const constants_1 = require("../constants");
10
+ const isShowingPlaceholder_1 = require("./utils/isShowingPlaceholder");
10
11
  const useLogic = ({ value, onChange, children, open: externalIsOpened, defaultOpen, multiple, isEnableAllSelection, isShowClearButton = true, valueOptions, onSelectAll, onUnselectAll, name, placeholder, onClose, onOpen, loading, }) => {
11
12
  const [isOpened, setOpened] = (0, react_1.useState)(!loading && (defaultOpen || externalIsOpened || false));
12
13
  const resetButtonRef = (0, react_1.useRef)(null);
@@ -114,7 +115,6 @@ const useLogic = ({ value, onChange, children, open: externalIsOpened, defaultOp
114
115
  }
115
116
  return null;
116
117
  };
117
- const isShowingPlaceholder = Boolean((value === '' || Array.isArray(value)) && !!placeholder);
118
118
  return {
119
119
  isOpened,
120
120
  isNoData,
@@ -131,7 +131,7 @@ const useLogic = ({ value, onChange, children, open: externalIsOpened, defaultOp
131
131
  handleChangeArrayValue,
132
132
  selectAllClassName,
133
133
  findMenuItemsLabel,
134
- isShowingPlaceholder,
134
+ isShowingPlaceholder: (0, isShowingPlaceholder_1.isShowingPlaceholder)(value, placeholder),
135
135
  };
136
136
  };
137
137
  exports.useLogic = useLogic;
@@ -0,0 +1 @@
1
+ export * from './isShowingPlaceholder';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./isShowingPlaceholder"), exports);
@@ -0,0 +1 @@
1
+ export declare const isShowingPlaceholder: (value: unknown, placeholder?: string) => boolean;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isShowingPlaceholder = void 0;
4
+ const isShowingPlaceholder = (value, placeholder) => {
5
+ if (!placeholder) {
6
+ return false;
7
+ }
8
+ if (value === null || value === undefined || value === '') {
9
+ return true;
10
+ }
11
+ if (Array.isArray(value) && value.length === 0) {
12
+ return true;
13
+ }
14
+ return false;
15
+ };
16
+ exports.isShowingPlaceholder = isShowingPlaceholder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astral/ui",
3
- "version": "4.103.2",
3
+ "version": "4.104.1",
4
4
  "browser": "./index.js",
5
5
  "main": "./node/index.js",
6
6
  "dependencies": {