@flemo/core 1.29.0 → 1.30.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 (53) hide show
  1. package/dist/core/TaskManger.d.ts +13 -0
  2. package/dist/core/engine/__tests__/architectureDoc.test.d.ts +10 -0
  3. package/dist/core/engine/__tests__/diagnosticRegistry.test.d.ts +10 -0
  4. package/dist/core/engine/__tests__/participantLayers.test.d.ts +1 -0
  5. package/dist/core/engine/__tests__/singleResolution.test.d.ts +1 -0
  6. package/dist/core/engine/arrivalHold.d.ts +1 -1
  7. package/dist/core/engine/cancelResume.d.ts +17 -0
  8. package/dist/core/engine/compositorWarmUp.d.ts +1 -1
  9. package/dist/core/engine/createSwipeController.d.ts +11 -0
  10. package/dist/core/engine/createTransitionEngine.d.ts +2 -1
  11. package/dist/core/engine/diagnosticFlags.d.ts +1 -10
  12. package/dist/core/engine/diagnosticRegistry.d.ts +38 -0
  13. package/dist/core/engine/flightHolds.d.ts +22 -0
  14. package/dist/core/engine/flightParticipants.d.ts +8 -0
  15. package/dist/core/engine/flightRouting.d.ts +70 -0
  16. package/dist/core/engine/gpuPipelinePrewarm.d.ts +1 -1
  17. package/dist/core/engine/imageDecodeOffloader.d.ts +1 -1
  18. package/dist/core/engine/landingGovernor.d.ts +3 -0
  19. package/dist/core/engine/participantLayers.d.ts +12 -0
  20. package/dist/core/engine/types.d.ts +1 -1
  21. package/dist/dom/__tests__/attributes.test.d.ts +10 -0
  22. package/dist/dom/attributes.d.ts +115 -0
  23. package/dist/index.d.ts +8 -4
  24. package/dist/index.mjs +1517 -1658
  25. package/dist/navigate/store.d.ts +1 -0
  26. package/dist/platform/__tests__/displayCadence.test.d.ts +1 -0
  27. package/dist/platform/__tests__/displayProbe.test.d.ts +1 -0
  28. package/dist/platform/__tests__/engineProbes.test.d.ts +1 -0
  29. package/dist/platform/__tests__/legacyBlinkGate.test.d.ts +1 -0
  30. package/dist/platform/__tests__/profile.test.d.ts +1 -0
  31. package/dist/platform/__tests__/steadySixtyCadence.test.d.ts +1 -0
  32. package/dist/platform/displayCadence.d.ts +3 -0
  33. package/dist/platform/displayProbe.d.ts +5 -0
  34. package/dist/platform/engineProbes.d.ts +4 -0
  35. package/dist/platform/profile.d.ts +70 -0
  36. package/dist/{core/engine → platform}/steadySixtyCadence.d.ts +1 -1
  37. package/dist/runtime/__tests__/flemoRuntime.test.d.ts +1 -0
  38. package/dist/runtime/flemoRuntime.d.ts +9 -0
  39. package/dist/transition/compileTransitionStyles.d.ts +0 -1
  40. package/dist/transition/cubicBezier.d.ts +8 -0
  41. package/dist/transition/swipeSettle.d.ts +82 -1
  42. package/dist/transition/typing.d.ts +2 -2
  43. package/package.json +3 -3
  44. package/dist/core/engine/driverPolicy.d.ts +0 -12
  45. package/dist/core/engine/landingPixelSnap.d.ts +0 -10
  46. package/dist/core/engine/motionDriverKind.d.ts +0 -7
  47. package/dist/core/engine/transitionPlayer.d.ts +0 -40
  48. /package/dist/core/engine/__tests__/{driverPolicy.test.d.ts → createSwipeController.stuckGesture.test.d.ts} +0 -0
  49. /package/dist/core/engine/__tests__/{landingPixelSnap.test.d.ts → createSwipeController.velocity.test.d.ts} +0 -0
  50. /package/dist/core/engine/__tests__/{motionDriverKind.test.d.ts → flightParticipants.test.d.ts} +0 -0
  51. /package/dist/core/engine/__tests__/{steadySixtyCadence.test.d.ts → flightRouting.test.d.ts} +0 -0
  52. /package/dist/core/engine/__tests__/{transitionPlayer.test.d.ts → landingGovernor.test.d.ts} +0 -0
  53. /package/dist/{core/engine → platform}/governedCompiled.d.ts +0 -0
@@ -1,5 +1,6 @@
1
1
  import { StoreApi } from 'zustand/vanilla';
2
2
  export type NavigateStatus = "IDLE" | "PUSHING" | "REPLACING" | "POPPING" | "COMPLETED";
3
+ export declare const TRANSITIONAL_STATUS_VALUES: readonly ["PUSHING", "POPPING", "REPLACING"];
3
4
  export interface NavigateStore {
4
5
  status: NavigateStatus;
5
6
  transitionTaskId: string | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const learnedFrameIntervalMs: () => number;
2
+ export declare const reportDisplayIntervalMs: (intervalMs: number) => void;
3
+ export declare const resetDisplayCadenceForTests: () => void;
@@ -0,0 +1,5 @@
1
+ export declare const COMPILED_TIER_MAX_INTERVAL_MS = 12;
2
+ export declare const armFramePacingKeepalive: () => (() => void);
3
+ export declare const cancelDisplayIntervalProbe: () => void;
4
+ export declare const resetDisplayProbeForTests: () => void;
5
+ export declare const armDisplayIntervalProbe: () => void;
@@ -0,0 +1,4 @@
1
+ export declare const detectBlinkEngine: () => boolean;
2
+ export declare const isLegacyAndroidBlink: () => boolean;
3
+ export declare const isDesktopMacWebKit: () => boolean;
4
+ export declare const isDesktopBlink: () => boolean;
@@ -0,0 +1,70 @@
1
+ export interface PlatformProfile {
2
+ /**
3
+ * This engine presents its compiled screen animations FROM THE MAIN THREAD
4
+ * (WebKit) rather than off the compositor (Blink). Everything about
5
+ * protecting a flight's opening follows from this one fact: where the clock
6
+ * is stamped on the main thread, a heavy commit between the stamp and the
7
+ * first paint is aged away rather than ridden through.
8
+ */
9
+ readonly mainThreadPresented: boolean;
10
+ /**
11
+ * Flip the hold attribute straight onto the DOM inside the readiness rAF
12
+ * instead of routing the release through a state commit. A rAF callback and
13
+ * its own frame's rendering update are atomic, so clock-start and first paint
14
+ * become simultaneous by construction.
15
+ *
16
+ * Device-verified on three populations; see readDesktopReleaseFlipFlag. An
17
+ * authored `driver: "native"` transition takes it too — pass
18
+ * `authoredNativeDriver` to fold that in.
19
+ */
20
+ readonly atomicReleaseFlip: boolean;
21
+ /**
22
+ * Hand the release's reconcile to the NEXT frame rather than flushing it
23
+ * synchronously, so it stops competing with the flight's first present.
24
+ * Only meaningful where the flip already released the hold — without the
25
+ * flip, the state commit IS the release.
26
+ */
27
+ readonly deferReleaseCommit: boolean;
28
+ /**
29
+ * Hold the release until the entering screen's mount render quiesces, so a
30
+ * heavy screen's own commit storm cannot eat the opening frames.
31
+ */
32
+ readonly renderSettleGate: boolean;
33
+ /**
34
+ * Park a push's entering screen ON TOP at near-zero opacity (so the browser
35
+ * genuinely paints its tiles during the hold) rather than beneath its cover.
36
+ */
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
+ /**
45
+ * Rewrite oversized `<img>` sources to decoded-to-scale blobs off the main
46
+ * thread. Auto on legacy Android Blink only — it touches consumer content,
47
+ * so it must never run where the paint is already cheap.
48
+ */
49
+ readonly imageDecodeOffload: boolean;
50
+ }
51
+ export interface PlatformProfileInput {
52
+ /**
53
+ * The transition being flown authored `driver: "native"` — an explicit opt-in
54
+ * to clock surgery, which carries the atomic release flip with it. The
55
+ * binding knows this and core does not, so it is the one input the profile
56
+ * takes.
57
+ */
58
+ readonly authoredNativeDriver?: boolean;
59
+ }
60
+ export declare const resolvePlatformProfile: (input?: PlatformProfileInput) => PlatformProfile;
61
+ /**
62
+ * The REST-promotion decision on its own, as a module-stable function.
63
+ *
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.
69
+ */
70
+ export declare const restLayerPromotionEnabled: () => boolean;
@@ -1,5 +1,5 @@
1
1
  export declare const HIGH_REFRESH_MAX_INTERVAL_MS = 12;
2
2
  export declare const reportInFlightCadence: (rawMedianMs: number, rawMaxMs?: number) => void;
3
3
  export declare const steadySixtyVerified: () => boolean;
4
- export declare const steadySixtyPlayerEligible: () => boolean;
4
+ export declare const steadySixtyDesktopProfile: () => boolean;
5
5
  export declare const resetSteadySixtyForTests: () => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Start flemo's ambient machinery, and return the release for this holder.
3
+ *
4
+ * A binding calls this once per Router mount and releases on unmount. Repeat
5
+ * calls share one runtime; the last release tears it down. Safe on the server:
6
+ * every piece degrades to a no-op without a document.
7
+ */
8
+ export declare const startFlemoRuntime: () => (() => void);
9
+ export declare const resetFlemoRuntimeForTests: () => void;
@@ -9,7 +9,6 @@ export type CssDecl = {
9
9
  export declare const collectAnimatedProperties: (transition: Pick<Transition, "initial" | "variants">) => string[];
10
10
  export declare const targetToDecls: (target: TransitionVariantValue["value"] | InitialTarget) => CssDecl[];
11
11
  export declare const easingToCss: (ease: AnimationOptions["ease"] | undefined) => string;
12
- export declare const softenFrontLoadedEasing: (easingCss: string, durationS: number) => string | null;
13
12
  export declare const animationName: (scope: "screen" | "decorator" | "part", name: string, variant: TransitionVariant) => string;
14
13
  export declare const HEAD_ANIMATION_SUFFIXES: readonly ["-gov", "-deskhead", "-govcreep"];
15
14
  export declare const matchesFlightAnimationName: (eventName: string, expectedName: string) => boolean;
@@ -1,4 +1,12 @@
1
1
  import { AnimationOptions } from './cssTypes';
2
2
  export type EasingFunction = (progress: number) => number;
3
3
  export declare const cubicBezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
4
+ /**
5
+ * The control points behind an authored ease, named or spelled out — `null`
6
+ * for `linear`, which has no handles to work with.
7
+ *
8
+ * Exported for the swipe release, which re-aims an authored curve's opening
9
+ * slope onto the gesture that produced it (see swipeSettle.ts).
10
+ */
11
+ export declare const easeControlPoints: (ease: AnimationOptions["ease"] | undefined) => [number, number, number, number] | null;
4
12
  export declare const resolveEasing: (ease: AnimationOptions["ease"] | undefined) => EasingFunction;
@@ -1,4 +1,22 @@
1
1
  export declare const MIN_SETTLE_SECONDS = 0.12;
2
+ /**
3
+ * The initial slope a release curve is allowed to reach, as a multiple of its
4
+ * own average speed — the ceiling on "the finger was going fast".
5
+ *
6
+ * It is also what the by-speed length is derived from: a curve that starts at
7
+ * the finger's speed and decelerates to rest covers the remaining distance in
8
+ * `slope x remaining / speed`, not `remaining / speed`. The old formula solved
9
+ * for a motion that never slows down, which no landing does.
10
+ */
11
+ export declare const RELEASE_LAUNCH_SLOPE = 1.6;
12
+ /**
13
+ * The floor on that slope. A very slow drag with most of the screen left
14
+ * cannot be honored literally — matching 150 px/s over 310px would take two
15
+ * seconds — so the settle does accelerate away from it. It leaves at half its
16
+ * average rather than at 2.25x, which is the difference between the system
17
+ * taking over and the screen being snatched.
18
+ */
19
+ export declare const MIN_LAUNCH_SLOPE = 0.5;
2
20
  export declare const MIN_REVERSAL_SECONDS = 0.28;
3
21
  export interface SwipeSettleInput {
4
22
  remainingPx: number;
@@ -6,6 +24,69 @@ export interface SwipeSettleInput {
6
24
  velocityPxPerSecond: number;
7
25
  reversing?: boolean;
8
26
  authoredSeconds: number;
27
+ /**
28
+ * The authored curve's control points. Given them, the distance term is the
29
+ * time that curve itself spends on the stretch that is left; without them it
30
+ * falls back to reading the motion as linear, which is what this used to do
31
+ * for every transition.
32
+ */
33
+ authoredEase?: readonly [number, number, number, number];
9
34
  minSeconds?: number;
10
35
  }
11
- export declare const swipeSettleSeconds: ({ remainingPx, spanPx, velocityPxPerSecond, authoredSeconds, reversing, minSeconds }: SwipeSettleInput) => number;
36
+ /**
37
+ * How long the authored curve takes to travel its LAST `1 - progress` — the
38
+ * stretch a release that let go at `progress` still has to cover.
39
+ *
40
+ * The curve maps time to distance, so this inverts it: find the time at which
41
+ * it has covered `progress`, and return what is left of the duration. Both
42
+ * searches are on a monotone function.
43
+ */
44
+ export declare const authoredTailSeconds: (progress: number, authoredSeconds: number, ease: readonly [number, number, number, number]) => number;
45
+ export declare const swipeSettleSeconds: ({ remainingPx, spanPx, velocityPxPerSecond, authoredSeconds, authoredEase, reversing, minSeconds }: SwipeSettleInput) => number;
46
+ /**
47
+ * The release curve's normalized initial slope: the speed the settle leaves at,
48
+ * as a multiple of its own average. `velocity x seconds / remaining` is exactly
49
+ * the finger's speed expressed in those units.
50
+ *
51
+ * ONE RULE, INCLUDING THE CANCEL. A reversal's finger was going the other way,
52
+ * so the speed it contributes to the settle's own direction is not its own
53
+ * speed — it is zero or less. Zero is what this reads it as, which puts every
54
+ * reversal on the floor: the screen is standing still when the settle begins,
55
+ * and it opens like something standing still.
56
+ *
57
+ * Full velocity continuity for a reversal would mean OVERSHOOT — the screen
58
+ * carrying on the way the finger was pushing for a hair before it returns,
59
+ * which is what a real spring does. That is a different primitive, not a
60
+ * re-aimed bezier, and it is deliberately not attempted here. Clamping at zero
61
+ * is the closest a monotone curve gets.
62
+ */
63
+ export declare const releaseLaunchSlope: ({ remainingPx, velocityPxPerSecond, seconds, authoredSlope, reversing }: {
64
+ remainingPx: number;
65
+ velocityPxPerSecond: number;
66
+ seconds: number;
67
+ /**
68
+ * The opening slope the transition's author drew. The floor never rises
69
+ * above it: a release that the gesture cannot support must not come out
70
+ * HARDER than the authored motion — the floor is there to stop a crawl, not
71
+ * to add energy the author did not ask for. The ceiling is not capped this
72
+ * way; a finger genuinely moving fast should leave fast, even out of a curve
73
+ * drawn to open gently, or the screen reads as braking the moment it is let
74
+ * go.
75
+ */
76
+ authoredSlope?: number;
77
+ reversing?: boolean;
78
+ }) => number | null;
79
+ /**
80
+ * Re-aim an authored cubic-bezier so it LEAVES at `slope` times its average
81
+ * speed, keeping its x handles and its landing.
82
+ *
83
+ * Only `y1` moves. `x1`/`x2` are the authored ones, so the result is still a
84
+ * valid CSS timing function by construction (x monotonicity is a property of
85
+ * the x handles alone) and still lands the way the transition's author drew it
86
+ * — the release differs from the authored curve exactly where the gesture
87
+ * differs from a standing start, and nowhere else.
88
+ *
89
+ * `y1` is capped below 1 so the curve cannot overshoot its own target; where a
90
+ * steep slope would need more, `x1` shrinks to buy it instead.
91
+ */
92
+ export declare const reaimReleaseEase: (authored: readonly [number, number, number, number], slope: number) => [number, number, number, number];
@@ -30,7 +30,7 @@ export interface SwipeInfo {
30
30
  export type SwipeAnimate = (target: HTMLElement, value: TransitionTarget, options?: AnimationOptions) => Promise<void>;
31
31
  export type TransitionOptions = {
32
32
  decoratorName?: DecoratorName;
33
- driver?: "native" | "player";
33
+ driver?: "native";
34
34
  swipeDirection: "x" | "y";
35
35
  onSwipeStart: (event: PointerEvent, info: SwipeInfo, options: {
36
36
  animate: SwipeAnimate;
@@ -52,7 +52,7 @@ export type TransitionOptions = {
52
52
  }) => Promise<boolean>;
53
53
  } | {
54
54
  decoratorName?: DecoratorName;
55
- driver?: "native" | "player";
55
+ driver?: "native";
56
56
  swipeDirection?: never;
57
57
  };
58
58
  export interface BaseTransition {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flemo/core",
3
- "version": "1.29.0",
3
+ "version": "1.30.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",
@@ -45,8 +45,8 @@
45
45
  "vite": "^8.2.1",
46
46
  "vite-plugin-dts": "^5.0.3",
47
47
  "vitest": "^4.1.10",
48
- "@flemo/tsconfig": "0.0.0",
49
- "@flemo/eslint-config": "0.0.0"
48
+ "@flemo/eslint-config": "0.0.0",
49
+ "@flemo/tsconfig": "0.0.0"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"
@@ -1,12 +0,0 @@
1
- export declare const FORCE_PIN_TTL_MS: number;
2
- export interface DriverPolicy {
3
- playerAllowed: () => boolean;
4
- pinnedDriver: () => "css" | "raf" | null;
5
- }
6
- export declare const detectBlinkEngine: () => boolean;
7
- export declare const isLegacyAndroidBlink: () => boolean;
8
- export declare const isDesktopMacWebKit: () => boolean;
9
- export declare const isDesktopBlink: () => boolean;
10
- export declare const createDriverPolicy: (playerByDefault?: boolean) => DriverPolicy;
11
- declare const driverPolicy: DriverPolicy;
12
- export default driverPolicy;
@@ -1,10 +0,0 @@
1
- import { VariantMotion } from '../../transition/variantMotion';
2
- import { PerceptualBox } from './perceptualSpan';
3
- export declare const SNAP_VELOCITY_DEVICE_PX_PER_FRAME = 40;
4
- export declare const SNAP_BAND_MAX_DEVICE_PX = 480;
5
- export declare const snappedEasingForMotion: (motion: VariantMotion, box: PerceptualBox, devicePixelRatio: number) => string | null;
6
- export declare const governedEasingForMotion: (motion: VariantMotion, box: PerceptualBox, devicePixelRatio: number, frameIntervalMs: number) => string | null;
7
- export declare const governedBezierForMotion: (motion: VariantMotion | null, box: PerceptualBox, devicePixelRatio: number, frameIntervalMs: number) => {
8
- easing: string;
9
- durationMs: number;
10
- } | null;
@@ -1,7 +0,0 @@
1
- import { Transition } from '../../transition/typing';
2
- import { VariantMotion } from '../../transition/variantMotion';
3
- import { PerceptualBox } from './perceptualSpan';
4
- export declare const NATIVE_PEAK_CSS_PX_PER_FRAME = 6;
5
- export declare const peakTranslationPxPerFrame: (motion: VariantMotion, box: PerceptualBox) => number | null;
6
- export type MotionDriverKind = "native" | "player";
7
- export declare const classifyTransitionDriver: (transition: Transition, _status: string, _box: PerceptualBox) => MotionDriverKind;
@@ -1,40 +0,0 @@
1
- import { VariantMotion } from '../../transition/variantMotion';
2
- export { resetSessionOverrideCachesForTests } from './diagnosticFlags';
3
- export declare const isPlayerDrivable: (motion: VariantMotion) => boolean;
4
- export interface SnapMemory {
5
- x: number | null;
6
- y: number | null;
7
- landingX?: number | null;
8
- landingY?: number | null;
9
- writtenX?: number | null;
10
- writtenY?: number | null;
11
- pendingLanding?: boolean;
12
- glide?: boolean;
13
- }
14
- export declare const learnedFrameIntervalMs: () => number;
15
- export declare const reportDisplayIntervalMs: (intervalMs: number) => void;
16
- export interface PlayerScheduler {
17
- request: (callback: (time: number) => void) => number;
18
- cancel: (handle: number) => void;
19
- devicePixelRatio: () => number;
20
- }
21
- export interface TrackInput {
22
- element: HTMLElement;
23
- motion: VariantMotion;
24
- role: "active" | "passive";
25
- onComplete?: () => void;
26
- status?: string;
27
- }
28
- export interface TransitionPlayerRegistry {
29
- join: (taskId: string, input: TrackInput) => (() => void) | null;
30
- dispose: (taskId: string) => void;
31
- onFrameGap?: (gapMs: number) => void;
32
- }
33
- export declare const createTransitionPlayerRegistry: (scheduler?: PlayerScheduler) => TransitionPlayerRegistry;
34
- declare const transitionPlayers: TransitionPlayerRegistry;
35
- export default transitionPlayers;
36
- declare global {
37
- interface Window {
38
- __flemoPlayerGaps?: number[];
39
- }
40
- }