js_lis 2.0.5 → 2.0.7

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,4 +1,5 @@
1
1
  import { layouts } from "./layouts.js";
2
+ import { injectCSS } from "./style/kbstyle.js";
2
3
 
3
4
  export class VirtualKeyboard {
4
5
  constructor() {
@@ -13,34 +14,16 @@ export class VirtualKeyboard {
13
14
  this.shiftActive = false;
14
15
  this.capsLockActive = false;
15
16
  this.isScrambled = false;
16
- this.isVirtualKeyboardActive = false; // Flag to track VK operations
17
+ this.isVirtualKeyboardActive = false;
17
18
 
18
- // Maintain plaintext buffers per input (not stored in DOM). DOM will store only encrypted text.
19
19
  this.inputPlaintextBuffers = new WeakMap();
20
-
21
- // Security state
22
- this.security = {
23
- detectorStatus: "idle",
24
- suspiciousEvents: [],
25
- observer: null,
26
- addSuspicious(event) {
27
- try {
28
- this.suspiciousEvents.push({
29
- time: new Date().toISOString(),
30
- ...event,
31
- });
32
- console.warn("[VK Security] Suspicious activity detected:", event);
33
- } catch (_) {}
34
- },
35
- };
36
-
20
+
37
21
  this.initialize();
38
22
  }
39
23
 
40
24
  async initialize() {
41
25
  try {
42
- this.applyCSPReportOnly();
43
- this.startKeyloggerDetector();
26
+ injectCSS();
44
27
  this.render();
45
28
  this.initializeInputListeners();
46
29
  console.log("VirtualKeyboard initialized successfully.");
@@ -49,113 +32,6 @@ export class VirtualKeyboard {
49
32
  }
50
33
  }
51
34
 
52
- // Inject CSP Report-Only meta for runtime visibility into potential violations
53
- applyCSPReportOnly() {
54
- // Disabled: Report-Only CSP must be set via HTTP headers. See server middleware in app.js.
55
- return;
56
- }
57
-
58
- // Start MutationObserver and limited listener-hook to detect potential keylogger injections
59
- startKeyloggerDetector() {
60
- try {
61
- const allowlistedScriptHosts = new Set([
62
- window.location.host,
63
- "cdnjs.cloudflare.com",
64
- ]);
65
-
66
- const isSuspiciousScript = (node) => {
67
- try {
68
- if (!(node instanceof HTMLScriptElement)) return false;
69
- const src = node.getAttribute("src") || "";
70
- if (!src) {
71
- const code = (node.textContent || "").toLowerCase();
72
- return /addEventListener\(['\"]key|document\.onkey|keylogger|send\(|fetch\(|xmlhttprequest/i.test(code);
73
- }
74
- const url = new URL(src, window.location.href);
75
- return !Array.from(allowlistedScriptHosts).some((h) => url.host.endsWith(h));
76
- } catch (_) {
77
- return true;
78
- }
79
- };
80
-
81
- const isSuspiciousElement = (node) => {
82
- try {
83
- if (!(node instanceof Element)) return false;
84
- const hasKeyHandlers = node.hasAttribute("onkeydown") || node.hasAttribute("onkeypress") || node.hasAttribute("onkeyup");
85
- const hiddenFrame = node.tagName === "IFRAME" && (node.getAttribute("sandbox") === null || node.getAttribute("srcdoc") !== null);
86
- return hasKeyHandlers || hiddenFrame;
87
- } catch (_) {
88
- return false;
89
- }
90
- };
91
-
92
- const quarantineScript = (script) => {
93
- try {
94
- script.type = "text/plain";
95
- script.setAttribute("data-quarantined", "true");
96
- } catch (_) {}
97
- };
98
-
99
- const observer = new MutationObserver((mutations) => {
100
- for (const m of mutations) {
101
- if (m.type === "childList") {
102
- m.addedNodes.forEach((node) => {
103
- if (isSuspiciousScript(node)) {
104
- this.security.addSuspicious({ type: "script", reason: "suspicious source or inline pattern", node });
105
- quarantineScript(node);
106
- } else if (isSuspiciousElement(node)) {
107
- this.security.addSuspicious({ type: "element", reason: "inline key handlers or risky frame", node });
108
- }
109
- });
110
- } else if (m.type === "attributes" && isSuspiciousElement(m.target)) {
111
- this.security.addSuspicious({ type: "attr", reason: `attribute ${m.attributeName}`, node: m.target });
112
- }
113
- }
114
- });
115
-
116
- observer.observe(document.documentElement, {
117
- childList: true,
118
- subtree: true,
119
- attributes: true,
120
- attributeFilter: ["onkeydown", "onkeypress", "onkeyup", "src", "type", "sandbox", "srcdoc"],
121
- });
122
-
123
- // Hook addEventListener to monitor keyboard listeners registration
124
- const OriginalAddEventListener = EventTarget.prototype.addEventListener;
125
- const selfRef = this;
126
- EventTarget.prototype.addEventListener = function(type, listener, options) {
127
- try {
128
- if (type && /^(key(?:down|up|press))$/i.test(type)) {
129
- const info = {
130
- type: "event-listener",
131
- reason: `keyboard listener registered: ${type}`,
132
- target: this,
133
- };
134
- selfRef.security.addSuspicious(info);
135
- }
136
- } catch (_) {}
137
- return OriginalAddEventListener.call(this, type, listener, options);
138
- };
139
-
140
- this.security.observer = observer;
141
- this.security.detectorStatus = "running";
142
-
143
- // Expose minimal security API
144
- window.VKSecurity = {
145
- getStatus: () => this.security.detectorStatus,
146
- getEvents: () => [...this.security.suspiciousEvents],
147
- stop: () => {
148
- try { observer.disconnect(); } catch (_) {}
149
- this.security.detectorStatus = "stopped";
150
- },
151
- };
152
- console.info("[VK Security] Keylogger detector started");
153
- } catch (err) {
154
- this.security.detectorStatus = "error";
155
- console.warn("[VK Security] Detector error:", err);
156
- }
157
- }
158
-
159
35
  getLayoutName(layout) {
160
36
  switch (layout) {
161
37
  case "full":
@@ -168,6 +44,8 @@ export class VirtualKeyboard {
168
44
  return "Numpad Keyboard";
169
45
  case "symbols":
170
46
  return "Symbols Keyboard";
47
+ case "full-layouts":
48
+ return "Full English + Numpad";
171
49
  default:
172
50
  return "Unknown Layout";
173
51
  }
@@ -177,40 +55,42 @@ export class VirtualKeyboard {
177
55
  document.addEventListener("click", (e) => {
178
56
  const target = e.target;
179
57
  if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
180
- this.setCurrentInput(target);
58
+ const clickCursorPos = target.selectionStart;
59
+ this.setCurrentInput(target, !this.isVirtualKeyboardActive);
60
+ setTimeout(() => {
61
+ if (target.selectionStart !== clickCursorPos) {
62
+ target.setSelectionRange(clickCursorPos, clickCursorPos);
63
+ }
64
+ }, 10);
181
65
  }
182
66
  });
183
67
 
184
- document.addEventListener(
185
- "focus",
186
- (e) => {
68
+ document.addEventListener("focus", (e) => {
187
69
  const target = e.target;
188
70
  if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
189
- this.setCurrentInput(target);
71
+ this.setCurrentInput(target, !this.isVirtualKeyboardActive);
190
72
  }
191
73
  },
192
74
  true
193
75
  );
194
76
 
195
- // Add real keyboard input listeners to handle encryption
77
+ //Add real keyboard input listeners to handle encryption
196
78
  document.addEventListener("input", (e) => {
197
79
  const target = e.target;
198
80
  if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
199
- // Only handle real keyboard input if it's not coming from virtual keyboard operations
200
81
  if (!this.isVirtualKeyboardActive) {
201
82
  this.handleRealKeyboardInput(target);
202
83
  }
203
84
  }
204
85
  });
205
86
 
87
+ // Add real keyboard arrow key support
206
88
  document.addEventListener("keydown", (e) => {
207
89
  const target = e.target;
208
90
  if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
209
- // Handle special keys that might not trigger input event
210
- if ((e.key === "Backspace" || e.key === "Delete") && !this.isVirtualKeyboardActive) {
211
- // Wait for the input event to handle the updated value
91
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.code)) {
212
92
  setTimeout(() => {
213
- this.handleRealKeyboardInput(target);
93
+ target.focus();
214
94
  }, 0);
215
95
  }
216
96
  }
@@ -222,7 +102,7 @@ export class VirtualKeyboard {
222
102
  }
223
103
  }
224
104
 
225
- setCurrentInput(inputElement) {
105
+ setCurrentInput(inputElement, moveToEnd = true) {
226
106
  if (this.currentInput) {
227
107
  this.currentInput.classList.remove("keyboard-active");
228
108
  }
@@ -230,6 +110,8 @@ export class VirtualKeyboard {
230
110
  this.currentInput = inputElement;
231
111
  this.currentInput.classList.add("keyboard-active");
232
112
 
113
+ this.currentInput.focus();
114
+
233
115
  // Ensure buffer initialized for this input
234
116
  if (!this.inputPlaintextBuffers.has(this.currentInput)) {
235
117
  const ds = this.currentInput.dataset ? this.currentInput.dataset.encrypted : undefined;
@@ -248,6 +130,14 @@ export class VirtualKeyboard {
248
130
  }
249
131
  // Sync visible with buffer (masking for password)
250
132
  this.updateDisplayedValue();
133
+
134
+ // Only move cursor to end on initial focus, not during virtual keyboard operations
135
+ if (moveToEnd) {
136
+ setTimeout(() => {
137
+ const length = this.currentInput.value.length;
138
+ this.currentInput.setSelectionRange(length, length);
139
+ }, 0);
140
+ }
251
141
  }
252
142
 
253
143
  // [1]
@@ -407,124 +297,79 @@ export class VirtualKeyboard {
407
297
  key.classList.toggle("active", this.isScrambled);
408
298
  });
409
299
  }
300
+
301
+ if (this.currentLayout === "full") {
302
+ if (this.isScrambled) {
303
+ this.unscrambleKeys();
304
+ } else {
305
+ this.scrambleFullLayoutKeys();
306
+ }
307
+ this.isScrambled = !this.isScrambled;
308
+ document
309
+ .querySelectorAll('.key[data-key="scrambled"]')
310
+ .forEach((key) => {
311
+ key.classList.toggle("active", this.isScrambled);
312
+ });
313
+ }
410
314
  this.isVirtualKeyboardActive = false;
411
315
  return;
412
316
  }
413
317
 
414
318
  switch (keyPressed) {
415
- case "Esc":
416
- const modals = document.querySelectorAll(".modal");
417
- if (modals.length === 0) {
418
- document.exitFullscreen();
419
- console.warn("No modals found to close.");
420
- }
421
- modals.forEach((modal) => {
422
- modal.classList.add("hidden");
423
- });
424
- break;
425
-
426
- case "F1":
427
- break;
428
-
429
- case "F2":
430
- // เปิดโหมดแก้ไขสำหรับ element ที่เลือก
431
- const activeElement = document.activeElement;
432
- if (activeElement && activeElement.contentEditable !== undefined) {
433
- activeElement.contentEditable = true;
434
- activeElement.focus();
435
- console.log(activeElement.contentEditable);
436
- } else {
437
- console.warn("No editable element found.");
438
- }
439
- break;
440
-
441
- case "F3":
442
- // เปิดการค้นหา
443
- event.preventDefault();
444
- this.option.openSearch();
445
- break;
446
-
447
- case "F4":
448
- // เปิดเมนูการตั้งค่า
449
- event.preventDefault();
450
- this.option.openSettings();
451
- break;
452
-
453
- case "F5":
454
- // รีโหลดหน้าเว็บ (คงเดิม)
455
- window.location.reload();
456
- break;
457
-
458
- case "F6":
459
- // สลับระหว่างโหมดกลางวัน/กลางคืน
460
- document.body.classList.toggle("dark-mode");
461
- break;
462
-
463
- case "F7":
464
- break;
465
-
466
- case "F8":
467
- break;
468
-
469
- case "F9":
470
- break;
471
-
472
- case "F10":
473
- break;
474
-
475
- case "F11":
476
- if (!document.fullscreenElement) {
477
- document.documentElement
478
- .requestFullscreen()
479
- .catch((err) =>
480
- console.error("Error attempting to enable fullscreen:", err)
481
- );
482
- } else {
483
- document
484
- .exitFullscreen()
485
- .catch((err) =>
486
- console.error("Error attempting to exit fullscreen:", err)
487
- );
488
- }
489
- break;
490
-
491
- case "F12":
492
- break;
493
-
494
319
  case "HOME":
495
- this.currentInput.setSelectionRange(Math.max(0, 0), Math.max(0, 0));
496
- break;
320
+ this.currentInput.setSelectionRange(0, 0);
321
+ this.currentInput.focus();
322
+ setTimeout(() => {
323
+ this.currentInput.setSelectionRange(0, 0);
324
+ this.currentInput.focus();
325
+ }, 5);
326
+ // Skip the general cursor repositioning for navigation keys
327
+ this.isVirtualKeyboardActive = false;
328
+ return;
497
329
 
498
330
  case "END":
499
- const length = this.currentInput.value.length;
500
- this.currentInput.setSelectionRange(length, length);
501
- break;
331
+ const bufferLengthEnd = this.getCurrentBuffer().length;
332
+ this.currentInput.setSelectionRange(bufferLengthEnd, bufferLengthEnd);
333
+ this.currentInput.focus();
334
+ setTimeout(() => {
335
+ this.currentInput.setSelectionRange(bufferLengthEnd, bufferLengthEnd);
336
+ this.currentInput.focus();
337
+ }, 5);
338
+ // Skip the general cursor repositioning for navigation keys
339
+ this.isVirtualKeyboardActive = false;
340
+ return;
502
341
 
503
342
  case "Backspace":
504
343
  case "backspace":
505
344
  if (start === end && start > 0) {
506
345
  const newBuffer = buffer.slice(0, start - 1) + buffer.slice(end);
507
346
  this.setCurrentBuffer(newBuffer);
508
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start - 1;
347
+ const newCursorPos = start - 1;
348
+ this.updateDisplayedValue(false);
349
+ this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
509
350
  } else {
510
351
  const newBuffer = buffer.slice(0, start) + buffer.slice(end);
511
352
  this.setCurrentBuffer(newBuffer);
512
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
353
+ const newCursorPos = start;
354
+ this.updateDisplayedValue(false);
355
+ this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
513
356
  }
514
- this.updateDisplayedValue();
515
357
  break;
516
358
 
517
359
  case "DEL⌦":
518
360
  if (start === end && start < buffer.length) {
519
361
  const newBuffer = buffer.slice(0, start) + buffer.slice(end + 1);
520
362
  this.setCurrentBuffer(newBuffer);
521
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
363
+ const newCursorPos = start;
364
+ this.updateDisplayedValue(false);
365
+ this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
522
366
  } else {
523
367
  const newBuffer = buffer.slice(0, start) + buffer.slice(end);
524
368
  this.setCurrentBuffer(newBuffer);
525
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
369
+ const newCursorPos = start;
370
+ this.updateDisplayedValue(false);
371
+ this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
526
372
  }
527
- this.updateDisplayedValue();
528
373
  break;
529
374
 
530
375
  case "Space":
@@ -539,8 +384,9 @@ export class VirtualKeyboard {
539
384
  if (this.currentInput.tagName === "TEXTAREA") {
540
385
  const newBuffer = buffer.slice(0, start) + "\n" + buffer.slice(end);
541
386
  this.setCurrentBuffer(newBuffer);
542
- this.updateDisplayedValue();
543
- this.currentInput.setSelectionRange(start + 1, start + 1);
387
+ const newCursorPos = start + 1;
388
+ this.updateDisplayedValue(false);
389
+ this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
544
390
  } else if (this.currentInput.tagName === "INPUT" || this.currentInput.type === "password" || this.currentInput.type === "text" ) {
545
391
  if (this.currentInput.form) {
546
392
  const submitButton = this.currentInput.form.querySelector(
@@ -550,7 +396,7 @@ export class VirtualKeyboard {
550
396
  } else {
551
397
  const newBuffer = buffer + "\n";
552
398
  this.setCurrentBuffer(newBuffer);
553
- this.updateDisplayedValue();
399
+ this.updateDisplayedValue(false);
554
400
  }
555
401
  }
556
402
  break;
@@ -564,33 +410,50 @@ export class VirtualKeyboard {
564
410
  break;
565
411
 
566
412
  case "←":
567
- this.currentInput.setSelectionRange(
568
- Math.max(0, start - 1),
569
- Math.max(0, start - 1)
570
- );
413
+ const leftNewPos = Math.max(0, start - 1);
414
+ this.currentInput.setSelectionRange(leftNewPos, leftNewPos);
571
415
  break;
572
416
 
573
417
  case "→":
574
- this.currentInput.setSelectionRange(start + 1, start + 1);
418
+ const bufferLength = this.getCurrentBuffer().length;
419
+ const rightNewPos = Math.min(bufferLength, start + 1);
420
+ this.currentInput.setSelectionRange(rightNewPos, rightNewPos);
575
421
  break;
576
422
 
577
423
  case "↑":
578
424
  case "↓":
579
- const text = this.currentInput.value;
580
- const lines = text.substring(0, start).split("\n");
581
- const currentLineIndex = lines.length - 1;
582
- const currentLine = lines[currentLineIndex];
583
- const columnIndex = start - text.lastIndexOf("\n", start - 1) - 1;
584
-
585
- if (keyPressed === "↑" && currentLineIndex > 0) {
586
- const prevLineLength = lines[currentLineIndex - 1].length;
587
- const newPos = start - currentLine.length - 1 - Math.min(columnIndex, prevLineLength);
588
- this.currentInput.setSelectionRange(newPos, newPos);
589
- } else if (keyPressed === "↓" && currentLineIndex < lines.length - 1) {
590
- const nextLine = lines[currentLineIndex + 1];
591
- const newPos = start + currentLine.length + 1 + Math.min(columnIndex, nextLine.length);
592
- this.currentInput.setSelectionRange(newPos, newPos);
425
+ // Use the actual buffer content instead of displayed value
426
+ const actualText = this.getCurrentBuffer();
427
+ const actualLines = actualText.substring(0, start).split("\n");
428
+ const actualCurrentLineIndex = actualLines.length - 1;
429
+ const actualCurrentLine = actualLines[actualCurrentLineIndex];
430
+ const actualColumnIndex = start - actualText.lastIndexOf("\n", start - 1) - 1;
431
+
432
+ if (keyPressed === "↑" && actualCurrentLineIndex > 0) {
433
+ // Move to previous line
434
+ const prevLineLength = actualLines[actualCurrentLineIndex - 1].length;
435
+ const upNewPos = start - actualCurrentLine.length - 1 - Math.max(0, actualColumnIndex - prevLineLength);
436
+ this.currentInput.setSelectionRange(Math.max(0, upNewPos), Math.max(0, upNewPos));
437
+ } else if (keyPressed === "↓") {
438
+ // Move to next line
439
+ const remainingText = actualText.substring(start);
440
+ const nextLineBreak = remainingText.indexOf("\n");
441
+
442
+ if (nextLineBreak !== -1) {
443
+ // There is a next line
444
+ const textAfterNextLineBreak = remainingText.substring(nextLineBreak + 1);
445
+ const nextLineEnd = textAfterNextLineBreak.indexOf("\n");
446
+ const nextLineLength = nextLineEnd === -1 ? textAfterNextLineBreak.length : nextLineEnd;
447
+
448
+ const downNewPos = start + nextLineBreak + 1 + Math.min(actualColumnIndex, nextLineLength);
449
+ this.currentInput.setSelectionRange(downNewPos, downNewPos);
450
+ }
451
+ // If no next line, do nothing (cursor stays at current position)
593
452
  }
453
+
454
+ this.currentInput.focus();
455
+ // Skip the general cursor repositioning for arrow keys
456
+ this.isVirtualKeyboardActive = false;
594
457
  break;
595
458
 
596
459
  default:
@@ -600,12 +463,17 @@ export class VirtualKeyboard {
600
463
 
601
464
  if (isShiftActive && !isCapsActive) this.toggleShift();
602
465
 
466
+ this.currentInput.focus();
467
+
468
+ setTimeout(() => {
469
+ this.currentInput.focus();
470
+ const cursorPos = this.currentInput.selectionStart;
471
+ this.currentInput.setSelectionRange(cursorPos, cursorPos);
472
+ }, 0);
473
+
603
474
  this.currentInput.focus();
604
475
  const event = new Event("input", { bubbles: true });
605
476
  this.currentInput.dispatchEvent(event);
606
-
607
- // Reset flag after virtual keyboard operation is complete
608
- this.isVirtualKeyboardActive = false;
609
477
  }
610
478
 
611
479
  // [3]
@@ -617,8 +485,12 @@ export class VirtualKeyboard {
617
485
  const buffer = this.getCurrentBuffer();
618
486
  const newBuffer = buffer.slice(0, start) + textvalue + buffer.slice(end);
619
487
  this.setCurrentBuffer(newBuffer);
620
- this.updateDisplayedValue();
621
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start + textvalue.length;
488
+
489
+ const newCursorPos = start + textvalue.length;
490
+
491
+ this.updateDisplayedValue(false);
492
+
493
+ this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
622
494
  }
623
495
 
624
496
  // Helper: get current plaintext buffer for active input
@@ -635,12 +507,22 @@ export class VirtualKeyboard {
635
507
  }
636
508
 
637
509
  // Helper: reflect masked value in the visible input/textarea
638
- updateDisplayedValue() {
510
+ updateDisplayedValue(preserveCursor = true) {
639
511
  if (!this.currentInput) return;
640
512
  const buffer = this.getCurrentBuffer();
641
513
  const isPassword = this.currentInput.type === "password";
642
514
  const maskChar = "•";
515
+
516
+ // Store current cursor position only if we want to preserve it
517
+ const currentStart = preserveCursor ? this.currentInput.selectionStart : 0;
518
+ const currentEnd = preserveCursor ? this.currentInput.selectionEnd : 0;
519
+
643
520
  this.currentInput.value = isPassword ? maskChar.repeat(buffer.length) : buffer;
521
+
522
+ // Restore cursor position after updating value only if preserving
523
+ if (preserveCursor) {
524
+ this.currentInput.setSelectionRange(currentStart, currentEnd);
525
+ }
644
526
  }
645
527
 
646
528
  // Handle real keyboard input and encrypt it
@@ -655,9 +537,6 @@ export class VirtualKeyboard {
655
537
 
656
538
  // Encrypt and store in data-encrypted attribute
657
539
  this.updateDomEncrypted(input, currentValue);
658
- // this.updateDisplayedValue();
659
- // console.log("Real keyboard input captured and encrypted for:", input.id || input.name || "unnamed input");
660
- // console.log("Input type:", input.type, "Value length:", currentValue.length);
661
540
  }
662
541
 
663
542
  // Helper: encrypt and store ciphertext in DOM attribute
@@ -820,7 +699,7 @@ export class VirtualKeyboard {
820
699
  // [12]
821
700
  scrambleEnglishKeys() {
822
701
  const keys = document.querySelectorAll(
823
- ".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 ↹'])"
702
+ ".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 ↹'])"
824
703
  );
825
704
  const englishAlphabet = "abcdefghijklmnopqrstuvwxyz/.,';\\][`1234567890-=".split("");
826
705
  this.shuffleArray(englishAlphabet);
@@ -833,7 +712,7 @@ export class VirtualKeyboard {
833
712
  // [13]
834
713
  scrambleThaiKeys() {
835
714
  const keys = document.querySelectorAll(
836
- ".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 ↹'])"
715
+ ".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 ↹'])"
837
716
  );
838
717
  const ThaiAlphabet = '_ๅ/-ภถุึคตจขชๆไำพะัีรนยบลฃฟหกดเ้่าสวงผปแอิืทมใฝ%+๑๒๓๔ู฿๕๖๗๘๙๐"ฎฑธํ๊ณฯญฐ,ฅฤฆฏโฌ็๋ษศซ.ฦฬฒ?์ฺฮฉ)('.split("");
839
718
  this.shuffleArray(ThaiAlphabet);
@@ -856,6 +735,21 @@ export class VirtualKeyboard {
856
735
  });
857
736
  }
858
737
 
738
+
739
+ scrambleFullLayoutKeys() {
740
+ const keys = document.querySelectorAll(
741
+ ".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 ↹'])"
742
+ );
743
+ const englishChars = "abcdefghijklmnopqrstuvwxyz1234567890;'\\/][`,.-=".split("");
744
+ this.shuffleArray(englishChars);
745
+ keys.forEach((key, index) => {
746
+ if (index < englishChars.length) {
747
+ key.textContent = englishChars[index];
748
+ key.dataset.key = englishChars[index];
749
+ }
750
+ });
751
+ }
752
+
859
753
  // [14] จัดการการลากคีย์บอร์ด
860
754
  startDrag(event) {
861
755
  this.isDragging = true;
package/layouts.js CHANGED
@@ -1,25 +1,25 @@
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
- ["Space", "←", "↓", "→"].concat(["0", "."]),
8
+ ["scrambled", " ", "←", "↓", "→"].concat(["0", "."]),
9
9
  ],
10
10
  en: [
11
11
  ["`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"],
12
12
  ["Tab ↹", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\"],
13
13
  ["Caps 🄰", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter"],
14
14
  ["Shift ⇧", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift ⇧"],
15
- ["scrambled", "Space"],
15
+ ["scrambled", " "],
16
16
  ],
17
17
  th: [
18
18
  ["_", "ๅ", "/", "-", "ภ", "ถ", "ุ", "ึ", "ค", "ต", "จ", "ข", "ช", "Backspace"],
19
19
  ["Tab ↹", "ๆ", "ไ", "ำ", "พ", "ะ", "ั", "ี", "ร", "น", "ย", "บ", "ล", "ฃ"],
20
20
  ["Caps 🄰", "ฟ", "ห", "ก", "ด", "เ", "้", "่", "า", "ส", "ว", "ง", "Enter"],
21
21
  ["Shift ⇧", "ผ", "ป", "แ", "อ", "ิ", "ื", "ท", "ม", "ใ", "ฝ", "Shift ⇧"],
22
- ["scrambled" ,"Space"],
22
+ ["scrambled" ," "],
23
23
  ],
24
24
  numpad: [
25
25
  ["scr", "+", "-", "*"],
package/main.js CHANGED
@@ -34,7 +34,7 @@ window.onload = () => {
34
34
  try {
35
35
  // Initialize per-page crypto helper
36
36
  window.VKCrypto = createSessionCrypto();
37
-
37
+
38
38
  window.keyboard = new VirtualKeyboard();
39
39
  console.log("VirtualKeyboard initialized successfully.");
40
40
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js_lis",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,10 @@
1
+ // CSS-in-JS version of kbstyle.css
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: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
+ `;
5
+
6
+ export function injectCSS() {
7
+ const style = document.createElement('style');
8
+ style.textContent = css;
9
+ document.head.appendChild(style);
10
+ }