floatPanelAnchors.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. /**
  2. * 浮动面板锚点:合并集合并按规则吸附到合法 height(无 Vue 依赖,便于测试与复用)。
  3. */
  4. export type AnchorsSnapResult = { anchors: number[]; height: number };
  5. /**
  6. * 根据新锚点候选合并列表,并计算吸附后的 `height`。
  7. * @param values - 本次写入的锚点(或单个)
  8. * @param reset - 为 `true` 时以 `values` 为基底;为 `false` 时与 `currentAnchors` 合并
  9. * @param prevHeight - 吸附前的面板总高度
  10. * @param maxContainerHeight - 允许的最大总高度(px)
  11. * @param currentAnchors - 当前 v-model 锚点(`reset === false` 时参与合并)
  12. */
  13. export function computeAnchorsAndSnapHeight(
  14. values: number | number[],
  15. reset: boolean,
  16. prevHeight: number,
  17. maxContainerHeight: number,
  18. currentAnchors: readonly number[],
  19. ): AnchorsSnapResult | null {
  20. const list = !Array.isArray(values) ? [values] : values;
  21. const set = new Set(list);
  22. if (!reset) currentAnchors.forEach((a) => set.add(a));
  23. const anchors = [...set].filter((v) => v <= maxContainerHeight).sort((a, b) => a - b);
  24. if (!anchors.length) return null;
  25. const hit = anchors.findIndex((v) => v === prevHeight);
  26. if (hit >= 0) {
  27. return { anchors, height: anchors[hit]! };
  28. }
  29. let nearest = anchors[0]!;
  30. let minDist = Math.abs(prevHeight - nearest);
  31. for (const a of anchors) {
  32. const d = Math.abs(prevHeight - a);
  33. if (d < minDist) {
  34. minDist = d;
  35. nearest = a;
  36. }
  37. }
  38. const positives = anchors.filter((a) => a > 0);
  39. if (nearest === 0 && prevHeight > 0 && positives.length) {
  40. const height = positives.reduce((m, a) => (Math.abs(a - prevHeight) < Math.abs(m - prevHeight) ? a : m));
  41. return { anchors, height };
  42. }
  43. return { anchors, height: nearest };
  44. }