You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/arts/floating/middleware/offset.ts

64 lines
2.1 KiB

feat(floating): P1 complete — detectOverflow + middleware + compute + autoUpdate (flag-gated) Finishes the in-house positioning engine that replaces @floating-ui. The DOM-read core (rects + clipping ancestor-walk) was already committed; this adds the rest: - engine/overflow.ts — detectOverflow, PURE over the pre-read clipping + viewportDelta/offsetScale (no DOM read in the middleware phase). - engine/middleware/{offset,shift,flip,arrow,size,hide,limit-shift}.ts — each a factory with @floating-ui's call signatures + index barrel. - engine/compute.ts — one dom.measure read phase → the pure, exported runMiddleware loop (base coords → chain → flip/arrow reset, capped at 50). - engine/auto-update.ts — ancestor scroll/resize via $adom listen, element resize via ResizeObserver, optional rAF loop; no raw window/getBoundingClientRect. - MiddlewareState gains viewportDelta/offsetScale/rtl (precomputed upfront so the middleware stay pure); MiddlewareReturn.reset widened to boolean | {placement}. - geometry.ts gains getOppositeAxis/getAlignmentSides/getExpandedPlacements/ getOppositeAxisPlacements; clipping.ts exports getOverflowAncestors. - Middleware de-vendored from @floating-ui: engine/types owns it, ../types re-exports it as the public surface. - engine/flag.ts — USE_OWN_ENGINE A/B switch, default OFF (runtime still @floating-ui). use-floating + floating.svelte branch on it and thread `dom`. Verified: engine/engine.test.ts — 13-case parity guard proving runMiddleware is pixel-identical to @floating-ui/core on synthetic rects (offset, flip with/without overflow, shift, limitShift, arrow centerOffset + alignmentOffset, size, hide, full chain with viewportDelta/scroll, rtl). npm run check at baseline (0 new). Note: the engine/ dir keeps its P1 no-semicolon style (pre-existing), so it does not match the repo's prettier semi:true; left as-is to avoid reformatting the committed P1 files. P2/P3 (broad foreground-browser A/B) + P4 (@floating-ui removal) remain — see engine CONTINUE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
// offset — shifts the floating element along the main / cross / alignment axes.
// PURE. Reimplemented from floating-ui's `offset` (spec).
refactor(floating): relocate the pure engine to the `$floating` art The positioning engine is pure collision geometry over `$adom` — a runtime artifact, not soma-specific. Moves it out of `soma/layers/floating/engine/` into a new `arts/floating` art (`$floating`), exactly parallel to `$motion`: both soma (the JS positioning path) and eidos (the CSS-anchor path, future) build on it, so the shared pure core belongs in arts, not buried in one consumer. Moved to `$floating` (git renames, history preserved): geometry, rects, clipping, overflow, supports, compute, auto-update, flag, types, the 7 middleware, and the two parity test suites. `placement.ts` and the contract types `Measurable` / `Middleware` / `MiddlewareData` move too — `$floating` is now self-contained (depends only on `$adom`, never back on soma). New `arts/floating/index.ts` barrel is the public surface. Alias `$floating` added to vite.config.ts + svelte.config.js. Stays in `soma/layers/floating/` (reactive composition): use-floating.svelte.ts (runes), floating.svelte.ts (providers/context), shell.ts, safe-polygon.ts, utils.ts, the reactive types, index.ts. These now import `$floating`; soma's types.ts/index.ts + soma/types/index.ts re-export the placement/contract types. Two latent type gaps the typed `$floating` surface exposed (the soma loose-factory shim had hidden them) are fixed: `DetectOverflowOptions` now declares `boundary` (the middleware genuinely accept it; compute's read phase extracts it), and a `size` test's `apply` returns void. `@floating-ui` is UNTOUCHED — P4 deliberately NOT executed: the dep stays installed, the fui imports + the `USE_OWN_ENGINE` flag (still OFF) remain in the wrappers, the runtime still positions via floating-ui. Verified: 350 synthetic + 137 real-DOM parity cases green from the new location, soma overlay providers green, check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
import type { Placement } from '../placement'
feat(floating): P1 complete — detectOverflow + middleware + compute + autoUpdate (flag-gated) Finishes the in-house positioning engine that replaces @floating-ui. The DOM-read core (rects + clipping ancestor-walk) was already committed; this adds the rest: - engine/overflow.ts — detectOverflow, PURE over the pre-read clipping + viewportDelta/offsetScale (no DOM read in the middleware phase). - engine/middleware/{offset,shift,flip,arrow,size,hide,limit-shift}.ts — each a factory with @floating-ui's call signatures + index barrel. - engine/compute.ts — one dom.measure read phase → the pure, exported runMiddleware loop (base coords → chain → flip/arrow reset, capped at 50). - engine/auto-update.ts — ancestor scroll/resize via $adom listen, element resize via ResizeObserver, optional rAF loop; no raw window/getBoundingClientRect. - MiddlewareState gains viewportDelta/offsetScale/rtl (precomputed upfront so the middleware stay pure); MiddlewareReturn.reset widened to boolean | {placement}. - geometry.ts gains getOppositeAxis/getAlignmentSides/getExpandedPlacements/ getOppositeAxisPlacements; clipping.ts exports getOverflowAncestors. - Middleware de-vendored from @floating-ui: engine/types owns it, ../types re-exports it as the public surface. - engine/flag.ts — USE_OWN_ENGINE A/B switch, default OFF (runtime still @floating-ui). use-floating + floating.svelte branch on it and thread `dom`. Verified: engine/engine.test.ts — 13-case parity guard proving runMiddleware is pixel-identical to @floating-ui/core on synthetic rects (offset, flip with/without overflow, shift, limitShift, arrow centerOffset + alignmentOffset, size, hide, full chain with viewportDelta/scroll, rtl). npm run check at baseline (0 new). Note: the engine/ dir keeps its P1 no-semicolon style (pre-existing), so it does not match the repo's prettier semi:true; left as-is to avoid reformatting the committed P1 files. P2/P3 (broad foreground-browser A/B) + P4 (@floating-ui removal) remain — see engine CONTINUE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
import { getAlignment, getSide, getSideAxis } from '../geometry'
import type { Coords, Middleware, MiddlewareState } from '../types'
export type OffsetOptions =
| number
| { mainAxis?: number; crossAxis?: number; alignmentAxis?: number | null }
const ORIGIN_SIDES = new Set<string>(['left', 'top'])
function convertValueToCoords(state: MiddlewareState, options: OffsetOptions): Coords {
const { placement, rtl } = state
const side = getSide(placement)
const alignment = getAlignment(placement)
const isVertical = getSideAxis(placement) === 'y'
const mainAxisMulti = ORIGIN_SIDES.has(side) ? -1 : 1
const crossAxisMulti = rtl && isVertical ? -1 : 1
let mainAxis: number
let crossAxis: number
let alignmentAxis: number | null
if (typeof options === 'number') {
mainAxis = options
crossAxis = 0
alignmentAxis = null
} else {
mainAxis = options.mainAxis || 0
crossAxis = options.crossAxis || 0
alignmentAxis = options.alignmentAxis ?? null
}
if (alignment && typeof alignmentAxis === 'number') {
crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis
}
return isVertical
? { x: crossAxis * crossAxisMulti, y: mainAxis * mainAxisMulti }
: { x: mainAxis * mainAxisMulti, y: crossAxis * crossAxisMulti }
}
export function offset(options: OffsetOptions = 0): Middleware {
return {
name: 'offset',
options,
fn(state) {
const { x, y, placement, middlewareData } = state
const diff = convertValueToCoords(state, options)
// If the placement is unchanged and the arrow applied an alignment
// offset, leave the coords alone (offset already ran for this placement).
const offsetData = middlewareData.offset as { placement?: Placement } | undefined
const arrowData = middlewareData.arrow as { alignmentOffset?: number } | undefined
if (placement === offsetData?.placement && arrowData?.alignmentOffset) {
return {}
}
return { x: x + diff.x, y: y + diff.y, data: { ...diff, placement } }
}
}
}

Powered by TurnKey Linux.