invariant.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import type { VisualRegionDefinition } from "./regions"
  2. type RegionSet<RegionName extends string> = readonly RegionName[] | "all"
  3. export type VisualInvariant<RegionName extends string = string> =
  4. | { type: "required"; regions: readonly RegionName[] }
  5. | { type: "continuous-any"; regions: readonly RegionName[] }
  6. | { type: "unique"; regions: readonly RegionName[] }
  7. | { type: "stable"; regions: readonly RegionName[] }
  8. | { type: "fixed"; regions: readonly RegionName[]; tolerance?: number }
  9. | { type: "opacity"; regions: RegionSet<RegionName>; floor?: number }
  10. | {
  11. type: "motion"
  12. regions: RegionSet<RegionName>
  13. tolerance?: number
  14. maxReversals?: number
  15. maxPositionReversals?: number
  16. }
  17. | { type: "continuity"; regions: RegionSet<RegionName> }
  18. | { type: "label-stability"; regions: RegionSet<RegionName> }
  19. | { type: "flow"; regions: readonly RegionName[]; overlapTolerance?: number }
  20. | { type: "preserve-bottom-anchor" }
  21. | { type: "acquire-bottom-anchor" }
  22. export type VisualPlan<RegionName extends string = string> = {
  23. regionNames?: readonly RegionName[]
  24. invariants: readonly VisualInvariant<RegionName>[]
  25. markerRequired?: readonly RegionName[]
  26. perMarker?: boolean
  27. aggregateMotion?: boolean
  28. }
  29. export type LegacyVisualStabilityOptions<RegionName extends string = string> = {
  30. flow?: RegionName[]
  31. motionTolerance?: number
  32. opacityFloor?: number
  33. overlapTolerance?: number
  34. maxReversals?: number
  35. maxPositionReversals?: number
  36. stable?: RegionName[]
  37. fixed?: RegionName[]
  38. motion?: RegionName[]
  39. unique?: RegionName[]
  40. preserveBottomAnchor?: boolean
  41. acquireBottomAnchor?: boolean
  42. perMarker?: boolean
  43. continuousAny?: RegionName[][]
  44. required?: RegionName[]
  45. aggregateMotion?: boolean
  46. inferRequired?: boolean
  47. }
  48. export function visualPlan<const Regions extends Record<string, VisualRegionDefinition>>(
  49. regions: Regions,
  50. invariants: readonly VisualInvariant<Extract<keyof Regions, string>>[],
  51. options: Omit<VisualPlan<Extract<keyof Regions, string>>, "regionNames" | "invariants"> = {},
  52. ): VisualPlan<Extract<keyof Regions, string>> {
  53. return { ...options, regionNames: Object.keys(regions) as Extract<keyof Regions, string>[], invariants }
  54. }
  55. export function legacyVisualPlan<RegionName extends string>(
  56. options: LegacyVisualStabilityOptions<RegionName> = {},
  57. ): VisualPlan<RegionName> {
  58. const inferred =
  59. options.inferRequired === false
  60. ? []
  61. : [
  62. ...(options.stable ?? []),
  63. ...(options.fixed ?? []),
  64. ...(options.unique ?? []),
  65. ...(options.motion ?? []),
  66. ...(options.flow ?? []),
  67. ]
  68. return {
  69. perMarker: options.perMarker,
  70. aggregateMotion: options.aggregateMotion,
  71. markerRequired: [
  72. ...(options.required ?? []),
  73. ...(options.stable ?? []),
  74. ...(options.fixed ?? []),
  75. ...(options.unique ?? []),
  76. ...(options.motion ?? []),
  77. ...(options.flow ?? []),
  78. ],
  79. invariants: [
  80. { type: "required", regions: [...(options.required ?? []), ...inferred] },
  81. ...(options.continuousAny ?? []).map(
  82. (regions): VisualInvariant<RegionName> => ({ type: "continuous-any", regions }),
  83. ),
  84. ...(options.unique ? [{ type: "unique" as const, regions: options.unique }] : []),
  85. ...(options.stable ? [{ type: "stable" as const, regions: options.stable }] : []),
  86. ...(options.fixed
  87. ? [{ type: "fixed" as const, regions: options.fixed, tolerance: options.motionTolerance }]
  88. : []),
  89. { type: "opacity", regions: "all", floor: options.opacityFloor },
  90. { type: "continuity", regions: "all" },
  91. {
  92. type: "motion",
  93. regions: options.motion ?? "all",
  94. tolerance: options.motionTolerance,
  95. maxReversals: options.maxReversals,
  96. maxPositionReversals: options.maxPositionReversals,
  97. },
  98. { type: "label-stability", regions: "all" },
  99. ...(options.preserveBottomAnchor ? [{ type: "preserve-bottom-anchor" as const }] : []),
  100. ...(options.acquireBottomAnchor ? [{ type: "acquire-bottom-anchor" as const }] : []),
  101. ...(options.flow
  102. ? [{ type: "flow" as const, regions: options.flow, overlapTolerance: options.overlapTolerance }]
  103. : []),
  104. ],
  105. }
  106. }