@flemo/core 2.1.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 (39) hide show
  1. package/dist/core/TaskManger.d.ts +5 -5
  2. package/dist/core/engine/__tests__/riderSwipe.test.d.ts +1 -0
  3. package/dist/core/engine/barPartStaging.d.ts +39 -0
  4. package/dist/core/engine/cancelResume.d.ts +6 -0
  5. package/dist/core/engine/createSwipeController.d.ts +10 -0
  6. package/dist/core/engine/flightRouting.d.ts +2 -2
  7. package/dist/core/engine/layerSettleHold.d.ts +0 -1
  8. package/dist/core/engine/riderSwipe.d.ts +28 -0
  9. package/dist/core/engine/types.d.ts +1 -0
  10. package/dist/dom/__tests__/staging.test.d.ts +1 -0
  11. package/dist/dom/attributes.d.ts +57 -4
  12. package/dist/dom/staging.d.ts +33 -0
  13. package/dist/history/historyDriver.d.ts +2 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.mjs +1944 -2047
  16. package/dist/platform/profile.d.ts +41 -15
  17. package/dist/screen/__tests__/partLayer.test.d.ts +1 -0
  18. package/dist/screen/computeScreenFreeze.d.ts +0 -1
  19. package/dist/screen/partLayer.d.ts +14 -0
  20. package/dist/transition/__tests__/partClockInheritance.test.d.ts +1 -0
  21. package/dist/transition/compileTransitionStyles.d.ts +9 -1
  22. package/dist/transition/gestureScrub.d.ts +33 -0
  23. package/dist/transition/partTransition/__tests__/resolvePartDefinition.test.d.ts +1 -0
  24. package/dist/transition/partTransition/createPartTransition.d.ts +4 -5
  25. package/dist/transition/partTransition/createRawPartTransition.d.ts +10 -11
  26. package/dist/transition/partTransition/partTransition.d.ts +17 -0
  27. package/dist/transition/partTransition/resolvePartClock.d.ts +41 -0
  28. package/dist/transition/partTransition/typing.d.ts +8 -2
  29. package/package.json +1 -1
  30. package/dist/core/engine/__tests__/diagnosticRegistry.test.d.ts +0 -10
  31. package/dist/core/engine/diagnosticFlags.d.ts +0 -16
  32. package/dist/core/engine/diagnosticRegistry.d.ts +0 -38
  33. package/dist/core/engine/imageRevealHold.d.ts +0 -2
  34. package/dist/morph/morphAnimations.d.ts +0 -2
  35. /package/dist/core/engine/__tests__/{createTransitionEngine.flightImageHold.test.d.ts → barPartStaging.test.d.ts} +0 -0
  36. /package/dist/core/engine/__tests__/{diagnosticFlags.test.d.ts → createSwipeController.barPartStaging.test.d.ts} +0 -0
  37. /package/dist/core/engine/__tests__/{documentedDefaults.test.d.ts → createSwipeController.dragRiders.test.d.ts} +0 -0
  38. /package/dist/core/engine/__tests__/{imageRevealHold.test.d.ts → createTransitionEngine.barPartStaging.test.d.ts} +0 -0
  39. /package/dist/{morph/__tests__/morphAnimations.test.d.ts → core/engine/__tests__/platformDefaults.test.d.ts} +0 -0
@@ -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 {};
@@ -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;
@@ -9,9 +9,17 @@ 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, 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
14
  export declare const decoratorAnimationName: (transitionName: string, decoratorName: string, variant: TransitionVariant) => string;
14
- export declare const HEAD_ANIMATION_SUFFIXES: readonly ["-gov", "-deskhead", "-govcreep"];
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[];
15
23
  export declare const matchesFlightAnimationName: (eventName: string, expectedName: string) => boolean;
16
24
  export declare const compileTransitionStyles: (transitions: Iterable<Transition>, decorators: Iterable<Decorator>, partTransitions?: Iterable<PartTransition>) => string;
17
25
  export declare const variantHasAnimation: (transitionLike: Pick<Transition, "initial" | "variants">, variant: TransitionVariant) => boolean;
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flemo/core",
3
- "version": "2.1.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;