js_lis 2.0.1 → 2.0.2

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.
Files changed (3) hide show
  1. package/VirtualKeyboard.js +698 -624
  2. package/main.js +33 -0
  3. package/package.json +1 -1
@@ -1,625 +1,699 @@
1
- import { layouts } from "./layouts.js";
2
-
3
- export class VirtualKeyboard {
4
- constructor() {
5
- this.currentLayout = "full";
6
- this.isVisible = false;
7
- this.container = document.getElementById("keyboard-container");
8
- this.currentInput = null;
9
- this.layouts = layouts;
10
- this.isDragging = false;
11
- this.offsetX = 0;
12
- this.offsetY = 0;
13
- this.shiftActive = false;
14
- this.capsLockActive = false;
15
- this.isScrambled = false;
16
-
17
- this.initialize();
18
- }
19
-
20
- async initialize() {
21
- try {
22
- this.render();
23
- this.initializeInputListeners();
24
- console.log("VirtualKeyboard initialized successfully.");
25
- } catch (error) {
26
- console.error("Error initializing VirtualKeyboard:", error);
27
- }
28
- }
29
-
30
- getLayoutName(layout) {
31
- switch (layout) {
32
- case "full":
33
- return "Full Keyboard";
34
- case "en":
35
- return "English Keyboard";
36
- case "th":
37
- return "Thai keyboard";
38
- case "numpad":
39
- return "Numpad Keyboard";
40
- case "symbols":
41
- return "Symbols Keyboard";
42
- default:
43
- return "Unknown Layout";
44
- }
45
- }
46
-
47
- initializeInputListeners() {
48
- document.addEventListener("click", (e) => {
49
- const target = e.target;
50
- if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
51
- this.setCurrentInput(target);
52
- }
53
- });
54
-
55
- document.addEventListener(
56
- "focus",
57
- (e) => {
58
- const target = e.target;
59
- if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
60
- this.setCurrentInput(target);
61
- }
62
- },
63
- true
64
- );
65
-
66
- const toggle = document.getElementById("toggle");
67
- if (toggle) {
68
- toggle.addEventListener("click", this.toggle.bind(this));
69
- }
70
- }
71
-
72
- setCurrentInput(inputElement) {
73
- if (this.currentInput) {
74
- this.currentInput.classList.remove("keyboard-active");
75
- }
76
-
77
- this.currentInput = inputElement;
78
- this.currentInput.classList.add("keyboard-active");
79
- }
80
-
81
- // [1]
82
- render() {
83
- const keyboard = document.createElement("div");
84
- keyboard.className = `virtual-keyboard ${this.currentLayout}`;
85
- keyboard.style.display = this.isVisible ? "block" : "none";
86
- keyboard.id = "keyboard";
87
-
88
- const controlsContainer = document.createElement("div");
89
- controlsContainer.className = "controls";
90
- controlsContainer.style.display = "flex";
91
- controlsContainer.style.justifyContent = "center";
92
- controlsContainer.style.alignItems = "center";
93
- controlsContainer.style.marginBottom = "10px";
94
-
95
- const layoutSelector = document.createElement("select");
96
- layoutSelector.id = "layout-selector";
97
- layoutSelector.onchange = (e) => this.changeLayout(e.target.value);
98
-
99
- const layouts = ["full", "en", "th", "numpad", "symbols"];
100
- layouts.forEach((layout) => {
101
- const option = document.createElement("option");
102
- option.value = layout;
103
- option.innerText = this.getLayoutName(layout);
104
- layoutSelector.appendChild(option);
105
- });
106
- layoutSelector.value = this.currentLayout;
107
- controlsContainer.appendChild(layoutSelector);
108
-
109
- keyboard.appendChild(controlsContainer);
110
-
111
- const layout = this.layouts[this.currentLayout];
112
-
113
- layout.forEach((row) => {
114
- const rowElement = document.createElement("div");
115
- rowElement.className = "keyboard-row";
116
-
117
- row.forEach((key, index) => {
118
- const keyElement = document.createElement("button");
119
- keyElement.className = "keyboard-key key";
120
- keyElement.textContent = key;
121
- keyElement.type = "button";
122
-
123
- keyElement.dataset.key = key;
124
-
125
- if (index >= row.length - 4) {
126
- keyElement.classList.add("concat-keys");
127
- }
128
-
129
- if (key === "Space") {
130
- keyElement.className += " space";
131
- }
132
-
133
- if (key === "backspace" || key === "Backspace") {
134
- keyElement.className += " backspacew";
135
- keyElement.innerHTML = '<i class="fa fa-backspace"></i>';
136
- }
137
-
138
- keyElement.onclick = (e) => {
139
- e.preventDefault();
140
- const keyPressed = keyElement.dataset.key || keyElement.textContent;
141
- if (keyPressed) {
142
- this.handleKeyPress(keyPressed);
143
- } else {
144
- console.error("The key element does not have a valid key value.");
145
- }
146
- };
147
-
148
- rowElement.appendChild(keyElement);
149
- });
150
-
151
- keyboard.appendChild(rowElement);
152
- });
153
-
154
- this.container.innerHTML = "";
155
- this.container.appendChild(keyboard);
156
-
157
- keyboard.addEventListener("mousedown", (event) => this.startDrag(event));
158
- }
159
-
160
- // [2]
161
- async handleKeyPress(keyPressed) {
162
- if (!this.currentInput) return;
163
- const start = this.currentInput.selectionStart;
164
- const end = this.currentInput.selectionEnd;
165
- const value = this.currentInput.value;
166
-
167
- const isCapsActive = this.capsLockActive;
168
- const isShiftActive = this.shiftActive;
169
-
170
- const convertToCorrectCase = (char) => {
171
- if (isCapsActive || isShiftActive) {
172
- return char.toUpperCase();
173
- }
174
- return char.toLowerCase();
175
- };
176
-
177
- if (!keyPressed) return console.error("Invalid key pressed.");
178
-
179
- if (keyPressed === "scr" || keyPressed === "scrambled") {
180
- if (this.currentLayout === "en") {
181
- if (this.isScrambled) {
182
- this.unscrambleKeys();
183
- } else {
184
- this.scrambleEnglishKeys();
185
- }
186
- this.isScrambled = !this.isScrambled;
187
- document
188
- .querySelectorAll('.key[data-key="scrambled"]')
189
- .forEach((key) => {
190
- key.classList.toggle("active", this.isScrambled);
191
- });
192
- }
193
-
194
- if (this.currentLayout === "th") {
195
- if (this.isScrambled) {
196
- this.unscrambleKeys();
197
- } else {
198
- this.scrambleThaiKeys();
199
- }
200
- this.isScrambled = !this.isScrambled;
201
- document
202
- .querySelectorAll('.key[data-key="scrambled"]')
203
- .forEach((key) => {
204
- key.classList.toggle("active", this.isScrambled);
205
- });
206
- }
207
-
208
- if (this.currentLayout === "numpad") {
209
- if (this.isScrambled) {
210
- this.unscrambleKeys();
211
- } else {
212
- this.scrambleKeyboard();
213
- }
214
- this.isScrambled = !this.isScrambled;
215
- document.querySelectorAll('.key[data-key="scr"]').forEach((key) => {
216
- key.classList.toggle("active", this.isScrambled);
217
- });
218
- }
219
-
220
- if (this.currentLayout === "symbols") {
221
- if (this.isScrambled) {
222
- this.unscrambleKeys();
223
- } else {
224
- this.scrambleSymbols();
225
- }
226
- this.isScrambled = !this.isScrambled;
227
- document.querySelectorAll('.key[data-key="scr"]').forEach((key) => {
228
- key.classList.toggle("active", this.isScrambled);
229
- });
230
- }
231
- return;
232
- }
233
-
234
- switch (keyPressed) {
235
- case "Esc":
236
- const modals = document.querySelectorAll(".modal");
237
- if (modals.length === 0) {
238
- document.exitFullscreen();
239
- console.warn("No modals found to close.");
240
- }
241
- modals.forEach((modal) => {
242
- modal.classList.add("hidden");
243
- });
244
- break;
245
-
246
- case "F1":
247
- break;
248
-
249
- case "F2":
250
- // เปิดโหมดแก้ไขสำหรับ element ที่เลือก
251
- const activeElement = document.activeElement;
252
- if (activeElement && activeElement.contentEditable !== undefined) {
253
- activeElement.contentEditable = true;
254
- activeElement.focus();
255
- console.log(activeElement.contentEditable);
256
- } else {
257
- console.warn("No editable element found.");
258
- }
259
- break;
260
-
261
- case "F3":
262
- // เปิดการค้นหา
263
- event.preventDefault();
264
- this.option.openSearch();
265
- break;
266
-
267
- case "F4":
268
- // เปิดเมนูการตั้งค่า
269
- event.preventDefault();
270
- this.option.openSettings();
271
- break;
272
-
273
- case "F5":
274
- // รีโหลดหน้าเว็บ (คงเดิม)
275
- window.location.reload();
276
- break;
277
-
278
- case "F6":
279
- // สลับระหว่างโหมดกลางวัน/กลางคืน
280
- document.body.classList.toggle("dark-mode");
281
- break;
282
-
283
- case "F7":
284
- break;
285
-
286
- case "F8":
287
- break;
288
-
289
- case "F9":
290
- break;
291
-
292
- case "F10":
293
- break;
294
-
295
- case "F11":
296
- if (!document.fullscreenElement) {
297
- document.documentElement
298
- .requestFullscreen()
299
- .catch((err) =>
300
- console.error("Error attempting to enable fullscreen:", err)
301
- );
302
- } else {
303
- document
304
- .exitFullscreen()
305
- .catch((err) =>
306
- console.error("Error attempting to exit fullscreen:", err)
307
- );
308
- }
309
- break;
310
-
311
- case "F12":
312
- break;
313
-
314
- case "HOME":
315
- this.currentInput.setSelectionRange(Math.max(0, 0), Math.max(0, 0));
316
- break;
317
-
318
- case "END":
319
- const length = this.currentInput.value.length;
320
- this.currentInput.setSelectionRange(length, length);
321
- break;
322
-
323
- case "Backspace":
324
- case "backspace":
325
- if (start === end && start > 0) {
326
- this.currentInput.value = value.slice(0, start - 1) + value.slice(end);
327
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start - 1;
328
- } else {
329
- this.currentInput.value = value.slice(0, start) + value.slice(end);
330
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
331
- }
332
- break;
333
-
334
- case "DEL⌦":
335
- if (start === end && start < value.length) {
336
- this.currentInput.value = value.slice(0, start) + value.slice(end + 1);
337
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
338
- } else {
339
- this.currentInput.value = value.slice(0, start) + value.slice(end);
340
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
341
- }
342
- break;
343
-
344
- case "Space":
345
- await this.insertText(" ");
346
- break;
347
-
348
- case "Tab ↹":
349
- await this.insertText("\t");
350
- break;
351
-
352
- case "Enter":
353
- if (this.currentInput.tagName === "TEXTAREA") {
354
- this.currentInput.value = value.slice(0, start) + "\n" + value.slice(end);
355
- this.currentInput.setSelectionRange(start + 1, start + 1);
356
- } else if (this.currentInput.tagName === "INPUT" || this.currentInput.type === "password" || this.currentInput.type === "text" ) {
357
- if (this.currentInput.form) {
358
- const submitButton = this.currentInput.form.querySelector(
359
- 'input[type="submit"], button[type="submit"], button[type="button"], button[onclick]'
360
- );
361
- if (submitButton) submitButton.click(); else this.currentInput.form.submit();
362
- } else {
363
- this.currentInput.value += "\n";
364
- }
365
- }
366
- break;
367
-
368
- case "Caps 🄰":
369
- this.toggleCapsLock();
370
- break;
371
-
372
- case "Shift ⇧":
373
- this.toggleShift();
374
- break;
375
-
376
- case "":
377
- this.currentInput.setSelectionRange(
378
- Math.max(0, start - 1),
379
- Math.max(0, start - 1)
380
- );
381
- break;
382
-
383
- case "→":
384
- this.currentInput.setSelectionRange(start + 1, start + 1);
385
- break;
386
-
387
- case "↑":
388
- case "↓":
389
- const text = this.currentInput.value;
390
- const lines = text.substring(0, start).split("\n");
391
- const currentLineIndex = lines.length - 1;
392
- const currentLine = lines[currentLineIndex];
393
- const columnIndex = start - text.lastIndexOf("\n", start - 1) - 1;
394
-
395
- if (keyPressed === "↑" && currentLineIndex > 0) {
396
- const prevLineLength = lines[currentLineIndex - 1].length;
397
- const newPos = start - currentLine.length - 1 - Math.min(columnIndex, prevLineLength);
398
- this.currentInput.setSelectionRange(newPos, newPos);
399
- } else if (keyPressed === "↓" && currentLineIndex < lines.length - 1) {
400
- const nextLine = lines[currentLineIndex + 1];
401
- const newPos = start + currentLine.length + 1 + Math.min(columnIndex, nextLine.length);
402
- this.currentInput.setSelectionRange(newPos, newPos);
403
- }
404
- break;
405
-
406
- default:
407
- const textvalue = await convertToCorrectCase(keyPressed);
408
- await this.insertText(textvalue);
409
- }
410
-
411
- if (isShiftActive && !isCapsActive) this.toggleShift();
412
-
413
- this.currentInput.focus();
414
- const event = new Event("input", { bubbles: true });
415
- this.currentInput.dispatchEvent(event);
416
- }
417
-
418
- // [3]
419
- async insertText(text) {
420
- const start = this.currentInput.selectionStart;
421
- const end = this.currentInput.selectionEnd;
422
- const textvalue = text;
423
-
424
- this.currentInput.value = this.currentInput.value.slice(0, start) + textvalue + this.currentInput.value.slice(end);
425
- this.currentInput.selectionStart = this.currentInput.selectionEnd = start + textvalue.length;
426
- }
427
-
428
- // [4]
429
- unscrambleKeys() {
430
- this.keys = this.originalKeys;
431
- this.render();
432
- }
433
-
434
- // [5]
435
- toggleCapsLock() {
436
- this.capsLockActive = !this.capsLockActive;
437
-
438
- document.querySelectorAll('.key[data-key="Caps 🄰"]').forEach((key) => {
439
- key.classList.toggle("active", this.capsLockActive);
440
- key.classList.toggle("bg-gray-400", this.capsLockActive);
441
- });
442
-
443
- document.querySelectorAll(".key").forEach((key) => {
444
- const isLetter = key.dataset.key.length === 1 && /[a-zA-Zก-๙]/.test(key.dataset.key);
445
-
446
- if (isLetter) {
447
- key.textContent = this.capsLockActive
448
- ? key.dataset.key.toUpperCase()
449
- : key.dataset.key.toLowerCase();
450
- }
451
- this.updateKeyContent(key, this.capsLockActive);
452
- });
453
- }
454
- updateKeyContent(key, capsLockActive) {
455
- const currentChar = key.textContent.trim();
456
-
457
- const layouts = {
458
- th: this.ThaiAlphabetShift,
459
- en: this.EngAlphabetShift,
460
- enSc: this.EngAlphabetShift,
461
- full: this.FullAlphabetShift,
462
- };
463
- const layout = layouts[this.currentLayout];
464
-
465
- if (!layout) return;
466
-
467
- if (capsLockActive && layout[currentChar]) {
468
- key.textContent = layout[currentChar];
469
- key.dataset.key = layout[currentChar];
470
- } else if (!capsLockActive && Object.values(layout).includes(currentChar)) {
471
- const originalKey = Object.keys(layout).find((k) => layout[k] === currentChar);
472
- if (originalKey) {
473
- key.textContent = originalKey;
474
- key.dataset.key = originalKey;
475
- }
476
- }
477
- }
478
-
479
- // [6]
480
- toggleShift() {
481
- if (this.capsLockActive) {
482
- return this.toggleCapsLock();
483
- }
484
-
485
- this.shiftActive = !this.shiftActive;
486
- document.querySelectorAll('.key[data-key="Shift ⇧"]').forEach((key) => {
487
- key.classList.toggle("active", this.shiftActive);
488
- key.classList.toggle("bg-gray-400", this.shiftActive);
489
- });
490
-
491
- document.querySelectorAll(".key").forEach((key) => {
492
- const isLetter = key.dataset.key.length === 1 && /[a-zA-Zก-๙]/.test(key.dataset.key);
493
-
494
- if (isLetter) {
495
- key.textContent = this.shiftActive
496
- ? key.dataset.key.toUpperCase()
497
- : key.dataset.key.toLowerCase();
498
- }
499
- this.updateKeyContent(key, this.shiftActive);
500
- });
501
- }
502
- updateKeyContent(key, shiftActive) {
503
- const currentChar = key.textContent.trim();
504
-
505
- const layouts = {
506
- th: this.ThaiAlphabetShift,
507
- en: this.EngAlphabetShift,
508
- enSc: this.EngAlphabetShift,
509
- full: this.FullAlphabetShift,
510
- };
511
- const layout = layouts[this.currentLayout];
512
-
513
- if (!layout) return;
514
-
515
- if (shiftActive && layout[currentChar]) {
516
- key.textContent = layout[currentChar];
517
- key.dataset.key = layout[currentChar];
518
- } else if (!shiftActive && Object.values(layout).includes(currentChar)) {
519
- const originalKey = Object.keys(layout).find((k) => layout[k] === currentChar);
520
- if (originalKey) {
521
- key.textContent = originalKey;
522
- key.dataset.key = originalKey;
523
- }
524
- }
525
- }
526
-
527
- // [7]
528
- EngAlphabetShift = { "`": "~", 1: "!", 2: "@", 3: "#", 4: "$", 5: "%", 6: "^", 7: "&", 8: "*", 9: "(", 0: ")", "-": "_", "=": "+", "[": "{", "]": "}", "\\": "|", ";": ":", "'": '"', ",": "<", ".": ">", "/": "?", };
529
- ThaiAlphabetShift = { _: "%", ๅ: "+", "/": "๑", "-": "๒", ภ: "๓", ถ: "๔", "ุ": "ู", "ึ": "฿", ค: "๕", ต: "๖", จ: "๗", ข: "๘", ช: "๙", ๆ: "๐", ไ: '"', ำ: "ฎ", พ: "ฑ", ะ: "ธ", "ั": "ํ", "ี": "๋", ร: "ณ", น: "ฯ", ย: "ญ", บ: "ฐ", ล: ",", ฃ: "ฅ", ฟ: "ฤ", ห: "ฆ", ก: "ฏ", ด: "โ", เ: "ฌ", "้": "็", "่": "๋", า: "ษ", ส: "ศ", ว: "ซ", ง: ".", ผ: "(", ป: ")", แ: "ฉ", อ: "ฮ", "ิ": "ฺ", "ื": "์", ท: "?", ม: "ฒ", ใ: "ฬ", ฝ: "ฦ"};
530
- FullAlphabetShift = { "[": "{", "]": "}", "\\": "|", ";": ":", "'": '"', ",": "<", ".": ">", "/": "?", };
531
-
532
- // [8]
533
- toggle() {
534
- this.isVisible = !this.isVisible;
535
- this.render();
536
- }
537
-
538
- // [9]
539
- changeLayout(layout) {
540
- this.currentLayout = layout;
541
- this.render();
542
- }
543
-
544
- // [10]
545
- shuffleArray(array) {
546
- for (let i = array.length - 1; i > 0; i--) {
547
- const j = Math.floor(Math.random() * (i + 1));
548
- [array[i], array[j]] = [array[j], array[i]];
549
- }
550
- }
551
-
552
- // [11]
553
- scrambleKeyboard() {
554
- const keys = document.querySelectorAll(
555
- ":not([data-key='std']):not([data-key='scr']).key:not([data-key=backspace]"
556
- );
557
- const numbers = "1234567890-=+%/*.()".split("");
558
- this.shuffleArray(numbers);
559
- keys.forEach((key, index) => {
560
- key.textContent = numbers[index];
561
- key.dataset.key = numbers[index];
562
- });
563
- }
564
-
565
- // [12]
566
- scrambleEnglishKeys() {
567
- const keys = document.querySelectorAll(
568
- ".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 ↹'])"
569
- );
570
- const englishAlphabet = "abcdefghijklmnopqrstuvwxyz/.,';\\][`1234567890-=".split("");
571
- this.shuffleArray(englishAlphabet);
572
- keys.forEach((key, index) => {
573
- key.textContent = englishAlphabet[index];
574
- key.dataset.key = englishAlphabet[index];
575
- });
576
- }
577
-
578
- // [13]
579
- scrambleThaiKeys() {
580
- const keys = document.querySelectorAll(
581
- ".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 ↹'])"
582
- );
583
- const ThaiAlphabet = '_ๅ/-ภถุึคตจขชๆไำพะัีรนยบลฃฟหกดเ้่าสวงผปแอิืทมใฝ%+๑๒๓๔ู฿๕๖๗๘๙๐"ฎฑธํ๊ณฯญฐ,ฅฤฆฏโฌ็๋ษศซ.ฦฬฒ?์ฺฮฉ)('.split("");
584
- this.shuffleArray(ThaiAlphabet);
585
- keys.forEach((key, index) => {
586
- key.textContent = ThaiAlphabet[index];
587
- key.dataset.key = ThaiAlphabet[index];
588
- });
589
- }
590
-
591
- // []
592
- scrambleSymbols() {
593
- const keys = document.querySelectorAll(
594
- ":not([data-key='std']):not([data-key='scr']).key:not([data-key=backspace]"
595
- );
596
- const numbers = "@#$%^&*()_+~`{}|\\:'<>?/[]±§¶!€£¥¢©®™℅‰†".split("");
597
- this.shuffleArray(numbers);
598
- keys.forEach((key, index) => {
599
- key.textContent = numbers[index];
600
- key.dataset.key = numbers[index];
601
- });
602
- }
603
-
604
- // [14] จัดการการลากคีย์บอร์ด
605
- startDrag(event) {
606
- this.isDragging = true;
607
- this.offsetX = event.clientX - document.getElementById("keyboard").offsetLeft;
608
- this.offsetY = event.clientY - document.getElementById("keyboard").offsetTop;
609
-
610
- document.addEventListener("mousemove", this.drag.bind(this));
611
- document.addEventListener("mouseup", () => {
612
- this.isDragging = false;
613
- document.removeEventListener("mousemove", this.drag.bind(this));
614
- });
615
- }
616
-
617
- // [15]
618
- drag(event) {
619
- if (this.isDragging) {
620
- const keyboard = document.getElementById("keyboard");
621
- keyboard.style.left = `${event.clientX - this.offsetX}px`;
622
- keyboard.style.top = `${event.clientY - this.offsetY}px`;
623
- }
624
- }
1
+ import { layouts } from "./layouts.js";
2
+
3
+ export class VirtualKeyboard {
4
+ constructor() {
5
+ this.currentLayout = "full";
6
+ this.isVisible = false;
7
+ this.container = document.getElementById("keyboard-container");
8
+ this.currentInput = null;
9
+ this.layouts = layouts;
10
+ this.isDragging = false;
11
+ this.offsetX = 0;
12
+ this.offsetY = 0;
13
+ this.shiftActive = false;
14
+ this.capsLockActive = false;
15
+ this.isScrambled = false;
16
+
17
+ // Maintain plaintext buffers per input (not stored in DOM). DOM will store only encrypted text.
18
+ this.inputPlaintextBuffers = new WeakMap();
19
+
20
+ this.initialize();
21
+ }
22
+
23
+ async initialize() {
24
+ try {
25
+ this.render();
26
+ this.initializeInputListeners();
27
+ console.log("VirtualKeyboard initialized successfully.");
28
+ } catch (error) {
29
+ console.error("Error initializing VirtualKeyboard:", error);
30
+ }
31
+ }
32
+
33
+ getLayoutName(layout) {
34
+ switch (layout) {
35
+ case "full":
36
+ return "Full Keyboard";
37
+ case "en":
38
+ return "English Keyboard";
39
+ case "th":
40
+ return "Thai keyboard";
41
+ case "numpad":
42
+ return "Numpad Keyboard";
43
+ case "symbols":
44
+ return "Symbols Keyboard";
45
+ default:
46
+ return "Unknown Layout";
47
+ }
48
+ }
49
+
50
+ initializeInputListeners() {
51
+ document.addEventListener("click", (e) => {
52
+ const target = e.target;
53
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
54
+ this.setCurrentInput(target);
55
+ }
56
+ });
57
+
58
+ document.addEventListener(
59
+ "focus",
60
+ (e) => {
61
+ const target = e.target;
62
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
63
+ this.setCurrentInput(target);
64
+ }
65
+ },
66
+ true
67
+ );
68
+
69
+ const toggle = document.getElementById("toggle");
70
+ if (toggle) {
71
+ toggle.addEventListener("click", this.toggle.bind(this));
72
+ }
73
+ }
74
+
75
+ setCurrentInput(inputElement) {
76
+ if (this.currentInput) {
77
+ this.currentInput.classList.remove("keyboard-active");
78
+ }
79
+
80
+ this.currentInput = inputElement;
81
+ this.currentInput.classList.add("keyboard-active");
82
+
83
+ // Ensure buffer initialized for this input
84
+ if (!this.inputPlaintextBuffers.has(this.currentInput)) {
85
+ const ds = this.currentInput.dataset ? this.currentInput.dataset.encrypted : undefined;
86
+ let initial = "";
87
+ try {
88
+ if (ds && window.VKCrypto && typeof window.VKCrypto.decrypt === "function") {
89
+ initial = window.VKCrypto.decrypt(ds) || "";
90
+ } else {
91
+ initial = this.currentInput.value || "";
92
+ }
93
+ } catch (_) {
94
+ initial = this.currentInput.value || "";
95
+ }
96
+ this.inputPlaintextBuffers.set(this.currentInput, initial);
97
+ this.updateDomEncrypted(this.currentInput, initial);
98
+ }
99
+ // Sync visible with buffer (masking for password)
100
+ this.updateDisplayedValue();
101
+ }
102
+
103
+ // [1]
104
+ render() {
105
+ const keyboard = document.createElement("div");
106
+ keyboard.className = `virtual-keyboard ${this.currentLayout}`;
107
+ keyboard.style.display = this.isVisible ? "block" : "none";
108
+ keyboard.id = "keyboard";
109
+
110
+ const controlsContainer = document.createElement("div");
111
+ controlsContainer.className = "controls";
112
+ controlsContainer.style.display = "flex";
113
+ controlsContainer.style.justifyContent = "center";
114
+ controlsContainer.style.alignItems = "center";
115
+ controlsContainer.style.marginBottom = "10px";
116
+
117
+ const layoutSelector = document.createElement("select");
118
+ layoutSelector.id = "layout-selector";
119
+ layoutSelector.onchange = (e) => this.changeLayout(e.target.value);
120
+
121
+ const layouts = ["full", "en", "th", "numpad", "symbols"];
122
+ layouts.forEach((layout) => {
123
+ const option = document.createElement("option");
124
+ option.value = layout;
125
+ option.innerText = this.getLayoutName(layout);
126
+ layoutSelector.appendChild(option);
127
+ });
128
+ layoutSelector.value = this.currentLayout;
129
+ controlsContainer.appendChild(layoutSelector);
130
+
131
+ keyboard.appendChild(controlsContainer);
132
+
133
+ const layout = this.layouts[this.currentLayout];
134
+
135
+ layout.forEach((row) => {
136
+ const rowElement = document.createElement("div");
137
+ rowElement.className = "keyboard-row";
138
+
139
+ row.forEach((key, index) => {
140
+ const keyElement = document.createElement("button");
141
+ keyElement.className = "keyboard-key key";
142
+ keyElement.textContent = key;
143
+ keyElement.type = "button";
144
+
145
+ keyElement.dataset.key = key;
146
+
147
+ if (index >= row.length - 4) {
148
+ keyElement.classList.add("concat-keys");
149
+ }
150
+
151
+ if (key === "Space") {
152
+ keyElement.className += " space";
153
+ }
154
+
155
+ if (key === "backspace" || key === "Backspace") {
156
+ keyElement.className += " backspacew";
157
+ keyElement.innerHTML = '<i class="fa fa-backspace"></i>';
158
+ }
159
+
160
+ keyElement.onclick = (e) => {
161
+ e.preventDefault();
162
+ const keyPressed = keyElement.dataset.key || keyElement.textContent;
163
+ if (keyPressed) {
164
+ this.handleKeyPress(keyPressed);
165
+ } else {
166
+ console.error("The key element does not have a valid key value.");
167
+ }
168
+ };
169
+
170
+ rowElement.appendChild(keyElement);
171
+ });
172
+
173
+ keyboard.appendChild(rowElement);
174
+ });
175
+
176
+ this.container.innerHTML = "";
177
+ this.container.appendChild(keyboard);
178
+
179
+ keyboard.addEventListener("mousedown", (event) => this.startDrag(event));
180
+ }
181
+
182
+ // [2]
183
+ async handleKeyPress(keyPressed) {
184
+ if (!this.currentInput) return;
185
+ const start = this.currentInput.selectionStart;
186
+ const end = this.currentInput.selectionEnd;
187
+ const buffer = this.getCurrentBuffer();
188
+
189
+ const isCapsActive = this.capsLockActive;
190
+ const isShiftActive = this.shiftActive;
191
+
192
+ const convertToCorrectCase = (char) => {
193
+ if (isCapsActive || isShiftActive) {
194
+ return char.toUpperCase();
195
+ }
196
+ return char.toLowerCase();
197
+ };
198
+
199
+ if (!keyPressed) return console.error("Invalid key pressed.");
200
+
201
+ if (keyPressed === "scr" || keyPressed === "scrambled") {
202
+ if (this.currentLayout === "en") {
203
+ if (this.isScrambled) {
204
+ this.unscrambleKeys();
205
+ } else {
206
+ this.scrambleEnglishKeys();
207
+ }
208
+ this.isScrambled = !this.isScrambled;
209
+ document
210
+ .querySelectorAll('.key[data-key="scrambled"]')
211
+ .forEach((key) => {
212
+ key.classList.toggle("active", this.isScrambled);
213
+ });
214
+ }
215
+
216
+ if (this.currentLayout === "th") {
217
+ if (this.isScrambled) {
218
+ this.unscrambleKeys();
219
+ } else {
220
+ this.scrambleThaiKeys();
221
+ }
222
+ this.isScrambled = !this.isScrambled;
223
+ document
224
+ .querySelectorAll('.key[data-key="scrambled"]')
225
+ .forEach((key) => {
226
+ key.classList.toggle("active", this.isScrambled);
227
+ });
228
+ }
229
+
230
+ if (this.currentLayout === "numpad") {
231
+ if (this.isScrambled) {
232
+ this.unscrambleKeys();
233
+ } else {
234
+ this.scrambleKeyboard();
235
+ }
236
+ this.isScrambled = !this.isScrambled;
237
+ document.querySelectorAll('.key[data-key="scr"]').forEach((key) => {
238
+ key.classList.toggle("active", this.isScrambled);
239
+ });
240
+ }
241
+
242
+ if (this.currentLayout === "symbols") {
243
+ if (this.isScrambled) {
244
+ this.unscrambleKeys();
245
+ } else {
246
+ this.scrambleSymbols();
247
+ }
248
+ this.isScrambled = !this.isScrambled;
249
+ document.querySelectorAll('.key[data-key="scr"]').forEach((key) => {
250
+ key.classList.toggle("active", this.isScrambled);
251
+ });
252
+ }
253
+ return;
254
+ }
255
+
256
+ switch (keyPressed) {
257
+ case "Esc":
258
+ const modals = document.querySelectorAll(".modal");
259
+ if (modals.length === 0) {
260
+ document.exitFullscreen();
261
+ console.warn("No modals found to close.");
262
+ }
263
+ modals.forEach((modal) => {
264
+ modal.classList.add("hidden");
265
+ });
266
+ break;
267
+
268
+ case "F1":
269
+ break;
270
+
271
+ case "F2":
272
+ // เปิดโหมดแก้ไขสำหรับ element ที่เลือก
273
+ const activeElement = document.activeElement;
274
+ if (activeElement && activeElement.contentEditable !== undefined) {
275
+ activeElement.contentEditable = true;
276
+ activeElement.focus();
277
+ console.log(activeElement.contentEditable);
278
+ } else {
279
+ console.warn("No editable element found.");
280
+ }
281
+ break;
282
+
283
+ case "F3":
284
+ // เปิดการค้นหา
285
+ event.preventDefault();
286
+ this.option.openSearch();
287
+ break;
288
+
289
+ case "F4":
290
+ // เปิดเมนูการตั้งค่า
291
+ event.preventDefault();
292
+ this.option.openSettings();
293
+ break;
294
+
295
+ case "F5":
296
+ // รีโหลดหน้าเว็บ (คงเดิม)
297
+ window.location.reload();
298
+ break;
299
+
300
+ case "F6":
301
+ // สลับระหว่างโหมดกลางวัน/กลางคืน
302
+ document.body.classList.toggle("dark-mode");
303
+ break;
304
+
305
+ case "F7":
306
+ break;
307
+
308
+ case "F8":
309
+ break;
310
+
311
+ case "F9":
312
+ break;
313
+
314
+ case "F10":
315
+ break;
316
+
317
+ case "F11":
318
+ if (!document.fullscreenElement) {
319
+ document.documentElement
320
+ .requestFullscreen()
321
+ .catch((err) =>
322
+ console.error("Error attempting to enable fullscreen:", err)
323
+ );
324
+ } else {
325
+ document
326
+ .exitFullscreen()
327
+ .catch((err) =>
328
+ console.error("Error attempting to exit fullscreen:", err)
329
+ );
330
+ }
331
+ break;
332
+
333
+ case "F12":
334
+ break;
335
+
336
+ case "HOME":
337
+ this.currentInput.setSelectionRange(Math.max(0, 0), Math.max(0, 0));
338
+ break;
339
+
340
+ case "END":
341
+ const length = this.currentInput.value.length;
342
+ this.currentInput.setSelectionRange(length, length);
343
+ break;
344
+
345
+ case "Backspace":
346
+ case "backspace":
347
+ if (start === end && start > 0) {
348
+ const newBuffer = buffer.slice(0, start - 1) + buffer.slice(end);
349
+ this.setCurrentBuffer(newBuffer);
350
+ this.currentInput.selectionStart = this.currentInput.selectionEnd = start - 1;
351
+ } else {
352
+ const newBuffer = buffer.slice(0, start) + buffer.slice(end);
353
+ this.setCurrentBuffer(newBuffer);
354
+ this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
355
+ }
356
+ this.updateDisplayedValue();
357
+ break;
358
+
359
+ case "DEL⌦":
360
+ if (start === end && start < buffer.length) {
361
+ const newBuffer = buffer.slice(0, start) + buffer.slice(end + 1);
362
+ this.setCurrentBuffer(newBuffer);
363
+ this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
364
+ } else {
365
+ const newBuffer = buffer.slice(0, start) + buffer.slice(end);
366
+ this.setCurrentBuffer(newBuffer);
367
+ this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
368
+ }
369
+ this.updateDisplayedValue();
370
+ break;
371
+
372
+ case "Space":
373
+ await this.insertText(" ");
374
+ break;
375
+
376
+ case "Tab ↹":
377
+ await this.insertText("\t");
378
+ break;
379
+
380
+ case "Enter":
381
+ if (this.currentInput.tagName === "TEXTAREA") {
382
+ const newBuffer = buffer.slice(0, start) + "\n" + buffer.slice(end);
383
+ this.setCurrentBuffer(newBuffer);
384
+ this.updateDisplayedValue();
385
+ this.currentInput.setSelectionRange(start + 1, start + 1);
386
+ } else if (this.currentInput.tagName === "INPUT" || this.currentInput.type === "password" || this.currentInput.type === "text" ) {
387
+ if (this.currentInput.form) {
388
+ const submitButton = this.currentInput.form.querySelector(
389
+ 'input[type="submit"], button[type="submit"], button[type="button"], button[onclick]'
390
+ );
391
+ if (submitButton) submitButton.click(); else this.currentInput.form.submit();
392
+ } else {
393
+ const newBuffer = buffer + "\n";
394
+ this.setCurrentBuffer(newBuffer);
395
+ this.updateDisplayedValue();
396
+ }
397
+ }
398
+ break;
399
+
400
+ case "Caps 🄰":
401
+ this.toggleCapsLock();
402
+ break;
403
+
404
+ case "Shift ⇧":
405
+ this.toggleShift();
406
+ break;
407
+
408
+ case "←":
409
+ this.currentInput.setSelectionRange(
410
+ Math.max(0, start - 1),
411
+ Math.max(0, start - 1)
412
+ );
413
+ break;
414
+
415
+ case "→":
416
+ this.currentInput.setSelectionRange(start + 1, start + 1);
417
+ break;
418
+
419
+ case "↑":
420
+ case "↓":
421
+ const text = this.currentInput.value;
422
+ const lines = text.substring(0, start).split("\n");
423
+ const currentLineIndex = lines.length - 1;
424
+ const currentLine = lines[currentLineIndex];
425
+ const columnIndex = start - text.lastIndexOf("\n", start - 1) - 1;
426
+
427
+ if (keyPressed === "↑" && currentLineIndex > 0) {
428
+ const prevLineLength = lines[currentLineIndex - 1].length;
429
+ const newPos = start - currentLine.length - 1 - Math.min(columnIndex, prevLineLength);
430
+ this.currentInput.setSelectionRange(newPos, newPos);
431
+ } else if (keyPressed === "↓" && currentLineIndex < lines.length - 1) {
432
+ const nextLine = lines[currentLineIndex + 1];
433
+ const newPos = start + currentLine.length + 1 + Math.min(columnIndex, nextLine.length);
434
+ this.currentInput.setSelectionRange(newPos, newPos);
435
+ }
436
+ break;
437
+
438
+ default:
439
+ const textvalue = await convertToCorrectCase(keyPressed);
440
+ await this.insertText(textvalue);
441
+ }
442
+
443
+ if (isShiftActive && !isCapsActive) this.toggleShift();
444
+
445
+ this.currentInput.focus();
446
+ const event = new Event("input", { bubbles: true });
447
+ this.currentInput.dispatchEvent(event);
448
+ }
449
+
450
+ // [3]
451
+ async insertText(text) {
452
+ const start = this.currentInput.selectionStart;
453
+ const end = this.currentInput.selectionEnd;
454
+ const textvalue = text;
455
+
456
+ const buffer = this.getCurrentBuffer();
457
+ const newBuffer = buffer.slice(0, start) + textvalue + buffer.slice(end);
458
+ this.setCurrentBuffer(newBuffer);
459
+ this.updateDisplayedValue();
460
+ this.currentInput.selectionStart = this.currentInput.selectionEnd = start + textvalue.length;
461
+ }
462
+
463
+ // Helper: get current plaintext buffer for active input
464
+ getCurrentBuffer() {
465
+ if (!this.currentInput) return "";
466
+ return this.inputPlaintextBuffers.get(this.currentInput) || "";
467
+ }
468
+
469
+ // Helper: set buffer and update encrypted dataset
470
+ setCurrentBuffer(newBuffer) {
471
+ if (!this.currentInput) return;
472
+ this.inputPlaintextBuffers.set(this.currentInput, newBuffer);
473
+ this.updateDomEncrypted(this.currentInput, newBuffer);
474
+ }
475
+
476
+ // Helper: reflect masked value in the visible input/textarea
477
+ updateDisplayedValue() {
478
+ if (!this.currentInput) return;
479
+ const buffer = this.getCurrentBuffer();
480
+ const isPassword = this.currentInput.type === "password";
481
+ const maskChar = "•";
482
+ this.currentInput.value = isPassword ? maskChar.repeat(buffer.length) : buffer;
483
+ }
484
+
485
+ // Helper: encrypt and store ciphertext in DOM attribute
486
+ updateDomEncrypted(input, plaintext) {
487
+ try {
488
+ const crypto = window.VKCrypto;
489
+ if (crypto && typeof crypto.encrypt === "function") {
490
+ const cipher = plaintext.length ? crypto.encrypt(plaintext) : "";
491
+ input.dataset.encrypted = cipher;
492
+ } else {
493
+ // Fallback: store plaintext if crypto is unavailable (not recommended)
494
+ input.dataset.encrypted = plaintext;
495
+ }
496
+ } catch (err) {
497
+ console.error("Failed to encrypt input:", err);
498
+ input.dataset.encrypted = "";
499
+ }
500
+ }
501
+
502
+ // [4]
503
+ unscrambleKeys() {
504
+ this.keys = this.originalKeys;
505
+ this.render();
506
+ }
507
+
508
+ // [5]
509
+ toggleCapsLock() {
510
+ this.capsLockActive = !this.capsLockActive;
511
+
512
+ document.querySelectorAll('.key[data-key="Caps 🄰"]').forEach((key) => {
513
+ key.classList.toggle("active", this.capsLockActive);
514
+ key.classList.toggle("bg-gray-400", this.capsLockActive);
515
+ });
516
+
517
+ document.querySelectorAll(".key").forEach((key) => {
518
+ const isLetter = key.dataset.key.length === 1 && /[a-zA-Zก-๙]/.test(key.dataset.key);
519
+
520
+ if (isLetter) {
521
+ key.textContent = this.capsLockActive
522
+ ? key.dataset.key.toUpperCase()
523
+ : key.dataset.key.toLowerCase();
524
+ }
525
+ this.updateKeyContent(key, this.capsLockActive);
526
+ });
527
+ }
528
+ updateKeyContent(key, capsLockActive) {
529
+ const currentChar = key.textContent.trim();
530
+
531
+ const layouts = {
532
+ th: this.ThaiAlphabetShift,
533
+ en: this.EngAlphabetShift,
534
+ enSc: this.EngAlphabetShift,
535
+ full: this.FullAlphabetShift,
536
+ };
537
+ const layout = layouts[this.currentLayout];
538
+
539
+ if (!layout) return;
540
+
541
+ if (capsLockActive && layout[currentChar]) {
542
+ key.textContent = layout[currentChar];
543
+ key.dataset.key = layout[currentChar];
544
+ } else if (!capsLockActive && Object.values(layout).includes(currentChar)) {
545
+ const originalKey = Object.keys(layout).find((k) => layout[k] === currentChar);
546
+ if (originalKey) {
547
+ key.textContent = originalKey;
548
+ key.dataset.key = originalKey;
549
+ }
550
+ }
551
+ }
552
+
553
+ // [6]
554
+ toggleShift() {
555
+ if (this.capsLockActive) {
556
+ return this.toggleCapsLock();
557
+ }
558
+
559
+ this.shiftActive = !this.shiftActive;
560
+ document.querySelectorAll('.key[data-key="Shift ⇧"]').forEach((key) => {
561
+ key.classList.toggle("active", this.shiftActive);
562
+ key.classList.toggle("bg-gray-400", this.shiftActive);
563
+ });
564
+
565
+ document.querySelectorAll(".key").forEach((key) => {
566
+ const isLetter = key.dataset.key.length === 1 && /[a-zA-Zก-๙]/.test(key.dataset.key);
567
+
568
+ if (isLetter) {
569
+ key.textContent = this.shiftActive
570
+ ? key.dataset.key.toUpperCase()
571
+ : key.dataset.key.toLowerCase();
572
+ }
573
+ this.updateKeyContent(key, this.shiftActive);
574
+ });
575
+ }
576
+ updateKeyContent(key, shiftActive) {
577
+ const currentChar = key.textContent.trim();
578
+
579
+ const layouts = {
580
+ th: this.ThaiAlphabetShift,
581
+ en: this.EngAlphabetShift,
582
+ enSc: this.EngAlphabetShift,
583
+ full: this.FullAlphabetShift,
584
+ };
585
+ const layout = layouts[this.currentLayout];
586
+
587
+ if (!layout) return;
588
+
589
+ if (shiftActive && layout[currentChar]) {
590
+ key.textContent = layout[currentChar];
591
+ key.dataset.key = layout[currentChar];
592
+ } else if (!shiftActive && Object.values(layout).includes(currentChar)) {
593
+ const originalKey = Object.keys(layout).find((k) => layout[k] === currentChar);
594
+ if (originalKey) {
595
+ key.textContent = originalKey;
596
+ key.dataset.key = originalKey;
597
+ }
598
+ }
599
+ }
600
+
601
+ // [7]
602
+ EngAlphabetShift = { "`": "~", 1: "!", 2: "@", 3: "#", 4: "$", 5: "%", 6: "^", 7: "&", 8: "*", 9: "(", 0: ")", "-": "_", "=": "+", "[": "{", "]": "}", "\\": "|", ";": ":", "'": '"', ",": "<", ".": ">", "/": "?", };
603
+ ThaiAlphabetShift = { _: "%", ๅ: "+", "/": "๑", "-": "๒", ภ: "๓", ถ: "๔", "ุ": "ู", "ึ": "฿", ค: "๕", ต: "๖", จ: "๗", ข: "๘", ช: "๙", ๆ: "๐", ไ: '"', ำ: "ฎ", พ: "ฑ", ะ: "ธ", "ั": "ํ", "ี": "๋", ร: "ณ", น: "ฯ", ย: "ญ", บ: "ฐ", ล: ",", ฃ: "ฅ", ฟ: "ฤ", ห: "ฆ", ก: "ฏ", ด: "โ", เ: "ฌ", "้": "็", "่": "๋", า: "ษ", ส: "ศ", ว: "ซ", ง: ".", ผ: "(", ป: ")", แ: "ฉ", อ: "ฮ", "ิ": "ฺ", "ื": "์", ท: "?", ม: "ฒ", ใ: "ฬ", ฝ: "ฦ"};
604
+ FullAlphabetShift = { "[": "{", "]": "}", "\\": "|", ";": ":", "'": '"', ",": "<", ".": ">", "/": "?", };
605
+
606
+ // [8]
607
+ toggle() {
608
+ this.isVisible = !this.isVisible;
609
+ this.render();
610
+ }
611
+
612
+ // [9]
613
+ changeLayout(layout) {
614
+ this.currentLayout = layout;
615
+ this.render();
616
+ }
617
+
618
+ // [10]
619
+ shuffleArray(array) {
620
+ for (let i = array.length - 1; i > 0; i--) {
621
+ const j = Math.floor(Math.random() * (i + 1));
622
+ [array[i], array[j]] = [array[j], array[i]];
623
+ }
624
+ }
625
+
626
+ // [11]
627
+ scrambleKeyboard() {
628
+ const keys = document.querySelectorAll(
629
+ ":not([data-key='std']):not([data-key='scr']).key:not([data-key=backspace]"
630
+ );
631
+ const numbers = "1234567890-=+%/*.()".split("");
632
+ this.shuffleArray(numbers);
633
+ keys.forEach((key, index) => {
634
+ key.textContent = numbers[index];
635
+ key.dataset.key = numbers[index];
636
+ });
637
+ }
638
+
639
+ // [12]
640
+ scrambleEnglishKeys() {
641
+ const keys = document.querySelectorAll(
642
+ ".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 ↹'])"
643
+ );
644
+ const englishAlphabet = "abcdefghijklmnopqrstuvwxyz/.,';\\][`1234567890-=".split("");
645
+ this.shuffleArray(englishAlphabet);
646
+ keys.forEach((key, index) => {
647
+ key.textContent = englishAlphabet[index];
648
+ key.dataset.key = englishAlphabet[index];
649
+ });
650
+ }
651
+
652
+ // [13]
653
+ scrambleThaiKeys() {
654
+ const keys = document.querySelectorAll(
655
+ ".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 ↹'])"
656
+ );
657
+ const ThaiAlphabet = '_ๅ/-ภถุึคตจขชๆไำพะัีรนยบลฃฟหกดเ้่าสวงผปแอิืทมใฝ%+๑๒๓๔ู฿๕๖๗๘๙๐"ฎฑธํ๊ณฯญฐ,ฅฤฆฏโฌ็๋ษศซ.ฦฬฒ?์ฺฮฉ)('.split("");
658
+ this.shuffleArray(ThaiAlphabet);
659
+ keys.forEach((key, index) => {
660
+ key.textContent = ThaiAlphabet[index];
661
+ key.dataset.key = ThaiAlphabet[index];
662
+ });
663
+ }
664
+
665
+ // []
666
+ scrambleSymbols() {
667
+ const keys = document.querySelectorAll(
668
+ ":not([data-key='std']):not([data-key='scr']).key:not([data-key=backspace]"
669
+ );
670
+ const numbers = "@#$%^&*()_+~`{}|\\:'<>?/[]±§¶!€£¥¢©®™℅‰†".split("");
671
+ this.shuffleArray(numbers);
672
+ keys.forEach((key, index) => {
673
+ key.textContent = numbers[index];
674
+ key.dataset.key = numbers[index];
675
+ });
676
+ }
677
+
678
+ // [14] จัดการการลากคีย์บอร์ด
679
+ startDrag(event) {
680
+ this.isDragging = true;
681
+ this.offsetX = event.clientX - document.getElementById("keyboard").offsetLeft;
682
+ this.offsetY = event.clientY - document.getElementById("keyboard").offsetTop;
683
+
684
+ document.addEventListener("mousemove", this.drag.bind(this));
685
+ document.addEventListener("mouseup", () => {
686
+ this.isDragging = false;
687
+ document.removeEventListener("mousemove", this.drag.bind(this));
688
+ });
689
+ }
690
+
691
+ // [15]
692
+ drag(event) {
693
+ if (this.isDragging) {
694
+ const keyboard = document.getElementById("keyboard");
695
+ keyboard.style.left = `${event.clientX - this.offsetX}px`;
696
+ keyboard.style.top = `${event.clientY - this.offsetY}px`;
697
+ }
698
+ }
625
699
  }