@capgo/capacitor-transitions 8.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.
@@ -0,0 +1,2244 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/solid/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ TransitionController: () => TransitionController,
24
+ createTransitionNavigate: () => createTransitionNavigate,
25
+ getController: () => getController,
26
+ getDirection: () => getDirection,
27
+ initTransitions: () => initTransitions,
28
+ setDirection: () => setDirection,
29
+ setupPage: () => setupPage,
30
+ setupRouterOutlet: () => setupRouterOutlet
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/core/animations.ts
35
+ var IOS_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";
36
+ var ANDROID_EASING = "cubic-bezier(0.36, 0.66, 0.04, 1)";
37
+ var ANDROID_BACK_EASING = "cubic-bezier(0.47, 0, 0.745, 0.715)";
38
+ var IOS_DURATION = 540;
39
+ var ANDROID_DURATION = 280;
40
+ var ANDROID_BACK_DURATION = 200;
41
+ var IOS_OFF_OPACITY = 0.8;
42
+ var IOS_CENTER = "0%";
43
+ var IOS_OFF_RIGHT = "99.5%";
44
+ var IOS_OFF_LEFT = "-33%";
45
+ var IOS_OFF_RIGHT_RTL = "-99.5%";
46
+ var IOS_OFF_LEFT_RTL = "33%";
47
+ var MD_OFF_BOTTOM = "40px";
48
+ var MD_CENTER = "0px";
49
+ function resolveEasing(easing) {
50
+ switch (easing) {
51
+ case "ios":
52
+ return IOS_EASING;
53
+ case "android":
54
+ return ANDROID_EASING;
55
+ case "linear":
56
+ return "linear";
57
+ case "ease":
58
+ return "ease";
59
+ case "ease-in":
60
+ return "ease-in";
61
+ case "ease-out":
62
+ return "ease-out";
63
+ case "ease-in-out":
64
+ return "ease-in-out";
65
+ default:
66
+ return easing;
67
+ }
68
+ }
69
+ function detectPlatform() {
70
+ if (typeof navigator === "undefined") return "ios";
71
+ const ua = navigator.userAgent.toLowerCase();
72
+ if (/iphone|ipad|ipod/.test(ua)) return "ios";
73
+ if (/android/.test(ua)) return "android";
74
+ return "ios";
75
+ }
76
+ function getDefaultDuration(platform, direction = "forward") {
77
+ const resolved = platform === "auto" ? detectPlatform() : platform;
78
+ if (resolved === "ios") {
79
+ return IOS_DURATION;
80
+ }
81
+ return direction === "back" ? ANDROID_BACK_DURATION : ANDROID_DURATION;
82
+ }
83
+ function getDefaultEasing(platform, direction = "forward") {
84
+ const resolved = platform === "auto" ? detectPlatform() : platform;
85
+ if (resolved === "ios") {
86
+ return IOS_EASING;
87
+ }
88
+ return direction === "back" ? ANDROID_BACK_EASING : ANDROID_EASING;
89
+ }
90
+ function getDocumentDirection(element) {
91
+ const doc = element.ownerDocument;
92
+ return doc.dir === "rtl" || doc.documentElement.dir === "rtl" ? "rtl" : "ltr";
93
+ }
94
+ function preparePageLayer(element, zIndex) {
95
+ element.classList.add("cap-transition-active");
96
+ element.style.display = "";
97
+ element.style.visibility = "visible";
98
+ element.style.position = "absolute";
99
+ element.style.top = "0";
100
+ element.style.left = "0";
101
+ element.style.width = "100%";
102
+ element.style.height = "100%";
103
+ element.style.zIndex = zIndex;
104
+ element.style.pointerEvents = "none";
105
+ element.style.willChange = "transform, opacity";
106
+ element.style.backfaceVisibility = "hidden";
107
+ element.style.transformStyle = "preserve-3d";
108
+ }
109
+ function createAnimation(element, keyframes, duration, easing) {
110
+ return element.animate(keyframes, {
111
+ duration,
112
+ easing,
113
+ fill: "both"
114
+ });
115
+ }
116
+ function resolvePageContents(element) {
117
+ return Array.from(
118
+ element.querySelectorAll(
119
+ ':scope > [data-cap-content], :scope > .cap-content, :scope > cap-content, :scope > [slot="content"]'
120
+ )
121
+ );
122
+ }
123
+ function resolvePageChrome(element) {
124
+ return Array.from(
125
+ element.querySelectorAll(
126
+ ':scope > [data-cap-header], :scope > .cap-header, :scope > cap-header, :scope > [slot="header"], :scope > [data-cap-footer], :scope > .cap-footer, :scope > cap-footer, :scope > [slot="footer"]'
127
+ )
128
+ );
129
+ }
130
+ function resolvePageHeaders(element) {
131
+ return Array.from(
132
+ element.querySelectorAll(
133
+ ':scope > [data-cap-header], :scope > .cap-header, :scope > cap-header, :scope > [slot="header"]'
134
+ )
135
+ );
136
+ }
137
+ function invertTranslateOffset(offset) {
138
+ if (offset === IOS_CENTER) {
139
+ return IOS_CENTER;
140
+ }
141
+ return offset.startsWith("-") ? offset.slice(1) : `-${offset}`;
142
+ }
143
+ function createPinnedChromeAnimations(elements, fromTransform, toTransform, duration, easing) {
144
+ return elements.map((element) => {
145
+ element.style.willChange = "transform";
146
+ element.style.backfaceVisibility = "hidden";
147
+ return createAnimation(element, [{ transform: fromTransform }, { transform: toTransform }], duration, easing);
148
+ });
149
+ }
150
+ function uniqueElements(elements) {
151
+ return Array.from(new Set(elements));
152
+ }
153
+ function resolveToolbarItems(header) {
154
+ const toolbarItems = Array.from(
155
+ header.querySelectorAll(
156
+ ':scope > [data-cap-toolbar] > *, :scope > .toolbar > *, :scope > [role="toolbar"] > *, :scope > ion-toolbar > *'
157
+ )
158
+ );
159
+ const directItems = Array.from(
160
+ header.querySelectorAll(
161
+ ":scope > [data-cap-toolbar-item], :scope > .cap-toolbar-item, :scope > h1, :scope > h2, :scope > h3, :scope > button, :scope > a"
162
+ )
163
+ );
164
+ return uniqueElements(
165
+ [...toolbarItems, ...directItems].filter(
166
+ (element) => !element.matches('[data-cap-toolbar-background], .toolbar-background, [aria-hidden="true"]') && element.tagName !== "STYLE" && element.tagName !== "SCRIPT"
167
+ )
168
+ );
169
+ }
170
+ function createOpacityAnimations(elements, fromOpacity, toOpacity, duration, easing) {
171
+ return elements.map((element) => {
172
+ element.style.willChange = "opacity";
173
+ element.style.backfaceVisibility = "hidden";
174
+ return createAnimation(element, [{ opacity: fromOpacity }, { opacity: toOpacity }], duration, easing);
175
+ });
176
+ }
177
+ function createToolbarItemAnimations(headers, fromTransform, toTransform, fromOpacity, toOpacity, duration, easing) {
178
+ return headers.flatMap(
179
+ (header) => resolveToolbarItems(header).map((element) => {
180
+ element.style.willChange = "transform, opacity";
181
+ element.style.backfaceVisibility = "hidden";
182
+ return createAnimation(
183
+ element,
184
+ [
185
+ { transform: fromTransform, opacity: fromOpacity },
186
+ { transform: toTransform, opacity: toOpacity }
187
+ ],
188
+ duration,
189
+ easing
190
+ );
191
+ })
192
+ );
193
+ }
194
+ function createIOSTransition(options) {
195
+ const { enteringEl, leavingEl, direction, duration, easing } = options;
196
+ const animations = [];
197
+ const isBack = direction === "back";
198
+ const isRoot = direction === "root";
199
+ const isRTL = getDocumentDirection(enteringEl) === "rtl";
200
+ const offRight = isRTL ? IOS_OFF_RIGHT_RTL : IOS_OFF_RIGHT;
201
+ const offLeft = isRTL ? IOS_OFF_LEFT_RTL : IOS_OFF_LEFT;
202
+ const chromeOffRight = invertTranslateOffset(offRight);
203
+ const chromeOffLeft = invertTranslateOffset(offLeft);
204
+ const leadingEdgeShadow = isRTL ? "8px 0 24px rgba(0, 0, 0, 0.18)" : "-8px 0 24px rgba(0, 0, 0, 0.18)";
205
+ const enteringContent = resolvePageContents(enteringEl);
206
+ const enteringHeaders = resolvePageHeaders(enteringEl);
207
+ const leavingContent = leavingEl ? resolvePageContents(leavingEl) : [];
208
+ const leavingHeaders = leavingEl ? resolvePageHeaders(leavingEl) : [];
209
+ preparePageLayer(enteringEl, isBack ? "99" : "101");
210
+ if (leavingEl) {
211
+ preparePageLayer(leavingEl, "100");
212
+ }
213
+ if (isRoot) {
214
+ animations.push(
215
+ createAnimation(
216
+ enteringEl,
217
+ [
218
+ { opacity: 0.01, transform: "translate3d(0, 0, 0)" },
219
+ { opacity: 1, transform: "translate3d(0, 0, 0)" }
220
+ ],
221
+ duration,
222
+ easing
223
+ )
224
+ );
225
+ if (leavingEl) {
226
+ animations.push(
227
+ createAnimation(
228
+ leavingEl,
229
+ [
230
+ { opacity: 1, transform: "translate3d(0, 0, 0)" },
231
+ { opacity: 0, transform: "translate3d(0, 0, 0)" }
232
+ ],
233
+ Math.min(duration, 240),
234
+ easing
235
+ )
236
+ );
237
+ }
238
+ } else if (isBack) {
239
+ animations.push(
240
+ ...createPinnedChromeAnimations(
241
+ resolvePageChrome(enteringEl),
242
+ `translate3d(${chromeOffLeft}, 0, 0)`,
243
+ `translate3d(${IOS_CENTER}, 0, 0)`,
244
+ duration,
245
+ easing
246
+ )
247
+ );
248
+ animations.push(
249
+ createAnimation(
250
+ enteringEl,
251
+ [{ transform: `translate3d(${offLeft}, 0, 0)` }, { transform: `translate3d(${IOS_CENTER}, 0, 0)` }],
252
+ duration,
253
+ easing
254
+ )
255
+ );
256
+ animations.push(...createOpacityAnimations(enteringContent, IOS_OFF_OPACITY, 1, duration, easing));
257
+ animations.push(
258
+ ...createToolbarItemAnimations(
259
+ enteringHeaders,
260
+ `translate3d(${offLeft}, 0, 0)`,
261
+ `translate3d(${IOS_CENTER}, 0, 0)`,
262
+ 0.01,
263
+ 1,
264
+ duration,
265
+ easing
266
+ )
267
+ );
268
+ if (leavingEl) {
269
+ leavingEl.style.boxShadow = leadingEdgeShadow;
270
+ animations.push(
271
+ ...createPinnedChromeAnimations(
272
+ resolvePageChrome(leavingEl),
273
+ `translate3d(${IOS_CENTER}, 0, 0)`,
274
+ `translate3d(${chromeOffRight}, 0, 0)`,
275
+ duration,
276
+ easing
277
+ )
278
+ );
279
+ animations.push(
280
+ createAnimation(
281
+ leavingEl,
282
+ [{ transform: `translate3d(${IOS_CENTER}, 0, 0)` }, { transform: `translate3d(${offRight}, 0, 0)` }],
283
+ duration,
284
+ easing
285
+ )
286
+ );
287
+ animations.push(
288
+ ...createToolbarItemAnimations(
289
+ leavingHeaders,
290
+ `translate3d(${IOS_CENTER}, 0, 0)`,
291
+ `translate3d(${offRight}, 0, 0)`,
292
+ 0.99,
293
+ 0,
294
+ duration,
295
+ easing
296
+ )
297
+ );
298
+ }
299
+ } else {
300
+ enteringEl.style.boxShadow = leadingEdgeShadow;
301
+ animations.push(
302
+ ...createPinnedChromeAnimations(
303
+ resolvePageChrome(enteringEl),
304
+ `translate3d(${chromeOffRight}, 0, 0)`,
305
+ `translate3d(${IOS_CENTER}, 0, 0)`,
306
+ duration,
307
+ easing
308
+ )
309
+ );
310
+ animations.push(
311
+ createAnimation(
312
+ enteringEl,
313
+ [{ transform: `translate3d(${offRight}, 0, 0)` }, { transform: `translate3d(${IOS_CENTER}, 0, 0)` }],
314
+ duration,
315
+ easing
316
+ )
317
+ );
318
+ animations.push(
319
+ ...createToolbarItemAnimations(
320
+ enteringHeaders,
321
+ `translate3d(${offRight}, 0, 0)`,
322
+ `translate3d(${IOS_CENTER}, 0, 0)`,
323
+ 0.01,
324
+ 1,
325
+ duration,
326
+ easing
327
+ )
328
+ );
329
+ if (leavingEl) {
330
+ animations.push(
331
+ ...createPinnedChromeAnimations(
332
+ resolvePageChrome(leavingEl),
333
+ `translate3d(${IOS_CENTER}, 0, 0)`,
334
+ `translate3d(${chromeOffLeft}, 0, 0)`,
335
+ duration,
336
+ easing
337
+ )
338
+ );
339
+ animations.push(
340
+ createAnimation(
341
+ leavingEl,
342
+ [{ transform: `translate3d(${IOS_CENTER}, 0, 0)` }, { transform: `translate3d(${offLeft}, 0, 0)` }],
343
+ duration,
344
+ easing
345
+ )
346
+ );
347
+ animations.push(...createOpacityAnimations(leavingContent, 1, IOS_OFF_OPACITY, duration, easing));
348
+ animations.push(
349
+ ...createToolbarItemAnimations(
350
+ leavingHeaders,
351
+ `translate3d(${IOS_CENTER}, 0, 0)`,
352
+ `translate3d(${offLeft}, 0, 0)`,
353
+ 0.99,
354
+ 0,
355
+ duration,
356
+ easing
357
+ )
358
+ );
359
+ }
360
+ }
361
+ return animations;
362
+ }
363
+ function createAndroidTransition(options) {
364
+ const { enteringEl, leavingEl, direction, duration, easing } = options;
365
+ const animations = [];
366
+ const isBack = direction === "back";
367
+ const isRoot = direction === "root";
368
+ preparePageLayer(enteringEl, isBack ? "99" : "101");
369
+ if (leavingEl) {
370
+ preparePageLayer(leavingEl, "100");
371
+ }
372
+ if (isRoot) {
373
+ animations.push(
374
+ createAnimation(
375
+ enteringEl,
376
+ [
377
+ { opacity: 0.01, transform: `translate3d(0, ${MD_OFF_BOTTOM}, 0)` },
378
+ { opacity: 1, transform: `translate3d(0, ${MD_CENTER}, 0)` }
379
+ ],
380
+ duration,
381
+ easing
382
+ )
383
+ );
384
+ if (leavingEl) {
385
+ animations.push(
386
+ createAnimation(
387
+ leavingEl,
388
+ [
389
+ { opacity: 1, transform: `translate3d(0, ${MD_CENTER}, 0)` },
390
+ { opacity: 0, transform: `translate3d(0, ${MD_CENTER}, 0)` }
391
+ ],
392
+ Math.min(duration, ANDROID_BACK_DURATION),
393
+ ANDROID_BACK_EASING
394
+ )
395
+ );
396
+ }
397
+ } else if (isBack) {
398
+ enteringEl.style.opacity = "1";
399
+ enteringEl.style.transform = `translate3d(0, ${MD_CENTER}, 0)`;
400
+ if (leavingEl) {
401
+ animations.push(
402
+ createAnimation(
403
+ leavingEl,
404
+ [
405
+ { opacity: 1, transform: `translate3d(0, ${MD_CENTER}, 0)` },
406
+ { opacity: 0, transform: `translate3d(0, ${MD_OFF_BOTTOM}, 0)` }
407
+ ],
408
+ duration,
409
+ easing
410
+ )
411
+ );
412
+ }
413
+ } else {
414
+ animations.push(
415
+ createAnimation(
416
+ enteringEl,
417
+ [
418
+ { opacity: 0.01, transform: `translate3d(0, ${MD_OFF_BOTTOM}, 0)` },
419
+ { opacity: 1, transform: `translate3d(0, ${MD_CENTER}, 0)` }
420
+ ],
421
+ duration,
422
+ easing
423
+ )
424
+ );
425
+ if (leavingEl) {
426
+ leavingEl.style.opacity = "1";
427
+ leavingEl.style.transform = `translate3d(0, ${MD_CENTER}, 0)`;
428
+ }
429
+ }
430
+ return animations;
431
+ }
432
+ function createNoneTransition(options) {
433
+ const { enteringEl, leavingEl } = options;
434
+ enteringEl.style.opacity = "1";
435
+ enteringEl.style.transform = "none";
436
+ if (leavingEl) {
437
+ leavingEl.style.opacity = "0";
438
+ leavingEl.style.transform = "none";
439
+ }
440
+ return [];
441
+ }
442
+ function createTransition(options, platform = "auto") {
443
+ if (options.direction === "none") {
444
+ return createNoneTransition(options);
445
+ }
446
+ const resolved = platform === "auto" ? detectPlatform() : platform;
447
+ if (resolved === "android") {
448
+ return createAndroidTransition(options);
449
+ }
450
+ return createIOSTransition(options);
451
+ }
452
+ async function waitForAnimations(animations) {
453
+ if (animations.length === 0) return;
454
+ await Promise.all(animations.map((anim) => anim.finished.catch(() => void 0)));
455
+ }
456
+ function cancelAnimations(animations) {
457
+ animations.forEach((anim) => anim.cancel());
458
+ }
459
+
460
+ // src/core/view-transitions.ts
461
+ function supportsViewTransitions() {
462
+ return typeof document !== "undefined" && "startViewTransition" in document;
463
+ }
464
+ async function runViewTransition(options) {
465
+ const { update, direction = "forward", skipAnimation = false } = options;
466
+ if (skipAnimation || !supportsViewTransitions()) {
467
+ await update();
468
+ return;
469
+ }
470
+ const root = document.documentElement;
471
+ root.dataset.transitionDirection = direction;
472
+ try {
473
+ const transition = document.startViewTransition(async () => {
474
+ await update();
475
+ });
476
+ await transition.finished;
477
+ } finally {
478
+ delete root.dataset.transitionDirection;
479
+ }
480
+ }
481
+ function setViewTransitionName(element, name) {
482
+ element.style.viewTransitionName = name;
483
+ }
484
+ function clearViewTransitionName(element) {
485
+ element.style.viewTransitionName = "";
486
+ }
487
+ var VIEW_TRANSITIONS_CSS = `
488
+ /* View Transitions API base styles */
489
+ ::view-transition-old(root),
490
+ ::view-transition-new(root) {
491
+ animation-duration: 0.4s;
492
+ animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
493
+ }
494
+
495
+ /* iOS-style forward navigation */
496
+ [data-transition-direction="forward"]::view-transition-old(root) {
497
+ animation-name: cap-slide-out-left;
498
+ }
499
+
500
+ [data-transition-direction="forward"]::view-transition-new(root) {
501
+ animation-name: cap-slide-in-right;
502
+ }
503
+
504
+ /* iOS-style back navigation */
505
+ [data-transition-direction="back"]::view-transition-old(root) {
506
+ animation-name: cap-slide-out-right;
507
+ }
508
+
509
+ [data-transition-direction="back"]::view-transition-new(root) {
510
+ animation-name: cap-slide-in-left;
511
+ }
512
+
513
+ /* Root/replace navigation - fade */
514
+ [data-transition-direction="root"]::view-transition-old(root) {
515
+ animation-name: cap-fade-out;
516
+ }
517
+
518
+ [data-transition-direction="root"]::view-transition-new(root) {
519
+ animation-name: cap-fade-in;
520
+ }
521
+
522
+ /* Header transitions */
523
+ ::view-transition-old(cap-header),
524
+ ::view-transition-new(cap-header) {
525
+ animation-duration: 0.3s;
526
+ }
527
+
528
+ [data-transition-direction="forward"]::view-transition-old(cap-header) {
529
+ animation-name: cap-header-out-left;
530
+ }
531
+
532
+ [data-transition-direction="forward"]::view-transition-new(cap-header) {
533
+ animation-name: cap-header-in-right;
534
+ }
535
+
536
+ [data-transition-direction="back"]::view-transition-old(cap-header) {
537
+ animation-name: cap-header-out-right;
538
+ }
539
+
540
+ [data-transition-direction="back"]::view-transition-new(cap-header) {
541
+ animation-name: cap-header-in-left;
542
+ }
543
+
544
+ /* Content transitions */
545
+ ::view-transition-old(cap-content),
546
+ ::view-transition-new(cap-content) {
547
+ animation-duration: 0.4s;
548
+ animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
549
+ }
550
+
551
+ [data-transition-direction="forward"]::view-transition-old(cap-content) {
552
+ animation-name: cap-slide-out-left;
553
+ }
554
+
555
+ [data-transition-direction="forward"]::view-transition-new(cap-content) {
556
+ animation-name: cap-slide-in-right;
557
+ }
558
+
559
+ [data-transition-direction="back"]::view-transition-old(cap-content) {
560
+ animation-name: cap-slide-out-right;
561
+ }
562
+
563
+ [data-transition-direction="back"]::view-transition-new(cap-content) {
564
+ animation-name: cap-slide-in-left;
565
+ }
566
+
567
+ /* Animation keyframes */
568
+ @keyframes cap-slide-in-right {
569
+ from {
570
+ transform: translateX(100%);
571
+ opacity: 1;
572
+ }
573
+ to {
574
+ transform: translateX(0);
575
+ opacity: 1;
576
+ }
577
+ }
578
+
579
+ @keyframes cap-slide-out-left {
580
+ from {
581
+ transform: translateX(0);
582
+ opacity: 1;
583
+ }
584
+ to {
585
+ transform: translateX(-30%);
586
+ opacity: 0.8;
587
+ }
588
+ }
589
+
590
+ @keyframes cap-slide-in-left {
591
+ from {
592
+ transform: translateX(-30%);
593
+ opacity: 0.8;
594
+ }
595
+ to {
596
+ transform: translateX(0);
597
+ opacity: 1;
598
+ }
599
+ }
600
+
601
+ @keyframes cap-slide-out-right {
602
+ from {
603
+ transform: translateX(0);
604
+ opacity: 1;
605
+ }
606
+ to {
607
+ transform: translateX(100%);
608
+ opacity: 1;
609
+ }
610
+ }
611
+
612
+ @keyframes cap-fade-in {
613
+ from { opacity: 0; }
614
+ to { opacity: 1; }
615
+ }
616
+
617
+ @keyframes cap-fade-out {
618
+ from { opacity: 1; }
619
+ to { opacity: 0; }
620
+ }
621
+
622
+ @keyframes cap-header-in-right {
623
+ from {
624
+ transform: translateX(20px);
625
+ opacity: 0;
626
+ }
627
+ to {
628
+ transform: translateX(0);
629
+ opacity: 1;
630
+ }
631
+ }
632
+
633
+ @keyframes cap-header-out-left {
634
+ from {
635
+ transform: translateX(0);
636
+ opacity: 1;
637
+ }
638
+ to {
639
+ transform: translateX(-20px);
640
+ opacity: 0;
641
+ }
642
+ }
643
+
644
+ @keyframes cap-header-in-left {
645
+ from {
646
+ transform: translateX(-20px);
647
+ opacity: 0;
648
+ }
649
+ to {
650
+ transform: translateX(0);
651
+ opacity: 1;
652
+ }
653
+ }
654
+
655
+ @keyframes cap-header-out-right {
656
+ from {
657
+ transform: translateX(0);
658
+ opacity: 1;
659
+ }
660
+ to {
661
+ transform: translateX(20px);
662
+ opacity: 0;
663
+ }
664
+ }
665
+
666
+ /* Reduced motion support */
667
+ @media (prefers-reduced-motion: reduce) {
668
+ ::view-transition-old(root),
669
+ ::view-transition-new(root),
670
+ ::view-transition-old(cap-header),
671
+ ::view-transition-new(cap-header),
672
+ ::view-transition-old(cap-content),
673
+ ::view-transition-new(cap-content) {
674
+ animation-duration: 0.01ms !important;
675
+ }
676
+ }
677
+ `;
678
+ function injectViewTransitionsCSS() {
679
+ if (typeof document === "undefined") return;
680
+ const styleId = "cap-view-transitions-css";
681
+ if (document.getElementById(styleId)) return;
682
+ const style = document.createElement("style");
683
+ style.id = styleId;
684
+ style.textContent = VIEW_TRANSITIONS_CSS;
685
+ document.head.appendChild(style);
686
+ }
687
+
688
+ // src/core/transition-controller.ts
689
+ var DEFAULT_CONFIG = {
690
+ platform: "auto",
691
+ duration: 0,
692
+ // Will use platform default
693
+ easing: "",
694
+ // Will use platform default
695
+ useViewTransitions: false,
696
+ detectPlatform
697
+ };
698
+ var TransitionController = class {
699
+ config;
700
+ pageStack = [];
701
+ currentAnimations = [];
702
+ isAnimating = false;
703
+ interactiveBackTransition = null;
704
+ lifecycleCallbacks = /* @__PURE__ */ new Map();
705
+ constructor(config = {}) {
706
+ this.config = { ...DEFAULT_CONFIG, ...config };
707
+ if (this.config.useViewTransitions && supportsViewTransitions()) {
708
+ injectViewTransitionsCSS();
709
+ }
710
+ }
711
+ /**
712
+ * Get the resolved platform
713
+ */
714
+ get platform() {
715
+ if (this.config.platform === "ios") {
716
+ return "ios";
717
+ }
718
+ if (this.config.platform === "android") {
719
+ return "android";
720
+ }
721
+ return this.config.detectPlatform();
722
+ }
723
+ /**
724
+ * Get the current page state
725
+ */
726
+ get currentPage() {
727
+ return this.pageStack[this.pageStack.length - 1];
728
+ }
729
+ /**
730
+ * Get the page stack
731
+ */
732
+ get stack() {
733
+ return this.pageStack;
734
+ }
735
+ /**
736
+ * Check if an animation is in progress
737
+ */
738
+ get animating() {
739
+ return this.isAnimating;
740
+ }
741
+ /**
742
+ * Update global configuration
743
+ */
744
+ configure(config) {
745
+ this.config = { ...this.config, ...config };
746
+ if (this.config.useViewTransitions && supportsViewTransitions()) {
747
+ injectViewTransitionsCSS();
748
+ }
749
+ }
750
+ /**
751
+ * Register lifecycle callbacks for a page
752
+ */
753
+ registerLifecycle(pageId, lifecycle) {
754
+ this.lifecycleCallbacks.set(pageId, lifecycle);
755
+ }
756
+ /**
757
+ * Unregister lifecycle callbacks for a page
758
+ */
759
+ unregisterLifecycle(pageId) {
760
+ this.lifecycleCallbacks.delete(pageId);
761
+ }
762
+ /**
763
+ * Create a page state from an element
764
+ */
765
+ createPageState(element, options = {}) {
766
+ const id = options.id || `page-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
767
+ const header = element.querySelector(
768
+ '[data-cap-header], .cap-header, cap-header, [slot="header"]'
769
+ );
770
+ const content = element.querySelector(
771
+ '[data-cap-content], .cap-content, cap-content, [slot="content"]'
772
+ );
773
+ const footer = element.querySelector(
774
+ '[data-cap-footer], .cap-footer, cap-footer, [slot="footer"]'
775
+ );
776
+ return {
777
+ id,
778
+ element,
779
+ header: header || void 0,
780
+ content: content || void 0,
781
+ footer: footer || void 0,
782
+ isActive: false,
783
+ data: options.data
784
+ };
785
+ }
786
+ /**
787
+ * Navigate to a new page (push)
788
+ */
789
+ async push(enteringEl, config = {}) {
790
+ return this.navigate(enteringEl, { ...config, direction: "forward" });
791
+ }
792
+ /**
793
+ * Navigate back (pop)
794
+ */
795
+ async pop(config = {}) {
796
+ if (this.pageStack.length <= 1) {
797
+ return { success: false, duration: 0, error: new Error("Cannot pop: no page to go back to") };
798
+ }
799
+ const leavingState = this.pageStack[this.pageStack.length - 1];
800
+ const enteringState = this.pageStack[this.pageStack.length - 2];
801
+ return this.navigateWithStates(enteringState, leavingState, { ...config, direction: "back" }, () => {
802
+ this.pageStack.pop();
803
+ });
804
+ }
805
+ /**
806
+ * Start an interactive iOS-style back transition using the cached previous page.
807
+ */
808
+ beginInteractiveBack(config = {}) {
809
+ if (this.pageStack.length <= 1 || this.isAnimating || this.interactiveBackTransition) {
810
+ return false;
811
+ }
812
+ const leavingState = this.pageStack[this.pageStack.length - 1];
813
+ const enteringState = this.pageStack[this.pageStack.length - 2];
814
+ const direction = "back";
815
+ const duration = config.duration ?? (this.config.duration || getDefaultDuration(this.platform, direction));
816
+ this.isAnimating = true;
817
+ enteringState.element.style.display = "";
818
+ enteringState.element.style.visibility = "visible";
819
+ const animOptions = {
820
+ enteringEl: enteringState.element,
821
+ leavingEl: leavingState.element,
822
+ direction,
823
+ duration,
824
+ easing: "linear",
825
+ isBack: true
826
+ };
827
+ const animations = createTransition(animOptions, this.platform);
828
+ for (const animation of animations) {
829
+ animation.pause();
830
+ animation.currentTime = 0;
831
+ }
832
+ this.currentAnimations = animations;
833
+ this.interactiveBackTransition = {
834
+ enteringState,
835
+ leavingState,
836
+ animations,
837
+ duration
838
+ };
839
+ return true;
840
+ }
841
+ /**
842
+ * Move the current interactive back transition to a progress step from 0 to 1.
843
+ */
844
+ stepInteractiveBack(step) {
845
+ const transition = this.interactiveBackTransition;
846
+ if (!transition) {
847
+ return;
848
+ }
849
+ const progress = Math.max(0, Math.min(step, 0.9999));
850
+ for (const animation of transition.animations) {
851
+ const duration = this.getAnimationDuration(animation, transition.duration);
852
+ animation.pause();
853
+ animation.currentTime = duration * progress;
854
+ }
855
+ }
856
+ /**
857
+ * Complete or cancel the current interactive back transition.
858
+ */
859
+ async endInteractiveBack(shouldComplete, releaseDuration, commitStack) {
860
+ const transition = this.interactiveBackTransition;
861
+ if (!transition) {
862
+ return;
863
+ }
864
+ try {
865
+ await this.playInteractiveAnimationsTo(shouldComplete ? 1 : 0, releaseDuration);
866
+ if (shouldComplete && commitStack) {
867
+ this.pageStack.pop();
868
+ this.updatePageVisibility(transition.enteringState, transition.leavingState);
869
+ transition.enteringState.isActive = true;
870
+ transition.leavingState.isActive = false;
871
+ cancelAnimations(transition.animations);
872
+ } else if (!shouldComplete) {
873
+ this.updatePageVisibility(transition.leavingState, transition.enteringState);
874
+ transition.leavingState.isActive = true;
875
+ transition.enteringState.isActive = false;
876
+ cancelAnimations(transition.animations);
877
+ }
878
+ } finally {
879
+ this.interactiveBackTransition = null;
880
+ this.currentAnimations = [];
881
+ this.isAnimating = false;
882
+ }
883
+ }
884
+ /**
885
+ * Cancel the current interactive back transition immediately.
886
+ */
887
+ cancelInteractiveBack() {
888
+ const transition = this.interactiveBackTransition;
889
+ if (!transition) {
890
+ return;
891
+ }
892
+ cancelAnimations(transition.animations);
893
+ this.updatePageVisibility(transition.leavingState, transition.enteringState);
894
+ this.interactiveBackTransition = null;
895
+ this.currentAnimations = [];
896
+ this.isAnimating = false;
897
+ }
898
+ /**
899
+ * Replace all pages with a new root
900
+ */
901
+ async setRoot(enteringEl, config = {}) {
902
+ return this.navigate(enteringEl, { ...config, direction: "root" });
903
+ }
904
+ /**
905
+ * Main navigation method
906
+ */
907
+ async navigate(enteringEl, config = {}) {
908
+ const direction = config.direction || "forward";
909
+ const enteringState = this.createPageState(enteringEl);
910
+ const leavingState = this.currentPage;
911
+ return this.navigateWithStates(enteringState, leavingState, config, () => {
912
+ if (direction === "root") {
913
+ this.pageStack = [enteringState];
914
+ } else if (direction === "back" && this.pageStack.length > 0) {
915
+ this.pageStack.pop();
916
+ const staleEnteringState = this.pageStack.pop();
917
+ if (staleEnteringState && staleEnteringState.element !== enteringState.element) {
918
+ staleEnteringState.element.remove();
919
+ this.lifecycleCallbacks.delete(staleEnteringState.id);
920
+ }
921
+ this.pageStack.push(enteringState);
922
+ } else {
923
+ this.pageStack.push(enteringState);
924
+ }
925
+ });
926
+ }
927
+ /**
928
+ * Navigate between two known page states
929
+ */
930
+ async navigateWithStates(enteringState, leavingState, config, updateStack) {
931
+ const startTime = performance.now();
932
+ const direction = config.direction || "forward";
933
+ if (this.isAnimating) {
934
+ cancelAnimations(this.currentAnimations);
935
+ }
936
+ this.isAnimating = true;
937
+ const event = {
938
+ direction,
939
+ from: leavingState,
940
+ to: enteringState
941
+ };
942
+ try {
943
+ if (leavingState) {
944
+ const lifecycle = this.lifecycleCallbacks.get(leavingState.id);
945
+ await lifecycle?.onWillLeave?.(event);
946
+ config.onStart?.();
947
+ }
948
+ const enteringLifecycle = this.lifecycleCallbacks.get(enteringState.id);
949
+ await enteringLifecycle?.onWillEnter?.(event);
950
+ const duration = config.duration ?? (this.config.duration || getDefaultDuration(this.platform, direction));
951
+ const easing = this.resolveTransitionEasing(config.easing || this.config.easing, direction);
952
+ const useViewTransitions = config.useViewTransitions !== false && this.config.useViewTransitions && supportsViewTransitions();
953
+ if (useViewTransitions) {
954
+ this.prepareViewTransitionElements(enteringState, leavingState);
955
+ await runViewTransition({
956
+ direction,
957
+ update: () => {
958
+ updateStack();
959
+ this.updatePageVisibility(enteringState, leavingState);
960
+ this.applyViewTransitionNames(enteringState);
961
+ this.clearViewTransitionNames(leavingState);
962
+ }
963
+ });
964
+ this.clearViewTransitionNames(enteringState, leavingState);
965
+ } else {
966
+ updateStack();
967
+ const animOptions = {
968
+ enteringEl: enteringState.element,
969
+ leavingEl: leavingState?.element,
970
+ direction,
971
+ duration,
972
+ easing,
973
+ isBack: direction === "back"
974
+ };
975
+ this.currentAnimations = createTransition(animOptions, this.platform);
976
+ await waitForAnimations(this.currentAnimations);
977
+ this.updatePageVisibility(enteringState, leavingState);
978
+ cancelAnimations(this.currentAnimations);
979
+ }
980
+ enteringState.isActive = true;
981
+ await enteringLifecycle?.onDidEnter?.(event);
982
+ if (leavingState) {
983
+ leavingState.isActive = false;
984
+ const lifecycle = this.lifecycleCallbacks.get(leavingState.id);
985
+ await lifecycle?.onDidLeave?.(event);
986
+ }
987
+ config.onComplete?.();
988
+ const totalDuration = performance.now() - startTime;
989
+ return { success: true, duration: totalDuration };
990
+ } catch (error) {
991
+ return {
992
+ success: false,
993
+ duration: performance.now() - startTime,
994
+ error: error instanceof Error ? error : new Error(String(error))
995
+ };
996
+ } finally {
997
+ this.isAnimating = false;
998
+ this.currentAnimations = [];
999
+ }
1000
+ }
1001
+ /**
1002
+ * Update page visibility after animation
1003
+ */
1004
+ updatePageVisibility(enteringState, leavingState) {
1005
+ enteringState.element.style.display = "";
1006
+ enteringState.element.style.visibility = "visible";
1007
+ enteringState.element.style.opacity = "1";
1008
+ enteringState.element.style.transform = "none";
1009
+ enteringState.element.style.position = "relative";
1010
+ this.clearTransitionOnlyStyles(enteringState.element);
1011
+ this.clearPagePartTransitionStyles(enteringState);
1012
+ if (leavingState) {
1013
+ leavingState.element.style.display = "none";
1014
+ leavingState.element.style.visibility = "hidden";
1015
+ leavingState.element.style.opacity = "1";
1016
+ leavingState.element.style.transform = "none";
1017
+ this.clearTransitionOnlyStyles(leavingState.element);
1018
+ this.clearPagePartTransitionStyles(leavingState);
1019
+ }
1020
+ }
1021
+ getAnimationDuration(animation, fallback) {
1022
+ const duration = animation.effect?.getTiming().duration;
1023
+ return typeof duration === "number" && Number.isFinite(duration) ? duration : fallback;
1024
+ }
1025
+ async playInteractiveAnimationsTo(targetProgress, releaseDuration) {
1026
+ const transition = this.interactiveBackTransition;
1027
+ if (!transition) {
1028
+ return;
1029
+ }
1030
+ if (releaseDuration <= 0) {
1031
+ for (const animation of transition.animations) {
1032
+ const duration = this.getAnimationDuration(animation, transition.duration);
1033
+ animation.pause();
1034
+ animation.currentTime = duration * targetProgress;
1035
+ }
1036
+ return;
1037
+ }
1038
+ const finished = transition.animations.map((animation) => {
1039
+ const duration = this.getAnimationDuration(animation, transition.duration);
1040
+ const currentTime = typeof animation.currentTime === "number" ? animation.currentTime : 0;
1041
+ const targetTime = duration * targetProgress;
1042
+ const distance = Math.abs(targetTime - currentTime);
1043
+ if (distance < 1) {
1044
+ animation.currentTime = targetTime;
1045
+ return Promise.resolve();
1046
+ }
1047
+ animation.playbackRate = Math.max(distance / releaseDuration, 1e-3) * (targetTime >= currentTime ? 1 : -1);
1048
+ animation.play();
1049
+ return animation.finished.catch(() => void 0);
1050
+ });
1051
+ await Promise.all(finished);
1052
+ }
1053
+ /**
1054
+ * Resolve configured easing presets after platform/direction are known.
1055
+ */
1056
+ resolveTransitionEasing(easing, direction) {
1057
+ if (!easing) {
1058
+ return getDefaultEasing(this.platform, direction || "forward");
1059
+ }
1060
+ if (typeof easing === "string" && ["ios", "android"].includes(easing)) {
1061
+ return getDefaultEasing(easing, direction || "forward");
1062
+ }
1063
+ return resolveEasing(easing);
1064
+ }
1065
+ /**
1066
+ * Remove styles that should only exist while a page is actively transitioning.
1067
+ */
1068
+ clearTransitionOnlyStyles(element) {
1069
+ element.classList.remove("cap-transition-active");
1070
+ element.style.removeProperty("z-index");
1071
+ element.style.removeProperty("pointer-events");
1072
+ element.style.removeProperty("will-change");
1073
+ element.style.removeProperty("backface-visibility");
1074
+ element.style.removeProperty("transform-style");
1075
+ element.style.removeProperty("box-shadow");
1076
+ }
1077
+ clearPagePartTransitionStyles(pageState) {
1078
+ const { header, content, footer } = this.resolvePageParts(pageState);
1079
+ for (const element of [header, content, footer]) {
1080
+ if (!element) continue;
1081
+ this.clearTransitionOnlyStyles(element);
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Prepare entering/leaving elements for a View Transition capture.
1086
+ * Entering page must be hidden in the "old" snapshot.
1087
+ */
1088
+ prepareViewTransitionElements(enteringState, leavingState) {
1089
+ this.clearAllKnownViewTransitionNames(enteringState, leavingState);
1090
+ if (leavingState) {
1091
+ this.applyViewTransitionNames(leavingState);
1092
+ enteringState.element.style.display = "none";
1093
+ enteringState.element.style.visibility = "hidden";
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Assign view transition names to a page's layout parts.
1098
+ */
1099
+ applyViewTransitionNames(pageState) {
1100
+ const { header, content, footer } = this.resolvePageParts(pageState);
1101
+ if (header) {
1102
+ setViewTransitionName(header, "cap-header");
1103
+ }
1104
+ if (content) {
1105
+ setViewTransitionName(content, "cap-content");
1106
+ }
1107
+ if (footer) {
1108
+ setViewTransitionName(footer, "cap-footer");
1109
+ }
1110
+ }
1111
+ /**
1112
+ * Clear view transition names for one or more page states.
1113
+ */
1114
+ clearViewTransitionNames(...states) {
1115
+ for (const state of states) {
1116
+ if (!state) continue;
1117
+ const { header, content, footer } = this.resolvePageParts(state);
1118
+ if (header) {
1119
+ clearViewTransitionName(header);
1120
+ }
1121
+ if (content) {
1122
+ clearViewTransitionName(content);
1123
+ }
1124
+ if (footer) {
1125
+ clearViewTransitionName(footer);
1126
+ }
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Clear view transition names from all known pages plus transient states.
1131
+ */
1132
+ clearAllKnownViewTransitionNames(...extraStates) {
1133
+ const knownStates = /* @__PURE__ */ new Set();
1134
+ for (const state of this.pageStack) {
1135
+ knownStates.add(state);
1136
+ }
1137
+ for (const state of extraStates) {
1138
+ if (state) {
1139
+ knownStates.add(state);
1140
+ }
1141
+ }
1142
+ this.clearViewTransitionNames(...knownStates);
1143
+ }
1144
+ /**
1145
+ * Resolve page parts lazily to avoid timing issues with custom-element setup.
1146
+ */
1147
+ resolvePageParts(pageState) {
1148
+ const header = pageState.header || pageState.element.querySelector(
1149
+ '[data-cap-header], .cap-header, cap-header, [slot="header"]'
1150
+ ) || void 0;
1151
+ const content = pageState.content || pageState.element.querySelector(
1152
+ '[data-cap-content], .cap-content, cap-content, [slot="content"]'
1153
+ ) || void 0;
1154
+ const footer = pageState.footer || pageState.element.querySelector(
1155
+ '[data-cap-footer], .cap-footer, cap-footer, [slot="footer"]'
1156
+ ) || void 0;
1157
+ if (header) pageState.header = header;
1158
+ if (content) pageState.content = content;
1159
+ if (footer) pageState.footer = footer;
1160
+ return { header, content, footer };
1161
+ }
1162
+ /**
1163
+ * Save scroll position for a page
1164
+ */
1165
+ saveScrollPosition(pageId) {
1166
+ const page = this.pageStack.find((p) => p.id === pageId);
1167
+ if (page?.content) {
1168
+ page.scrollPosition = {
1169
+ x: page.content.scrollLeft,
1170
+ y: page.content.scrollTop
1171
+ };
1172
+ }
1173
+ }
1174
+ /**
1175
+ * Restore scroll position for a page
1176
+ */
1177
+ restoreScrollPosition(pageId) {
1178
+ const page = this.pageStack.find((p) => p.id === pageId);
1179
+ if (page?.content && page.scrollPosition) {
1180
+ page.content.scrollLeft = page.scrollPosition.x;
1181
+ page.content.scrollTop = page.scrollPosition.y;
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Remove a page from the stack (used when cleaning up)
1186
+ */
1187
+ removePage(pageId) {
1188
+ const index = this.pageStack.findIndex((p) => p.id === pageId);
1189
+ if (index !== -1) {
1190
+ this.pageStack.splice(index, 1);
1191
+ this.lifecycleCallbacks.delete(pageId);
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Clear all pages
1196
+ */
1197
+ clear() {
1198
+ this.pageStack = [];
1199
+ this.lifecycleCallbacks.clear();
1200
+ cancelAnimations(this.currentAnimations);
1201
+ this.currentAnimations = [];
1202
+ this.isAnimating = false;
1203
+ }
1204
+ };
1205
+ function createTransitionController(config) {
1206
+ return new TransitionController(config);
1207
+ }
1208
+
1209
+ // src/core/native-platform.ts
1210
+ function getCapacitorRuntime() {
1211
+ return globalThis.Capacitor;
1212
+ }
1213
+ function normalizePlatform(platform) {
1214
+ if (platform === "ios" || platform === "android" || platform === "web") {
1215
+ return platform;
1216
+ }
1217
+ return "unknown";
1218
+ }
1219
+ function detectNativePlatform() {
1220
+ const capacitor = getCapacitorRuntime();
1221
+ const platform = normalizePlatform(capacitor?.getPlatform?.());
1222
+ const isNative = capacitor?.isNativePlatform?.() ?? (platform === "ios" || platform === "android");
1223
+ return {
1224
+ platform,
1225
+ isNative
1226
+ };
1227
+ }
1228
+ function isNativeSwipeGesturePlatform() {
1229
+ const { platform, isNative } = detectNativePlatform();
1230
+ return isNative && platform === "ios";
1231
+ }
1232
+
1233
+ // src/components/cap-router-outlet.ts
1234
+ var CapRouterOutlet = class extends HTMLElement {
1235
+ controller;
1236
+ options;
1237
+ observer = null;
1238
+ pendingPage = null;
1239
+ ignoredNodes = /* @__PURE__ */ new WeakSet();
1240
+ swipeGesturePointer = null;
1241
+ swipeGestureListenersActive = false;
1242
+ skipNextHistoryBackTransition = false;
1243
+ swipeBackDepth = 0;
1244
+ lastNavigationHref = null;
1245
+ swipeGestureEdgeWidth = 50;
1246
+ swipeGestureThreshold = 10;
1247
+ swipeGestureMinimumVelocity = 0.2;
1248
+ static get observedAttributes() {
1249
+ return ["platform", "duration", "keep-in-dom", "max-cached", "swipe-gesture"];
1250
+ }
1251
+ constructor() {
1252
+ super();
1253
+ this.options = {
1254
+ keepInDom: true,
1255
+ maxCached: 10,
1256
+ swipeGesture: "auto"
1257
+ };
1258
+ this.controller = createTransitionController();
1259
+ }
1260
+ connectedCallback() {
1261
+ this.style.display = "block";
1262
+ this.style.position = "relative";
1263
+ this.style.width = "100%";
1264
+ this.style.height = "100%";
1265
+ this.style.overflow = "hidden";
1266
+ this.lastNavigationHref = this.getCurrentNavigationHref();
1267
+ this.observer = new MutationObserver((mutations) => {
1268
+ this.handleMutations(mutations);
1269
+ });
1270
+ this.observer.observe(this, {
1271
+ childList: true,
1272
+ subtree: false
1273
+ });
1274
+ const children = Array.from(this.children);
1275
+ if (children.length > 0) {
1276
+ this.initializeFirstPage(children[children.length - 1]);
1277
+ }
1278
+ this.updateSwipeGestureListeners();
1279
+ }
1280
+ disconnectedCallback() {
1281
+ this.observer?.disconnect();
1282
+ this.removeSwipeGestureListeners();
1283
+ this.controller.clear();
1284
+ this.swipeBackDepth = 0;
1285
+ this.lastNavigationHref = null;
1286
+ }
1287
+ attributeChangedCallback(name, _oldValue, newValue) {
1288
+ switch (name) {
1289
+ case "platform":
1290
+ this.controller.configure({ platform: newValue });
1291
+ break;
1292
+ case "duration":
1293
+ this.controller.configure({ duration: parseInt(newValue, 10) });
1294
+ break;
1295
+ case "keep-in-dom":
1296
+ this.options.keepInDom = newValue !== "false";
1297
+ break;
1298
+ case "max-cached":
1299
+ this.options.maxCached = parseInt(newValue, 10);
1300
+ break;
1301
+ case "swipe-gesture":
1302
+ this.options.swipeGesture = this.parseSwipeGestureAttribute(newValue);
1303
+ this.updateSwipeGestureListeners();
1304
+ break;
1305
+ }
1306
+ }
1307
+ /**
1308
+ * Handle DOM mutations (child additions/removals)
1309
+ */
1310
+ handleMutations(mutations) {
1311
+ const addedNodes = [];
1312
+ const removedNodes = [];
1313
+ for (const mutation of mutations) {
1314
+ for (const node of mutation.removedNodes) {
1315
+ if (node instanceof HTMLElement) {
1316
+ removedNodes.push(node);
1317
+ }
1318
+ }
1319
+ for (const node of mutation.addedNodes) {
1320
+ if (node instanceof HTMLElement) {
1321
+ addedNodes.push(node);
1322
+ }
1323
+ }
1324
+ }
1325
+ const currentEl = this.controller.currentPage?.element;
1326
+ if (currentEl && removedNodes.includes(currentEl) && addedNodes.length > 0 && !currentEl.isConnected) {
1327
+ this.stylePageForTransition(currentEl);
1328
+ currentEl.style.display = "";
1329
+ currentEl.style.visibility = "visible";
1330
+ const anchor = addedNodes[0];
1331
+ this.ignoredNodes.add(currentEl);
1332
+ if (anchor.parentElement === this) {
1333
+ this.insertBefore(currentEl, anchor);
1334
+ } else {
1335
+ this.appendChild(currentEl);
1336
+ }
1337
+ }
1338
+ for (const node of removedNodes) {
1339
+ if (node === currentEl) continue;
1340
+ const state = this.controller.stack.find((pageState) => pageState.element === node);
1341
+ if (state) {
1342
+ this.controller.removePage(state.id);
1343
+ }
1344
+ }
1345
+ for (const node of addedNodes) {
1346
+ if (node === this.pendingPage) continue;
1347
+ if (this.ignoredNodes.has(node)) {
1348
+ this.ignoredNodes.delete(node);
1349
+ continue;
1350
+ }
1351
+ this.handleNewPage(node);
1352
+ }
1353
+ }
1354
+ /**
1355
+ * Initialize the first page without animation
1356
+ */
1357
+ initializeFirstPage(page) {
1358
+ page.style.position = "relative";
1359
+ page.style.width = "100%";
1360
+ page.style.height = "100%";
1361
+ const state = this.controller.createPageState(page);
1362
+ state.isActive = true;
1363
+ this.controller.pageStack.push(state);
1364
+ this.swipeBackDepth = 0;
1365
+ this.lastNavigationHref = this.getCurrentNavigationHref();
1366
+ }
1367
+ /**
1368
+ * Handle a new page being added
1369
+ */
1370
+ async handleNewPage(page) {
1371
+ const outletDirection = this.dataset.direction;
1372
+ const direction = page.dataset.direction || outletDirection || "forward";
1373
+ if (outletDirection) {
1374
+ delete this.dataset.direction;
1375
+ }
1376
+ const skipTransition = this.skipNextHistoryBackTransition && direction === "back";
1377
+ this.skipNextHistoryBackTransition = false;
1378
+ const hadPageBefore = this.controller.stack.length > 0;
1379
+ this.stylePageForTransition(page);
1380
+ this.pendingPage = page;
1381
+ try {
1382
+ const result = await this.controller.navigate(page, { direction, duration: skipTransition ? 0 : void 0 });
1383
+ if (result.success) {
1384
+ this.recordCompletedNavigation(direction, { hadPageBefore });
1385
+ }
1386
+ } finally {
1387
+ this.pendingPage = null;
1388
+ }
1389
+ if (!this.options.keepInDom) {
1390
+ this.cleanupOldPages();
1391
+ } else {
1392
+ this.enforceCacheLimit();
1393
+ }
1394
+ }
1395
+ /**
1396
+ * Clean up pages that are no longer needed
1397
+ */
1398
+ cleanupOldPages() {
1399
+ const stack = this.controller.stack;
1400
+ const children = Array.from(this.children);
1401
+ for (const child of children) {
1402
+ const inStack = stack.some((s) => s.element === child);
1403
+ if (!inStack && !child.dataset.keepInDom) {
1404
+ child.remove();
1405
+ }
1406
+ }
1407
+ }
1408
+ /**
1409
+ * Enforce the cache limit
1410
+ */
1411
+ enforceCacheLimit() {
1412
+ const stack = this.controller.stack;
1413
+ const maxCached = this.options.maxCached || 10;
1414
+ if (stack.length > maxCached) {
1415
+ const toRemove = stack.slice(0, stack.length - maxCached);
1416
+ for (const page of toRemove) {
1417
+ if (!page.isActive) {
1418
+ page.element.remove();
1419
+ this.controller.removePage(page.id);
1420
+ }
1421
+ }
1422
+ }
1423
+ }
1424
+ /**
1425
+ * Programmatic navigation - push a new page
1426
+ */
1427
+ async push(page, config = {}) {
1428
+ this.stylePageForTransition(page);
1429
+ const hadPageBefore = this.controller.stack.length > 0;
1430
+ this.pendingPage = page;
1431
+ this.appendChild(page);
1432
+ try {
1433
+ const result = await this.controller.push(page, config);
1434
+ if (result.success) {
1435
+ this.recordCompletedNavigation("forward", { hadPageBefore, forceForward: true });
1436
+ }
1437
+ } finally {
1438
+ this.pendingPage = null;
1439
+ }
1440
+ }
1441
+ /**
1442
+ * Programmatic navigation - pop current page
1443
+ */
1444
+ async pop(config = {}) {
1445
+ const result = await this.controller.pop(config);
1446
+ if (result.success) {
1447
+ this.recordCompletedNavigation("back", { hadPageBefore: true });
1448
+ if (!this.options.keepInDom) {
1449
+ const children = Array.from(this.children);
1450
+ const lastChild = children[children.length - 1];
1451
+ if (lastChild) {
1452
+ lastChild.remove();
1453
+ }
1454
+ }
1455
+ }
1456
+ }
1457
+ /**
1458
+ * Programmatic navigation - set root page
1459
+ */
1460
+ async setRoot(page, config = {}) {
1461
+ const oldChildren = Array.from(this.children);
1462
+ this.stylePageForTransition(page);
1463
+ this.pendingPage = page;
1464
+ this.appendChild(page);
1465
+ try {
1466
+ const result = await this.controller.setRoot(page, config);
1467
+ if (result.success) {
1468
+ this.recordCompletedNavigation("root", { hadPageBefore: true });
1469
+ }
1470
+ } finally {
1471
+ this.pendingPage = null;
1472
+ }
1473
+ for (const child of oldChildren) {
1474
+ child.remove();
1475
+ }
1476
+ }
1477
+ /**
1478
+ * Get the current page stack length
1479
+ */
1480
+ get stackLength() {
1481
+ return this.controller.stack.length;
1482
+ }
1483
+ /**
1484
+ * Check if we can go back
1485
+ */
1486
+ get canGoBack() {
1487
+ return this.controller.stack.length > 1 && this.swipeBackDepth > 0;
1488
+ }
1489
+ /**
1490
+ * Get whether edge swipe-back gesture is enabled.
1491
+ */
1492
+ get swipeGesture() {
1493
+ return this.options.swipeGesture ?? "auto";
1494
+ }
1495
+ /**
1496
+ * Enable, disable, or auto-detect edge swipe-back gesture.
1497
+ */
1498
+ set swipeGesture(value) {
1499
+ this.setSwipeGesture(value);
1500
+ }
1501
+ /**
1502
+ * Enable, disable, or auto-detect edge swipe-back gesture.
1503
+ */
1504
+ setSwipeGesture(value) {
1505
+ this.options.swipeGesture = value;
1506
+ const serialized = this.serializeSwipeGesture(value);
1507
+ if (this.getAttribute("swipe-gesture") !== serialized) {
1508
+ this.setAttribute("swipe-gesture", serialized);
1509
+ } else {
1510
+ this.updateSwipeGestureListeners();
1511
+ }
1512
+ }
1513
+ /**
1514
+ * Get the transition controller for advanced usage
1515
+ */
1516
+ getController() {
1517
+ return this.controller;
1518
+ }
1519
+ /**
1520
+ * Apply layout styles required for transition animations.
1521
+ */
1522
+ stylePageForTransition(page) {
1523
+ page.style.position = "absolute";
1524
+ page.style.top = "0";
1525
+ page.style.left = "0";
1526
+ page.style.width = "100%";
1527
+ page.style.height = "100%";
1528
+ }
1529
+ parseSwipeGestureAttribute(value) {
1530
+ if (value === null || value === "auto") {
1531
+ return "auto";
1532
+ }
1533
+ if (value === "false") {
1534
+ return false;
1535
+ }
1536
+ return true;
1537
+ }
1538
+ serializeSwipeGesture(value) {
1539
+ return typeof value === "boolean" ? String(value) : value;
1540
+ }
1541
+ updateSwipeGestureListeners() {
1542
+ if (this.options.swipeGesture === false) {
1543
+ this.removeSwipeGestureListeners();
1544
+ return;
1545
+ }
1546
+ if (this.swipeGestureListenersActive || typeof PointerEvent === "undefined") {
1547
+ return;
1548
+ }
1549
+ this.addEventListener("pointerdown", this.handleSwipeGesturePointerDown);
1550
+ this.addEventListener("pointermove", this.handleSwipeGesturePointerMove, { passive: false });
1551
+ this.addEventListener("pointerup", this.handleSwipeGesturePointerEnd);
1552
+ this.addEventListener("pointercancel", this.handleSwipeGesturePointerCancel);
1553
+ this.swipeGestureListenersActive = true;
1554
+ }
1555
+ removeSwipeGestureListeners() {
1556
+ if (!this.swipeGestureListenersActive) {
1557
+ return;
1558
+ }
1559
+ this.removeEventListener("pointerdown", this.handleSwipeGesturePointerDown);
1560
+ this.removeEventListener("pointermove", this.handleSwipeGesturePointerMove);
1561
+ this.removeEventListener("pointerup", this.handleSwipeGesturePointerEnd);
1562
+ this.removeEventListener("pointercancel", this.handleSwipeGesturePointerCancel);
1563
+ this.swipeGestureListenersActive = false;
1564
+ this.swipeGesturePointer = null;
1565
+ }
1566
+ isSwipeGestureEnabled() {
1567
+ const option = this.options.swipeGesture ?? "auto";
1568
+ if (option === true) {
1569
+ return true;
1570
+ }
1571
+ if (option === false) {
1572
+ return false;
1573
+ }
1574
+ return isNativeSwipeGesturePlatform();
1575
+ }
1576
+ getCurrentNavigationHref() {
1577
+ return this.ownerDocument.defaultView?.location.href ?? null;
1578
+ }
1579
+ recordCompletedNavigation(direction, options) {
1580
+ const currentHref = this.getCurrentNavigationHref();
1581
+ if (!options.hadPageBefore || direction === "root") {
1582
+ this.swipeBackDepth = 0;
1583
+ this.lastNavigationHref = currentHref;
1584
+ return;
1585
+ }
1586
+ if (direction === "back") {
1587
+ this.swipeBackDepth = Math.max(0, this.swipeBackDepth - 1);
1588
+ this.lastNavigationHref = currentHref;
1589
+ return;
1590
+ }
1591
+ if (direction === "forward" || direction === "none") {
1592
+ const hrefChanged = currentHref === null || this.lastNavigationHref === null || currentHref !== this.lastNavigationHref;
1593
+ if (options.forceForward || hrefChanged) {
1594
+ this.swipeBackDepth += 1;
1595
+ }
1596
+ this.lastNavigationHref = currentHref;
1597
+ return;
1598
+ }
1599
+ this.lastNavigationHref = currentHref;
1600
+ }
1601
+ canStartSwipeGesture(event) {
1602
+ if (!this.isSwipeGestureEnabled() || this.controller.animating || this.pendingPage || !this.canGoBack) {
1603
+ return false;
1604
+ }
1605
+ if (!event.isPrimary || event.pointerType === "mouse" && event.button !== 0) {
1606
+ return false;
1607
+ }
1608
+ if (this.isInteractiveSwipeTarget(event.target) || this.hasScrollableInlineAncestor(event.target)) {
1609
+ return false;
1610
+ }
1611
+ const rect = this.getBoundingClientRect();
1612
+ const startX = event.clientX - rect.left;
1613
+ if (event.clientY < rect.top || event.clientY > rect.bottom) {
1614
+ return false;
1615
+ }
1616
+ return this.isRTL() ? startX >= rect.width - this.swipeGestureEdgeWidth : startX <= this.swipeGestureEdgeWidth;
1617
+ }
1618
+ isRTL() {
1619
+ const doc = this.ownerDocument;
1620
+ return doc.dir === "rtl" || doc.documentElement.dir === "rtl" || getComputedStyle(this).direction === "rtl";
1621
+ }
1622
+ getSwipeGestureDeltaX(pointer) {
1623
+ const deltaX = pointer.currentX - pointer.startX;
1624
+ return this.isRTL() ? -deltaX : deltaX;
1625
+ }
1626
+ isInteractiveSwipeTarget(target) {
1627
+ if (!(target instanceof Element)) {
1628
+ return false;
1629
+ }
1630
+ return Boolean(
1631
+ target.closest(
1632
+ 'a, button, input, textarea, select, option, [contenteditable="true"], [data-swipe-gesture-ignore], [data-swipe-back-ignore]'
1633
+ )
1634
+ );
1635
+ }
1636
+ hasScrollableInlineAncestor(target) {
1637
+ let element = target instanceof Element ? target : null;
1638
+ while (element && element !== this) {
1639
+ if (element instanceof HTMLElement) {
1640
+ const style = getComputedStyle(element);
1641
+ const canScrollInline = /(auto|scroll)/.test(style.overflowX) && element.scrollWidth > element.clientWidth + 1;
1642
+ if (canScrollInline && element.scrollLeft > 0) {
1643
+ return true;
1644
+ }
1645
+ }
1646
+ element = element.parentElement;
1647
+ }
1648
+ return false;
1649
+ }
1650
+ handleSwipeGesturePointerDown = (event) => {
1651
+ if (!this.canStartSwipeGesture(event)) {
1652
+ return;
1653
+ }
1654
+ this.swipeGesturePointer = {
1655
+ pointerId: event.pointerId,
1656
+ startX: event.clientX,
1657
+ startY: event.clientY,
1658
+ currentX: event.clientX,
1659
+ currentY: event.clientY,
1660
+ startTime: performance.now(),
1661
+ dragging: false,
1662
+ transitionStarted: false
1663
+ };
1664
+ try {
1665
+ this.setPointerCapture(event.pointerId);
1666
+ } catch {
1667
+ }
1668
+ };
1669
+ handleSwipeGesturePointerMove = (event) => {
1670
+ const pointer = this.swipeGesturePointer;
1671
+ if (!pointer || pointer.pointerId !== event.pointerId) {
1672
+ return;
1673
+ }
1674
+ pointer.currentX = event.clientX;
1675
+ pointer.currentY = event.clientY;
1676
+ const deltaX = this.getSwipeGestureDeltaX(pointer);
1677
+ const deltaY = pointer.currentY - pointer.startY;
1678
+ const absX = Math.abs(deltaX);
1679
+ const absY = Math.abs(deltaY);
1680
+ if (!pointer.dragging && absY > 12 && absY > absX) {
1681
+ this.cancelSwipeGesturePointer(event.pointerId);
1682
+ return;
1683
+ }
1684
+ if (deltaX < -this.swipeGestureThreshold) {
1685
+ this.cancelSwipeGesture(event.pointerId);
1686
+ return;
1687
+ }
1688
+ if (!pointer.dragging && deltaX > this.swipeGestureThreshold && absX > absY) {
1689
+ pointer.dragging = true;
1690
+ pointer.transitionStarted = this.controller.beginInteractiveBack({ direction: "back" });
1691
+ if (!pointer.transitionStarted) {
1692
+ this.cancelSwipeGesturePointer(event.pointerId);
1693
+ return;
1694
+ }
1695
+ }
1696
+ if (pointer.dragging && pointer.transitionStarted) {
1697
+ if (event.cancelable) event.preventDefault();
1698
+ const width = Math.max(this.getBoundingClientRect().width, 1);
1699
+ this.controller.stepInteractiveBack(deltaX / width);
1700
+ }
1701
+ };
1702
+ handleSwipeGesturePointerEnd = (event) => {
1703
+ const pointer = this.swipeGesturePointer;
1704
+ if (!pointer || pointer.pointerId !== event.pointerId) {
1705
+ return;
1706
+ }
1707
+ pointer.currentX = event.clientX;
1708
+ pointer.currentY = event.clientY;
1709
+ const deltaX = this.getSwipeGestureDeltaX(pointer);
1710
+ const elapsed = Math.max(performance.now() - pointer.startTime, 1);
1711
+ const velocityX = deltaX / elapsed;
1712
+ const width = Math.max(this.getBoundingClientRect().width, 1);
1713
+ const step = deltaX / width;
1714
+ const shouldCommit = pointer.dragging && pointer.transitionStarted && velocityX >= 0 && (velocityX > this.swipeGestureMinimumVelocity || deltaX > width / 2);
1715
+ const missing = shouldCommit ? 1 - step : step;
1716
+ const missingDistance = Math.max(missing, 0) * width;
1717
+ const releaseDuration = missingDistance > 5 && Math.abs(velocityX) > 0 ? Math.min(missingDistance / Math.abs(velocityX), 540) : 0;
1718
+ this.releaseSwipeGesturePointer(event.pointerId);
1719
+ void this.finishSwipeGestureBack(shouldCommit, releaseDuration);
1720
+ };
1721
+ handleSwipeGesturePointerCancel = (event) => {
1722
+ this.cancelSwipeGesture(event.pointerId);
1723
+ };
1724
+ cancelSwipeGesturePointer(pointerId) {
1725
+ this.releaseSwipeGesturePointer(pointerId);
1726
+ }
1727
+ releaseSwipeGesturePointer(pointerId) {
1728
+ if (this.swipeGesturePointer?.pointerId !== pointerId) {
1729
+ return;
1730
+ }
1731
+ try {
1732
+ this.releasePointerCapture(pointerId);
1733
+ } catch {
1734
+ }
1735
+ this.swipeGesturePointer = null;
1736
+ }
1737
+ cancelSwipeGesture(pointerId) {
1738
+ const pointer = this.swipeGesturePointer;
1739
+ if (!pointer || pointer.pointerId !== pointerId) {
1740
+ return;
1741
+ }
1742
+ this.releaseSwipeGesturePointer(pointerId);
1743
+ if (pointer.transitionStarted) {
1744
+ void this.finishSwipeGestureBack(false, 0);
1745
+ }
1746
+ }
1747
+ async finishSwipeGestureBack(shouldComplete, releaseDuration) {
1748
+ const shouldUseHistory = shouldComplete && typeof window !== "undefined" && window.history.length > 1;
1749
+ await this.controller.endInteractiveBack(shouldComplete, releaseDuration, !shouldUseHistory);
1750
+ if (!shouldComplete) {
1751
+ return;
1752
+ }
1753
+ if (shouldUseHistory) {
1754
+ this.skipNextHistoryBackTransition = true;
1755
+ this.dataset.direction = "back";
1756
+ window.history.back();
1757
+ return;
1758
+ }
1759
+ if (!this.options.keepInDom) {
1760
+ this.cleanupOldPages();
1761
+ }
1762
+ }
1763
+ };
1764
+ if (typeof customElements !== "undefined" && !customElements.get("cap-router-outlet")) {
1765
+ customElements.define("cap-router-outlet", CapRouterOutlet);
1766
+ }
1767
+
1768
+ // src/components/cap-page.ts
1769
+ var CapPage = class extends HTMLElement {
1770
+ _lifecycle = {};
1771
+ _isActive = false;
1772
+ static get observedAttributes() {
1773
+ return ["key", "cache-scroll"];
1774
+ }
1775
+ constructor() {
1776
+ super();
1777
+ const shadow = this.attachShadow({ mode: "open" });
1778
+ shadow.innerHTML = `
1779
+ <style>
1780
+ :host {
1781
+ display: flex;
1782
+ flex-direction: column;
1783
+ width: 100%;
1784
+ height: 100%;
1785
+ position: relative;
1786
+ overflow: hidden;
1787
+ background: var(--cap-page-background, Canvas);
1788
+ color: var(--cap-page-color, CanvasText);
1789
+ }
1790
+
1791
+ :host(.cap-transition-active) {
1792
+ overflow: visible;
1793
+ }
1794
+
1795
+ .header-container {
1796
+ flex-shrink: 0;
1797
+ position: relative;
1798
+ z-index: 10;
1799
+ }
1800
+
1801
+ .content-container {
1802
+ flex: 1;
1803
+ position: relative;
1804
+ overflow: hidden;
1805
+ }
1806
+
1807
+ .footer-container {
1808
+ flex-shrink: 0;
1809
+ position: relative;
1810
+ z-index: 10;
1811
+ }
1812
+
1813
+ ::slotted([slot="header"]),
1814
+ ::slotted([data-cap-header]) {
1815
+ display: block;
1816
+ }
1817
+
1818
+ ::slotted([slot="content"]),
1819
+ ::slotted([data-cap-content]) {
1820
+ display: block;
1821
+ height: 100%;
1822
+ overflow: auto;
1823
+ }
1824
+
1825
+ ::slotted([slot="footer"]),
1826
+ ::slotted([data-cap-footer]) {
1827
+ display: block;
1828
+ }
1829
+ </style>
1830
+
1831
+ <div class="header-container" part="header">
1832
+ <slot name="header"></slot>
1833
+ </div>
1834
+
1835
+ <div class="content-container" part="content">
1836
+ <slot name="content"></slot>
1837
+ <slot></slot>
1838
+ </div>
1839
+
1840
+ <div class="footer-container" part="footer">
1841
+ <slot name="footer"></slot>
1842
+ </div>
1843
+ `;
1844
+ }
1845
+ connectedCallback() {
1846
+ this.markTransitionElements();
1847
+ this.dispatchEvent(
1848
+ new CustomEvent("cap-page-connected", {
1849
+ bubbles: true,
1850
+ composed: true,
1851
+ detail: { page: this }
1852
+ })
1853
+ );
1854
+ }
1855
+ disconnectedCallback() {
1856
+ this.dispatchEvent(
1857
+ new CustomEvent("cap-page-disconnected", {
1858
+ bubbles: true,
1859
+ composed: true,
1860
+ detail: { page: this }
1861
+ })
1862
+ );
1863
+ }
1864
+ /**
1865
+ * Mark child elements for transition controller
1866
+ */
1867
+ markTransitionElements() {
1868
+ const header = this.querySelector('[slot="header"]');
1869
+ if (header) {
1870
+ header.setAttribute("data-cap-header", "");
1871
+ }
1872
+ const content = this.querySelector('[slot="content"]');
1873
+ if (content) {
1874
+ content.setAttribute("data-cap-content", "");
1875
+ }
1876
+ const footer = this.querySelector('[slot="footer"]');
1877
+ if (footer) {
1878
+ footer.setAttribute("data-cap-footer", "");
1879
+ }
1880
+ }
1881
+ /**
1882
+ * Set lifecycle callbacks
1883
+ */
1884
+ set lifecycle(callbacks) {
1885
+ this._lifecycle = callbacks;
1886
+ }
1887
+ get lifecycle() {
1888
+ return this._lifecycle;
1889
+ }
1890
+ /**
1891
+ * Check if page is active
1892
+ */
1893
+ get isActive() {
1894
+ return this._isActive;
1895
+ }
1896
+ /**
1897
+ * Called when page will enter view
1898
+ */
1899
+ async willEnter(event) {
1900
+ await this._lifecycle.onWillEnter?.(event);
1901
+ this.dispatchEvent(
1902
+ new CustomEvent("cap-will-enter", {
1903
+ bubbles: true,
1904
+ detail: event
1905
+ })
1906
+ );
1907
+ }
1908
+ /**
1909
+ * Called when page has entered view
1910
+ */
1911
+ async didEnter(event) {
1912
+ this._isActive = true;
1913
+ await this._lifecycle.onDidEnter?.(event);
1914
+ this.dispatchEvent(
1915
+ new CustomEvent("cap-did-enter", {
1916
+ bubbles: true,
1917
+ detail: event
1918
+ })
1919
+ );
1920
+ }
1921
+ /**
1922
+ * Called when page will leave view
1923
+ */
1924
+ async willLeave(event) {
1925
+ await this._lifecycle.onWillLeave?.(event);
1926
+ this.dispatchEvent(
1927
+ new CustomEvent("cap-will-leave", {
1928
+ bubbles: true,
1929
+ detail: event
1930
+ })
1931
+ );
1932
+ }
1933
+ /**
1934
+ * Called when page has left view
1935
+ */
1936
+ async didLeave(event) {
1937
+ this._isActive = false;
1938
+ await this._lifecycle.onDidLeave?.(event);
1939
+ this.dispatchEvent(
1940
+ new CustomEvent("cap-did-leave", {
1941
+ bubbles: true,
1942
+ detail: event
1943
+ })
1944
+ );
1945
+ }
1946
+ /**
1947
+ * Get the header element
1948
+ */
1949
+ get headerElement() {
1950
+ return this.querySelector('[slot="header"], [data-cap-header]');
1951
+ }
1952
+ /**
1953
+ * Get the content element
1954
+ */
1955
+ get contentElement() {
1956
+ return this.querySelector('[slot="content"], [data-cap-content]');
1957
+ }
1958
+ /**
1959
+ * Get the footer element
1960
+ */
1961
+ get footerElement() {
1962
+ return this.querySelector('[slot="footer"], [data-cap-footer]');
1963
+ }
1964
+ /**
1965
+ * Save scroll position
1966
+ */
1967
+ saveScrollPosition() {
1968
+ const content = this.contentElement;
1969
+ if (!content) return null;
1970
+ return {
1971
+ x: content.scrollLeft,
1972
+ y: content.scrollTop
1973
+ };
1974
+ }
1975
+ /**
1976
+ * Restore scroll position
1977
+ */
1978
+ restoreScrollPosition(position) {
1979
+ const content = this.contentElement;
1980
+ if (!content) return;
1981
+ content.scrollLeft = position.x;
1982
+ content.scrollTop = position.y;
1983
+ }
1984
+ };
1985
+ if (typeof customElements !== "undefined" && !customElements.get("cap-page")) {
1986
+ customElements.define("cap-page", CapPage);
1987
+ }
1988
+
1989
+ // src/components/cap-header.ts
1990
+ var CapHeader = class extends HTMLElement {
1991
+ static get observedAttributes() {
1992
+ return ["translucent", "collapse"];
1993
+ }
1994
+ constructor() {
1995
+ super();
1996
+ }
1997
+ connectedCallback() {
1998
+ this.style.display = "block";
1999
+ this.style.position = "relative";
2000
+ this.style.zIndex = "10";
2001
+ this.setAttribute("data-cap-header", "");
2002
+ if (this.parentElement?.tagName === "CAP-PAGE" && !this.hasAttribute("slot")) {
2003
+ this.setAttribute("slot", "header");
2004
+ }
2005
+ }
2006
+ attributeChangedCallback(name, _oldValue, newValue) {
2007
+ switch (name) {
2008
+ case "translucent":
2009
+ this.dataset.translucent = newValue !== null ? "true" : "false";
2010
+ break;
2011
+ case "collapse":
2012
+ this.dataset.collapse = newValue;
2013
+ break;
2014
+ }
2015
+ }
2016
+ /**
2017
+ * Get the current height of the header
2018
+ */
2019
+ get height() {
2020
+ return this.offsetHeight;
2021
+ }
2022
+ };
2023
+ if (typeof customElements !== "undefined" && !customElements.get("cap-header")) {
2024
+ customElements.define("cap-header", CapHeader);
2025
+ }
2026
+
2027
+ // src/components/cap-content.ts
2028
+ var CapContent = class extends HTMLElement {
2029
+ _scrollPosition = { x: 0, y: 0 };
2030
+ static get observedAttributes() {
2031
+ return ["fullscreen", "scroll-x", "scroll-y"];
2032
+ }
2033
+ constructor() {
2034
+ super();
2035
+ }
2036
+ connectedCallback() {
2037
+ this.style.display = "block";
2038
+ this.style.position = "relative";
2039
+ this.style.flex = "1";
2040
+ this.style.overflow = "auto";
2041
+ this.style.overscrollBehavior = "contain";
2042
+ this.style.webkitOverflowScrolling = "touch";
2043
+ this.setAttribute("data-cap-content", "");
2044
+ if (this.parentElement?.tagName === "CAP-PAGE" && !this.hasAttribute("slot")) {
2045
+ this.setAttribute("slot", "content");
2046
+ }
2047
+ this.addEventListener("scroll", this.handleScroll.bind(this), { passive: true });
2048
+ }
2049
+ disconnectedCallback() {
2050
+ this.removeEventListener("scroll", this.handleScroll.bind(this));
2051
+ }
2052
+ attributeChangedCallback(name, _oldValue, newValue) {
2053
+ switch (name) {
2054
+ case "fullscreen":
2055
+ this.dataset.fullscreen = newValue !== null ? "true" : "false";
2056
+ break;
2057
+ case "scroll-x":
2058
+ this.style.overflowX = newValue === "false" ? "hidden" : "auto";
2059
+ break;
2060
+ case "scroll-y":
2061
+ this.style.overflowY = newValue === "false" ? "hidden" : "auto";
2062
+ break;
2063
+ }
2064
+ }
2065
+ /**
2066
+ * Handle scroll events
2067
+ */
2068
+ handleScroll() {
2069
+ this._scrollPosition = {
2070
+ x: this.scrollLeft,
2071
+ y: this.scrollTop
2072
+ };
2073
+ this.dispatchEvent(
2074
+ new CustomEvent("cap-scroll", {
2075
+ bubbles: true,
2076
+ detail: this._scrollPosition
2077
+ })
2078
+ );
2079
+ }
2080
+ /**
2081
+ * Get current scroll position
2082
+ */
2083
+ get scrollPosition() {
2084
+ return { ...this._scrollPosition };
2085
+ }
2086
+ /**
2087
+ * Save current scroll position
2088
+ */
2089
+ saveScrollPosition() {
2090
+ this._scrollPosition = {
2091
+ x: this.scrollLeft,
2092
+ y: this.scrollTop
2093
+ };
2094
+ return { ...this._scrollPosition };
2095
+ }
2096
+ /**
2097
+ * Restore scroll position
2098
+ */
2099
+ restoreScrollPosition(position) {
2100
+ const pos = position || this._scrollPosition;
2101
+ this.scrollLeft = pos.x;
2102
+ this.scrollTop = pos.y;
2103
+ }
2104
+ /**
2105
+ * Scroll to top
2106
+ */
2107
+ scrollToTop(smooth = true) {
2108
+ this.scrollTo({
2109
+ top: 0,
2110
+ behavior: smooth ? "smooth" : "instant"
2111
+ });
2112
+ }
2113
+ /**
2114
+ * Scroll to bottom
2115
+ */
2116
+ scrollToBottom(smooth = true) {
2117
+ this.scrollTo({
2118
+ top: this.scrollHeight,
2119
+ behavior: smooth ? "smooth" : "instant"
2120
+ });
2121
+ }
2122
+ /**
2123
+ * Scroll to element
2124
+ */
2125
+ scrollToElement(element, smooth = true) {
2126
+ element.scrollIntoView({
2127
+ behavior: smooth ? "smooth" : "instant",
2128
+ block: "start"
2129
+ });
2130
+ }
2131
+ };
2132
+ if (typeof customElements !== "undefined" && !customElements.get("cap-content")) {
2133
+ customElements.define("cap-content", CapContent);
2134
+ }
2135
+
2136
+ // src/components/cap-footer.ts
2137
+ var CapFooter = class extends HTMLElement {
2138
+ static get observedAttributes() {
2139
+ return ["translucent"];
2140
+ }
2141
+ constructor() {
2142
+ super();
2143
+ }
2144
+ connectedCallback() {
2145
+ this.style.display = "block";
2146
+ this.style.position = "relative";
2147
+ this.style.zIndex = "10";
2148
+ this.setAttribute("data-cap-footer", "");
2149
+ if (this.parentElement?.tagName === "CAP-PAGE" && !this.hasAttribute("slot")) {
2150
+ this.setAttribute("slot", "footer");
2151
+ }
2152
+ }
2153
+ attributeChangedCallback(name, _oldValue, newValue) {
2154
+ switch (name) {
2155
+ case "translucent":
2156
+ this.dataset.translucent = newValue !== null ? "true" : "false";
2157
+ break;
2158
+ }
2159
+ }
2160
+ /**
2161
+ * Get the current height of the footer
2162
+ */
2163
+ get height() {
2164
+ return this.offsetHeight;
2165
+ }
2166
+ };
2167
+ if (typeof customElements !== "undefined" && !customElements.get("cap-footer")) {
2168
+ customElements.define("cap-footer", CapFooter);
2169
+ }
2170
+
2171
+ // src/solid/index.ts
2172
+ var globalController = null;
2173
+ var globalDirection = "forward";
2174
+ function initTransitions(config = {}) {
2175
+ globalController = createTransitionController(config);
2176
+ return globalController;
2177
+ }
2178
+ function getController() {
2179
+ if (!globalController) {
2180
+ globalController = createTransitionController();
2181
+ }
2182
+ return globalController;
2183
+ }
2184
+ function getDirection() {
2185
+ return globalDirection;
2186
+ }
2187
+ function setDirection(direction) {
2188
+ globalDirection = direction;
2189
+ if (typeof document !== "undefined") {
2190
+ for (const outlet of document.querySelectorAll("cap-router-outlet")) {
2191
+ outlet.dataset.direction = direction;
2192
+ }
2193
+ }
2194
+ }
2195
+ function setupRouterOutlet(element, options = {}) {
2196
+ const { keepInDom = true, maxCached = 10, platform = "auto", duration, swipeGesture } = options;
2197
+ element.setAttribute("platform", platform);
2198
+ if (duration) element.setAttribute("duration", String(duration));
2199
+ element.setAttribute("keep-in-dom", String(keepInDom));
2200
+ element.setAttribute("max-cached", String(maxCached));
2201
+ if (swipeGesture !== void 0) element.setAttribute("swipe-gesture", String(swipeGesture));
2202
+ }
2203
+ function setupPage(element, callbacks) {
2204
+ const handleWillEnter = (e) => {
2205
+ callbacks?.onWillEnter?.(e.detail);
2206
+ };
2207
+ const handleDidEnter = (e) => {
2208
+ callbacks?.onDidEnter?.(e.detail);
2209
+ };
2210
+ const handleWillLeave = (e) => {
2211
+ callbacks?.onWillLeave?.(e.detail);
2212
+ };
2213
+ const handleDidLeave = (e) => {
2214
+ callbacks?.onDidLeave?.(e.detail);
2215
+ };
2216
+ element.addEventListener("cap-will-enter", handleWillEnter);
2217
+ element.addEventListener("cap-did-enter", handleDidEnter);
2218
+ element.addEventListener("cap-will-leave", handleWillLeave);
2219
+ element.addEventListener("cap-did-leave", handleDidLeave);
2220
+ return () => {
2221
+ element.removeEventListener("cap-will-enter", handleWillEnter);
2222
+ element.removeEventListener("cap-did-enter", handleDidEnter);
2223
+ element.removeEventListener("cap-will-leave", handleWillLeave);
2224
+ element.removeEventListener("cap-did-leave", handleDidLeave);
2225
+ };
2226
+ }
2227
+ function createTransitionNavigate(navigate) {
2228
+ return (to, direction = "forward") => {
2229
+ setDirection(direction);
2230
+ navigate(to);
2231
+ };
2232
+ }
2233
+ // Annotate the CommonJS export names for ESM import in node:
2234
+ 0 && (module.exports = {
2235
+ TransitionController,
2236
+ createTransitionNavigate,
2237
+ getController,
2238
+ getDirection,
2239
+ initTransitions,
2240
+ setDirection,
2241
+ setupPage,
2242
+ setupRouterOutlet
2243
+ });
2244
+ //# sourceMappingURL=index.js.map