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