Skip to content

Commit 73b8c3e

Browse files
committed
feat(mobile): native ios haptics + search-overlay close-on-enter
- Add tauri-plugin-haptics (mobile-only) so iOS gets real Taptic Engine feedback; navigator.vibrate is a no-op in WKWebView. triggerHaptic routes through the plugin on Tauri mobile, falls back to navigator.vibrate on web/desktop. Scoped to a mobile.json capability so the desktop build doesn't see the mobile-only permission. - MobileSearchOverlay: Enter submits the query and closes the overlay so results show in the list.
1 parent 172e142 commit 73b8c3e

8 files changed

Lines changed: 80 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@tauri-apps/plugin-dialog": "^2.7.0",
6464
"@tauri-apps/plugin-fs": "^2.5.0",
6565
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
66+
"@tauri-apps/plugin-haptics": "2.3.2",
6667
"@tauri-apps/plugin-notification": "^2.3.3",
6768
"@tauri-apps/plugin-opener": "^2.5.3",
6869
"@tauri-apps/plugin-os": "^2.3.2",

pnpm-lock.yaml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ tauri-plugin-webdriver = { version = "0.2.1", optional = true }
5959
[target.'cfg(not(target_os = "ios"))'.dependencies]
6060
tauri-plugin-remote-push = "1.0.10"
6161

62+
# Mobile-only: native haptic feedback (iOS Taptic Engine + Android vibrator).
63+
# navigator.vibrate is a no-op inside the iOS WKWebView, so iOS gets no haptics
64+
# without this plugin; the JS bridge falls back to navigator.vibrate elsewhere.
65+
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
66+
tauri-plugin-haptics = "2"
67+
6268
# macOS-only: Objective-C bindings for dock badge + the custom NSOpenPanel
6369
# wrapper that works around the rfd 0.16 / objc2-app-kit 0.3.2 panic on
6470
# macOS 26 Tahoe (the upstream openPanel binding is non-nullable but the

src-tauri/capabilities/mobile.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/tauri-v2.0.0/crates/tauri-cli/capabilities-schema.json",
3+
"identifier": "mobile",
4+
"description": "Mobile-only permissions for plugins not available on desktop (native haptics).",
5+
"windows": ["main"],
6+
"permissions": ["haptics:default"],
7+
"platforms": ["android", "iOS"]
8+
}

src-tauri/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,13 @@ pub fn run() {
807807
builder = builder.plugin(tauri_plugin_webdriver::init());
808808
}
809809

810+
// Native haptic feedback (iOS Taptic Engine / Android vibrator). Mobile-only:
811+
// navigator.vibrate is a no-op in the iOS WKWebView.
812+
#[cfg(mobile)]
813+
{
814+
builder = builder.plugin(tauri_plugin_haptics::init());
815+
}
816+
810817
builder
811818
.plugin(tauri_plugin_notification::init())
812819
.plugin(tauri_plugin_deep_link::init())

src/svelte/components/MobileSearchOverlay.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@
7878
};
7979
8080
const handleKeydown = (e: KeyboardEvent) => {
81-
if (e.key === 'Escape') handleClose();
81+
if (e.key === 'Escape') {
82+
handleClose();
83+
} else if (e.key === 'Enter') {
84+
// Submit the search and return to the list to see results — the overlay
85+
// only renders suggestions, so staying open would hide the results.
86+
e.preventDefault();
87+
onSearch?.(localQuery);
88+
handleClose();
89+
}
8290
};
8391
8492
// Group suggestions by type for display

src/utils/tauri-bridge.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
* - Event payloads are type-checked before dispatch.
1717
*/
1818

19+
import { isTauriMobile } from './platform.js';
20+
1921
let _invoke;
2022
let _listen;
2123
let _emit;
@@ -262,6 +264,13 @@ export async function onBackButton(handler) {
262264
* No-op on platforms without navigator.vibrate.
263265
*/
264266
export function triggerHaptic(style = 'light') {
267+
// iOS WKWebView has no navigator.vibrate, so route through the native haptics
268+
// plugin on Tauri mobile (works on Android too, with proper iOS-style impact /
269+
// notification feedback). Fall back to navigator.vibrate on web/desktop.
270+
if (isTauriMobile) {
271+
void nativeHaptic(style);
272+
return;
273+
}
265274
if (typeof navigator === 'undefined' || !navigator.vibrate) return;
266275
const patterns = {
267276
light: 10,
@@ -273,6 +282,21 @@ export function triggerHaptic(style = 'light') {
273282
navigator.vibrate(patterns[style] || 10);
274283
}
275284

285+
async function nativeHaptic(style) {
286+
try {
287+
const haptics = await import('@tauri-apps/plugin-haptics');
288+
if (style === 'success' || style === 'warning' || style === 'error') {
289+
await haptics.notificationFeedback(style);
290+
} else if (style === 'medium' || style === 'heavy') {
291+
await haptics.impactFeedback(style);
292+
} else {
293+
await haptics.impactFeedback('light');
294+
}
295+
} catch {
296+
// Plugin not registered in this binary (e.g. desktop) — best-effort no-op.
297+
}
298+
}
299+
276300
/**
277301
* Register a handler for Android share intents (ACTION_SEND).
278302
* The Android MainActivity dispatches 'app:share-received' CustomEvents.

0 commit comments

Comments
 (0)