Skip to content

fix: cleanup API surface - #1

Merged
ryu-man merged 30 commits into
mainfrom
api-surface-cleanup
Aug 15, 2026
Merged

fix: cleanup API surface#1
ryu-man merged 30 commits into
mainfrom
api-surface-cleanup

Conversation

@ryu-man

@ryu-man ryu-man commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

No description provided.

ryu-man and others added 30 commits July 30, 2026 20:44
Unused default Storybook install (config, sample stories, assets).
Nothing imported it and it was not wired into any script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drops the Storybook devDependencies, eslint plugin and vitest project left
behind by the scaffolding removal. Adds a single `verify` script (check +
lint + test + size budgets) and a GitHub workflow that runs it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tabs + single quotes to match the rest of the repo; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Root entry now re-exports every module, not just animate + flip, so the
old 12 kB budget measured the wrong thing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fourteen module READMEs and INDEX.md restated what the TSDoc and the root
README already say, and drifted every time the API moved. One document to
keep in sync instead of sixteen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…elpers

Three concerns were duplicated across attachments, JS-driven animations and
the property registry:
- frame-batch: one rAF per burst of subscribers instead of one each
- frame-tween: shared delay/completion/cancellation lifecycle
- inline-style: save/restore of a forced inline property + !important

Also drops the shared/index.ts barrel — callers import the module they need.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
animate/flip.ts reimplemented what flip/ already does with a richer option
set; its exports (flipFromRect, flipToRect, captureRect, AnimateFlipRect) are
gone — use @ixirjs/pulse/flip instead. animate/spring.ts was a second copy of
shared/spring-core, now re-exported from there. style-utils moved to
shared/inline-style.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Helpers with no callers outside their module lose their export; adds a
transform-tracker browser test covering concurrent transform components.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Forward FLIP that animates an element from its current box to a target
element's box, for popovers/tooltips that adopt an anchor's geometry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- geometry lives in one module instead of being split across animate/ and flip/
- cancel-controller folded into controller-slot; scope into bridge;
  observer-manager + scheduler into tracking/observers
- delta math (computeDelta, isIdentityDelta, diagonal, rectsEqual, FlipDelta,
  DeltaOptions) is no longer exported — it is an animator internal

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flipTo was cut as dead code while flipFrom survived, leaving the surface with
a 'from' half and no 'to' half. It is a four-line forward-FLIP wrapper over
animateFlip; the symmetry is worth more than the lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nearest-slot search and ascending-order check are pure functions that were
buried in the attachment; testing them no longer needs a browser. Binary search
for ordinary linear lists, linear scan for wrapped/grid layouts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
planMorph, interpolatePlan, toPathString, subdivideTo, alignSubpaths,
minimizeAnchorTravel, rotateClosed, reverseClosed and MorphPlan are no longer
exported. morph, parsePath and normalizePath are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ernals

Same surface trim as the other modules: helpers with no callers outside their
own file stop being part of the package API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t scroll index

Directory imports resolved fine in this repo but not in consuming Vite
projects; easing was reachable only via the subpath export.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Interactive playground for the new modules; app.html applies the stored or
system theme before first paint to avoid a flash.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…llbacks

Two duplications kept reappearing across modules: every attachment hand-wired
addEventListener/removeEventListener pairs (one cast per handler, one mirrored
removal that could drift), and both animation controllers repeated the same
pause/play/reverse/seek fan-out over a group of WAAPI animations.

- `shared/listen` attaches a typed handler map and returns the exact teardown.
- `shared/playback` owns the transport controls, including the swallowed throw
  when seeking a cancelled animation.
- `isBrowser()` checks only `document`; a `window`-less DOM is not a case we
  support, and the extra clause implied it was.
- `createFrameBatch` no longer falls back to a synchronous callback when
  `requestAnimationFrame` is missing. Tests run in a real DOM, so the fallback
  only masked scheduling bugs. Sampling assertions that relied on the extra
  synchronous frame drop from >3 to >2 samples.

`scroll()` moves to `listen()` as the first consumer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both controllers drop their hand-rolled pause/play/reverse/seek in favour of
the shared fan-out. `wireTransform()` pairs the two setup calls every caller
already made together (`ensurePropertiesRegistered` + `ensureTransformWired`),
so no caller can register the properties and forget the element's chain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ease`, `easeIn`, `easeOut` and `easeInOut` are exactly the CSS keyword curves,
yet `easingToCss` resampled them into 25-point `linear(…)` approximations of
timing the browser already implements natively. Tag each with its keyword —
mirroring how `springEasing` carries `_linearEasing` — and prefer that tag.

Exact timing, smaller keyframes. The resampling tests move to `backOut`, which
genuinely has no keyword form.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The types file held only the `Timeline` / `TimelineDefaults` interfaces that
`timeline.ts` implements and re-exported anyway. One file, one indirection less
between a method's docs and its body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`createVariants()` and the `variants()` attachment that drives it were split
across two files for no reason beyond the `.svelte.ts` suffix — which the
merged file needs anyway. The index now re-exports from a single module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`size()` and the `width`/`height` fields of the other presence transitions both
grew a box from a start fraction to its natural footprint, but each carried its
own metric snapshot and its own declaration list — the `size()` copy spelled
all fourteen properties out by hand and could drift from `AXIS_SPACING`.

Both now go through `axisMetrics()` + `growDecls()`. `size()` keeps its
fraction-valued `start` (no `resolveSize()` round-trip) by building its plans
directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every gesture attachment repeated the same three things: a cast-per-handler
addEventListener block mirrored by a removal block, the `touch-action` save /
set / restore dance, and — in `draggable` and `swipe` — an inlined copy of
two-sample pointer velocity tracking.

- All of them now wire events through `shared/listen`.
- `lockTouchAction(element, axis)` joins the pointer-capture helpers, since it
  is the same "hand this axis to us, put it back on teardown" concern.
- `trackVelocity()` holds the velocity model once, with the reason two samples
  is the whole model written down where it lives.
- `release()` drops its `hasPointerCapture` guard — the try/catch it sits
  inside already covers the only case it could catch.
- `reorder-internals` folds back into `reorder.ts` as a single linear
  `nearestCenterIndex`. The binary-search path plus its `centersAreAscending`
  precondition was real complexity for lists of a dozen elements; the test
  moves alongside it.

**Breaking:** `disabled` is gone from every gesture attachment. It only ever
short-circuited setup, so flipping it did nothing to an already-live
attachment; conditional attachment (`{@Attach on ? draggable() : undefined}`)
is the form that actually tears the listeners down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`auto` asked the caller to do the library's job. `auto: () => { void open }`
read the state, discarded it, and left flip to re-measure a frame later — and
the `ObserverManager` variant made an internal a public type just so it could
be passed back in.

Instead the attachment owns the attribute:

    <div {@Attach flip({ class: () => ({ 'is-open': open }) })}>

The thunk is tracked, so flip writes the class (or inline style) itself and
measures `from` → write → `to` → animate in one tick. Writes are diffed against
what flip previously applied — never a wholesale `className` / `cssText`
assignment — so markup classes and the animator's own `--motion-*` writes
survive. Style strings go through the platform's parser (a detached element),
which handles `!important`, comments, and malformed input for free.

Layout tracking (`ResizeObserver` + parent `MutationObserver`) is now
unconditional, so a shuffled `{#each}` animates with no options at all.

Also in this pass:

- Interrupted runs converge. A burst of retargets (drag, rapid shuffles) used
  to restart the full duration on every trigger and never settle; the slot now
  charges elapsed time against the replacement, floored at 120ms.
- `carryVisualOffset()` re-expresses a live visual rect in the previous layout
  frame. An in-flight transform is an offset from the resting box, so once
  layout changes the raw visual rect belongs to the *new* box — using it
  directly made an interrupting FLIP shoot out of place.
- The skip counter only counts cycles that would actually animate, so
  `skip: (n) => n === 0` lands on the first real move rather than the no-op
  callback observers fire on connect.
- `flip/animation/`, `flip/integration/`, and `flip/tracking/` flatten into
  `flip/`. Three directories held one to four files each and every import paid
  for the nesting.
- `DEFAULT_DURATION` → `FLIP_DEFAULT_DURATION`, unambiguous next to the
  animate-side default; the layout bridge drops its `performance.now` fallback.

**Breaking:** `auto`, the `FlipAuto` type, and `createObserverManager` /
`ObserverManager` are removed from the public API. Delete `auto:` from existing
call sites — tracking is on by default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a card for the new option and drops the now-unnecessary `auto:` thunks
from the existing FLIP demos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`repository.url` gets the `git+` prefix npm expects, and the CHANGELOG release
link points at ixirjs/pulse instead of the old svelte-atoms/vibra path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ryu-man
ryu-man merged commit 998a53c into main Aug 15, 2026
1 check failed
@ryu-man
ryu-man deleted the api-surface-cleanup branch August 15, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant