@apollo-design/position 0.0.0-stage → 0.1.9

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.
@@ -0,0 +1,459 @@
1
+ /**
2
+ * position 的类型契约。
3
+ *
4
+ * 这些类型全部是**纯数据**:矩形、区域、点、对齐配置。
5
+ *
6
+ * ⚠️ 旧注释写的是「不含任何 DOM 类型 —— 测量由调用方完成后以 Rect 传入」,
7
+ * **这是错的**(2026-09-17 改正)。它与两处更高优先级的事实来源冲突:
8
+ * · `registry/dependencies.json` 里 position 的 `purpose` 是「纯几何 **+ 尺寸测量**」
9
+ * · `ARCHITECTURE.md` §9.1 明确要求「别把 DOM 测量外壳误派给 overlay」
10
+ * DOM 测量就在本包的 `measure.ts`。本文件保持纯数据是对的 —— 测量的**输出**
11
+ * 正是这里的 `Rect` / `Area`,测量过程只是不被这些类型描述而已。
12
+ *
13
+ * 这样切分正好让 AR1 可以被验证:几何内核(`align.ts`)只认这些纯数据类型,
14
+ * 能被差分测试穷举;测量外壳(`measure.ts`)单独承担不可纯化的 DOM 副作用。
15
+ */
16
+ /** 与 DOMRect 同构的最小矩形。x/y 是视口坐标(等价于 getBoundingClientRect 的 left/top)。 */
17
+ interface Rect {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ }
23
+ /** 一个矩形区域,用于溢出判定。 */
24
+ interface Area {
25
+ left: number;
26
+ top: number;
27
+ right: number;
28
+ bottom: number;
29
+ }
30
+ /** 上/中/下 */
31
+ type VerticalPoint = 't' | 'c' | 'b';
32
+ /** 左/中/右 */
33
+ type HorizontalPoint = 'l' | 'c' | 'r';
34
+ /** 两个字符的对齐点,如 'tl'(左上)、'cc'(中心)。 */
35
+ type AlignPoint = `${VerticalPoint}${HorizontalPoint}`;
36
+ /**
37
+ * antd 的 12 个 placement。
38
+ * 顺序与 antd `es/_util/placements.js` 的 `PlacementAlignMap` 一致,不要重排。
39
+ */
40
+ type Placement = 'top' | 'left' | 'right' | 'bottom' | 'topLeft' | 'leftTop' | 'topRight' | 'rightTop' | 'bottomRight' | 'rightBottom' | 'bottomLeft' | 'leftBottom';
41
+ /** offset 支持数字与百分比字符串;百分比相对**自身**尺寸。 */
42
+ type OffsetType = number | `${number}%`;
43
+ interface OverflowConfig {
44
+ /** 溢出时在 X 方向翻转 */
45
+ adjustX?: boolean | number;
46
+ /** 溢出时在 Y 方向翻转 */
47
+ adjustY?: boolean | number;
48
+ /** 溢出时在 X 方向平移(true 等价于 0) */
49
+ shiftX?: boolean | number;
50
+ /** 溢出时在 Y 方向平移(true 等价于 0) */
51
+ shiftY?: boolean | number;
52
+ }
53
+ /**
54
+ * 对齐配置,字段与 antd `AlignType` 一一对应。
55
+ *
56
+ * 有意**不实现**的 antd 字段(DOM/渲染相关,不属于几何):
57
+ * `useCssRight` / `useCssBottom` / `useCssTransform` / `ignoreShake` / `_experimental`
58
+ */
59
+ interface AlignType {
60
+ /** [浮层点, 目标点],如 ['tc','bc'] 表示浮层上边缘中点对齐目标下边缘中点 */
61
+ points?: readonly [string, string];
62
+ /** 浮层自身偏移;百分比相对浮层尺寸 */
63
+ offset?: readonly OffsetType[];
64
+ /** 目标偏移;百分比相对目标尺寸 */
65
+ targetOffset?: readonly OffsetType[];
66
+ overflow?: OverflowConfig;
67
+ /** 是否自动调整箭头位置 */
68
+ autoArrow?: boolean;
69
+ htmlRegion?: 'visible' | 'scroll' | 'visibleFirst';
70
+ /** 自动选择 top/bottom 内缩 */
71
+ dynamicInset?: boolean;
72
+ }
73
+ /**
74
+ * 翻转记忆。
75
+ *
76
+ * antd 用 `prevFlipRef` 在多次对齐之间**粘住**翻转结果:一旦翻转过,
77
+ * 即使后续尺寸变化导致不再溢出,也会保持翻转,避免在临界点抖动。
78
+ * 这里把它显式建模为输入+输出,使核心函数保持纯函数。
79
+ */
80
+ interface FlipMemory {
81
+ /** bottom → top */
82
+ bt?: boolean;
83
+ /** top → bottom */
84
+ tb?: boolean;
85
+ /** right → left */
86
+ rl?: boolean;
87
+ /** left → right */
88
+ lr?: boolean;
89
+ }
90
+ /**
91
+ * 一次对齐的全部输入。
92
+ *
93
+ * 两个区域都按滚动容器裁剪过,由调用方(overlay 包)测量后传入:
94
+ * - `visible` 视口可视区 —— 平移(shift)与 `visibleFirst` 的翻转判定用它
95
+ * - `scroll` 文档滚动区 —— `htmlRegion='scroll'` 的判定用它
96
+ */
97
+ interface AlignContext {
98
+ target: Rect;
99
+ popup: Rect;
100
+ /** 视口可视区 */
101
+ visible: Area;
102
+ /** 文档滚动区 */
103
+ scroll: Area;
104
+ /** CSS 缩放。1 表示无缩放;由 computedStyle 与 rect 的比值测得。 */
105
+ scaleX?: number;
106
+ scaleY?: number;
107
+ }
108
+ /** 对齐结果。offsetX/offsetY 是相对浮层**当前**位置的位移增量。 */
109
+ interface AlignResult {
110
+ offsetX: number;
111
+ offsetY: number;
112
+ /** 箭头中心相对浮层左边的距离 */
113
+ arrowX: number;
114
+ /** 箭头中心相对浮层上边的距离 */
115
+ arrowY: number;
116
+ /** 实际生效的对齐点(翻转后会变化) */
117
+ points: [string, string];
118
+ /** 本次对齐后的翻转状态,需由调用方保存并在下次对齐时传回 */
119
+ flip: FlipMemory;
120
+ }
121
+ /** 构造 getPlacements 的参数,与 antd `getPlacements(config)` 对齐。 */
122
+ interface PlacementConfig {
123
+ /** 箭头宽度(正方形边长) */
124
+ arrowWidth: number;
125
+ /** 箭头与目标的间距 */
126
+ offset: number;
127
+ /** 浮层圆角,影响 arrowOffsetHorizontal */
128
+ borderRadius: number;
129
+ /** false 时完全关闭自动调整;对象时覆盖默认值 */
130
+ autoAdjustOverflow?: boolean | OverflowConfig;
131
+ /** 箭头是否指向目标中心(改变 8 个角 placement 的 points) */
132
+ arrowPointAtCenter?: boolean;
133
+ /** 是否优先保证视口内可见 */
134
+ visibleFirst?: boolean;
135
+ }
136
+
137
+ interface AlignOutcome {
138
+ /** 相对浮层当前位置的位移 */
139
+ offsetX: number;
140
+ offsetY: number;
141
+ /** 箭头中心相对浮层左上角的偏移(已按 scale 归一) */
142
+ arrowX: number;
143
+ arrowY: number;
144
+ /** 实际生效的对齐点(翻转后与原配置不同) */
145
+ points: [string, string];
146
+ /** 本次结果对应的翻转记忆,调用方需保存并在下次对齐时回传 */
147
+ flip: FlipMemory;
148
+ /** 实际生效的 overflow 配置回显,便于调试与快照 */
149
+ overflow: OverflowConfig;
150
+ }
151
+ /**
152
+ * 计算一次对齐。
153
+ *
154
+ * @param ctx 测量好的矩形与区域
155
+ * @param align 对齐配置(来自 `getPlacements` 或用户传入)
156
+ * @param flip 上一次的翻转记忆;首次对齐传 `{}` 或省略
157
+ */
158
+ declare function alignPopup(ctx: AlignContext, align: AlignType, flip?: FlipMemory): AlignOutcome;
159
+ /** `alignPopup` 的简化签名:只关心位移与箭头时用它。 */
160
+ declare function toAlignResult(outcome: AlignOutcome): AlignResult;
161
+
162
+ /**
163
+ * 浮层按 (offsetX, offsetY) 位移后,与 `area` 的相交面积。
164
+ *
165
+ * 这是判断「翻转后是否更好」的唯一度量 —— 翻转只在**可见面积变大**
166
+ * (或相等但推荐区面积更大)时才被接受。
167
+ *
168
+ * ⚠️ 与 antd 的**有意差异**(登记为开放决策 `intersection-area-clamp`):
169
+ * antd 的实现是 `Math.max(0, (right - left) * (bottom - top))`,
170
+ * 当浮层**整体**落在区域外侧时,两个差值同为负数,乘积为正 —— 于是
171
+ * 「完全不可见」被算成一个巨大的正面积,翻转判定会据此接受一个更差的位置。
172
+ * 这里改为**逐轴先夹到 0 再相乘**,数学上正确,且与 antd 在所有
173
+ * 「部分相交」的常见情形下结果完全一致(两条轴都非负时两式等价)。
174
+ *
175
+ * 为什么改而不是复刻:本项目的规则是「antd 自身的缺陷 → 登记差异,不复刻」。
176
+ * 且该差异只在退化情形(目标远离可视区)下才可能改变翻转结果,
177
+ * 而那种情形 antd 自己也会因 `isVisible(target)` 为假而提前返回。
178
+ */
179
+ declare function getIntersectionArea(rect: Rect, offsetX: number, offsetY: number, area: Area): number;
180
+ /**
181
+ * 用滚动容器逐级裁剪区域(antd `getVisibleArea` 的**纯数据**部分)。
182
+ *
183
+ * antd 版直接读 `getComputedStyle` 与 `offsetWidth/clientWidth` 来扣除边框与滚动条;
184
+ * 这里改为接收已经算好的裁剪矩形 `clip`,使几何部分保持可测。
185
+ * 调用方(overlay 包)负责从 DOM 计算 `clip`。
186
+ *
187
+ * @param initArea 初始区域(视口或文档滚动区)
188
+ * @param clips 每个滚动容器的可视矩形,自内向外
189
+ */
190
+ declare function clipArea(initArea: Area, clips: readonly Area[]): Area;
191
+ /**
192
+ * 构造一个矩形的区域表示。
193
+ * 与 DOMRect 的区别:DOMRect 是 x/y/width/height,溢出判定用 left/top/right/bottom 更直接。
194
+ */
195
+ declare function rectToArea(rect: Rect): Area;
196
+
197
+ /**
198
+ * DOM 测量层 —— `position` 包的另一半。
199
+ *
200
+ * 几何内核(`align.ts`)是纯函数:给它矩形与区域,它给出位移。
201
+ * 但「矩形与区域从哪来」这件事无法纯函数化 —— 它必须问 DOM。本模块就是那一次问询。
202
+ *
203
+ * 为什么这一层**必须**留在 `position` 而不是 `overlay`:
204
+ * `dependencies.json` 的 `purpose` 写的是「纯几何 **+ 尺寸测量**」,
205
+ * `ARCHITECTURE.md` §9.1 明确写着「别把 DOM 测量外壳误派给 overlay」。
206
+ * ⚠️ `src/index.ts` 与 `src/types.ts` 的旧头部注释说「测量由 overlay 完成后传入」,
207
+ * 与该契约冲突 —— 已随本次实现改正。
208
+ *
209
+ * 设计原则与几何内核一致:**能纯函数化的部分一律纯函数化**,
210
+ * DOM 侧只做「取值 + 临时改写 + 还原」这一件不可测的事。
211
+ * 于是 §「纯数据侧」的所有函数都可以被穷举测试,而副作用集中在 `measureAlign` 一处。
212
+ *
213
+ * 初期公式参考 `@rc-component/trigger@3.10.1` 的 `useAlign.js` / `util.js`。
214
+ * 已交付纯几何与 Oracle 保留;DOM 侧按当前所属文档、CSS 盒模型及恢复契约工作。
215
+ * 当前约定和带日期的历史证据见 `docs/foundation/position-contract.md`。
216
+ */
217
+
218
+ /**
219
+ * 最小矩形投影。
220
+ *
221
+ * `x/left` 与 `y/top` 两组都存在是因为 antd 有 `rect.x = rect.x ?? rect.left` 的
222
+ * 兼容分支:老浏览器(与部分 DOMRect polyfill)只提供 `left/top`。
223
+ * 这里把兼容逻辑收进 `toRect`,而不是散落在每个测量点。
224
+ */
225
+ interface RectLike {
226
+ x?: number;
227
+ y?: number;
228
+ left?: number;
229
+ top?: number;
230
+ width: number;
231
+ height: number;
232
+ }
233
+ /** `htmlRegion` 的合法取值。antd 里其余任何值(含 `undefined`)都降级为 `'visible'`。 */
234
+ type HtmlRegion = 'visible' | 'scroll' | 'visibleFirst';
235
+ /** target 的两种形态:元素(对齐到它)或坐标点(右键菜单这类场景)。 */
236
+ type MeasureTarget = Element | readonly [number, number];
237
+ interface MeasureInput {
238
+ /** 浮层元素。会被临时改写 inline style,结束时**一定**还原。 */
239
+ popupEle: HTMLElement;
240
+ target: MeasureTarget;
241
+ htmlRegion?: HtmlRegion;
242
+ /**
243
+ * 复用已收集的滚动容器。
244
+ *
245
+ * 缺省每次读取当前组合祖先树。复用列表的调用者负责在祖先/容器改变后重采;
246
+ * 列表内每个元素的当前 computed overflow 仍会按轴重新读取。
247
+ */
248
+ scrollers?: readonly HTMLElement[];
249
+ }
250
+ interface MeasureResult {
251
+ /** 目标矩形(已减 `targetOffset` 之前 —— 那是几何内核的事) */
252
+ target: Rect;
253
+ /** 浮层矩形,测得于「left/top 归零」之后 —— 见 `docs/.../position-contract.md` §3.1 */
254
+ popup: Rect;
255
+ /** 镜像矩形,测得于「right/bottom 归零」之后。仅用于 `offsetR` / `offsetB`。 */
256
+ mirror: Rect;
257
+ scaleX: number;
258
+ scaleY: number;
259
+ /** 几何计算用的区域(由 `htmlRegion` 选定) */
260
+ visible: Area;
261
+ /** 翻转判定用的区域。`htmlRegion='visibleFirst'` 时与 `visible` 不同。 */
262
+ check: Area;
263
+ }
264
+ interface ScaleResult {
265
+ scaleX: number;
266
+ scaleY: number;
267
+ }
268
+ /**
269
+ * antd 的 `toNum`:NaN → 兜底值。
270
+ *
271
+ * 这个兜底**不是防御性编程,是 jsdom 下唯一能让测量跑通的路径**:
272
+ * jsdom 没有布局引擎,`offsetWidth` 恒为 0,于是
273
+ * `rect.width / offsetWidth` = `0 / 0` = NaN,再经这里变回 1(无缩放)。
274
+ */
275
+ declare function toSafeNum(value: number, fallback?: number): number;
276
+ /** `parseFloat` 的 NaN → 0 版本。用于 border-width 这类「缺省即 0」的量。 */
277
+ declare function pxValue(value: string | undefined): number;
278
+ /**
279
+ * `htmlRegion` 归一化。
280
+ *
281
+ * antd(`useAlign.js` 185–193)只保留 `'scroll'` 与 `'visibleFirst'`,
282
+ * 其余(含 `undefined`、拼错的串)**一律**降级为 `'visible'`。
283
+ * 这是白名单而不是黑名单,反向抄成「非 visible 即 scroll」会改变默认行为。
284
+ */
285
+ declare function normalizeHtmlRegion(value: string | undefined): HtmlRegion;
286
+ /** 数组态 target → 0×0 矩形(`useAlign.js` 134–140)。 */
287
+ declare function pointRect(x: number, y: number): Rect;
288
+ /** DOMRect → Rect,含 `x ?? left` 兼容。 */
289
+ declare function toRect(rect: RectLike): Rect;
290
+ /**
291
+ * `scale === 1` 才取整(antd `useAlign.js` 483–490)。
292
+ *
293
+ * 有缩放时取整会把误差按 1/scale 放大 —— 这也是为什么必须在**除以 scale 之前**取整:
294
+ * antd 的顺序是「先 `Math.floor(nextOffsetX)`,再 `/ scaleX`」。
295
+ * `align.ts` 的 309–316 行已按此实现。
296
+ */
297
+ declare function scaleFloor(value: number, scale: number): number;
298
+ /**
299
+ * 由镜像矩形反解「浮层右缘到**容器**右缘的距离」(antd `useAlign.js` 481)。
300
+ *
301
+ * 与 `offsetX` 的差别:这个量是给 `dynamicInset` 走 `right` 定位用的
302
+ * (见 `useOffsetStyle.js` 19–32),而 `offsetX` 走 `left`。
303
+ */
304
+ declare function mirrorOffsetR(mirror: Rect, popup: Rect, offsetX: number): number;
305
+ /** `mirrorOffsetR` 的 Y 轴版本(antd `useAlign.js` 482)。 */
306
+ declare function mirrorOffsetB(mirror: Rect, popup: Rect, offsetY: number): number;
307
+ /**
308
+ * 早退判定(antd `useAlign.js` 224)。
309
+ *
310
+ * 抽成纯函数是为了让「早退」这条分支能被穷举 —— 在 jsdom 里
311
+ * `isVisible` 恒为 false(无布局引擎),真实早退路径**测不到**,
312
+ * 只能靠这个纯函数保证条件本身是对的。
313
+ *
314
+ * @param targetVisible 非元素形态的 target(坐标点)恒为 `true`
315
+ */
316
+ declare function shouldMeasure(scaleX: number, scaleY: number, targetVisible: boolean): boolean;
317
+ /**
318
+ * 取元素所属窗口(antd `util.js` 21–23)。
319
+ *
320
+ * 刻意不用全局 `window`:浮层可能挂在 iframe 的文档里。
321
+ * 所属 Document 没有 defaultView 时返回 null;普通 detached 元素仍保留其所属窗口。
322
+ */
323
+ declare function getWin(node: Node): Window | null;
324
+ declare function collectScroller(ele: Element): HTMLElement[];
325
+ declare function getVisibleArea(initArea: Area, scrollers: readonly HTMLElement[]): Area;
326
+ /**
327
+ * 视口可视区(antd `useAlign.js` 173–178)。
328
+ *
329
+ * 用 `documentElement` 的 `clientWidth/clientHeight` —— 即**不含**滚动条的可视区,
330
+ * 与「横向滚动条不该算作可放置区域」的意图一致。
331
+ */
332
+ declare function getViewportArea(doc: Document): Area;
333
+ /**
334
+ * 文档滚动区(antd `useAlign.js` 179–184)。
335
+ *
336
+ * ⚠️ 原点是 `(-scrollLeft, -scrollTop)` 而不是 0 —— 这是「文档坐标系」,
337
+ * 与视口坐标系差一个滚动量。写错成 0 会让 `htmlRegion='scroll'` 整体错位。
338
+ */
339
+ declare function getScrollArea(doc: Document): Area;
340
+ /** 元素的视口矩形。 */
341
+ declare function measureRect(ele: Element): Rect;
342
+ /**
343
+ * CSS `scale` 测量(antd `useAlign.js` 219–221)。
344
+ *
345
+ * 分子是**实测**矩形(`getBoundingClientRect`,受 transform 影响),
346
+ * 分母是 computed width/height 对应的未变换 border-box;content-box 时包含
347
+ * computed padding 与 border。两者之比才是缩放系数,不把装饰尺寸当成缩放。
348
+ *
349
+ * ⚠️ 分母可能是 `auto`(内容撑开的浮层)→ `parseFloat` → NaN → 比值 NaN →
350
+ * 经 `toSafeNum` 兜底为 1。这也是 jsdom 下的常态(见 `toSafeNum` 注释)。
351
+ */
352
+ declare function measureScale(ele: Element, rect: Rect, win: Window): ScaleResult;
353
+ /**
354
+ * 一次完整的测量:归零 → 测 → 镜像 → 还原。
355
+ *
356
+ * 对应 antd `onAlign` 的 DOM 侧(94–226 行)。**顺序不可调换**:
357
+ * 1. 插 placeholder —— 防止 popup 被改成 `left:0` 时父容器塌陷,塌陷会污染后续 rect
358
+ * 2. 归零 left/top —— 见 `position-contract.md` §3.1,这一步让 `getPopupContainer`
359
+ * 的容器偏移在减法里自动消掉,是**免换算**的关键
360
+ * 3. 测 target / popup —— 必须在归零之后
361
+ * 4. 镜像 right/bottom —— 只为了 offsetR / offsetB
362
+ * 5. 还原 + 移除 placeholder(早退也**必须**还原 —— antd 把还原放在早退之前)
363
+ *
364
+ * ⚠️ 与 antd 的一处**有意**差异:antd 没有 `try/finally`,任一步抛异常就会把
365
+ * `left:0/right:0/overflow:hidden` 永久留在 inline style 上。这里用 `finally`
366
+ * 包住,保证任何退出路径都还原。正常路径下两者的可观测行为完全一致,
367
+ * 差异只出现在异常路径 —— 而异常路径下 antd 的行为是「把浮层留在错误位置」,
368
+ * 不构成需要复刻的契约。
369
+ *
370
+ * @returns 不可测时 `null`(scale 为 0,或目标是元素但不可见)。
371
+ * 对应 antd 的 `return` —— 此时调用方不应更新浮层位置。
372
+ */
373
+ declare function measureAlign(input: MeasureInput): MeasureResult | null;
374
+
375
+ /**
376
+ * 解析单个 offset:`12` → 12;`'50%'` → size * 0.5。
377
+ *
378
+ * antd 的实现是 `offsetStr.match(/^(.*)\%$/)`,注意它用 `.*` 贪婪匹配,
379
+ * 因此 `'abc%'` 会得到 parseFloat('abc') = NaN。这里等价保留 NaN 语义 ——
380
+ * 因为下游 `getNumberOffset` 对 `offset || []` 的默认处理依赖它,
381
+ * 擅自改成 0 会改变未定义行为下的一致性。
382
+ */
383
+ declare function getUnitOffset(size: number, offset?: OffsetType): number;
384
+ /**
385
+ * 把 [x, y] 的 offset 解析为像素。
386
+ * 百分比相对 `rect` 自身的宽高。
387
+ */
388
+ declare function getNumberOffset(rect: Rect, offset?: readonly OffsetType[]): [number, number];
389
+
390
+ /**
391
+ * antd 的 12 个 placement → 对齐点。
392
+ *
393
+ * 逐字取自 antd 6.6.4 `es/_util/placements.js` 的 `PlacementAlignMap`。
394
+ * `points: [浮层点, 目标点]` —— 例如 `top: ['bc','tc']` 表示浮层下边缘中点
395
+ * 对齐目标上边缘中点(浮层在目标上方)。
396
+ */
397
+ declare const PLACEMENT_POINTS: Record<Placement, readonly [AlignPoint, AlignPoint]>;
398
+ /**
399
+ * `arrowPointAtCenter` 为真时,8 个角 placement 改用这套点,
400
+ * 使箭头指向目标中心而不是目标角。
401
+ */
402
+ declare const ARROW_CENTER_PLACEMENT_POINTS: Partial<Record<Placement, readonly [AlignPoint, AlignPoint]>>;
403
+ /**
404
+ * antd `getArrowOffsetToken({ contentRadius, limitVerticalRadius: true })`。
405
+ * 圆角大于 12 时箭头偏移随之增大,否则固定 12。
406
+ */
407
+ declare function getArrowOffsetToken(contentRadius: number): {
408
+ arrowOffsetHorizontal: number;
409
+ arrowOffsetVertical: number;
410
+ };
411
+ /**
412
+ * antd `getOverflowOptions`。
413
+ *
414
+ * 关键点:`shiftX` / `shiftY` 一旦为真值就不会再设置 `adjustX` / `adjustY`;
415
+ * 未设置 shift 的轴才回退为 adjust。8 个角 placement 的 `baseOverflow` 是空的,
416
+ * 因此它们只做翻转、不做平移。
417
+ */
418
+ declare function getOverflowOptions(placement: Placement, arrowWidth: number, borderRadius: number, autoAdjustOverflow?: boolean | OverflowConfig): OverflowConfig;
419
+ /**
420
+ * 生成 12 个 placement 的完整配置 —— antd `getPlacements` 的等价实现。
421
+ *
422
+ * 生成的 `offset` 已包含箭头宽度的一半与 `config.offset` 间距,
423
+ * 因此调用方不需要再自己加箭头偏移。
424
+ */
425
+ declare function getPlacements(config: PlacementConfig): Record<Placement, AlignType>;
426
+
427
+ /**
428
+ * 把 'tl' 这样的两字符点拆成 [垂直, 水平] 两个分量。
429
+ *
430
+ * antd 的实现是 `splitPoints(points = '')` → `[points[0], points[1]]`,
431
+ * 对空串会返回 [undefined, undefined],靠后续 fallthrough 到 center。
432
+ * 这里显式兜底为 'c',语义等价但类型安全。
433
+ */
434
+ declare function splitPoints(point: string): [VerticalPoint, HorizontalPoint];
435
+ interface Point {
436
+ x: number;
437
+ y: number;
438
+ }
439
+ /**
440
+ * 求矩形上某个对齐点的坐标。
441
+ *
442
+ * 't' → 上边,'b' → 下边,其它(含 'c')→ 垂直中心
443
+ * 'l' → 左边,'r' → 右边,其它(含 'c')→ 水平中心
444
+ */
445
+ declare function getAlignPoint(rect: Rect, point: readonly [VerticalPoint, HorizontalPoint]): Point;
446
+ /** 从矩形按点取坐标的便捷重载:直接传 'tl' 这样的字符串。 */
447
+ declare function alignPointOf(rect: Rect, point: AlignPoint | string): Point;
448
+ /**
449
+ * 翻转某个分量:t↔b,l↔r,c 保持 c。
450
+ *
451
+ * @param point 长度为 2 的分量数组
452
+ * @param index 0 = 垂直分量,1 = 水平分量
453
+ */
454
+ declare function reversePoint(point: readonly [VerticalPoint, HorizontalPoint], index: 0 | 1): [VerticalPoint, HorizontalPoint];
455
+ /** ['t','c'] → 'tc' */
456
+ declare function flatPoint(point: readonly [VerticalPoint, HorizontalPoint]): string;
457
+
458
+ export { ARROW_CENTER_PLACEMENT_POINTS, PLACEMENT_POINTS, alignPointOf, alignPopup, clipArea, collectScroller, flatPoint, getAlignPoint, getArrowOffsetToken, getIntersectionArea, getNumberOffset, getOverflowOptions, getPlacements, getScrollArea, getUnitOffset, getViewportArea, getVisibleArea, getWin, measureAlign, measureRect, measureScale, mirrorOffsetB, mirrorOffsetR, normalizeHtmlRegion, pointRect, pxValue, rectToArea, reversePoint, scaleFloor, shouldMeasure, splitPoints, toAlignResult, toRect, toSafeNum };
459
+ export type { AlignContext, AlignOutcome, AlignPoint, AlignResult, AlignType, Area, FlipMemory, HorizontalPoint, HtmlRegion, MeasureInput, MeasureResult, MeasureTarget, OffsetType, OverflowConfig, Placement, PlacementConfig, Point, Rect, RectLike, ScaleResult, VerticalPoint };