@mks2508/mks-ui 0.11.0 → 0.11.1

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.
@@ -41,21 +41,22 @@
41
41
  position: absolute;
42
42
  top: 0;
43
43
  left: 50%;
44
- transform: translateX(-50%);
44
+ /* translateZ(0) keeps the filtered region on its own GPU layer so Safari
45
+ * re-renders the gooey filter smoothly while the body rect's geometry is
46
+ * animated via WAAPI (Sileo Safari formula). */
47
+ transform: translateX(-50%) translateZ(0);
48
+ contain: layout style;
45
49
  display: block;
46
50
  overflow: visible;
47
51
  pointer-events: none;
48
52
  }
49
53
 
54
+ /* The body rect's x/y/width/height are driven by the Web Animations API
55
+ * (rect.animate, see index.tsx) — NOT a CSS transition, which WebKit refuses
56
+ * to interpolate on SVG geometry attributes. `will-change` is the only hint
57
+ * left here. */
50
58
  .gms-body-rect {
51
59
  will-change: x, y, width, height;
52
- transition:
53
- x var(--gms-duration) var(--gms-ease),
54
- y var(--gms-duration) var(--gms-ease),
55
- width var(--gms-duration) var(--gms-ease),
56
- height var(--gms-duration) var(--gms-ease),
57
- rx var(--gms-duration) var(--gms-ease),
58
- ry var(--gms-duration) var(--gms-ease);
59
60
  }
60
61
 
61
62
  .gms-pill {
package/dist/index.css CHANGED
@@ -1033,21 +1033,22 @@
1033
1033
  position: absolute;
1034
1034
  top: 0;
1035
1035
  left: 50%;
1036
- transform: translateX(-50%);
1036
+ /* translateZ(0) keeps the filtered region on its own GPU layer so Safari
1037
+ * re-renders the gooey filter smoothly while the body rect's geometry is
1038
+ * animated via WAAPI (Sileo Safari formula). */
1039
+ transform: translateX(-50%) translateZ(0);
1040
+ contain: layout style;
1037
1041
  display: block;
1038
1042
  overflow: visible;
1039
1043
  pointer-events: none;
1040
1044
  }
1041
1045
 
1046
+ /* The body rect's x/y/width/height are driven by the Web Animations API
1047
+ * (rect.animate, see index.tsx) — NOT a CSS transition, which WebKit refuses
1048
+ * to interpolate on SVG geometry attributes. `will-change` is the only hint
1049
+ * left here. */
1042
1050
  .gms-body-rect {
1043
1051
  will-change: x, y, width, height;
1044
- transition:
1045
- x var(--gms-duration) var(--gms-ease),
1046
- y var(--gms-duration) var(--gms-ease),
1047
- width var(--gms-duration) var(--gms-ease),
1048
- height var(--gms-duration) var(--gms-ease),
1049
- rx var(--gms-duration) var(--gms-ease),
1050
- ry var(--gms-duration) var(--gms-ease);
1051
1052
  }
1052
1053
 
1053
1054
  .gms-pill {
@@ -7,9 +7,9 @@ import { useIsInView } from "./hooks/DOM/UseIsInView.js";
7
7
  import { CountingNumber } from "./primitives/CountingNumber/index.js";
8
8
  import { DOT_MATRIX_PATTERNS, buildDelays } from "./primitives/DotMatrix/patterns.js";
9
9
  import { DotMatrix } from "./primitives/DotMatrix/index.js";
10
+ import { ANIMATION_CONFIGS, ANIMATION_DEFAULTS, EASINGS, EFFECTS, PRESETS, RESPONSIVE_CONFIGS, TIMING, TRANSFORMS, getResponsiveDuration, getResponsiveStagger } from "./primitives/waapi/core/animationConstants.js";
10
11
  import { DEFAULT_GOOEY_CONFIG } from "./primitives/GooeyMorphingSurface/GooeyMorphingSurface.types.js";
11
12
  import { GooeyMorphingSurface } from "./primitives/GooeyMorphingSurface/index.js";
12
- import { ANIMATION_CONFIGS, ANIMATION_DEFAULTS, EASINGS, EFFECTS, PRESETS, RESPONSIVE_CONFIGS, TIMING, TRANSFORMS, getResponsiveDuration, getResponsiveStagger } from "./primitives/waapi/core/animationConstants.js";
13
13
  import { useElementRegistry } from "./primitives/waapi/core/useElementRegistry.js";
14
14
  import { usePositionCapture } from "./primitives/waapi/core/usePositionCapture.js";
15
15
  import { useFLIPAnimation } from "./primitives/waapi/core/useFLIPAnimation.js";
@@ -41,21 +41,22 @@
41
41
  position: absolute;
42
42
  top: 0;
43
43
  left: 50%;
44
- transform: translateX(-50%);
44
+ /* translateZ(0) keeps the filtered region on its own GPU layer so Safari
45
+ * re-renders the gooey filter smoothly while the body rect's geometry is
46
+ * animated via WAAPI (Sileo Safari formula). */
47
+ transform: translateX(-50%) translateZ(0);
48
+ contain: layout style;
45
49
  display: block;
46
50
  overflow: visible;
47
51
  pointer-events: none;
48
52
  }
49
53
 
54
+ /* The body rect's x/y/width/height are driven by the Web Animations API
55
+ * (rect.animate, see index.tsx) — NOT a CSS transition, which WebKit refuses
56
+ * to interpolate on SVG geometry attributes. `will-change` is the only hint
57
+ * left here. */
50
58
  .gms-body-rect {
51
59
  will-change: x, y, width, height;
52
- transition:
53
- x var(--gms-duration) var(--gms-ease),
54
- y var(--gms-duration) var(--gms-ease),
55
- width var(--gms-duration) var(--gms-ease),
56
- height var(--gms-duration) var(--gms-ease),
57
- rx var(--gms-duration) var(--gms-ease),
58
- ry var(--gms-duration) var(--gms-ease);
59
60
  }
60
61
 
61
62
  .gms-pill {
@@ -1,21 +1,3 @@
1
- /**
2
- * GooeyMorphingSurface — headless pill→card gooey morph primitive.
3
- *
4
- * Paints an SVG canvas where a pill-shaped rect sits on top and a card-
5
- * shaped rect sits below (body). Both rects share a gaussian-blur +
6
- * alpha-threshold filter that fuses them into a single silhouette with a
7
- * soft metaball throat wherever they overlap. When `open` is false, the
8
- * body rect is a geometric clone of the pill (same x/y/w/h/rx) — no extra
9
- * halo, no pop. When `open` flips to true, all six SVG presentation attrs
10
- * (x, y, width, height, rx, ry) interpolate in lock-step, producing a
11
- * continuous morph from pill to card.
12
- *
13
- * The primitive is purely presentational: it does not own open state, does
14
- * not bind click handlers, does not trap focus. Use `<GooeyButton>` for
15
- * the ergonomic wrapper that adds those behaviours.
16
- *
17
- * @module @mks2508/mks-ui/react/primitives/GooeyMorphingSurface
18
- */
19
1
  import './gooey-morphing-surface.css';
20
2
  import { type IGooeyMorphingSurfaceProps } from './GooeyMorphingSurface.types';
21
3
  export * from './GooeyMorphingSurface.types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-ui/primitives/GooeyMorphingSurface/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,8BAA8B,CAAC;AACtC,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,8BAA8B,CAAC;AAEtC,cAAc,8BAA8B,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,EACpC,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,MAAM,EAAE,UAAU,EAClB,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAiB,GAChC,EAAE,0BAA0B,2CAkH5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-ui/primitives/GooeyMorphingSurface/index.tsx"],"names":[],"mappings":"AAuCA,OAAO,8BAA8B,CAAC;AACtC,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,8BAA8B,CAAC;AAEtC,cAAc,8BAA8B,CAAC;AAoB7C;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,EACpC,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,MAAM,EAAE,UAAU,EAClB,SAAS,EACT,KAAK,EACL,aAAa,EAAE,UAAiB,GAChC,EAAE,0BAA0B,2CAsL5B"}
@@ -1,7 +1,10 @@
1
+ 'use client';
2
+
1
3
  import { cn } from "../../lib/utils.js";
4
+ import { getResponsiveDuration } from "../waapi/core/animationConstants.js";
2
5
  import "./gooey-morphing-surface.js";
3
6
  import { DEFAULT_GOOEY_CONFIG } from "./GooeyMorphingSurface.types.js";
4
- import { useId } from "react";
7
+ import { useEffect, useId, useRef, useState } from "react";
5
8
  import { jsx, jsxs } from "react/jsx-runtime";
6
9
 
7
10
  //#region src/react-ui/primitives/GooeyMorphingSurface/index.tsx
@@ -12,10 +15,23 @@ import { jsx, jsxs } from "react/jsx-runtime";
12
15
  * shaped rect sits below (body). Both rects share a gaussian-blur +
13
16
  * alpha-threshold filter that fuses them into a single silhouette with a
14
17
  * soft metaball throat wherever they overlap. When `open` is false, the
15
- * body rect is a geometric clone of the pill (same x/y/w/h/rx) — no extra
16
- * halo, no pop. When `open` flips to true, all six SVG presentation attrs
17
- * (x, y, width, height, rx, ry) interpolate in lock-step, producing a
18
- * continuous morph from pill to card.
18
+ * body rect is a geometric clone of the pill (same x/y/w/h) — no extra
19
+ * halo, no pop. When `open` flips to true, the body rect's geometry
20
+ * (x, y, width, height) interpolates to the card, producing a continuous
21
+ * morph from pill to card.
22
+ *
23
+ * ── Cross-browser note (the reason this primitive is WAAPI-driven) ──
24
+ * The body rect morphs via the **Web Animations API** (`rect.animate(...)`),
25
+ * NOT a CSS `transition` on the SVG geometry attributes. WebKit/Safari does
26
+ * not interpolate `x/y/width/height` of an `<rect>` through a CSS transition
27
+ * (the morph hard-SNAPS), whereas it DOES animate them through WAAPI. This is
28
+ * the same "Sileo Safari formula" the `GooeyCanvas` primitive uses:
29
+ * - geometry animated via `rect.animate([from], [to], { fill: 'forwards' })`
30
+ * with explicit `px` units (Chrome WAAPI requires units on SVG geom props),
31
+ * - a 1-rAF readiness gate before the first animation,
32
+ * - `lastValues` ref so mid-flight reversals start from the live target,
33
+ * - `transform: translateZ(0)` + a static filter id so Safari keeps the
34
+ * filtered region on its own layer and re-renders it smoothly.
19
35
  *
20
36
  * The primitive is purely presentational: it does not own open state, does
21
37
  * not bind click handlers, does not trap focus. Use `<GooeyButton>` for
@@ -23,6 +39,15 @@ import { jsx, jsxs } from "react/jsx-runtime";
23
39
  *
24
40
  * @module @mks2508/mks-ui/react/primitives/GooeyMorphingSurface
25
41
  */
42
+ /** Build a WAAPI keyframe from a geometry, with the `px` units Chrome needs. */
43
+ function geomKeyframe(g) {
44
+ return {
45
+ x: `${g.x}px`,
46
+ y: `${g.y}px`,
47
+ width: `${g.width}px`,
48
+ height: `${g.height}px`
49
+ };
50
+ }
26
51
  /**
27
52
  * Headless gooey pill→card morph surface.
28
53
  *
@@ -45,11 +70,67 @@ function GooeyMorphingSurface({ open, pill, card, config: userConfig, className,
45
70
  const pillRadius = pillHeight / 2;
46
71
  const svgHeight = pillHeight + cardHeight + mergeOverlap;
47
72
  const pillX = (cardWidth - pillWidth) / 2 + pillOffsetX;
48
- const bodyX = open ? 0 : pillX;
49
- const bodyY = open ? pillHeight - mergeOverlap : 0;
50
- const bodyW = open ? cardWidth : pillWidth;
51
- const bodyH = open ? cardHeight + mergeOverlap : pillHeight;
52
- const bodyR = open ? cardRadius : pillRadius;
73
+ const closedGeom = {
74
+ x: pillX,
75
+ y: 0,
76
+ width: pillWidth,
77
+ height: pillHeight
78
+ };
79
+ const openGeom = {
80
+ x: 0,
81
+ y: pillHeight - mergeOverlap,
82
+ width: cardWidth,
83
+ height: cardHeight + mergeOverlap
84
+ };
85
+ const bodyRectRef = useRef(null);
86
+ const animRef = useRef(null);
87
+ const lastValues = useRef(open ? openGeom : closedGeom);
88
+ const [ready, setReady] = useState(false);
89
+ useEffect(() => {
90
+ const rect = bodyRectRef.current;
91
+ if (!rect) return;
92
+ if (open) {
93
+ lastValues.current = openGeom;
94
+ rect.animate([geomKeyframe(openGeom)], {
95
+ duration: 0,
96
+ fill: "forwards"
97
+ });
98
+ }
99
+ const raf = requestAnimationFrame(() => setReady(true));
100
+ return () => cancelAnimationFrame(raf);
101
+ }, []);
102
+ useEffect(() => {
103
+ const rect = bodyRectRef.current;
104
+ if (!rect || !ready) return;
105
+ const to = open ? openGeom : closedGeom;
106
+ const from = lastValues.current;
107
+ lastValues.current = to;
108
+ const duration = getResponsiveDuration(durationMs);
109
+ if (animRef.current) animRef.current.cancel();
110
+ if (duration === 0) {
111
+ rect.animate([geomKeyframe(to)], {
112
+ duration: 0,
113
+ fill: "forwards"
114
+ });
115
+ return;
116
+ }
117
+ animRef.current = rect.animate([geomKeyframe(from), geomKeyframe(to)], {
118
+ duration,
119
+ easing,
120
+ fill: "forwards"
121
+ });
122
+ }, [
123
+ ready,
124
+ open,
125
+ durationMs,
126
+ easing,
127
+ pillWidth,
128
+ pillHeight,
129
+ cardWidth,
130
+ cardHeight,
131
+ mergeOverlap,
132
+ pillOffsetX
133
+ ]);
53
134
  const rootStyle = {
54
135
  "--gms-pill-w": `${pillWidth}px`,
55
136
  "--gms-pill-h": `${pillHeight}px`,
@@ -111,16 +192,15 @@ function GooeyMorphingSurface({ open, pill, card, config: userConfig, className,
111
192
  ry: pillRadius,
112
193
  fill
113
194
  }), /* @__PURE__ */ jsx("rect", {
195
+ ref: bodyRectRef,
114
196
  className: "gms-body-rect",
115
- fill,
116
- style: {
117
- x: bodyX,
118
- y: bodyY,
119
- width: bodyW,
120
- height: bodyH,
121
- rx: bodyR,
122
- ry: bodyR
123
- }
197
+ x: closedGeom.x,
198
+ y: closedGeom.y,
199
+ width: closedGeom.width,
200
+ height: closedGeom.height,
201
+ rx: cardRadius,
202
+ ry: cardRadius,
203
+ fill
124
204
  })]
125
205
  })]
126
206
  }),
@@ -4,9 +4,9 @@ import { AutoHeight } from "./AutoHeight/index.js";
4
4
  import { CountingNumber } from "./CountingNumber/index.js";
5
5
  import { DOT_MATRIX_PATTERNS, buildDelays } from "./DotMatrix/patterns.js";
6
6
  import { DotMatrix } from "./DotMatrix/index.js";
7
+ import { ANIMATION_CONFIGS, ANIMATION_DEFAULTS, EASINGS, EFFECTS, PRESETS, RESPONSIVE_CONFIGS, TIMING, TRANSFORMS, getResponsiveDuration, getResponsiveStagger } from "./waapi/core/animationConstants.js";
7
8
  import { DEFAULT_GOOEY_CONFIG } from "./GooeyMorphingSurface/GooeyMorphingSurface.types.js";
8
9
  import { GooeyMorphingSurface } from "./GooeyMorphingSurface/index.js";
9
- import { ANIMATION_CONFIGS, ANIMATION_DEFAULTS, EASINGS, EFFECTS, PRESETS, RESPONSIVE_CONFIGS, TIMING, TRANSFORMS, getResponsiveDuration, getResponsiveStagger } from "./waapi/core/animationConstants.js";
10
10
  import { useElementRegistry } from "./waapi/core/useElementRegistry.js";
11
11
  import { usePositionCapture } from "./waapi/core/usePositionCapture.js";
12
12
  import { useFLIPAnimation } from "./waapi/core/useFLIPAnimation.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mks2508/mks-ui",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "UI component library - Shadcn/Animate UI based with DevEnv components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",