react-native-browser-with-polyfill 1.0.0 → 1.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.
@@ -1,670 +0,0 @@
1
- // ==UserScript==
2
- // @name Dev Keyboard Bar
3
- // @namespace dev-keyboard-bar
4
- // @description Floating dev keyboard (Esc, Ctrl, Alt, Tab, arrows, symbols) for mobile browsers
5
- // @match *://*/*
6
- // @grant none
7
- // @version 1.1
8
- // @run-at document-idle
9
- // ==/UserScript==
10
-
11
- (function () {
12
- 'use strict';
13
-
14
- var DKB_PREFIX = 'dkb-';
15
-
16
- /* ─── Mobile Detection ─── */
17
- function isMobile() {
18
- if (typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches) {
19
- return true;
20
- }
21
- var ua = navigator.userAgent || '';
22
- return /Android|iPhone|iPad|iPod|webOS|BlackBerry|IEMobile|Opera Mini/i.test(ua);
23
- }
24
-
25
- /* ─── State ─── */
26
- var activeModifiers = { ctrl: false, alt: false, shift: false, meta: false };
27
- var autoResetTimer = null;
28
- var barVisible = false;
29
- var f1RowExpanded = false;
30
- var primaryTarget = null;
31
- var hiddenInput = null;
32
-
33
- /* ─── CSS Injection ─── */
34
- function injectCSS() {
35
- if (document.getElementById(DKB_PREFIX + 'css')) return;
36
- var style = document.createElement('style');
37
- style.id = DKB_PREFIX + 'css';
38
- style.textContent =
39
- '#' + DKB_PREFIX + 'bar {' +
40
- 'position:fixed;' +
41
- 'left:0;right:0;' +
42
- 'bottom:0;' +
43
- 'max-height:50vh;' +
44
- 'z-index:999999;' +
45
- 'background:#1e1e1e;' +
46
- 'color:#fff;' +
47
- 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;' +
48
- 'font-size:13px;' +
49
- 'line-height:1;' +
50
- 'user-select:none;' +
51
- '-webkit-user-select:none;' +
52
- '-webkit-touch-callout:none;' +
53
- 'touch-action:manipulation;' +
54
- 'overflow-x:auto;' +
55
- 'overflow-y:hidden;' +
56
- 'white-space:nowrap;' +
57
- 'display:none;' +
58
- 'flex-direction:column;' +
59
- 'gap:4px;' +
60
- 'padding:6px env(safe-area-inset-right,8px) env(safe-area-inset-bottom,8px) env(safe-area-inset-left,8px);' +
61
- 'box-sizing:border-box;' +
62
- 'border-top:1px solid #333;' +
63
- '}' +
64
- '#' + DKB_PREFIX + 'bar.dkb-visible {' +
65
- 'display:flex;' +
66
- '}' +
67
- '#' + DKB_PREFIX + 'row {' +
68
- 'display:flex;' +
69
- 'gap:4px;' +
70
- 'height:40px;' +
71
- 'flex-shrink:0;' +
72
- '}' +
73
- '#' + DKB_PREFIX + 'f1row {' +
74
- 'display:flex;' +
75
- 'gap:4px;' +
76
- 'height:40px;' +
77
- 'flex-shrink:0;' +
78
- 'overflow-x:auto;' +
79
- 'flex-wrap:nowrap;' +
80
- 'max-height:0;' +
81
- 'opacity:0;' +
82
- 'transition:max-height 0.2s ease,opacity 0.2s ease;' +
83
- 'padding:0;' +
84
- '}' +
85
- '#' + DKB_PREFIX + 'f1row.dkb-expanded {' +
86
- 'max-height:48px;' +
87
- 'opacity:1;' +
88
- 'padding:0;' +
89
- '}' +
90
- '.' + DKB_PREFIX + 'btn {' +
91
- 'height:32px;' +
92
- 'min-width:36px;' +
93
- 'padding:0 8px;' +
94
- 'border:1px solid #444;' +
95
- 'border-radius:6px;' +
96
- 'background:#2a2a2a;' +
97
- 'color:#fff;' +
98
- 'font-size:13px;' +
99
- 'font-weight:400;' +
100
- 'cursor:pointer;' +
101
- 'display:inline-flex;' +
102
- 'align-items:center;' +
103
- 'justify-content:center;' +
104
- 'flex-shrink:0;' +
105
- 'touch-action:manipulation;' +
106
- '-webkit-tap-highlight-color:transparent;' +
107
- 'white-space:nowrap;' +
108
- 'overflow:hidden;' +
109
- 'text-overflow:ellipsis;' +
110
- '}' +
111
- '.' + DKB_PREFIX + 'btn:active {' +
112
- 'background:#555;' +
113
- '}' +
114
- '.' + DKB_PREFIX + 'btn.modifier-active {' +
115
- 'background:#007acc;' +
116
- 'border-color:#007acc;' +
117
- 'font-weight:700;' +
118
- '}' +
119
- '.' + DKB_PREFIX + 'btn-symbol {' +
120
- 'font-size:14px;' +
121
- 'font-weight:600;' +
122
- '}' +
123
- '.' + DKB_PREFIX + 'btn-collapse {' +
124
- 'min-width:32px;' +
125
- 'font-size:11px;' +
126
- 'opacity:0.7;' +
127
- '}' +
128
- '#hidden-input-dkb {' +
129
- 'position:fixed;' +
130
- 'left:-9999px;' +
131
- 'top:-9999px;' +
132
- 'opacity:0;' +
133
- 'width:1px;' +
134
- 'height:1px;' +
135
- 'pointer-events:none;' +
136
- '}' +
137
- '#' + DKB_PREFIX + 'bar::-webkit-scrollbar {' +
138
- 'display:none;' +
139
- '}' +
140
- '#' + DKB_PREFIX + 'bar {' +
141
- '-ms-overflow-style:none;' +
142
- 'scrollbar-width:none;' +
143
- '}' +
144
- '@media(max-width:360px) {' +
145
- '.' + DKB_PREFIX + 'btn {' +
146
- 'min-width:30px;' +
147
- 'font-size:11px;' +
148
- 'padding:0 5px;' +
149
- '}' +
150
- '}' +
151
- '@media(max-width:320px) {' +
152
- '.' + DKB_PREFIX + 'btn {' +
153
- 'min-width:26px;' +
154
- 'font-size:10px;' +
155
- 'padding:0 3px;' +
156
- '}' +
157
- '}';
158
- document.head.appendChild(style);
159
- }
160
-
161
- /* ─── Hidden Input (iOS Safari trick — keeps keyboard open) ─── */
162
- function createHiddenInput() {
163
- if (hiddenInput) return hiddenInput;
164
- hiddenInput = document.createElement('input');
165
- hiddenInput.type = 'text';
166
- hiddenInput.id = 'hidden-input-dkb';
167
- hiddenInput.setAttribute('autocomplete', 'off');
168
- hiddenInput.setAttribute('autocorrect', 'off');
169
- hiddenInput.setAttribute('autocapitalize', 'off');
170
- hiddenInput.setAttribute('spellcheck', 'false');
171
- document.body.appendChild(hiddenInput);
172
- return hiddenInput;
173
- }
174
-
175
- function focusHiddenInput() {
176
- var hi = createHiddenInput();
177
- try { hi.focus({ preventScroll: true }); } catch (e) { /* ignore */ }
178
- }
179
-
180
- /* ─── Keyboard Event Dispatch ─── */
181
- function dispatchKey(target, key, code, keyCode, modifiers) {
182
- if (!target) return;
183
- modifiers = modifiers || activeModifiers;
184
- var opts = {
185
- key: key,
186
- code: code,
187
- keyCode: keyCode,
188
- ctrlKey: !!modifiers.ctrl,
189
- altKey: !!modifiers.alt,
190
- shiftKey: !!modifiers.shift,
191
- metaKey: !!modifiers.meta,
192
- bubbles: true,
193
- cancelable: true,
194
- composed: true
195
- };
196
-
197
- target.dispatchEvent(new KeyboardEvent('keydown', opts));
198
- target.dispatchEvent(new KeyboardEvent('keyup', opts));
199
- }
200
-
201
- function insertChar(target, char) {
202
- if (!target) return;
203
- // Use execCommand for contenteditable / textarea / input
204
- if (typeof document.execCommand === 'function') {
205
- try { document.execCommand('insertText', false, char); return; } catch (e) { /* fallback */ }
206
- }
207
- // Fallback: InputEvent
208
- var inputEvent = new InputEvent('input', {
209
- data: char,
210
- inputType: 'insertText',
211
- bubbles: true,
212
- cancelable: true
213
- });
214
- target.dispatchEvent(inputEvent);
215
- }
216
-
217
- /* ─── Modifier Handling ─── */
218
- function resetModifiers() {
219
- activeModifiers = { ctrl: false, alt: false, shift: false, meta: false };
220
- clearAutoResetTimer();
221
- updateModifierButtons();
222
- }
223
-
224
- function clearAutoResetTimer() {
225
- if (autoResetTimer) {
226
- clearTimeout(autoResetTimer);
227
- autoResetTimer = null;
228
- }
229
- }
230
-
231
- function startAutoResetTimer() {
232
- clearAutoResetTimer();
233
- autoResetTimer = setTimeout(function () {
234
- if (hasActiveModifiers()) {
235
- resetModifiers();
236
- }
237
- }, 3000);
238
- }
239
-
240
- function hasActiveModifiers() {
241
- return activeModifiers.ctrl || activeModifiers.alt || activeModifiers.shift || activeModifiers.meta;
242
- }
243
-
244
- function setActiveModifier(modName, value) {
245
- activeModifiers[modName] = value;
246
- updateModifierButtons();
247
- if (value) {
248
- startAutoResetTimer();
249
- } else {
250
- if (!hasActiveModifiers()) {
251
- clearAutoResetTimer();
252
- }
253
- }
254
- }
255
-
256
- function updateModifierButtons() {
257
- var names = ['dkb-btn-ctrl', 'dkb-btn-alt', 'dkb-btn-shift', 'dkb-btn-meta'];
258
- var keys = ['ctrl', 'alt', 'shift', 'meta'];
259
- for (var i = 0; i < names.length; i++) {
260
- var btn = document.getElementById(names[i]);
261
- if (!btn) continue;
262
- if (activeModifiers[keys[i]]) {
263
- btn.classList.add('modifier-active');
264
- } else {
265
- btn.classList.remove('modifier-active');
266
- }
267
- }
268
- }
269
-
270
- /* ─── Detect Target Element ─── */
271
- function detectTarget() {
272
- var active = document.activeElement;
273
- if (!active || active === document.body) return null;
274
-
275
- // xterm.js helper textarea (code-server / VS Code web terminal)
276
- if (active.classList && active.classList.contains('xterm-helper-textarea')) return active;
277
- var xterm = active.closest && active.closest('.xterm-helper-textarea');
278
- if (xterm) return xterm;
279
-
280
- // Monaco editor textarea
281
- if (active.classList && active.classList.contains('monaco-editor')) {
282
- var mt = active.querySelector('textarea') || active.querySelector('.monaco-inputbox input');
283
- if (mt) return mt;
284
- }
285
- var monacoInput = active.closest && (active.closest('.monaco-editor') || active.closest('.monaco-inputbox'));
286
- if (monacoInput) {
287
- var mi = monacoInput.querySelector('textarea') || monacoInput.querySelector('input[type="text"]');
288
- if (mi) return mi;
289
- }
290
-
291
- // Regular text elements
292
- var tag = active.tagName.toLowerCase();
293
- if (tag === 'input') {
294
- var inputType = (active.getAttribute('type') || 'text').toLowerCase();
295
- if (inputType === 'text' || inputType === 'search' || inputType === 'email' ||
296
- inputType === 'password' || inputType === 'url' || inputType === 'tel' ||
297
- inputType === 'number') {
298
- return active;
299
- }
300
- return null;
301
- }
302
- if (tag === 'textarea' || active.getAttribute('contenteditable') === 'true') {
303
- return active;
304
- }
305
-
306
- // Walk up from activeElement looking for focusable elements
307
- var el = active;
308
- for (var depth = 0; depth < 5; depth++) {
309
- if (!el || el === document.body) break;
310
- tag = el.tagName ? el.tagName.toLowerCase() : '';
311
- if (tag === 'input') {
312
- var it = (el.getAttribute('type') || 'text').toLowerCase();
313
- if (it === 'text' || it === 'search' || it === 'email' || it === 'password' ||
314
- it === 'url' || it === 'tel' || it === 'number') {
315
- return el;
316
- }
317
- }
318
- if (tag === 'textarea') return el;
319
- if (el.getAttribute && el.getAttribute('contenteditable') === 'true') return el;
320
- if (el.classList && el.classList.contains('xterm-helper-textarea')) return el;
321
- if (el.classList && el.classList.contains('monaco-editor')) {
322
- var mt2 = el.querySelector('textarea') || el.querySelector('.monaco-inputbox input');
323
- if (mt2) return mt2;
324
- }
325
- el = el.parentElement;
326
- }
327
-
328
- return null;
329
- }
330
-
331
- /* ─── Build Bar UI ─── */
332
- function createBar() {
333
- if (document.getElementById(DKB_PREFIX + 'bar')) return;
334
-
335
- var bar = document.createElement('div');
336
- bar.id = DKB_PREFIX + 'bar';
337
- bar.setAttribute('role', 'toolbar');
338
- bar.setAttribute('aria-label', 'Developer Keyboard Bar');
339
-
340
- // Prevent ALL touch/mouse events on the bar from reaching the document
341
- // This is the key to keeping the keyboard open
342
- bar.addEventListener('touchstart', function (e) { e.stopPropagation(); }, { passive: false });
343
- bar.addEventListener('touchend', function (e) { e.stopPropagation(); }, { passive: false });
344
- bar.addEventListener('mousedown', function (e) { e.stopPropagation(); });
345
-
346
- // Primary row
347
- var primaryRow = document.createElement('div');
348
- primaryRow.id = DKB_PREFIX + 'row';
349
-
350
- var primaryKeys = [
351
- { id: 'dkb-btn-esc', key: 'Escape', code: 'Escape', keyCode: 27, label: 'Esc' },
352
- { id: 'dkb-btn-tab', key: 'Tab', code: 'Tab', keyCode: 9, label: 'Tab' },
353
- { id: 'dkb-btn-ctrl', key: 'Control', code: 'ControlLeft', keyCode: 17, label: 'Ctrl', modifier: 'ctrl' },
354
- { id: 'dkb-btn-alt', key: 'Alt', code: 'AltLeft', keyCode: 18, label: 'Alt', modifier: 'alt' },
355
- { id: 'dkb-btn-meta', key: 'Meta', code: 'MetaLeft', keyCode: 91, label: '⌘', modifier: 'meta' },
356
- { id: 'dkb-btn-shift', key: 'Shift', code: 'ShiftLeft', keyCode: 16, label: 'Shift', modifier: 'shift' },
357
- { id: 'dkb-btn-up', key: 'ArrowUp', code: 'ArrowUp', keyCode: 38, label: '↑' },
358
- { id: 'dkb-btn-down', key: 'ArrowDown', code: 'ArrowDown', keyCode: 40, label: '↓' },
359
- { id: 'dkb-btn-left', key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37, label: '←' },
360
- { id: 'dkb-btn-right', key: 'ArrowRight',code: 'ArrowRight', keyCode: 39, label: '→' },
361
- { id: 'dkb-btn-tilde', key: '~', code: 'Backquote', keyCode: 192, label: '~', symbol: '~' },
362
- { id: 'dkb-btn-minus', key: '-', code: 'Minus', keyCode: 189, label: '-', symbol: '-' },
363
- { id: 'dkb-btn-underscore', key: '_', code: 'Minus', keyCode: 189, label: '_', symbol: '_' },
364
- { id: 'dkb-btn-slash', key: '/', code: 'Slash', keyCode: 191, label: '/', symbol: '/' },
365
- { id: 'dkb-btn-pipe', key: '|', code: 'Backslash', keyCode: 220, label: '|', symbol: '|' },
366
- { id: 'dkb-btn-lbrace',key: '{', code: 'BracketLeft', keyCode: 219, label: '{', symbol: '{' },
367
- { id: 'dkb-btn-rbrace',key: '}', code: 'BracketRight', keyCode: 221, label: '}', symbol: '}' },
368
- { id: 'dkb-btn-lbracket',key: '[', code: 'BracketLeft', keyCode: 219, label: '[', symbol: '[' },
369
- { id: 'dkb-btn-rbracket',key: ']', code: 'BracketRight', keyCode: 221, label: ']', symbol: ']' },
370
- { id: 'dkb-btn-lparen',key: '(', code: 'Digit9', keyCode: 57, label: '(', symbol: '(' },
371
- { id: 'dkb-btn-rparen',key: ')', code: 'Digit0', keyCode: 48, label: ')', symbol: ')' }
372
- ];
373
-
374
- for (var i = 0; i < primaryKeys.length; i++) {
375
- var btn = document.createElement('button');
376
- btn.id = primaryKeys[i].id;
377
- btn.className = DKB_PREFIX + 'btn';
378
- if (primaryKeys[i].symbol) btn.classList.add(DKB_PREFIX + 'btn-symbol');
379
- btn.textContent = primaryKeys[i].label;
380
- btn.setAttribute('data-key', primaryKeys[i].key);
381
- btn.setAttribute('data-code', primaryKeys[i].code);
382
- btn.setAttribute('data-keycode', primaryKeys[i].keyCode);
383
- btn.setAttribute('data-symbol', primaryKeys[i].symbol || '');
384
- btn.setAttribute('data-modifier', primaryKeys[i].modifier || '');
385
- primaryRow.appendChild(btn);
386
- }
387
-
388
- // F1-F12 toggle button
389
- var collapseBtn = document.createElement('button');
390
- collapseBtn.className = DKB_PREFIX + 'btn ' + DKB_PREFIX + 'btn-collapse';
391
- collapseBtn.id = DKB_PREFIX + 'btn-expand';
392
- collapseBtn.textContent = 'F1–F12';
393
- collapseBtn.setAttribute('data-key', 'F1–F12');
394
- collapseBtn.setAttribute('aria-label', 'Expand F1-F12 row');
395
- primaryRow.appendChild(collapseBtn);
396
-
397
- bar.appendChild(primaryRow);
398
-
399
- // F1-F12 row
400
- var f1Row = document.createElement('div');
401
- f1Row.id = DKB_PREFIX + 'f1row';
402
- for (var f = 1; f <= 12; f++) {
403
- var f1Btn = document.createElement('button');
404
- f1Btn.className = DKB_PREFIX + 'btn';
405
- f1Btn.textContent = 'F' + f;
406
- f1Btn.setAttribute('data-key', 'F' + f);
407
- f1Btn.setAttribute('data-code', 'F' + f);
408
- f1Btn.setAttribute('data-keycode', 111 + f - 1);
409
- f1Row.appendChild(f1Btn);
410
- }
411
- bar.appendChild(f1Row);
412
-
413
- document.body.appendChild(bar);
414
- }
415
-
416
- /* ─── Position Bar ─── */
417
- function positionBar() {
418
- var bar = document.getElementById(DKB_PREFIX + 'bar');
419
- if (!bar) return;
420
- if (typeof visualViewport !== 'undefined' && visualViewport) {
421
- // Position above the keyboard with a small gap
422
- var kbHeight = window.innerHeight - visualViewport.height;
423
- bar.style.bottom = (kbHeight + 8) + 'px';
424
- } else {
425
- bar.style.bottom = '0px';
426
- }
427
- }
428
-
429
- /* ─── Show / Hide Bar ─── */
430
- function showBar() {
431
- if (barVisible) return;
432
- barVisible = true;
433
- var bar = document.getElementById(DKB_PREFIX + 'bar');
434
- if (!bar) return;
435
- bar.classList.add('dkb-visible');
436
- createHiddenInput();
437
- positionBar();
438
- updateModifierButtons();
439
- // Don't steal focus — keep whatever the user was typing in focused
440
- }
441
-
442
- function hideBar() {
443
- if (!barVisible) return;
444
- barVisible = false;
445
- resetModifiers();
446
- primaryTarget = null;
447
- var bar = document.getElementById(DKB_PREFIX + 'bar');
448
- if (bar) bar.classList.remove('dkb-visible');
449
- }
450
-
451
- /* ─── Key Tap Handler ─── */
452
- function handleKeyTap(btn) {
453
- var key = btn.getAttribute('data-key');
454
- var code = btn.getAttribute('data-code');
455
- var keyCode = parseInt(btn.getAttribute('data-keycode'), 10);
456
- var symbol = btn.getAttribute('data-symbol');
457
- var modifier = btn.getAttribute('data-modifier');
458
-
459
- // Handle modifier toggle
460
- if (modifier) {
461
- setActiveModifier(modifier, !activeModifiers[modifier]);
462
- if (activeModifiers[modifier]) {
463
- startAutoResetTimer();
464
- }
465
- return;
466
- }
467
-
468
- // F1-F12 expand toggle
469
- if (key === 'F1–F12') {
470
- toggleF1Row();
471
- return;
472
- }
473
-
474
- // For non-modifier, non-toggle keys: dispatch to the real target
475
- var target = primaryTarget || detectTarget();
476
- if (!target) return;
477
-
478
- if (hasActiveModifiers()) {
479
- dispatchKey(target, key, code, keyCode, activeModifiers);
480
- if (symbol) {
481
- insertChar(target, symbol);
482
- }
483
- resetModifiers();
484
- } else {
485
- dispatchKey(target, key, code, keyCode, { ctrl: false, alt: false, shift: false, meta: false });
486
- if (symbol) {
487
- insertChar(target, symbol);
488
- }
489
- }
490
-
491
- // Keep focus on the target (not the button) so keyboard stays open
492
- try { target.focus({ preventScroll: true }); } catch (e) { /* ignore */ }
493
- }
494
-
495
- function toggleF1Row() {
496
- f1RowExpanded = !f1RowExpanded;
497
- var f1row = document.getElementById(DKB_PREFIX + 'f1row');
498
- var expandBtn = document.getElementById(DKB_PREFIX + 'btn-expand');
499
- if (!f1row || !expandBtn) return;
500
- if (f1RowExpanded) {
501
- f1row.classList.add('dkb-expanded');
502
- expandBtn.textContent = '▼';
503
- } else {
504
- f1row.classList.remove('dkb-expanded');
505
- expandBtn.textContent = 'F1–F12';
506
- }
507
- }
508
-
509
- /* ─── Intercept native keyboard when modifiers are active ─── */
510
- function onNativeKeyDown(e) {
511
- if (!barVisible || !hasActiveModifiers()) return;
512
- // Don't intercept modifier keys themselves
513
- if (['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) return;
514
- // Don't intercept if event already has our modifiers (prevent infinite loop)
515
- if (e._dkbSynthetic) return;
516
-
517
- // Cancel the original keypress and re-dispatch with modifiers
518
- e.preventDefault();
519
- e.stopImmediatePropagation();
520
-
521
- var target = e.target;
522
- var syntheticOpts = {
523
- key: e.key,
524
- code: e.code,
525
- keyCode: e.keyCode,
526
- ctrlKey: activeModifiers.ctrl,
527
- altKey: activeModifiers.alt,
528
- shiftKey: activeModifiers.shift,
529
- metaKey: activeModifiers.meta,
530
- bubbles: true,
531
- cancelable: true,
532
- composed: true
533
- };
534
-
535
- // Mark as synthetic to prevent re-interception
536
- var synthetic = new KeyboardEvent('keydown', syntheticOpts);
537
- synthetic._dkbSynthetic = true;
538
- target.dispatchEvent(synthetic);
539
-
540
- // Also dispatch keyup
541
- var syntheticUp = new KeyboardEvent('keyup', syntheticOpts);
542
- syntheticUp._dkbSynthetic = true;
543
- target.dispatchEvent(syntheticUp);
544
-
545
- resetModifiers();
546
- }
547
-
548
- /* ─── Event Listeners ─── */
549
- function setupListeners() {
550
- // Intercept native keyboard for modifier+key combos (e.g. Ctrl+C from native keyboard)
551
- document.addEventListener('keydown', onNativeKeyDown, true);
552
-
553
- // Button interaction via touch — prevent keyboard dismiss
554
- var bar = document.getElementById(DKB_PREFIX + 'bar');
555
- if (bar) {
556
- // Use touchend with preventDefault to handle taps without dismissing keyboard
557
- bar.addEventListener('touchend', function (e) {
558
- var btn = e.target.closest('.' + DKB_PREFIX + 'btn');
559
- if (!btn) return;
560
- e.preventDefault(); // Prevent focus shift (keeps keyboard open)
561
- e.stopPropagation();
562
- handleKeyTap(btn);
563
- }, { passive: false });
564
-
565
- // Also handle click for non-touch scenarios (desktop testing)
566
- bar.addEventListener('click', function (e) {
567
- var btn = e.target.closest('.' + DKB_PREFIX + 'btn');
568
- if (!btn) return;
569
- e.preventDefault();
570
- e.stopPropagation();
571
- handleKeyTap(btn);
572
- }, true);
573
- }
574
-
575
- // Focus / blur detection
576
- document.addEventListener('focusin', function (e) {
577
- if (!isMobile()) return;
578
- // Don't react to hidden input gaining focus
579
- if (e.target && e.target.id === 'hidden-input-dkb') return;
580
- var target = detectTarget();
581
- if (target) {
582
- primaryTarget = target;
583
- if (typeof visualViewport !== 'undefined' && visualViewport) {
584
- if (window.innerHeight - visualViewport.height > 50) {
585
- showBar();
586
- }
587
- } else if (barVisible === false) {
588
- setTimeout(function () {
589
- if (primaryTarget) showBar();
590
- }, 150);
591
- }
592
- }
593
- });
594
-
595
- document.addEventListener('focusout', function (e) {
596
- if (!isMobile()) return;
597
- // Don't react to hidden input losing focus
598
- if (e.target && e.target.id === 'hidden-input-dkb') return;
599
- // Don't clear primaryTarget if focus moved to a bar button (user tapping bar)
600
- var related = e.relatedTarget || document.activeElement;
601
- if (related && related.closest && related.closest('#' + DKB_PREFIX + 'bar')) return;
602
- // Check if focus moved to another input-like element
603
- var newTarget = detectTarget();
604
- if (!newTarget) {
605
- primaryTarget = null;
606
- if (typeof visualViewport !== 'undefined' && visualViewport) {
607
- if (window.innerHeight - visualViewport.height < 50) {
608
- hideBar();
609
- }
610
- } else {
611
- setTimeout(function () {
612
- if (!detectTarget()) hideBar();
613
- }, 300);
614
- }
615
- } else {
616
- primaryTarget = newTarget;
617
- }
618
- });
619
-
620
- // VisualViewport resize (keyboard open / close)
621
- if (typeof visualViewport !== 'undefined') {
622
- visualViewport.addEventListener('resize', function () {
623
- positionBar();
624
- var keyboardOpened = (window.innerHeight - visualViewport.height) > 50;
625
- if (keyboardOpened && primaryTarget) {
626
- showBar();
627
- } else if (!keyboardOpened) {
628
- hideBar();
629
- }
630
- }, { passive: true });
631
-
632
- visualViewport.addEventListener('scroll', function () {
633
- positionBar();
634
- }, { passive: true });
635
- }
636
-
637
- // Window resize
638
- window.addEventListener('resize', function () {
639
- positionBar();
640
- });
641
- }
642
-
643
- /* ─── Initialization ─── */
644
- function init() {
645
- if (!isMobile()) return;
646
- injectCSS();
647
- createBar();
648
- setupListeners();
649
-
650
- // If keyboard is already open when script loads
651
- if (typeof visualViewport !== 'undefined' && visualViewport) {
652
- if (window.innerHeight - visualViewport.height > 50) {
653
- var tgt = detectTarget();
654
- if (tgt) {
655
- primaryTarget = tgt;
656
- showBar();
657
- }
658
- }
659
- }
660
- }
661
-
662
- // Run at document idle
663
- if (document.readyState === 'loading') {
664
- document.addEventListener('DOMContentLoaded', function () {
665
- setTimeout(init, 500);
666
- });
667
- } else {
668
- setTimeout(init, 100);
669
- }
670
- })();