@textbus/platform-browser 4.0.0-alpha.8 → 4.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.
@@ -1,7 +1,8 @@
1
1
  import 'reflect-metadata';
2
- import { Slot, Textbus, ViewAdapter, createBidirectionalMapping, ComponentInstance, VElement, VTextNode, Controller, Selection, RootComponentRef, ContentType, Event, invokeListener, Keyboard, Commander, Scheduler, NativeSelectionBridge, FocusManager } from '@textbus/core';
2
+ import { Slot, Textbus, Adapter, Controller, Selection, RootComponentRef, ContentType, Event, invokeListener, Keyboard, Commander, Scheduler, makeError, NativeSelectionBridge, FocusManager, Component, Registry } from '@textbus/core';
3
3
  import { Subject, filter, fromEvent, Subscription, distinctUntilChanged, merge, map, Observable } from '@tanbo/stream';
4
4
  import { InjectionToken, Injectable, Inject, Optional } from '@viewfly/core';
5
+ import { UserActivity } from '@textbus/collaborate';
5
6
 
6
7
  function createElement(tagName, options = {}) {
7
8
  const el = document.createElement(tagName);
@@ -33,9 +34,6 @@ function createElement(tagName, options = {}) {
33
34
  }
34
35
  return el;
35
36
  }
36
- function createTextNode(content) {
37
- return document.createTextNode(content);
38
- }
39
37
  function getLayoutRectByRange(range) {
40
38
  let { startContainer, startOffset } = range;
41
39
  if (startContainer.nodeType === Node.TEXT_NODE) {
@@ -53,7 +51,7 @@ function getLayoutRectByRange(range) {
53
51
  return {
54
52
  left: rect.right,
55
53
  top: rect.top,
56
- width: rect.width,
54
+ width: range.collapsed ? 0 : rect.width,
57
55
  height: rect.height
58
56
  };
59
57
  }
@@ -61,7 +59,13 @@ function getLayoutRectByRange(range) {
61
59
  const range2 = document.createRange();
62
60
  range2.setStart(beforeNode, beforeNode.textContent.length);
63
61
  range2.setEnd(beforeNode, beforeNode.textContent.length);
64
- return range2.getBoundingClientRect();
62
+ const rect = range2.getBoundingClientRect();
63
+ return {
64
+ left: rect.right,
65
+ top: rect.top,
66
+ width: range.collapsed ? 0 : rect.width,
67
+ height: rect.height
68
+ };
65
69
  }
66
70
  }
67
71
  const offsetNode = startContainer.childNodes[startOffset];
@@ -73,14 +77,20 @@ function getLayoutRectByRange(range) {
73
77
  return {
74
78
  left: rect.right,
75
79
  top: rect.top,
76
- width: rect.width,
80
+ width: range.collapsed ? 0 : rect.width,
77
81
  height: rect.height
78
82
  };
79
83
  }
80
84
  }
81
85
  if (offsetNode) {
82
86
  if (offsetNode.nodeType === Node.ELEMENT_NODE && offsetNode.nodeName.toLowerCase() !== 'br') {
83
- return offsetNode.getBoundingClientRect();
87
+ const rect = offsetNode.getBoundingClientRect();
88
+ return {
89
+ left: rect.left,
90
+ top: rect.top,
91
+ width: range.collapsed ? 0 : rect.width,
92
+ height: rect.height
93
+ };
84
94
  }
85
95
  isInsertBefore = true;
86
96
  }
@@ -95,7 +105,12 @@ function getLayoutRectByRange(range) {
95
105
  }
96
106
  const rect = span.getBoundingClientRect();
97
107
  startContainer.removeChild(span);
98
- return rect;
108
+ return {
109
+ left: rect.left,
110
+ top: rect.top,
111
+ width: range.collapsed ? 0 : rect.width,
112
+ height: rect.height
113
+ };
99
114
  }
100
115
 
101
116
  const isWindows = () => /win(dows|32|64)/i.test(navigator.userAgent);
@@ -167,7 +182,6 @@ let Parser = Parser_1 = class Parser {
167
182
  return new DOMParser().parseFromString(html, 'text/html').body;
168
183
  }
169
184
  constructor(options, textbus) {
170
- this.options = options;
171
185
  this.textbus = textbus;
172
186
  const componentLoaders = [
173
187
  ...(options.componentLoaders || [])
@@ -212,8 +226,9 @@ let Parser = Parser_1 = class Parser {
212
226
  slot.insert('\n');
213
227
  return;
214
228
  }
229
+ const schema = [...slot.schema];
215
230
  for (const t of this.componentLoaders) {
216
- if (t.match(el)) {
231
+ if (t.match(el, schema)) {
217
232
  const result = t.read(el, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
218
233
  return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
219
234
  });
@@ -300,13 +315,10 @@ Parser = Parser_1 = __decorate([
300
315
  class Input {
301
316
  }
302
317
 
303
- /**
304
- * Textbus PC 端浏览器渲染能力桥接器抽象类,提供了 DOM 元素查询能力,具体渲染能力由各前端框架实现相应桥接
305
- */
306
- class DomAdapter extends ViewAdapter {
307
- constructor(mount) {
308
- super();
309
- this.mount = mount;
318
+ class DomAdapter extends Adapter {
319
+ constructor() {
320
+ super(...arguments);
321
+ this.onViewUpdated = new Subject();
310
322
  this.host = createElement('div', {
311
323
  styles: {
312
324
  cursor: 'text',
@@ -322,126 +334,6 @@ class DomAdapter extends ViewAdapter {
322
334
  id: 'textbus-' + Number((Math.random() + '').substring(2)).toString(16)
323
335
  }
324
336
  });
325
- this.componentRootElementCaches = createBidirectionalMapping(a => {
326
- return a instanceof ComponentInstance;
327
- });
328
- this.slotRootNativeElementCaches = createBidirectionalMapping(a => {
329
- return a instanceof Slot;
330
- });
331
- this.slotRootVElementCaches = new WeakMap();
332
- }
333
- render(rootComponent) {
334
- const view = this.componentRender(rootComponent);
335
- return this.mount(this.host, view);
336
- }
337
- copy() {
338
- document.execCommand('copy');
339
- }
340
- /**
341
- * 根据组件获取组件的根 DOM 节点
342
- * @param component
343
- */
344
- getNativeNodeByComponent(component) {
345
- return this.componentRootElementCaches.get(component) || null;
346
- }
347
- /**
348
- * 根据 DOM 节点,获对对应的组件根节点,如传入的 DOM 节点不为组件的根节点,则返回 null
349
- * @param node
350
- */
351
- getComponentByNativeNode(node) {
352
- return this.componentRootElementCaches.get(node) || null;
353
- }
354
- /**
355
- * 根据插槽获取插槽的根 DOM 节点
356
- * @param slot
357
- */
358
- getNativeNodeBySlot(slot) {
359
- return this.slotRootNativeElementCaches.get(slot) || null;
360
- }
361
- /**
362
- * 根据 DOM 节点,获对对应的插槽根节点,如传入的 DOM 节点不为插槽的根节点,则返回 null
363
- * @param node
364
- */
365
- getSlotByNativeNode(node) {
366
- return this.slotRootNativeElementCaches.get(node) || null;
367
- }
368
- /**
369
- * 获取插槽内容节点集合
370
- * @param slot
371
- */
372
- getNodesBySlot(slot) {
373
- const rootNativeNode = this.getNativeNodeBySlot(slot);
374
- if (!rootNativeNode) {
375
- return [];
376
- }
377
- const rootVNode = this.slotRootVElementCaches.get(slot);
378
- const getNodes = (vElement, nativeNode, result) => {
379
- if (vElement.location) {
380
- result.push(nativeNode);
381
- }
382
- for (let i = 0; i < vElement.children.length; i++) {
383
- const vChild = vElement.children[i];
384
- const nativeChild = nativeNode.childNodes[i];
385
- if (vChild instanceof VElement) {
386
- getNodes(vChild, nativeChild, result);
387
- }
388
- else if (vChild instanceof VTextNode) {
389
- result.push(nativeChild);
390
- }
391
- else {
392
- result.push(this.getNativeNodeByComponent(vChild));
393
- }
394
- }
395
- return result;
396
- };
397
- return getNodes(rootVNode, rootNativeNode, []);
398
- }
399
- /**
400
- * 获取原生节点的原始数据在文档中的位置
401
- * @param node
402
- */
403
- getLocationByNativeNode(node) {
404
- let slotRootNode = node;
405
- while (!this.slotRootNativeElementCaches.get(slotRootNode)) {
406
- slotRootNode = slotRootNode.parentNode;
407
- if (!slotRootNode) {
408
- return null;
409
- }
410
- }
411
- const slot = this.slotRootNativeElementCaches.get(slotRootNode);
412
- const rootVNode = this.slotRootVElementCaches.get(slot);
413
- const getLocation = (target, tree, vNodeTree) => {
414
- if (target === tree) {
415
- return Object.assign({}, vNodeTree.location);
416
- }
417
- const childNodes = tree.childNodes;
418
- for (let i = 0; i < childNodes.length; i++) {
419
- const child = vNodeTree.children[i];
420
- const nativeChild = tree.childNodes[i];
421
- if (nativeChild === target) {
422
- if (child instanceof ComponentInstance) {
423
- const index = child.parent.indexOf(child);
424
- return {
425
- slot: child.parent,
426
- startIndex: index,
427
- endIndex: index + 1
428
- };
429
- }
430
- return child.location;
431
- }
432
- else if (child instanceof VElement) {
433
- let r = null;
434
- if (nativeChild.nodeType === Node.ELEMENT_NODE) {
435
- r = getLocation(target, nativeChild, child);
436
- }
437
- if (r) {
438
- return r;
439
- }
440
- }
441
- }
442
- return null;
443
- };
444
- return getLocation(node, slotRootNode, rootVNode);
445
337
  }
446
338
  }
447
339
 
@@ -540,7 +432,33 @@ let SelectionBridge = class SelectionBridge {
540
432
  this.listen(this.connector);
541
433
  return;
542
434
  }
543
- this.nativeSelection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
435
+ function tryOffset(position) {
436
+ if (!position.node) {
437
+ return;
438
+ }
439
+ if (position.node.nodeType === Node.TEXT_NODE) {
440
+ const len = position.node.textContent.length;
441
+ if (position.offset > len) {
442
+ position.offset = len;
443
+ }
444
+ }
445
+ else if (position.node.nodeType === Node.ELEMENT_NODE) {
446
+ const len = position.node.childNodes.length;
447
+ if (position.offset > len) {
448
+ position.offset = len;
449
+ }
450
+ }
451
+ }
452
+ try {
453
+ tryOffset(focus);
454
+ tryOffset(anchor);
455
+ this.nativeSelection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
456
+ }
457
+ catch (e) {
458
+ setTimeout(() => {
459
+ throw e;
460
+ });
461
+ }
544
462
  if (this.nativeSelection.rangeCount) {
545
463
  const nativeRange = this.nativeSelection.getRangeAt(0);
546
464
  this.selectionChangeEvent.next(nativeRange);
@@ -563,6 +481,7 @@ let SelectionBridge = class SelectionBridge {
563
481
  // hack end
564
482
  }
565
483
  destroy() {
484
+ this.subs.forEach(i => i.unsubscribe());
566
485
  this.sub.unsubscribe();
567
486
  }
568
487
  getPositionByRange(abstractSelection) {
@@ -645,9 +564,10 @@ let SelectionBridge = class SelectionBridge {
645
564
  }
646
565
  minLeft = rect2.left;
647
566
  minTop = rect2.top;
567
+ // oldPosition = position
648
568
  }
649
569
  if (isToPrevLine) {
650
- if (rect2.left < startLeft) {
570
+ if (rect2.left <= startLeft) {
651
571
  return position;
652
572
  }
653
573
  if (oldPosition) {
@@ -760,14 +680,14 @@ let SelectionBridge = class SelectionBridge {
760
680
  const isFocusStart = selection.focusNode === nativeRange.startContainer && selection.focusOffset === nativeRange.startOffset;
761
681
  if (!this.docContainer.contains(selection.focusNode)) {
762
682
  if (isFocusEnd) {
763
- const nativeNode = this.domAdapter.getNativeNodeBySlot(this.rootComponentRef.component.slots.first);
683
+ const nativeNode = this.domAdapter.getNativeNodeBySlot(this.rootComponentRef.component.slots.at(0));
764
684
  if (!nativeNode) {
765
685
  return;
766
686
  }
767
687
  nativeRange.setEndAfter(nativeNode.lastChild);
768
688
  }
769
689
  else {
770
- const nativeNode = this.domAdapter.getNativeNodeBySlot(this.rootComponentRef.component.slots.last);
690
+ const nativeNode = this.domAdapter.getNativeNodeBySlot(this.rootComponentRef.component.slots.at(-1));
771
691
  if (!nativeNode) {
772
692
  return;
773
693
  }
@@ -1028,25 +948,24 @@ class ExperimentalCaret {
1028
948
  get display() {
1029
949
  return this._display;
1030
950
  }
1031
- constructor(scheduler, editorMask) {
951
+ constructor(domRenderer, scheduler, editorMask) {
952
+ this.domRenderer = domRenderer;
1032
953
  this.scheduler = scheduler;
1033
954
  this.editorMask = editorMask;
1034
- this.compositionState = null;
1035
- this.compositionElement = createElement('span', {
1036
- styles: {
1037
- textDecoration: 'underline'
1038
- }
1039
- });
955
+ this.changeFromSelf = false;
956
+ this.getLimit = function () {
957
+ return {
958
+ top: 0,
959
+ bottom: document.documentElement.clientHeight
960
+ };
961
+ };
1040
962
  this.timer = null;
1041
- this.oldPosition = null;
1042
963
  this._display = true;
1043
964
  this.flashing = true;
1044
- this.subs = [];
1045
965
  this.subscription = new Subscription();
1046
966
  this.positionChangeEvent = new Subject();
1047
967
  this.styleChangeEvent = new Subject();
1048
968
  this.oldRange = null;
1049
- this.isFixed = false;
1050
969
  this.onPositionChange = this.positionChangeEvent.pipe(distinctUntilChanged());
1051
970
  this.onStyleChange = this.styleChangeEvent.asObservable();
1052
971
  this.elementRef = createElement('div', {
@@ -1074,20 +993,12 @@ class ExperimentalCaret {
1074
993
  }));
1075
994
  this.editorMask.appendChild(this.elementRef);
1076
995
  }
1077
- refresh(isFixedCaret = false) {
1078
- this.isFixed = isFixedCaret;
996
+ refresh() {
1079
997
  if (this.oldRange) {
1080
998
  this.show(this.oldRange, false);
1081
999
  }
1082
- this.isFixed = false;
1083
1000
  }
1084
1001
  show(range, restart) {
1085
- const oldRect = this.elementRef.getBoundingClientRect();
1086
- this.oldPosition = {
1087
- top: oldRect.top,
1088
- left: oldRect.left,
1089
- height: oldRect.height
1090
- };
1091
1002
  this.oldRange = range;
1092
1003
  if (restart || this.scheduler.lastChangesHasLocalUpdate) {
1093
1004
  clearTimeout(this.timer);
@@ -1116,62 +1027,8 @@ class ExperimentalCaret {
1116
1027
  }
1117
1028
  destroy() {
1118
1029
  clearTimeout(this.timer);
1030
+ // this.caret.
1119
1031
  this.subscription.unsubscribe();
1120
- this.subs.forEach(i => i.unsubscribe());
1121
- }
1122
- correctScrollTop(scroller) {
1123
- this.subs.forEach(i => i.unsubscribe());
1124
- this.subs = [];
1125
- const scheduler = this.scheduler;
1126
- let docIsChanged = true;
1127
- function limitPosition(position) {
1128
- const { top, bottom } = scroller.getLimit();
1129
- const caretTop = position.top;
1130
- if (caretTop + position.height > bottom) {
1131
- const offset = caretTop - bottom + position.height;
1132
- scroller.setOffset(offset);
1133
- }
1134
- else if (position.top < top) {
1135
- scroller.setOffset(-(top - position.top));
1136
- }
1137
- }
1138
- let isPressed = false;
1139
- this.subs.push(scroller.onScroll.subscribe(() => {
1140
- if (this.oldPosition) {
1141
- const rect = this.rect;
1142
- this.oldPosition.top = rect.top;
1143
- this.oldPosition.left = rect.left;
1144
- this.oldPosition.height = rect.height;
1145
- }
1146
- }), fromEvent(document, 'mousedown', true).subscribe(() => {
1147
- isPressed = true;
1148
- }), fromEvent(document, 'mouseup', true).subscribe(() => {
1149
- isPressed = false;
1150
- }), scheduler.onDocChange.subscribe(() => {
1151
- docIsChanged = true;
1152
- }), this.onPositionChange.subscribe(position => {
1153
- if (position) {
1154
- if (docIsChanged) {
1155
- if (scheduler.lastChangesHasLocalUpdate) {
1156
- limitPosition(position);
1157
- }
1158
- else if (this.oldPosition) {
1159
- const offset = Math.floor(position.top - this.oldPosition.top);
1160
- scroller.setOffset(offset);
1161
- }
1162
- }
1163
- else if (!isPressed) {
1164
- if (this.isFixed && this.oldPosition) {
1165
- const offset = Math.floor(position.top - this.oldPosition.top);
1166
- scroller.setOffset(offset);
1167
- }
1168
- else {
1169
- limitPosition(position);
1170
- }
1171
- }
1172
- }
1173
- docIsChanged = false;
1174
- }));
1175
1032
  }
1176
1033
  updateCursorPosition(nativeRange) {
1177
1034
  const startContainer = nativeRange.startContainer;
@@ -1180,12 +1037,10 @@ class ExperimentalCaret {
1180
1037
  this.positionChangeEvent.next(null);
1181
1038
  return;
1182
1039
  }
1183
- if (this.compositionState) {
1184
- const compositionElement = this.compositionElement;
1185
- compositionElement.innerText = this.compositionState.data;
1040
+ const compositionNode = this.domRenderer.compositionNode;
1041
+ if (compositionNode) {
1186
1042
  nativeRange = nativeRange.cloneRange();
1187
- nativeRange.insertNode(compositionElement);
1188
- nativeRange.selectNodeContents(compositionElement);
1043
+ nativeRange.selectNodeContents(compositionNode);
1189
1044
  nativeRange.collapse();
1190
1045
  }
1191
1046
  const rect = getLayoutRectByRange(nativeRange);
@@ -1212,13 +1067,34 @@ class ExperimentalCaret {
1212
1067
  rectTop = Math.floor(rectTop);
1213
1068
  const containerRect = this.editorMask.getBoundingClientRect();
1214
1069
  const top = Math.floor(rectTop - containerRect.top);
1215
- const left = Math.floor(rect.left - containerRect.left);
1070
+ const left = Math.floor(rect.left + rect.width / 2 - containerRect.left);
1071
+ let rotate = 0;
1072
+ if (nativeRange.collapsed) {
1073
+ rotate = Math.round(Math.atan2(rect.width, rect.height) * 180 / Math.PI);
1074
+ if (rotate !== 0) {
1075
+ const hackEle = document.createElement('span');
1076
+ hackEle.style.cssText = 'display: inline-block; width: 10px; height: 10px; position: relative; contain: layout style size;';
1077
+ const pointEle = document.createElement('span');
1078
+ pointEle.style.cssText = 'position: absolute; left: 0; top: 0; width:0;height:0';
1079
+ hackEle.append(pointEle);
1080
+ node.append(hackEle);
1081
+ const t1 = pointEle.getBoundingClientRect().top;
1082
+ pointEle.style.right = '0';
1083
+ pointEle.style.left = '';
1084
+ const t2 = pointEle.getBoundingClientRect().top;
1085
+ if (t2 < t1) {
1086
+ rotate = -rotate;
1087
+ }
1088
+ hackEle.remove();
1089
+ }
1090
+ }
1216
1091
  Object.assign(this.elementRef.style, {
1217
1092
  left: left + 'px',
1218
1093
  top: top + 'px',
1219
1094
  height: boxHeight + 'px',
1220
1095
  lineHeight: boxHeight + 'px',
1221
- fontSize
1096
+ fontSize,
1097
+ transform: `rotate(${rotate}deg)`,
1222
1098
  });
1223
1099
  this.caret.style.backgroundColor = color;
1224
1100
  this.styleChangeEvent.next({
@@ -1231,6 +1107,21 @@ class ExperimentalCaret {
1231
1107
  top: rectTop,
1232
1108
  height: boxHeight
1233
1109
  });
1110
+ if (this.changeFromSelf) {
1111
+ this.changeFromSelf = false;
1112
+ const selfRect = this.elementRef.getBoundingClientRect();
1113
+ const limit = this.getLimit();
1114
+ if (selfRect.top < limit.top) {
1115
+ this.elementRef.scrollIntoView({
1116
+ block: 'start'
1117
+ });
1118
+ }
1119
+ else if (selfRect.bottom > limit.bottom) {
1120
+ this.elementRef.scrollIntoView({
1121
+ block: 'end'
1122
+ });
1123
+ }
1124
+ }
1234
1125
  }
1235
1126
  }
1236
1127
  /**
@@ -1246,8 +1137,9 @@ let MagicInput = class MagicInput extends Input {
1246
1137
  get disabled() {
1247
1138
  return this._disabled;
1248
1139
  }
1249
- constructor(parser, keyboard, commander, selection, controller, scheduler, textbus) {
1140
+ constructor(domAdapter, parser, keyboard, commander, selection, controller, scheduler, textbus) {
1250
1141
  super();
1142
+ this.domAdapter = domAdapter;
1251
1143
  this.parser = parser;
1252
1144
  this.keyboard = keyboard;
1253
1145
  this.commander = commander;
@@ -1256,8 +1148,7 @@ let MagicInput = class MagicInput extends Input {
1256
1148
  this.scheduler = scheduler;
1257
1149
  this.textbus = textbus;
1258
1150
  this.composition = false;
1259
- this.compositionState = null;
1260
- this.caret = new ExperimentalCaret(this.scheduler, this.textbus.get(VIEW_MASK));
1151
+ this.caret = new ExperimentalCaret(this.domAdapter, this.scheduler, this.textbus.get(VIEW_MASK));
1261
1152
  this.isSafari = isSafari();
1262
1153
  this.isFirefox = isFirefox();
1263
1154
  this.isMac = isMac();
@@ -1297,13 +1188,8 @@ let MagicInput = class MagicInput extends Input {
1297
1188
  if (!this.isFocus) {
1298
1189
  (_a = this.textarea) === null || _a === void 0 ? void 0 : _a.focus();
1299
1190
  setTimeout(() => {
1300
- var _a, _b, _c;
1301
1191
  if (!this.nativeFocus && this.isFocus) {
1302
- this.subscription.unsubscribe();
1303
- (_b = (_a = this.textarea) === null || _a === void 0 ? void 0 : _a.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(this.textarea);
1304
- this.subscription = new Subscription();
1305
- this.init();
1306
- (_c = this.textarea) === null || _c === void 0 ? void 0 : _c.focus();
1192
+ this.reInit();
1307
1193
  }
1308
1194
  });
1309
1195
  }
@@ -1319,6 +1205,22 @@ let MagicInput = class MagicInput extends Input {
1319
1205
  this.caret.destroy();
1320
1206
  this.subscription.unsubscribe();
1321
1207
  }
1208
+ reInit(delay = false) {
1209
+ var _a, _b, _c;
1210
+ this.subscription.unsubscribe();
1211
+ (_b = (_a = this.textarea) === null || _a === void 0 ? void 0 : _a.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(this.textarea);
1212
+ this.subscription = new Subscription();
1213
+ this.init();
1214
+ if (delay) {
1215
+ setTimeout(() => {
1216
+ var _a;
1217
+ (_a = this.textarea) === null || _a === void 0 ? void 0 : _a.focus();
1218
+ });
1219
+ }
1220
+ else {
1221
+ (_c = this.textarea) === null || _c === void 0 ? void 0 : _c.focus();
1222
+ }
1223
+ }
1322
1224
  init() {
1323
1225
  const doc = this.doc;
1324
1226
  const contentBody = doc.body;
@@ -1327,9 +1229,19 @@ let MagicInput = class MagicInput extends Input {
1327
1229
  contentBody.appendChild(textarea);
1328
1230
  this.textarea = textarea;
1329
1231
  this.subscription.add(fromEvent(textarea, 'blur').subscribe(() => {
1232
+ // if (this.isFocus) {
1233
+ // this.isFocus = false
1234
+ // this.reInit(true)
1235
+ // }
1330
1236
  this.isFocus = false;
1331
1237
  this.nativeFocus = false;
1332
1238
  this.caret.hide();
1239
+ if (this.domAdapter.composition) {
1240
+ const slot = this.domAdapter.composition.slot;
1241
+ this.domAdapter.composition = null;
1242
+ this.domAdapter.compositionNode = null;
1243
+ slot.__changeMarker__.forceMarkDirtied();
1244
+ }
1333
1245
  }), fromEvent(textarea, 'focus').subscribe(() => {
1334
1246
  this.nativeFocus = true;
1335
1247
  }), this.caret.onStyleChange.subscribe(style => {
@@ -1340,7 +1252,7 @@ let MagicInput = class MagicInput extends Input {
1340
1252
  this.handleDefaultActions(textarea);
1341
1253
  }
1342
1254
  handleDefaultActions(textarea) {
1343
- this.subscription.add(fromEvent(document, 'copy').subscribe(ev => {
1255
+ this.subscription.add(fromEvent(isFirefox() ? textarea : document, 'copy').subscribe(ev => {
1344
1256
  const selection = this.selection;
1345
1257
  if (!selection.isSelected) {
1346
1258
  return;
@@ -1410,6 +1322,7 @@ let MagicInput = class MagicInput extends Input {
1410
1322
  }), fromEvent(textarea, 'compositionend').subscribe(() => {
1411
1323
  isWriting = false;
1412
1324
  }), fromEvent(textarea, 'beforeinput').subscribe(ev => {
1325
+ this.ignoreComposition = false;
1413
1326
  if (this.isSafari) {
1414
1327
  if (ev.inputType === 'insertFromComposition') {
1415
1328
  isIgnore = true;
@@ -1431,6 +1344,7 @@ let MagicInput = class MagicInput extends Input {
1431
1344
  key = keys.charAt(+ev.code.substring(5));
1432
1345
  ev.preventDefault();
1433
1346
  }
1347
+ this.caret.changeFromSelf = true;
1434
1348
  const is = this.keyboard.execShortcut({
1435
1349
  key: key,
1436
1350
  altKey: ev.altKey,
@@ -1441,6 +1355,9 @@ let MagicInput = class MagicInput extends Input {
1441
1355
  this.ignoreComposition = true;
1442
1356
  ev.preventDefault();
1443
1357
  }
1358
+ else {
1359
+ this.caret.changeFromSelf = false;
1360
+ }
1444
1361
  }));
1445
1362
  }
1446
1363
  handleInput(textarea) {
@@ -1449,10 +1366,10 @@ let MagicInput = class MagicInput extends Input {
1449
1366
  return !this.ignoreComposition;
1450
1367
  })).subscribe(() => {
1451
1368
  if (!this.selection.isCollapsed) {
1369
+ this.caret.changeFromSelf = true;
1452
1370
  this.commander.delete();
1453
1371
  }
1454
1372
  this.composition = true;
1455
- this.caret.compositionState = this.compositionState = null;
1456
1373
  startIndex = this.selection.startOffset;
1457
1374
  const startSlot = this.selection.startSlot;
1458
1375
  const event = new Event(startSlot, {
@@ -1469,17 +1386,20 @@ let MagicInput = class MagicInput extends Input {
1469
1386
  return;
1470
1387
  }
1471
1388
  const startSlot = this.selection.startSlot;
1472
- this.caret.compositionState = this.compositionState = {
1389
+ this.domAdapter.composition = {
1473
1390
  slot: startSlot,
1474
- index: startIndex,
1475
- data: ev.data
1391
+ text: ev.data,
1392
+ offset: ev.data.length,
1393
+ index: startIndex
1476
1394
  };
1477
- this.caret.refresh(true);
1395
+ this.caret.changeFromSelf = true;
1396
+ this.caret.refresh();
1478
1397
  const event = new Event(startSlot, {
1479
1398
  index: startIndex,
1480
1399
  data: ev.data
1481
1400
  });
1482
1401
  invokeListener(startSlot.parent, 'onCompositionUpdate', event);
1402
+ startSlot.__changeMarker__.forceMarkDirtied();
1483
1403
  }));
1484
1404
  let isCompositionEnd = false;
1485
1405
  this.subscription.add(merge(fromEvent(textarea, 'beforeinput').pipe(filter(ev => {
@@ -1503,24 +1423,16 @@ let MagicInput = class MagicInput extends Input {
1503
1423
  textarea.value = '';
1504
1424
  return ev.data;
1505
1425
  }))).subscribe(text => {
1426
+ var _a;
1506
1427
  this.composition = false;
1507
- this.caret.compositionState = this.compositionState = null;
1508
- const compositionElement = this.caret.compositionElement;
1509
- let nextSibling = compositionElement.nextSibling;
1510
- while (nextSibling) {
1511
- if (!nextSibling.textContent) {
1512
- const next = nextSibling.nextSibling;
1513
- nextSibling.remove();
1514
- nextSibling = next;
1515
- continue;
1516
- }
1517
- nextSibling.remove();
1518
- break;
1519
- }
1520
- compositionElement.remove();
1428
+ this.domAdapter.composition = null;
1521
1429
  if (text) {
1430
+ this.caret.changeFromSelf = true;
1522
1431
  this.commander.write(text);
1523
1432
  }
1433
+ else {
1434
+ (_a = this.selection.startSlot) === null || _a === void 0 ? void 0 : _a.__changeMarker__.forceMarkDirtied();
1435
+ }
1524
1436
  if (isCompositionEnd) {
1525
1437
  const startSlot = this.selection.startSlot;
1526
1438
  if (startSlot) {
@@ -1549,7 +1461,8 @@ let MagicInput = class MagicInput extends Input {
1549
1461
  };
1550
1462
  MagicInput = __decorate([
1551
1463
  Injectable(),
1552
- __metadata("design:paramtypes", [Parser,
1464
+ __metadata("design:paramtypes", [DomAdapter,
1465
+ Parser,
1553
1466
  Keyboard,
1554
1467
  Commander,
1555
1468
  Selection,
@@ -1567,10 +1480,11 @@ class CollaborateSelectionAwarenessDelegate {
1567
1480
  * 协作光标绘制类
1568
1481
  */
1569
1482
  let CollaborateCursor = class CollaborateCursor {
1570
- constructor(textbus, nativeSelection, scheduler, selection, awarenessDelegate) {
1483
+ constructor(textbus, nativeSelection, scheduler, selection, userActivity, awarenessDelegate) {
1571
1484
  this.nativeSelection = nativeSelection;
1572
1485
  this.scheduler = scheduler;
1573
1486
  this.selection = selection;
1487
+ this.userActivity = userActivity;
1574
1488
  this.awarenessDelegate = awarenessDelegate;
1575
1489
  this.host = createElement('div', {
1576
1490
  styles: {
@@ -1620,6 +1534,7 @@ let CollaborateCursor = class CollaborateCursor {
1620
1534
  this.onRectsChange = new Subject();
1621
1535
  this.subscription = new Subscription();
1622
1536
  this.currentSelection = [];
1537
+ this.ratio = window.devicePixelRatio || 1;
1623
1538
  this.container = textbus.get(VIEW_CONTAINER);
1624
1539
  this.canvasContainer.append(this.canvas);
1625
1540
  this.host.append(this.canvasContainer, this.tooltips);
@@ -1639,6 +1554,13 @@ let CollaborateCursor = class CollaborateCursor {
1639
1554
  this.refresh();
1640
1555
  }));
1641
1556
  }
1557
+ init() {
1558
+ if (this.userActivity) {
1559
+ this.subscription.add(this.userActivity.onStateChange.subscribe(v => {
1560
+ this.draw(v);
1561
+ }));
1562
+ }
1563
+ }
1642
1564
  /**
1643
1565
  * 刷新协作光标,由于 Textbus 只会绘制可视区域的光标,当可视区域发生变化时,需要重新绘制
1644
1566
  */
@@ -1656,15 +1578,16 @@ let CollaborateCursor = class CollaborateCursor {
1656
1578
  this.currentSelection = paths;
1657
1579
  const containerRect = this.container.getBoundingClientRect();
1658
1580
  this.canvas.style.top = containerRect.top * -1 + 'px';
1659
- this.canvas.width = this.canvas.offsetWidth;
1660
- this.canvas.height = this.canvas.offsetHeight;
1581
+ this.canvas.width = this.canvas.offsetWidth * this.ratio;
1582
+ this.canvas.height = this.canvas.offsetHeight * this.ratio;
1583
+ this.context.scale(this.ratio, this.ratio);
1661
1584
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
1662
1585
  const users = [];
1663
1586
  paths.filter(i => {
1664
- return i.paths.anchor.length && i.paths.focus.length;
1587
+ return i.selection.anchor.length && i.selection.focus.length;
1665
1588
  }).forEach(item => {
1666
- const anchorPaths = [...item.paths.anchor];
1667
- const focusPaths = [...item.paths.focus];
1589
+ const anchorPaths = [...item.selection.anchor];
1590
+ const focusPaths = [...item.selection.focus];
1668
1591
  const anchorOffset = anchorPaths.pop();
1669
1592
  const anchorSlot = this.selection.findSlotByPaths(anchorPaths);
1670
1593
  const focusOffset = focusPaths.pop();
@@ -1682,8 +1605,13 @@ let CollaborateCursor = class CollaborateCursor {
1682
1605
  return;
1683
1606
  }
1684
1607
  const nativeRange = document.createRange();
1685
- nativeRange.setStart(anchor.node, anchor.offset);
1686
- nativeRange.setEnd(focus.node, focus.offset);
1608
+ try {
1609
+ nativeRange.setStart(anchor.node, anchor.offset);
1610
+ nativeRange.setEnd(focus.node, focus.offset);
1611
+ }
1612
+ catch (e) {
1613
+ return;
1614
+ }
1687
1615
  if ((anchor.node !== focus.node || anchor.offset !== focus.offset) && nativeRange.collapsed) {
1688
1616
  nativeRange.setStart(focus.node, focus.offset);
1689
1617
  nativeRange.setEnd(anchor.node, anchor.offset);
@@ -1812,10 +1740,12 @@ let CollaborateCursor = class CollaborateCursor {
1812
1740
  CollaborateCursor = __decorate([
1813
1741
  Injectable(),
1814
1742
  __param(4, Optional()),
1743
+ __param(5, Optional()),
1815
1744
  __metadata("design:paramtypes", [Textbus,
1816
1745
  SelectionBridge,
1817
1746
  Scheduler,
1818
1747
  Selection,
1748
+ UserActivity,
1819
1749
  CollaborateSelectionAwarenessDelegate])
1820
1750
  ], CollaborateCursor);
1821
1751
 
@@ -1852,9 +1782,7 @@ class NativeCaret {
1852
1782
  height: 0
1853
1783
  };
1854
1784
  }
1855
- constructor(scheduler) {
1856
- this.scheduler = scheduler;
1857
- this.oldPosition = null;
1785
+ constructor() {
1858
1786
  this._nativeRange = null;
1859
1787
  this.subs = [];
1860
1788
  this.positionChangeEvent = new Subject();
@@ -1863,59 +1791,6 @@ class NativeCaret {
1863
1791
  refresh() {
1864
1792
  //
1865
1793
  }
1866
- correctScrollTop(scroller) {
1867
- this.destroy();
1868
- const scheduler = this.scheduler;
1869
- let docIsChanged = true;
1870
- function limitPosition(position) {
1871
- const { top, bottom } = scroller.getLimit();
1872
- const caretTop = position.top;
1873
- if (caretTop + position.height > bottom) {
1874
- const offset = caretTop - bottom + position.height;
1875
- scroller.setOffset(offset);
1876
- }
1877
- else if (position.top < top) {
1878
- scroller.setOffset(-(top - position.top));
1879
- }
1880
- }
1881
- let isPressed = false;
1882
- this.subs.push(scroller.onScroll.subscribe(() => {
1883
- if (this.oldPosition) {
1884
- const rect = this.rect;
1885
- this.oldPosition.top = rect.top;
1886
- this.oldPosition.left = rect.left;
1887
- this.oldPosition.height = rect.height;
1888
- }
1889
- }), fromEvent(document, 'mousedown', true).subscribe(() => {
1890
- isPressed = true;
1891
- }), fromEvent(document, 'mouseup', true).subscribe(() => {
1892
- isPressed = false;
1893
- }), scheduler.onDocChange.subscribe(() => {
1894
- docIsChanged = true;
1895
- }), this.onPositionChange.subscribe(position => {
1896
- if (position) {
1897
- if (docIsChanged) {
1898
- if (scheduler.lastChangesHasLocalUpdate) {
1899
- limitPosition(position);
1900
- }
1901
- else if (this.oldPosition) {
1902
- const offset = Math.floor(position.top - this.oldPosition.top);
1903
- scroller.setOffset(offset);
1904
- }
1905
- }
1906
- else if (!isPressed) {
1907
- if (this.oldPosition) {
1908
- const offset = Math.floor(position.top - this.oldPosition.top);
1909
- scroller.setOffset(offset);
1910
- }
1911
- else {
1912
- limitPosition(position);
1913
- }
1914
- }
1915
- }
1916
- docIsChanged = false;
1917
- }));
1918
- }
1919
1794
  destroy() {
1920
1795
  this.subs.forEach(i => i.unsubscribe());
1921
1796
  this.subs = [];
@@ -1933,18 +1808,17 @@ let NativeInput = class NativeInput extends Input {
1933
1808
  get disabled() {
1934
1809
  return this._disabled;
1935
1810
  }
1936
- constructor(textbus, parser, scheduler, selection, keyboard, domAdapter, commander, controller) {
1811
+ constructor(textbus, parser, selection, keyboard, domAdapter, commander, controller) {
1937
1812
  super();
1938
1813
  this.parser = parser;
1939
- this.scheduler = scheduler;
1940
1814
  this.selection = selection;
1941
1815
  this.keyboard = keyboard;
1942
1816
  this.domAdapter = domAdapter;
1943
1817
  this.commander = commander;
1944
1818
  this.controller = controller;
1945
- this.caret = new NativeCaret(this.scheduler);
1819
+ this.caret = new NativeCaret();
1946
1820
  this.composition = false;
1947
- this.compositionState = null;
1821
+ // compositionState: CompositionState | null = null
1948
1822
  this.onReady = Promise.resolve();
1949
1823
  this._disabled = false;
1950
1824
  this.nativeSelection = document.getSelection();
@@ -1987,7 +1861,7 @@ let NativeInput = class NativeInput extends Input {
1987
1861
  this.subscription.unsubscribe();
1988
1862
  }
1989
1863
  handleDefaultActions(textarea) {
1990
- this.subscription.add(fromEvent(document, 'copy').subscribe(ev => {
1864
+ this.subscription.add(fromEvent(isFirefox() ? textarea : document, 'copy').subscribe(ev => {
1991
1865
  const selection = this.selection;
1992
1866
  if (!selection.isSelected) {
1993
1867
  return;
@@ -2041,8 +1915,8 @@ let NativeInput = class NativeInput extends Input {
2041
1915
  });
2042
1916
  }));
2043
1917
  }
2044
- handlePaste(html, text) {
2045
- const slot = this.parser.parse(html, new Slot([
1918
+ handlePaste(dom, text) {
1919
+ const slot = this.parser.parse(dom, new Slot([
2046
1920
  ContentType.BlockComponent,
2047
1921
  ContentType.InlineComponent,
2048
1922
  ContentType.Text
@@ -2102,7 +1976,6 @@ let NativeInput = class NativeInput extends Input {
2102
1976
  let startIndex;
2103
1977
  const compositionStart = () => {
2104
1978
  this.composition = true;
2105
- this.compositionState = null;
2106
1979
  startIndex = this.selection.startOffset;
2107
1980
  const startSlot = this.selection.startSlot;
2108
1981
  const event = new Event(startSlot, {
@@ -2112,11 +1985,6 @@ let NativeInput = class NativeInput extends Input {
2112
1985
  };
2113
1986
  const compositionUpdate = (data) => {
2114
1987
  const startSlot = this.selection.startSlot;
2115
- this.compositionState = {
2116
- slot: startSlot,
2117
- index: startIndex,
2118
- data
2119
- };
2120
1988
  const event = new Event(startSlot, {
2121
1989
  index: startIndex,
2122
1990
  data
@@ -2140,6 +2008,10 @@ let NativeInput = class NativeInput extends Input {
2140
2008
  compositionUpdate(ev.data);
2141
2009
  }), fromEvent(input, 'compositionend').subscribe(ev => {
2142
2010
  compositionEnd(ev.data);
2011
+ const startContainer = this.nativeSelection.focusNode;
2012
+ if (startContainer instanceof Text && startContainer.textContent === ev.data) {
2013
+ startContainer.remove();
2014
+ }
2143
2015
  }), fromEvent(input, 'beforeinput').subscribe(ev => {
2144
2016
  var _a;
2145
2017
  switch (ev.inputType) {
@@ -2203,7 +2075,6 @@ let NativeInput = class NativeInput extends Input {
2203
2075
  return !this.ignoreComposition;
2204
2076
  })).subscribe(() => {
2205
2077
  this.composition = true;
2206
- this.compositionState = null;
2207
2078
  startIndex = this.selection.startOffset;
2208
2079
  const startSlot = this.selection.startSlot;
2209
2080
  const event = new Event(startSlot, {
@@ -2214,11 +2085,6 @@ let NativeInput = class NativeInput extends Input {
2214
2085
  return !this.ignoreComposition;
2215
2086
  })).subscribe(ev => {
2216
2087
  const startSlot = this.selection.startSlot;
2217
- this.compositionState = {
2218
- slot: startSlot,
2219
- index: startIndex,
2220
- data: ev.data
2221
- };
2222
2088
  const event = new Event(startSlot, {
2223
2089
  index: startIndex,
2224
2090
  data: ev.data
@@ -2268,8 +2134,11 @@ let NativeInput = class NativeInput extends Input {
2268
2134
  return !b;
2269
2135
  }))).subscribe(text => {
2270
2136
  this.composition = false;
2271
- this.compositionState = null;
2272
2137
  if (text) {
2138
+ const startContainer = this.nativeSelection.focusNode;
2139
+ if (startContainer instanceof Text && startContainer.textContent === text) {
2140
+ startContainer.remove();
2141
+ }
2273
2142
  this.commander.write(text);
2274
2143
  }
2275
2144
  if (isCompositionEnd) {
@@ -2287,7 +2156,6 @@ NativeInput = __decorate([
2287
2156
  Injectable(),
2288
2157
  __metadata("design:paramtypes", [Textbus,
2289
2158
  Parser,
2290
- Scheduler,
2291
2159
  Selection,
2292
2160
  Keyboard,
2293
2161
  DomAdapter,
@@ -2295,9 +2163,9 @@ NativeInput = __decorate([
2295
2163
  Controller])
2296
2164
  ], NativeInput);
2297
2165
 
2166
+ const browserErrorFn = makeError('BrowserModule');
2298
2167
  class BrowserModule {
2299
- constructor(host, config) {
2300
- this.host = host;
2168
+ constructor(config) {
2301
2169
  this.config = config;
2302
2170
  const { mask, wrapper } = BrowserModule.createLayout();
2303
2171
  wrapper.prepend(config.adapter.host);
@@ -2323,11 +2191,8 @@ class BrowserModule {
2323
2191
  provide: Input,
2324
2192
  useClass: config.useContentEditable ? NativeInput : MagicInput
2325
2193
  }, {
2326
- provide: ViewAdapter,
2327
- useFactory(v) {
2328
- return v;
2329
- },
2330
- deps: [DomAdapter]
2194
+ provide: Adapter,
2195
+ useValue: config.adapter
2331
2196
  }, {
2332
2197
  provide: DomAdapter,
2333
2198
  useValue: config.adapter
@@ -2355,10 +2220,54 @@ class BrowserModule {
2355
2220
  SelectionBridge,
2356
2221
  CollaborateCursor];
2357
2222
  this.workbench = wrapper;
2358
- this.host.append(wrapper);
2359
2223
  }
2360
- onDestroy() {
2361
- this.workbench.remove();
2224
+ /**
2225
+ * 解析 HTML 并返回一个组件实例
2226
+ * @param html 要解析的 HTML
2227
+ * @param rootComponentLoader 文档根组件加载器
2228
+ * @param textbus
2229
+ */
2230
+ readDocumentByHTML(html, rootComponentLoader, textbus) {
2231
+ const parser = textbus.get(Parser);
2232
+ const doc = parser.parseDoc(html, rootComponentLoader);
2233
+ if (doc instanceof Component) {
2234
+ return doc;
2235
+ }
2236
+ throw browserErrorFn('rootComponentLoader must return a component instance.');
2237
+ }
2238
+ /**
2239
+ * 将组件数据解析到组件实例中
2240
+ * @param data 要解析的 JSON 数据
2241
+ * @param rootComponent 根组件
2242
+ * @param textbus
2243
+ */
2244
+ readDocumentByComponentLiteral(data, rootComponent, textbus) {
2245
+ const registry = textbus.get(Registry);
2246
+ return registry.createComponentByFactory(data, rootComponent);
2247
+ }
2248
+ setup(textbus) {
2249
+ this.textbus = textbus;
2250
+ const host = this.config.renderTo();
2251
+ if (!(host instanceof HTMLElement)) {
2252
+ throw browserErrorFn('view container is not a HTMLElement');
2253
+ }
2254
+ const cursor = textbus.get(CollaborateCursor);
2255
+ cursor.init();
2256
+ host.append(this.workbench);
2257
+ return () => {
2258
+ cursor.destroy();
2259
+ this.workbench.remove();
2260
+ };
2261
+ }
2262
+ onAfterStartup(textbus) {
2263
+ if (this.config.autoFocus) {
2264
+ textbus.focus();
2265
+ }
2266
+ }
2267
+ onDestroy(textbus) {
2268
+ textbus.get(Input).destroy();
2269
+ textbus.get(SelectionBridge).destroy();
2270
+ textbus.get(CollaborateCursor).destroy();
2362
2271
  }
2363
2272
  static createLayout() {
2364
2273
  const mask = createElement('div', {
@@ -2371,10 +2280,23 @@ class BrowserModule {
2371
2280
  right: 0,
2372
2281
  top: 0,
2373
2282
  bottom: 0,
2283
+ pointerEvents: 'none',
2284
+ // overflow: 'hidden'
2285
+ }
2286
+ });
2287
+ const maskWrapper = createElement('div', {
2288
+ styles: {
2289
+ position: 'absolute',
2290
+ left: 0,
2291
+ right: 0,
2292
+ top: 0,
2293
+ bottom: 0,
2294
+ margin: '0 -2px',
2374
2295
  zIndex: 1,
2375
2296
  pointerEvents: 'none',
2376
2297
  overflow: 'hidden'
2377
- }
2298
+ },
2299
+ children: [mask]
2378
2300
  });
2379
2301
  const wrapper = createElement('div', {
2380
2302
  attrs: {
@@ -2386,7 +2308,7 @@ class BrowserModule {
2386
2308
  position: 'relative',
2387
2309
  flexDirection: 'column'
2388
2310
  },
2389
- children: [mask]
2311
+ children: [maskWrapper]
2390
2312
  });
2391
2313
  return {
2392
2314
  wrapper,
@@ -2395,4 +2317,4 @@ class BrowserModule {
2395
2317
  }
2396
2318
  }
2397
2319
 
2398
- export { BrowserModule, CollaborateCursor, CollaborateSelectionAwarenessDelegate, DomAdapter, EDITOR_OPTIONS, Input, MagicInput, Parser, SelectionBridge, VIEW_CONTAINER, VIEW_DOCUMENT, VIEW_MASK, createElement, createTextNode, getLayoutRectByRange, isFirefox, isMac, isMobileBrowser, isSafari, isWindows };
2320
+ export { BrowserModule, CollaborateCursor, CollaborateSelectionAwarenessDelegate, DomAdapter, EDITOR_OPTIONS, Input, MagicInput, NativeInput, Parser, SelectionBridge, VIEW_CONTAINER, VIEW_DOCUMENT, VIEW_MASK, createElement, getLayoutRectByRange, isFirefox, isMac, isMobileBrowser, isSafari, isWindows };