@claralight-design/react 0.0.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.
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/lib/anchored.d.ts +152 -0
- package/dist/lib/anchored.js +821 -0
- package/dist/lib/anchored.js.map +1 -0
- package/dist/lib/morph.js +484 -0
- package/dist/lib/morph.js.map +1 -0
- package/dist/lib/squircle.d.ts +95 -0
- package/dist/lib/squircle.js +220 -0
- package/dist/lib/squircle.js.map +1 -0
- package/dist/lib/utils.d.ts +30 -0
- package/dist/lib/utils.js +76 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/ui/button.d.ts +40 -0
- package/dist/ui/button.js +104 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/card.d.ts +54 -0
- package/dist/ui/card.js +82 -0
- package/dist/ui/card.js.map +1 -0
- package/dist/ui/dialog.d.ts +82 -0
- package/dist/ui/dialog.js +129 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/input.d.ts +34 -0
- package/dist/ui/input.js +60 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/popover.d.ts +67 -0
- package/dist/ui/popover.js +93 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/ui/scroll-area.d.ts +110 -0
- package/dist/ui/scroll-area.js +125 -0
- package/dist/ui/scroll-area.js.map +1 -0
- package/dist/ui/select.d.ts +113 -0
- package/dist/ui/select.js +213 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/tooltip.d.ts +108 -0
- package/dist/ui/tooltip.js +142 -0
- package/dist/ui/tooltip.js.map +1 -0
- package/package.json +72 -0
- package/styles/anchored.css +115 -0
- package/styles/base.css +309 -0
- package/styles/fonts/README.md +63 -0
- package/styles/index.css +28 -0
- package/styles/scroll-area.css +299 -0
- package/styles/theme.css +540 -0
- package/styles/tooltip.css +156 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchored.js","names":[],"sources":["../../src/lib/anchored.tsx"],"sourcesContent":["\"use client\";\n\nimport { type BorderConfig, generatePath, getLayoutSize, parseBorder } from \"@lisse/core\";\nimport {\n type ComponentProps,\n type CSSProperties,\n cloneElement,\n Fragment,\n isValidElement,\n type ReactNode,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { composeRefs, type RadiusToken } from \"@/lib/squircle\";\nimport { cn, tokenNumber } from \"@/lib/utils\";\n\n/**\n * ClaraLight's anchored overlay surface — the shape behind `Popover` and\n * `Tooltip`.\n *\n * ## Why this is not `Squircle`\n *\n * A ClaraLight overlay points at its anchor, and the arrow is **part of the\n * surface**, not a second element parked against its edge. That is forced by\n * the design language rather than chosen for elegance: the frost fill is\n * translucent, so two overlapping elements composite their alpha into a\n * visible dark seam, and the 1px outline of the body would run straight across\n * the arrow's mouth. Both are plainly visible at rest, not just in motion.\n *\n * So the body and the tail are a single closed path. `Squircle` cannot express\n * that — Lisse's API takes corner options, with no way to hand it a path — so\n * this primitive owns the same three jobs `Squircle` delegates to Lisse:\n *\n * clip-path the fused path, which is also what clips the backdrop blur\n * the outline an SVG stroke over the same path, because `clip-path` crops\n * `border` (and `outline`) to nothing\n * sync re-measure when the surface resizes, flips side, or the anchor\n * moves the tail — and, for a shared tooltip, drive the tail\n * ahead of a surface that is still travelling\n *\n * It deliberately does **not** render a shadow. Anchored overlays in ClaraLight\n * sit on their outline and their blur alone.\n *\n * ## Why the body still comes from Lisse\n *\n * `generatePath` emits the Figma smoothing construction, which is the whole\n * argument in `squircle.tsx` for not using CSS `corner-shape`. Reimplementing\n * it here would fork the corner geometry of the design system, so the tail is\n * spliced into Lisse's own output instead: its straight edges are discrete\n * absolute `L` commands, which makes the splice an edit rather than a boolean\n * union.\n */\n\n/**\n * The physical side of the anchor a surface sits on.\n *\n * Base UI also accepts the logical `inline-start` / `inline-end`, which are\n * deliberately not forwarded: with a physical side in, Base UI's resolved side\n * (and therefore `data-side`, which drives both the tail and the padding) is\n * guaranteed to be one of these four, so the geometry never has to resolve\n * writing direction to know which edge the tail belongs on.\n */\nexport type AnchoredSide = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/** The tail sits on the edge facing the anchor — opposite the resolved side. */\nconst OPPOSITE: Record<AnchoredSide, AnchoredSide> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n};\n\n/** Direction of travel along each edge, following the generated path's winding. */\nconst ALONG: Record<AnchoredSide, Point> = {\n top: { x: 1, y: 0 },\n right: { x: 0, y: 1 },\n bottom: { x: -1, y: 0 },\n left: { x: 0, y: -1 },\n};\n\n/** Outward normal of each edge. */\nconst OUTWARD: Record<AnchoredSide, Point> = {\n top: { x: 0, y: -1 },\n right: { x: 1, y: 0 },\n bottom: { x: 0, y: 1 },\n left: { x: -1, y: 0 },\n};\n\ninterface Point {\n x: number;\n y: number;\n}\n\n/*\n * Tail control points, as fractions of the tail's half-width and extent.\n *\n * Two cubic segments per half keep the join with the body G2 continuous, so the\n * tail grows out of the edge instead of being stuck onto it. The compact\n * `0.84` body handle controls the root rounding; the shoulder and mid controls\n * are then *derived* so that join stays C2, and mirroring preserves tangent and\n * curvature at the rounded tip, whose `0.225` handle gives a tip radius of\n * about `0.446 * extent` at the 24x7 tail ClaraLight ships.\n *\n * These are curve construction, not design values: the tail's actual size is\n * `--cl-arrow-width` and `--cl-arrow-extent`.\n */\nconst TAIL_BODY_CONTROL = -0.84;\nconst TAIL_MID = -0.5;\nconst TAIL_TIP_CONTROL = -0.225;\nconst TAIL_SHOULDER_CONTROL = (4 * TAIL_MID + TAIL_BODY_CONTROL - TAIL_TIP_CONTROL) / 4;\nconst TAIL_MID_CONTROL = 2 * TAIL_MID - TAIL_SHOULDER_CONTROL;\n\n/** Sub-pixel slack when deciding whether a point sits on an edge. */\nconst EPSILON = 0.01;\n\nconst round = (value: number) => Number(value.toFixed(4));\nconst format = (point: Point) => `${round(point.x)} ${round(point.y)}`;\nconst clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);\n\nexport interface SurfaceGeometry {\n /** Border box of the whole surface, tail included. */\n width: number;\n height: number;\n /** Side of the anchor the surface was placed on. */\n side: AnchoredSide;\n radius: number;\n smoothing: number;\n /** Whether to splice the tail in at all. */\n arrow: boolean;\n /** How far the tail reaches past the body, and its full base width. */\n extent: number;\n arrowWidth: number;\n /** Tail centre along the tail's edge, from the surface's own origin. */\n center: number;\n}\n\nexport interface SurfaceOutline {\n /** The `clip-path` value: the surface's whole filled region. */\n clip: string;\n /**\n * The subpaths to stroke. One when the tail is part of the body's own\n * outline; two when the surface is a union, in which case each is stroked\n * clipped to the outside of the other so the shared join is never drawn.\n */\n outline: string[];\n /** Where the surface grows from: the tail's tip, or the anchored edge. */\n origin: Point;\n /**\n * Where the tail actually landed, after being held clear of the corners.\n *\n * The requested centre is a wish: it comes from the anchor, which can sit\n * anywhere, including off the end of the surface. This is the answer, and the\n * only value a caller may animate towards — following the wish instead would\n * chase a position the surface will not draw.\n */\n center: number;\n /**\n * The centres this surface could have drawn: how far the tail can reach along\n * its edge.\n *\n * Anything animating the tail needs this, not just the answer for one frame. A\n * follower aimed at an unreachable centre covers the whole distance to it in\n * its first step and lands on the limit — which is a jump, not a movement.\n * Aimed at the limit itself, the same follower slides.\n */\n range: { min: number; max: number };\n}\n\n/**\n * The surface's filled region and the strokes that trace its boundary.\n *\n * The tail is spliced into the body's own path whenever the anchored edge has\n * a straight run long enough to hold its base. That is the case for every\n * top and bottom overlay and for tall ones on the left or right, and it is the\n * better construction: one continuous outline, stroked in one pass.\n *\n * A short edge has no straight run at all — the corners meet in the middle of\n * it — and no amount of splicing can put a 24px base on it. There the surface\n * becomes the union of the body and a tail whose base sinks below the body's\n * outline, which is what Flutter draws too. The `clip-path` unions them by\n * winding, and the two strokes are clipped against each other.\n *\n * Both constructions need to know where the corners let go of the edge, and\n * that is read off the path Lisse emitted rather than derived from the radius —\n * see `edgeRun`.\n */\nexport function surfacePath(geometry: SurfaceGeometry): SurfaceOutline {\n const edge = OPPOSITE[geometry.side];\n const vertical = edge === \"left\" || edge === \"right\";\n const inset = geometry.arrow ? geometry.extent : 0;\n const bodyWidth = Math.max(0, geometry.width - (vertical ? inset : 0));\n const bodyHeight = Math.max(0, geometry.height - (vertical ? 0 : inset));\n // The body is pushed away from whichever edge the tail occupies.\n const offset: Point = { x: edge === \"left\" ? inset : 0, y: edge === \"top\" ? inset : 0 };\n const body = segments(\n generatePath(bodyWidth, bodyHeight, {\n radius: geometry.radius,\n smoothing: geometry.smoothing,\n }),\n );\n const span = vertical ? bodyHeight : bodyWidth;\n const depth = vertical ? bodyWidth : bodyHeight;\n\n if (!geometry.arrow || span <= 0 || depth <= 0) {\n const only = translate(body, offset);\n const middle = span / 2;\n return {\n clip: only,\n outline: [only],\n origin: edgeOrigin(edge, geometry, middle),\n center: middle,\n range: { min: middle, max: middle },\n };\n }\n\n const run = edgeRun(body, edge, bodyWidth, bodyHeight);\n const length = run ? run.end - run.start : 0;\n const halfWidth = fitTail(geometry.arrowWidth / 2, geometry.extent, span, run);\n\n if (run && length >= geometry.arrowWidth) {\n // The straight run holds the whole base, so the tail can be an edit to the\n // body's own outline. Both base ends stay on the run: a base that reached\n // into a corner would make the splice double back along the edge, which\n // self-intersects the fill and strokes the corner twice.\n const range = { min: run.start + halfWidth, max: run.end - halfWidth };\n const center = clamp(geometry.center, range.min, range.max);\n const spliced = translate(body, offset, {\n edge,\n tail: tail(edge, center, halfWidth, 0, geometry.extent, bodyWidth, bodyHeight, offset, false),\n width: bodyWidth,\n height: bodyHeight,\n });\n return {\n clip: spliced,\n outline: [spliced],\n origin: edgeOrigin(edge, geometry, center),\n center,\n range,\n };\n }\n\n // Keep the tail clear of the corners, or centre it when the body is too\n // small to hold it anywhere else.\n const minimum = geometry.radius + halfWidth + 1;\n const maximum = span - minimum;\n const range =\n maximum < minimum ? { min: span / 2, max: span / 2 } : { min: minimum, max: maximum };\n const center = clamp(geometry.center, range.min, range.max);\n const origin = edgeOrigin(edge, geometry, center);\n\n // No straight run: sink the base to where the body's outline actually is, so\n // the tail's sides cross it instead of stopping short and leaving a step.\n const startCorner = run ? run.start : span / 2;\n const endCorner = run ? span - run.end : span / 2;\n const sink = Math.min(\n depth / 2,\n Math.max(\n sunkBase(center - halfWidth, startCorner),\n sunkBase(span - center - halfWidth, endCorner),\n ) + 1,\n );\n if (halfWidth <= sink) {\n const only = translate(body, offset);\n return { clip: only, outline: [only], origin, center, range };\n }\n const bodyPath = translate(body, offset);\n const tailPath = tail(\n edge,\n center,\n halfWidth,\n -sink,\n geometry.extent,\n bodyWidth,\n bodyHeight,\n offset,\n true,\n );\n return {\n clip: `${bodyPath} ${tailPath}`,\n outline: [bodyPath, tailPath],\n origin,\n center,\n range,\n };\n}\n\n/**\n * The widest half-tail this edge can carry.\n *\n * A tail wider than the edge's straight run has to sit partly on the corners,\n * where the body's outline curves away beneath it — so the wider it gets, the\n * deeper it has to sink and the less of it stays above the surface. Rather than\n * bury a full-width tail and leave only its tip showing, narrow it until it\n * sinks by no more than a quarter of its own height, which keeps the shape\n * ClaraLight draws and simply scales it to the surface.\n */\nfunction fitTail(halfWidth: number, extent: number, span: number, run: Run | null): number {\n if (run && run.end - run.start >= halfWidth * 2) return halfWidth;\n // Without a run the corners meet in the middle of the edge, so each one is\n // half of it; otherwise take the deeper of the two the run left behind.\n const corner = run ? Math.max(run.start, span - run.end) : span / 2;\n const budget = extent / 4;\n // Inverse of `sunkBase`: how far into the corner a base end may reach before\n // the outline has dropped further than the budget allows.\n const reach = Math.sqrt(Math.max(0, 2 * corner * budget - budget * budget));\n return Math.max(0, Math.min(halfWidth, Math.max(0, span / 2 - corner) + reach));\n}\n\n/**\n * How deep the body's outline lies at a base end that has run `distance` past\n * the start of the edge, approximating the corner as a circle of its own\n * tangency length — exact at both ends of the corner and within a fraction of\n * a pixel between them, which is all the join needs.\n */\nfunction sunkBase(distance: number, corner: number): number {\n if (distance >= corner) return 0;\n const into = clamp(corner - distance, 0, corner);\n return corner - Math.sqrt(Math.max(0, corner * corner - into * into));\n}\n\n/** The tail's tip, in the surface's own coordinates: what the entrance grows from. */\nfunction edgeOrigin(edge: AnchoredSide, geometry: SurfaceGeometry, center: number): Point {\n switch (edge) {\n case \"top\":\n return { x: center, y: 0 };\n case \"bottom\":\n return { x: center, y: geometry.height };\n case \"left\":\n return { x: 0, y: center };\n case \"right\":\n return { x: geometry.width, y: center };\n }\n}\n\n/**\n * The tail, entering and leaving along the edge's own direction of travel so it\n * can be dropped straight into the body's path, or closed into a subpath of its\n * own when the surface is a union.\n *\n * `base` and `tip` are the tail's two ends measured outwards from the body's\n * edge, so a negative base is one that starts below the surface it grows from.\n */\nfunction tail(\n edge: AnchoredSide,\n center: number,\n halfWidth: number,\n base: number,\n tip: number,\n bodyWidth: number,\n bodyHeight: number,\n offset: Point,\n closed: boolean,\n): string {\n const along = ALONG[edge];\n const outward = OUTWARD[edge];\n const start: Point = {\n x: (edge === \"right\" ? bodyWidth : edge === \"left\" ? 0 : center) + offset.x,\n y: (edge === \"bottom\" ? bodyHeight : edge === \"top\" ? 0 : center) + offset.y,\n };\n /** `cross` runs along the edge from the tail's centre, `out` from base to tip. */\n const point = (cross: number, out: number): Point => {\n const distance = base + out * (tip - base);\n return {\n x: start.x + along.x * cross * halfWidth + outward.x * distance,\n y: start.y + along.y * cross * halfWidth + outward.y * distance,\n };\n };\n const curve = (c1: Point, c2: Point, end: Point) =>\n `C ${format(c1)} ${format(c2)} ${format(end)}`;\n\n return [\n `${closed ? \"M\" : \"L\"} ${format(point(-1, 0))}`,\n curve(point(TAIL_BODY_CONTROL, 0), point(TAIL_SHOULDER_CONTROL, 0), point(TAIL_MID, 0.25)),\n curve(point(TAIL_MID_CONTROL, 0.5), point(TAIL_TIP_CONTROL, 1), point(0, 1)),\n curve(point(-TAIL_TIP_CONTROL, 1), point(-TAIL_MID_CONTROL, 0.5), point(-TAIL_MID, 0.25)),\n curve(point(-TAIL_SHOULDER_CONTROL, 0), point(-TAIL_BODY_CONTROL, 0), point(1, 0)),\n ...(closed ? [\"Z\"] : []),\n ].join(\" \");\n}\n\ninterface Splice {\n edge: AnchoredSide;\n tail: string;\n width: number;\n height: number;\n}\n\n/** One command of a generated path, with the pen positions it runs between. */\ninterface Segment {\n type: string;\n /** The command as written, which is what a relative corner is re-emitted as. */\n raw: string;\n from: Point;\n to: Point;\n}\n\n/**\n * Lisse's output, split into commands that know where the pen was.\n *\n * Only `M` and `L` are absolute; corners are relative `c` and `a`, but they\n * still advance the pen, and every one of them ends on its last coordinate\n * pair — which is all the tracking needs.\n */\nfunction segments(path: string): Segment[] {\n const commands = path.match(/[A-Za-z][^A-Za-z]*/g) ?? [];\n const parsed: Segment[] = [];\n let pen: Point = { x: 0, y: 0 };\n\n for (const command of commands) {\n const type = command[0] ?? \"\";\n const from = pen;\n if (type === \"M\" || type === \"L\") {\n const numbers = (command.slice(1).match(/-?\\d*\\.?\\d+(?:e[-+]?\\d+)?/gi) ?? []).map(Number);\n pen = { x: numbers[0] ?? 0, y: numbers[1] ?? 0 };\n } else if (type !== \"Z\" && type !== \"z\") {\n const numbers = (command.slice(1).match(/-?\\d*\\.?\\d+(?:e[-+]?\\d+)?/gi) ?? []).map(Number);\n pen = { x: pen.x + (numbers.at(-2) ?? 0), y: pen.y + (numbers.at(-1) ?? 0) };\n }\n parsed.push({ type, raw: command.trim(), from, to: pen });\n }\n return parsed;\n}\n\n/** The straight part of one edge, as the interval it covers along that edge. */\ninterface Run {\n start: number;\n end: number;\n}\n\n/**\n * Where the corners let go of one edge, read off the body Lisse actually drew.\n *\n * Not derived from the radius, because `(1 + smoothing) * radius` is not what\n * Lisse spends on a corner: it caps the radius to half the shorter side, caps\n * the smoothing again to the corner's budget, and for a body as flat as a\n * tooltip it switches construction altogether. Each of those makes the\n * arithmetic wrong in a different direction — too long a corner sends a surface\n * to the union branch that had room to splice, too short a one lets the tail's\n * base reach into the corner, where the splice doubles back along the edge and\n * the stroke runs over the curve twice.\n *\n * The emitted path already knows the answer. Every edge carries exactly one\n * non-degenerate `L`, which is the run — the generator emits a zero-length `L`\n * after each corner as well, so length is part of the test rather than position\n * alone, and an edge whose corners meet has no run at all.\n */\nfunction edgeRun(body: Segment[], edge: AnchoredSide, width: number, height: number): Run | null {\n const along = edge === \"left\" || edge === \"right\" ? \"y\" : \"x\";\n for (const segment of body) {\n if (segment.type !== \"L\") continue;\n if (!onEdge(segment.from, edge, width, height)) continue;\n if (!onEdge(segment.to, edge, width, height)) continue;\n const start = Math.min(segment.from[along], segment.to[along]);\n const end = Math.max(segment.from[along], segment.to[along]);\n if (end - start > EPSILON) return { start, end };\n }\n return null;\n}\n\n/**\n * Move the generated body into place and, when asked, insert the tail.\n *\n * Absolute commands are rewritten; relative corners come through as they were.\n * The tail replaces the `L` that runs along the target edge, which is the same\n * segment `edgeRun` measured.\n */\nfunction translate(body: Segment[], offset: Point, splice?: Splice): string {\n const output: string[] = [];\n let inserted = false;\n\n for (const segment of body) {\n const { type, from, to } = segment;\n if (type === \"M\" || type === \"L\") {\n if (\n splice &&\n !inserted &&\n type === \"L\" &&\n onEdge(from, splice.edge, splice.width, splice.height) &&\n onEdge(to, splice.edge, splice.width, splice.height) &&\n (Math.abs(to.x - from.x) > EPSILON || Math.abs(to.y - from.y) > EPSILON)\n ) {\n output.push(splice.tail);\n inserted = true;\n }\n output.push(`${type} ${format({ x: to.x + offset.x, y: to.y + offset.y })}`);\n } else if (type === \"Z\" || type === \"z\") {\n output.push(\"Z\");\n } else {\n output.push(segment.raw);\n }\n }\n return output.join(\" \");\n}\n\nfunction onEdge(point: Point, edge: AnchoredSide, width: number, height: number): boolean {\n switch (edge) {\n case \"top\":\n return Math.abs(point.y) < EPSILON;\n case \"bottom\":\n return Math.abs(point.y - height) < EPSILON;\n case \"left\":\n return Math.abs(point.x) < EPSILON;\n case \"right\":\n return Math.abs(point.x - width) < EPSILON;\n }\n}\n\n/* ---------------------------------------------------------------------------\n * The component\n * ------------------------------------------------------------------------- */\n\nexport interface AnchoredSurfaceProps extends Omit<ComponentProps<\"div\">, \"children\"> {\n /** Which `--radius-*` token shapes the body's corners. */\n radius: RadiusToken;\n /**\n * Preferred side, used until Base UI reports the side it actually resolved\n * on `data-side`. Only the fallback: a flip updates the tail on its own.\n */\n side: AnchoredSide;\n /** Whether the surface points at its anchor. */\n arrow?: boolean;\n /** Figma's `xi`; defaults to `--cl-corner-smoothing`, as in `Squircle`. */\n smoothing?: number;\n /** Classes for the wrapper: placement, transform, and the entrance. */\n wrapperClassName?: string;\n /** Classes for the shape itself: fill, border, padding, text. */\n className?: string;\n /** Whether the fused tail follows a new trigger on an interruptible track. */\n tailMotion?: \"none\" | \"fast\";\n /**\n * Merge onto a single child rather than rendering a `div`. Needed for the\n * same reason as in `Squircle`: the clipped element is a Base UI part that\n * owns its own tag and behaviour.\n */\n asChild?: boolean;\n children?: ReactNode;\n}\n\ninterface Appearance {\n clip: string;\n outline: string[];\n origin: Point | null;\n width: number;\n height: number;\n side: AnchoredSide;\n /** Where the tail was drawn, clamped — `null` when there is no tail to draw. */\n tailCenter: number | null;\n border?: BorderConfig;\n}\n\n/**\n * The rendered parts a tail in motion writes into directly.\n *\n * The tail's slide is a new path every frame. Committing each one to state would\n * put React's reconciler inside the animation for no gain: the *structure* is\n * settled — one surface, one clip, one stroke per subpath — and only the\n * coordinates inside it move. So the frames set attributes on these, and state\n * is committed once the tail comes to rest. See `paint`.\n */\ninterface Nodes {\n /** The wrapper, which carries the entrance's origin. */\n root: HTMLDivElement | null;\n svg: SVGSVGElement | null;\n /** The clip that keeps the stroke inside the surface. */\n clip: SVGPathElement | null;\n /** One stroke per subpath, each with the mask that hides its shared join. */\n strokes: (SVGPathElement | null)[];\n masks: (SVGPathElement | null)[];\n}\n\n/** Big enough to cover any surface, so `evenodd` leaves everything outside a subpath. */\nconst COVER = \"M -9999 -9999 H 9999 V 9999 H -9999 Z\";\n\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nconst EMPTY: Appearance = {\n clip: \"\",\n outline: [],\n origin: null,\n width: 0,\n height: 0,\n side: \"top\",\n tailCenter: null,\n};\n\nexport function AnchoredSurface({\n radius,\n side,\n arrow = true,\n smoothing,\n wrapperClassName,\n className,\n asChild = false,\n children,\n ref: forwardedRef,\n style,\n tailMotion = \"none\",\n ...props\n}: AnchoredSurfaceProps) {\n const [element, setElement] = useState<HTMLDivElement | null>(null);\n const nodes = useRef<Nodes>({ root: null, svg: null, clip: null, strokes: [], masks: [] });\n const child = asChild ? getShapeChild(children) : undefined;\n const childProps = child?.props as ComponentProps<\"div\"> | undefined;\n const mergedRef = useMemo(\n () => composeRefs(setElement, forwardedRef, childProps?.ref),\n [forwardedRef, childProps?.ref],\n );\n const mergedStyle = { ...style, ...childProps?.style };\n const appearance = useAppearance(element, nodes, { radius, side, arrow, smoothing, tailMotion });\n // React's own ids are not valid in a fragment reference.\n const clipId = `cl-anchored-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n const shaped = appearance.clip.length > 0;\n\n const shapeProps = {\n ...props,\n ref: mergedRef,\n \"data-cl-anchored\": radius,\n style: {\n ...mergedStyle,\n // CSS intersects `border-radius` with `clip-path`, so the SSR fallback\n // has to go once the real shape lands.\n borderRadius: shaped ? undefined : (mergedStyle.borderRadius ?? `var(--radius-${radius})`),\n clipPath: shaped ? `path(\"${appearance.clip}\")` : undefined,\n } as CSSProperties,\n className: cn(\"cl-anchored\", arrow && \"cl-anchored-arrow\", className, childProps?.className),\n };\n const border = appearance.border;\n // A gradient border has no meaning for a 1px outline, and Lisse only reports\n // one when the author asked for it; a plain colour is the whole contract here.\n const stroke = shaped && border && typeof border.color === \"string\" ? border : undefined;\n\n return (\n /*\n * `.cl-anchored-root` is the entrance — the surface grows out of its tail,\n * which is the one point that does not move while it plays. Base UI's own\n * `--transform-origin` cannot say that: it is the anchor's edge, so it sits\n * in the gap *outside* the surface, and its cross-axis position is the\n * anchor's centre rather than the arrow's clamped one.\n *\n * The origin below is therefore the tail this component measured, and the\n * two have to agree: the entrance scales about the point the path draws, or\n * the surface would grow sideways out of a tail that is somewhere else.\n */\n <div\n ref={(node) => {\n nodes.current.root = node;\n }}\n className={cn(\"cl-anchored-root relative\", wrapperClassName)}\n style={\n appearance.origin\n ? { transformOrigin: `${appearance.origin.x}px ${appearance.origin.y}px` }\n : undefined\n }\n >\n {child ? cloneElement(child, shapeProps) : <div {...shapeProps}>{children}</div>}\n {/*\n The outline is a sibling, not a border: `clip-path` crops the element's\n own border paint, and it has to trace the tail as well. Drawn at twice\n the authored width and clipped back to the surface, so the stroke lands\n wholly inside it — the same inner-border construction Lisse uses.\n\n When the surface is a union, each subpath is stroked clipped to the\n outside of the other, so the boundary they share — the tail's base,\n buried under the body — is never painted.\n */}\n {stroke ? (\n <svg\n ref={(node) => {\n nodes.current.svg = node;\n }}\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute top-0 left-0\"\n width={appearance.width}\n height={appearance.height}\n >\n <defs>\n <clipPath id={clipId}>\n <path\n ref={(node) => {\n nodes.current.clip = node;\n }}\n d={appearance.clip}\n />\n </clipPath>\n {appearance.outline.length > 1\n ? appearance.outline.map((_, index) => (\n <clipPath\n // biome-ignore lint/suspicious/noArrayIndexKey: the two subpaths are positional\n key={index}\n id={`${clipId}-${index}`}\n >\n <path\n ref={(node) => {\n nodes.current.masks[index] = node;\n }}\n clipRule=\"evenodd\"\n d={`${COVER} ${appearance.outline[index === 0 ? 1 : 0]}`}\n />\n </clipPath>\n ))\n : null}\n </defs>\n <g clipPath={`url(#${clipId})`}>\n {appearance.outline.map((d, index) => (\n <path\n // biome-ignore lint/suspicious/noArrayIndexKey: the two subpaths are positional\n key={index}\n ref={(node) => {\n nodes.current.strokes[index] = node;\n }}\n d={d}\n fill=\"none\"\n stroke={stroke.color as string}\n strokeOpacity={stroke.opacity}\n strokeWidth={stroke.width * 2}\n clipPath={appearance.outline.length > 1 ? `url(#${clipId}-${index})` : undefined}\n />\n ))}\n </g>\n </svg>\n ) : null}\n </div>\n );\n}\n\nfunction getShapeChild(child: ReactNode) {\n if (!isValidElement<ComponentProps<\"div\">>(child) || child.type === Fragment) {\n throw new Error(\"AnchoredSurface: `asChild` expects exactly one non-Fragment React element.\");\n }\n return child;\n}\n\nconst BORDER_SIDES = [\n \"border-top-color\",\n \"border-right-color\",\n \"border-bottom-color\",\n \"border-left-color\",\n] as const;\n\ninterface Inputs {\n radius: RadiusToken;\n side: AnchoredSide;\n arrow: boolean;\n smoothing: number | undefined;\n tailMotion: \"none\" | \"fast\";\n}\n\n/** Insets Base UI writes the positioner's place with, per axis. */\nconst ALONG_X = [\"left\", \"right\"] as const;\nconst ALONG_Y = [\"top\", \"bottom\"] as const;\n\n/** What one pass of the reader found: everything the surface's shape depends on. */\ninterface Measured {\n geometry: SurfaceGeometry;\n border: BorderConfig | undefined;\n /** Time to close 95% of the tail's standing gap, and the surface's own morph. */\n lead: number;\n morph: number;\n}\n\n/**\n * Measure the surface and keep the fused path in sync.\n *\n * Every input is read from the shape's own computed style rather than from\n * `documentElement`, so a locally scoped theme, a `rem` radius or an inline\n * token override all work — the same reasoning as `Squircle`'s reader.\n *\n * There is no frame polling at rest. Four things move the tail, and each one has\n * a signal: the surface resizing (`ResizeObserver`), Base UI flipping the side\n * (`data-side`), Floating UI re-solving the arrow offset, which it writes as\n * inline `left`/`top` on the probe, and the positioner being sent to a new\n * anchor, which it writes as inline insets (`[data-cl-anchor-track]`). The last\n * is what a shared tooltip changing trigger looks like, and the only one that\n * starts the motion track.\n */\nfunction useAppearance(\n element: HTMLDivElement | null,\n nodes: { current: Nodes },\n inputs: Inputs,\n): Appearance {\n const [appearance, setAppearance] = useState<Appearance>(EMPTY);\n const latest = useRef(inputs);\n latest.current = inputs;\n /** What React last rendered, which is the DOM structure a frame may write into. */\n const renderedRef = useRef(EMPTY);\n const syncRef = useRef<(() => void) | null>(null);\n\n useIsomorphicLayoutEffect(() => {\n if (!element) return;\n const view = element.ownerDocument.defaultView;\n if (!view) return;\n const saved = new Map<string, { value: string; priority: string }>();\n let frame: number | null = null;\n let measured: Measured | null = null;\n /** Whether the surface has changed since it was last read. */\n let stale = true;\n /** What the DOM is showing, which a frame of the motion track may have written. */\n let showing = EMPTY;\n /** The tail centre the track is holding, or `null` when the tail is at rest. */\n let leading: number | null = null;\n /** How far along its edge the tail could reach, as of the last surface built. */\n let reach: { min: number; max: number } | null = null;\n let clock = 0;\n let deadline = 0;\n\n const restore = () => {\n for (const [property, source] of saved) {\n if (element.style.getPropertyValue(property) === \"transparent\") {\n element.style.setProperty(property, source.value, source.priority);\n }\n }\n saved.clear();\n };\n\n const same = (a: Appearance, b: Appearance) =>\n a.clip === b.clip &&\n a.width === b.width &&\n a.height === b.height &&\n a.side === b.side &&\n a.tailCenter === b.tailCenter &&\n a.origin?.x === b.origin?.x &&\n a.origin?.y === b.origin?.y &&\n a.outline.length === b.outline.length &&\n a.outline.every((subpath, index) => subpath === b.outline[index]) &&\n a.border?.color === b.border?.color &&\n a.border?.opacity === b.border?.opacity &&\n a.border?.width === b.border?.width;\n\n const commit = (next: Appearance) => {\n renderedRef.current = next;\n showing = next;\n setAppearance((current) => (same(current, next) ? current : next));\n };\n\n /** Whether an appearance renders the outline, and with how many subpaths. */\n const structure = (value: Appearance) =>\n `${value.outline.length}:${value.clip.length > 0 && typeof value.border?.color === \"string\"}`;\n\n /**\n * Write one frame of the tail's slide straight into the DOM.\n *\n * Only the coordinates move while the tail travels, so the frames set\n * attributes on what React already rendered and leave the reconciler out of\n * the animation; the last frame commits. A frame that *would* change the\n * structure — the surface becoming a union, or its outline appearing — goes\n * through state instead, and the frame after it paints again.\n */\n const paint = (next: Appearance) => {\n if (structure(next) !== structure(renderedRef.current)) {\n commit(next);\n return;\n }\n showing = next;\n const { root, svg, clip, strokes, masks } = nodes.current;\n element.style.clipPath = `path(\"${next.clip}\")`;\n if (root && next.origin) {\n root.style.transformOrigin = `${next.origin.x}px ${next.origin.y}px`;\n }\n svg?.setAttribute(\"width\", String(next.width));\n svg?.setAttribute(\"height\", String(next.height));\n clip?.setAttribute(\"d\", next.clip);\n for (const [index, subpath] of next.outline.entries()) {\n strokes[index]?.setAttribute(\"d\", subpath);\n const other = next.outline[index === 0 ? 1 : 0];\n if (other !== undefined) masks[index]?.setAttribute(\"d\", `${COVER} ${other}`);\n }\n };\n\n const measure = (): Measured => {\n const { radius: token, side, arrow, smoothing } = latest.current;\n // Sample the authored border, not the transparent mask we leave behind.\n restore();\n const computed = view.getComputedStyle(element);\n const { width, height } = getLayoutSize(element, computed);\n const border = parseBorder(element, computed);\n const number = (name: string) => Number.parseFloat(computed.getPropertyValue(name));\n const duration = (name: string) => tokenNumber(computed.getPropertyValue(name));\n\n // Let the browser resolve the radius token's units and inheritance.\n const authored = element.style.borderTopLeftRadius;\n element.style.borderTopLeftRadius = `var(--radius-${token})`;\n const radius = Number.parseFloat(computed.borderTopLeftRadius);\n element.style.borderTopLeftRadius = authored;\n\n const resolvedSide = readSide(element) ?? side;\n const tokenSmoothing = number(\"--cl-corner-smoothing\");\n const extent = number(\"--cl-arrow-extent\");\n const arrowWidth = number(\"--cl-arrow-width\");\n const drawArrow = arrow && Number.isFinite(extent) && Number.isFinite(arrowWidth);\n const geometry: SurfaceGeometry = {\n width,\n height,\n side: resolvedSide,\n radius: Number.isFinite(radius) ? Math.max(0, radius) : 0,\n smoothing: clamp(smoothing ?? (Number.isFinite(tokenSmoothing) ? tokenSmoothing : 0), 0, 1),\n arrow: drawArrow,\n extent: drawArrow ? extent : 0,\n arrowWidth: drawArrow ? arrowWidth : 0,\n center: drawArrow ? tailCenter(element, resolvedSide, width, height) : 0,\n };\n\n if (border) {\n for (const property of BORDER_SIDES) {\n saved.set(property, {\n value: element.style.getPropertyValue(property),\n priority: element.style.getPropertyPriority(property),\n });\n // Keep the border's width, which is what holds the content inset.\n element.style.setProperty(property, \"transparent\", \"important\");\n }\n }\n stale = false;\n return {\n geometry,\n border,\n lead: duration(\"--cl-duration-tooltip-tail\") ?? 60,\n morph: duration(\"--cl-duration-tooltip-morph\") ?? 180,\n };\n };\n\n const build = (source: Measured, center = source.geometry.center): Appearance => {\n const geometry = { ...source.geometry, center };\n const surface = geometry.width > 0 && geometry.height > 0 ? surfacePath(geometry) : undefined;\n if (surface) reach = surface.range;\n return {\n width: geometry.width,\n height: geometry.height,\n side: geometry.side,\n // The centre the surface resolved to, never the one it was asked for: the\n // tail follows what is drawn, and the ask is often out of reach.\n tailCenter: geometry.arrow ? (surface?.center ?? null) : null,\n border: source.border,\n clip: surface?.clip ?? \"\",\n outline: surface?.outline ?? [],\n origin: surface?.origin ?? null,\n };\n };\n\n /**\n * How far the surface still has to travel along the tail's edge, in px.\n *\n * The morph is a CSS transition on the positioner's insets, so the browser is\n * already holding both numbers this needs: the inline style is the inset Base\n * UI asked for — the end of the journey, which a transition does not touch —\n * and the computed style is how far along it is. Their difference is the\n * travel left, read off the browser rather than predicted from a duration and\n * a curve, which is what lets the tail survive a morph that is interrupted,\n * re-aimed, or overtaken by the anchor moving.\n */\n const remaining = (horizontal: boolean): number => {\n const track = element.closest<HTMLElement>(\"[data-cl-anchor-track]\");\n if (!track) return 0;\n const computed = view.getComputedStyle(track);\n for (const property of horizontal ? ALONG_X : ALONG_Y) {\n const end = tokenNumber(track.style.getPropertyValue(property));\n if (end === undefined) continue;\n const now = tokenNumber(computed.getPropertyValue(property));\n if (now === undefined) continue;\n // `right` and `bottom` grow the other way down the axis.\n return property === \"left\" || property === \"top\" ? end - now : now - end;\n }\n return 0;\n };\n\n const stop = () => {\n if (frame !== null) {\n view.cancelAnimationFrame(frame);\n frame = null;\n }\n leading = null;\n };\n\n /**\n * The tail leads the surface to its new anchor, then waits for it there.\n *\n * Each frame asks for the centre that would keep the tail pointing at the\n * anchor from where the surface currently is: its resting centre, plus the\n * travel the surface has left. Early on that is far beyond the edge, so the\n * ask is brought back to the end of the straight run *before* the follower\n * sees it — the tail then slides to that limit over the time constant and\n * rides there. Aiming the follower at the raw ask instead would land it on the\n * limit in one step, because a first step of 60% of 180px is not a slide but a\n * jump. Once the surface has closed enough of the distance for the ask to come\n * back inside the run, the tail is already where it belongs and holds still on\n * screen while the surface finishes arriving.\n *\n * The follow is a time constant rather than a duration, because there is no\n * fixed distance to cover: the aim moves with the surface, and a new trigger\n * can change it mid-flight. `--cl-duration-tooltip-tail` is the time to close\n * 95% of a standing gap, which is three of those constants.\n *\n * Both halves of this movement are worth reading together when judging it: the\n * surface travels on a CSS transition and the tail on this timer, so a\n * DevTools playback rate slows one and not the other. Scale\n * `--cl-duration-tooltip-morph` and `--cl-duration-tooltip-tail` by the same\n * factor instead.\n */\n const step = (now: number) => {\n frame = null;\n if (leading === null || latest.current.tailMotion !== \"fast\") return;\n if (stale || measured === null) measured = measure();\n const { geometry, lead } = measured;\n const horizontal = geometry.side === \"top\" || geometry.side === \"bottom\";\n const travel = remaining(horizontal);\n const landed = Math.abs(travel) < 0.5;\n const ask = geometry.center + travel;\n const aim = reach ? clamp(ask, reach.min, reach.max) : ask;\n // Frame-rate independent, and a stalled tab cannot make it overshoot.\n const elapsed = clamp(now - clock, 0, 64);\n clock = now;\n const closed = 1 - Math.exp((-3 * elapsed) / Math.max(1, lead));\n const from = leading;\n const next = build(measured, from + (aim - from) * closed);\n paint(next);\n leading = next.tailCenter ?? from;\n\n // Stop on the distance left, not on the size of the last step: a step\n // small enough to stop on can still leave a tenth of a pixel to the exact\n // centre, and committing it lands as a visible nudge after the movement.\n if ((landed && Math.abs(aim - leading) < 0.1) || now > deadline) {\n stop();\n commit(build(measured));\n return;\n }\n frame = view.requestAnimationFrame(step);\n };\n\n const start = (from: number, morph: number) => {\n // A transition that never settles — an anchor moving under a scroll, say —\n // must not be able to keep the track alive for good.\n deadline = view.performance.now() + 2 * morph + 400;\n if (frame !== null) return;\n leading = from;\n clock = view.performance.now();\n frame = view.requestAnimationFrame(step);\n };\n\n /**\n * Re-read the surface and put it on screen.\n *\n * `retarget` says the signal was the tail being sent somewhere new, which is\n * the one that may start the motion track. While the track runs it owns the\n * tail, so a pass of any kind only re-measures and repaints around it.\n */\n const sync = (retarget: boolean) => {\n measured = measure();\n const { geometry, morph } = measured;\n const flipped = showing.side !== geometry.side;\n const reduced = view.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches ?? false;\n\n // A flip moves the tail onto another edge, where there is nothing to slide\n // along; same for a tail that was not on screen to lead from.\n if (flipped) stop();\n if (\n retarget &&\n !flipped &&\n !reduced &&\n latest.current.tailMotion === \"fast\" &&\n geometry.arrow &&\n showing.tailCenter !== null\n ) {\n start(showing.tailCenter, morph);\n }\n\n if (frame !== null && leading !== null) {\n paint(build(measured, leading));\n } else {\n commit(build(measured));\n }\n observer.takeRecords();\n };\n\n const observer = new view.MutationObserver(() => sync(true));\n const resize = new view.ResizeObserver(() => {\n stale = true;\n // The track re-reads on its own next frame; a pass here would do it twice.\n if (frame === null) sync(false);\n });\n const cleanup = () => {\n stop();\n observer.disconnect();\n resize.disconnect();\n syncRef.current = null;\n restore();\n };\n try {\n syncRef.current = () => sync(false);\n observer.observe(element, { attributes: true, attributeFilter: [\"data-side\"] });\n const probe = element.querySelector(\"[data-cl-anchor-probe]\");\n if (probe) observer.observe(probe, { attributes: true, attributeFilter: [\"style\"] });\n const track = element.closest(\"[data-cl-anchor-track]\");\n if (track) observer.observe(track, { attributes: true, attributeFilter: [\"style\"] });\n resize.observe(element);\n sync(false);\n return cleanup;\n } catch (error) {\n cleanup();\n throw error;\n }\n }, [element, nodes, inputs.arrow, inputs.tailMotion]);\n\n useIsomorphicLayoutEffect(() => {\n syncRef.current?.();\n });\n return appearance;\n}\n\n/**\n * The reader itself lives in `utils`, shared with the surface morph; it is\n * re-exported here so this module keeps the surface it had.\n */\nexport { tokenNumber };\n\n/**\n * The leading number of a length or duration token, read from an element's own\n * cascade — a local theme, a scoped override or a `rem` value all resolve.\n */\nexport function themedNumber(\n element: Element | null | undefined,\n name: string,\n): number | undefined {\n const view = element?.ownerDocument.defaultView;\n if (!element || !view) return undefined;\n return tokenNumber(view.getComputedStyle(element).getPropertyValue(name));\n}\n\n/**\n * `themedNumber` for the props Base UI cannot take as a callback.\n *\n * `sideOffset` accepts a function and is resolved during positioning, so it can\n * read the token inline; `collisionPadding` and the tooltip delays are plain\n * numbers, and reading them needs a mounted element. Running in a layout effect\n * keeps the correction ahead of paint, so a first pass on Base UI's own default\n * is never visible.\n */\nexport function useThemedNumber(\n ref: { current: Element | null },\n name: string,\n): number | undefined {\n const [value, setValue] = useState<number>();\n useIsomorphicLayoutEffect(() => {\n const next = themedNumber(ref.current, name);\n setValue((current) => (current === next ? current : next));\n });\n return value;\n}\n\nfunction readSide(element: HTMLElement): AnchoredSide | undefined {\n const side = element.dataset.side;\n return side === \"top\" || side === \"bottom\" || side === \"left\" || side === \"right\"\n ? side\n : undefined;\n}\n\n/**\n * Where the tail meets the body, from the surface's own origin.\n *\n * Read off Base UI's arrow element, which Floating UI has already solved and\n * clamped against the anchor.\n *\n * Offset geometry rather than `getBoundingClientRect`, because this has to\n * survive the entrance. The wrapper scales from zero, so on the first frame\n * every rect inside it collapses to a point, and nothing can be recovered from\n * the ratio — a couple of rects at 0 measure a tail at the surface's left edge.\n * Offsets are layout space: a transform does not move them, and the measurement\n * is therefore right the first time instead of being right later, which is the\n * difference between a tail that follows the anchor and one that waits for the\n * next resize.\n *\n * An absolutely positioned box is placed from its containing block's *padding*\n * box, so a probe whose offset parent is the surface itself is short by that\n * surface's own border — and the path is drawn in border-box space. The surface\n * is the probe's containing block whenever it carries the frost, whose\n * `backdrop-filter` establishes one; the wrapper is the alternative, and it has\n * no insets of its own to correct for.\n */\nfunction tailCenter(\n element: HTMLElement,\n side: AnchoredSide,\n width: number,\n height: number,\n): number {\n const probe = element.querySelector<HTMLElement>(\"[data-cl-anchor-probe]\");\n if (!probe) return (side === \"left\" || side === \"right\" ? height : width) / 2;\n const horizontal = side === \"top\" || side === \"bottom\";\n const border =\n probe.offsetParent === element ? (horizontal ? element.clientLeft : element.clientTop) : 0;\n return horizontal\n ? probe.offsetLeft + border + probe.offsetWidth / 2\n : probe.offsetTop + border + probe.offsetHeight / 2;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { tokenNumber } from "./utils.js";
|
|
3
|
+
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/lib/morph.tsx
|
|
6
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
7
|
+
/** The identity, in `matrix3d()` argument order. */
|
|
8
|
+
const IDENTITY = [
|
|
9
|
+
1,
|
|
10
|
+
0,
|
|
11
|
+
0,
|
|
12
|
+
0,
|
|
13
|
+
0,
|
|
14
|
+
1,
|
|
15
|
+
0,
|
|
16
|
+
0,
|
|
17
|
+
0,
|
|
18
|
+
0,
|
|
19
|
+
1,
|
|
20
|
+
0,
|
|
21
|
+
0,
|
|
22
|
+
0,
|
|
23
|
+
0,
|
|
24
|
+
1
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* How far into a leg the content is fully revealed, and how it gets there.
|
|
28
|
+
*
|
|
29
|
+
* The Flutter dialog's `easeOut(t / 0.35)`, kept with the corner curves because
|
|
30
|
+
* it is the same construction: a progress the whole layer shares. The select
|
|
31
|
+
* fades its list on a curve of its own (`pow(t, 0.6)`, on a controller with a
|
|
32
|
+
* duration of its own); this is the one the two surfaces agree on.
|
|
33
|
+
*
|
|
34
|
+
* `Curves.easeOut` in Flutter is `Cubic(0, 0, 0.58, 1)`, which is not the
|
|
35
|
+
* `--ease-cl-out` the design system uses elsewhere — the morph's legs are its own
|
|
36
|
+
* numbers, and only the durations come from tokens.
|
|
37
|
+
*/
|
|
38
|
+
const REVEAL = .35;
|
|
39
|
+
const REVEAL_CURVE = {
|
|
40
|
+
x1: 0,
|
|
41
|
+
y1: 0,
|
|
42
|
+
x2: .58,
|
|
43
|
+
y2: 1
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* How far ahead of the geometry the content is gone on a dismissal.
|
|
47
|
+
*
|
|
48
|
+
* Base UI unmounts the layer when the exit sentinel's animation ends, and that end
|
|
49
|
+
* lands in the same rendering update as this file's last frame — earlier in it, in
|
|
50
|
+
* fact, because animation events are dispatched before animation frame callbacks.
|
|
51
|
+
* Whatever that final frame writes is therefore never on screen, and a dismissal's
|
|
52
|
+
* last tenth is its fastest-moving part: the corners hold near the target and then
|
|
53
|
+
* collapse, so the layer would be removed while still plainly visible — measured
|
|
54
|
+
* at 33% opacity, mid-collapse. Starting the fade a frame and a half early leaves
|
|
55
|
+
* the last thing on screen already empty, and puts the disappearance where nothing
|
|
56
|
+
* is moving.
|
|
57
|
+
*
|
|
58
|
+
* Only a dismissal: an entrance has nothing to race, and keeps the Flutter fade
|
|
59
|
+
* exactly as it is.
|
|
60
|
+
*/
|
|
61
|
+
const REVEAL_LEAD = .15;
|
|
62
|
+
/** A cubic Bézier on one axis, with `P0 = 0` and `P3 = 1`. */
|
|
63
|
+
const axis = (a, b, u) => {
|
|
64
|
+
const m = 1 - u;
|
|
65
|
+
return 3 * m * m * u * a + 3 * m * u * u * b + u * u * u;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* `cubic-bezier(x1, y1, x2, y2)` evaluated at `x`, the way CSS evaluates it.
|
|
69
|
+
*
|
|
70
|
+
* The curve is parametric: `x` is not the parameter, so `x` has to be inverted
|
|
71
|
+
* before `y` can be read. Newton converges in a few steps for the control points
|
|
72
|
+
* this file uses, where `x(u)` is monotonic; the bracket keeps a caller's own
|
|
73
|
+
* parameters from stepping off the curve when a slope goes flat.
|
|
74
|
+
*/
|
|
75
|
+
function bezier(x1, y1, x2, y2, x) {
|
|
76
|
+
if (x <= 0) return 0;
|
|
77
|
+
if (x >= 1) return 1;
|
|
78
|
+
let low = 0;
|
|
79
|
+
let high = 1;
|
|
80
|
+
let u = x;
|
|
81
|
+
for (let step = 0; step < 8; step++) {
|
|
82
|
+
const error = axis(x1, x2, u) - x;
|
|
83
|
+
if (Math.abs(error) < 1e-6) break;
|
|
84
|
+
if (error > 0) high = u;
|
|
85
|
+
else low = u;
|
|
86
|
+
const slope = 3 * (1 - u) ** 2 * x1 + 6 * (1 - u) * u * (x2 - x1) + 3 * u ** 2 * (1 - x2);
|
|
87
|
+
if (slope === 0) break;
|
|
88
|
+
u = Math.min(high, Math.max(low, u - error / slope));
|
|
89
|
+
}
|
|
90
|
+
return axis(y1, y2, u);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Where one corner is at `progress`, along the straight line from the source
|
|
94
|
+
* rectangle to the target one.
|
|
95
|
+
*
|
|
96
|
+
* The corners do not share a curve, and that is the effect: a corner with a long
|
|
97
|
+
* way to travel leaves early and eases into place, one that barely moves waits
|
|
98
|
+
* and snaps in behind it. Four straight lines on four clocks is what turns a
|
|
99
|
+
* rectangle into a trapezoid on the way across.
|
|
100
|
+
*/
|
|
101
|
+
function corner(ratio, progress) {
|
|
102
|
+
const r = Math.min(1, Math.max(0, ratio));
|
|
103
|
+
return bezier(.35 - .2 * r, .85 * r, .45 - .2 * r, 1, progress);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The quad a layer covers at `progress`, in its own resting coordinates.
|
|
107
|
+
*
|
|
108
|
+
* `source` is the rectangle that opened it, relative to the same box. `null`
|
|
109
|
+
* means there is nothing to grow out of, and the layer starts as a small centred
|
|
110
|
+
* rect instead.
|
|
111
|
+
*/
|
|
112
|
+
function morphQuad(box, source, progress) {
|
|
113
|
+
const target = [
|
|
114
|
+
{
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
x: box.width,
|
|
120
|
+
y: 0
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
x: 0,
|
|
124
|
+
y: box.height
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
x: box.width,
|
|
128
|
+
y: box.height
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
const inset = .7 / 2;
|
|
132
|
+
const from = source ? [
|
|
133
|
+
{
|
|
134
|
+
x: source.x,
|
|
135
|
+
y: source.y
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
x: source.x + source.width,
|
|
139
|
+
y: source.y
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
x: source.x,
|
|
143
|
+
y: source.y + source.height
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
x: source.x + source.width,
|
|
147
|
+
y: source.y + source.height
|
|
148
|
+
}
|
|
149
|
+
] : [
|
|
150
|
+
{
|
|
151
|
+
x: box.width * inset,
|
|
152
|
+
y: box.height * inset
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
x: box.width * .65,
|
|
156
|
+
y: box.height * inset
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
x: box.width * inset,
|
|
160
|
+
y: box.height * .65
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
x: box.width * .65,
|
|
164
|
+
y: box.height * .65
|
|
165
|
+
}
|
|
166
|
+
];
|
|
167
|
+
const clamped = Math.min(1, Math.max(0, progress));
|
|
168
|
+
const journey = (index) => Math.hypot(target[index].x - from[index].x, target[index].y - from[index].y);
|
|
169
|
+
const longest = Math.max(journey(0), journey(1), journey(2), journey(3), 1);
|
|
170
|
+
const landed = (index) => {
|
|
171
|
+
const arrived = corner(journey(index) / longest, clamped);
|
|
172
|
+
return {
|
|
173
|
+
x: from[index].x + (target[index].x - from[index].x) * arrived,
|
|
174
|
+
y: from[index].y + (target[index].y - from[index].y) * arrived
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
return [
|
|
178
|
+
landed(0),
|
|
179
|
+
landed(1),
|
|
180
|
+
landed(2),
|
|
181
|
+
landed(3)
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The projective transform that carries a `box` onto `quad`, in `matrix3d()`
|
|
186
|
+
* argument order.
|
|
187
|
+
*
|
|
188
|
+
* This is the Flutter dialog's `_computeHomography`, and the transposition is
|
|
189
|
+
* the one thing that can not be copied literally. Flutter writes its matrix for
|
|
190
|
+
* column vectors — `v' = M·v`, so its translation sits in the last *column* —
|
|
191
|
+
* while CSS transforms a row vector, `(x, y, 0, 1)·M`, and takes its arguments
|
|
192
|
+
* column by column. The two arrays are transposes of each other: the translation
|
|
193
|
+
* ends up at 12..14 in both, which is exactly why the mistake survives a visual
|
|
194
|
+
* check, but the perspective terms are Flutter's `storage[3]` and `storage[7]`
|
|
195
|
+
* and CSS's `args[3]` and `args[7]` — the pair that makes the quad a trapezoid
|
|
196
|
+
* instead of a parallelogram.
|
|
197
|
+
*
|
|
198
|
+
* A quad that has collapsed to a line or a point has no inverse; the perspective
|
|
199
|
+
* terms stay zero and the corners are placed affinely rather than the layer
|
|
200
|
+
* disappearing.
|
|
201
|
+
*/
|
|
202
|
+
function homographyMatrix(box, quad) {
|
|
203
|
+
const [p1, p2, p3, p4] = quad;
|
|
204
|
+
const { width, height } = box;
|
|
205
|
+
if (width <= 0 || height <= 0) return [...IDENTITY];
|
|
206
|
+
const a = (p2.x - p4.x) * width;
|
|
207
|
+
const b = (p3.x - p4.x) * height;
|
|
208
|
+
const d = (p2.y - p4.y) * width;
|
|
209
|
+
const e = (p3.y - p4.y) * height;
|
|
210
|
+
const c = p4.x - p2.x - p3.x + p1.x;
|
|
211
|
+
const f = p4.y - p2.y - p3.y + p1.y;
|
|
212
|
+
const determinant = a * e - b * d;
|
|
213
|
+
let m30 = 0;
|
|
214
|
+
let m31 = 0;
|
|
215
|
+
if (Math.abs(determinant) > 1e-6) {
|
|
216
|
+
m30 = (c * e - b * f) / determinant;
|
|
217
|
+
m31 = (a * f - c * d) / determinant;
|
|
218
|
+
}
|
|
219
|
+
const m00 = (p2.x - p1.x + m30 * width * p2.x) / width;
|
|
220
|
+
const m10 = (p2.y - p1.y + m30 * width * p2.y) / width;
|
|
221
|
+
const m01 = (p3.x - p1.x + m31 * height * p3.x) / height;
|
|
222
|
+
const m11 = (p3.y - p1.y + m31 * height * p3.y) / height;
|
|
223
|
+
return [
|
|
224
|
+
flat(m00),
|
|
225
|
+
flat(m10),
|
|
226
|
+
0,
|
|
227
|
+
flat(m30),
|
|
228
|
+
flat(m01),
|
|
229
|
+
flat(m11),
|
|
230
|
+
0,
|
|
231
|
+
flat(m31),
|
|
232
|
+
0,
|
|
233
|
+
0,
|
|
234
|
+
1,
|
|
235
|
+
0,
|
|
236
|
+
p1.x,
|
|
237
|
+
p1.y,
|
|
238
|
+
0,
|
|
239
|
+
1
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
/** The transform a layer at `progress` should carry, ready to serialise. */
|
|
243
|
+
function morphMatrix(box, source, progress) {
|
|
244
|
+
return homographyMatrix(box, morphQuad(box, source, progress));
|
|
245
|
+
}
|
|
246
|
+
const MorphSourceContext = createContext(null);
|
|
247
|
+
/**
|
|
248
|
+
* Publishes the trigger to the layer it opens, and the open state with it.
|
|
249
|
+
*
|
|
250
|
+
* A ref rather than state: the anchor is already in the DOM by the time a layer
|
|
251
|
+
* needs it, and a render in between would only delay the first measured frame.
|
|
252
|
+
* The value is memoised on `open` alone, so a parent re-render does not detach
|
|
253
|
+
* the ref the trigger registered.
|
|
254
|
+
*/
|
|
255
|
+
function MorphSourceProvider({ open, children }) {
|
|
256
|
+
const anchor = useRef(null);
|
|
257
|
+
const source = useMemo(() => ({
|
|
258
|
+
anchor,
|
|
259
|
+
open
|
|
260
|
+
}), [open]);
|
|
261
|
+
return /* @__PURE__ */ jsx(MorphSourceContext.Provider, {
|
|
262
|
+
value: source,
|
|
263
|
+
children
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function useMorphSource() {
|
|
267
|
+
return useContext(MorphSourceContext);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Registers the element its layer should grow out of.
|
|
271
|
+
*
|
|
272
|
+
* Returns `null` outside a provider, so a component used without one degrades
|
|
273
|
+
* to the plain CSS entrance instead of failing.
|
|
274
|
+
*/
|
|
275
|
+
function useMorphAnchor() {
|
|
276
|
+
const anchor = useContext(MorphSourceContext)?.anchor ?? null;
|
|
277
|
+
return useMemo(() => {
|
|
278
|
+
if (!anchor) return null;
|
|
279
|
+
return (element) => {
|
|
280
|
+
anchor.current = element;
|
|
281
|
+
return () => {
|
|
282
|
+
if (anchor.current === element) anchor.current = null;
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
}, [anchor]);
|
|
286
|
+
}
|
|
287
|
+
/** Six decimals is past the point a transform is distinguishable and short enough to write. */
|
|
288
|
+
const round = (value) => Number(value.toFixed(6));
|
|
289
|
+
/** `-0` is not a different transform, and it is noise in a serialised matrix. */
|
|
290
|
+
const flat = (value) => value === 0 ? 0 : value;
|
|
291
|
+
const rectOf = (rect) => ({
|
|
292
|
+
x: rect.x,
|
|
293
|
+
y: rect.y,
|
|
294
|
+
width: rect.width,
|
|
295
|
+
height: rect.height
|
|
296
|
+
});
|
|
297
|
+
/** The element a layer should grow out of, or `null` when it is no longer there. */
|
|
298
|
+
function readSource(anchor) {
|
|
299
|
+
const element = anchor?.current;
|
|
300
|
+
return element?.isConnected ? rectOf(element.getBoundingClientRect()) : null;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The exit sentinel, or `null` when the layer has none.
|
|
304
|
+
*
|
|
305
|
+
* The class is part of `base.css`'s own contract — the non-visual animation Base
|
|
306
|
+
* UI can await while the wrapper moves — so it is looked up rather than assumed,
|
|
307
|
+
* and a surface without one keeps the duration token as its clock.
|
|
308
|
+
*/
|
|
309
|
+
function exitSentinel(element) {
|
|
310
|
+
return element.querySelector(".cl-exit-sentinel");
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The layer's own box and its source, both at rest.
|
|
314
|
+
*
|
|
315
|
+
* The transform is lifted for the read and put straight back: a homography
|
|
316
|
+
* cannot be subtracted out of a rect, and the resting box is the only honest
|
|
317
|
+
* measurement of where the layer belongs. Nothing else runs in between, so the
|
|
318
|
+
* browser paints the frame with the transform in place.
|
|
319
|
+
*/
|
|
320
|
+
function measure(state) {
|
|
321
|
+
const element = state.element;
|
|
322
|
+
if (!element) return null;
|
|
323
|
+
const authored = element.style.transform;
|
|
324
|
+
element.style.transform = "none";
|
|
325
|
+
const box = element.getBoundingClientRect();
|
|
326
|
+
element.style.transform = authored;
|
|
327
|
+
return {
|
|
328
|
+
box: rectOf(box),
|
|
329
|
+
source: readSource(state.anchor)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Drops the inline geometry, leaving the CSS state to describe the same frame.
|
|
334
|
+
*
|
|
335
|
+
* Opacity goes with it: at rest the CSS value is the same one — the morph's
|
|
336
|
+
* neutralisation sets it to 1 — so nothing is left behind that a consumer's own
|
|
337
|
+
* styles could not override.
|
|
338
|
+
*/
|
|
339
|
+
function release(element) {
|
|
340
|
+
element?.style.removeProperty("transform");
|
|
341
|
+
element?.style.removeProperty("transform-origin");
|
|
342
|
+
element?.style.removeProperty("opacity");
|
|
343
|
+
}
|
|
344
|
+
function paint(state) {
|
|
345
|
+
const element = state.element;
|
|
346
|
+
const geometry = state.geometry;
|
|
347
|
+
if (!element || !geometry) return;
|
|
348
|
+
const { box, source } = geometry;
|
|
349
|
+
const matrix = morphMatrix(box, source ? {
|
|
350
|
+
x: source.x - box.x,
|
|
351
|
+
y: source.y - box.y,
|
|
352
|
+
width: source.width,
|
|
353
|
+
height: source.height
|
|
354
|
+
} : null, state.progress).map(round);
|
|
355
|
+
const leading = state.clock ? REVEAL_LEAD : 0;
|
|
356
|
+
const revealed = Math.min(1, Math.max(0, (state.progress - leading) / REVEAL));
|
|
357
|
+
element.style.transform = `matrix3d(${matrix.join(",")})`;
|
|
358
|
+
element.style.opacity = String(round(bezier(REVEAL_CURVE.x1, REVEAL_CURVE.y1, REVEAL_CURVE.x2, REVEAL_CURVE.y2, revealed)));
|
|
359
|
+
}
|
|
360
|
+
function stop(state) {
|
|
361
|
+
if (state.frame && state.view) state.view.cancelAnimationFrame(state.frame);
|
|
362
|
+
state.frame = 0;
|
|
363
|
+
state.clock = null;
|
|
364
|
+
state.sentinel = null;
|
|
365
|
+
}
|
|
366
|
+
function step(state) {
|
|
367
|
+
state.frame = 0;
|
|
368
|
+
const view = state.view;
|
|
369
|
+
if (!view) return;
|
|
370
|
+
state.clock ??= state.sentinel?.getAnimations()[0] ?? null;
|
|
371
|
+
const elapsed = state.clock ? state.clock.effect?.getComputedTiming().progress ?? 0 : (view.performance.now() - state.started) / state.duration;
|
|
372
|
+
const ratio = Math.min(1, Math.max(0, elapsed));
|
|
373
|
+
state.progress = state.from + (state.target - state.from) * ratio;
|
|
374
|
+
paint(state);
|
|
375
|
+
if (ratio < 1) {
|
|
376
|
+
state.frame = view.requestAnimationFrame(() => step(state));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
state.progress = state.target;
|
|
380
|
+
state.clock = null;
|
|
381
|
+
state.sentinel = null;
|
|
382
|
+
if (state.target === 1) release(state.element);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Begin a leg towards `target`, from wherever the layer actually is.
|
|
386
|
+
*
|
|
387
|
+
* The duration is the leg's own token scaled by the distance left, so a layer
|
|
388
|
+
* caught mid-flight reverses quickly instead of replaying a full entrance
|
|
389
|
+
* backwards. A dismissal mostly does not use it at all: see `clock` below.
|
|
390
|
+
*/
|
|
391
|
+
function start(state, target) {
|
|
392
|
+
const { element, view } = state;
|
|
393
|
+
if (!element || !view) return;
|
|
394
|
+
if (state.frame) {
|
|
395
|
+
if (state.geometry) state.geometry = {
|
|
396
|
+
...state.geometry,
|
|
397
|
+
source: readSource(state.anchor)
|
|
398
|
+
};
|
|
399
|
+
} else {
|
|
400
|
+
const computed = view.getComputedStyle(element);
|
|
401
|
+
state.enter = tokenNumber(computed.getPropertyValue("--cl-duration-morph")) ?? state.enter;
|
|
402
|
+
state.exit = tokenNumber(computed.getPropertyValue("--cl-duration-surface")) ?? state.exit;
|
|
403
|
+
state.geometry = measure(state);
|
|
404
|
+
}
|
|
405
|
+
if (!state.geometry) return;
|
|
406
|
+
state.from = state.progress;
|
|
407
|
+
state.target = target;
|
|
408
|
+
state.duration = Math.max(1, (target === 1 ? state.enter : state.exit) * Math.abs(target - state.progress));
|
|
409
|
+
state.started = view.performance.now();
|
|
410
|
+
state.sentinel = target === 0 ? exitSentinel(element) : null;
|
|
411
|
+
state.clock = null;
|
|
412
|
+
element.style.transformOrigin = "0 0";
|
|
413
|
+
paint(state);
|
|
414
|
+
state.frame = view.requestAnimationFrame(() => step(state));
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Drive a layer's morph.
|
|
418
|
+
*
|
|
419
|
+
* The effect runs on mount and whenever `open` changes, and either starts a leg
|
|
420
|
+
* or retargets the one in flight. Everything else — measurement, painting,
|
|
421
|
+
* settling — happens outside React; a layer that re-rendered per frame would put
|
|
422
|
+
* the reconciler inside the animation for no gain, which is the same reason the
|
|
423
|
+
* anchored tail writes into the DOM directly.
|
|
424
|
+
*/
|
|
425
|
+
function useSurfaceMorph({ surface, anchor, open }) {
|
|
426
|
+
const runtime = useRef({
|
|
427
|
+
view: null,
|
|
428
|
+
element: null,
|
|
429
|
+
anchor: null,
|
|
430
|
+
progress: 0,
|
|
431
|
+
target: 1,
|
|
432
|
+
geometry: null,
|
|
433
|
+
frame: 0,
|
|
434
|
+
clock: null,
|
|
435
|
+
sentinel: null,
|
|
436
|
+
from: 0,
|
|
437
|
+
started: 0,
|
|
438
|
+
duration: 0,
|
|
439
|
+
enter: 380,
|
|
440
|
+
exit: 160
|
|
441
|
+
});
|
|
442
|
+
useIsomorphicLayoutEffect(() => {
|
|
443
|
+
const state = runtime.current;
|
|
444
|
+
if (state.element !== surface) {
|
|
445
|
+
stop(state);
|
|
446
|
+
state.element = surface;
|
|
447
|
+
state.progress = 0;
|
|
448
|
+
state.target = 1;
|
|
449
|
+
state.geometry = null;
|
|
450
|
+
}
|
|
451
|
+
if (!surface) {
|
|
452
|
+
stop(state);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const view = surface.ownerDocument.defaultView;
|
|
456
|
+
if (!view) return;
|
|
457
|
+
state.view = view;
|
|
458
|
+
state.anchor = anchor ?? null;
|
|
459
|
+
const reduced = view.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
460
|
+
if (reduced?.matches) {
|
|
461
|
+
stop(state);
|
|
462
|
+
release(surface);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const target = open ? 1 : 0;
|
|
466
|
+
if (state.target !== target || state.frame === 0 && state.progress !== target) start(state, target);
|
|
467
|
+
const onPreference = () => {
|
|
468
|
+
if (!reduced?.matches) return;
|
|
469
|
+
stop(state);
|
|
470
|
+
release(surface);
|
|
471
|
+
};
|
|
472
|
+
reduced?.addEventListener?.("change", onPreference);
|
|
473
|
+
return () => reduced?.removeEventListener?.("change", onPreference);
|
|
474
|
+
}, [
|
|
475
|
+
surface,
|
|
476
|
+
anchor,
|
|
477
|
+
open
|
|
478
|
+
]);
|
|
479
|
+
useEffect(() => () => stop(runtime.current), []);
|
|
480
|
+
}
|
|
481
|
+
//#endregion
|
|
482
|
+
export { MorphSourceProvider, homographyMatrix, morphMatrix, morphQuad, useMorphAnchor, useMorphSource, useSurfaceMorph };
|
|
483
|
+
|
|
484
|
+
//# sourceMappingURL=morph.js.map
|