@patternmode/aperto 0.1.4 → 0.2.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.js CHANGED
@@ -1,52 +1,30 @@
1
1
  "use client";
2
2
 
3
- // src/Aperto/ApertoGroup.tsx
3
+ // src/Aperto/aperto-group.tsx
4
4
  import { ChevronLeft, ChevronRight, X } from "lucide-react";
5
- import {
6
- useCallback as useCallback3,
7
- useId as useId2,
8
- useLayoutEffect,
9
- useMemo as useMemo3,
10
- useReducer,
11
- useRef
12
- } from "react";
5
+ import { useId as useId2, useLayoutEffect, useReducer, useRef } from "react";
13
6
 
14
7
  // src/aperto-content.tsx
15
8
  import * as Dialog from "@radix-ui/react-dialog";
16
- import {
17
- m,
18
- useMotionValue,
19
- useSpring,
20
- useTransform
21
- } from "motion/react";
22
- import {
23
- forwardRef,
24
- useCallback,
25
- useMemo,
26
- useState
27
- } from "react";
9
+ import { m, useMotionValue, useSpring, useTransform } from "motion/react";
10
+ import { useState } from "react";
28
11
 
29
12
  // src/context.ts
30
- import { createContext, useContext } from "react";
13
+ import { createContext, use } from "react";
31
14
  var ApertoContext = createContext(null);
32
- function useApertoContext() {
33
- const ctx = useContext(ApertoContext);
15
+ var useApertoContext = () => {
16
+ const ctx = use(ApertoContext);
34
17
  if (!ctx) {
35
18
  throw new Error("Aperto components must be used within <Aperto.Root>");
36
19
  }
37
20
  return ctx;
38
- }
21
+ };
39
22
 
40
23
  // src/physics.ts
41
24
  var RESISTANCE_DIVISOR = 20;
42
25
  var RESISTANCE_SCALE = 4;
43
26
  var RESISTANCE_MIN = 0.1;
44
- function calculateResistance(distance) {
45
- return Math.max(
46
- RESISTANCE_MIN,
47
- 1 - Math.log(distance / RESISTANCE_DIVISOR + 1) / RESISTANCE_SCALE
48
- );
49
- }
27
+ var calculateResistance = (distance) => Math.max(RESISTANCE_MIN, 1 - Math.log(distance / RESISTANCE_DIVISOR + 1) / RESISTANCE_SCALE);
50
28
  var SCALE_INPUT = [0, 50, 100];
51
29
  var SCALE_OUTPUT = [1, 0.98, 0.95];
52
30
  var OPACITY_INPUT = [0, 80];
@@ -54,59 +32,44 @@ var OPACITY_OUTPUT = [1, 0.96];
54
32
  var DEFAULT_DISTANCE_THRESHOLD = 100;
55
33
  var DEFAULT_VELOCITY_THRESHOLD = 500;
56
34
 
57
- // src/motion-tokens.ts
58
- var durations = {
59
- snappy: 0.22,
60
- moderate: 0.4
61
- };
62
- var easings = {
63
- customGentle: [0.25, 0.1, 0.12, 1],
64
- snappy: [0.22, 1, 0.36, 1]
65
- };
66
- var springs = {
67
- bouncy: { stiffness: 260, damping: 12, mass: 1 },
68
- natural: { stiffness: 200, damping: 20, mass: 1 },
69
- snappy: { stiffness: 400, damping: 28, mass: 0.8 },
70
- stiff: { stiffness: 500, damping: 30, mass: 1 }
71
- };
72
-
73
35
  // src/presets.ts
36
+ import { durations, easings, springs } from "@howells/motion";
74
37
  var PRESETS = {
75
- snappy: {
76
- transition: { ease: easings.snappy, duration: durations.snappy },
38
+ bouncy: {
77
39
  drag: {
78
- stiffness: springs.stiff.stiffness,
79
- damping: springs.stiff.damping,
80
- restDelta: 0.01
40
+ damping: springs.snappy.damping,
41
+ restDelta: 0.01,
42
+ stiffness: springs.snappy.stiffness
43
+ },
44
+ transition: {
45
+ damping: springs.bouncy.damping,
46
+ mass: springs.bouncy.mass,
47
+ stiffness: springs.bouncy.stiffness,
48
+ type: "spring"
81
49
  }
82
50
  },
51
+ reduced: {
52
+ drag: { damping: 50, restDelta: 0.1, stiffness: 1e3 },
53
+ transition: { duration: 0.01, ease: "linear" }
54
+ },
83
55
  smooth: {
84
- transition: { ease: easings.customGentle, duration: durations.moderate },
85
56
  drag: {
86
- stiffness: springs.natural.stiffness,
87
57
  damping: springs.natural.damping,
88
- restDelta: 0.01
89
- }
90
- },
91
- bouncy: {
92
- transition: {
93
- type: "spring",
94
- stiffness: springs.bouncy.stiffness,
95
- damping: springs.bouncy.damping,
96
- mass: springs.bouncy.mass
58
+ restDelta: 0.01,
59
+ stiffness: springs.natural.stiffness
97
60
  },
98
- drag: {
99
- stiffness: springs.snappy.stiffness,
100
- damping: springs.snappy.damping,
101
- restDelta: 0.01
102
- }
61
+ transition: { duration: durations.moderate, ease: easings.customGentle }
103
62
  },
104
- reduced: {
105
- transition: { ease: "linear", duration: 0.01 },
106
- drag: { stiffness: 1e3, damping: 50, restDelta: 0.1 }
63
+ snappy: {
64
+ drag: {
65
+ damping: springs.stiff.damping,
66
+ restDelta: 0.01,
67
+ stiffness: springs.stiff.stiffness
68
+ },
69
+ transition: { duration: durations.snappy, ease: easings.snappy }
107
70
  }
108
71
  };
109
- function resolvePreset(componentType, componentMotion, globalPreset, variants, reduceMotion) {
72
+ var resolvePreset = (componentType, componentMotion, globalPreset, variants, reduceMotion) => {
110
73
  if (reduceMotion) {
111
74
  return PRESETS.reduced;
112
75
  }
@@ -117,203 +80,171 @@ function resolvePreset(componentType, componentMotion, globalPreset, variants, r
117
80
  return PRESETS[variants[componentType]];
118
81
  }
119
82
  return PRESETS[globalPreset];
120
- }
121
- function getDragSpring(preset) {
122
- return preset.drag;
123
- }
83
+ };
84
+ var getDragSpring = (preset) => preset.drag;
124
85
 
125
86
  // src/aperto-content.tsx
126
87
  import { jsx } from "react/jsx-runtime";
127
- var ApertoContent = forwardRef(
128
- ({
129
- children,
130
- className,
131
- motion: motionOverride,
132
- placement = "center",
133
- sharedLayoutId,
134
- style,
135
- ...props
136
- }, ref) => {
137
- const ctx = useApertoContext();
138
- const [isDragging, setIsDragging] = useState(false);
139
- const resolved = resolvePreset(
140
- "content",
141
- motionOverride,
142
- ctx.presetName,
143
- ctx.variants,
144
- ctx.reduceMotion
145
- );
146
- const contentLayoutId = sharedLayoutId === false ? void 0 : sharedLayoutId ?? `${ctx.layoutId}-shared`;
147
- const dragSpring = getDragSpring(resolved);
148
- const isDismissible = ctx.dismissible !== false;
149
- const dismissConfig = typeof ctx.dismissible === "object" ? ctx.dismissible : {};
150
- const distanceThreshold = dismissConfig.threshold ?? DEFAULT_DISTANCE_THRESHOLD;
151
- const velocityThreshold = dismissConfig.velocity ?? DEFAULT_VELOCITY_THRESHOLD;
152
- const dragX = useMotionValue(0);
153
- const dragY = useMotionValue(0);
154
- const springX = useSpring(dragX, dragSpring);
155
- const springY = useSpring(dragY, dragSpring);
156
- const distance = useTransform(
157
- () => Math.hypot(springX.get(), springY.get())
158
- );
159
- const scaleMotion = useTransform(distance, SCALE_INPUT, SCALE_OUTPUT);
160
- const opacityMotion = useTransform(distance, OPACITY_INPUT, OPACITY_OUTPUT);
161
- const resistance = useTransform(distance, calculateResistance);
162
- const handleDrag = useCallback(
163
- (_event, info) => {
164
- if (!isDragging) {
165
- setIsDragging(true);
166
- }
167
- const r = resistance.get();
168
- dragX.set(info.offset.x * r);
169
- dragY.set(info.offset.y * r);
170
- },
171
- [dragX, dragY, isDragging, resistance]
172
- );
173
- const handleDragEnd = useCallback(
174
- (_event, info) => {
175
- setIsDragging(false);
176
- const speed = Math.hypot(info.velocity.x, info.velocity.y);
177
- const dist = Math.hypot(info.offset.x, info.offset.y);
178
- if (speed > velocityThreshold || dist > distanceThreshold) {
179
- ctx.onOpenChange(false);
180
- return;
181
- }
182
- dragX.set(0);
183
- dragY.set(0);
184
- },
185
- [ctx.onOpenChange, distanceThreshold, dragX, dragY, velocityThreshold]
186
- );
187
- const motionStyle = useMemo(
188
- () => ({
189
- cursor: isDragging ? "grabbing" : isDismissible ? "grab" : void 0,
190
- opacity: opacityMotion,
191
- scale: scaleMotion,
192
- x: springX,
193
- y: springY,
194
- ...placement === "center" ? {
195
- left: "50%",
196
- top: "50%",
197
- translate: "-50% -50%"
198
- } : {},
199
- ...style
200
- }),
201
- [
202
- isDismissible,
203
- isDragging,
204
- opacityMotion,
205
- placement,
206
- scaleMotion,
207
- springX,
208
- springY,
209
- style
210
- ]
211
- );
212
- return /* @__PURE__ */ jsx(Dialog.Content, { asChild: true, forceMount: true, ...props, children: /* @__PURE__ */ jsx(
213
- m.div,
214
- {
215
- className,
216
- "data-slot": "aperto-content",
217
- drag: isDismissible,
218
- dragConstraints: { bottom: 0, left: 0, right: 0, top: 0 },
219
- dragElastic: 0,
220
- dragMomentum: false,
221
- dragSnapToOrigin: true,
222
- layout: contentLayoutId ? true : void 0,
223
- layoutId: contentLayoutId,
224
- onDrag: isDismissible ? handleDrag : void 0,
225
- onDragEnd: isDismissible ? handleDragEnd : void 0,
226
- ref,
227
- style: motionStyle,
228
- transition: resolved.transition,
229
- children
230
- }
231
- ) });
88
+ var ApertoContent = ({
89
+ children,
90
+ className,
91
+ motion: motionOverride,
92
+ placement = "center",
93
+ ref,
94
+ sharedLayoutId,
95
+ style,
96
+ ...props
97
+ }) => {
98
+ const ctx = useApertoContext();
99
+ const [isDragging, setIsDragging] = useState(false);
100
+ const resolved = resolvePreset(
101
+ "content",
102
+ motionOverride,
103
+ ctx.presetName,
104
+ ctx.variants,
105
+ ctx.reduceMotion
106
+ );
107
+ const contentLayoutId = sharedLayoutId === false ? void 0 : sharedLayoutId ?? `${ctx.layoutId}-shared`;
108
+ const dragSpring = getDragSpring(resolved);
109
+ const isDismissible = ctx.dismissible !== false;
110
+ const dismissConfig = typeof ctx.dismissible === "object" ? ctx.dismissible : {};
111
+ const distanceThreshold = dismissConfig.threshold ?? DEFAULT_DISTANCE_THRESHOLD;
112
+ const velocityThreshold = dismissConfig.velocity ?? DEFAULT_VELOCITY_THRESHOLD;
113
+ const dragX = useMotionValue(0);
114
+ const dragY = useMotionValue(0);
115
+ const springX = useSpring(dragX, dragSpring);
116
+ const springY = useSpring(dragY, dragSpring);
117
+ const distance = useTransform(() => Math.hypot(springX.get(), springY.get()));
118
+ const scaleMotion = useTransform(distance, SCALE_INPUT, SCALE_OUTPUT);
119
+ const opacityMotion = useTransform(distance, OPACITY_INPUT, OPACITY_OUTPUT);
120
+ const resistance = useTransform(distance, calculateResistance);
121
+ const { onOpenChange } = ctx;
122
+ const handleDrag = (_event, info) => {
123
+ if (!isDragging) {
124
+ setIsDragging(true);
125
+ }
126
+ const r = resistance.get();
127
+ dragX.set(info.offset.x * r);
128
+ dragY.set(info.offset.y * r);
129
+ };
130
+ const handleDragEnd = (_event, info) => {
131
+ setIsDragging(false);
132
+ const speed = Math.hypot(info.velocity.x, info.velocity.y);
133
+ const dist = Math.hypot(info.offset.x, info.offset.y);
134
+ if (speed > velocityThreshold || dist > distanceThreshold) {
135
+ onOpenChange(false);
136
+ return;
137
+ }
138
+ dragX.set(0);
139
+ dragY.set(0);
140
+ };
141
+ const placementStyle = placement === "center" ? {
142
+ left: "50%",
143
+ top: "50%",
144
+ translate: "-50% -50%"
145
+ } : {};
146
+ let cursor;
147
+ if (isDragging) {
148
+ cursor = "grabbing";
149
+ } else if (isDismissible) {
150
+ cursor = "grab";
232
151
  }
233
- );
152
+ const motionStyle = {
153
+ cursor,
154
+ opacity: opacityMotion,
155
+ scale: scaleMotion,
156
+ x: springX,
157
+ y: springY,
158
+ ...placementStyle,
159
+ ...style
160
+ };
161
+ return /* @__PURE__ */ jsx(Dialog.Content, { asChild: true, forceMount: true, ...props, children: /* @__PURE__ */ jsx(
162
+ m.div,
163
+ {
164
+ className,
165
+ "data-slot": "aperto-content",
166
+ drag: isDismissible,
167
+ dragConstraints: { bottom: 0, left: 0, right: 0, top: 0 },
168
+ dragElastic: 0,
169
+ dragMomentum: false,
170
+ dragSnapToOrigin: true,
171
+ layout: contentLayoutId === void 0 || contentLayoutId === "" ? void 0 : true,
172
+ layoutId: contentLayoutId,
173
+ onDrag: isDismissible ? handleDrag : void 0,
174
+ onDragEnd: isDismissible ? handleDragEnd : void 0,
175
+ ref,
176
+ style: motionStyle,
177
+ transition: resolved.transition,
178
+ children
179
+ }
180
+ ) });
181
+ };
234
182
  ApertoContent.displayName = "ApertoContent";
235
183
 
236
184
  // src/aperto-description.tsx
237
185
  import * as Dialog2 from "@radix-ui/react-dialog";
238
- import { forwardRef as forwardRef2 } from "react";
239
186
  import { jsx as jsx2 } from "react/jsx-runtime";
240
- var ApertoDescription = forwardRef2((props, ref) => {
241
- return /* @__PURE__ */ jsx2(Dialog2.Description, { "data-slot": "aperto-description", ref, ...props });
242
- });
187
+ var ApertoDescription = ({ ref, ...props }) => /* @__PURE__ */ jsx2(Dialog2.Description, { "data-slot": "aperto-description", ref, ...props });
243
188
  ApertoDescription.displayName = "ApertoDescription";
244
189
 
245
190
  // src/aperto-group-context.ts
246
- import { createContext as createContext2, useContext as useContext2 } from "react";
247
- var ApertoGroupContext = createContext2(
248
- null
249
- );
250
- function useApertoGroup() {
251
- const ctx = useContext2(ApertoGroupContext);
191
+ import { createContext as createContext2, use as use2 } from "react";
192
+ var ApertoGroupContext = createContext2(null);
193
+ var useApertoGroup = () => {
194
+ const ctx = use2(ApertoGroupContext);
252
195
  if (!ctx) {
253
196
  throw new Error("Aperto.Thumbnail must be used within <Aperto.Group>");
254
197
  }
255
198
  return ctx;
256
- }
199
+ };
257
200
 
258
201
  // src/aperto-overlay.tsx
259
202
  import * as Dialog3 from "@radix-ui/react-dialog";
260
203
  import { m as m2 } from "motion/react";
261
- import { forwardRef as forwardRef3 } from "react";
204
+ import { easings as easings2 } from "@howells/motion";
262
205
  import { jsx as jsx3 } from "react/jsx-runtime";
263
206
  var OVERLAY_TRANSITION = {
264
207
  duration: 0.3,
265
- ease: easings.customGentle
208
+ ease: easings2.customGentle
266
209
  };
267
210
  var OVERLAY_TRANSITION_REDUCED = {
268
211
  duration: 0.01,
269
212
  ease: "linear"
270
213
  };
271
- var ApertoOverlay = forwardRef3(
272
- ({ className, fadeOut, style }, ref) => {
273
- const ctx = useApertoContext();
274
- const transition = ctx.reduceMotion ? OVERLAY_TRANSITION_REDUCED : OVERLAY_TRANSITION;
275
- return /* @__PURE__ */ jsx3(Dialog3.Overlay, { asChild: true, forceMount: true, children: /* @__PURE__ */ jsx3(
276
- m2.div,
277
- {
278
- animate: { opacity: fadeOut ? 0 : 1 },
279
- className,
280
- "data-slot": "aperto-overlay",
281
- exit: { opacity: 0 },
282
- initial: { opacity: 0 },
283
- ref,
284
- style,
285
- transition
286
- }
287
- ) });
288
- }
289
- );
214
+ var ApertoOverlay = ({ className, fadeOut, ref, style }) => {
215
+ const ctx = useApertoContext();
216
+ const transition = ctx.reduceMotion ? OVERLAY_TRANSITION_REDUCED : OVERLAY_TRANSITION;
217
+ return /* @__PURE__ */ jsx3(Dialog3.Overlay, { asChild: true, forceMount: true, children: /* @__PURE__ */ jsx3(
218
+ m2.div,
219
+ {
220
+ animate: { opacity: fadeOut === true ? 0 : 1 },
221
+ className,
222
+ "data-slot": "aperto-overlay",
223
+ exit: { opacity: 0 },
224
+ initial: { opacity: 0 },
225
+ ref,
226
+ style,
227
+ transition
228
+ }
229
+ ) });
230
+ };
290
231
  ApertoOverlay.displayName = "ApertoOverlay";
291
232
 
292
233
  // src/aperto-portal.tsx
293
234
  import * as Dialog4 from "@radix-ui/react-dialog";
294
235
  import { AnimatePresence } from "motion/react";
295
236
  import { jsx as jsx4 } from "react/jsx-runtime";
296
- function ApertoPortal({ children, container }) {
237
+ var ApertoPortal = ({ children, container }) => {
297
238
  const { open } = useApertoContext();
298
239
  return /* @__PURE__ */ jsx4(AnimatePresence, { children: open && /* @__PURE__ */ jsx4(Dialog4.Portal, { container, forceMount: true, children }) });
299
- }
240
+ };
300
241
 
301
242
  // src/aperto-root.tsx
302
243
  import * as Dialog5 from "@radix-ui/react-dialog";
303
- import {
304
- domMax,
305
- LayoutGroup,
306
- LazyMotion,
307
- useReducedMotion
308
- } from "motion/react";
309
- import {
310
- useCallback as useCallback2,
311
- useId,
312
- useMemo as useMemo2,
313
- useState as useState2
314
- } from "react";
244
+ import { domMax, LayoutGroup, LazyMotion, useReducedMotion } from "motion/react";
245
+ import { useId, useState as useState2 } from "react";
315
246
  import { jsx as jsx5 } from "react/jsx-runtime";
316
- function ApertoRoot({
247
+ var ApertoRoot = ({
317
248
  children,
318
249
  dismissible = true,
319
250
  layoutId: layoutIdProp,
@@ -322,7 +253,7 @@ function ApertoRoot({
322
253
  onOpenChange: controlledOnOpenChange,
323
254
  reduceMotion: reduceMotionProp,
324
255
  ...dialogProps
325
- }) {
256
+ }) => {
326
257
  const generatedId = useId();
327
258
  const layoutId = layoutIdProp ?? `aperto-${generatedId}`;
328
259
  const systemReducedMotion = useReducedMotion() ?? false;
@@ -330,67 +261,46 @@ function ApertoRoot({
330
261
  const [internalOpen, setInternalOpen] = useState2(false);
331
262
  const isControlled = controlledOpen !== void 0;
332
263
  const open = isControlled ? controlledOpen : internalOpen;
333
- const onOpenChange = useCallback2(
334
- (nextOpen) => {
335
- if (!isControlled) {
336
- setInternalOpen(nextOpen);
337
- }
338
- controlledOnOpenChange?.(nextOpen);
339
- },
340
- [isControlled, controlledOnOpenChange]
341
- );
264
+ const onOpenChange = (nextOpen) => {
265
+ if (!isControlled) {
266
+ setInternalOpen(nextOpen);
267
+ }
268
+ controlledOnOpenChange?.(nextOpen);
269
+ };
342
270
  const globalPresetName = typeof motionProp === "string" ? motionProp : "smooth";
343
271
  const variants = typeof motionProp === "object" ? motionProp : void 0;
344
272
  const presetName = shouldReduce ? "reduced" : globalPresetName;
345
273
  const preset = PRESETS[presetName];
346
- const contextValue = useMemo2(
347
- () => ({
348
- dismissible,
349
- layoutId,
350
- onOpenChange,
351
- open,
352
- preset,
353
- presetName,
354
- reduceMotion: shouldReduce,
355
- variants
356
- }),
357
- [
358
- dismissible,
359
- layoutId,
360
- onOpenChange,
361
- open,
362
- preset,
363
- presetName,
364
- shouldReduce,
365
- variants
366
- ]
367
- );
274
+ const contextValue = {
275
+ dismissible,
276
+ layoutId,
277
+ onOpenChange,
278
+ open,
279
+ preset,
280
+ presetName,
281
+ reduceMotion: shouldReduce,
282
+ variants
283
+ };
368
284
  return /* @__PURE__ */ jsx5(ApertoContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx5(Dialog5.Root, { onOpenChange, open, ...dialogProps, children: /* @__PURE__ */ jsx5(LazyMotion, { features: domMax, children: /* @__PURE__ */ jsx5(LayoutGroup, { id: layoutId, children }) }) }) });
369
- }
285
+ };
370
286
 
371
287
  // src/aperto-title.tsx
372
288
  import * as Dialog6 from "@radix-ui/react-dialog";
373
- import { forwardRef as forwardRef4 } from "react";
374
289
  import { jsx as jsx6 } from "react/jsx-runtime";
375
- var ApertoTitle = forwardRef4((props, ref) => {
376
- return /* @__PURE__ */ jsx6(Dialog6.Title, { "data-slot": "aperto-title", ref, ...props });
377
- });
290
+ var ApertoTitle = ({ ref, ...props }) => /* @__PURE__ */ jsx6(Dialog6.Title, { "data-slot": "aperto-title", ref, ...props });
378
291
  ApertoTitle.displayName = "ApertoTitle";
379
292
 
380
293
  // src/expanded-media-stage.tsx
381
- import {
382
- AnimatePresence as AnimatePresence2,
383
- m as m3
384
- } from "motion/react";
294
+ import { AnimatePresence as AnimatePresence2, m as m3 } from "motion/react";
385
295
 
386
296
  // src/media-rendering.tsx
387
297
  import { createElement } from "react";
388
298
  import { jsx as jsx7 } from "react/jsx-runtime";
389
- function renderDefaultImage(props) {
299
+ var renderDefaultImage = (props) => {
390
300
  const { item: _item, variant: _variant, ...imageProps } = props;
391
301
  return createElement("img", { ...imageProps, alt: imageProps.alt ?? "" });
392
- }
393
- function renderDefaultVideo(props) {
302
+ };
303
+ var renderDefaultVideo = (props) => {
394
304
  const { item: _item, variant, ...videoProps } = props;
395
305
  if (variant === "thumbnail") {
396
306
  return createElement("img", {
@@ -399,12 +309,12 @@ function renderDefaultVideo(props) {
399
309
  });
400
310
  }
401
311
  return /* @__PURE__ */ jsx7("video", { ...videoProps, children: /* @__PURE__ */ jsx7("track", { kind: "captions", src: props.item.captionsSrc ?? "data:text/vtt," }) });
402
- }
403
- function ApertoThumbnailMedia({
312
+ };
313
+ var ApertoThumbnailMedia = ({
404
314
  item,
405
315
  renderImage,
406
316
  renderVideo
407
- }) {
317
+ }) => {
408
318
  if (item.type === "image") {
409
319
  const imageProps = {
410
320
  alt: "",
@@ -426,12 +336,12 @@ function ApertoThumbnailMedia({
426
336
  width: item.width
427
337
  };
428
338
  return (renderVideo ?? renderDefaultVideo)(videoProps);
429
- }
430
- function ApertoExpandedMedia({
339
+ };
340
+ var ApertoExpandedMedia = ({
431
341
  item,
432
342
  renderImage,
433
343
  renderVideo
434
- }) {
344
+ }) => {
435
345
  if (item.type === "image") {
436
346
  const imageProps = {
437
347
  alt: item.alt,
@@ -454,12 +364,12 @@ function ApertoExpandedMedia({
454
364
  width: item.width
455
365
  };
456
366
  return (renderVideo ?? renderDefaultVideo)(videoProps);
457
- }
458
- function ApertoTransitionMedia({
367
+ };
368
+ var ApertoTransitionMedia = ({
459
369
  item,
460
370
  renderImage,
461
371
  renderVideo
462
- }) {
372
+ }) => {
463
373
  if (item.type === "image") {
464
374
  const imageProps = {
465
375
  alt: item.alt,
@@ -481,18 +391,12 @@ function ApertoTransitionMedia({
481
391
  width: item.width
482
392
  };
483
393
  return (renderVideo ?? renderDefaultVideo)(videoProps);
484
- }
394
+ };
485
395
 
486
396
  // src/media-utils.ts
487
- function getMediaLabel(item) {
488
- return item.title ?? item.alt ?? "media";
489
- }
490
- function getMediaKey(item, index) {
491
- return item.id ?? `${item.type}:${item.src}:${index}`;
492
- }
493
- function getDescriptionProps(item) {
494
- return item.description ? {} : { "aria-describedby": void 0 };
495
- }
397
+ var getMediaLabel = (item) => item.title ?? item.alt ?? "media";
398
+ var getMediaKey = (item, index) => item.id ?? `${item.type}:${item.src}:${index}`;
399
+ var getDescriptionProps = (item) => item.description === void 0 || item.description === "" ? { "aria-describedby": void 0 } : {};
496
400
 
497
401
  // src/expanded-media-stage.tsx
498
402
  import { jsx as jsx8 } from "react/jsx-runtime";
@@ -535,20 +439,12 @@ var expandedMediaVariants = {
535
439
  scale: 1,
536
440
  x: 0
537
441
  },
538
- enter: ({
539
- direction,
540
- offset,
541
- scale
542
- }) => ({
442
+ enter: ({ direction, offset, scale }) => ({
543
443
  opacity: 0,
544
444
  scale: direction === 0 ? 1 : scale,
545
445
  x: direction * offset
546
446
  }),
547
- exit: ({
548
- direction,
549
- offset,
550
- scale
551
- }) => ({
447
+ exit: ({ direction, offset, scale }) => ({
552
448
  opacity: 0,
553
449
  scale: direction === 0 ? 1 : scale,
554
450
  x: direction * -offset
@@ -568,14 +464,14 @@ var reducedExpandedMediaVariants = {
568
464
  x: 0
569
465
  }
570
466
  };
571
- function ApertoExpandedMediaStage({
467
+ var ApertoExpandedMediaStage = ({
572
468
  direction,
573
469
  index,
574
470
  item,
575
471
  navigationMotion,
576
472
  renderImage,
577
473
  renderVideo
578
- }) {
474
+ }) => {
579
475
  const ctx = useApertoContext();
580
476
  const preset = NAVIGATION_MOTION_PRESETS[navigationMotion];
581
477
  const transition = ctx.reduceMotion ? REDUCED_EXPANDED_MEDIA_TRANSITION : preset.transition;
@@ -597,25 +493,18 @@ function ApertoExpandedMediaStage({
597
493
  initial: "enter",
598
494
  transition,
599
495
  variants,
600
- children: /* @__PURE__ */ jsx8(
601
- ApertoExpandedMedia,
602
- {
603
- item,
604
- renderImage,
605
- renderVideo
606
- }
607
- )
496
+ children: /* @__PURE__ */ jsx8(ApertoExpandedMedia, { item, renderImage, renderVideo })
608
497
  },
609
498
  getMediaKey(item, index)
610
499
  ) });
611
- }
500
+ };
612
501
 
613
502
  // src/media-transition.tsx
614
503
  import { m as m4 } from "motion/react";
615
504
  import { useEffect } from "react";
616
505
 
617
506
  // src/media-transition-utils.ts
618
- function rectFromElement(element) {
507
+ var rectFromElement = (element) => {
619
508
  if (!element) {
620
509
  return null;
621
510
  }
@@ -626,39 +515,36 @@ function rectFromElement(element) {
626
515
  top: rect.top,
627
516
  width: rect.width
628
517
  };
629
- }
630
- function rectTarget(rect) {
631
- return {
632
- height: rect.height,
633
- left: rect.left,
634
- top: rect.top,
635
- width: rect.width
636
- };
637
- }
638
- function transitionDurationMs(transition) {
639
- return typeof transition.duration === "number" ? transition.duration * 1e3 : 450;
640
- }
518
+ };
519
+ var rectTarget = (rect) => ({
520
+ height: rect.height,
521
+ left: rect.left,
522
+ top: rect.top,
523
+ width: rect.width
524
+ });
525
+ var transitionDurationMs = (transition) => typeof transition.duration === "number" ? transition.duration * 1e3 : 450;
641
526
 
642
527
  // src/media-transition.tsx
643
528
  import { jsx as jsx9 } from "react/jsx-runtime";
644
- function ApertoMediaTransitionClone({
529
+ var ApertoMediaTransitionClone = ({
645
530
  onComplete,
646
531
  renderImage,
647
532
  renderVideo,
648
533
  transition
649
- }) {
534
+ }) => {
650
535
  const ctx = useApertoContext();
651
536
  useEffect(() => {
652
- if (!transition?.to) {
653
- return;
537
+ let timer;
538
+ if (transition?.to !== void 0) {
539
+ timer = setTimeout(onComplete, transitionDurationMs(ctx.preset.transition));
654
540
  }
655
- const timer = setTimeout(
656
- onComplete,
657
- transitionDurationMs(ctx.preset.transition)
658
- );
659
- return () => clearTimeout(timer);
541
+ return () => {
542
+ if (timer !== void 0) {
543
+ clearTimeout(timer);
544
+ }
545
+ };
660
546
  }, [ctx.preset.transition, onComplete, transition]);
661
- if (!transition?.to) {
547
+ if (transition?.to === void 0) {
662
548
  return null;
663
549
  }
664
550
  return /* @__PURE__ */ jsx9(
@@ -686,24 +572,23 @@ function ApertoMediaTransitionClone({
686
572
  )
687
573
  }
688
574
  );
689
- }
575
+ };
690
576
 
691
- // src/Aperto/ApertoGroupState.ts
692
- function getInitialGroupState(initialIndex) {
693
- return {
694
- closing: false,
695
- internalIndex: initialIndex,
696
- layoutSourceIndex: initialIndex,
697
- mediaTransition: null,
698
- navigationDirection: 0,
699
- open: false
700
- };
701
- }
702
- function apertoGroupReducer(state, action) {
577
+ // src/Aperto/aperto-group-state.ts
578
+ var getInitialGroupState = (initialIndex) => ({
579
+ closing: false,
580
+ internalIndex: initialIndex,
581
+ layoutSourceIndex: initialIndex,
582
+ mediaTransition: null,
583
+ navigationDirection: 0,
584
+ open: false
585
+ });
586
+ var apertoGroupReducer = (state, action) => {
703
587
  switch (action.type) {
704
- case "close-without-transition":
588
+ case "close-without-transition": {
705
589
  return { ...state, closing: false, mediaTransition: null, open: false };
706
- case "complete-opening-target":
590
+ }
591
+ case "complete-opening-target": {
707
592
  if (state.mediaTransition?.phase !== "opening" || state.mediaTransition.to) {
708
593
  return state;
709
594
  }
@@ -711,21 +596,24 @@ function apertoGroupReducer(state, action) {
711
596
  ...state,
712
597
  mediaTransition: { ...state.mediaTransition, to: action.rect }
713
598
  };
714
- case "finish-transition":
599
+ }
600
+ case "finish-transition": {
715
601
  return {
716
602
  ...state,
717
603
  closing: false,
718
604
  mediaTransition: null,
719
605
  open: state.mediaTransition?.phase === "closing" ? false : state.open
720
606
  };
721
- case "navigate":
607
+ }
608
+ case "navigate": {
722
609
  return {
723
610
  ...state,
724
611
  internalIndex: action.index,
725
612
  layoutSourceIndex: action.index,
726
613
  navigationDirection: action.direction
727
614
  };
728
- case "open-at-index":
615
+ }
616
+ case "open-at-index": {
729
617
  return {
730
618
  ...state,
731
619
  closing: false,
@@ -735,22 +623,29 @@ function apertoGroupReducer(state, action) {
735
623
  navigationDirection: 0,
736
624
  open: true
737
625
  };
738
- case "set-index":
626
+ }
627
+ case "set-index": {
739
628
  return { ...state, internalIndex: action.index };
740
- case "set-open":
629
+ }
630
+ case "set-open": {
741
631
  return action.open ? { ...state, closing: false, open: true } : { ...state, open: false };
742
- case "start-close":
632
+ }
633
+ case "start-close": {
743
634
  return {
744
635
  ...state,
745
636
  closing: true,
746
637
  mediaTransition: action.transition
747
638
  };
639
+ }
640
+ default: {
641
+ return state;
642
+ }
748
643
  }
749
- }
644
+ };
750
645
 
751
- // src/Aperto/ApertoKeyboard.ts
752
- function shouldIgnoreKeyboardNavigationTarget(target) {
753
- if (!(target instanceof Element)) {
646
+ // src/Aperto/aperto-keyboard.ts
647
+ var shouldIgnoreKeyboardNavigationTarget = (target) => {
648
+ if (!(target instanceof HTMLElement)) {
754
649
  return false;
755
650
  }
756
651
  return target.isContentEditable || Boolean(
@@ -758,444 +653,443 @@ function shouldIgnoreKeyboardNavigationTarget(target) {
758
653
  'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], audio, video'
759
654
  )
760
655
  );
761
- }
656
+ };
762
657
 
763
- // src/Aperto/ApertoGroup.tsx
658
+ // src/Aperto/aperto-group.tsx
764
659
  import { Fragment, jsx as jsx10, jsxs } from "react/jsx-runtime";
765
- function ApertoGroup({
766
- children,
660
+ var ApertoMediaNavigationButtons = ({
767
661
  classNames,
768
- dismissible,
769
- index: controlledIndex,
770
- initialIndex = 0,
662
+ enabled,
663
+ onNext,
664
+ onPrevious
665
+ }) => {
666
+ if (!enabled) {
667
+ return null;
668
+ }
669
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
670
+ /* @__PURE__ */ jsx10(
671
+ "button",
672
+ {
673
+ "aria-label": "Previous media",
674
+ className: classNames?.previousButton,
675
+ "data-slot": "aperto-previous-button",
676
+ onClick: onPrevious,
677
+ type: "button",
678
+ children: /* @__PURE__ */ jsx10(ChevronLeft, { "aria-hidden": "true", size: 18, strokeWidth: 2 })
679
+ }
680
+ ),
681
+ /* @__PURE__ */ jsx10(
682
+ "button",
683
+ {
684
+ "aria-label": "Next media",
685
+ className: classNames?.nextButton,
686
+ "data-slot": "aperto-next-button",
687
+ onClick: onNext,
688
+ type: "button",
689
+ children: /* @__PURE__ */ jsx10(ChevronRight, { "aria-hidden": "true", size: 18, strokeWidth: 2 })
690
+ }
691
+ )
692
+ ] });
693
+ };
694
+ var useThumbnailMap = () => {
695
+ const thumbnailRefs = useRef(null);
696
+ thumbnailRefs.current ??= /* @__PURE__ */ new Map();
697
+ return thumbnailRefs.current;
698
+ };
699
+ var getExpandedMediaStyle = (activeMedia) => {
700
+ if (activeMedia === void 0 || activeMedia.width === void 0 || activeMedia.height === void 0) {
701
+ return void 0;
702
+ }
703
+ return {
704
+ "--aperto-expanded-aspect-ratio": `${activeMedia.width} / ${activeMedia.height}`,
705
+ "--aperto-expanded-aspect-ratio-height": activeMedia.height,
706
+ "--aperto-expanded-aspect-ratio-width": activeMedia.width
707
+ };
708
+ };
709
+ var ApertoGroupPortal = ({
710
+ activeMedia,
711
+ classNames,
712
+ expandedMediaStyle,
713
+ handleCloseAutoFocus,
714
+ handleContentKeyDown,
715
+ handleMediaTransitionComplete,
716
+ hasNavigation,
717
+ index,
718
+ mediaLength,
719
+ navigationDirection,
720
+ navigationMotion,
721
+ renderImage,
722
+ renderVideo,
723
+ setExpandedMediaNode,
724
+ startClose,
725
+ transition,
726
+ goToNext,
727
+ goToPrevious
728
+ }) => {
729
+ if (activeMedia === void 0) {
730
+ return null;
731
+ }
732
+ return /* @__PURE__ */ jsxs(ApertoPortal, { children: [
733
+ /* @__PURE__ */ jsx10(ApertoOverlay, { className: classNames?.overlay, fadeOut: transition?.phase === "closing" }),
734
+ /* @__PURE__ */ jsxs(
735
+ ApertoContent,
736
+ {
737
+ className: classNames?.content,
738
+ "data-aperto-transition": transition?.phase,
739
+ onCloseAutoFocus: handleCloseAutoFocus,
740
+ onKeyDown: handleContentKeyDown,
741
+ sharedLayoutId: false,
742
+ ...getDescriptionProps(activeMedia),
743
+ children: [
744
+ /* @__PURE__ */ jsxs("div", { "data-slot": "aperto-media", ref: setExpandedMediaNode, style: expandedMediaStyle, children: [
745
+ /* @__PURE__ */ jsx10(
746
+ ApertoExpandedMediaStage,
747
+ {
748
+ direction: navigationDirection,
749
+ index,
750
+ item: activeMedia,
751
+ navigationMotion,
752
+ renderImage,
753
+ renderVideo
754
+ }
755
+ ),
756
+ /* @__PURE__ */ jsx10(
757
+ ApertoMediaNavigationButtons,
758
+ {
759
+ classNames,
760
+ enabled: hasNavigation,
761
+ onNext: goToNext,
762
+ onPrevious: goToPrevious
763
+ }
764
+ )
765
+ ] }),
766
+ /* @__PURE__ */ jsx10(ApertoTitle, { children: activeMedia.title ?? getMediaLabel(activeMedia) }),
767
+ activeMedia.description === void 0 || activeMedia.description === "" ? null : /* @__PURE__ */ jsx10(ApertoDescription, { children: activeMedia.description }),
768
+ hasNavigation ? /* @__PURE__ */ jsxs("div", { className: classNames?.counter, "data-slot": "aperto-counter", children: [
769
+ index + 1,
770
+ " / ",
771
+ mediaLength
772
+ ] }) : null,
773
+ /* @__PURE__ */ jsx10(
774
+ "button",
775
+ {
776
+ "aria-label": "Close",
777
+ className: classNames?.closeButton,
778
+ "data-slot": "aperto-close",
779
+ onClick: startClose,
780
+ type: "button",
781
+ children: /* @__PURE__ */ jsx10(X, { "aria-hidden": "true", size: 17, strokeWidth: 2 })
782
+ }
783
+ )
784
+ ]
785
+ }
786
+ ),
787
+ /* @__PURE__ */ jsx10(
788
+ ApertoMediaTransitionClone,
789
+ {
790
+ onComplete: handleMediaTransitionComplete,
791
+ renderImage,
792
+ renderVideo,
793
+ transition
794
+ }
795
+ )
796
+ ] });
797
+ };
798
+ var useApertoMediaLifecycle = ({
799
+ activeMedia,
800
+ dispatch,
801
+ index,
771
802
  media,
772
- motion: motionProp,
773
- navigationMotion = "glide",
774
803
  onIndexChange,
775
- renderImage,
776
- renderVideo
777
- }) {
778
- const generatedId = useId2();
779
- const [state, dispatch] = useReducer(
780
- apertoGroupReducer,
781
- initialIndex,
782
- getInitialGroupState
783
- );
784
- const isControlled = controlledIndex !== void 0;
785
- const index = isControlled ? controlledIndex : state.internalIndex;
786
- const activeMedia = media[index] ?? media[0];
804
+ state
805
+ }) => {
787
806
  const expandedMediaRef = useRef(null);
788
- const thumbnailRefs = useRef(null);
789
- if (thumbnailRefs.current === null) {
790
- thumbnailRefs.current = /* @__PURE__ */ new Map();
791
- }
792
- const thumbnailMap = thumbnailRefs.current;
793
- const sharedLayoutIdForIndex = useCallback3(
794
- (nextIndex) => `aperto-group-${generatedId}-${nextIndex}-shared`,
795
- [generatedId]
796
- );
797
- const sharedLayoutId = sharedLayoutIdForIndex(state.layoutSourceIndex);
798
- const measureOpeningTarget = useCallback3((node) => {
807
+ const thumbnailMap = useThumbnailMap();
808
+ const setExpandedMediaNode = (node) => {
809
+ expandedMediaRef.current = node;
810
+ if (node === null) {
811
+ return;
812
+ }
799
813
  const targetRect = rectFromElement(node);
800
- if (!targetRect) {
814
+ if (targetRect !== null) {
815
+ dispatch({ rect: targetRect, type: "complete-opening-target" });
816
+ }
817
+ };
818
+ const registerThumbnail = (thumbIndex, node) => {
819
+ if (node !== null) {
820
+ thumbnailMap.set(thumbIndex, node);
801
821
  return;
802
822
  }
803
- dispatch({ rect: targetRect, type: "complete-opening-target" });
804
- }, []);
805
- const setExpandedMediaNode = useCallback3(
806
- (node) => {
807
- expandedMediaRef.current = node;
808
- if (node) {
809
- measureOpeningTarget(node);
810
- }
811
- },
812
- [measureOpeningTarget]
813
- );
814
- const registerThumbnail = useCallback3(
815
- (thumbIndex, node) => {
816
- if (node) {
817
- thumbnailMap.set(thumbIndex, node);
818
- return;
819
- }
820
- thumbnailMap.delete(thumbIndex);
821
- },
822
- [thumbnailMap]
823
- );
823
+ thumbnailMap.delete(thumbIndex);
824
+ };
824
825
  useLayoutEffect(() => {
825
- if (state.mediaTransition?.phase !== "opening" || state.mediaTransition.to || !expandedMediaRef.current) {
826
+ if (state.mediaTransition?.phase !== "opening" || state.mediaTransition.to !== void 0 || expandedMediaRef.current === null) {
826
827
  return;
827
828
  }
828
- measureOpeningTarget(expandedMediaRef.current);
829
- }, [measureOpeningTarget, state.mediaTransition]);
830
- const setIndex = useCallback3(
831
- (nextIndex) => {
832
- if (!isControlled) {
833
- dispatch({ index: nextIndex, type: "set-index" });
834
- }
835
- onIndexChange?.(nextIndex);
836
- },
837
- [isControlled, onIndexChange]
838
- );
839
- const openAtIndex = useCallback3(
840
- (thumbIndex) => {
841
- const sourceRect = rectFromElement(thumbnailMap.get(thumbIndex) ?? null);
842
- const item = media[thumbIndex];
843
- const transition = sourceRect && item ? { from: sourceRect, item, phase: "opening" } : null;
844
- dispatch({ index: thumbIndex, transition, type: "open-at-index" });
845
- onIndexChange?.(thumbIndex);
846
- },
847
- [media, onIndexChange, thumbnailMap]
848
- );
849
- const startClose = useCallback3(() => {
829
+ const targetRect = rectFromElement(expandedMediaRef.current);
830
+ if (targetRect !== null) {
831
+ dispatch({ rect: targetRect, type: "complete-opening-target" });
832
+ }
833
+ }, [dispatch, state.mediaTransition]);
834
+ const openAtIndex = (thumbIndex) => {
835
+ const sourceRect = rectFromElement(thumbnailMap.get(thumbIndex) ?? null);
836
+ const item = media[thumbIndex] ?? null;
837
+ const transition = sourceRect === null || item === null ? null : { from: sourceRect, item, phase: "opening" };
838
+ dispatch({ index: thumbIndex, transition, type: "open-at-index" });
839
+ onIndexChange?.(thumbIndex);
840
+ };
841
+ const startClose = () => {
850
842
  if (!state.open || state.closing) {
851
843
  return;
852
844
  }
853
845
  const sourceRect = rectFromElement(expandedMediaRef.current);
854
846
  const targetRect = rectFromElement(thumbnailMap.get(index) ?? null);
855
- if (!activeMedia || !sourceRect || !targetRect) {
847
+ if (activeMedia === void 0 || sourceRect === null || targetRect === null) {
856
848
  dispatch({ type: "close-without-transition" });
857
849
  return;
858
850
  }
859
851
  dispatch({
860
- transition: {
861
- from: sourceRect,
862
- item: activeMedia,
863
- phase: "closing",
864
- to: targetRect
865
- },
852
+ transition: { from: sourceRect, item: activeMedia, phase: "closing", to: targetRect },
866
853
  type: "start-close"
867
854
  });
868
- }, [activeMedia, index, state.closing, state.open, thumbnailMap]);
869
- const handleCloseAutoFocus = useCallback3(
870
- (event) => {
871
- event.preventDefault();
872
- thumbnailMap.get(index)?.focus({ preventScroll: true });
873
- },
874
- [index, thumbnailMap]
875
- );
876
- const handleMediaTransitionComplete = useCallback3(() => {
855
+ };
856
+ const handleCloseAutoFocus = (event) => {
857
+ event.preventDefault();
858
+ thumbnailMap.get(index)?.focus({ preventScroll: true });
859
+ };
860
+ return { handleCloseAutoFocus, openAtIndex, registerThumbnail, setExpandedMediaNode, startClose };
861
+ };
862
+ var useApertoGroupController = ({
863
+ classNames,
864
+ index: controlledIndex,
865
+ initialIndex = 0,
866
+ media,
867
+ onIndexChange,
868
+ renderImage,
869
+ renderVideo
870
+ }) => {
871
+ const generatedId = useId2();
872
+ const [state, dispatch] = useReducer(apertoGroupReducer, initialIndex, getInitialGroupState);
873
+ const isControlled = controlledIndex !== void 0;
874
+ const index = isControlled ? controlledIndex : state.internalIndex;
875
+ const activeMedia = media[index] ?? media[0];
876
+ const lifecycle = useApertoMediaLifecycle({
877
+ activeMedia,
878
+ dispatch,
879
+ index,
880
+ media,
881
+ onIndexChange,
882
+ state
883
+ });
884
+ const sharedLayoutIdForIndex = (nextIndex) => `aperto-group-${generatedId}-${nextIndex}-shared`;
885
+ const sharedLayoutId = sharedLayoutIdForIndex(state.layoutSourceIndex);
886
+ const setIndex = (nextIndex) => {
887
+ if (!isControlled) {
888
+ dispatch({ index: nextIndex, type: "set-index" });
889
+ }
890
+ onIndexChange?.(nextIndex);
891
+ };
892
+ const handleMediaTransitionComplete = () => {
877
893
  dispatch({ type: "finish-transition" });
878
- }, []);
879
- const handleOpenChange = useCallback3(
880
- (nextOpen) => {
881
- if (nextOpen) {
882
- dispatch({ open: true, type: "set-open" });
883
- return;
884
- }
885
- startClose();
886
- },
887
- [startClose]
888
- );
889
- const value = useMemo3(
890
- () => ({
891
- classNames,
892
- index,
893
- media,
894
- open: state.open,
895
- openAtIndex,
896
- registerThumbnail,
897
- renderImage,
898
- renderVideo,
899
- setIndex,
900
- sharedLayoutId,
901
- sharedLayoutIdForIndex
902
- }),
903
- [
904
- classNames,
905
- index,
906
- media,
907
- state.open,
908
- openAtIndex,
909
- registerThumbnail,
910
- renderImage,
911
- renderVideo,
912
- setIndex,
913
- sharedLayoutId,
914
- sharedLayoutIdForIndex
915
- ]
916
- );
894
+ };
895
+ const handleOpenChange = (nextOpen) => {
896
+ if (nextOpen) {
897
+ dispatch({ open: true, type: "set-open" });
898
+ return;
899
+ }
900
+ lifecycle.startClose();
901
+ };
902
+ const value = {
903
+ classNames,
904
+ index,
905
+ media,
906
+ open: state.open,
907
+ openAtIndex: lifecycle.openAtIndex,
908
+ registerThumbnail: lifecycle.registerThumbnail,
909
+ renderImage,
910
+ renderVideo,
911
+ setIndex,
912
+ sharedLayoutId,
913
+ sharedLayoutIdForIndex
914
+ };
917
915
  const hasNavigation = media.length > 1;
918
- const navigateToIndex = useCallback3(
919
- (nextIndex, direction) => {
920
- dispatch({ direction, index: nextIndex, type: "navigate" });
921
- onIndexChange?.(nextIndex);
922
- },
923
- [onIndexChange]
924
- );
925
- const goToPrevious = useCallback3(() => {
916
+ const navigateToIndex = (nextIndex, direction) => {
917
+ dispatch({ direction, index: nextIndex, type: "navigate" });
918
+ onIndexChange?.(nextIndex);
919
+ };
920
+ const goToPrevious = () => {
926
921
  navigateToIndex((index - 1 + media.length) % media.length, -1);
927
- }, [index, media.length, navigateToIndex]);
928
- const goToNext = useCallback3(() => {
922
+ };
923
+ const goToNext = () => {
929
924
  navigateToIndex((index + 1) % media.length, 1);
930
- }, [index, media.length, navigateToIndex]);
931
- const handleContentKeyDown = useCallback3(
932
- (event) => {
933
- if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || shouldIgnoreKeyboardNavigationTarget(event.target)) {
934
- return;
935
- }
936
- if (event.key === "ArrowLeft") {
937
- event.preventDefault();
938
- goToPrevious();
939
- }
940
- if (event.key === "ArrowRight") {
941
- event.preventDefault();
942
- goToNext();
943
- }
944
- },
945
- [goToNext, goToPrevious]
946
- );
947
- const expandedMediaStyle = useMemo3(() => {
948
- if (!activeMedia?.width || !activeMedia.height) {
949
- return void 0;
925
+ };
926
+ const handleContentKeyDown = (event) => {
927
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || shouldIgnoreKeyboardNavigationTarget(event.target)) {
928
+ return;
950
929
  }
951
- return {
952
- "--aperto-expanded-aspect-ratio": `${activeMedia.width} / ${activeMedia.height}`,
953
- "--aperto-expanded-aspect-ratio-height": activeMedia.height,
954
- "--aperto-expanded-aspect-ratio-width": activeMedia.width
955
- };
956
- }, [activeMedia?.height, activeMedia?.width]);
957
- return /* @__PURE__ */ jsx10(ApertoGroupContext.Provider, { value, children: /* @__PURE__ */ jsxs(
930
+ if (event.key === "ArrowLeft") {
931
+ event.preventDefault();
932
+ goToPrevious();
933
+ }
934
+ if (event.key === "ArrowRight") {
935
+ event.preventDefault();
936
+ goToNext();
937
+ }
938
+ };
939
+ const expandedMediaStyle = getExpandedMediaStyle(activeMedia);
940
+ return {
941
+ activeMedia,
942
+ expandedMediaStyle,
943
+ goToNext,
944
+ goToPrevious,
945
+ handleCloseAutoFocus: lifecycle.handleCloseAutoFocus,
946
+ handleContentKeyDown,
947
+ handleMediaTransitionComplete,
948
+ handleOpenChange,
949
+ hasNavigation,
950
+ index,
951
+ setExpandedMediaNode: lifecycle.setExpandedMediaNode,
952
+ startClose: lifecycle.startClose,
953
+ state,
954
+ value
955
+ };
956
+ };
957
+ var ApertoGroup = ({
958
+ children,
959
+ classNames,
960
+ dismissible,
961
+ index,
962
+ initialIndex,
963
+ media,
964
+ motion: motionProp,
965
+ navigationMotion = "glide",
966
+ onIndexChange,
967
+ renderImage,
968
+ renderVideo
969
+ }) => {
970
+ const controller = useApertoGroupController({
971
+ classNames,
972
+ index,
973
+ initialIndex,
974
+ media,
975
+ onIndexChange,
976
+ renderImage,
977
+ renderVideo
978
+ });
979
+ return /* @__PURE__ */ jsx10(ApertoGroupContext.Provider, { value: controller.value, children: /* @__PURE__ */ jsxs(
958
980
  ApertoRoot,
959
981
  {
960
982
  dismissible,
961
983
  motion: motionProp,
962
- onOpenChange: handleOpenChange,
963
- open: state.open,
984
+ onOpenChange: controller.handleOpenChange,
985
+ open: controller.state.open,
964
986
  children: [
965
987
  children,
966
- activeMedia ? /* @__PURE__ */ jsxs(ApertoPortal, { children: [
967
- /* @__PURE__ */ jsx10(
968
- ApertoOverlay,
969
- {
970
- className: classNames?.overlay,
971
- fadeOut: state.closing
972
- }
973
- ),
974
- /* @__PURE__ */ jsxs(
975
- ApertoContent,
976
- {
977
- className: classNames?.content,
978
- "data-aperto-transition": state.mediaTransition?.phase,
979
- onCloseAutoFocus: handleCloseAutoFocus,
980
- onKeyDown: hasNavigation ? handleContentKeyDown : void 0,
981
- sharedLayoutId: false,
982
- ...getDescriptionProps(activeMedia),
983
- children: [
984
- /* @__PURE__ */ jsxs(
985
- "div",
986
- {
987
- "data-slot": "aperto-media",
988
- ref: setExpandedMediaNode,
989
- style: expandedMediaStyle,
990
- children: [
991
- /* @__PURE__ */ jsx10(
992
- ApertoExpandedMediaStage,
993
- {
994
- direction: state.navigationDirection,
995
- index,
996
- item: activeMedia,
997
- navigationMotion,
998
- renderImage,
999
- renderVideo
1000
- }
1001
- ),
1002
- /* @__PURE__ */ jsx10(
1003
- ApertoMediaNavigationButtons,
1004
- {
1005
- classNames,
1006
- enabled: hasNavigation,
1007
- onNext: goToNext,
1008
- onPrevious: goToPrevious
1009
- }
1010
- )
1011
- ]
1012
- }
1013
- ),
1014
- /* @__PURE__ */ jsx10(ApertoTitle, { children: activeMedia.title ?? getMediaLabel(activeMedia) }),
1015
- activeMedia.description ? /* @__PURE__ */ jsx10(ApertoDescription, { children: activeMedia.description }) : null,
1016
- hasNavigation ? /* @__PURE__ */ jsxs("div", { className: classNames?.counter, "data-slot": "aperto-counter", children: [
1017
- index + 1,
1018
- " / ",
1019
- media.length
1020
- ] }) : null,
1021
- /* @__PURE__ */ jsx10(
1022
- "button",
1023
- {
1024
- "aria-label": "Close",
1025
- className: classNames?.closeButton,
1026
- "data-slot": "aperto-close",
1027
- onClick: startClose,
1028
- type: "button",
1029
- children: /* @__PURE__ */ jsx10(X, { "aria-hidden": "true", size: 17, strokeWidth: 2 })
1030
- }
1031
- )
1032
- ]
1033
- }
1034
- ),
1035
- /* @__PURE__ */ jsx10(
1036
- ApertoMediaTransitionClone,
1037
- {
1038
- onComplete: handleMediaTransitionComplete,
1039
- renderImage,
1040
- renderVideo,
1041
- transition: state.mediaTransition
1042
- }
1043
- )
1044
- ] }) : null
988
+ /* @__PURE__ */ jsx10(
989
+ ApertoGroupPortal,
990
+ {
991
+ activeMedia: controller.activeMedia,
992
+ classNames,
993
+ expandedMediaStyle: controller.expandedMediaStyle,
994
+ goToNext: controller.goToNext,
995
+ goToPrevious: controller.goToPrevious,
996
+ handleCloseAutoFocus: controller.handleCloseAutoFocus,
997
+ handleContentKeyDown: controller.hasNavigation ? controller.handleContentKeyDown : void 0,
998
+ handleMediaTransitionComplete: controller.handleMediaTransitionComplete,
999
+ hasNavigation: controller.hasNavigation,
1000
+ index: controller.index,
1001
+ mediaLength: media.length,
1002
+ navigationDirection: controller.state.navigationDirection,
1003
+ navigationMotion,
1004
+ renderImage,
1005
+ renderVideo,
1006
+ setExpandedMediaNode: controller.setExpandedMediaNode,
1007
+ startClose: controller.startClose,
1008
+ transition: controller.state.mediaTransition
1009
+ }
1010
+ )
1045
1011
  ]
1046
1012
  }
1047
1013
  ) });
1048
- }
1049
- function ApertoMediaNavigationButtons({
1050
- classNames,
1051
- enabled,
1052
- onNext,
1053
- onPrevious
1054
- }) {
1055
- if (!enabled) {
1056
- return null;
1057
- }
1058
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1059
- /* @__PURE__ */ jsx10(
1060
- "button",
1061
- {
1062
- "aria-label": "Previous media",
1063
- className: classNames?.previousButton,
1064
- "data-slot": "aperto-previous-button",
1065
- onClick: onPrevious,
1066
- type: "button",
1067
- children: /* @__PURE__ */ jsx10(ChevronLeft, { "aria-hidden": "true", size: 18, strokeWidth: 2 })
1068
- }
1069
- ),
1070
- /* @__PURE__ */ jsx10(
1071
- "button",
1072
- {
1073
- "aria-label": "Next media",
1074
- className: classNames?.nextButton,
1075
- "data-slot": "aperto-next-button",
1076
- onClick: onNext,
1077
- type: "button",
1078
- children: /* @__PURE__ */ jsx10(ChevronRight, { "aria-hidden": "true", size: 18, strokeWidth: 2 })
1079
- }
1080
- )
1081
- ] });
1082
- }
1014
+ };
1083
1015
 
1084
1016
  // src/aperto-close.tsx
1085
1017
  import * as Dialog7 from "@radix-ui/react-dialog";
1086
1018
  import { X as X2 } from "lucide-react";
1087
- import { forwardRef as forwardRef5 } from "react";
1088
1019
  import { jsx as jsx11 } from "react/jsx-runtime";
1089
- var ApertoClose = forwardRef5(({ children, ...props }, ref) => {
1090
- return /* @__PURE__ */ jsx11(Dialog7.Close, { "data-slot": "aperto-close", ref, ...props, children: children ?? /* @__PURE__ */ jsx11(X2, { "aria-hidden": "true", size: 17, strokeWidth: 2 }) });
1091
- });
1020
+ var ApertoClose = ({ children, ref, ...props }) => /* @__PURE__ */ jsx11(Dialog7.Close, { "data-slot": "aperto-close", ref, ...props, children: children ?? /* @__PURE__ */ jsx11(X2, { "aria-hidden": "true", size: 17, strokeWidth: 2 }) });
1092
1021
  ApertoClose.displayName = "ApertoClose";
1093
1022
 
1094
1023
  // src/aperto-trigger.tsx
1095
1024
  import * as Dialog8 from "@radix-ui/react-dialog";
1096
1025
  import { m as m5 } from "motion/react";
1097
- import { forwardRef as forwardRef6 } from "react";
1098
1026
  import { jsx as jsx12 } from "react/jsx-runtime";
1099
1027
  var TRIGGER_OPEN_Z_INDEX = 1e3;
1100
- var ApertoTrigger = forwardRef6(
1101
- ({ active, children, motion: motionOverride, sharedLayoutId, ...props }, ref) => {
1102
- const ctx = useApertoContext();
1103
- const shouldRaise = ctx.open && (active ?? true);
1104
- const zIndex = shouldRaise ? TRIGGER_OPEN_Z_INDEX : 0;
1105
- const resolved = resolvePreset(
1106
- "trigger",
1107
- motionOverride,
1108
- ctx.presetName,
1109
- ctx.variants,
1110
- ctx.reduceMotion
1111
- );
1112
- const layoutId = sharedLayoutId === false ? void 0 : sharedLayoutId ?? `${ctx.layoutId}-shared`;
1113
- return /* @__PURE__ */ jsx12(Dialog8.Trigger, { asChild: true, ref, ...props, children: /* @__PURE__ */ jsx12(
1114
- m5.button,
1115
- {
1116
- "data-aperto-active": shouldRaise ? "" : void 0,
1117
- "data-slot": "aperto-trigger",
1118
- layout: true,
1119
- layoutCrossfade: false,
1120
- layoutId,
1121
- style: { zIndex },
1122
- transition: resolved.transition,
1123
- type: "button",
1124
- children
1125
- },
1126
- layoutId ?? "unshared"
1127
- ) });
1128
- }
1129
- );
1028
+ var ApertoTrigger = ({
1029
+ active,
1030
+ children,
1031
+ motion: motionOverride,
1032
+ ref,
1033
+ sharedLayoutId,
1034
+ ...props
1035
+ }) => {
1036
+ const ctx = useApertoContext();
1037
+ const shouldRaise = ctx.open && (active ?? true);
1038
+ const zIndex = shouldRaise ? TRIGGER_OPEN_Z_INDEX : 0;
1039
+ const resolved = resolvePreset(
1040
+ "trigger",
1041
+ motionOverride,
1042
+ ctx.presetName,
1043
+ ctx.variants,
1044
+ ctx.reduceMotion
1045
+ );
1046
+ const layoutId = sharedLayoutId === false ? void 0 : sharedLayoutId ?? `${ctx.layoutId}-shared`;
1047
+ return /* @__PURE__ */ jsx12(Dialog8.Trigger, { asChild: true, ref, ...props, children: /* @__PURE__ */ jsx12(
1048
+ m5.button,
1049
+ {
1050
+ "data-aperto-active": shouldRaise ? "" : void 0,
1051
+ "data-slot": "aperto-trigger",
1052
+ layout: true,
1053
+ layoutCrossfade: false,
1054
+ layoutId,
1055
+ style: { zIndex },
1056
+ transition: resolved.transition,
1057
+ type: "button",
1058
+ children
1059
+ },
1060
+ layoutId ?? "unshared"
1061
+ ) });
1062
+ };
1130
1063
  ApertoTrigger.displayName = "ApertoTrigger";
1131
1064
 
1132
- // src/Aperto/ApertoSingle.tsx
1065
+ // src/Aperto/aperto-single.tsx
1133
1066
  import { jsx as jsx13, jsxs as jsxs2 } from "react/jsx-runtime";
1134
- function ApertoSingle({
1067
+ var ApertoSingle = ({
1135
1068
  classNames,
1136
1069
  dismissible,
1137
1070
  media,
1138
1071
  renderImage,
1139
1072
  renderVideo
1140
- }) {
1073
+ }) => {
1141
1074
  const label = getMediaLabel(media);
1142
1075
  const title = media.title ?? label;
1143
1076
  return /* @__PURE__ */ jsxs2(ApertoRoot, { dismissible, children: [
1144
- /* @__PURE__ */ jsx13(
1145
- ApertoTrigger,
1146
- {
1147
- "aria-label": `Open ${label}`,
1148
- className: classNames?.thumbnail,
1149
- children: /* @__PURE__ */ jsx13(
1150
- ApertoThumbnailMedia,
1151
- {
1152
- item: media,
1153
- renderImage,
1154
- renderVideo
1155
- }
1156
- )
1157
- }
1158
- ),
1077
+ /* @__PURE__ */ jsx13(ApertoTrigger, { "aria-label": `Open ${label}`, className: classNames?.thumbnail, children: /* @__PURE__ */ jsx13(ApertoThumbnailMedia, { item: media, renderImage, renderVideo }) }),
1159
1078
  /* @__PURE__ */ jsxs2(ApertoPortal, { children: [
1160
1079
  /* @__PURE__ */ jsx13(ApertoOverlay, { className: classNames?.overlay }),
1161
- /* @__PURE__ */ jsxs2(
1162
- ApertoContent,
1163
- {
1164
- className: classNames?.content,
1165
- ...getDescriptionProps(media),
1166
- children: [
1167
- /* @__PURE__ */ jsx13(
1168
- ApertoExpandedMedia,
1169
- {
1170
- item: media,
1171
- renderImage,
1172
- renderVideo
1173
- }
1174
- ),
1175
- /* @__PURE__ */ jsx13(ApertoTitle, { children: title }),
1176
- media.description ? /* @__PURE__ */ jsx13(ApertoDescription, { children: media.description }) : null,
1177
- /* @__PURE__ */ jsx13(
1178
- ApertoClose,
1179
- {
1180
- "aria-label": "Close",
1181
- className: classNames?.closeButton,
1182
- type: "button"
1183
- }
1184
- )
1185
- ]
1186
- }
1187
- )
1080
+ /* @__PURE__ */ jsxs2(ApertoContent, { className: classNames?.content, ...getDescriptionProps(media), children: [
1081
+ /* @__PURE__ */ jsx13(ApertoExpandedMedia, { item: media, renderImage, renderVideo }),
1082
+ /* @__PURE__ */ jsx13(ApertoTitle, { children: title }),
1083
+ media.description === void 0 || media.description === "" ? null : /* @__PURE__ */ jsx13(ApertoDescription, { children: media.description }),
1084
+ /* @__PURE__ */ jsx13(ApertoClose, { "aria-label": "Close", className: classNames?.closeButton, type: "button" })
1085
+ ] })
1188
1086
  ] })
1189
1087
  ] });
1190
- }
1088
+ };
1191
1089
 
1192
- // src/Aperto/ApertoThumbnail.tsx
1090
+ // src/Aperto/aperto-thumbnail.tsx
1193
1091
  import { jsx as jsx14 } from "react/jsx-runtime";
1194
- function ApertoThumbnail({
1195
- children,
1196
- className,
1197
- index
1198
- }) {
1092
+ var ApertoThumbnail = ({ children, className, index }) => {
1199
1093
  const group = useApertoGroup();
1200
1094
  const media = group.media[index];
1201
1095
  if (!media) {
@@ -1207,8 +1101,12 @@ function ApertoThumbnail({
1207
1101
  active: group.index === index,
1208
1102
  "aria-label": `Open ${getMediaLabel(media)}`,
1209
1103
  className: className ?? group.classNames?.thumbnail,
1210
- onClick: () => group.openAtIndex(index),
1211
- ref: (node) => group.registerThumbnail(index, node),
1104
+ onClick: () => {
1105
+ group.openAtIndex(index);
1106
+ },
1107
+ ref: (node) => {
1108
+ group.registerThumbnail(index, node);
1109
+ },
1212
1110
  sharedLayoutId: false,
1213
1111
  children: children ?? /* @__PURE__ */ jsx14(
1214
1112
  ApertoThumbnailMedia,
@@ -1220,7 +1118,7 @@ function ApertoThumbnail({
1220
1118
  )
1221
1119
  }
1222
1120
  );
1223
- }
1121
+ };
1224
1122
 
1225
1123
  // src/index.ts
1226
1124
  var ApertoPrimitive = {