@b9g/revise 0.1.3 → 0.1.4

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/contentarea.js DELETED
@@ -1,708 +0,0 @@
1
- /// <reference types="contentarea.d.ts" />
2
- import { Edit } from './edit.js';
3
-
4
- /// <reference lib="dom" />
5
- /***************************************************/
6
- /*** ContentAreaElement private property symbols ***/
7
- /***************************************************/
8
- const _cache = Symbol.for("ContentArea._cache");
9
- const _observer = Symbol.for("ContentArea._observer");
10
- const _onselectionchange = Symbol.for("ContentArea._onselectionchange");
11
- const _value = Symbol.for("ContentArea._value");
12
- const _selectionRange = Symbol.for("ContentArea._selectionRange");
13
- const _staleValue = Symbol.for("ContentArea._staleValue");
14
- const _staleSelectionRange = Symbol.for("ContentArea._slateSelectionRange");
15
- const _compositionBuffer = Symbol.for("ContentArea._compositionBuffer");
16
- const _compositionStartValue = Symbol.for("ContentArea._compositionStartValue");
17
- const _compositionSelectionRange = Symbol.for("ContentArea._compositionSelectionRange");
18
- class ContentAreaElement extends HTMLElement {
19
- constructor() {
20
- super();
21
- this[_cache] = new Map();
22
- this[_observer] = new MutationObserver((records) => {
23
- if (this[_compositionBuffer]) {
24
- // Buffer mutations during composition but still process them to keep cache in sync
25
- this[_compositionBuffer].push(...records);
26
- }
27
- validate(this, records);
28
- });
29
- this[_onselectionchange] = () => {
30
- this[_selectionRange] = getSelectionRange(this);
31
- };
32
- this[_value] = "";
33
- this[_selectionRange] = { start: 0, end: 0, direction: "none" };
34
- this[_staleValue] = undefined;
35
- this[_staleSelectionRange] = undefined;
36
- this[_compositionBuffer] = undefined;
37
- this[_compositionStartValue] = undefined;
38
- this[_compositionSelectionRange] = undefined;
39
- }
40
- /******************************/
41
- /*** Custom Element methods ***/
42
- /******************************/
43
- connectedCallback() {
44
- this[_observer].observe(this, {
45
- subtree: true,
46
- childList: true,
47
- characterData: true,
48
- characterDataOldValue: true,
49
- attributes: true,
50
- attributeOldValue: true,
51
- attributeFilter: [
52
- "data-content",
53
- // TODO: implement these attributes
54
- //"data-contentbefore",
55
- //"data-contentafter",
56
- ],
57
- });
58
- document.addEventListener("selectionchange", this[_onselectionchange],
59
- // We use capture in an attempt to run before other event listeners.
60
- true);
61
- validate(this);
62
- this[_onselectionchange]();
63
- // Composition event handling
64
- let processCompositionTimeout;
65
- this.addEventListener("compositionstart", () => {
66
- clearTimeout(processCompositionTimeout); // Cancel pending commit
67
- if (processCompositionTimeout == null) {
68
- this[_compositionBuffer] = [];
69
- this[_compositionStartValue] = this[_value];
70
- this[_compositionSelectionRange] = { ...this[_selectionRange] };
71
- }
72
- processCompositionTimeout = undefined;
73
- });
74
- const processComposition = () => {
75
- if (this[_compositionBuffer] &&
76
- this[_compositionBuffer].length > 0 &&
77
- this[_compositionStartValue] !== undefined &&
78
- this[_compositionSelectionRange] !== undefined) {
79
- const edit = Edit.diff(this[_compositionStartValue], this[_value], this[_compositionSelectionRange].start);
80
- const ev = new ContentEvent("contentchange", {
81
- detail: { edit, source: null, mutations: this[_compositionBuffer] }
82
- });
83
- this.dispatchEvent(ev);
84
- this[_staleValue] = undefined;
85
- this[_staleSelectionRange] = undefined;
86
- }
87
- this[_compositionBuffer] = undefined;
88
- this[_compositionStartValue] = undefined;
89
- this[_compositionSelectionRange] = undefined;
90
- processCompositionTimeout = undefined;
91
- };
92
- this.addEventListener("compositionend", () => {
93
- clearTimeout(processCompositionTimeout);
94
- processCompositionTimeout = setTimeout(processComposition);
95
- });
96
- this.addEventListener("blur", () => {
97
- clearTimeout(processCompositionTimeout);
98
- processComposition();
99
- });
100
- this.addEventListener("keydown", (e) => {
101
- if (e.key === "Escape" && this[_compositionBuffer]) {
102
- clearTimeout(processCompositionTimeout);
103
- processComposition();
104
- }
105
- });
106
- }
107
- disconnectedCallback() {
108
- this[_cache].clear();
109
- this[_value] = "";
110
- this[_observer].disconnect();
111
- // JSDOM-based environments like Jest sometimes make the global document
112
- // null before calling the disconnectedCallback for some reason.
113
- if (document) {
114
- document.removeEventListener("selectionchange", this[_onselectionchange], true);
115
- }
116
- }
117
- get value() {
118
- validate(this);
119
- return this[_staleValue] == null ? this[_value] : this[_staleValue];
120
- }
121
- get selectionStart() {
122
- validate(this);
123
- const range = this[_staleSelectionRange] || this[_selectionRange];
124
- return range.start;
125
- }
126
- set selectionStart(start) {
127
- validate(this);
128
- const { end, direction } = getSelectionRange(this);
129
- setSelectionRange(this, { start, end, direction });
130
- }
131
- get selectionEnd() {
132
- validate(this);
133
- const range = this[_staleSelectionRange] || this[_selectionRange];
134
- return range.end;
135
- }
136
- set selectionEnd(end) {
137
- validate(this);
138
- const { start, direction } = getSelectionRange(this);
139
- setSelectionRange(this, { start, end, direction });
140
- }
141
- get selectionDirection() {
142
- validate(this);
143
- const range = this[_staleSelectionRange] || this[_selectionRange];
144
- return range.direction;
145
- }
146
- set selectionDirection(direction) {
147
- validate(this);
148
- const { start, end } = getSelectionRange(this);
149
- setSelectionRange(this, { start, end, direction });
150
- }
151
- getSelectionRange() {
152
- validate(this);
153
- const range = this[_staleSelectionRange] || this[_selectionRange];
154
- return { ...range };
155
- }
156
- setSelectionRange(start, end, direction = "none") {
157
- validate(this);
158
- setSelectionRange(this, { start, end, direction });
159
- }
160
- indexAt(node, offset) {
161
- validate(this);
162
- return indexAt(this, node, offset);
163
- }
164
- nodeOffsetAt(index) {
165
- validate(this);
166
- return nodeOffsetAt(this, index);
167
- }
168
- source(source) {
169
- return validate(this, this[_observer].takeRecords(), source);
170
- }
171
- }
172
- const PreventDefaultSource = Symbol.for("ContentArea.PreventDefaultSource");
173
- class ContentEvent extends CustomEvent {
174
- constructor(typeArg, eventInit) {
175
- // Maybe we should do some runtime eventInit validation.
176
- super(typeArg, { bubbles: true, ...eventInit });
177
- }
178
- preventDefault() {
179
- if (this.defaultPrevented) {
180
- return;
181
- }
182
- super.preventDefault();
183
- const area = this.target;
184
- area[_staleValue] = area[_value];
185
- area[_staleSelectionRange] = area[_selectionRange];
186
- const records = this.detail.mutations;
187
- for (let i = records.length - 1; i >= 0; i--) {
188
- const record = records[i];
189
- switch (record.type) {
190
- case 'childList': {
191
- for (let j = 0; j < record.addedNodes.length; j++) {
192
- const node = record.addedNodes[j];
193
- if (node.parentNode) {
194
- node.parentNode.removeChild(node);
195
- }
196
- }
197
- for (let j = 0; j < record.removedNodes.length; j++) {
198
- const node = record.removedNodes[j];
199
- record.target.insertBefore(node, record.nextSibling);
200
- }
201
- break;
202
- }
203
- case 'characterData': {
204
- if (record.oldValue !== null) {
205
- record.target.data = record.oldValue;
206
- }
207
- break;
208
- }
209
- case 'attributes': {
210
- if (record.oldValue === null) {
211
- record.target.removeAttribute(record.attributeName);
212
- }
213
- else {
214
- record.target.setAttribute(record.attributeName, record.oldValue);
215
- }
216
- break;
217
- }
218
- }
219
- }
220
- const records1 = (area)[_observer].takeRecords();
221
- validate(area, records1, PreventDefaultSource);
222
- }
223
- }
224
- /*** NodeInfo.flags ***/
225
- /** Whether the node is old. */
226
- const IS_OLD = 1 << 0;
227
- /** Whether the node’s info is still up-to-date. */
228
- const IS_VALID = 1 << 1;
229
- /** Whether the node has a styling of type display: block or similar. */
230
- const IS_BLOCKLIKE = 1 << 2;
231
- /** Whether the node is responsible for the newline before it. */
232
- const PREPENDS_NEWLINE = 1 << 3;
233
- /** Whether the node is responsible for the newline after it. */
234
- const APPENDS_NEWLINE = 1 << 4;
235
- /** Data associated with the child nodes of a ContentAreaElement. */
236
- class NodeInfo {
237
- constructor(offset) {
238
- this.f = 0;
239
- this.offset = offset;
240
- this.length = 0;
241
- }
242
- }
243
- /**
244
- * Should be called before calling any ContentAreaElement methods.
245
- *
246
- * This function ensures the cache is up to date.
247
- *
248
- * Dispatches "contentchange" events.
249
- *
250
- * @returns whether a change was detected
251
- */
252
- function validate(_this, records = _this[_observer].takeRecords(), source = null) {
253
- if (typeof _this !== "object" || _this[_cache] == null) {
254
- throw new TypeError("this is not a ContentAreaElement");
255
- }
256
- else if (!document.contains(_this)) {
257
- throw new Error("ContentArea cannot be read before it is inserted into the DOM");
258
- }
259
- if (!invalidate(_this, records)) {
260
- return false;
261
- }
262
- const oldValue = _this[_value];
263
- const edit = diff(_this, oldValue, _this[_selectionRange].start);
264
- _this[_value] = edit.apply(oldValue);
265
- _this[_selectionRange] = getSelectionRange(_this);
266
- // Don't dispatch events during composition or preventDefault operations
267
- if (source !== PreventDefaultSource && !_this[_compositionBuffer]) {
268
- const ev = new ContentEvent("contentchange", { detail: { edit, source, mutations: records } });
269
- _this.dispatchEvent(ev);
270
- _this[_staleValue] = undefined;
271
- _this[_staleSelectionRange] = undefined;
272
- }
273
- return true;
274
- }
275
- function invalidate(_this, records) {
276
- const cache = _this[_cache];
277
- if (!cache.get(_this)) {
278
- // The root ContentAreaElement will not be deleted from the cache until the
279
- // element is removed from the DOM, so this is the first time the
280
- // ContentAreaElement is being validated.
281
- return true;
282
- }
283
- let invalid = false;
284
- for (let i = 0; i < records.length; i++) {
285
- const record = records[i];
286
- // We make sure all added and removed nodes and their children are deleted
287
- // from the cache in case of any weirdness where nodes have been moved.
288
- for (let j = 0; j < record.addedNodes.length; j++) {
289
- const addedNode = record.addedNodes[j];
290
- clear(addedNode, cache);
291
- }
292
- for (let j = 0; j < record.removedNodes.length; j++) {
293
- clear(record.removedNodes[j], cache);
294
- }
295
- let node = record.target;
296
- if (node === _this) {
297
- invalid = true;
298
- continue;
299
- }
300
- else if (!_this.contains(node)) {
301
- clear(node, cache);
302
- continue;
303
- }
304
- for (; node !== _this; node = node.parentNode) {
305
- if (!cache.has(node)) {
306
- break;
307
- }
308
- const nodeInfo = cache.get(node);
309
- if (nodeInfo) {
310
- nodeInfo.f &= ~IS_VALID;
311
- }
312
- invalid = true;
313
- }
314
- }
315
- if (invalid) {
316
- const nodeInfo = cache.get(_this);
317
- nodeInfo.f &= ~IS_VALID;
318
- }
319
- return invalid;
320
- }
321
- /**
322
- * For a given parent node and node info cache, clear the info for the node and
323
- * all of its child nodes from the cache.
324
- */
325
- function clear(parent, cache) {
326
- const walker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
327
- for (let node = parent; node !== null; node = walker.nextNode()) {
328
- cache.delete(node);
329
- }
330
- }
331
- // TODO: custom newlines?
332
- const NEWLINE = "\n";
333
- // THIS IS THE MOST COMPLICATED FUNCTION IN THE LIBRARY!
334
- /**
335
- * This function both returns an edit which represents changes to the
336
- * ContentAreaElement, and populates the cache with info about nodes for future
337
- * reads.
338
- */
339
- function diff(_this, oldValue, oldSelectionStart) {
340
- const walker = document.createTreeWalker(_this, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
341
- const cache = _this[_cache];
342
- const stack = [];
343
- let nodeInfo;
344
- let value = "";
345
- for (let node = _this, descending = true,
346
- /** the current offset relative to the parent */
347
- offset = 0,
348
- /** the index into the old string */
349
- oldIndex = 0,
350
- /** the index into the old string of the parent */
351
- oldIndexRelative = 0,
352
- /** Whether or not the value being built currently ends with a newline */
353
- hasNewline = false;; node = walker.currentNode) {
354
- if (descending) {
355
- // PRE-ORDER LOGIC
356
- nodeInfo = cache.get(node);
357
- if (nodeInfo === undefined) {
358
- cache.set(node, (nodeInfo = new NodeInfo(offset)));
359
- if (isBlocklikeElement(node)) {
360
- nodeInfo.f |= IS_BLOCKLIKE;
361
- }
362
- }
363
- else {
364
- const expectedOffset = oldIndex - oldIndexRelative;
365
- const deleteLength = nodeInfo.offset - expectedOffset;
366
- if (deleteLength < 0) {
367
- // this should never happen
368
- throw new Error("cache offset error");
369
- }
370
- else if (deleteLength > 0) {
371
- // deletion detected
372
- oldIndex += deleteLength;
373
- }
374
- nodeInfo.offset = offset;
375
- }
376
- if (offset && !hasNewline && nodeInfo.f & IS_BLOCKLIKE) {
377
- // Block-like elements prepend a newline when they appear after text or
378
- // inline elements.
379
- hasNewline = true;
380
- offset += NEWLINE.length;
381
- value += NEWLINE;
382
- if (nodeInfo.f & PREPENDS_NEWLINE) {
383
- oldIndex += NEWLINE.length;
384
- }
385
- nodeInfo.f |= PREPENDS_NEWLINE;
386
- }
387
- else {
388
- if (nodeInfo.f & PREPENDS_NEWLINE) {
389
- // deletion detected
390
- oldIndex += NEWLINE.length;
391
- }
392
- nodeInfo.f &= ~PREPENDS_NEWLINE;
393
- }
394
- descending = false;
395
- if (nodeInfo.f & IS_VALID) {
396
- // The node and its children are unchanged, so we read from the length.
397
- if (nodeInfo.length) {
398
- value += oldValue.slice(oldIndex, oldIndex + nodeInfo.length);
399
- oldIndex += nodeInfo.length;
400
- offset += nodeInfo.length;
401
- hasNewline =
402
- oldValue.slice(Math.max(0, oldIndex - NEWLINE.length), oldIndex) ===
403
- NEWLINE;
404
- }
405
- }
406
- else if (node.nodeType === Node.TEXT_NODE) {
407
- const text = node.data;
408
- if (text.length) {
409
- value += text;
410
- offset += text.length;
411
- hasNewline = text.endsWith(NEWLINE);
412
- }
413
- if (nodeInfo.f & IS_OLD) {
414
- oldIndex += nodeInfo.length;
415
- }
416
- }
417
- else if (node.hasAttribute("data-content")) {
418
- const text = node.getAttribute("data-content") || "";
419
- if (text.length) {
420
- value += text;
421
- offset += text.length;
422
- hasNewline = text.endsWith(NEWLINE);
423
- }
424
- if (nodeInfo.f & IS_OLD) {
425
- oldIndex += nodeInfo.length;
426
- }
427
- }
428
- else if (node.nodeName === "BR") {
429
- value += NEWLINE;
430
- offset += NEWLINE.length;
431
- hasNewline = true;
432
- if (nodeInfo.f & IS_OLD) {
433
- oldIndex += nodeInfo.length;
434
- }
435
- }
436
- else {
437
- descending = !!walker.firstChild();
438
- if (descending) {
439
- stack.push({ nodeInfo, oldIndexRelative });
440
- offset = 0;
441
- oldIndexRelative = oldIndex;
442
- }
443
- }
444
- }
445
- else {
446
- if (!stack.length) {
447
- // This should never happen.
448
- throw new Error("Stack is empty");
449
- }
450
- // If the child node prepends a newline, add to offset to increase the
451
- // length of the parent node.
452
- if (nodeInfo.f & PREPENDS_NEWLINE) {
453
- offset += NEWLINE.length;
454
- }
455
- ({ nodeInfo, oldIndexRelative } = stack.pop());
456
- offset = nodeInfo.offset + offset;
457
- }
458
- if (!descending) {
459
- // POST-ORDER LOGIC
460
- if (!(nodeInfo.f & IS_VALID)) {
461
- // TODO: Figure out if we should always recalculate APPENDS_NEWLINE???
462
- if (!hasNewline && nodeInfo.f & IS_BLOCKLIKE) {
463
- value += NEWLINE;
464
- offset += NEWLINE.length;
465
- hasNewline = true;
466
- nodeInfo.f |= APPENDS_NEWLINE;
467
- }
468
- else {
469
- nodeInfo.f &= ~APPENDS_NEWLINE;
470
- }
471
- nodeInfo.length = offset - nodeInfo.offset;
472
- nodeInfo.f |= IS_VALID;
473
- }
474
- nodeInfo.f |= IS_OLD;
475
- descending = !!walker.nextSibling();
476
- if (!descending) {
477
- if (walker.currentNode === _this) {
478
- break;
479
- }
480
- walker.parentNode();
481
- }
482
- }
483
- if (oldIndex > oldValue.length) {
484
- // This should never happen.
485
- throw new Error("cache length error");
486
- }
487
- }
488
- const selectionStart = getSelectionRange(_this).start;
489
- // TODO: Doing a diff over the entirety of both oldValue and value is a
490
- // performance bottleneck. Figure out how to reduce the search for changed
491
- // values.
492
- return Edit.diff(oldValue, value, Math.min(oldSelectionStart, selectionStart));
493
- }
494
- const BLOCKLIKE_DISPLAYS = new Set([
495
- "block",
496
- "flex",
497
- "grid",
498
- "flow-root",
499
- "list-item",
500
- "table",
501
- "table-row-group",
502
- "table-header-group",
503
- "table-footer-group",
504
- "table-row",
505
- "table-caption",
506
- ]);
507
- function isBlocklikeElement(node) {
508
- return (node.nodeType === Node.ELEMENT_NODE &&
509
- BLOCKLIKE_DISPLAYS.has(
510
- // handle two-value display syntax like `display: block flex`
511
- getComputedStyle(node).display.split(" ")[0]));
512
- }
513
- /***********************/
514
- /*** Selection Logic ***/
515
- /***********************/
516
- /**
517
- * Finds the string index of a node and offset pair provided by a browser API
518
- * like document.getSelection() for a given root and cache.
519
- */
520
- function indexAt(_this, node, offset) {
521
- const cache = _this[_cache];
522
- if (node == null || !_this.contains(node)) {
523
- return -1;
524
- }
525
- if (!cache.has(node)) {
526
- // If the node is not found in the cache but is contained in the root, then
527
- // it is the child of an element with a data-content attribute.
528
- offset = 0;
529
- while (!cache.has(node)) {
530
- node = node.parentNode;
531
- }
532
- }
533
- let index;
534
- if (node.nodeType === Node.TEXT_NODE) {
535
- const nodeInfo = cache.get(node);
536
- index = offset + nodeInfo.offset;
537
- node = node.parentNode;
538
- }
539
- else {
540
- if (offset <= 0) {
541
- index = 0;
542
- }
543
- else if (offset >= node.childNodes.length) {
544
- const nodeInfo = cache.get(node);
545
- index =
546
- nodeInfo.f & APPENDS_NEWLINE
547
- ? nodeInfo.length - NEWLINE.length
548
- : nodeInfo.length;
549
- }
550
- else {
551
- let child = node.childNodes[offset];
552
- while (child !== null && !cache.has(child)) {
553
- child = child.previousSibling;
554
- }
555
- if (child === null) {
556
- index = 0;
557
- }
558
- else {
559
- node = child;
560
- const nodeInfo = cache.get(node);
561
- // If the offset references an element which prepends a newline
562
- // ("hello<div>world</div>"), we have to start from -1 because the
563
- // element’s info.offset will not account for the newline.
564
- index = nodeInfo.f & PREPENDS_NEWLINE ? -1 : 0;
565
- }
566
- }
567
- }
568
- for (; node !== _this; node = node.parentNode) {
569
- const nodeInfo = cache.get(node);
570
- index += nodeInfo.offset;
571
- if (nodeInfo.f & PREPENDS_NEWLINE) {
572
- index += NEWLINE.length;
573
- }
574
- }
575
- return index;
576
- }
577
- /**
578
- * Finds the node and offset pair to use with browser APIs like
579
- * selection.collapse() from a given string index.
580
- */
581
- function nodeOffsetAt(_this, index) {
582
- if (index < 0) {
583
- return [null, 0];
584
- }
585
- const [node, offset] = findNodeOffset(_this, index);
586
- if (node && node.nodeName === "BR") {
587
- // Some browsers seem to have trouble when calling `selection.collapse()`
588
- // with a BR element, so we try to avoid returning them from this function.
589
- return nodeOffsetFromChild(node);
590
- }
591
- return [node, offset];
592
- }
593
- // TODO: Can this function be inlined?
594
- function findNodeOffset(_this, index) {
595
- const cache = _this[_cache];
596
- const walker = document.createTreeWalker(_this, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
597
- for (let node = _this; node !== null;) {
598
- const nodeInfo = cache.get(node);
599
- if (nodeInfo == null) {
600
- return nodeOffsetFromChild(node, index > 0);
601
- }
602
- if (nodeInfo.f & PREPENDS_NEWLINE) {
603
- index -= 1;
604
- }
605
- if (index === nodeInfo.length && node.nodeType === Node.TEXT_NODE) {
606
- return [node, node.data.length];
607
- }
608
- else if (index >= nodeInfo.length) {
609
- index -= nodeInfo.length;
610
- const nextSibling = walker.nextSibling();
611
- if (nextSibling === null) {
612
- // This branch seems necessary mainly when working with data-content
613
- // nodes.
614
- if (node === _this) {
615
- return [node, getNodeLength(node)];
616
- }
617
- return nodeOffsetFromChild(walker.currentNode, true);
618
- }
619
- node = nextSibling;
620
- }
621
- else {
622
- if (node.nodeType === Node.ELEMENT_NODE &&
623
- node.hasAttribute("data-content")) {
624
- return nodeOffsetFromChild(node, index > 0);
625
- }
626
- const firstChild = walker.firstChild();
627
- if (firstChild === null) {
628
- const offset = node.nodeType === Node.TEXT_NODE ? index : index > 0 ? 1 : 0;
629
- return [node, offset];
630
- }
631
- else {
632
- node = firstChild;
633
- }
634
- }
635
- }
636
- const node = walker.currentNode;
637
- return [node, getNodeLength(node)];
638
- }
639
- function getNodeLength(node) {
640
- if (node.nodeType === Node.TEXT_NODE) {
641
- return node.data.length;
642
- }
643
- return node.childNodes.length;
644
- }
645
- function nodeOffsetFromChild(node, after = false) {
646
- const parentNode = node.parentNode;
647
- if (parentNode === null) {
648
- return [null, 0];
649
- }
650
- let offset = Array.from(parentNode.childNodes).indexOf(node);
651
- if (after) {
652
- offset++;
653
- }
654
- return [parentNode, offset];
655
- }
656
- function getSelectionRange(_this) {
657
- const selection = document.getSelection();
658
- if (!selection) {
659
- return { start: 0, end: 0, direction: "none" };
660
- }
661
- const { focusNode, focusOffset, anchorNode, anchorOffset, isCollapsed, } = selection;
662
- const focus = Math.max(0, indexAt(_this, focusNode, focusOffset));
663
- const anchor = isCollapsed
664
- ? focus
665
- : Math.max(0, indexAt(_this, anchorNode, anchorOffset));
666
- return {
667
- start: Math.min(focus, anchor),
668
- end: Math.max(focus, anchor),
669
- direction: focus < anchor ? "backward" : focus > anchor ? "forward" : "none",
670
- };
671
- }
672
- function setSelectionRange(_this, { start, end, direction }) {
673
- const selection = document.getSelection();
674
- if (!selection) {
675
- return;
676
- }
677
- start = Math.max(0, start || 0);
678
- end = Math.max(0, end || 0);
679
- if (end < start) {
680
- start = end;
681
- }
682
- // Focus is the side of the selection where the pointer is released.
683
- const [focus, anchor] = direction === "backward" ? [start, end] : [end, start];
684
- if (focus === anchor) {
685
- const [node, offset] = nodeOffsetAt(_this, focus);
686
- selection.collapse(node, offset);
687
- }
688
- else {
689
- const [anchorNode, anchorOffset] = nodeOffsetAt(_this, anchor);
690
- const [focusNode, focusOffset] = nodeOffsetAt(_this, focus);
691
- if (anchorNode === null && focusNode === null) {
692
- selection.collapse(null);
693
- }
694
- else if (anchorNode === null) {
695
- selection.collapse(focusNode, focusOffset);
696
- }
697
- else if (focusNode === null) {
698
- selection.collapse(anchorNode, anchorOffset);
699
- }
700
- else {
701
- // NOTE: This method is not implemented in IE.
702
- selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
703
- }
704
- }
705
- }
706
-
707
- export { ContentAreaElement, ContentEvent };
708
- //# sourceMappingURL=contentarea.js.map