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.
- package/build.js +11 -0
- package/dist/index.js +194 -0
- package/package.json +20 -6
- package/index.js +0 -29
- package/src/createBrowser.jsx +0 -258
- package/src/polyfills/dev-keyboard-bar.js +0 -670
- package/src/polyfills/ipados15-polyfill.js +0 -1859
|
@@ -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
|
-
})();
|