@livingdocs/editable.js 5.0.0 → 5.0.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@livingdocs/editable.js",
3
3
  "description": "Friendly contenteditable API",
4
- "version": "5.0.0",
4
+ "version": "5.0.1",
5
5
  "dependencies": {
6
6
  "@babel/runtime": "^7.16.7",
7
7
  "lodash-es": "^4.17.21"
@@ -34,7 +34,7 @@
34
34
  "react-dom": "^18.0.0",
35
35
  "react-transition-group": "^4.4.2",
36
36
  "rimraf": "^5.0.0",
37
- "sinon": "^16.0.0",
37
+ "sinon": "^17.0.0",
38
38
  "style-loader": "^3.3.1",
39
39
  "url-loader": "^4.1.1",
40
40
  "webpack": "^5.68.0",
@@ -80,11 +80,11 @@
80
80
  "isCi": true,
81
81
  "name": "Drone",
82
82
  "service": "drone",
83
- "commit": "6be0ace70ff4180dfe612c8026ecfb9a88a92651",
84
- "build": "1333",
83
+ "commit": "a2b34bb63d4d98116766f241ac62ea8d5c6fdfcb",
84
+ "build": "1376",
85
85
  "branch": "master",
86
86
  "isPr": false,
87
87
  "slug": "livingdocsIO/editable.js",
88
- "date": "2023-10-20T10:13:40.826Z"
88
+ "date": "2023-12-07T13:01:13.070Z"
89
89
  }
90
90
  }
package/src/selection.js CHANGED
@@ -5,7 +5,12 @@ import * as block from './block'
5
5
  import config from './config'
6
6
  import highlightSupport from './highlight-support'
7
7
  import highlightText from './highlight-text'
8
- import {toCharacterRange, rangeToHtml} from './util/dom'
8
+ import {
9
+ toCharacterRange,
10
+ rangeToHtml,
11
+ findStartExcludingWhitespace,
12
+ findEndExcludingWhitespace
13
+ } from './util/dom'
9
14
 
10
15
  /**
11
16
  * The Selection module provides a cross-browser abstraction layer for range
@@ -96,9 +101,25 @@ export default class Selection extends Cursor {
96
101
  const textToTrim = this.range.toString()
97
102
  const whitespacesOnTheLeft = textToTrim.search(/\S|$/)
98
103
  const lastNonWhitespace = textToTrim.search(/\S[\s]+$/)
99
- const whitespacesOnTheRight = lastNonWhitespace === -1 ? 0 : textToTrim.length - (lastNonWhitespace + 1)
100
- this.range.setStart(this.range.startContainer, this.range.startOffset + whitespacesOnTheLeft)
101
- this.range.setEnd(this.range.endContainer, this.range.endOffset - whitespacesOnTheRight)
104
+ const whitespacesOnTheRight = lastNonWhitespace === -1
105
+ ? 0
106
+ : textToTrim.length - (lastNonWhitespace + 1)
107
+
108
+ const [startContainer, startOffset] = findStartExcludingWhitespace({
109
+ root: this.range.commonAncestorContainer,
110
+ startContainer: this.range.startContainer,
111
+ startOffset: this.range.startOffset,
112
+ whitespacesOnTheLeft
113
+ })
114
+ this.range.setStart(startContainer, startOffset)
115
+
116
+ const [endContainer, endOffset] = findEndExcludingWhitespace({
117
+ root: this.range.commonAncestorContainer,
118
+ endContainer: this.range.endContainer,
119
+ endOffset: this.range.endOffset,
120
+ whitespacesOnTheRight
121
+ })
122
+ this.range.setEnd(endContainer, endOffset)
102
123
  }
103
124
 
104
125
  unlink () {
@@ -156,6 +177,7 @@ export default class Selection extends Cursor {
156
177
  // e.g. toggle('<em>')
157
178
  toggle (elem) {
158
179
  if (block.isPlainTextBlock(this.host)) return
180
+ if (this.range.collapsed) return
159
181
  elem = this.adoptElement(elem)
160
182
  this.range = content.toggleTag(this.host, this.range, elem)
161
183
  this.setVisibleSelection()
@@ -320,6 +342,7 @@ export default class Selection extends Cursor {
320
342
  // remove first.
321
343
  forceWrap (elem) {
322
344
  if (block.isPlainTextBlock(this.host)) return
345
+ if (this.range.collapsed) return
323
346
  elem = this.adoptElement(elem)
324
347
  this.range = content.forceWrap(this.host, this.range, elem)
325
348
  this.setVisibleSelection()
package/src/util/dom.js CHANGED
@@ -1,3 +1,6 @@
1
+ import NodeIterator from '../node-iterator'
2
+ import {textNode} from '../node-type'
3
+
1
4
  /**
2
5
  * @param {HTMLElement | Array | String} target
3
6
  * @param {Document} document
@@ -228,8 +231,8 @@ export const createRangeFromCharacterRange = (element, actualStartIndex, actualE
228
231
  let startNode, endNode, startOffset, endOffset
229
232
 
230
233
  while (walker.nextNode()) {
231
- const textNode = walker.currentNode
232
- const nodeLength = textNode.nodeValue.length
234
+ const node = walker.currentNode
235
+ const nodeLength = node.nodeValue.length
233
236
 
234
237
  if (currentIndex + nodeLength <= actualStartIndex) {
235
238
  currentIndex += nodeLength
@@ -237,12 +240,12 @@ export const createRangeFromCharacterRange = (element, actualStartIndex, actualE
237
240
  }
238
241
 
239
242
  if (!startNode) {
240
- startNode = textNode
243
+ startNode = node
241
244
  startOffset = actualStartIndex - currentIndex
242
245
  }
243
246
 
244
247
  if (currentIndex + nodeLength >= actualEndIndex) {
245
- endNode = textNode
248
+ endNode = node
246
249
  endOffset = actualEndIndex - currentIndex
247
250
  break
248
251
  }
@@ -260,3 +263,87 @@ export const createRangeFromCharacterRange = (element, actualStartIndex, actualE
260
263
  }
261
264
  }
262
265
 
266
+ export function findStartExcludingWhitespace ({root, startContainer, startOffset, whitespacesOnTheLeft}) {
267
+ const isTextNode = startContainer.nodeType === textNode
268
+ if (!isTextNode) {
269
+ return findStartExcludingWhitespace({
270
+ root,
271
+ startContainer: startContainer.childNodes[startOffset],
272
+ startOffset: 0,
273
+ whitespacesOnTheLeft
274
+ })
275
+ }
276
+
277
+ const offsetAfterWhitespace = startOffset + whitespacesOnTheLeft
278
+ if (startContainer.length > offsetAfterWhitespace) {
279
+ return [startContainer, offsetAfterWhitespace]
280
+ }
281
+
282
+ // Pass the root so that the iterator can traverse to siblings
283
+ const iterator = new NodeIterator(root)
284
+ // Set the position to the node which is selected
285
+ iterator.nextNode = startContainer
286
+ // Iterate once to avoid returning self
287
+ iterator.getNextTextNode()
288
+
289
+ const container = iterator.getNextTextNode()
290
+ if (!container) {
291
+ // No more text nodes - use the end of the last text node
292
+ const previousTextNode = iterator.getPreviousTextNode()
293
+ return [previousTextNode, previousTextNode.length]
294
+ }
295
+
296
+ return findStartExcludingWhitespace({
297
+ root,
298
+ startContainer: container,
299
+ startOffset: 0,
300
+ whitespacesOnTheLeft: offsetAfterWhitespace - startContainer.length
301
+ })
302
+ }
303
+
304
+ export function findEndExcludingWhitespace ({root, endContainer, endOffset, whitespacesOnTheRight}) {
305
+ const isTextNode = endContainer.nodeType === textNode
306
+ if (!isTextNode) {
307
+ const isFirstNode = !endContainer.childNodes[endOffset - 1]
308
+ const container = isFirstNode
309
+ ? endContainer.childNodes[endOffset]
310
+ : endContainer.childNodes[endOffset - 1]
311
+ let offset = 0
312
+ if (!isFirstNode) {
313
+ offset = container.nodeType === textNode
314
+ ? container.length
315
+ : container.childNodes.length
316
+ }
317
+ return findEndExcludingWhitespace({
318
+ root,
319
+ endContainer: container,
320
+ endOffset: offset,
321
+ whitespacesOnTheRight
322
+ })
323
+ }
324
+
325
+ const offsetBeforeWhitespace = endOffset - whitespacesOnTheRight
326
+ if (offsetBeforeWhitespace > 0) {
327
+ return [endContainer, offsetBeforeWhitespace]
328
+ }
329
+
330
+ // Pass the root so that the iterator can traverse to siblings
331
+ const iterator = new NodeIterator(root)
332
+ // Set the position to the node which is selected
333
+ iterator.previous = endContainer
334
+ // Iterate once to avoid returning self
335
+ iterator.getPreviousTextNode()
336
+
337
+ const container = iterator.getPreviousTextNode()
338
+ if (!container) {
339
+ // No more text nodes - use the start of the first text node
340
+ return [iterator.getNextTextNode(), 0]
341
+ }
342
+
343
+ return findEndExcludingWhitespace({
344
+ root,
345
+ endContainer: container,
346
+ endOffset: container.length,
347
+ whitespacesOnTheRight: whitespacesOnTheRight - endOffset
348
+ })
349
+ }