@mt-gloss/ui 0.0.42 → 0.0.43

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.
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+ export interface DevTuningPanelProps {
3
+ /**
4
+ * Reserved — optional DOM portal target. Not currently used; panel is
5
+ * position:fixed so it does not need a portal. Included in the public
6
+ * interface for forward compatibility.
7
+ */
8
+ container?: HTMLElement;
9
+ }
10
+ export declare function DevTuningPanel(_props?: DevTuningPanelProps): React.ReactElement | null;
11
+ export declare namespace DevTuningPanel {
12
+ var displayName: string;
13
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Phase 19 Plan 02 — DevTuningPanel barrel export.
3
+ *
4
+ * Re-exports the dev-only Resize Tuning primitive + its localStorage-backed
5
+ * hook. Barrel is zero-side-effect; the SCSS is imported from
6
+ * `DevTuningPanel.tsx` so consumers that only import the hook pay no CSS cost.
7
+ */
8
+ export { DevTuningPanel } from './DevTuningPanel';
9
+ export type { DevTuningPanelProps } from './DevTuningPanel';
10
+ export { useResizeTuning, TUNING_STORAGE_KEY, TUNING_DEFAULTS } from './useResizeTuning';
11
+ export type { UseResizeTuningReturn, TuningConfig } from './useResizeTuning';
@@ -0,0 +1,47 @@
1
+ /** D-03 — exact localStorage key. */
2
+ export declare const TUNING_STORAGE_KEY = "resize.tuning.dev";
3
+ /**
4
+ * TUNING_DEFAULTS extends ResizePhysicsConfig with px-expressed snap thresholds
5
+ * that the UI surfaces (UI-SPEC §Surface 3 rows 4 & 5). The hook consumer
6
+ * converts to fractions at drag-start (`snapInFraction = snapInPx / step`).
7
+ * Defaults match DEFAULT_RESIZE_PHYSICS fraction math against the 180px GRID_STEP:
8
+ * snapInPx = 180 × 1/3 = 60
9
+ * snapOutPx = 180 × 1/2 = 90
10
+ */
11
+ /**
12
+ * Raw defaults — declared as `as const` so the shape's keys are pinned, but
13
+ * `TuningConfig` below widens each field to `number` so downstream setters
14
+ * accept the full slider range (e.g. `snapOutPx: 110`), not just the literal
15
+ * default (`snapOutPx: 90`).
16
+ */
17
+ declare const _TUNING_DEFAULTS_LITERAL: {
18
+ readonly snapInPx: 60;
19
+ readonly snapOutPx: 90;
20
+ readonly stiffness: 500;
21
+ readonly damping: 35;
22
+ readonly mass: 1;
23
+ readonly snapInFraction: number;
24
+ readonly snapOutFraction: number;
25
+ readonly blueprintOpacity: 0.25;
26
+ readonly blueprintFadeInMs: 150;
27
+ readonly blueprintFadeOutMs: 120;
28
+ readonly rippleStaggerMs: 50;
29
+ readonly velocityMultiplier: 1;
30
+ };
31
+ export type TuningConfig = {
32
+ -readonly [K in keyof typeof _TUNING_DEFAULTS_LITERAL]: number;
33
+ };
34
+ /** Public defaults — widened to `TuningConfig` so consumers can reassign. */
35
+ export declare const TUNING_DEFAULTS: TuningConfig;
36
+ export interface UseResizeTuningReturn {
37
+ /** Current tuning values (defaults merged with any persisted deltas). */
38
+ values: TuningConfig;
39
+ /** Update a single key; persists to localStorage. */
40
+ setValue<K extends keyof TuningConfig>(key: K, value: TuningConfig[K]): void;
41
+ /** Restore a single key to its TUNING_DEFAULTS value. */
42
+ resetRow<K extends keyof TuningConfig>(key: K): void;
43
+ /** Restore every key to TUNING_DEFAULTS. */
44
+ resetAll(): void;
45
+ }
46
+ export declare function useResizeTuning(): UseResizeTuningReturn;
47
+ export {};
@@ -1,4 +1,4 @@
1
- import { ResizeState } from './useEdgeHoverResize';
1
+ import { ResizeState, ResizeDirection } from './useEdgeHoverResize';
2
2
  export interface EdgeHoverHandleProps {
3
3
  state: ResizeState;
4
4
  onPointerDown: (e: React.PointerEvent) => void;
@@ -11,16 +11,23 @@ export interface EdgeHoverHandleProps {
11
11
  */
12
12
  onPointerEnter?: () => void;
13
13
  onPointerLeave?: (e: React.PointerEvent) => void;
14
- /** Which card edge the handle is positioned on. Default: 'right' */
15
- edge?: 'right' | 'left';
14
+ /**
15
+ * D-13 — right-edge resize only. Left-edge support dropped (CONTEXT D-13);
16
+ * narrowed to the literal 'right'. Prop retained so downstream data-edge
17
+ * attributes and future bidirectional revival can slot in without a
18
+ * breaking API change.
19
+ */
20
+ edge?: 'right';
21
+ /** D-16 — keyboard step handler (ArrowLeft → 'shrink', ArrowRight → 'grow'). */
22
+ onKeyboardStep?: (direction: ResizeDirection) => void;
16
23
  ariaLabel?: string;
17
24
  }
18
25
  /**
19
26
  * EdgeHoverHandle — Corner/edge handle affordance for the resize gesture.
20
27
  *
21
28
  * Visibility is derived from resize state: only visible when state is
22
- * 'handle-visible' or 'dragging'. Invisible at 'idle' to avoid colliding
23
- * with the chip-menu click target on the value/row area.
29
+ * 'handle-visible', 'dragging', or 'settling'. Invisible at 'idle' to avoid
30
+ * colliding with the chip-menu click target on the value/row area.
24
31
  *
25
32
  * Motion contract (AN-06): opacity transitions driven by edge-hover.scss
26
33
  * using enterMs=180ms (enter) / exitMs=120ms (exit via ghost-preview).
@@ -1,8 +1,11 @@
1
+ import { MotionValue } from 'framer-motion';
1
2
  export interface GhostPreviewProps {
2
- /** Whether the ghost overlay is currently visible (active during drag). */
3
+ /** Whether the ghost overlay is currently visible (active during drag/settling). */
3
4
  visible: boolean;
4
5
  /** The target column span being previewed. */
5
6
  targetColSpan: 1 | 2 | 3;
7
+ /** D-07 — MotionValue driving translateX. Zero React re-renders per pointermove. */
8
+ motionX: MotionValue<number>;
6
9
  /** AN-06 enter duration in ms. Default: 180ms */
7
10
  enterMs?: number;
8
11
  /** AN-06 exit duration in ms. Default: 120ms */
@@ -11,14 +14,14 @@ export interface GhostPreviewProps {
11
14
  /**
12
15
  * GhostPreview — Dashed outline overlay rendered during an active resize drag.
13
16
  *
14
- * Provides visual feedback of the target card size before the user commits
15
- * by releasing the pointer. Uses the same ghost-preview vocabulary as the
16
- * existing chart-area resize affordance.
17
+ * Phase 19 evolution: consumes a framer-motion MotionValue for translateX
18
+ * (D-07) so ghost tracks the cursor 1:1 without React re-renders on
19
+ * pointermove (AP-17). Opacity transitions still use CSS enterMs/exitMs.
17
20
  *
18
21
  * Hidden via aria-hidden — purely decorative visual feedback.
19
22
  * Motion contract (AN-06): enter 180ms, exit 120ms.
20
23
  */
21
- export declare function GhostPreview({ visible, targetColSpan, enterMs, exitMs, }: GhostPreviewProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function GhostPreview({ visible, targetColSpan, motionX, enterMs, exitMs, }: GhostPreviewProps): import("react/jsx-runtime").JSX.Element;
22
25
  export declare namespace GhostPreview {
23
26
  var displayName: string;
24
27
  }
@@ -10,6 +10,10 @@ export { EdgeHoverHandle } from './EdgeHoverHandle';
10
10
  export { GhostPreview } from './GhostPreview';
11
11
  export { useSlideOutReveal } from './useSlideOutReveal';
12
12
  export { useEdgeHoverResize } from './useEdgeHoverResize';
13
+ export { DEFAULT_RESIZE_PHYSICS } from './resizePhysics';
14
+ export type { ResizePhysicsConfig } from './resizePhysics';
15
+ export { VelocityBuffer } from './velocityBuffer';
16
+ export type { VelocitySample } from './velocityBuffer';
13
17
  export * from './flipAndStage';
14
18
  export * from './types';
15
19
  export type { CardBackProps } from './CardBack';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * resizePhysics — D-08 / D-06 / D-11 / D-20 single source of truth.
3
+ *
4
+ * Constants consumed by both useEdgeHoverResize (spring commit) and the
5
+ * DevTuningPanel (Plan 19-02) so the panel and the hook never drift.
6
+ */
7
+ export declare const DEFAULT_RESIZE_PHYSICS: {
8
+ readonly stiffness: 500;
9
+ readonly damping: 35;
10
+ readonly mass: 1;
11
+ readonly snapInFraction: number;
12
+ readonly snapOutFraction: number;
13
+ readonly blueprintOpacity: 0.25;
14
+ readonly blueprintFadeInMs: 150;
15
+ readonly blueprintFadeOutMs: 120;
16
+ readonly rippleStaggerMs: 50;
17
+ readonly velocityMultiplier: 1;
18
+ };
19
+ export type ResizePhysicsConfig = typeof DEFAULT_RESIZE_PHYSICS;
@@ -1,4 +1,6 @@
1
- export type ResizeState = 'idle' | 'dwelling' | 'handle-visible' | 'dragging' | 'committed' | 'cancelled';
1
+ import { MotionValue } from 'framer-motion';
2
+ import { ResizePhysicsConfig } from './resizePhysics';
3
+ export type ResizeState = 'idle' | 'dwelling' | 'handle-visible' | 'dragging' | 'settling' | 'cancelled';
2
4
  export type ResizeDirection = 'grow' | 'shrink';
3
5
  export type ResizeMode = 'slot-fill' | 'sparkline-fidelity';
4
6
  export interface ResizeCommit {
@@ -11,6 +13,10 @@ export interface UseEdgeHoverResizeArgs {
11
13
  stackable: boolean;
12
14
  onResize: (commit: ResizeCommit) => void;
13
15
  onCancel?: () => void;
16
+ /** D-14 — colWidth snapshot passed per-render; captured at pointerdown. Default: 180px (1280px viewport). */
17
+ step?: number;
18
+ /** D-03 panel override; falls back to DEFAULT_RESIZE_PHYSICS. */
19
+ tuning?: Partial<ResizePhysicsConfig>;
14
20
  /** Hover dwell time before handle appears. Default: 120ms */
15
21
  dwellMs?: number;
16
22
  /** AN-06 enter motion duration exposed for consumer CSS. Default: 180ms */
@@ -29,35 +35,46 @@ export interface UseEdgeHoverResizeReturn {
29
35
  onPointerLeave: () => void;
30
36
  };
31
37
  /**
32
- * quick-260417-2na §2a — Live preview during drag.
33
- * While `state === 'dragging'`, reflects the colSpan the user would commit
34
- * if they released the pointer at the current position. Resets to
35
- * `currentColSpan` on transition out of 'dragging'. Always clamped to 1..3.
38
+ * Live preview during drag. Always clamped to 1..3.
39
+ * Reflects the colSpan the user would commit at the current cursor position,
40
+ * with hysteresis snap (D-06).
36
41
  */
37
42
  previewColSpan: 1 | 2 | 3;
38
43
  ghostProps: {
39
44
  visible: boolean;
40
- /** Same value as `previewColSpan`; exposed here for `<GhostPreview targetColSpan>` plumbing. */
41
45
  targetColSpan: 1 | 2 | 3;
46
+ /** D-07 — MotionValue driving ghost translateX. Zero React re-renders per pointermove. */
47
+ motionX: MotionValue<number>;
42
48
  enterMs: number;
43
49
  exitMs: number;
44
50
  };
51
+ /** D-16 — keyboard resize step (grow/shrink by 1) on focused handle. */
52
+ onKeyboardStep: (direction: ResizeDirection) => void;
53
+ /** D-19 — touch tap-to-reveal: show handle immediately on touch pointerdown on card body. */
54
+ onTouchReveal: () => void;
55
+ /** D-19 — outside-tap dismissal for touch. */
56
+ onTouchDismiss: () => void;
57
+ /** D-18 — cancel (used by parent; springs back if dragging). */
58
+ cancel: () => void;
45
59
  }
46
60
  /**
47
- * useEdgeHoverResize — State machine for edge-hover-to-resize gesture.
61
+ * useEdgeHoverResize — Phase 19 reworked state machine for edge-hover-to-resize.
48
62
  *
49
- * State flow:
50
- * idle dwelling (hover dwell 120ms) handle-visible dragging (pointerdown) → committed (pointerup) | cancelled
51
- * dwelling | handle-visible idle (pointer leaves edge zone)
52
- * dragging | handle-visible cancelled (Escape key)
63
+ * Changes vs Phase 10.5 original:
64
+ * - MotionValue ghost tracking (D-07) zero React re-renders per pointermove
65
+ * - Hysteresis snap (D-06) replaces Math.round(dx/step)
66
+ * - Velocity ring buffer (D-09) feeds spring commit
67
+ * - Spring commit via framer-motion animate() (D-08: stiffness 500, damping 35, mass 1)
68
+ * - 'settling' state (D-12) hard-blocks re-engagement during spring
69
+ * - committed state removed (D-05) — rolls into settling → idle
70
+ * - Keyboard step (D-16): onKeyboardStep('grow'|'shrink')
71
+ * - Escape spring-back with cursor velocity (D-15)
72
+ * - Touch tap-to-reveal with 5s idle (D-19)
73
+ * - prefers-reduced-motion bypass (D-22) — instant transform
53
74
  *
54
- * The hook does NOT inspect any reptime-domain data. The consumer passes `stackable`
55
- * (from metricInfo) and the hook emits the correct mode string no cross-domain leakage.
56
- *
57
- * Threat mitigations:
58
- * T-10.03-01: clearDwell() in cleanup useEffect prevents timer leak on unmount
59
- * T-10.03-02: Math.min/max clamp keeps targetColSpan ∈ {1,2,3}
60
- *
61
- * Motion contract (AN-06): enterMs=180, exitMs=120 (defaults)
75
+ * Preserved invariants:
76
+ * - Bug A React 19 updater purity side effects fire OUTSIDE setState updaters
77
+ * - Bug E handle pointerenter keeps zone alive on sentinel→handle crossover
78
+ * - Dwell timer cleanup on unmount
62
79
  */
63
- export declare function useEdgeHoverResize({ currentColSpan, stackable, onResize, onCancel, dwellMs, enterMs, exitMs, }: UseEdgeHoverResizeArgs): UseEdgeHoverResizeReturn;
80
+ export declare function useEdgeHoverResize({ currentColSpan, stackable, onResize, onCancel, step, tuning, dwellMs, enterMs, exitMs, }: UseEdgeHoverResizeArgs): UseEdgeHoverResizeReturn;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * VelocityBuffer — D-09 ring buffer for pointer velocity sampling.
3
+ *
4
+ * Stores up to `capacity` (3) VelocitySample entries. Computes a
5
+ * weighted-average velocity over the ring using 3×/2×/1× weights
6
+ * with divisor 6 (canonical formula per PLAN 19-01 resolution of
7
+ * §U1). When fewer than 3 samples exist, degrades gracefully:
8
+ * - 0 samples → 0
9
+ * - 1 sample → 0 (caller sees no velocity)
10
+ * - 2 samples → single-event dx/dt fallback (AP-20: never zero on a gesture)
11
+ * - 3+ samples → (vRecent·3 + vMid·2 + vOld·1) / 6
12
+ * where vOld degrades to vMid when only 2 deltas are available across 3 samples
13
+ * (flat fallback; buffer still lives on 3 instants — plan §Task 2 resolution).
14
+ *
15
+ * dt is clamped to >= 1ms via Math.max(1, …) to avoid Infinity on same-tick duplicates.
16
+ *
17
+ * Units: px/sec.
18
+ */
19
+ export interface VelocitySample {
20
+ x: number;
21
+ t: number;
22
+ }
23
+ export declare class VelocityBuffer {
24
+ private samples;
25
+ private readonly capacity;
26
+ push(sample: VelocitySample): void;
27
+ clear(): void;
28
+ /**
29
+ * D-09 — weighted-average velocity (px/sec).
30
+ *
31
+ * 0 samples → 0
32
+ * 1 sample → 0
33
+ * 2 samples → single-event fallback dx/dt
34
+ * 3 samples → weighted avg with vOld falling back to vMid to keep the
35
+ * divisor at 6. Formula: (vRecent·3 + vMid·2 + vMid·1) / 6
36
+ * (see PLAN §Task 2 §U1 resolution).
37
+ */
38
+ getWeightedVelocity(): number;
39
+ }
@@ -11,3 +11,4 @@ export * from './ReconfigBacksideButtons';
11
11
  export * from './MetricCardGrid';
12
12
  export * from './TabbedDataView';
13
13
  export * from './MetricGroupContainer';
14
+ export * from './DevTuningPanel';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mt-gloss/ui",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "restricted"