@webcoder49/code-input 2.2.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,15 +6,40 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
6
6
  useCtrlF = false;
7
7
  useCtrlH = false;
8
8
 
9
+ findMatchesOnValueChange = true; // Needed so the program can insert text to the find value and thus add it to Ctrl+Z without highlighting matches.
10
+
11
+ instructions = {
12
+ start: "Search for matches in your code.",
13
+ none: "No matches",
14
+ oneFound: "1 match found.",
15
+ matchIndex: (index, count) => `${index} of ${count} matches.`,
16
+ error: (message) => `Error: ${message}`,
17
+ infiniteLoopError: "Causes an infinite loop",
18
+ closeDialog: "Close Dialog and Return to Editor",
19
+ findPlaceholder: "Find",
20
+ findCaseSensitive: "Match Case Sensitive",
21
+ findRegExp: "Use JavaScript Regular Expression",
22
+ replaceTitle: "Replace",
23
+ replacePlaceholder: "Replace with",
24
+ findNext: "Find Next Occurrence",
25
+ findPrevious: "Find Previous Occurrence",
26
+ replaceActionShort: "Replace",
27
+ replaceAction: "Replace This Occurrence",
28
+ replaceAllActionShort: "Replace All",
29
+ replaceAllAction: "Replace All Occurrences"
30
+ };
31
+
9
32
  /**
10
33
  * Create a find-and-replace command plugin to pass into a template
11
34
  * @param {boolean} useCtrlF Should Ctrl+F be overriden for find-and-replace find functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element, false)`.
12
35
  * @param {boolean} useCtrlH Should Ctrl+H be overriden for find-and-replace replace functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element, true)`.
36
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the find-and-replace.js source code for the English text and available keys.
13
37
  */
14
- constructor(useCtrlF = true, useCtrlH = true) {
38
+ constructor(useCtrlF = true, useCtrlH = true, instructionTranslations = {}) {
15
39
  super([]); // No observed attributes
16
40
  this.useCtrlF = useCtrlF;
17
41
  this.useCtrlH = useCtrlH;
42
+ this.addTranslations(this.instructions, instructionTranslations);
18
43
  }
19
44
 
20
45
  /* Add keystroke events */
@@ -54,13 +79,13 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
54
79
  updateMatchDescription(dialog) {
55
80
  // 1-indexed
56
81
  if(dialog.findInput.value.length == 0) {
57
- dialog.matchDescription.textContent = "Search for matches in your code.";
82
+ dialog.matchDescription.textContent = this.instructions.start;
58
83
  } else if(dialog.findMatchState.numMatches <= 0) {
59
- dialog.matchDescription.textContent = "No matches.";
84
+ dialog.matchDescription.textContent = this.instructions.none;
60
85
  } else if(dialog.findMatchState.numMatches == 1) {
61
- dialog.matchDescription.textContent = "1 match found.";
86
+ dialog.matchDescription.textContent = this.instructions.oneFound;
62
87
  } else {
63
- dialog.matchDescription.textContent = `${dialog.findMatchState.focusedMatchID+1} of ${dialog.findMatchState.numMatches} matches.`;
88
+ dialog.matchDescription.textContent = this.instructions.matchIndex(dialog.findMatchState.focusedMatchID+1, dialog.findMatchState.numMatches);
64
89
  }
65
90
  }
66
91
 
@@ -81,7 +106,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
81
106
  dialog.findInput.classList.add('code-input_find-and-replace_error');
82
107
  // Only show last part of error message
83
108
  let messageParts = err.message.split(": ");
84
- dialog.matchDescription.textContent = "Error: " + messageParts[messageParts.length-1]; // Show only last part of error.
109
+ dialog.matchDescription.textContent = this.instructions.error(messageParts[messageParts.length-1]); // Show only last part of error.
85
110
  return;
86
111
  } else {
87
112
  throw err;
@@ -100,7 +125,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
100
125
  }, 100);
101
126
  }
102
127
 
103
- /* Deal with Enter and Escape being pressed in the find field */
128
+ /* Deal with Enter being pressed in the find field */
104
129
  checkFindPrompt(dialog, codeInput, event) {
105
130
  if (event.key == 'Enter') {
106
131
  // Find next match
@@ -109,11 +134,12 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
109
134
  }
110
135
  }
111
136
 
112
- /* Deal with Enter and Escape being pressed in the replace field */
137
+ /* Deal with Enter being pressed in the replace field */
113
138
  checkReplacePrompt(dialog, codeInput, event) {
114
139
  if (event.key == 'Enter') {
115
140
  // Replace focused match
116
141
  dialog.findMatchState.replaceOnce(dialog.replaceInput.value);
142
+ dialog.replaceInput.focus();
117
143
  this.updateMatchDescription(dialog);
118
144
  }
119
145
  }
@@ -121,9 +147,21 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
121
147
  /* Called with a dialog box keyup event to close and clear the dialog box */
122
148
  cancelPrompt(dialog, codeInput, event) {
123
149
  event.preventDefault();
124
-
150
+
151
+ // Add current value of find/replace to Ctrl+Z stack.
152
+ this.findMatchesOnValueChange = false;
153
+ dialog.findInput.focus();
154
+ dialog.findInput.selectionStart = 0;
155
+ dialog.findInput.selectionEnd = dialog.findInput.value.length;
156
+ document.execCommand("insertText", false, dialog.findInput.value);
157
+ this.findMatchesOnValueChange = true;
158
+
125
159
  // Reset original selection in code-input
126
160
  dialog.textarea.focus();
161
+ dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
162
+ dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
163
+ dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
164
+
127
165
  if(dialog.findMatchState.numMatches > 0) {
128
166
  // Select focused match
129
167
  codeInput.textareaElement.selectionStart = dialog.findMatchState.matchStartIndexes[dialog.findMatchState.focusedMatchID];
@@ -146,14 +184,16 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
146
184
  * @param {boolean} replacePartExpanded whether the replace part of the find-and-replace dialog should be expanded
147
185
  */
148
186
  showPrompt(codeInputElement, replacePartExpanded) {
187
+ let dialog;
149
188
  if(codeInputElement.pluginData.findAndReplace == undefined || codeInputElement.pluginData.findAndReplace.dialog == undefined) {
150
189
  const textarea = codeInputElement.textareaElement;
151
190
 
152
- const dialog = document.createElement('div');
191
+ dialog = document.createElement('div');
153
192
  const findInput = document.createElement('input');
154
193
  const findCaseSensitiveCheckbox = document.createElement('input');
155
194
  const findRegExpCheckbox = document.createElement('input');
156
195
  const matchDescription = document.createElement('code');
196
+ matchDescription.setAttribute("aria-live", "assertive"); // Screen reader must read the number of matches found.
157
197
 
158
198
  const replaceInput = document.createElement('input');
159
199
  const replaceDropdown = document.createElement('details');
@@ -164,7 +204,13 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
164
204
  const findPreviousButton = document.createElement('button');
165
205
  const replaceButton = document.createElement('button');
166
206
  const replaceAllButton = document.createElement('button');
207
+
208
+ // TODO: Make a button element (semantic HTML for accessibility) in next major version
167
209
  const cancel = document.createElement('span');
210
+ cancel.setAttribute("role", "button");
211
+ cancel.setAttribute("aria-label", this.instructions.closeDialog);
212
+ cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
213
+ cancel.setAttribute("title", this.instructions.closeDialog);
168
214
 
169
215
  buttonContainer.appendChild(findNextButton);
170
216
  buttonContainer.appendChild(findPreviousButton);
@@ -184,31 +230,41 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
184
230
 
185
231
  dialog.className = 'code-input_find-and-replace_dialog';
186
232
  findInput.spellcheck = false;
187
- findInput.placeholder = "Find";
233
+ findInput.placeholder = this.instructions.findPlaceholder;
188
234
  findCaseSensitiveCheckbox.setAttribute("type", "checkbox");
189
- findCaseSensitiveCheckbox.title = "Match Case Sensitive";
235
+ findCaseSensitiveCheckbox.title = this.instructions.findCaseSensitive;
190
236
  findCaseSensitiveCheckbox.classList.add("code-input_find-and-replace_case-sensitive-checkbox");
191
237
  findRegExpCheckbox.setAttribute("type", "checkbox");
192
- findRegExpCheckbox.title = "Use JavaScript Regular Expression";
238
+ findRegExpCheckbox.title = this.instructions.findRegExp;
193
239
  findRegExpCheckbox.classList.add("code-input_find-and-replace_reg-exp-checkbox");
194
240
 
195
241
  matchDescription.textContent = "Search for matches in your code.";
196
242
  matchDescription.classList.add("code-input_find-and-replace_match-description");
197
243
 
198
244
 
199
- replaceSummary.innerText = "Replace";
245
+ replaceSummary.innerText = this.instructions.replaceTitle;
200
246
  replaceInput.spellcheck = false;
201
- replaceInput.placeholder = "Replace with";
247
+ replaceInput.placeholder = this.instructions.replacePlaceholder;
202
248
  findNextButton.innerText = "↓";
203
- findNextButton.title = "Find Next Occurence";
249
+ findNextButton.title = this.instructions.findNext;
250
+ findNextButton.setAttribute("aria-label", this.instructions.findNext);
204
251
  findPreviousButton.innerText = "↑";
205
- findPreviousButton.title = "Find Previous Occurence";
252
+ findPreviousButton.title = this.instructions.findPrevious;
253
+ findNextButton.setAttribute("aria-label", this.instructions.findPrevious);
206
254
  replaceButton.className = 'code-input_find-and-replace_button-hidden';
207
- replaceButton.innerText = "Replace";
208
- replaceButton.title = "Replace This Occurence";
255
+ replaceButton.innerText = this.instructions.replaceActionShort;
256
+ replaceButton.title = this.instructions.replaceAction;
257
+ replaceButton.addEventListener("focus", () => {
258
+ // Show replace section
259
+ replaceDropdown.setAttribute("open", true);
260
+ });
209
261
  replaceAllButton.className = 'code-input_find-and-replace_button-hidden';
210
- replaceAllButton.innerText = "Replace All";
211
- replaceAllButton.title = "Replace All Occurences";
262
+ replaceAllButton.innerText = this.instructions.replaceAllActionShort;
263
+ replaceAllButton.title = this.instructions.replaceAllAction;
264
+ replaceAllButton.addEventListener("focus", () => {
265
+ // Show replace section
266
+ replaceDropdown.setAttribute("open", true);
267
+ });
212
268
 
213
269
  findNextButton.addEventListener("click", (event) => {
214
270
  // Stop form submit
@@ -229,7 +285,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
229
285
  event.preventDefault();
230
286
 
231
287
  dialog.findMatchState.replaceOnce(replaceInput.value);
232
- replaceButton.focus();
288
+ dialog.focus();
233
289
  });
234
290
  replaceAllButton.addEventListener("click", (event) => {
235
291
  // Stop form submit
@@ -275,6 +331,16 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
275
331
  /* Stop enter from submitting form */
276
332
  if (event.key == 'Enter') event.preventDefault();
277
333
  });
334
+ replaceInput.addEventListener('input', (event) => {
335
+ // Ctrl+Z can trigger this. If the dialog/replace dropdown aren't open, open them!
336
+ if(dialog.classList.contains("code-input_find-and-replace_hidden-dialog")) {
337
+ // Show prompt
338
+ this.showPrompt(dialog.codeInput, true);
339
+ } else if(!dialog.replaceDropdown.hasAttribute("open")) {
340
+ // Open dropdown
341
+ dialog.replaceDropdown.setAttribute("open", true);
342
+ }
343
+ });
278
344
 
279
345
  dialog.addEventListener('keyup', (event) => {
280
346
  /* Close prompt on Enter pressed */
@@ -282,7 +348,13 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
282
348
  });
283
349
 
284
350
  findInput.addEventListener('keyup', (event) => { this.checkFindPrompt(dialog, codeInputElement, event); });
285
- findInput.addEventListener('input', (event) => { this.updateFindMatches(dialog); });
351
+ findInput.addEventListener('input', (event) => {
352
+ if(this.findMatchesOnValueChange) this.updateFindMatches(dialog);
353
+ // Ctrl+Z can trigger this. If the dialog isn't open, open it!
354
+ if(dialog.classList.contains("code-input_find-and-replace_hidden-dialog")) {
355
+ this.showPrompt(dialog.codeInput, false);
356
+ }
357
+ });
286
358
  findCaseSensitiveCheckbox.addEventListener('click', (event) => { this.updateFindMatches(dialog); });
287
359
  findRegExpCheckbox.addEventListener('click', (event) => { this.updateFindMatches(dialog); });
288
360
 
@@ -291,6 +363,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
291
363
  replaceInput.focus();
292
364
  });
293
365
  cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, codeInputElement, event); });
366
+ cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, codeInputElement, event); });
294
367
 
295
368
  codeInputElement.dialogContainerElement.appendChild(dialog);
296
369
  codeInputElement.pluginData.findAndReplace = {dialog: dialog};
@@ -303,24 +376,45 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
303
376
  // Save selection position
304
377
  dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
305
378
  dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
379
+
380
+ if(dialog.selectionStart < dialog.selectionEnd) {
381
+ // Copy selected text to Find input
382
+ let textToFind = codeInputElement.textareaElement.value.substring(dialog.selectionStart, dialog.selectionEnd);
383
+ dialog.findInput.focus();
384
+ dialog.findInput.selectionStart = 0;
385
+ dialog.findInput.selectionEnd = dialog.findInput.value.length;
386
+ document.execCommand("insertText", false, textToFind);
387
+ }
306
388
  } else {
389
+ dialog = codeInputElement.pluginData.findAndReplace.dialog;
307
390
  // Re-open dialog
308
- codeInputElement.pluginData.findAndReplace.dialog.classList.remove("code-input_find-and-replace_hidden-dialog");
309
- codeInputElement.pluginData.findAndReplace.dialog.findInput.focus();
391
+ dialog.classList.remove("code-input_find-and-replace_hidden-dialog");
392
+ dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
393
+ dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
394
+ dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
395
+ dialog.findInput.focus();
310
396
  if(replacePartExpanded) {
311
- codeInputElement.pluginData.findAndReplace.dialog.replaceDropdown.setAttribute("open", true);
397
+ dialog.replaceDropdown.setAttribute("open", true);
312
398
  } else {
313
- codeInputElement.pluginData.findAndReplace.dialog.replaceDropdown.removeAttribute("open");
399
+ dialog.replaceDropdown.removeAttribute("open");
314
400
  }
401
+ }
315
402
 
316
-
317
- // Highlight matches
318
- this.updateFindMatches(codeInputElement.pluginData.findAndReplace.dialog);
319
-
320
- // Save selection position
321
- codeInputElement.pluginData.findAndReplace.dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
322
- codeInputElement.pluginData.findAndReplace.dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
403
+ // Save selection position
404
+ dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
405
+ dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
406
+
407
+ if(dialog.selectionStart < dialog.selectionEnd) {
408
+ // Copy selected text to Find input
409
+ let textToFind = codeInputElement.textareaElement.value.substring(dialog.selectionStart, dialog.selectionEnd);
410
+ dialog.findInput.focus();
411
+ dialog.findInput.selectionStart = 0;
412
+ dialog.findInput.selectionEnd = dialog.findInput.value.length;
413
+ document.execCommand("insertText", false, textToFind);
323
414
  }
415
+
416
+ // Highlight matches
417
+ this.updateFindMatches(dialog);
324
418
  }
325
419
 
326
420
  /* Event handler for keydown event that makes Ctrl+F open find dialog */
@@ -405,7 +499,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
405
499
  while ((match = searchRegexp.exec(this.codeInput.value)) !== null) {
406
500
  let matchText = match[0];
407
501
  if(matchText.length == 0) {
408
- throw SyntaxError("Causes an infinite loop");
502
+ throw SyntaxError(this.instructions.infiniteLoopError);
409
503
  }
410
504
 
411
505
  // Add next match block if needed
@@ -441,12 +535,14 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
441
535
  this.focusedMatchStartIndex += replacementText.length;
442
536
 
443
537
  // Select the match
538
+ this.codeInput.handleEventsFromTextarea = false;
444
539
  this.codeInput.textareaElement.focus();
445
540
  this.codeInput.textareaElement.selectionStart = this.matchStartIndexes[this.focusedMatchID];
446
541
  this.codeInput.textareaElement.selectionEnd = this.matchEndIndexes[this.focusedMatchID];
447
542
 
448
543
  // Replace it with the replacement text
449
544
  document.execCommand("insertText", false, replacementText);
545
+ this.codeInput.handleEventsFromTextarea = true;
450
546
  }
451
547
  }
452
548
 
@@ -459,6 +555,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
459
555
  // Replace each match
460
556
 
461
557
  // Select the match, taking into account characters added before
558
+ this.codeInput.handleEventsFromTextarea = false;
462
559
  this.codeInput.textareaElement.focus();
463
560
  this.codeInput.textareaElement.selectionStart = this.matchStartIndexes[i] + numCharsAdded;
464
561
  this.codeInput.textareaElement.selectionEnd = this.matchEndIndexes[i] + numCharsAdded;
@@ -467,6 +564,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
467
564
 
468
565
  // Replace it with the replacement text
469
566
  document.execCommand("insertText", false, replacementText);
567
+ this.codeInput.handleEventsFromTextarea = true;
470
568
  }
471
569
  }
472
570
 
@@ -527,7 +625,8 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
527
625
 
528
626
  /* Highlight a match from the find functionality given its start and end indexes in the text.
529
627
  Start from the currentElement as this function is recursive. Use the matchID in the class name
530
- of the match so different matches can be identified. */
628
+ of the match so different matches can be identified.
629
+ This code is similar to codeInput.plugins.SelectTokenCallbacks.SelectedTokenState.updateSelectedTokens*/
531
630
  highlightMatch(matchID, currentElement, startIndex, endIndex) {
532
631
  for(let i = 0; i < currentElement.childNodes.length; i++) {
533
632
  let childElement = currentElement.childNodes[i];
@@ -535,6 +634,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
535
634
 
536
635
  let noInnerElements = false;
537
636
  if(childElement.nodeType == 3) {
637
+ // Text node
538
638
  if(i + 1 < currentElement.childNodes.length && currentElement.childNodes[i+1].nodeType == 3) {
539
639
  // Can merge with next text node
540
640
  currentElement.childNodes[i+1].textContent = childElement.textContent + currentElement.childNodes[i+1].textContent; // Merge textContent with next node
@@ -547,7 +647,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
547
647
 
548
648
  let replacementElement = document.createElement("span");
549
649
  replacementElement.textContent = childText;
550
- replacementElement.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
650
+ replacementElement.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
551
651
 
552
652
  currentElement.replaceChild(replacementElement, childElement);
553
653
  childElement = replacementElement;
@@ -562,7 +662,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
562
662
  let startSpan = document.createElement("span");
563
663
  startSpan.classList.add("code-input_find-and-replace_find-match"); // Highlighted
564
664
  startSpan.setAttribute("data-code-input_find-and-replace_match-id", matchID);
565
- startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
665
+ startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
566
666
  startSpan.textContent = childText.substring(0, endIndex);
567
667
  if(startSpan.textContent[0] == "\n") {
568
668
  // Newline at start - make clear
@@ -597,7 +697,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
597
697
  // Match starts and ends in childElement - highlight middle part
598
698
  // Text node - highlight last part
599
699
  let startSpan = document.createElement("span");
600
- startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
700
+ startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
601
701
  startSpan.textContent = childText.substring(0, startIndex);
602
702
 
603
703
  let middleText = childText.substring(startIndex, endIndex);
@@ -610,7 +710,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
610
710
  }
611
711
 
612
712
  let endSpan = document.createElement("span");
613
- endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
713
+ endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
614
714
  endSpan.textContent = childText.substring(endIndex);
615
715
 
616
716
  childElement.insertAdjacentElement('beforebegin', startSpan);
@@ -624,7 +724,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
624
724
  let endSpan = document.createElement("span");
625
725
  endSpan.classList.add("code-input_find-and-replace_find-match"); // Highlighted
626
726
  endSpan.setAttribute("data-code-input_find-and-replace_match-id", matchID);
627
- endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
727
+ endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
628
728
  endSpan.textContent = childText.substring(startIndex);
629
729
  if(endSpan.textContent[0] == "\n") {
630
730
  // Newline at start - make clear
@@ -649,4 +749,4 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
649
749
  endIndex -= childText.length;
650
750
  }
651
751
  }
652
- }
752
+ }
@@ -1 +1 @@
1
- codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;constructor(a=!0,b=!0){super([]),this.useCtrlF=a,this.useCtrlH=b}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?"Search for matches in your code.":0>=a.findMatchState.numMatches?"No matches.":1==a.findMatchState.numMatches?"1 match found.":`${a.findMatchState.focusedMatchID+1} of ${a.findMatchState.numMatches} matches.`}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent="Error: "+c[c.length-1])}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),a.textarea.focus(),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){if(a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null){const c=a.textareaElement,d=document.createElement("div"),e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code"),i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),d.appendChild(l),d.appendChild(e),d.appendChild(g),d.appendChild(f),d.appendChild(h),j.appendChild(k),j.appendChild(i),d.appendChild(j),d.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder="Find",f.setAttribute("type","checkbox"),f.title="Match Case Sensitive",f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title="Use JavaScript Regular Expression",g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText="Replace",i.spellcheck=!1,i.placeholder="Replace with",m.innerText="\u2193",m.title="Find Next Occurence",n.innerText="\u2191",n.title="Find Previous Occurence",o.className="code-input_find-and-replace_button-hidden",o.innerText="Replace",o.title="Replace This Occurence",p.className="code-input_find-and-replace_button-hidden",p.innerText="Replace All",p.title="Replace All Occurences",m.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.nextMatch(),this.updateMatchDescription(d)}),n.addEventListener("click",()=>{event.preventDefault(),d.findMatchState.previousMatch(),this.updateMatchDescription(d)}),o.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.replaceOnce(i.value),o.focus()}),p.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),d.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),d.codeInput=a,d.textarea=c,d.findInput=e,d.findCaseSensitiveCheckbox=f,d.findRegExpCheckbox=g,d.matchDescription=h,d.replaceInput=i,d.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(d,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(d,a,b)}),e.addEventListener("input",()=>{this.updateFindMatches(d)}),f.addEventListener("click",()=>{this.updateFindMatches(d)}),g.addEventListener("click",()=>{this.updateFindMatches(d)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(d,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(d,a,b)}),a.dialogContainerElement.appendChild(d),a.pluginData.findAndReplace={dialog:d},e.focus(),b&&j.setAttribute("open",!0),d.selectionStart=a.textareaElement.selectionStart,d.selectionEnd=a.textareaElement.selectionEnd}else a.pluginData.findAndReplace.dialog.classList.remove("code-input_find-and-replace_hidden-dialog"),a.pluginData.findAndReplace.dialog.findInput.focus(),b?a.pluginData.findAndReplace.dialog.replaceDropdown.setAttribute("open",!0):a.pluginData.findAndReplace.dialog.replaceDropdown.removeAttribute("open"),this.updateFindMatches(a.pluginData.findAndReplace.dialog),a.pluginData.findAndReplace.dialog.selectionStart=a.textareaElement.selectionStart,a.pluginData.findAndReplace.dialog.selectionEnd=a.textareaElement.selectionEnd}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError("Causes an infinite loop");let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a))}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a)}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
1
+ codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;findMatchesOnValueChange=!0;instructions={start:"Search for matches in your code.",none:"No matches",oneFound:"1 match found.",matchIndex:(a,b)=>`${a} of ${b} matches.`,error:a=>`Error: ${a}`,infiniteLoopError:"Causes an infinite loop",closeDialog:"Close Dialog and Return to Editor",findPlaceholder:"Find",findCaseSensitive:"Match Case Sensitive",findRegExp:"Use JavaScript Regular Expression",replaceTitle:"Replace",replacePlaceholder:"Replace with",findNext:"Find Next Occurrence",findPrevious:"Find Previous Occurrence",replaceActionShort:"Replace",replaceAction:"Replace This Occurrence",replaceAllActionShort:"Replace All",replaceAllAction:"Replace All Occurrences"};constructor(a=!0,b=!0,c={}){super([]),this.useCtrlF=a,this.useCtrlH=b,this.addTranslations(this.instructions,c)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?this.instructions.start:0>=a.findMatchState.numMatches?this.instructions.none:1==a.findMatchState.numMatches?this.instructions.oneFound:this.instructions.matchIndex(a.findMatchState.focusedMatchID+1,a.findMatchState.numMatches)}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent=this.instructions.error(c[c.length-1]))}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),a.replaceInput.focus(),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),this.findMatchesOnValueChange=!1,a.findInput.focus(),a.findInput.selectionStart=0,a.findInput.selectionEnd=a.findInput.value.length,document.execCommand("insertText",!1,a.findInput.value),this.findMatchesOnValueChange=!0,a.textarea.focus(),a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){let c;if(null==a.pluginData.findAndReplace||null==a.pluginData.findAndReplace.dialog){const d=a.textareaElement;c=document.createElement("div");const e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code");h.setAttribute("aria-live","assertive");const i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");if(q.setAttribute("role","button"),q.setAttribute("aria-label",this.instructions.closeDialog),q.setAttribute("tabindex",0),q.setAttribute("title",this.instructions.closeDialog),l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),c.appendChild(l),c.appendChild(e),c.appendChild(g),c.appendChild(f),c.appendChild(h),j.appendChild(k),j.appendChild(i),c.appendChild(j),c.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder=this.instructions.findPlaceholder,f.setAttribute("type","checkbox"),f.title=this.instructions.findCaseSensitive,f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title=this.instructions.findRegExp,g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText=this.instructions.replaceTitle,i.spellcheck=!1,i.placeholder=this.instructions.replacePlaceholder,m.innerText="\u2193",m.title=this.instructions.findNext,m.setAttribute("aria-label",this.instructions.findNext),n.innerText="\u2191",n.title=this.instructions.findPrevious,m.setAttribute("aria-label",this.instructions.findPrevious),o.className="code-input_find-and-replace_button-hidden",o.innerText=this.instructions.replaceActionShort,o.title=this.instructions.replaceAction,o.addEventListener("focus",()=>{j.setAttribute("open",!0)}),p.className="code-input_find-and-replace_button-hidden",p.innerText=this.instructions.replaceAllActionShort,p.title=this.instructions.replaceAllAction,p.addEventListener("focus",()=>{j.setAttribute("open",!0)}),m.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.nextMatch(),this.updateMatchDescription(c)}),n.addEventListener("click",()=>{event.preventDefault(),c.findMatchState.previousMatch(),this.updateMatchDescription(c)}),o.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceOnce(i.value),c.focus()}),p.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),c.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),c.codeInput=a,c.textarea=d,c.findInput=e,c.findCaseSensitiveCheckbox=f,c.findRegExpCheckbox=g,c.matchDescription=h,c.replaceInput=i,c.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("input",()=>{c.classList.contains("code-input_find-and-replace_hidden-dialog")?this.showPrompt(c.codeInput,!0):!c.replaceDropdown.hasAttribute("open")&&c.replaceDropdown.setAttribute("open",!0)}),c.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(c,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(c,a,b)}),e.addEventListener("input",()=>{this.findMatchesOnValueChange&&this.updateFindMatches(c),c.classList.contains("code-input_find-and-replace_hidden-dialog")&&this.showPrompt(c.codeInput,!1)}),f.addEventListener("click",()=>{this.updateFindMatches(c)}),g.addEventListener("click",()=>{this.updateFindMatches(c)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(c,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(c,a,b)}),q.addEventListener("keypress",b=>{("Space"==b.key||"Enter"==b.key)&&this.cancelPrompt(c,a,b)}),a.dialogContainerElement.appendChild(c),a.pluginData.findAndReplace={dialog:c},e.focus(),b&&j.setAttribute("open",!0),c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}}else c=a.pluginData.findAndReplace.dialog,c.classList.remove("code-input_find-and-replace_hidden-dialog"),c.removeAttribute("inert"),c.setAttribute("tabindex",0),c.removeAttribute("aria-hidden"),c.findInput.focus(),b?c.replaceDropdown.setAttribute("open",!0):c.replaceDropdown.removeAttribute("open");if(c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}this.updateFindMatches(c)}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError(this.instructions.infiniteLoopError);let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0)}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
@@ -5,13 +5,20 @@
5
5
  codeInput.plugins.GoToLine = class extends codeInput.Plugin {
6
6
  useCtrlG = false;
7
7
 
8
+ instructions = {
9
+ closeDialog: "Close Dialog and Return to Editor",
10
+ input: "Line:Column / Line no. then Enter",
11
+ };
12
+
8
13
  /**
9
14
  * Create a go-to-line command plugin to pass into a template
10
15
  * @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
16
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
11
17
  */
12
- constructor(useCtrlG = true) {
18
+ constructor(useCtrlG = true, instructionTranslations = {}) {
13
19
  super([]); // No observed attributes
14
20
  this.useCtrlG = useCtrlG;
21
+ this.addTranslations(this.instructions, instructionTranslations);
15
22
  }
16
23
 
17
24
  /* Add keystroke events */
@@ -61,7 +68,12 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
61
68
  /* Called with a dialog box keyup event to close and clear the dialog box */
62
69
  cancelPrompt(dialog, event) {
63
70
  event.preventDefault();
71
+ dialog.codeInput.handleEventsFromTextarea = false;
64
72
  dialog.textarea.focus();
73
+ dialog.codeInput.handleEventsFromTextarea = true;
74
+ dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
75
+ dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
76
+ dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
65
77
 
66
78
  // Remove dialog after animation
67
79
  dialog.classList.add('code-input_go-to-line_hidden-dialog');
@@ -78,14 +90,20 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
78
90
 
79
91
  const dialog = document.createElement('div');
80
92
  const input = document.createElement('input');
93
+
94
+ // TODO: Make a button element (semantic HTML for accessibility) in next major version
81
95
  const cancel = document.createElement('span');
96
+ cancel.setAttribute("role", "button");
97
+ cancel.setAttribute("aria-label", this.instructions.closeDialog);
98
+ cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
99
+ cancel.setAttribute("title", this.instructions.closeDialog);
82
100
 
83
101
  dialog.appendChild(input);
84
102
  dialog.appendChild(cancel);
85
103
 
86
104
  dialog.className = 'code-input_go-to-line_dialog';
87
105
  input.spellcheck = false;
88
- input.placeholder = "Line:Column / Line no. then Enter";
106
+ input.placeholder = this.instructions.input;
89
107
  dialog.codeInput = codeInput;
90
108
  dialog.textarea = textarea;
91
109
  dialog.input = input;
@@ -97,12 +115,16 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
97
115
 
98
116
  input.addEventListener('keyup', (event) => { return this.checkPrompt(dialog, event); });
99
117
  cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
118
+ cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, event); });
100
119
 
101
120
  codeInput.dialogContainerElement.appendChild(dialog);
102
121
  codeInput.pluginData.goToLine = {dialog: dialog};
103
122
  input.focus();
104
123
  } else {
105
124
  codeInput.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog");
125
+ codeInput.pluginData.goToLine.dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
126
+ codeInput.pluginData.goToLine.dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
127
+ codeInput.pluginData.goToLine.dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
106
128
  codeInput.pluginData.goToLine.dialog.input.focus();
107
129
  }
108
130
  }
@@ -1 +1 @@
1
- codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;constructor(a=!0){super([]),this.useCtrlG=a}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.textarea.focus(),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder="Line:Column / Line no. then Enter",c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};
1
+ codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;instructions={closeDialog:"Close Dialog and Return to Editor",input:"Line:Column / Line no. then Enter"};constructor(a=!0,b={}){super([]),this.useCtrlG=a,this.addTranslations(this.instructions,b)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.codeInput.handleEventsFromTextarea=!1,a.textarea.focus(),a.codeInput.handleEventsFromTextarea=!0,a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");e.setAttribute("role","button"),e.setAttribute("aria-label",this.instructions.closeDialog),e.setAttribute("tabindex",0),e.setAttribute("title",this.instructions.closeDialog),c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder=this.instructions.input,c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),e.addEventListener("keypress",a=>{("Space"==a.key||"Enter"==a.key)&&this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.removeAttribute("inert"),a.pluginData.goToLine.dialog.setAttribute("tabindex",0),a.pluginData.goToLine.dialog.removeAttribute("aria-hidden"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};