js_lis 2.0.6 → 2.0.8

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.
@@ -14,35 +14,16 @@ export class VirtualKeyboard {
14
14
  this.shiftActive = false;
15
15
  this.capsLockActive = false;
16
16
  this.isScrambled = false;
17
- this.isVirtualKeyboardActive = false; // Flag to track VK operations
17
+ this.isVirtualKeyboardActive = false;
18
18
 
19
- // Maintain plaintext buffers per input (not stored in DOM). DOM will store only encrypted text.
20
19
  this.inputPlaintextBuffers = new WeakMap();
21
-
22
- // Security state
23
- // this.security = {
24
- // detectorStatus: "idle",
25
- // suspiciousEvents: [],
26
- // observer: null,
27
- // addSuspicious(event) {
28
- // try {
29
- // this.suspiciousEvents.push({
30
- // time: new Date().toISOString(),
31
- // ...event,
32
- // });
33
- // console.warn("[VK Security] Suspicious activity detected:", event);
34
- // } catch (_) {}
35
- // },
36
- // };
37
-
38
- injectCSS();
20
+
39
21
  this.initialize();
40
22
  }
41
23
 
42
24
  async initialize() {
43
25
  try {
44
- // this.applyCSPReportOnly();
45
- // this.startKeyloggerDetector();
26
+ injectCSS();
46
27
  this.render();
47
28
  this.initializeInputListeners();
48
29
  console.log("VirtualKeyboard initialized successfully.");
@@ -51,113 +32,6 @@ export class VirtualKeyboard {
51
32
  }
52
33
  }
53
34
 
54
- // Inject CSP Report-Only meta for runtime visibility into potential violations
55
- // applyCSPReportOnly() {
56
- // // Disabled: Report-Only CSP must be set via HTTP headers. See server middleware in app.js.
57
- // return;
58
- // }
59
-
60
- // Start MutationObserver and limited listener-hook to detect potential keylogger injections
61
- // startKeyloggerDetector() {
62
- // try {
63
- // const allowlistedScriptHosts = new Set([
64
- // window.location.host,
65
- // "cdnjs.cloudflare.com",
66
- // ]);
67
-
68
- // const isSuspiciousScript = (node) => {
69
- // try {
70
- // if (!(node instanceof HTMLScriptElement)) return false;
71
- // const src = node.getAttribute("src") || "";
72
- // if (!src) {
73
- // const code = (node.textContent || "").toLowerCase();
74
- // return /addEventListener\(['\"]key|document\.onkey|keylogger|send\(|fetch\(|xmlhttprequest/i.test(code);
75
- // }
76
- // const url = new URL(src, window.location.href);
77
- // return !Array.from(allowlistedScriptHosts).some((h) => url.host.endsWith(h));
78
- // } catch (_) {
79
- // return true;
80
- // }
81
- // };
82
-
83
- // const isSuspiciousElement = (node) => {
84
- // try {
85
- // if (!(node instanceof Element)) return false;
86
- // const hasKeyHandlers = node.hasAttribute("onkeydown") || node.hasAttribute("onkeypress") || node.hasAttribute("onkeyup");
87
- // const hiddenFrame = node.tagName === "IFRAME" && (node.getAttribute("sandbox") === null || node.getAttribute("srcdoc") !== null);
88
- // return hasKeyHandlers || hiddenFrame;
89
- // } catch (_) {
90
- // return false;
91
- // }
92
- // };
93
-
94
- // const quarantineScript = (script) => {
95
- // try {
96
- // script.type = "text/plain";
97
- // script.setAttribute("data-quarantined", "true");
98
- // } catch (_) {}
99
- // };
100
-
101
- // const observer = new MutationObserver((mutations) => {
102
- // for (const m of mutations) {
103
- // if (m.type === "childList") {
104
- // m.addedNodes.forEach((node) => {
105
- // if (isSuspiciousScript(node)) {
106
- // this.security.addSuspicious({ type: "script", reason: "suspicious source or inline pattern", node });
107
- // quarantineScript(node);
108
- // } else if (isSuspiciousElement(node)) {
109
- // this.security.addSuspicious({ type: "element", reason: "inline key handlers or risky frame", node });
110
- // }
111
- // });
112
- // } else if (m.type === "attributes" && isSuspiciousElement(m.target)) {
113
- // this.security.addSuspicious({ type: "attr", reason: `attribute ${m.attributeName}`, node: m.target });
114
- // }
115
- // }
116
- // });
117
-
118
- // observer.observe(document.documentElement, {
119
- // childList: true,
120
- // subtree: true,
121
- // attributes: true,
122
- // attributeFilter: ["onkeydown", "onkeypress", "onkeyup", "src", "type", "sandbox", "srcdoc"],
123
- // });
124
-
125
- // // Hook addEventListener to monitor keyboard listeners registration
126
- // const OriginalAddEventListener = EventTarget.prototype.addEventListener;
127
- // const selfRef = this;
128
- // EventTarget.prototype.addEventListener = function(type, listener, options) {
129
- // try {
130
- // if (type && /^(key(?:down|up|press))$/i.test(type)) {
131
- // const info = {
132
- // type: "event-listener",
133
- // reason: `keyboard listener registered: ${type}`,
134
- // target: this,
135
- // };
136
- // selfRef.security.addSuspicious(info);
137
- // }
138
- // } catch (_) {}
139
- // return OriginalAddEventListener.call(this, type, listener, options);
140
- // };
141
-
142
- // this.security.observer = observer;
143
- // this.security.detectorStatus = "running";
144
-
145
- // // Expose minimal security API
146
- // window.VKSecurity = {
147
- // getStatus: () => this.security.detectorStatus,
148
- // getEvents: () => [...this.security.suspiciousEvents],
149
- // stop: () => {
150
- // try { observer.disconnect(); } catch (_) {}
151
- // this.security.detectorStatus = "stopped";
152
- // },
153
- // };
154
- // console.info("[VK Security] Keylogger detector started");
155
- // } catch (err) {
156
- // this.security.detectorStatus = "error";
157
- // console.warn("[VK Security] Detector error:", err);
158
- // }
159
- // }
160
-
161
35
  getLayoutName(layout) {
162
36
  switch (layout) {
163
37
  case "full":
@@ -170,6 +44,8 @@ export class VirtualKeyboard {
170
44
  return "Numpad Keyboard";
171
45
  case "symbols":
172
46
  return "Symbols Keyboard";
47
+ case "full-layouts":
48
+ return "Full English + Numpad";
173
49
  default:
174
50
  return "Unknown Layout";
175
51
  }
@@ -179,13 +55,8 @@ export class VirtualKeyboard {
179
55
  document.addEventListener("click", (e) => {
180
56
  const target = e.target;
181
57
  if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
182
- // Store the cursor position immediately after click
183
58
  const clickCursorPos = target.selectionStart;
184
-
185
- // Don't move cursor to end if virtual keyboard is currently active
186
59
  this.setCurrentInput(target, !this.isVirtualKeyboardActive);
187
-
188
- // Restore the cursor position that user clicked to set
189
60
  setTimeout(() => {
190
61
  if (target.selectionStart !== clickCursorPos) {
191
62
  target.setSelectionRange(clickCursorPos, clickCursorPos);
@@ -194,12 +65,9 @@ export class VirtualKeyboard {
194
65
  }
195
66
  });
196
67
 
197
- document.addEventListener(
198
- "focus",
199
- (e) => {
68
+ document.addEventListener("focus", (e) => {
200
69
  const target = e.target;
201
70
  if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
202
- // Don't move cursor to end if virtual keyboard is currently active
203
71
  this.setCurrentInput(target, !this.isVirtualKeyboardActive);
204
72
  }
205
73
  },
@@ -210,21 +78,28 @@ export class VirtualKeyboard {
210
78
  document.addEventListener("input", (e) => {
211
79
  const target = e.target;
212
80
  if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
213
- // Only handle real keyboard input if it's not coming from virtual keyboard operations
214
81
  if (!this.isVirtualKeyboardActive) {
215
- this.handleRealKeyboardInput(target);
82
+ this.handleRealKeyboardInput(target, e);
216
83
  }
217
84
  }
218
85
  });
219
86
 
220
- // Add real keyboard arrow key support
87
+ // Add real keyboard keydown listener for proper character tracking
221
88
  document.addEventListener("keydown", (e) => {
222
89
  const target = e.target;
223
90
  if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
224
- // Handle arrow keys, HOME, END from real keyboard
91
+ // Reset the virtual keyboard flag when real keyboard is used
92
+ if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.code)) {
93
+ this.isVirtualKeyboardActive = false;
94
+ }
95
+
96
+ // Handle real keyboard input
97
+ if (!this.isVirtualKeyboardActive) {
98
+ this.handleRealKeyboardKeydown(target, e);
99
+ }
100
+
101
+ // Handle arrow keys for all input types
225
102
  if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.code)) {
226
- // Let the browser handle the navigation naturally
227
- // Just ensure the input stays focused
228
103
  setTimeout(() => {
229
104
  target.focus();
230
105
  }, 0);
@@ -246,7 +121,6 @@ export class VirtualKeyboard {
246
121
  this.currentInput = inputElement;
247
122
  this.currentInput.classList.add("keyboard-active");
248
123
 
249
- // Ensure the input is focused and cursor is visible
250
124
  this.currentInput.focus();
251
125
 
252
126
  // Ensure buffer initialized for this input
@@ -293,6 +167,7 @@ export class VirtualKeyboard {
293
167
 
294
168
  const layoutSelector = document.createElement("select");
295
169
  layoutSelector.id = "layout-selector";
170
+ layoutSelector.setAttribute("aria-label", "Select keyboard layout"); // ✅ เพิ่มบรรทัดนี้
296
171
  layoutSelector.onchange = (e) => this.changeLayout(e.target.value);
297
172
 
298
173
  const layouts = ["full", "en", "th", "numpad", "symbols"];
@@ -305,6 +180,7 @@ export class VirtualKeyboard {
305
180
  layoutSelector.value = this.currentLayout;
306
181
  controlsContainer.appendChild(layoutSelector);
307
182
 
183
+
308
184
  keyboard.appendChild(controlsContainer);
309
185
 
310
186
  const layout = this.layouts[this.currentLayout];
@@ -325,13 +201,14 @@ export class VirtualKeyboard {
325
201
  keyElement.classList.add("concat-keys");
326
202
  }
327
203
 
328
- if (key === "Space") {
204
+ if (key === " ") {
329
205
  keyElement.className += " space";
206
+ keyElement.innerHTML = 'ฺ';
330
207
  }
331
208
 
332
209
  if (key === "backspace" || key === "Backspace") {
333
210
  keyElement.className += " backspacew";
334
- keyElement.innerHTML = '<i class="fa fa-backspace"></i>';
211
+ keyElement.innerHTML = '<i class="fa fa-backspace"></i>ฺ';
335
212
  }
336
213
 
337
214
  keyElement.onclick = (e) => {
@@ -434,6 +311,20 @@ export class VirtualKeyboard {
434
311
  key.classList.toggle("active", this.isScrambled);
435
312
  });
436
313
  }
314
+
315
+ if (this.currentLayout === "full") {
316
+ if (this.isScrambled) {
317
+ this.unscrambleKeys();
318
+ } else {
319
+ this.scrambleFullLayoutKeys();
320
+ }
321
+ this.isScrambled = !this.isScrambled;
322
+ document
323
+ .querySelectorAll('.key[data-key="scrambled"]')
324
+ .forEach((key) => {
325
+ key.classList.toggle("active", this.isScrambled);
326
+ });
327
+ }
437
328
  this.isVirtualKeyboardActive = false;
438
329
  return;
439
330
  }
@@ -535,28 +426,12 @@ export class VirtualKeyboard {
535
426
  case "←":
536
427
  const leftNewPos = Math.max(0, start - 1);
537
428
  this.currentInput.setSelectionRange(leftNewPos, leftNewPos);
538
- // Force immediate focus and ensure cursor is visible
539
- this.currentInput.focus();
540
- setTimeout(() => {
541
- this.currentInput.setSelectionRange(leftNewPos, leftNewPos);
542
- this.currentInput.focus();
543
- }, 5);
544
- // Skip the general cursor repositioning for arrow keys
545
- this.isVirtualKeyboardActive = false;
546
429
  break;
547
430
 
548
431
  case "→":
549
432
  const bufferLength = this.getCurrentBuffer().length;
550
433
  const rightNewPos = Math.min(bufferLength, start + 1);
551
434
  this.currentInput.setSelectionRange(rightNewPos, rightNewPos);
552
- // Force immediate focus and ensure cursor is visible
553
- this.currentInput.focus();
554
- setTimeout(() => {
555
- this.currentInput.setSelectionRange(rightNewPos, rightNewPos);
556
- this.currentInput.focus();
557
- }, 5);
558
- // Skip the general cursor repositioning for arrow keys
559
- this.isVirtualKeyboardActive = false;
560
435
  break;
561
436
 
562
437
  case "↑":
@@ -602,24 +477,20 @@ export class VirtualKeyboard {
602
477
 
603
478
  if (isShiftActive && !isCapsActive) this.toggleShift();
604
479
 
605
- // Only apply general focus and cursor management if not an arrow key
606
- // Arrow keys handle their own cursor positioning and return early
607
480
  this.currentInput.focus();
608
-
609
- // Use setTimeout to ensure cursor positioning happens after DOM updates
481
+
610
482
  setTimeout(() => {
611
483
  this.currentInput.focus();
612
- // Ensure cursor is visible by setting selection range
613
484
  const cursorPos = this.currentInput.selectionStart;
614
485
  this.currentInput.setSelectionRange(cursorPos, cursorPos);
615
486
  }, 0);
616
487
 
488
+ this.currentInput.focus();
617
489
  const event = new Event("input", { bubbles: true });
618
490
  this.currentInput.dispatchEvent(event);
619
491
 
620
- // Reset flag after virtual keyboard operation is complete
492
+ // Reset the flag after virtual keyboard operation is complete
621
493
  this.isVirtualKeyboardActive = false;
622
- // console.log(keyPressed)
623
494
  }
624
495
 
625
496
  // [3]
@@ -632,17 +503,11 @@ export class VirtualKeyboard {
632
503
  const newBuffer = buffer.slice(0, start) + textvalue + buffer.slice(end);
633
504
  this.setCurrentBuffer(newBuffer);
634
505
 
635
- // Calculate new cursor position first
636
506
  const newCursorPos = start + textvalue.length;
637
507
 
638
- // Update displayed value without preserving old cursor position
639
508
  this.updateDisplayedValue(false);
640
509
 
641
- // Set cursor position after the inserted text
642
510
  this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
643
-
644
- // Ensure the input remains focused with visible cursor
645
- // this.currentInput.focus();
646
511
  }
647
512
 
648
513
  // Helper: get current plaintext buffer for active input
@@ -677,22 +542,110 @@ export class VirtualKeyboard {
677
542
  }
678
543
  }
679
544
 
680
- // Handle real keyboard input and encrypt it
681
- handleRealKeyboardInput(input) {
545
+ // Handle real keyboard keydown events to track actual input before masking
546
+ handleRealKeyboardKeydown(input, event) {
682
547
  if (!input) return;
683
548
 
684
- // Get the current value from the input
685
- const currentValue = input.value;
549
+ const key = event.key;
550
+ const start = input.selectionStart;
551
+ const end = input.selectionEnd;
552
+ const buffer = this.getCurrentBuffer();
686
553
 
687
- // Update the plaintext buffer with the real keyboard input
688
- this.inputPlaintextBuffers.set(input, currentValue);
554
+ // Skip modifier keys and special keys
555
+ if (event.ctrlKey || event.altKey || event.metaKey ||
556
+ ['Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'Tab', 'Escape',
557
+ 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End',
558
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'].includes(key)) {
559
+ return;
560
+ }
689
561
 
690
- // Encrypt and store in data-encrypted attribute
691
- this.updateDomEncrypted(input, currentValue);
562
+ let newBuffer = buffer;
692
563
 
693
- // this.updateDisplayedValue();
694
- // console.log("Real keyboard input captured and encrypted for:", input.id || input.name || "unnamed input");
695
- // console.log("Input type:", input.type, "Value length:", currentValue.length);
564
+ if (key === 'Backspace') {
565
+ if (start === end && start > 0) {
566
+ // Single character deletion
567
+ newBuffer = buffer.slice(0, start - 1) + buffer.slice(start);
568
+ } else if (start !== end) {
569
+ // Range deletion
570
+ newBuffer = buffer.slice(0, start) + buffer.slice(end);
571
+ }
572
+
573
+ // Prevent default and update manually
574
+ event.preventDefault();
575
+ this.setCurrentBuffer(newBuffer);
576
+ this.updateDisplayedValue(false);
577
+
578
+ // Set cursor position
579
+ const newCursorPos = start === end ? Math.max(0, start - 1) : start;
580
+ setTimeout(() => {
581
+ input.setSelectionRange(newCursorPos, newCursorPos);
582
+ input.focus();
583
+ }, 0);
584
+
585
+ } else if (key === 'Delete') {
586
+ if (start === end && start < buffer.length) {
587
+ // Single character deletion forward
588
+ newBuffer = buffer.slice(0, start) + buffer.slice(start + 1);
589
+ } else if (start !== end) {
590
+ // Range deletion
591
+ newBuffer = buffer.slice(0, start) + buffer.slice(end);
592
+ }
593
+
594
+ // Prevent default and update manually
595
+ event.preventDefault();
596
+ this.setCurrentBuffer(newBuffer);
597
+ this.updateDisplayedValue(false);
598
+
599
+ // Set cursor position
600
+ setTimeout(() => {
601
+ input.setSelectionRange(start, start);
602
+ input.focus();
603
+ }, 0);
604
+
605
+ } else if (key === 'Enter') {
606
+ if (input.tagName === 'TEXTAREA') {
607
+ newBuffer = buffer.slice(0, start) + '\n' + buffer.slice(end);
608
+
609
+ // Prevent default and update manually
610
+ event.preventDefault();
611
+ this.setCurrentBuffer(newBuffer);
612
+ this.updateDisplayedValue(false);
613
+
614
+ // Set cursor position
615
+ const newCursorPos = start + 1;
616
+ setTimeout(() => {
617
+ input.setSelectionRange(newCursorPos, newCursorPos);
618
+ input.focus();
619
+ }, 0);
620
+ }
621
+ // For input fields, let the default behavior handle form submission
622
+
623
+ } else if (key.length === 1) {
624
+ // Regular character input
625
+ newBuffer = buffer.slice(0, start) + key + buffer.slice(end);
626
+
627
+ // Prevent default and update manually
628
+ event.preventDefault();
629
+ this.setCurrentBuffer(newBuffer);
630
+ this.updateDisplayedValue(false);
631
+
632
+ // Set cursor position
633
+ const newCursorPos = start + key.length;
634
+ setTimeout(() => {
635
+ input.setSelectionRange(newCursorPos, newCursorPos);
636
+ input.focus();
637
+ }, 0);
638
+ }
639
+ }
640
+
641
+ // Handle real keyboard input and encrypt it (simplified version)
642
+ handleRealKeyboardInput(input, event) {
643
+ if (!input) return;
644
+
645
+ // For most cases, the keydown handler already took care of updating the buffer
646
+ // This method now just ensures the encryption is up to date
647
+ const currentBuffer = this.getCurrentBuffer();
648
+ this.updateDomEncrypted(input, currentBuffer);
696
649
  }
697
650
 
698
651
  // Helper: encrypt and store ciphertext in DOM attribute
@@ -855,7 +808,7 @@ export class VirtualKeyboard {
855
808
  // [12]
856
809
  scrambleEnglishKeys() {
857
810
  const keys = document.querySelectorAll(
858
- ".key:not([data-key='scrambled']):not([data-key='std']):not([data-key='scr']):not([data-key='Space']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='Tab ↹'])"
811
+ ".key:not([data-key='scrambled']):not([data-key='std']):not([data-key='scr']):not([data-key=' ']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='Tab ↹'])"
859
812
  );
860
813
  const englishAlphabet = "abcdefghijklmnopqrstuvwxyz/.,';\\][`1234567890-=".split("");
861
814
  this.shuffleArray(englishAlphabet);
@@ -868,7 +821,7 @@ export class VirtualKeyboard {
868
821
  // [13]
869
822
  scrambleThaiKeys() {
870
823
  const keys = document.querySelectorAll(
871
- ".key:not([data-key='scrambled']):not([data-key='scr']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='Space']):not([data-key='Tab ↹'])"
824
+ ".key:not([data-key='scrambled']):not([data-key='scr']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key=' ']):not([data-key='Tab ↹'])"
872
825
  );
873
826
  const ThaiAlphabet = '_ๅ/-ภถุึคตจขชๆไำพะัีรนยบลฃฟหกดเ้่าสวงผปแอิืทมใฝ%+๑๒๓๔ู฿๕๖๗๘๙๐"ฎฑธํ๊ณฯญฐ,ฅฤฆฏโฌ็๋ษศซ.ฦฬฒ?์ฺฮฉ)('.split("");
874
827
  this.shuffleArray(ThaiAlphabet);
@@ -891,6 +844,21 @@ export class VirtualKeyboard {
891
844
  });
892
845
  }
893
846
 
847
+
848
+ scrambleFullLayoutKeys() {
849
+ const keys = document.querySelectorAll(
850
+ ".key:not(.concat-keys):not([data-key='scrambled']):not([data-key='std']):not([data-key='scr']):not([data-key=' ']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='Tab ↹'])"
851
+ );
852
+ const englishChars = "abcdefghijklmnopqrstuvwxyz1234567890;'\\/][`,.-=".split("");
853
+ this.shuffleArray(englishChars);
854
+ keys.forEach((key, index) => {
855
+ if (index < englishChars.length) {
856
+ key.textContent = englishChars[index];
857
+ key.dataset.key = englishChars[index];
858
+ }
859
+ });
860
+ }
861
+
894
862
  // [14] จัดการการลากคีย์บอร์ด
895
863
  startDrag(event) {
896
864
  this.isDragging = true;
package/layouts.js CHANGED
@@ -1,11 +1,11 @@
1
1
  export const layouts = {
2
2
  full: [
3
- // ["Esc", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "DEL⌦", "HOME", "END"],
3
+ // ["Esc", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "DEL⌦", "HOME", "END"],"ㅤ","ㅤ","ㅤ","ㅤ",
4
4
  ["`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"].concat(["+", "-", "*","/"]),
5
5
  ["Tab ↹", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\"].concat(["7", "8", "9", "%"]),
6
6
  ["Caps 🄰", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter"].concat(["4", "5", "6", "_"]),
7
7
  ["Shift ⇧", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift ⇧", "↑"].concat(["1", "2", "3", "="]),
8
- ["@", " ", "←", "↓", "→"].concat(["0", "."]),
8
+ ["scrambled", " ", "←", "↓", "→"].concat(["0", "."]),
9
9
  ],
10
10
  en: [
11
11
  ["`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js_lis",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
package/style/kbstyle.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // CSS-in-JS version of kbstyle.css
2
2
  export const css = `
3
- .keyboard-row,.virtual-keyboard{display:flex;display:flex}.keyb,.keyboard-key{cursor:pointer;font-size:16px;text-align:center}.virtual-keyboard{flex-direction:column;width:100%;max-width:800px;margin:0 auto;border:2px solid #333;border-radius:10px;padding:20px;background-color:#f5f5f5;box-shadow:0 4px 10px rgba(0,0,0,.1)}.keyboard-row{justify-content:space-between}.keyboard-key{flex:1;padding:10px 15px;margin:2px;border:1px solid #ccc;border-radius:5px;background-color:#fff;transition:background-color .2s}.keyboard-active,input:focus,textarea:focus{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.backspacew{flex:1.5;width:260px}.keyboard-key:hover{background:#e0e0e0}.keyboard-key:active{background:#d0d0d0;transform:translateY(1px)}.keyboard-row{display:flex;justify-content:flex-start;margin:5px 0}.controls{margin-bottom:20px}button,select{margin-right:10px;padding:8px 12px;border:1px solid #ccc;border-radius:4px;font-size:14px;cursor:pointer}button:hover{background:#f0f0f0}input:focus,textarea:focus{outline:0}#keyboard{position:absolute;cursor:move;top:80%;left:50%;transform:translate(-50%,-50%)}.keyb{color:#fff;border:#000;width:40px;height:25px;line-height:50px;margin:8px}.keyboard-row .active{background:#c7c7c7}body.dark-mode .virtual-keyboard{background-color:#333;box-shadow:0 4px 8px rgba(247,245,245,.1)}body.dark-mode{background-color:#333}body.dark-mode .login-container{background-color:#000;box-shadow:0 4px 8px rgba(247,245,245,.1);color:#fff}body.dark-mode .login-container input{background-color:#3333335d;color:#fff}body.dark-mode .login-container button{background-color:#005703;color:#fff}.fa-keyboard{color:#000}body.dark-mode .keyb .fa-keyboard{color:#ffffffde}body.dark-mode .keyboard-key.key{background-color:#222;color:#ffffffde}[data-key="Caps 🄰"],[data-key="Shift ⇧"],[data-key="Tab ↹"]{min-width:90px}.keyboard-key.key[data-key="Shift ⇧"]{color:#333}.virtual-keyboard.full [data-key=Enter]{min-width:95px}.virtual-keyboard.full [data-key="Shift ⇧"]{min-width:92px}.virtual-keyboard.full [data-key=" "]{min-width:620.8px}.virtual-keyboard.full .concat-keys[data-key="0"]{min-width:102px}.virtual-keyboard.full{min-width:1000px}.virtual-keyboard.Scnum,.virtual-keyboard.numpad{max-width:300px}.hidden{display:none!important}[data-key="Tab ↹"],[data-key=Backspace]{min-width:80px}[data-key="Shift ⇧"]{min-width:100px}[data-key=" "]{min-width:670px}
3
+ .keyboard-row,.virtual-keyboard{display:flex;display:flex}.keyb,.keyboard-key{cursor:pointer;font-size:16px;text-align:center}.virtual-keyboard{flex-direction:column;width:100%;max-width:800px;margin:0 auto;border:2px solid #333;border-radius:10px;padding:20px;background-color:#f5f5f5;box-shadow:0 4px 10px rgba(0,0,0,.1)}.keyboard-row{justify-content:space-between}.keyboard-key{flex:1;padding:10px 15px;margin:2px;border:1px solid #ccc;border-radius:5px;background-color:#fff;transition:background-color .2s}.keyboard-active,input:focus,textarea:focus{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.backspacew{flex:1.5;width:260px}.keyboard-key:hover{background:#e0e0e0}.keyboard-key:active{background:#d0d0d0;transform:translateY(1px)}.keyboard-row{display:flex;justify-content:flex-start;margin:5px 0}.controls{margin-bottom:20px}button,select{margin-right:10px;padding:8px 12px;border:1px solid #ccc;border-radius:4px;font-size:14px;cursor:pointer}button:hover{background:#f0f0f0}input:focus,textarea:focus{outline:0}#keyboard{position:absolute;cursor:move;top:80%;left:50%;transform:translate(-50%,-50%)}.keyb{color:#fff;border:#000;width:40px;height:25px;line-height:50px;margin:8px}.keyboard-row .active{background:#c7c7c7}body.dark-mode .virtual-keyboard{background-color:#333;box-shadow:0 4px 8px rgba(247,245,245,.1)}body.dark-mode{background-color:#333}body.dark-mode .login-container{background-color:#000;box-shadow:0 4px 8px rgba(247,245,245,.1);color:#fff}body.dark-mode .login-container input{background-color:#3333335d;color:#fff}body.dark-mode .login-container button{background-color:#005703;color:#fff}.fa-keyboard{color:#000}body.dark-mode .keyb .fa-keyboard{color:#ffffffde}body.dark-mode .keyboard-key.key{background-color:#222;color:#ffffffde}[data-key="Caps 🄰"],[data-key="Shift ⇧"],[data-key="Tab ↹"]{min-width:90px}.keyboard-key.key[data-key="Shift ⇧"]{color:#333}.virtual-keyboard.full [data-key=Enter]{min-width:95px}.virtual-keyboard.full [data-key="Shift ⇧"]{min-width:92px}.virtual-keyboard.full [data-key=" "]{min-width:563.8px}.virtual-keyboard.full .concat-keys[data-key="0"]{min-width:102px}.virtual-keyboard.full{min-width:1000px}.virtual-keyboard.Scnum,.virtual-keyboard.numpad{max-width:300px}.hidden{display:none!important}[data-key="Tab ↹"],[data-key=Backspace]{min-width:80px}[data-key="Shift ⇧"]{min-width:100px}[data-key=" "]{min-width:670px}
4
4
  `;
5
5
 
6
6
  export function injectCSS() {