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