@olddevs/ui 4.2.1 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/components/navigation/MegaMenu.d.ts +43 -23
- package/dist/components/navigation/MegaMenu.d.ts.map +1 -1
- package/dist/components/navigation/MegaMenu.js +110 -47
- package/dist/components/navigation/MegaMenu.js.map +1 -1
- package/dist/components/navigation/useMenuAim.d.ts +8 -8
- package/dist/components/navigation/useMenuAim.d.ts.map +1 -1
- package/dist/components/overlays/Menu.d.ts +4 -2
- package/dist/components/overlays/Menu.d.ts.map +1 -1
- package/dist/components/overlays/Menu.js.map +1 -1
- package/docs/components/index.en.md +2 -2
- package/docs/components/index.md +1 -1
- package/docs/components/navigation/mega-menu.en.md +91 -61
- package/docs/components/navigation/mega-menu.md +86 -56
- package/docs/components/overlays/menu.en.md +8 -4
- package/docs/components/overlays/menu.md +6 -3
- package/docs/getting-started.en.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/glossary.en.md +23 -23
- package/docs/glossary.md +22 -22
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/components/navigation/MegaMenu.tsx +300 -140
- package/src/components/navigation/useMenuAim.ts +19 -19
- package/src/components/overlays/Menu.tsx +12 -2
|
@@ -5,14 +5,14 @@ import { useCallback, useEffect, useRef, type RefObject } from "react";
|
|
|
5
5
|
/*
|
|
6
6
|
ПРИЦЕЛ МЕГАМЕНЮ — приём Amazon, разобранный Беном Каменсом в 2013 году.
|
|
7
7
|
|
|
8
|
-
Курсор идёт от Категории к её
|
|
9
|
-
Категории. Наивное «наведение открывает» сменило бы
|
|
8
|
+
Курсор идёт от Категории к её Полке наискосок и по пути задевает соседние
|
|
9
|
+
Категории. Наивное «наведение открывает» сменило бы Полку у человека под
|
|
10
10
|
рукой. Прицел смотрит, КУДА движется курсор: пока он идёт в сторону открытой
|
|
11
|
-
|
|
11
|
+
Полки, соседняя Категория наведение не перехватывает.
|
|
12
12
|
|
|
13
13
|
Модель — как у `useSafelyMouseToSubmenu` в react-aria, чтобы подменю `Menu` и
|
|
14
14
|
мегаменю вели себя одинаково: направление последнего шага сравнивается с
|
|
15
|
-
сектором от предыдущей точки к двум углам ближнего края
|
|
15
|
+
сектором от предыдущей точки к двум углам ближнего края Полки; смена
|
|
16
16
|
направления прощается; обработка прорежена до кадра; только мышь.
|
|
17
17
|
|
|
18
18
|
Два осознанных расхождения с react-aria:
|
|
@@ -32,7 +32,7 @@ export const AIM_SETTLE_MS = 300;
|
|
|
32
32
|
const THROTTLE_MS = 16;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Сколько шагов «не туда» подряд прощается. Счётчик растёт на шаг к
|
|
35
|
+
* Сколько шагов «не туда» подряд прощается. Счётчик растёт на шаг к Полке и
|
|
36
36
|
* падает на шаг мимо; целится курсор, пока счётчик выше нуля. Отсюда: одно
|
|
37
37
|
* случайное движение вбок прицел не сбрасывает, два подряд — сбрасывают.
|
|
38
38
|
*/
|
|
@@ -40,7 +40,7 @@ const ALLOWED_INVALID_MOVES = 2;
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Запас сектора по вертикали, пиксели. Без него курсор, идущий точно в угол
|
|
43
|
-
*
|
|
43
|
+
* Полки, выпадал бы из сектора на дрожании руки.
|
|
44
44
|
*/
|
|
45
45
|
const SECTOR_PADDING = 24;
|
|
46
46
|
|
|
@@ -50,7 +50,7 @@ interface Point {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Идёт ли шаг `from → to` к
|
|
53
|
+
* Идёт ли шаг `from → to` к Полке: направление шага лежит внутри сектора от
|
|
54
54
|
* `from` к двум углам её ближнего края.
|
|
55
55
|
*
|
|
56
56
|
* Векторное произведение, а не `atan2`: тот же сектор без разрыва угла на ±π.
|
|
@@ -61,9 +61,9 @@ export function isMovingToward(from: Point, to: Point, rect: DOMRect, side: "lef
|
|
|
61
61
|
const dy = to.y - from.y;
|
|
62
62
|
if (dx === 0 && dy === 0) return false;
|
|
63
63
|
const edge = side === "right" ? rect.left : rect.right;
|
|
64
|
-
// Курсор уже за ближним краем — он в
|
|
64
|
+
// Курсор уже за ближним краем — он в Полке, а не на пути к ней.
|
|
65
65
|
if (side === "right" ? from.x >= edge : from.x <= edge) return false;
|
|
66
|
-
// Шаг от
|
|
66
|
+
// Шаг от Полки по горизонтали не может к ней вести.
|
|
67
67
|
if (side === "right" ? dx <= 0 : dx >= 0) return false;
|
|
68
68
|
const top = { x: edge - from.x, y: rect.top - SECTOR_PADDING - from.y };
|
|
69
69
|
const bottom = { x: edge - from.x, y: rect.bottom + SECTOR_PADDING - from.y };
|
|
@@ -79,25 +79,25 @@ export function isMovingToward(from: Point, to: Point, rect: DOMRect, side: "lef
|
|
|
79
79
|
const NONE = Symbol("none");
|
|
80
80
|
|
|
81
81
|
interface MenuAimOptions {
|
|
82
|
-
/** Открытая Категория; `null` —
|
|
82
|
+
/** Открытая Категория; `null` — Полки нет, и целиться не во что. */
|
|
83
83
|
value: string | null;
|
|
84
|
-
/** Открытая сейчас
|
|
84
|
+
/** Открытая сейчас Полка: её прямоугольник — цель. */
|
|
85
85
|
showcaseRef: RefObject<HTMLElement | null>;
|
|
86
|
-
/** Сменить Категорию (или закрыть
|
|
86
|
+
/** Сменить Категорию (или закрыть Полку — `null`). */
|
|
87
87
|
commit: (next: string | null) => void;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Прицел: решает, сменить Категорию под курсором сейчас или отложить.
|
|
92
92
|
*
|
|
93
|
-
* `request` зовётся из наведения на строку Столбца. Если
|
|
93
|
+
* `request` зовётся из наведения на строку Столбца. Если Полки нет или
|
|
94
94
|
* курсор не целится — смена сразу. Если целится — смена откладывается: курсор
|
|
95
95
|
* перестал целиться или стоит `AIM_SETTLE_MS` — открывается то, что под ним
|
|
96
|
-
* сейчас; дошёл до
|
|
96
|
+
* сейчас; дошёл до Полки — смена отменяется.
|
|
97
97
|
*
|
|
98
|
-
* Прямоугольник
|
|
98
|
+
* Прямоугольник Полки берётся заново на каждом шаге, а не кешируется:
|
|
99
99
|
* прокрутка страницы и смена размера окна не оставляют его устаревшим, а цена
|
|
100
|
-
* — одно чтение раскладки на кадр, и только пока
|
|
100
|
+
* — одно чтение раскладки на кадр, и только пока Полка открыта.
|
|
101
101
|
*/
|
|
102
102
|
export function useMenuAim({ value, showcaseRef, commit }: MenuAimOptions) {
|
|
103
103
|
const previous = useRef<Point | null>(null);
|
|
@@ -125,7 +125,7 @@ export function useMenuAim({ value, showcaseRef, commit }: MenuAimOptions) {
|
|
|
125
125
|
timer.current = setTimeout(flush, AIM_SETTLE_MS);
|
|
126
126
|
}, [flush]);
|
|
127
127
|
|
|
128
|
-
// Новая
|
|
128
|
+
// Новая Полка — новая цель: прежний счёт к ней отношения не имеет.
|
|
129
129
|
useEffect(() => {
|
|
130
130
|
aim.current = 0;
|
|
131
131
|
previous.current = null;
|
|
@@ -148,7 +148,7 @@ export function useMenuAim({ value, showcaseRef, commit }: MenuAimOptions) {
|
|
|
148
148
|
previous.current = point;
|
|
149
149
|
if (!showcase || !from) return;
|
|
150
150
|
|
|
151
|
-
// Курсор дошёл до
|
|
151
|
+
// Курсор дошёл до Полки — отложенная смена больше не нужна.
|
|
152
152
|
if (event.target instanceof Node && showcase.contains(event.target)) {
|
|
153
153
|
pending.current = NONE;
|
|
154
154
|
clearTimer();
|
|
@@ -171,7 +171,7 @@ export function useMenuAim({ value, showcaseRef, commit }: MenuAimOptions) {
|
|
|
171
171
|
return () => document.removeEventListener("pointermove", onPointerMove);
|
|
172
172
|
}, [value, showcaseRef, flush, settleLater]);
|
|
173
173
|
|
|
174
|
-
/** Курсор наведён на строку Столбца: открыть её (`null` — закрыть
|
|
174
|
+
/** Курсор наведён на строку Столбца: открыть её (`null` — закрыть Полку). */
|
|
175
175
|
const request = useCallback(
|
|
176
176
|
(next: string | null) => {
|
|
177
177
|
if (next === value) {
|
|
@@ -89,9 +89,19 @@ export interface MenuProps<T extends object = object> extends AriaMenuProps<T> {
|
|
|
89
89
|
* Сторона, с которой панель раскрывается. Корневое меню по умолчанию — под
|
|
90
90
|
* кнопкой слева (`bottom start`); вложенное, внутри `SubmenuTrigger`, —
|
|
91
91
|
* сбоку от своего пункта, а на узком экране под ним. Явное значение
|
|
92
|
-
* побеждает оба умолчания.
|
|
92
|
+
* побеждает оба умолчания. `end top` и `start top` — сбоку по концу или
|
|
93
|
+
* началу строки: при письме справа налево они меняются местами, а
|
|
94
|
+
* `right top` и `left top` — нет.
|
|
93
95
|
*/
|
|
94
|
-
placement?:
|
|
96
|
+
placement?:
|
|
97
|
+
| "bottom start"
|
|
98
|
+
| "bottom end"
|
|
99
|
+
| "top start"
|
|
100
|
+
| "top end"
|
|
101
|
+
| "end top"
|
|
102
|
+
| "start top"
|
|
103
|
+
| "right top"
|
|
104
|
+
| "left top";
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
export interface MenuItemProps extends Omit<AriaMenuItemProps, "children" | "className"> {
|