@ajuarezso/capacitor-liquid-glass 0.3.7 → 0.4.0
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/README.es.md +163 -0
- package/README.md +232 -72
- package/dist/esm/definitions.d.ts +48 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +31 -2
- package/dist/esm/tab-bar-binder.d.ts +56 -0
- package/dist/esm/tab-bar-binder.js +186 -0
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +3 -0
- package/dist/plugin.cjs.js +217 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +217 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/LiquidGlassPlugin/LiquidGlassPlugin.swift +35 -3
- package/ios/Sources/LiquidGlassPlugin/LiquidGlassTabBarOverlay.swift +153 -31
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { LiquidGlassPlugin, ShowTabBarOptions } from './definitions';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps the native tab bar glued to an HTML element when `containerElement` is
|
|
4
|
+
* passed to `showTabBar`. Mirrors the measure-and-observe approach of
|
|
5
|
+
* `@capacitor/google-maps`, but adapted for a **fixed overlay** rather than an
|
|
6
|
+
* inline view:
|
|
7
|
+
*
|
|
8
|
+
* - Google Maps (iOS) reparents its native view into a `WKChildScrollView` so
|
|
9
|
+
* it scrolls *with* page content. A tab bar must stay glued to the element's
|
|
10
|
+
* on-screen rect, so we instead push the rect to native, which positions an
|
|
11
|
+
* on-top overlay via Auto Layout constraints (never `setFrame` — see the
|
|
12
|
+
* iOS 26 Liquid Glass notes in `LiquidGlassTabBarOverlay.swift`).
|
|
13
|
+
* - We re-sync on `ResizeObserver` + `scroll` (capture phase, catches nested
|
|
14
|
+
* scroll containers) + `resize`/`orientationchange` + `visualViewport`
|
|
15
|
+
* (keyboard / browser-chrome / pinch-zoom), all **coalesced through a single
|
|
16
|
+
* `requestAnimationFrame`** so a 120 Hz scroll fires at most one bridge call
|
|
17
|
+
* per frame (the Maps plugin's lack of this is its main jitter source).
|
|
18
|
+
*
|
|
19
|
+
* When `containerElement` is omitted, this is a transparent pass-through to the
|
|
20
|
+
* native bottom-pinned behaviour — zero regression.
|
|
21
|
+
*/
|
|
22
|
+
export declare class TabBarBinder {
|
|
23
|
+
private readonly native;
|
|
24
|
+
private element;
|
|
25
|
+
private resizeObserver;
|
|
26
|
+
private rafId;
|
|
27
|
+
/** Last rect pushed to native — skip redundant bridge calls when unchanged. */
|
|
28
|
+
private lastSent;
|
|
29
|
+
/**
|
|
30
|
+
* Bumped on every teardown. Async work (the `measure` retry loop, the awaits
|
|
31
|
+
* in `showTabBar`) snapshots it and bails if a newer call superseded it —
|
|
32
|
+
* prevents a slow first measurement from clobbering a second `showTabBar`.
|
|
33
|
+
*/
|
|
34
|
+
private generation;
|
|
35
|
+
/** Stable identity so `removeEventListener` actually detaches the listeners. */
|
|
36
|
+
private readonly onReflow;
|
|
37
|
+
constructor(native: LiquidGlassPlugin);
|
|
38
|
+
showTabBar(options: ShowTabBarOptions): Promise<void>;
|
|
39
|
+
hideTabBar(): Promise<void>;
|
|
40
|
+
/** Removes the (possibly non-serializable) element ref before crossing the bridge. */
|
|
41
|
+
private stripElement;
|
|
42
|
+
private resolve;
|
|
43
|
+
private rect;
|
|
44
|
+
/**
|
|
45
|
+
* Retry until the element has a non-zero width AND height (guards against
|
|
46
|
+
* pre-layout reads). Bails early if a newer `showTabBar`/`hideTabBar` bumped
|
|
47
|
+
* the generation, so a stale interval can't outlive the call that started it.
|
|
48
|
+
* If it still measures 0 after ~3s, resolves with the zero rect (native falls
|
|
49
|
+
* back to bottom-pinned) and warns so the misconfig is visible.
|
|
50
|
+
*/
|
|
51
|
+
private measure;
|
|
52
|
+
private observe;
|
|
53
|
+
private scheduleSync;
|
|
54
|
+
private sameRect;
|
|
55
|
+
private teardown;
|
|
56
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Capacitor } from '@capacitor/core';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps the native tab bar glued to an HTML element when `containerElement` is
|
|
4
|
+
* passed to `showTabBar`. Mirrors the measure-and-observe approach of
|
|
5
|
+
* `@capacitor/google-maps`, but adapted for a **fixed overlay** rather than an
|
|
6
|
+
* inline view:
|
|
7
|
+
*
|
|
8
|
+
* - Google Maps (iOS) reparents its native view into a `WKChildScrollView` so
|
|
9
|
+
* it scrolls *with* page content. A tab bar must stay glued to the element's
|
|
10
|
+
* on-screen rect, so we instead push the rect to native, which positions an
|
|
11
|
+
* on-top overlay via Auto Layout constraints (never `setFrame` — see the
|
|
12
|
+
* iOS 26 Liquid Glass notes in `LiquidGlassTabBarOverlay.swift`).
|
|
13
|
+
* - We re-sync on `ResizeObserver` + `scroll` (capture phase, catches nested
|
|
14
|
+
* scroll containers) + `resize`/`orientationchange` + `visualViewport`
|
|
15
|
+
* (keyboard / browser-chrome / pinch-zoom), all **coalesced through a single
|
|
16
|
+
* `requestAnimationFrame`** so a 120 Hz scroll fires at most one bridge call
|
|
17
|
+
* per frame (the Maps plugin's lack of this is its main jitter source).
|
|
18
|
+
*
|
|
19
|
+
* When `containerElement` is omitted, this is a transparent pass-through to the
|
|
20
|
+
* native bottom-pinned behaviour — zero regression.
|
|
21
|
+
*/
|
|
22
|
+
export class TabBarBinder {
|
|
23
|
+
constructor(native) {
|
|
24
|
+
this.native = native;
|
|
25
|
+
this.element = null;
|
|
26
|
+
this.resizeObserver = null;
|
|
27
|
+
this.rafId = null;
|
|
28
|
+
/** Last rect pushed to native — skip redundant bridge calls when unchanged. */
|
|
29
|
+
this.lastSent = null;
|
|
30
|
+
/**
|
|
31
|
+
* Bumped on every teardown. Async work (the `measure` retry loop, the awaits
|
|
32
|
+
* in `showTabBar`) snapshots it and bails if a newer call superseded it —
|
|
33
|
+
* prevents a slow first measurement from clobbering a second `showTabBar`.
|
|
34
|
+
*/
|
|
35
|
+
this.generation = 0;
|
|
36
|
+
/** Stable identity so `removeEventListener` actually detaches the listeners. */
|
|
37
|
+
this.onReflow = () => this.scheduleSync();
|
|
38
|
+
}
|
|
39
|
+
async showTabBar(options) {
|
|
40
|
+
// A fresh call always supersedes any previous binding (bumps generation).
|
|
41
|
+
this.teardown();
|
|
42
|
+
const gen = this.generation;
|
|
43
|
+
const target = options.containerElement;
|
|
44
|
+
const wantsBinding = Capacitor.getPlatform() === 'ios' && target != null;
|
|
45
|
+
if (!wantsBinding) {
|
|
46
|
+
return this.native.showTabBar(this.stripElement(options));
|
|
47
|
+
}
|
|
48
|
+
const element = this.resolve(target);
|
|
49
|
+
if (!element) {
|
|
50
|
+
// Selector didn't match — fall back to bottom-pinned instead of throwing.
|
|
51
|
+
return this.native.showTabBar(this.stripElement(options));
|
|
52
|
+
}
|
|
53
|
+
this.element = element;
|
|
54
|
+
const bounds = await this.measure(element, gen);
|
|
55
|
+
if (gen !== this.generation)
|
|
56
|
+
return; // superseded while measuring
|
|
57
|
+
await this.native.showTabBar({ ...this.stripElement(options), bounds });
|
|
58
|
+
if (gen !== this.generation)
|
|
59
|
+
return; // superseded while the bridge call ran
|
|
60
|
+
this.lastSent = bounds;
|
|
61
|
+
this.observe(element);
|
|
62
|
+
}
|
|
63
|
+
async hideTabBar() {
|
|
64
|
+
this.teardown();
|
|
65
|
+
return this.native.hideTabBar();
|
|
66
|
+
}
|
|
67
|
+
// --- internals -----------------------------------------------------------
|
|
68
|
+
/** Removes the (possibly non-serializable) element ref before crossing the bridge. */
|
|
69
|
+
stripElement(options) {
|
|
70
|
+
if (options.containerElement == null)
|
|
71
|
+
return options;
|
|
72
|
+
const { containerElement: _drop, ...rest } = options;
|
|
73
|
+
void _drop;
|
|
74
|
+
return rest;
|
|
75
|
+
}
|
|
76
|
+
resolve(target) {
|
|
77
|
+
if (typeof target !== 'string')
|
|
78
|
+
return target;
|
|
79
|
+
return document.getElementById(target) ?? document.querySelector(target);
|
|
80
|
+
}
|
|
81
|
+
rect(element) {
|
|
82
|
+
const r = element.getBoundingClientRect();
|
|
83
|
+
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Retry until the element has a non-zero width AND height (guards against
|
|
87
|
+
* pre-layout reads). Bails early if a newer `showTabBar`/`hideTabBar` bumped
|
|
88
|
+
* the generation, so a stale interval can't outlive the call that started it.
|
|
89
|
+
* If it still measures 0 after ~3s, resolves with the zero rect (native falls
|
|
90
|
+
* back to bottom-pinned) and warns so the misconfig is visible.
|
|
91
|
+
*/
|
|
92
|
+
measure(element, gen) {
|
|
93
|
+
const valid = (b) => b.width !== 0 && b.height !== 0;
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
let bounds = this.rect(element);
|
|
96
|
+
if (valid(bounds)) {
|
|
97
|
+
resolve(bounds);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let retries = 0;
|
|
101
|
+
const id = setInterval(() => {
|
|
102
|
+
if (gen !== this.generation) {
|
|
103
|
+
clearInterval(id);
|
|
104
|
+
resolve(bounds);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
bounds = this.rect(element);
|
|
108
|
+
retries++;
|
|
109
|
+
if (valid(bounds) || retries >= 30) {
|
|
110
|
+
clearInterval(id);
|
|
111
|
+
if (!valid(bounds)) {
|
|
112
|
+
console.warn('[LiquidGlass] containerElement still measures 0 after 3s — the native bar will fall back to bottom-pinned.');
|
|
113
|
+
}
|
|
114
|
+
resolve(bounds);
|
|
115
|
+
}
|
|
116
|
+
}, 100);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
observe(element) {
|
|
120
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
121
|
+
this.resizeObserver = new ResizeObserver(this.onReflow);
|
|
122
|
+
this.resizeObserver.observe(element);
|
|
123
|
+
}
|
|
124
|
+
// Position changes a ResizeObserver won't catch. Capture phase so scrolls in
|
|
125
|
+
// nested `overflow:auto` containers (which don't bubble to window) re-sync too.
|
|
126
|
+
window.addEventListener('scroll', this.onReflow, { passive: true, capture: true });
|
|
127
|
+
window.addEventListener('resize', this.onReflow, { passive: true });
|
|
128
|
+
window.addEventListener('orientationchange', this.onReflow, { passive: true });
|
|
129
|
+
const vv = window.visualViewport;
|
|
130
|
+
if (vv) {
|
|
131
|
+
vv.addEventListener('resize', this.onReflow);
|
|
132
|
+
vv.addEventListener('scroll', this.onReflow);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
scheduleSync() {
|
|
136
|
+
if (this.rafId != null)
|
|
137
|
+
return;
|
|
138
|
+
this.rafId = requestAnimationFrame(() => {
|
|
139
|
+
this.rafId = null;
|
|
140
|
+
if (!this.element)
|
|
141
|
+
return;
|
|
142
|
+
const bounds = this.rect(this.element);
|
|
143
|
+
// Hidden / collapsed (display:none, detached, off-screen with 0 width):
|
|
144
|
+
// keep the last position, don't push a degenerate rect. A zero in EITHER
|
|
145
|
+
// dimension is useless — matches the native guard (rect.width/height > 0).
|
|
146
|
+
if (bounds.width === 0 || bounds.height === 0)
|
|
147
|
+
return;
|
|
148
|
+
// Skip redundant bridge calls when the rect didn't actually move (e.g. a
|
|
149
|
+
// scroll in an unrelated container, or a position:fixed element). Each
|
|
150
|
+
// skipped call also saves a native `layoutIfNeeded`.
|
|
151
|
+
if (this.sameRect(bounds, this.lastSent))
|
|
152
|
+
return;
|
|
153
|
+
this.lastSent = bounds;
|
|
154
|
+
void this.native.setTabBarBounds({ bounds });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
sameRect(a, b) {
|
|
158
|
+
if (!b)
|
|
159
|
+
return false;
|
|
160
|
+
return (Math.abs(a.x - b.x) < 0.5 &&
|
|
161
|
+
Math.abs(a.y - b.y) < 0.5 &&
|
|
162
|
+
Math.abs(a.width - b.width) < 0.5 &&
|
|
163
|
+
Math.abs(a.height - b.height) < 0.5);
|
|
164
|
+
}
|
|
165
|
+
teardown() {
|
|
166
|
+
// Invalidate any in-flight measure/await chain from a previous call.
|
|
167
|
+
this.generation++;
|
|
168
|
+
this.lastSent = null;
|
|
169
|
+
if (this.rafId != null) {
|
|
170
|
+
cancelAnimationFrame(this.rafId);
|
|
171
|
+
this.rafId = null;
|
|
172
|
+
}
|
|
173
|
+
this.resizeObserver?.disconnect();
|
|
174
|
+
this.resizeObserver = null;
|
|
175
|
+
// `capture` must match the add-time flag for removal to take effect.
|
|
176
|
+
window.removeEventListener('scroll', this.onReflow, { capture: true });
|
|
177
|
+
window.removeEventListener('resize', this.onReflow);
|
|
178
|
+
window.removeEventListener('orientationchange', this.onReflow);
|
|
179
|
+
const vv = window.visualViewport;
|
|
180
|
+
if (vv) {
|
|
181
|
+
vv.removeEventListener('resize', this.onReflow);
|
|
182
|
+
vv.removeEventListener('scroll', this.onReflow);
|
|
183
|
+
}
|
|
184
|
+
this.element = null;
|
|
185
|
+
}
|
|
186
|
+
}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
|
-
import type { LiquidGlassPlugin, SetSelectedTabOptions, ShowSearchBarOptions, ShowTabBarOptions, TabBarLayoutEvent, UpdateTabBadgeOptions } from './definitions';
|
|
2
|
+
import type { LiquidGlassPlugin, SetSelectedTabOptions, SetTabBarBoundsOptions, ShowSearchBarOptions, ShowTabBarOptions, TabBarLayoutEvent, UpdateTabBadgeOptions } from './definitions';
|
|
3
3
|
/**
|
|
4
4
|
* Web fallback — real Liquid Glass requires native iOS 26. On the web we
|
|
5
5
|
* resolve no-ops so the app can run in the browser during development; the
|
|
@@ -12,6 +12,7 @@ export declare class LiquidGlassWeb extends WebPlugin implements LiquidGlassPlug
|
|
|
12
12
|
setSelectedTab(_options: SetSelectedTabOptions): Promise<void>;
|
|
13
13
|
updateTabBadge(_options: UpdateTabBadgeOptions): Promise<void>;
|
|
14
14
|
getTabBarLayout(): Promise<TabBarLayoutEvent>;
|
|
15
|
+
setTabBarBounds(_options: SetTabBarBoundsOptions): Promise<void>;
|
|
15
16
|
showSearchBar(_options?: ShowSearchBarOptions): Promise<void>;
|
|
16
17
|
hideSearchBar(): Promise<void>;
|
|
17
18
|
clearSearchText(): Promise<void>;
|
package/dist/esm/web.js
CHANGED
|
@@ -21,6 +21,9 @@ export class LiquidGlassWeb extends WebPlugin {
|
|
|
21
21
|
async getTabBarLayout() {
|
|
22
22
|
return { height: 0, bottomSafeArea: 0 };
|
|
23
23
|
}
|
|
24
|
+
async setTabBarBounds(_options) {
|
|
25
|
+
// no-op on web — native-only positioning.
|
|
26
|
+
}
|
|
24
27
|
async showSearchBar(_options) {
|
|
25
28
|
// no-op on web — caller should render its own DOM search input.
|
|
26
29
|
}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -2,9 +2,222 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@capacitor/core');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Keeps the native tab bar glued to an HTML element when `containerElement` is
|
|
7
|
+
* passed to `showTabBar`. Mirrors the measure-and-observe approach of
|
|
8
|
+
* `@capacitor/google-maps`, but adapted for a **fixed overlay** rather than an
|
|
9
|
+
* inline view:
|
|
10
|
+
*
|
|
11
|
+
* - Google Maps (iOS) reparents its native view into a `WKChildScrollView` so
|
|
12
|
+
* it scrolls *with* page content. A tab bar must stay glued to the element's
|
|
13
|
+
* on-screen rect, so we instead push the rect to native, which positions an
|
|
14
|
+
* on-top overlay via Auto Layout constraints (never `setFrame` — see the
|
|
15
|
+
* iOS 26 Liquid Glass notes in `LiquidGlassTabBarOverlay.swift`).
|
|
16
|
+
* - We re-sync on `ResizeObserver` + `scroll` (capture phase, catches nested
|
|
17
|
+
* scroll containers) + `resize`/`orientationchange` + `visualViewport`
|
|
18
|
+
* (keyboard / browser-chrome / pinch-zoom), all **coalesced through a single
|
|
19
|
+
* `requestAnimationFrame`** so a 120 Hz scroll fires at most one bridge call
|
|
20
|
+
* per frame (the Maps plugin's lack of this is its main jitter source).
|
|
21
|
+
*
|
|
22
|
+
* When `containerElement` is omitted, this is a transparent pass-through to the
|
|
23
|
+
* native bottom-pinned behaviour — zero regression.
|
|
24
|
+
*/
|
|
25
|
+
class TabBarBinder {
|
|
26
|
+
constructor(native) {
|
|
27
|
+
this.native = native;
|
|
28
|
+
this.element = null;
|
|
29
|
+
this.resizeObserver = null;
|
|
30
|
+
this.rafId = null;
|
|
31
|
+
/** Last rect pushed to native — skip redundant bridge calls when unchanged. */
|
|
32
|
+
this.lastSent = null;
|
|
33
|
+
/**
|
|
34
|
+
* Bumped on every teardown. Async work (the `measure` retry loop, the awaits
|
|
35
|
+
* in `showTabBar`) snapshots it and bails if a newer call superseded it —
|
|
36
|
+
* prevents a slow first measurement from clobbering a second `showTabBar`.
|
|
37
|
+
*/
|
|
38
|
+
this.generation = 0;
|
|
39
|
+
/** Stable identity so `removeEventListener` actually detaches the listeners. */
|
|
40
|
+
this.onReflow = () => this.scheduleSync();
|
|
41
|
+
}
|
|
42
|
+
async showTabBar(options) {
|
|
43
|
+
// A fresh call always supersedes any previous binding (bumps generation).
|
|
44
|
+
this.teardown();
|
|
45
|
+
const gen = this.generation;
|
|
46
|
+
const target = options.containerElement;
|
|
47
|
+
const wantsBinding = core.Capacitor.getPlatform() === 'ios' && target != null;
|
|
48
|
+
if (!wantsBinding) {
|
|
49
|
+
return this.native.showTabBar(this.stripElement(options));
|
|
50
|
+
}
|
|
51
|
+
const element = this.resolve(target);
|
|
52
|
+
if (!element) {
|
|
53
|
+
// Selector didn't match — fall back to bottom-pinned instead of throwing.
|
|
54
|
+
return this.native.showTabBar(this.stripElement(options));
|
|
55
|
+
}
|
|
56
|
+
this.element = element;
|
|
57
|
+
const bounds = await this.measure(element, gen);
|
|
58
|
+
if (gen !== this.generation)
|
|
59
|
+
return; // superseded while measuring
|
|
60
|
+
await this.native.showTabBar({ ...this.stripElement(options), bounds });
|
|
61
|
+
if (gen !== this.generation)
|
|
62
|
+
return; // superseded while the bridge call ran
|
|
63
|
+
this.lastSent = bounds;
|
|
64
|
+
this.observe(element);
|
|
65
|
+
}
|
|
66
|
+
async hideTabBar() {
|
|
67
|
+
this.teardown();
|
|
68
|
+
return this.native.hideTabBar();
|
|
69
|
+
}
|
|
70
|
+
// --- internals -----------------------------------------------------------
|
|
71
|
+
/** Removes the (possibly non-serializable) element ref before crossing the bridge. */
|
|
72
|
+
stripElement(options) {
|
|
73
|
+
if (options.containerElement == null)
|
|
74
|
+
return options;
|
|
75
|
+
const { containerElement: _drop, ...rest } = options;
|
|
76
|
+
return rest;
|
|
77
|
+
}
|
|
78
|
+
resolve(target) {
|
|
79
|
+
if (typeof target !== 'string')
|
|
80
|
+
return target;
|
|
81
|
+
return document.getElementById(target) ?? document.querySelector(target);
|
|
82
|
+
}
|
|
83
|
+
rect(element) {
|
|
84
|
+
const r = element.getBoundingClientRect();
|
|
85
|
+
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Retry until the element has a non-zero width AND height (guards against
|
|
89
|
+
* pre-layout reads). Bails early if a newer `showTabBar`/`hideTabBar` bumped
|
|
90
|
+
* the generation, so a stale interval can't outlive the call that started it.
|
|
91
|
+
* If it still measures 0 after ~3s, resolves with the zero rect (native falls
|
|
92
|
+
* back to bottom-pinned) and warns so the misconfig is visible.
|
|
93
|
+
*/
|
|
94
|
+
measure(element, gen) {
|
|
95
|
+
const valid = (b) => b.width !== 0 && b.height !== 0;
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
let bounds = this.rect(element);
|
|
98
|
+
if (valid(bounds)) {
|
|
99
|
+
resolve(bounds);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
let retries = 0;
|
|
103
|
+
const id = setInterval(() => {
|
|
104
|
+
if (gen !== this.generation) {
|
|
105
|
+
clearInterval(id);
|
|
106
|
+
resolve(bounds);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
bounds = this.rect(element);
|
|
110
|
+
retries++;
|
|
111
|
+
if (valid(bounds) || retries >= 30) {
|
|
112
|
+
clearInterval(id);
|
|
113
|
+
if (!valid(bounds)) {
|
|
114
|
+
console.warn('[LiquidGlass] containerElement still measures 0 after 3s — the native bar will fall back to bottom-pinned.');
|
|
115
|
+
}
|
|
116
|
+
resolve(bounds);
|
|
117
|
+
}
|
|
118
|
+
}, 100);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
observe(element) {
|
|
122
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
123
|
+
this.resizeObserver = new ResizeObserver(this.onReflow);
|
|
124
|
+
this.resizeObserver.observe(element);
|
|
125
|
+
}
|
|
126
|
+
// Position changes a ResizeObserver won't catch. Capture phase so scrolls in
|
|
127
|
+
// nested `overflow:auto` containers (which don't bubble to window) re-sync too.
|
|
128
|
+
window.addEventListener('scroll', this.onReflow, { passive: true, capture: true });
|
|
129
|
+
window.addEventListener('resize', this.onReflow, { passive: true });
|
|
130
|
+
window.addEventListener('orientationchange', this.onReflow, { passive: true });
|
|
131
|
+
const vv = window.visualViewport;
|
|
132
|
+
if (vv) {
|
|
133
|
+
vv.addEventListener('resize', this.onReflow);
|
|
134
|
+
vv.addEventListener('scroll', this.onReflow);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
scheduleSync() {
|
|
138
|
+
if (this.rafId != null)
|
|
139
|
+
return;
|
|
140
|
+
this.rafId = requestAnimationFrame(() => {
|
|
141
|
+
this.rafId = null;
|
|
142
|
+
if (!this.element)
|
|
143
|
+
return;
|
|
144
|
+
const bounds = this.rect(this.element);
|
|
145
|
+
// Hidden / collapsed (display:none, detached, off-screen with 0 width):
|
|
146
|
+
// keep the last position, don't push a degenerate rect. A zero in EITHER
|
|
147
|
+
// dimension is useless — matches the native guard (rect.width/height > 0).
|
|
148
|
+
if (bounds.width === 0 || bounds.height === 0)
|
|
149
|
+
return;
|
|
150
|
+
// Skip redundant bridge calls when the rect didn't actually move (e.g. a
|
|
151
|
+
// scroll in an unrelated container, or a position:fixed element). Each
|
|
152
|
+
// skipped call also saves a native `layoutIfNeeded`.
|
|
153
|
+
if (this.sameRect(bounds, this.lastSent))
|
|
154
|
+
return;
|
|
155
|
+
this.lastSent = bounds;
|
|
156
|
+
void this.native.setTabBarBounds({ bounds });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
sameRect(a, b) {
|
|
160
|
+
if (!b)
|
|
161
|
+
return false;
|
|
162
|
+
return (Math.abs(a.x - b.x) < 0.5 &&
|
|
163
|
+
Math.abs(a.y - b.y) < 0.5 &&
|
|
164
|
+
Math.abs(a.width - b.width) < 0.5 &&
|
|
165
|
+
Math.abs(a.height - b.height) < 0.5);
|
|
166
|
+
}
|
|
167
|
+
teardown() {
|
|
168
|
+
// Invalidate any in-flight measure/await chain from a previous call.
|
|
169
|
+
this.generation++;
|
|
170
|
+
this.lastSent = null;
|
|
171
|
+
if (this.rafId != null) {
|
|
172
|
+
cancelAnimationFrame(this.rafId);
|
|
173
|
+
this.rafId = null;
|
|
174
|
+
}
|
|
175
|
+
this.resizeObserver?.disconnect();
|
|
176
|
+
this.resizeObserver = null;
|
|
177
|
+
// `capture` must match the add-time flag for removal to take effect.
|
|
178
|
+
window.removeEventListener('scroll', this.onReflow, { capture: true });
|
|
179
|
+
window.removeEventListener('resize', this.onReflow);
|
|
180
|
+
window.removeEventListener('orientationchange', this.onReflow);
|
|
181
|
+
const vv = window.visualViewport;
|
|
182
|
+
if (vv) {
|
|
183
|
+
vv.removeEventListener('resize', this.onReflow);
|
|
184
|
+
vv.removeEventListener('scroll', this.onReflow);
|
|
185
|
+
}
|
|
186
|
+
this.element = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const native = core.registerPlugin('LiquidGlass', {
|
|
6
191
|
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.LiquidGlassWeb()),
|
|
7
192
|
});
|
|
193
|
+
/**
|
|
194
|
+
* Drives the HTML-element binding lifecycle (measure + observe) on top of the
|
|
195
|
+
* native bridge. When `showTabBar` is called without `containerElement` it is a
|
|
196
|
+
* transparent pass-through, so existing callers behave exactly as before.
|
|
197
|
+
*/
|
|
198
|
+
const binder = new TabBarBinder(native);
|
|
199
|
+
/**
|
|
200
|
+
* Public plugin façade. Everything delegates straight to the native bridge
|
|
201
|
+
* except `showTabBar`/`hideTabBar`, which route through {@link TabBarBinder} so
|
|
202
|
+
* the optional `containerElement` binding works without any consumer wiring.
|
|
203
|
+
*/
|
|
204
|
+
const LiquidGlass = {
|
|
205
|
+
showTabBar: (options) => binder.showTabBar(options),
|
|
206
|
+
hideTabBar: () => binder.hideTabBar(),
|
|
207
|
+
// iOS-only: the native method only exists on iOS. On Android the bridge would
|
|
208
|
+
// throw "not implemented"; resolve quietly instead (the binding layer already
|
|
209
|
+
// gates on getPlatform() === 'ios', this guards direct low-level callers).
|
|
210
|
+
setTabBarBounds: (options) => core.Capacitor.getPlatform() === 'ios' ? native.setTabBarBounds(options) : Promise.resolve(),
|
|
211
|
+
setSelectedTab: (options) => native.setSelectedTab(options),
|
|
212
|
+
updateTabBadge: (options) => native.updateTabBadge(options),
|
|
213
|
+
getTabBarLayout: () => native.getTabBarLayout(),
|
|
214
|
+
showSearchBar: (options) => native.showSearchBar(options),
|
|
215
|
+
hideSearchBar: () => native.hideSearchBar(),
|
|
216
|
+
clearSearchText: () => native.clearSearchText(),
|
|
217
|
+
// Preserve the overloaded signature for consumers (the bind keeps `this`).
|
|
218
|
+
addListener: native.addListener.bind(native),
|
|
219
|
+
removeAllListeners: () => native.removeAllListeners(),
|
|
220
|
+
};
|
|
8
221
|
|
|
9
222
|
/**
|
|
10
223
|
* Web fallback — real Liquid Glass requires native iOS 26. On the web we
|
|
@@ -28,6 +241,9 @@ class LiquidGlassWeb extends core.WebPlugin {
|
|
|
28
241
|
async getTabBarLayout() {
|
|
29
242
|
return { height: 0, bottomSafeArea: 0 };
|
|
30
243
|
}
|
|
244
|
+
async setTabBarBounds(_options) {
|
|
245
|
+
// no-op on web — native-only positioning.
|
|
246
|
+
}
|
|
31
247
|
async showSearchBar(_options) {
|
|
32
248
|
// no-op on web — caller should render its own DOM search input.
|
|
33
249
|
}
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst LiquidGlass = registerPlugin('LiquidGlass', {\n web: () => import('./web').then((m) => new m.LiquidGlassWeb()),\n});\nexport * from './definitions';\nexport { LiquidGlass };\n","import { WebPlugin } from '@capacitor/core';\n/**\n * Web fallback — real Liquid Glass requires native iOS 26. On the web we\n * resolve no-ops so the app can run in the browser during development; the\n * Angular shell is expected to render its own CSS glassmorphism tab bar when\n * `isNativePlatform()` is false.\n */\nexport class LiquidGlassWeb extends WebPlugin {\n async showTabBar(_options) {\n // no-op on web\n }\n async hideTabBar() {\n // no-op on web\n }\n async setSelectedTab(_options) {\n // no-op on web\n }\n async updateTabBadge(_options) {\n // no-op on web\n }\n async getTabBarLayout() {\n return { height: 0, bottomSafeArea: 0 };\n }\n async showSearchBar(_options) {\n // no-op on web — caller should render its own DOM search input.\n }\n async hideSearchBar() {\n // no-op on web\n }\n async clearSearchText() {\n // no-op on web\n }\n}\n"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,WAAW,GAAGA,mBAAc,CAAC,aAAa,EAAE;AAClD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AAClE,CAAC;;ACFD;AACA;AACA;AACA;AACA;AACA;AACO,MAAM,cAAc,SAASC,cAAS,CAAC;AAC9C,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE;AAC/B;AACA,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB;AACA,IAAI;AACJ,IAAI,MAAM,cAAc,CAAC,QAAQ,EAAE;AACnC;AACA,IAAI;AACJ,IAAI,MAAM,cAAc,CAAC,QAAQ,EAAE;AACnC;AACA,IAAI;AACJ,IAAI,MAAM,eAAe,GAAG;AAC5B,QAAQ,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE;AAC/C,IAAI;AACJ,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;AAClC;AACA,IAAI;AACJ,IAAI,MAAM,aAAa,GAAG;AAC1B;AACA,IAAI;AACJ,IAAI,MAAM,eAAe,GAAG;AAC5B;AACA,IAAI;AACJ;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/tab-bar-binder.js","esm/index.js","esm/web.js"],"sourcesContent":["import { Capacitor } from '@capacitor/core';\n/**\n * Keeps the native tab bar glued to an HTML element when `containerElement` is\n * passed to `showTabBar`. Mirrors the measure-and-observe approach of\n * `@capacitor/google-maps`, but adapted for a **fixed overlay** rather than an\n * inline view:\n *\n * - Google Maps (iOS) reparents its native view into a `WKChildScrollView` so\n * it scrolls *with* page content. A tab bar must stay glued to the element's\n * on-screen rect, so we instead push the rect to native, which positions an\n * on-top overlay via Auto Layout constraints (never `setFrame` — see the\n * iOS 26 Liquid Glass notes in `LiquidGlassTabBarOverlay.swift`).\n * - We re-sync on `ResizeObserver` + `scroll` (capture phase, catches nested\n * scroll containers) + `resize`/`orientationchange` + `visualViewport`\n * (keyboard / browser-chrome / pinch-zoom), all **coalesced through a single\n * `requestAnimationFrame`** so a 120 Hz scroll fires at most one bridge call\n * per frame (the Maps plugin's lack of this is its main jitter source).\n *\n * When `containerElement` is omitted, this is a transparent pass-through to the\n * native bottom-pinned behaviour — zero regression.\n */\nexport class TabBarBinder {\n constructor(native) {\n this.native = native;\n this.element = null;\n this.resizeObserver = null;\n this.rafId = null;\n /** Last rect pushed to native — skip redundant bridge calls when unchanged. */\n this.lastSent = null;\n /**\n * Bumped on every teardown. Async work (the `measure` retry loop, the awaits\n * in `showTabBar`) snapshots it and bails if a newer call superseded it —\n * prevents a slow first measurement from clobbering a second `showTabBar`.\n */\n this.generation = 0;\n /** Stable identity so `removeEventListener` actually detaches the listeners. */\n this.onReflow = () => this.scheduleSync();\n }\n async showTabBar(options) {\n // A fresh call always supersedes any previous binding (bumps generation).\n this.teardown();\n const gen = this.generation;\n const target = options.containerElement;\n const wantsBinding = Capacitor.getPlatform() === 'ios' && target != null;\n if (!wantsBinding) {\n return this.native.showTabBar(this.stripElement(options));\n }\n const element = this.resolve(target);\n if (!element) {\n // Selector didn't match — fall back to bottom-pinned instead of throwing.\n return this.native.showTabBar(this.stripElement(options));\n }\n this.element = element;\n const bounds = await this.measure(element, gen);\n if (gen !== this.generation)\n return; // superseded while measuring\n await this.native.showTabBar({ ...this.stripElement(options), bounds });\n if (gen !== this.generation)\n return; // superseded while the bridge call ran\n this.lastSent = bounds;\n this.observe(element);\n }\n async hideTabBar() {\n this.teardown();\n return this.native.hideTabBar();\n }\n // --- internals -----------------------------------------------------------\n /** Removes the (possibly non-serializable) element ref before crossing the bridge. */\n stripElement(options) {\n if (options.containerElement == null)\n return options;\n const { containerElement: _drop, ...rest } = options;\n void _drop;\n return rest;\n }\n resolve(target) {\n if (typeof target !== 'string')\n return target;\n return document.getElementById(target) ?? document.querySelector(target);\n }\n rect(element) {\n const r = element.getBoundingClientRect();\n return { x: r.x, y: r.y, width: r.width, height: r.height };\n }\n /**\n * Retry until the element has a non-zero width AND height (guards against\n * pre-layout reads). Bails early if a newer `showTabBar`/`hideTabBar` bumped\n * the generation, so a stale interval can't outlive the call that started it.\n * If it still measures 0 after ~3s, resolves with the zero rect (native falls\n * back to bottom-pinned) and warns so the misconfig is visible.\n */\n measure(element, gen) {\n const valid = (b) => b.width !== 0 && b.height !== 0;\n return new Promise((resolve) => {\n let bounds = this.rect(element);\n if (valid(bounds)) {\n resolve(bounds);\n return;\n }\n let retries = 0;\n const id = setInterval(() => {\n if (gen !== this.generation) {\n clearInterval(id);\n resolve(bounds);\n return;\n }\n bounds = this.rect(element);\n retries++;\n if (valid(bounds) || retries >= 30) {\n clearInterval(id);\n if (!valid(bounds)) {\n console.warn('[LiquidGlass] containerElement still measures 0 after 3s — the native bar will fall back to bottom-pinned.');\n }\n resolve(bounds);\n }\n }, 100);\n });\n }\n observe(element) {\n if (typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(this.onReflow);\n this.resizeObserver.observe(element);\n }\n // Position changes a ResizeObserver won't catch. Capture phase so scrolls in\n // nested `overflow:auto` containers (which don't bubble to window) re-sync too.\n window.addEventListener('scroll', this.onReflow, { passive: true, capture: true });\n window.addEventListener('resize', this.onReflow, { passive: true });\n window.addEventListener('orientationchange', this.onReflow, { passive: true });\n const vv = window.visualViewport;\n if (vv) {\n vv.addEventListener('resize', this.onReflow);\n vv.addEventListener('scroll', this.onReflow);\n }\n }\n scheduleSync() {\n if (this.rafId != null)\n return;\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null;\n if (!this.element)\n return;\n const bounds = this.rect(this.element);\n // Hidden / collapsed (display:none, detached, off-screen with 0 width):\n // keep the last position, don't push a degenerate rect. A zero in EITHER\n // dimension is useless — matches the native guard (rect.width/height > 0).\n if (bounds.width === 0 || bounds.height === 0)\n return;\n // Skip redundant bridge calls when the rect didn't actually move (e.g. a\n // scroll in an unrelated container, or a position:fixed element). Each\n // skipped call also saves a native `layoutIfNeeded`.\n if (this.sameRect(bounds, this.lastSent))\n return;\n this.lastSent = bounds;\n void this.native.setTabBarBounds({ bounds });\n });\n }\n sameRect(a, b) {\n if (!b)\n return false;\n return (Math.abs(a.x - b.x) < 0.5 &&\n Math.abs(a.y - b.y) < 0.5 &&\n Math.abs(a.width - b.width) < 0.5 &&\n Math.abs(a.height - b.height) < 0.5);\n }\n teardown() {\n // Invalidate any in-flight measure/await chain from a previous call.\n this.generation++;\n this.lastSent = null;\n if (this.rafId != null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n // `capture` must match the add-time flag for removal to take effect.\n window.removeEventListener('scroll', this.onReflow, { capture: true });\n window.removeEventListener('resize', this.onReflow);\n window.removeEventListener('orientationchange', this.onReflow);\n const vv = window.visualViewport;\n if (vv) {\n vv.removeEventListener('resize', this.onReflow);\n vv.removeEventListener('scroll', this.onReflow);\n }\n this.element = null;\n }\n}\n","import { Capacitor, registerPlugin } from '@capacitor/core';\nimport { TabBarBinder } from './tab-bar-binder';\nconst native = registerPlugin('LiquidGlass', {\n web: () => import('./web').then((m) => new m.LiquidGlassWeb()),\n});\n/**\n * Drives the HTML-element binding lifecycle (measure + observe) on top of the\n * native bridge. When `showTabBar` is called without `containerElement` it is a\n * transparent pass-through, so existing callers behave exactly as before.\n */\nconst binder = new TabBarBinder(native);\n/**\n * Public plugin façade. Everything delegates straight to the native bridge\n * except `showTabBar`/`hideTabBar`, which route through {@link TabBarBinder} so\n * the optional `containerElement` binding works without any consumer wiring.\n */\nconst LiquidGlass = {\n showTabBar: (options) => binder.showTabBar(options),\n hideTabBar: () => binder.hideTabBar(),\n // iOS-only: the native method only exists on iOS. On Android the bridge would\n // throw \"not implemented\"; resolve quietly instead (the binding layer already\n // gates on getPlatform() === 'ios', this guards direct low-level callers).\n setTabBarBounds: (options) => Capacitor.getPlatform() === 'ios' ? native.setTabBarBounds(options) : Promise.resolve(),\n setSelectedTab: (options) => native.setSelectedTab(options),\n updateTabBadge: (options) => native.updateTabBadge(options),\n getTabBarLayout: () => native.getTabBarLayout(),\n showSearchBar: (options) => native.showSearchBar(options),\n hideSearchBar: () => native.hideSearchBar(),\n clearSearchText: () => native.clearSearchText(),\n // Preserve the overloaded signature for consumers (the bind keeps `this`).\n addListener: native.addListener.bind(native),\n removeAllListeners: () => native.removeAllListeners(),\n};\nexport * from './definitions';\nexport { LiquidGlass };\n","import { WebPlugin } from '@capacitor/core';\n/**\n * Web fallback — real Liquid Glass requires native iOS 26. On the web we\n * resolve no-ops so the app can run in the browser during development; the\n * Angular shell is expected to render its own CSS glassmorphism tab bar when\n * `isNativePlatform()` is false.\n */\nexport class LiquidGlassWeb extends WebPlugin {\n async showTabBar(_options) {\n // no-op on web\n }\n async hideTabBar() {\n // no-op on web\n }\n async setSelectedTab(_options) {\n // no-op on web\n }\n async updateTabBadge(_options) {\n // no-op on web\n }\n async getTabBarLayout() {\n return { height: 0, bottomSafeArea: 0 };\n }\n async setTabBarBounds(_options) {\n // no-op on web — native-only positioning.\n }\n async showSearchBar(_options) {\n // no-op on web — caller should render its own DOM search input.\n }\n async hideSearchBar() {\n // no-op on web\n }\n async clearSearchText() {\n // no-op on web\n }\n}\n"],"names":["Capacitor","registerPlugin","WebPlugin"],"mappings":"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"}
|