@enigmax/primitives 0.4.0 → 0.5.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.
@@ -11,7 +11,7 @@ var DEFAULTS = {
11
11
  reverse: false,
12
12
  vertical: false,
13
13
  draggable: true,
14
- hoverScale: 1,
14
+ hover: "off",
15
15
  decay: DEFAULT_DECAY,
16
16
  manageStyles: true,
17
17
  copies: "clone"
@@ -37,10 +37,17 @@ function createMarquee(lane, track, options = {}) {
37
37
  let lastTouchAt = 0;
38
38
  const motionQuery = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
39
39
  let reducedMotion = motionQuery?.matches ?? false;
40
+ function hoverTarget(cruise) {
41
+ const hover = opts.hover ?? "off";
42
+ if (hover === "off") return cruise;
43
+ if (hover === "pause") return 0;
44
+ if (typeof hover === "number") return cruise * hover;
45
+ return (cruise < 0 ? -1 : 1) * Math.abs(hover.speed);
46
+ }
40
47
  function target() {
41
48
  if (reducedMotion || paused) return 0;
42
49
  const cruise = opts.reverse ? -opts.speed : opts.speed;
43
- return hovering ? cruise * opts.hoverScale : cruise;
50
+ return hovering ? hoverTarget(cruise) : cruise;
44
51
  }
45
52
  function axisSize(element) {
46
53
  return opts.vertical ? element.offsetHeight : element.offsetWidth;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,21 @@ export { F as FuseConstructor, a as FuseLike, S as SearchInstance, b as SearchMa
11
11
  * comments marked "non-negotiable" name the bug that the obvious implementation
12
12
  * shipped with.
13
13
  */
14
+ /**
15
+ * What a MOUSE resting on the row does to its speed. One option covers every case, so
16
+ * there is no pile of booleans to reconcile:
17
+ *
18
+ * - `"off"` ignore hover entirely (the default)
19
+ * - `"pause"` stop while the pointer is on it
20
+ * - a number multiply the cruise speed: `0.15` crawls, `1` changes nothing, `2` doubles
21
+ * - `{ speed }` an ABSOLUTE speed in px/s, whatever the cruise is; direction still
22
+ * follows `reverse`, so you give a magnitude and not a sign
23
+ *
24
+ * Touch never triggers any of it - see the pointerType note on the hover handler.
25
+ */
26
+ type MarqueeHover = number | "off" | "pause" | {
27
+ speed: number;
28
+ };
14
29
  interface MarqueeOptions {
15
30
  /**
16
31
  * Pixels per second. NOT a duration: a duration ties the speed to the item
@@ -23,8 +38,8 @@ interface MarqueeOptions {
23
38
  vertical?: boolean;
24
39
  /** Allow grabbing and throwing the row. */
25
40
  draggable?: boolean;
26
- /** Speed multiplier while a MOUSE rests on the row. 0 pauses it. */
27
- hoverScale?: number;
41
+ /** What a mouse resting on the row does to its speed. See {@link MarqueeHover}. */
42
+ hover?: MarqueeHover;
28
43
  /** Fraction of the remaining velocity gap left after one second. */
29
44
  decay?: number;
30
45
  /**
@@ -147,4 +162,4 @@ interface InputInstance {
147
162
  */
148
163
  declare function createInput(input: HTMLInputElement, options?: InputOptions): InputInstance;
149
164
 
150
- export { type InputAction, type InputActionState, type InputIcon, type InputInstance, type InputOptions, type MarqueeInstance, type MarqueeOptions, createInput, createMarquee };
165
+ export { type InputAction, type InputActionState, type InputIcon, type InputInstance, type InputOptions, type MarqueeHover, type MarqueeInstance, type MarqueeOptions, createInput, createMarquee };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { createInput, createMarquee } from './chunk-W2DRHYC2.js';
1
+ export { createInput, createMarquee } from './chunk-2DVVWPHM.js';
2
2
  export { createSearch } from './chunk-UZFEEFMF.js';
@@ -1,6 +1,6 @@
1
1
  import { RefObject } from 'react';
2
2
  import { MarqueeOptions, InputOptions } from '../index.js';
3
- export { InputAction, InputIcon, MarqueeInstance } from '../index.js';
3
+ export { InputAction, InputIcon, MarqueeHover, MarqueeInstance } from '../index.js';
4
4
  import { b as SearchMatch, c as SearchOptions } from '../search-CsO3L1Lw.js';
5
5
  export { F as FuseConstructor } from '../search-CsO3L1Lw.js';
6
6
 
@@ -28,7 +28,7 @@ interface UseMarqueeResult {
28
28
  * to the item count, so the row accelerates every time content is added.
29
29
  *
30
30
  * ```tsx
31
- * const { laneRef, trackRef, copies } = useMarquee({ speed: 80, hoverScale: 0.15 });
31
+ * const { laneRef, trackRef, copies } = useMarquee({ speed: 80, hover: 0.15 });
32
32
  * return (
33
33
  * <div ref={laneRef}>
34
34
  * <div ref={trackRef} style={{ display: "flex" }}>
@@ -1,4 +1,4 @@
1
- import { createMarquee, createInput } from '../chunk-W2DRHYC2.js';
1
+ import { createMarquee, createInput } from '../chunk-2DVVWPHM.js';
2
2
  import { createSearch } from '../chunk-UZFEEFMF.js';
3
3
  import { useRef, useState, useMemo, useEffect, useLayoutEffect } from 'react';
4
4
 
@@ -39,7 +39,7 @@ function useMarquee(options = {}) {
39
39
  }, []);
40
40
  useIsomorphicLayoutEffect(() => {
41
41
  instanceRef.current?.update(options);
42
- }, [options.speed, options.reverse, options.vertical, options.draggable, options.hoverScale, options.decay]);
42
+ }, [options.speed, options.reverse, options.vertical, options.draggable, options.hover, options.decay]);
43
43
  useIsomorphicLayoutEffect(() => {
44
44
  instanceRef.current?.measure();
45
45
  }, [copies]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/primitives",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Headless interaction primitives: the behaviour, the timing and the accessibility of components like a draggable infinite marquee, with no styles of their own. Framework-agnostic core plus thin adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  import "./marquee.css";
2
2
  import type { ReactNode, Ref } from "react";
3
- import { useMarquee } from "@enigmax/primitives/react";
3
+ import { useMarquee, type MarqueeHover } from "@enigmax/primitives/react";
4
4
 
5
5
  /**
6
6
  * A styled marquee, yours to edit.
@@ -12,8 +12,12 @@ export interface MarqueeProps<T> {
12
12
  items: T[];
13
13
  /** Pixels per second. Never a duration - the row would speed up as items are added. */
14
14
  speed?: number;
15
- /** Speed multiplier while a mouse rests on the row. 0 pauses it. */
16
- hoverScale?: number;
15
+ /**
16
+ * What a mouse resting on the row does to its speed:
17
+ * "off" ignores hover, "pause" stops, a number multiplies the cruise speed
18
+ * (0.15 crawls, 2 doubles), { speed } sets an absolute px/s.
19
+ */
20
+ hover?: MarqueeHover;
17
21
  reverse?: boolean;
18
22
  /** Fade both ends so items enter and leave instead of being cut. */
19
23
  fade?: boolean;
@@ -24,13 +28,13 @@ export interface MarqueeProps<T> {
24
28
  export function Marquee<T>({
25
29
  items,
26
30
  speed = 70,
27
- hoverScale = 1,
31
+ hover = "off",
28
32
  reverse = false,
29
33
  fade = true,
30
34
  className = "",
31
35
  children
32
36
  }: MarqueeProps<T>) {
33
- const { laneRef, trackRef, copies, dragging } = useMarquee({ speed, hoverScale, reverse });
37
+ const { laneRef, trackRef, copies, dragging } = useMarquee({ speed, hover, reverse });
34
38
 
35
39
  return (
36
40
  <div
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode, Ref } from "react";
2
- import { useMarquee } from "@enigmax/primitives/react";
2
+ import { useMarquee, type MarqueeHover } from "@enigmax/primitives/react";
3
3
 
4
4
  /**
5
5
  * A styled marquee, yours to edit.
@@ -16,8 +16,12 @@ export interface MarqueeProps<T> {
16
16
  items: T[];
17
17
  /** Pixels per second. Never a duration - the row would speed up as items are added. */
18
18
  speed?: number;
19
- /** Speed multiplier while a mouse rests on the row. 0 pauses it. */
20
- hoverScale?: number;
19
+ /**
20
+ * What a mouse resting on the row does to its speed:
21
+ * "off" ignores hover, "pause" stops, a number multiplies the cruise speed
22
+ * (0.15 crawls, 2 doubles), { speed } sets an absolute px/s.
23
+ */
24
+ hover?: MarqueeHover;
21
25
  reverse?: boolean;
22
26
  /** Fade both ends so items enter and leave instead of being cut. */
23
27
  fade?: boolean;
@@ -28,13 +32,13 @@ export interface MarqueeProps<T> {
28
32
  export function Marquee<T>({
29
33
  items,
30
34
  speed = 70,
31
- hoverScale = 1,
35
+ hover = "off",
32
36
  reverse = false,
33
37
  fade = true,
34
38
  className = "",
35
39
  children
36
40
  }: MarqueeProps<T>) {
37
- const { laneRef, trackRef, copies, dragging } = useMarquee({ speed, hoverScale, reverse });
41
+ const { laneRef, trackRef, copies, dragging } = useMarquee({ speed, hover, reverse });
38
42
 
39
43
  return (
40
44
  <div
@@ -29,6 +29,20 @@ const TOUCH_HOVER_SUPPRESS_MS = 1000;
29
29
  /** Below this the row is idle and the frame loop can stop. */
30
30
  const IDLE_EPSILON = 0.01;
31
31
 
32
+ /**
33
+ * What a MOUSE resting on the row does to its speed. One option covers every case, so
34
+ * there is no pile of booleans to reconcile:
35
+ *
36
+ * - `"off"` ignore hover entirely (the default)
37
+ * - `"pause"` stop while the pointer is on it
38
+ * - a number multiply the cruise speed: `0.15` crawls, `1` changes nothing, `2` doubles
39
+ * - `{ speed }` an ABSOLUTE speed in px/s, whatever the cruise is; direction still
40
+ * follows `reverse`, so you give a magnitude and not a sign
41
+ *
42
+ * Touch never triggers any of it - see the pointerType note on the hover handler.
43
+ */
44
+ export type MarqueeHover = number | "off" | "pause" | { speed: number; };
45
+
32
46
  export interface MarqueeOptions {
33
47
  /**
34
48
  * Pixels per second. NOT a duration: a duration ties the speed to the item
@@ -41,8 +55,8 @@ export interface MarqueeOptions {
41
55
  vertical?: boolean;
42
56
  /** Allow grabbing and throwing the row. */
43
57
  draggable?: boolean;
44
- /** Speed multiplier while a MOUSE rests on the row. 0 pauses it. */
45
- hoverScale?: number;
58
+ /** What a mouse resting on the row does to its speed. See {@link MarqueeHover}. */
59
+ hover?: MarqueeHover;
46
60
  /** Fraction of the remaining velocity gap left after one second. */
47
61
  decay?: number;
48
62
  /**
@@ -94,7 +108,7 @@ const DEFAULTS: ResolvedOptions = {
94
108
  reverse: false,
95
109
  vertical: false,
96
110
  draggable: true,
97
- hoverScale: 1,
111
+ hover: "off",
98
112
  decay: DEFAULT_DECAY,
99
113
  manageStyles: true,
100
114
  copies: "clone"
@@ -134,11 +148,21 @@ export function createMarquee(lane: HTMLElement, track: HTMLElement, options: Ma
134
148
  : null;
135
149
  let reducedMotion = motionQuery?.matches ?? false;
136
150
 
151
+ /** The speed a mouse on the row asks for. One option, four shapes, no flags to reconcile. */
152
+ function hoverTarget(cruise: number): number {
153
+ const hover = opts.hover ?? "off";
154
+ if (hover === "off") return cruise;
155
+ if (hover === "pause") return 0;
156
+ if (typeof hover === "number") return cruise * hover;
157
+ // An absolute speed is a magnitude: the row keeps travelling the way it was.
158
+ return (cruise < 0 ? -1 : 1) * Math.abs(hover.speed);
159
+ }
160
+
137
161
  /** Read live every frame, never captured: cruise, hover and reduced motion all ease. */
138
162
  function target(): number {
139
163
  if (reducedMotion || paused) return 0;
140
164
  const cruise = opts.reverse ? -opts.speed : opts.speed;
141
- return hovering ? cruise * opts.hoverScale : cruise;
165
+ return hovering ? hoverTarget(cruise) : cruise;
142
166
  }
143
167
 
144
168
  function axisSize(element: HTMLElement): number {
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { createMarquee, type MarqueeOptions, type MarqueeInstance } from "@/core/marquee";
1
+ export { createMarquee, type MarqueeOptions, type MarqueeInstance, type MarqueeHover } from "@/core/marquee";
2
2
  export { createInput, type InputOptions, type InputInstance, type InputAction, type InputIcon, type InputActionState } from "@/core/input";
3
3
  export { createSearch, type SearchOptions, type SearchInstance, type SearchMatch, type FuseConstructor, type FuseLike } from "@/core/search";
@@ -1,5 +1,5 @@
1
1
  export { useMarquee, type UseMarqueeResult } from "@/react/use-marquee";
2
- export { type MarqueeOptions, type MarqueeInstance } from "@/core/marquee";
2
+ export { type MarqueeOptions, type MarqueeInstance, type MarqueeHover } from "@/core/marquee";
3
3
  export { useInput, type UseInputResult } from "@/react/use-input";
4
4
  export { useSearch, type UseSearchResult } from "@/react/use-search";
5
5
  export { type InputOptions, type InputAction, type InputIcon } from "@/core/input";
@@ -29,7 +29,7 @@ export interface UseMarqueeResult {
29
29
  * to the item count, so the row accelerates every time content is added.
30
30
  *
31
31
  * ```tsx
32
- * const { laneRef, trackRef, copies } = useMarquee({ speed: 80, hoverScale: 0.15 });
32
+ * const { laneRef, trackRef, copies } = useMarquee({ speed: 80, hover: 0.15 });
33
33
  * return (
34
34
  * <div ref={laneRef}>
35
35
  * <div ref={trackRef} style={{ display: "flex" }}>
@@ -87,7 +87,7 @@ export function useMarquee(options: MarqueeOptions = {}): UseMarqueeResult {
87
87
 
88
88
  useIsomorphicLayoutEffect(() => {
89
89
  instanceRef.current?.update(options);
90
- }, [options.speed, options.reverse, options.vertical, options.draggable, options.hoverScale, options.decay]);
90
+ }, [options.speed, options.reverse, options.vertical, options.draggable, options.hover, options.decay]);
91
91
 
92
92
  // A copy count change means new children; the lap has to be read again.
93
93
  useIsomorphicLayoutEffect(() => {