autopair 1.2.2 → 1.2.3

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.
@@ -93,6 +93,7 @@ describe('autopair', () => {
93
93
  cy.get('textarea').then($el => {
94
94
  expect($el.val()).to.eq('();');
95
95
  expect($el[0].selectionStart).to.eq(1);
96
+ expect($el[0].selectionEnd).to.eq(1);
96
97
  });
97
98
  });
98
99
 
@@ -115,6 +116,7 @@ describe('autopair', () => {
115
116
  cy.get('textarea').then($el => {
116
117
  expect($el.val()).to.eq('(.');
117
118
  expect($el[0].selectionStart).to.eq(1);
119
+ expect($el[0].selectionEnd).to.eq(1);
118
120
  });
119
121
  });
120
122
 
@@ -128,6 +130,7 @@ describe('autopair', () => {
128
130
  cy.get('textarea').then($el => {
129
131
  expect($el.val()).to.eq('((()))');
130
132
  expect($el[0].selectionStart).to.eq(3); // cursor inside the innermost pair
133
+ expect($el[0].selectionEnd).to.eq(3);
131
134
  });
132
135
  });
133
136
 
@@ -141,6 +144,7 @@ describe('autopair', () => {
141
144
  cy.get('textarea').then($el => {
142
145
  expect($el.val()).to.eq("''");
143
146
  expect($el[0].selectionStart).to.eq(1); // cursor inside
147
+ expect($el[0].selectionEnd).to.eq(1);
144
148
  });
145
149
 
146
150
  // Move cursor to the end
@@ -156,37 +160,40 @@ describe('autopair', () => {
156
160
  cy.get('textarea').then($el => {
157
161
  expect($el.val()).to.eq("'''");
158
162
  expect($el[0].selectionStart).to.eq(3); // cursor after last '
163
+ expect($el[0].selectionEnd).to.eq(3);
159
164
  });
160
165
  });
161
166
 
162
- it('handles custom pairing correctly', () => {
167
+ it('autopairs existing custom quotes “ and ”', () => {
163
168
  cy.visit('/index.html');
164
169
 
165
- // Inject autopair immediately
166
- cy.document().then(doc => {
167
- const script = doc.createElement('script');
168
- script.type = 'module';
169
- script.textContent = `
170
- import autopair from './autopair.js';
171
- autopair(document.querySelector('textarea'), {
172
- '(': ')',
173
- '[': ']',
174
- '{': '}',
175
- "'": "'",
176
- '"': '"',
177
- '<': '>'
178
- });
179
- `;
180
- doc.body.appendChild(script);
181
- });
182
-
183
- // Type <
184
- cy.get('textarea').type('<');
185
-
186
- // Should autopair to <>
187
- cy.get('textarea').then($el => {
188
- expect($el.val()).to.eq('<>');
189
- expect($el[0].selectionStart).to.eq(1);
170
+ // Type opening custom quote
171
+ cy.get('textarea').type('“');
172
+
173
+ // Should autopair to “”
174
+ cy.get('textarea').then($el => {
175
+ expect($el.val()).to.eq('“”');
176
+ expect($el[0].selectionStart).to.eq(1); // cursor inside
177
+ expect($el[0].selectionEnd).to.eq(1);
178
+ });
179
+ });
180
+
181
+ it('supports undo and redo after typing a pair', () => {
182
+ cy.visit('/index.html');
183
+
184
+ // Type a pair
185
+ cy.get('textarea').type('()');
186
+
187
+ // Undo
188
+ cy.get('textarea').then($el => {
189
+ $el[0].ownerDocument.execCommand('undo');
190
+ });
191
+ cy.get('textarea').should('have.value', '');
192
+
193
+ // Redo
194
+ cy.get('textarea').then($el => {
195
+ $el[0].ownerDocument.execCommand('redo');
190
196
  });
197
+ cy.get('textarea').should('have.value', '()');
191
198
  });
192
199
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autopair",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Automatically closes parentheses and similar characters.",
5
5
  "main": "src/autopair.js",
6
6
  "author": "Dennis Hackethal <engineering@dennishackethal.com>",
package/src/autopair.js CHANGED
@@ -5,32 +5,38 @@ export default function autopair(textarea, pairs = {
5
5
  "'": "'",
6
6
  '"': '"'
7
7
  }) {
8
+ let openings = Object.keys(pairs);
9
+ let closings = Object.values(pairs);
10
+
11
+ let insertText = text => document.execCommand('insertText', false, text);
12
+ let setCursor = pos => {
13
+ textarea.selectionStart = textarea.selectionEnd = pos;
14
+ };
15
+
8
16
  let handler = evt => {
9
- const { selectionStart: start, selectionEnd: end, value } = textarea;
17
+ let { selectionStart: start, selectionEnd: end, value } = textarea;
10
18
 
11
19
  // Typethrough
12
- if (start === end) {
13
- const next = value[end];
14
- const isClosing = Object.values(pairs).includes(evt.key);
15
-
16
- if (isClosing && next === evt.key) {
17
- evt.preventDefault();
18
- textarea.selectionStart = textarea.selectionEnd = end + 1;
19
- return;
20
- }
20
+ if (start === end && closings.includes(evt.key) && value[end] === evt.key) {
21
+ evt.preventDefault();
22
+ setCursor(end + 1);
23
+ return;
21
24
  }
22
25
 
23
26
  // Handle backspace inside a direct pair
24
27
  if (evt.key === 'Backspace' && start === end && start > 0) {
25
- const left = value[start - 1];
26
- const right = value[start];
27
- const opening = Object.keys(pairs).find(k => pairs[k] === right);
28
+ let left = value[start - 1];
29
+ let right = value[start];
30
+ let opening = openings.find(k => pairs[k] === right);
31
+
28
32
  if (left === opening) {
29
33
  evt.preventDefault();
34
+
30
35
  // Select the pair and delete in one go
31
36
  textarea.selectionStart = start - 1;
32
37
  textarea.selectionEnd = start + 1;
33
- document.execCommand('insertText', false, '');
38
+
39
+ insertText('');
34
40
 
35
41
  return;
36
42
  }
@@ -38,7 +44,7 @@ export default function autopair(textarea, pairs = {
38
44
  return; // normal backspace
39
45
  }
40
46
 
41
- const closing = pairs[evt.key];
47
+ let closing = pairs[evt.key];
42
48
  if (!closing) return;
43
49
 
44
50
  // Wrap selection if present
@@ -46,18 +52,17 @@ export default function autopair(textarea, pairs = {
46
52
  evt.preventDefault();
47
53
  textarea.selectionStart = start;
48
54
  textarea.selectionEnd = end;
49
- document.execCommand('insertText', false, evt.key + value.slice(start, end) + closing);
50
- textarea.selectionStart = start + 1;
51
- textarea.selectionEnd = end + 1;
55
+ insertText(evt.key + value.slice(start, end) + closing);
56
+ setCursor(start + 1);
52
57
  return;
53
58
  }
54
59
 
55
- const nextCharWhitelist = /[\s;})\]]/;
56
- const nextChar = value[end] || '';
57
- const prevChar = value[start - 1] || '';
58
- const insidePair = closing === nextChar;
59
- const safeNext = nextChar === '' || nextCharWhitelist.test(nextChar);
60
- const isSymmetric = evt.key === closing;
60
+ let nextCharWhitelist = /[\s;})\]]/;
61
+ let nextChar = value[end] || '';
62
+ let prevChar = value[start - 1] || '';
63
+ let insidePair = closing === nextChar;
64
+ let safeNext = nextChar === '' || nextCharWhitelist.test(nextChar);
65
+ let isSymmetric = evt.key === closing;
61
66
 
62
67
  // Autoclose only when allowed
63
68
  if (!insidePair && (!safeNext || (isSymmetric && start === end && prevChar === evt.key))) {
@@ -65,9 +70,9 @@ export default function autopair(textarea, pairs = {
65
70
  }
66
71
 
67
72
  evt.preventDefault();
68
- textarea.selectionStart = textarea.selectionEnd = start;
69
- document.execCommand('insertText', false, evt.key + closing);
70
- textarea.selectionStart = textarea.selectionEnd = start + 1;
73
+
74
+ insertText(evt.key + closing);
75
+ setCursor(start + 1);
71
76
  };
72
77
 
73
78
  textarea.addEventListener('keydown', handler);