@flemo/core 2.0.0 → 2.2.0

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.
Files changed (59) hide show
  1. package/dist/core/TaskManger.d.ts +5 -5
  2. package/dist/core/createRouterScope.d.ts +1 -0
  3. package/dist/core/engine/__tests__/createSwipeController.nativeDrag.test.d.ts +1 -0
  4. package/dist/core/engine/__tests__/createSwipeController.rideOffset.test.d.ts +1 -0
  5. package/dist/core/engine/__tests__/createTransitionEngine.barPartStaging.test.d.ts +1 -0
  6. package/dist/core/engine/__tests__/platformDefaults.test.d.ts +1 -0
  7. package/dist/core/engine/__tests__/riderSwipe.test.d.ts +1 -0
  8. package/dist/core/engine/__tests__/variantStub.d.ts +13 -0
  9. package/dist/core/engine/barPartStaging.d.ts +39 -0
  10. package/dist/core/engine/cancelResume.d.ts +6 -0
  11. package/dist/core/engine/createSwipeController.d.ts +10 -0
  12. package/dist/core/engine/flightRouting.d.ts +2 -2
  13. package/dist/core/engine/layerSettleHold.d.ts +0 -1
  14. package/dist/core/engine/riderSwipe.d.ts +28 -0
  15. package/dist/core/engine/types.d.ts +1 -0
  16. package/dist/dom/__tests__/staging.test.d.ts +1 -0
  17. package/dist/dom/attributes.d.ts +57 -4
  18. package/dist/dom/staging.d.ts +33 -0
  19. package/dist/history/createHistorySync.d.ts +2 -0
  20. package/dist/history/historyDriver.d.ts +2 -0
  21. package/dist/index.d.ts +6 -3
  22. package/dist/index.mjs +2093 -2107
  23. package/dist/morph/attachMorph.d.ts +14 -0
  24. package/dist/platform/profile.d.ts +41 -15
  25. package/dist/screen/__tests__/partLayer.test.d.ts +1 -0
  26. package/dist/screen/__tests__/publishRideBox.test.d.ts +1 -0
  27. package/dist/screen/computeScreenFreeze.d.ts +0 -1
  28. package/dist/screen/partLayer.d.ts +14 -0
  29. package/dist/screen/publishRideBox.d.ts +1 -0
  30. package/dist/transition/__tests__/compileTransitionStyles.test.d.ts +2 -0
  31. package/dist/transition/__tests__/partClockInheritance.test.d.ts +1 -0
  32. package/dist/transition/__tests__/rideOffset.test.d.ts +1 -0
  33. package/dist/transition/compileTransitionStyles.d.ts +11 -2
  34. package/dist/transition/decorator/__tests__/resolveDecoratorClock.test.d.ts +14 -0
  35. package/dist/transition/decorator/createDecorator.d.ts +4 -5
  36. package/dist/transition/decorator/createRawDecorator.d.ts +10 -11
  37. package/dist/transition/decorator/resolveDecoratorClock.d.ts +36 -0
  38. package/dist/transition/decorator/typing.d.ts +8 -2
  39. package/dist/transition/gestureScrub.d.ts +33 -0
  40. package/dist/transition/partTransition/__tests__/resolvePartDefinition.test.d.ts +1 -0
  41. package/dist/transition/partTransition/createPartTransition.d.ts +4 -5
  42. package/dist/transition/partTransition/createRawPartTransition.d.ts +10 -11
  43. package/dist/transition/partTransition/partTransition.d.ts +17 -0
  44. package/dist/transition/partTransition/resolvePartClock.d.ts +41 -0
  45. package/dist/transition/partTransition/typing.d.ts +8 -2
  46. package/dist/transition/rideOffset.d.ts +5 -0
  47. package/dist/transition/swipeSettle.d.ts +5 -1
  48. package/dist/transition/typing.d.ts +12 -1
  49. package/package.json +1 -1
  50. package/dist/core/engine/__tests__/diagnosticRegistry.test.d.ts +0 -10
  51. package/dist/core/engine/diagnosticFlags.d.ts +0 -16
  52. package/dist/core/engine/diagnosticRegistry.d.ts +0 -38
  53. package/dist/core/engine/imageRevealHold.d.ts +0 -2
  54. package/dist/morph/morphAnimations.d.ts +0 -2
  55. /package/dist/core/engine/__tests__/{createTransitionEngine.flightImageHold.test.d.ts → barPartStaging.test.d.ts} +0 -0
  56. /package/dist/core/engine/__tests__/{diagnosticFlags.test.d.ts → createSwipeController.barPartStaging.test.d.ts} +0 -0
  57. /package/dist/core/engine/__tests__/{documentedDefaults.test.d.ts → createSwipeController.dragProgress.test.d.ts} +0 -0
  58. /package/dist/core/engine/__tests__/{imageRevealHold.test.d.ts → createSwipeController.dragRiders.test.d.ts} +0 -0
  59. /package/dist/{morph/__tests__/morphAnimations.test.d.ts → core/engine/__tests__/createSwipeController.forcedCancel.test.d.ts} +0 -0
@@ -46,6 +46,20 @@ interface MorphFlight {
46
46
  * stage a flight the runtime does not clock.
47
47
  */
48
48
  export declare const stageHeldFlights: (store: NavigateStoreApi, status: NavigateStatus) => MorphFlight[];
49
+ /**
50
+ * Forget any delivery the navigation never came to collect. A NEW gesture
51
+ * supersedes the last one; only the gesture's own entry point may do this,
52
+ * because the navigation reaches the scope through `stageHeldFlights` too and
53
+ * clearing there would erase the mark a moment before reading it.
54
+ */
55
+ /**
56
+ * Record that a gesture's release has DELIVERED whatever it is carrying — it
57
+ * plays those flights out to the arrival itself, so the navigation it commits
58
+ * must not stage them again. Whatever is in the air at the release is exactly
59
+ * what the gesture delivers. See `MorphScope.delivered`.
60
+ */
61
+ export declare const clearGestureDeliveries: (store: NavigateStoreApi) => void;
62
+ export declare const markGestureDelivered: (store: NavigateStoreApi) => void;
49
63
  /** The flights a scope currently holds — the nested ones included. */
50
64
  export declare const heldFlights: (store: NavigateStoreApi) => MorphFlight[];
51
65
  export type { MorphFlight };
@@ -13,7 +13,7 @@ export interface PlatformProfile {
13
13
  * its own frame's rendering update are atomic, so clock-start and first paint
14
14
  * become simultaneous by construction.
15
15
  *
16
- * Device-verified on three populations; see readDesktopReleaseFlipFlag. An
16
+ * Device-verified on three populations; see the resolver below. An
17
17
  * authored `driver: "native"` transition takes it too — pass
18
18
  * `authoredNativeDriver` to fold that in.
19
19
  */
@@ -35,12 +35,6 @@ export interface PlatformProfile {
35
35
  * genuinely paints its tiles during the hold) rather than beneath its cover.
36
36
  */
37
37
  readonly parkOver: boolean;
38
- /**
39
- * Keep the screen scope's layer promoted at REST. Off everywhere by default:
40
- * a promotion is also a stacking context, and at rest it outranks any
41
- * consumer overlay inside the screen.
42
- */
43
- readonly restLayerPromotion: boolean;
44
38
  /**
45
39
  * Rewrite oversized `<img>` sources to decoded-to-scale blobs off the main
46
40
  * thread. Auto on legacy Android Blink only — it touches consumer content,
@@ -57,14 +51,46 @@ export interface PlatformProfileInput {
57
51
  */
58
52
  readonly authoredNativeDriver?: boolean;
59
53
  }
60
- export declare const resolvePlatformProfile: (input?: PlatformProfileInput) => PlatformProfile;
61
54
  /**
62
- * The REST-promotion decision on its own, as a module-stable function.
55
+ * The render-settle entry gate, as its own predicate the flight routing asks
56
+ * for it directly (a PUSH only forces the compiled tier behind the gate), and
57
+ * the profile publishes it as `renderSettleGate`. One definition, because the
58
+ * two drifted apart once already: the ARMING widened in the react binding while
59
+ * core's stayed WebKit-only, and Android ran ungated for two release rounds.
60
+ *
61
+ * ON for touch WebKit (governedCompiledActive — the governed-compiled tier
62
+ * ships with it), for steady-60 desktop Blink sessions, for touch Blink, AND
63
+ * for desktop macOS Safari.
63
64
  *
64
- * A binding that server-renders must read this through a hydration-scoped
65
- * snapshot (it reaches the DOM as an inline style, so a render-phase read
66
- * mismatches the server HTML), and React's `useSyncExternalStore` requires the
67
- * reader identity to be stable across renders which an inline
68
- * `() => resolvePlatformProfile().restLayerPromotion` would not be.
65
+ * The steady-60 desktop term is a PROFILE, not a driver claim. It was written
66
+ * when a verified steady-60 session routed to the player, whose main-thread
67
+ * per-frame write the entering mount commit would stall; that routing is gone
68
+ * (Blink runs compiled everywhere since 2026-08-19) but the term stays, because
69
+ * the reason that survives is the tier-independent one: the measured ~50ms
70
+ * desktop mount hitch ages a wall-clocked compiled animation just as it starved
71
+ * the player.
72
+ *
73
+ * Touch Blink was the gap: the pop-convergence round (de35c13) widened the
74
+ * arming to "ALL engines" after a device A/B on a demoted Note 9 falsified the
75
+ * WebKit-only reading — its heavy detail mount runs a ~290ms main-thread task
76
+ * that stalls even the compositor's initial commit, so gating the release to
77
+ * AFTER that task measurably helped. Re-confirmed on the same device class
78
+ * 2026-08-19.
79
+ *
80
+ * Desktop macOS Safari was the same gap one platform over. It runs the compiled
81
+ * tier on purpose (macOS Safari caps rAF at 60Hz), but WebKit presents compiled
82
+ * animations from the MAIN THREAD, so a heavy entering mount eats the opening
83
+ * exactly as it does on a phone. Frame-level measurement of the docs site's own
84
+ * Home -> Showcase push (2026-08-20, production build, WebKit): the entering
85
+ * screen's mount blocked the main thread for 103-135ms while the animation's
86
+ * clock ran, so the FIRST presented frame already stood at 48-77% progress; the
87
+ * release commit then re-anchored the animation and it replayed from zero — a
88
+ * jump followed by a rewind, both visible. The same flight on Chromium was
89
+ * clean, which is why it read as Safari-only.
90
+ *
91
+ * The gate is adaptive, which is why this is safe to arm widely: with no
92
+ * qualifying mount commit inside firstWaitMs it releases with no felt delay, so
93
+ * a fast phone pays nothing for carrying it.
69
94
  */
70
- export declare const restLayerPromotionEnabled: () => boolean;
95
+ export declare const settleGateActive: () => boolean;
96
+ export declare const resolvePlatformProfile: (input?: PlatformProfileInput) => PlatformProfile;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,4 @@
1
1
  import { NavigateStatus } from '../navigate/store';
2
- export { resetShallowFreezeForTesting } from '../core/engine/diagnosticFlags';
3
2
  export interface ScreenFreezeInput {
4
3
  isActive: boolean;
5
4
  isPrev: boolean;
@@ -0,0 +1,14 @@
1
+ import { NavigateStoreApi } from '../navigate/store';
2
+ /**
3
+ * Publish the element a scope's shared-bar parts should be staged in. A binding
4
+ * calls this from its Router lifecycle — the Router is the only thing that knows
5
+ * which box bounds its screens.
6
+ */
7
+ export declare const registerPartLayer: (store: NavigateStoreApi, element: HTMLElement | null) => void;
8
+ /**
9
+ * The layer to stage a flight's bar parts in, creating a document-level fallback
10
+ * for a binding that publishes none. The fallback is correct for a root Router
11
+ * (its screens fill the viewport anyway) and wrong only for a CONTAINED one,
12
+ * which is exactly the case a binding is expected to publish.
13
+ */
14
+ export declare const resolvePartLayer: (store: NavigateStoreApi) => HTMLElement | null;
@@ -0,0 +1 @@
1
+ export default function publishRideBox(element: HTMLElement): () => void;
@@ -1,5 +1,6 @@
1
1
  declare module '../typing' {
2
2
  interface RegisterTransition {
3
+ "custom-snap-percent": "custom-snap-percent";
3
4
  "custom-fade-blur": "custom-fade-blur";
4
5
  "custom-slide-fade": "custom-slide-fade";
5
6
  "custom-rich-css": "custom-rich-css";
@@ -13,6 +14,7 @@ declare module '../decorator/typing' {
13
14
  interface RegisterDecorator {
14
15
  "rich-deco": "rich-deco";
15
16
  "held-deco": "held-deco";
17
+ "moving-deco": "moving-deco";
16
18
  "authored-deco": "authored-deco";
17
19
  }
18
20
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -7,10 +7,19 @@ export type CssDecl = {
7
7
  value: string;
8
8
  };
9
9
  export declare const collectAnimatedProperties: (transition: Pick<Transition, "initial" | "variants">) => string[];
10
- export declare const targetToDecls: (target: TransitionVariantValue["value"] | InitialTarget) => CssDecl[];
10
+ export declare const targetToDecls: (target: TransitionVariantValue["value"] | InitialTarget, ride?: boolean) => CssDecl[];
11
11
  export declare const easingToCss: (ease: AnimationOptions["ease"] | undefined) => string;
12
+ export declare const dedupeKeyframeBlocks: (css: string) => string;
12
13
  export declare const animationName: (scope: "screen" | "decorator" | "part", name: string, variant: TransitionVariant) => string;
13
- export declare const HEAD_ANIMATION_SUFFIXES: readonly ["-gov", "-deskhead", "-govcreep"];
14
+ export declare const decoratorAnimationName: (transitionName: string, decoratorName: string, variant: TransitionVariant) => string;
15
+ export declare const HEAD_SUFFIXES: {
16
+ readonly governed: "gov";
17
+ readonly desktop: "deskhead";
18
+ readonly creep: "govcreep";
19
+ readonly governedPark: "govpark";
20
+ readonly desktopPark: "deskpark";
21
+ };
22
+ export declare const HEAD_ANIMATION_SUFFIXES: string[];
14
23
  export declare const matchesFlightAnimationName: (eventName: string, expectedName: string) => boolean;
15
24
  export declare const compileTransitionStyles: (transitions: Iterable<Transition>, decorators: Iterable<Decorator>, partTransitions?: Iterable<PartTransition>) => string;
16
25
  export declare const variantHasAnimation: (transitionLike: Pick<Transition, "initial" | "variants">, variant: TransitionVariant) => boolean;
@@ -0,0 +1,14 @@
1
+ declare module '../../typing' {
2
+ interface RegisterTransition {
3
+ "clock-long": "clock-long";
4
+ "clock-short": "clock-short";
5
+ "clock-directional": "clock-directional";
6
+ }
7
+ }
8
+ declare module '../typing' {
9
+ interface RegisterDecorator {
10
+ "clock-dim": "clock-dim";
11
+ "clock-fixed": "clock-fixed";
12
+ }
13
+ }
14
+ export {};
@@ -1,12 +1,11 @@
1
1
  import { InitialTarget } from '../cssTypes';
2
- import { TransitionVariantValue } from '../typing';
3
- import { DecoratorName, Decorator, DecoratorOptions } from './typing';
2
+ import { DecoratorName, Decorator, DecoratorOptions, DecoratorVariantValue } from './typing';
4
3
  interface CreateDecoratorProps {
5
4
  name: DecoratorName;
6
5
  initial: InitialTarget;
7
- idle: TransitionVariantValue;
8
- enter: TransitionVariantValue;
9
- exit: TransitionVariantValue;
6
+ idle: DecoratorVariantValue;
7
+ enter: DecoratorVariantValue;
8
+ exit: DecoratorVariantValue;
10
9
  options?: DecoratorOptions;
11
10
  }
12
11
  export default function createDecorator({ name, initial, idle, enter, exit, options }: CreateDecoratorProps): Decorator;
@@ -1,18 +1,17 @@
1
1
  import { InitialTarget } from '../cssTypes';
2
- import { TransitionVariantValue } from '../typing';
3
- import { DecoratorName, Decorator, DecoratorOptions } from './typing';
2
+ import { DecoratorName, Decorator, DecoratorOptions, DecoratorVariantValue } from './typing';
4
3
  interface CreateRawDecoratorProps {
5
4
  name: DecoratorName;
6
5
  initial: InitialTarget;
7
- idle: TransitionVariantValue;
8
- pushOnEnter: TransitionVariantValue;
9
- pushOnExit: TransitionVariantValue;
10
- replaceOnEnter: TransitionVariantValue;
11
- replaceOnExit: TransitionVariantValue;
12
- popOnEnter: TransitionVariantValue;
13
- popOnExit: TransitionVariantValue;
14
- completedOnEnter: TransitionVariantValue;
15
- completedOnExit: TransitionVariantValue;
6
+ idle: DecoratorVariantValue;
7
+ pushOnEnter: DecoratorVariantValue;
8
+ pushOnExit: DecoratorVariantValue;
9
+ replaceOnEnter: DecoratorVariantValue;
10
+ replaceOnExit: DecoratorVariantValue;
11
+ popOnEnter: DecoratorVariantValue;
12
+ popOnExit: DecoratorVariantValue;
13
+ completedOnEnter: DecoratorVariantValue;
14
+ completedOnExit: DecoratorVariantValue;
16
15
  options?: DecoratorOptions;
17
16
  }
18
17
  export default function createRawDecorator({ name, initial, idle, pushOnEnter, pushOnExit, replaceOnEnter, replaceOnExit, popOnEnter, popOnExit, completedOnEnter, completedOnExit, options }: CreateRawDecoratorProps): Decorator;
@@ -0,0 +1,36 @@
1
+ import { BaseTransition, Transition } from '../typing';
2
+ import { Decorator } from './typing';
3
+ /**
4
+ * A decorator's variant table with its clock filled in from the screen
5
+ * transition that names it.
6
+ *
7
+ * A decorator is only ever reached through `transition.decoratorName`, so the
8
+ * flight it dresses already decides how long it has. Authoring the length a
9
+ * second time is how the two drift apart, and this repository worked around
10
+ * that three times before removing the duplication:
11
+ *
12
+ * - `overlay` and `cupertino` each restated 0.7s, with a comment on the
13
+ * decorator saying the number came from the transition.
14
+ * - the playground's `drift` and `recess` share a constants file for no
15
+ * other reason than to keep two hand-written clocks equal.
16
+ * - `layout` dropped its dim ALTOGETHER rather than inherit `overlay`'s
17
+ * 0.7s over a 0.4s flight: measured on a pop, the dismissing screen was
18
+ * fully gone at 335ms while the screen underneath still carried a 10%
19
+ * black wash, which reads as a grey cast appearing from nowhere and then
20
+ * lifting for no reason.
21
+ *
22
+ * The rule is the SAME VARIANT KEY, which is exactly the mapping those
23
+ * workarounds were doing by hand: a decorator's `enter` sits at PUSHING-false
24
+ * with the screen's `exit`, its `exit` at POPPING-false with the screen's
25
+ * `exitBack`. It therefore also carries direction for free — a preset whose
26
+ * push and pop differ (material runs 0.35s and 0.25s) gives its dim the same
27
+ * asymmetry without the author restating it.
28
+ *
29
+ * Resolution is COMPILE TIME and produces a literal. It must never become a
30
+ * `var()` in `animation-duration`: timing that depended on custom properties
31
+ * lost WebKit's accelerated playback and collapsed to a 2-frame snap under
32
+ * main-thread starvation (device-bisected 2026-08-13, see
33
+ * compileTransitionStyles.ts).
34
+ */
35
+ export declare const resolveDecoratorClock: (transition: Pick<Transition, "variants">, decorator: Pick<Decorator, "initial" | "variants">) => Pick<BaseTransition, "initial" | "variants">;
36
+ export default resolveDecoratorClock;
@@ -1,4 +1,5 @@
1
- import { BaseTransition, SwipeAnimate } from '../typing';
1
+ import { AnimationOptions, TransitionTarget } from '../cssTypes';
2
+ import { BaseTransition, SwipeAnimate, TransitionVariant } from '../typing';
2
3
  export interface RegisterDecorator {
3
4
  }
4
5
  export type DecoratorName = RegisterDecorator[keyof RegisterDecorator] | "overlay";
@@ -19,6 +20,11 @@ export type DecoratorOptions = {
19
20
  prevDecorator: HTMLDivElement;
20
21
  }) => void;
21
22
  };
22
- export interface Decorator extends Omit<BaseTransition, "name">, DecoratorOptions {
23
+ export type DecoratorVariantValue = {
24
+ value: TransitionTarget;
25
+ options?: AnimationOptions;
26
+ };
27
+ export interface Decorator extends Omit<BaseTransition, "name" | "variants">, DecoratorOptions {
23
28
  name: DecoratorName;
29
+ variants: Record<TransitionVariant, DecoratorVariantValue>;
24
30
  }
@@ -0,0 +1,33 @@
1
+ import { AnimationOptions } from './cssTypes';
2
+ export interface ScrubClock {
3
+ /** Seconds from the animation's zero to the first frame of travel. */
4
+ readonly start: number;
5
+ readonly duration: number;
6
+ readonly ease: AnimationOptions["ease"];
7
+ }
8
+ /**
9
+ * Put every animation at one time and keep it there.
10
+ *
11
+ * Paused first: an animation the browser is still running would otherwise
12
+ * advance between the seek and the next pointer move.
13
+ */
14
+ export declare const holdScrubAt: (animations: readonly Animation[], seconds: number) => void;
15
+ /**
16
+ * Move to a fraction of the TRAVEL, not of the clock.
17
+ *
18
+ * Those are the same number only for a linear ease; under the built-in curve a
19
+ * finger a tenth of the way across moves the element a fiftieth, and the
20
+ * release is left to rush the rest.
21
+ */
22
+ export declare const scrubTo: (animations: readonly Animation[], clock: ScrubClock, progress: number) => void;
23
+ /**
24
+ * Hand the animations back to the browser at the speed the release settled at.
25
+ *
26
+ * `commit` plays them out to the arrival — the gesture became a navigation.
27
+ * Otherwise they run BACKWARDS to where they started.
28
+ *
29
+ * `onReverseFinish` exists because backwards an animation finishes at its start
30
+ * and fires no `animationend`. Anything listening for one has to be told
31
+ * explicitly instead.
32
+ */
33
+ export declare const settleScrubbed: (animations: readonly Animation[], clock: ScrubClock, commit: boolean, seconds: number, onReverseFinish?: () => void) => void;
@@ -1,12 +1,11 @@
1
1
  import { InitialTarget } from '../cssTypes';
2
- import { TransitionVariantValue } from '../typing';
3
- import { PartTransition, PartTransitionName, PartTransitionOptions } from './typing';
2
+ import { PartTransition, PartTransitionName, PartTransitionOptions, PartVariantValue } from './typing';
4
3
  interface CreatePartProps {
5
4
  name: PartTransitionName;
6
5
  initial: InitialTarget;
7
- idle: TransitionVariantValue;
8
- enter: TransitionVariantValue;
9
- exit: TransitionVariantValue;
6
+ idle: PartVariantValue;
7
+ enter: PartVariantValue;
8
+ exit: PartVariantValue;
10
9
  options?: PartTransitionOptions;
11
10
  }
12
11
  export default function createPartTransition({ name, initial, idle, enter, exit, options }: CreatePartProps): PartTransition;
@@ -1,18 +1,17 @@
1
1
  import { InitialTarget } from '../cssTypes';
2
- import { TransitionVariantValue } from '../typing';
3
- import { PartTransition, PartTransitionName, PartTransitionOptions } from './typing';
2
+ import { PartTransition, PartTransitionName, PartTransitionOptions, PartVariantValue } from './typing';
4
3
  interface CreateRawPartProps {
5
4
  name: PartTransitionName;
6
5
  initial: InitialTarget;
7
- idle: TransitionVariantValue;
8
- pushOnEnter: TransitionVariantValue;
9
- pushOnExit: TransitionVariantValue;
10
- replaceOnEnter: TransitionVariantValue;
11
- replaceOnExit: TransitionVariantValue;
12
- popOnEnter: TransitionVariantValue;
13
- popOnExit: TransitionVariantValue;
14
- completedOnEnter: TransitionVariantValue;
15
- completedOnExit: TransitionVariantValue;
6
+ idle: PartVariantValue;
7
+ pushOnEnter: PartVariantValue;
8
+ pushOnExit: PartVariantValue;
9
+ replaceOnEnter: PartVariantValue;
10
+ replaceOnExit: PartVariantValue;
11
+ popOnEnter: PartVariantValue;
12
+ popOnExit: PartVariantValue;
13
+ completedOnEnter: PartVariantValue;
14
+ completedOnExit: PartVariantValue;
16
15
  options?: PartTransitionOptions;
17
16
  }
18
17
  export default function createRawPartTransition({ name, initial, idle, pushOnEnter, pushOnExit, replaceOnEnter, replaceOnExit, popOnEnter, popOnExit, completedOnEnter, completedOnExit, options }: CreateRawPartProps): PartTransition;
@@ -1,2 +1,19 @@
1
+ import { BaseTransition, Transition } from '../typing';
1
2
  import { PartTransition, PartTransitionName } from './typing';
2
3
  export declare const partTransitionMap: Map<PartTransitionName, PartTransition>;
4
+ /**
5
+ * A registered part, with the clock the CURRENT flight gives it.
6
+ *
7
+ * Every reader of a part's timing goes through here, because the compiled CSS
8
+ * does: the rule a part actually runs carries the resolved clock (see
9
+ * resolvePartClock and the pair pass in compileTransitionStyles), so a reader
10
+ * that resolved the authored variants instead would disagree with the glass.
11
+ * That disagreement is not cosmetic — the choreography span decides how long
12
+ * the whole flight stays open, and the layer pin decides what stays promoted
13
+ * across the COMPLETED flip.
14
+ *
15
+ * `transition` is null only where there is no flight to inherit from, which is
16
+ * a part mounted outside any screen. It then reads exactly what it authored,
17
+ * matching the by-name rule that is the only one such a part selects.
18
+ */
19
+ export declare const resolvePartDefinition: (name: string | null, transition: Pick<Transition, "variants"> | null) => Pick<BaseTransition, "initial" | "variants"> | undefined;
@@ -0,0 +1,41 @@
1
+ import { BaseTransition, Transition } from '../typing';
2
+ import { PartTransition } from './typing';
3
+ /**
4
+ * A part's variant table with its clock filled in from the screen transition
5
+ * carrying the flight.
6
+ *
7
+ * A `<Part>` declares a POSE: what the piece of chrome looks like on each side
8
+ * of a hand-over. How long it takes to get there is not its own question — it
9
+ * is the flight's, and the flight already answered it. Authoring the length a
10
+ * second time is how the two drift apart, and an omitted length was worse than
11
+ * drift: it resolved to zero and the part SNAPPED while the screen carrying it
12
+ * took three quarters of a second.
13
+ *
14
+ * This is the rule `resolveDecoratorClock` already applies to decorators, for
15
+ * the same reason and by the same mapping: the SAME VARIANT KEY. A part's
16
+ * PUSHING-false sits with the screen's PUSHING-false, so a preset whose push
17
+ * and pop differ (material runs 0.35s and 0.25s) gives its parts the same
18
+ * asymmetry without the author restating it.
19
+ *
20
+ * WHERE IT DIFFERS from a decorator, and why this could not simply reuse it: a
21
+ * decorator is reached through `transition.decoratorName`, so it belongs to one
22
+ * transition and can be resolved once. A part is referenced by NAME and may
23
+ * appear under any transition in the Router, so the pair is resolved per
24
+ * transition and the compiled rule carries a `data-flemo-transition` term to
25
+ * select the right one. A part mounted outside any screen has no transition to
26
+ * inherit from and keeps what it authored.
27
+ *
28
+ * `transition` is null where there is no flight to inherit from — the by-name
29
+ * pass, and a part mounted outside any screen. It then normalizes rather than
30
+ * inherits: every variant comes back with a clock, so nothing downstream has to
31
+ * carry the optional shape. That normalization is the reason PartVariantValue's
32
+ * looseness stops here, exactly as DecoratorVariantValue's does.
33
+ *
34
+ * Resolution is COMPILE TIME and produces a literal. It must never become a
35
+ * `var()` in `animation-duration`: timing that depended on custom properties
36
+ * lost WebKit's accelerated playback and collapsed to a 2-frame snap under
37
+ * main-thread starvation (device-bisected 2026-08-13, see
38
+ * compileTransitionStyles.ts).
39
+ */
40
+ export declare const resolvePartClock: (transition: Pick<Transition, "variants"> | null, part: Pick<PartTransition, "initial" | "variants">) => Pick<BaseTransition, "initial" | "variants">;
41
+ export default resolvePartClock;
@@ -1,4 +1,9 @@
1
- import { BaseTransition, SwipeAnimate } from '../typing';
1
+ import { AnimationOptions, TransitionTarget } from '../cssTypes';
2
+ import { BaseTransition, SwipeAnimate, TransitionVariant } from '../typing';
3
+ export type PartVariantValue = {
4
+ value: TransitionTarget;
5
+ options?: AnimationOptions;
6
+ };
2
7
  export interface RegisterPartTransition {
3
8
  }
4
9
  export type PartTransitionName = RegisterPartTransition[keyof RegisterPartTransition] | (string & {});
@@ -19,6 +24,7 @@ export type PartTransitionOptions = {
19
24
  active: boolean;
20
25
  }) => void;
21
26
  };
22
- export interface PartTransition extends Omit<BaseTransition, "name">, PartTransitionOptions {
27
+ export interface PartTransition extends Omit<BaseTransition, "name" | "variants">, PartTransitionOptions {
23
28
  name: PartTransitionName;
29
+ variants: Record<TransitionVariant, PartVariantValue>;
24
30
  }
@@ -0,0 +1,5 @@
1
+ import { TransitionTarget } from './cssTypes';
2
+ export declare const RIDE_HEIGHT_VAR = "--flemo-ride-y";
3
+ export declare const percentRatio: (value: unknown) => number | null;
4
+ export declare const rideLength: (ratio: number) => string;
5
+ export declare const resolveRideTarget: <T extends TransitionTarget>(target: T, screenHeight: number) => T;
@@ -29,8 +29,12 @@ export interface SwipeSettleInput {
29
29
  * time that curve itself spends on the stretch that is left; without them it
30
30
  * falls back to reading the motion as linear, which is what this used to do
31
31
  * for every transition.
32
+ *
33
+ * `null` is accepted as well as omission, because the callers that build
34
+ * this read their curve from `easeControlPoints`, which reports "no curve"
35
+ * as null. Converting at every call site bought nothing but a branch.
32
36
  */
33
- authoredEase?: readonly [number, number, number, number];
37
+ authoredEase?: readonly [number, number, number, number] | null;
34
38
  minSeconds?: number;
35
39
  }
36
40
  /**
@@ -42,7 +42,18 @@ export type TransitionOptions = {
42
42
  animate: SwipeAnimate;
43
43
  currentScreen: HTMLDivElement;
44
44
  prevScreen: HTMLDivElement;
45
- onProgress?: (triggered: boolean, progress: number) => void;
45
+ /**
46
+ * Report the gesture's VERDICT so the decorator and the parts can
47
+ * follow it. The controller supplies their progress itself, against
48
+ * the box the screen is actually dragged over, so a second argument
49
+ * is accepted for source compatibility and is not read.
50
+ *
51
+ * It used to be. A transition's own progress is in the transition's
52
+ * own unit — material's is a pull in pixels, layout's is a constant
53
+ * — so what reached a decorator depended on which preset it was
54
+ * paired with, and could not honour the 0-100 those hooks document.
55
+ */
56
+ onProgress?: (triggered: boolean, progress?: number) => void;
46
57
  }) => number;
47
58
  onSwipeEnd: (event: PointerEvent, info: SwipeInfo, options: {
48
59
  animate: SwipeAnimate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flemo/core",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Framework-agnostic primitives for flemo: history, navigation, transitions, task manager.",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.mjs",
@@ -1,10 +0,0 @@
1
- declare global {
2
- interface ImportMeta {
3
- glob(pattern: string, options: {
4
- query: string;
5
- import: string;
6
- eager: true;
7
- }): Record<string, string>;
8
- }
9
- }
10
- export {};
@@ -1,16 +0,0 @@
1
- export declare const readImageHoldFlag: () => "on" | "off" | null;
2
- export declare const morphTraceArmed: () => boolean;
3
- export declare const readSettleGateFlag: () => boolean;
4
- export declare const readArrivalHoldFlag: () => boolean;
5
- export declare const readDesktopReleaseFlipFlag: () => boolean;
6
- export declare const readCreepHeadFlag: () => boolean;
7
- export declare const readDeferReleaseCommitFlag: () => boolean;
8
- export declare const readDesktopHeadFlag: () => boolean;
9
- export declare const readPrerasterFlag: () => boolean;
10
- export declare const readRestLayerPromotionFlag: () => boolean;
11
- export declare const readImageOffloadOverride: () => "on" | "off" | null;
12
- export declare const readBlinkGovernedOverride: () => "on" | "off" | null;
13
- export declare const residentScreenLayers: () => boolean;
14
- export declare const resetResidentLayersForTesting: () => void;
15
- export declare const shallowFreeze: () => boolean;
16
- export declare const resetShallowFreezeForTesting: () => void;
@@ -1,38 +0,0 @@
1
- /**
2
- * What kind of thing a flag is — which decides how a report should read it.
3
- *
4
- * - `production-state`: written by the library itself (a learned ledger). Never
5
- * set by hand; the KEY STRINGS are frozen, since users' devices carry
6
- * persisted values.
7
- * - `production-default-with-override`: the library computes a default and the
8
- * key overrides it both ways, for field debugging.
9
- * - `opt-in-diagnostic`: default OFF. A measurement instrument that ships so a
10
- * device session can be probed without a custom build.
11
- */
12
- export type DiagnosticFlagKind = "production-state" | "production-default-with-override" | "opt-in-diagnostic";
13
- export interface DiagnosticFlag {
14
- readonly key: string;
15
- readonly storage: "session" | "local";
16
- readonly kind: DiagnosticFlagKind;
17
- /** Accepted values, for a report or a panel to offer. */
18
- readonly values: string;
19
- /** The default when the key is unset. */
20
- readonly fallback: string;
21
- readonly effect: string;
22
- }
23
- /** Every `flemo:*` key the library reads today. */
24
- export declare const DIAGNOSTIC_FLAGS: readonly DiagnosticFlag[];
25
- export interface RetiredDiagnosticFlag {
26
- readonly key: string;
27
- readonly storage: "session" | "local";
28
- /** What it used to do, and when it stopped doing it. */
29
- readonly retiredWith: string;
30
- }
31
- /**
32
- * Keys the library once read and no longer does. Declared so a report can say
33
- * "this is set, and it does nothing" — residue that is merely unknown reads as
34
- * a lead worth chasing, and one such key once burned a multi-day investigation.
35
- *
36
- * Values persisted on users' devices are never read again.
37
- */
38
- export declare const RETIRED_DIAGNOSTIC_FLAGS: readonly RetiredDiagnosticFlag[];
@@ -1,2 +0,0 @@
1
- export declare function beginImageRevealHold(scope: Element | null, backstopMs: number, unpaintedOnly?: boolean): () => void;
2
- export default beginImageRevealHold;
@@ -1,2 +0,0 @@
1
- export declare function preserveDescendantAnimations(root: Element, move: () => void): void;
2
- export default preserveDescendantAnimations;