@textbus/platform-browser 4.0.0-alpha.0 → 4.0.0-alpha.10

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/README.md CHANGED
@@ -4,120 +4,6 @@ Textbus PC 浏览器支持模块
4
4
  Textbus 是一套用于构建富交互的富文本编辑框架。和大多数富文本编辑器不同的是,Textbus 以组件为核心,格式为辅助,并大幅简化了富文本编辑器开发中常见
5
5
  API,且提供了更高的抽象层,使 Textbus 不仅易于上手,同时还能驱动复杂的富文本应用。
6
6
 
7
- 本项目为 Textbus PC 端浏览器中间层实现,提供了 Textbus PC 端所需要的编辑器基本支持能力。如光标、选区桥接、DOM 解析及渲染能力桥接等。
8
-
9
- 如果你需要一个开箱即用的编辑器,请参考[官方文档](https://textbus.io)。
10
-
11
- ## 安装
12
-
13
- ```
14
- npm install @textbus/core @textbus/platform-browser
15
- ```
16
-
17
- ## 创建编辑器
18
-
19
- ```ts
20
- import { Viewer, ViewOptions } from '@textbus/browser'
21
-
22
- import { rootComponent, rootComponentLoader } from './root.component'
23
-
24
- const config: ViewOptions = {
25
- // ...配置项
26
- }
27
-
28
- const editor = new Viewer(rootComponent, rootComponentLoader, config)
29
- ```
30
-
31
- 其中 `rootComponent`,`rootComponentLoader` 实现请参考[官方文档](https://textbus.io/docs/guide)。
32
-
33
- ### 配置项
34
-
35
- 配置项接口如下:
36
-
37
- ```ts
38
- export interface ViewModule extends Module {
39
- componentLoaders?: ComponentLoader[]
40
- formatLoaders?: FormatLoader[]
41
- }
42
-
43
- /**
44
- * Textbus PC 端配置接口
45
- */
46
- export interface ViewOptions extends TextbusConfig {
47
- imports?: ViewModule[]
48
- /** 自动获取焦点 */
49
- autoFocus?: boolean
50
- /** 编辑区最小高度 */
51
- minHeight?: string
52
- /** 组件加载器 */
53
- componentLoaders?: ComponentLoader[]
54
- /** 格式加载器 */
55
- formatLoaders?: FormatLoader[]
56
- /** 默认内容 */
57
- content?: string | ComponentLiteral
58
- /** 文档默认样式表 */
59
- styleSheets?: string[]
60
- /** 配置文档编辑状态下用到的样式 */
61
- editingStyleSheets?: string[]
62
- }
63
- ```
64
-
65
- ### 启动
66
-
67
- ```ts
68
- const host = document.getElementById('editor')
69
-
70
- editor.mount(host).then(() => {
71
- console.log('编辑器创建成功!')
72
- })
73
- ```
74
-
75
- ### 销毁编辑器
76
-
77
- ```ts
78
- editor.destroy()
79
- ```
80
-
81
- ### 获取焦点
82
-
83
- ```ts
84
- editor.focus()
85
- ```
86
-
87
- ### 取消焦点
88
-
89
- ```ts
90
- editor.blur()
91
- ```
92
-
93
- ### 获取 HTML 内容
94
-
95
- ```ts
96
- const content = editor.getContents().content
97
- ```
98
-
99
- ### 获取 JSON 内容
100
-
101
- ```ts
102
- const json = editor.getJSON().content
103
- ```
104
-
105
- ### 替换内容
106
-
107
- ```ts
108
- editor.replaceContent('<p>新内容!</p>')
109
-
110
- editor.replaceContent({
111
- // 必须为 Textbus 导出的 JSON 格式
112
- })
113
- ```
114
-
115
- ### 清空编辑器
116
-
117
- ```ts
118
- editor.replaceContent('')
119
- ```
120
-
121
7
  ### 官方文档
122
8
 
123
9
  更多文档请参考:[中文文档](https://textbus.io)
@@ -1,5 +1,4 @@
1
- import { Injector } from '@viewfly/core';
2
- import { Selection, SelectionPaths, AbstractSelection, Scheduler } from '@textbus/core';
1
+ import { Selection, SelectionPaths, AbstractSelection, Scheduler, Textbus } from '@textbus/core';
3
2
  import { SelectionBridge } from './selection-bridge';
4
3
  import { Rect } from './_utils/uikit';
5
4
  /**
@@ -44,7 +43,7 @@ export declare class CollaborateCursor {
44
43
  private subscription;
45
44
  private currentSelection;
46
45
  private container;
47
- constructor(injector: Injector, nativeSelection: SelectionBridge, scheduler: Scheduler, selection: Selection, awarenessDelegate?: CollaborateSelectionAwarenessDelegate | undefined);
46
+ constructor(textbus: Textbus, nativeSelection: SelectionBridge, scheduler: Scheduler, selection: Selection, awarenessDelegate?: CollaborateSelectionAwarenessDelegate | undefined);
48
47
  /**
49
48
  * 刷新协作光标,由于 Textbus 只会绘制可视区域的光标,当可视区域发生变化时,需要重新绘制
50
49
  */
@@ -1,29 +1,58 @@
1
- import { ComponentInstance, Slot, ViewAdapter, NodeLocation, VElement } from '@textbus/core';
1
+ import { ComponentInstance, Slot, ViewAdapter, NodeLocation, VElement, VTextNode } from '@textbus/core';
2
2
  /**
3
- * Textbus PC 端浏览器渲染能力实现
3
+ * Textbus PC 端浏览器渲染能力桥接器抽象类,提供了 DOM 元素查询能力,具体渲染能力由各前端框架实现相应桥接
4
4
  */
5
- export declare abstract class DomAdapter<ViewComponent, ViewElement> extends ViewAdapter<ViewElement> {
5
+ export declare abstract class DomAdapter<ViewComponent, ViewElement> extends ViewAdapter {
6
6
  private mount;
7
7
  host: HTMLElement;
8
- protected componentRootElementCaches: WeakMap<ComponentInstance<unknown, unknown, unknown>, HTMLElement>;
8
+ protected componentRootElementCaches: {
9
+ set: {
10
+ (key: ComponentInstance<unknown, unknown, unknown>, value: HTMLElement): void;
11
+ (key: HTMLElement, value: ComponentInstance<unknown, unknown, unknown>): void;
12
+ };
13
+ get: {
14
+ (key: ComponentInstance<unknown, unknown, unknown>): HTMLElement;
15
+ (key: HTMLElement): ComponentInstance<unknown, unknown, unknown>;
16
+ };
17
+ remove: (key: HTMLElement | ComponentInstance<unknown, unknown, unknown>) => void;
18
+ };
9
19
  protected slotRootNativeElementCaches: {
10
20
  set: {
11
- (key: Slot<any>, value: Node): void;
12
- (key: Node, value: Slot<any>): void;
21
+ (key: Slot<any>, value: HTMLElement): void;
22
+ (key: HTMLElement, value: Slot<any>): void;
13
23
  };
14
24
  get: {
15
- (key: Slot<any>): Node;
16
- (key: Node): Slot<any>;
25
+ (key: Slot<any>): HTMLElement;
26
+ (key: HTMLElement): Slot<any>;
17
27
  };
18
- remove: (key: Node | Slot<any>) => void;
28
+ remove: (key: HTMLElement | Slot<any>) => void;
19
29
  };
20
30
  protected slotRootVElementCaches: WeakMap<Slot<any>, VElement>;
21
31
  protected constructor(mount: (host: HTMLElement, viewComponent: ViewComponent) => (void | (() => void)));
22
32
  render(rootComponent: ComponentInstance): void | (() => void);
23
33
  abstract componentRender(component: ComponentInstance): ViewComponent;
34
+ abstract slotRender(slot: Slot, slotHostRender: (children: Array<VElement | VTextNode | ComponentInstance>) => VElement, renderEnv: any): ViewElement;
24
35
  copy(): void;
36
+ /**
37
+ * 根据组件获取组件的根 DOM 节点
38
+ * @param component
39
+ */
25
40
  getNativeNodeByComponent(component: ComponentInstance): HTMLElement | null;
41
+ /**
42
+ * 根据 DOM 节点,获对对应的组件根节点,如传入的 DOM 节点不为组件的根节点,则返回 null
43
+ * @param node
44
+ */
45
+ getComponentByNativeNode(node: HTMLElement): ComponentInstance | null;
46
+ /**
47
+ * 根据插槽获取插槽的根 DOM 节点
48
+ * @param slot
49
+ */
26
50
  getNativeNodeBySlot(slot: Slot): HTMLElement | null;
51
+ /**
52
+ * 根据 DOM 节点,获对对应的插槽根节点,如传入的 DOM 节点不为插槽的根节点,则返回 null
53
+ * @param node
54
+ */
55
+ getSlotByNativeNode(node: HTMLElement): Slot | null;
27
56
  /**
28
57
  * 获取插槽内容节点集合
29
58
  * @param slot
@@ -1,7 +1,7 @@
1
1
  import 'reflect-metadata';
2
- import { Slot, ViewAdapter, createBidirectionalMapping, VElement, VTextNode, ComponentInstance, Controller, Selection, RootComponentRef, ContentType, Event, invokeListener, Keyboard, Commander, Scheduler, NativeSelectionBridge } from '@textbus/core';
3
- import { InjectionToken, Injectable, Inject, Injector, Optional } from '@viewfly/core';
2
+ import { Slot, Textbus, ViewAdapter, createBidirectionalMapping, ComponentInstance, VElement, VTextNode, Controller, Selection, RootComponentRef, ContentType, Event, invokeListener, Keyboard, Commander, Scheduler, NativeSelectionBridge, FocusManager } from '@textbus/core';
4
3
  import { Subject, filter, fromEvent, Subscription, distinctUntilChanged, merge, map, Observable } from '@tanbo/stream';
4
+ import { InjectionToken, Injectable, Inject, Optional } from '@viewfly/core';
5
5
 
6
6
  function createElement(tagName, options = {}) {
7
7
  const el = document.createElement(tagName);
@@ -166,9 +166,9 @@ let Parser = Parser_1 = class Parser {
166
166
  static parseHTML(html) {
167
167
  return new DOMParser().parseFromString(html, 'text/html').body;
168
168
  }
169
- constructor(options, injector) {
169
+ constructor(options, textbus) {
170
170
  this.options = options;
171
- this.injector = injector;
171
+ this.textbus = textbus;
172
172
  const componentLoaders = [
173
173
  ...(options.componentLoaders || [])
174
174
  ];
@@ -187,23 +187,23 @@ let Parser = Parser_1 = class Parser {
187
187
  this.attributeLoaders = attributeLoaders;
188
188
  }
189
189
  /**
190
- * 使用指定的组件加载器解析一段 HTML 字符串
190
+ * 使用指定的组件加载器解析一段 HTML 字符串或 DOM 元素
191
191
  * @param html
192
192
  * @param rootComponentLoader
193
193
  */
194
194
  parseDoc(html, rootComponentLoader) {
195
- const element = Parser_1.parseHTML(html);
196
- return rootComponentLoader.read(element, this.injector, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
195
+ const element = typeof html === 'string' ? Parser_1.parseHTML(html) : html;
196
+ return rootComponentLoader.read(element, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
197
197
  return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
198
198
  });
199
199
  }
200
200
  /**
201
- * 将一段 HTML 解析到指定插槽
201
+ * 将一段 HTML 或 DOM 元素解析到指定插槽
202
202
  * @param html
203
203
  * @param rootSlot
204
204
  */
205
205
  parse(html, rootSlot) {
206
- const element = Parser_1.parseHTML(html);
206
+ const element = typeof html === 'string' ? Parser_1.parseHTML(html) : html;
207
207
  return this.readFormats(element, rootSlot);
208
208
  }
209
209
  readComponent(el, slot) {
@@ -214,7 +214,7 @@ let Parser = Parser_1 = class Parser {
214
214
  }
215
215
  for (const t of this.componentLoaders) {
216
216
  if (t.match(el)) {
217
- const result = t.read(el, this.injector, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
217
+ const result = t.read(el, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
218
218
  return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
219
219
  });
220
220
  if (!result) {
@@ -294,14 +294,14 @@ let Parser = Parser_1 = class Parser {
294
294
  Parser = Parser_1 = __decorate([
295
295
  Injectable(),
296
296
  __param(0, Inject(EDITOR_OPTIONS)),
297
- __metadata("design:paramtypes", [Object, Injector])
297
+ __metadata("design:paramtypes", [Object, Textbus])
298
298
  ], Parser);
299
299
 
300
300
  class Input {
301
301
  }
302
302
 
303
303
  /**
304
- * Textbus PC 端浏览器渲染能力实现
304
+ * Textbus PC 端浏览器渲染能力桥接器抽象类,提供了 DOM 元素查询能力,具体渲染能力由各前端框架实现相应桥接
305
305
  */
306
306
  class DomAdapter extends ViewAdapter {
307
307
  constructor(mount) {
@@ -322,7 +322,9 @@ class DomAdapter extends ViewAdapter {
322
322
  id: 'textbus-' + Number((Math.random() + '').substring(2)).toString(16)
323
323
  }
324
324
  });
325
- this.componentRootElementCaches = new WeakMap();
325
+ this.componentRootElementCaches = createBidirectionalMapping(a => {
326
+ return a instanceof ComponentInstance;
327
+ });
326
328
  this.slotRootNativeElementCaches = createBidirectionalMapping(a => {
327
329
  return a instanceof Slot;
328
330
  });
@@ -335,12 +337,34 @@ class DomAdapter extends ViewAdapter {
335
337
  copy() {
336
338
  document.execCommand('copy');
337
339
  }
340
+ /**
341
+ * 根据组件获取组件的根 DOM 节点
342
+ * @param component
343
+ */
338
344
  getNativeNodeByComponent(component) {
339
345
  return this.componentRootElementCaches.get(component) || null;
340
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
+ */
341
358
  getNativeNodeBySlot(slot) {
342
359
  return this.slotRootNativeElementCaches.get(slot) || null;
343
360
  }
361
+ /**
362
+ * 根据 DOM 节点,获对对应的插槽根节点,如传入的 DOM 节点不为插槽的根节点,则返回 null
363
+ * @param node
364
+ */
365
+ getSlotByNativeNode(node) {
366
+ return this.slotRootNativeElementCaches.get(node) || null;
367
+ }
344
368
  /**
345
369
  * 获取插槽内容节点集合
346
370
  * @param slot
@@ -425,7 +449,7 @@ class DomAdapter extends ViewAdapter {
425
449
  * Textbus PC 端选区桥接实现
426
450
  */
427
451
  let SelectionBridge = class SelectionBridge {
428
- constructor(config, injector, controller, selection, rootComponentRef, input, domAdapter) {
452
+ constructor(config, textbus, controller, selection, rootComponentRef, input, domAdapter) {
429
453
  this.config = config;
430
454
  this.selection = selection;
431
455
  this.rootComponentRef = rootComponentRef;
@@ -437,7 +461,7 @@ let SelectionBridge = class SelectionBridge {
437
461
  this.connector = null;
438
462
  this.ignoreSelectionChange = false;
439
463
  this.changeFromUser = false;
440
- this.docContainer = injector.get(VIEW_DOCUMENT);
464
+ this.docContainer = textbus.get(VIEW_DOCUMENT);
441
465
  this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(filter(() => {
442
466
  return !controller.readonly;
443
467
  }));
@@ -966,7 +990,7 @@ let SelectionBridge = class SelectionBridge {
966
990
  SelectionBridge = __decorate([
967
991
  Injectable(),
968
992
  __param(0, Inject(EDITOR_OPTIONS)),
969
- __metadata("design:paramtypes", [Object, Injector,
993
+ __metadata("design:paramtypes", [Object, Textbus,
970
994
  Controller,
971
995
  Selection,
972
996
  RootComponentRef,
@@ -1222,7 +1246,7 @@ let MagicInput = class MagicInput extends Input {
1222
1246
  get disabled() {
1223
1247
  return this._disabled;
1224
1248
  }
1225
- constructor(parser, keyboard, commander, selection, controller, scheduler, injector) {
1249
+ constructor(parser, keyboard, commander, selection, controller, scheduler, textbus) {
1226
1250
  super();
1227
1251
  this.parser = parser;
1228
1252
  this.keyboard = keyboard;
@@ -1230,10 +1254,10 @@ let MagicInput = class MagicInput extends Input {
1230
1254
  this.selection = selection;
1231
1255
  this.controller = controller;
1232
1256
  this.scheduler = scheduler;
1233
- this.injector = injector;
1257
+ this.textbus = textbus;
1234
1258
  this.composition = false;
1235
1259
  this.compositionState = null;
1236
- this.caret = new ExperimentalCaret(this.scheduler, this.injector.get(VIEW_MASK));
1260
+ this.caret = new ExperimentalCaret(this.scheduler, this.textbus.get(VIEW_MASK));
1237
1261
  this.isSafari = isSafari();
1238
1262
  this.isFirefox = isFirefox();
1239
1263
  this.isMac = isMac();
@@ -1364,17 +1388,14 @@ let MagicInput = class MagicInput extends Input {
1364
1388
  this.doc.body.appendChild(div);
1365
1389
  div.focus();
1366
1390
  setTimeout(() => {
1367
- let html = div.innerHTML;
1368
- if (!html && text && this.isFirefox) {
1369
- html = text;
1370
- }
1371
- this.handlePaste(html, text);
1372
1391
  this.doc.body.removeChild(div);
1392
+ div.style.cssText = '';
1393
+ this.handlePaste(div, text);
1373
1394
  });
1374
1395
  }));
1375
1396
  }
1376
- handlePaste(html, text) {
1377
- const slot = this.parser.parse(html, new Slot([
1397
+ handlePaste(dom, text) {
1398
+ const slot = this.parser.parse(dom, new Slot([
1378
1399
  ContentType.BlockComponent,
1379
1400
  ContentType.InlineComponent,
1380
1401
  ContentType.Text
@@ -1534,7 +1555,7 @@ MagicInput = __decorate([
1534
1555
  Selection,
1535
1556
  Controller,
1536
1557
  Scheduler,
1537
- Injector])
1558
+ Textbus])
1538
1559
  ], MagicInput);
1539
1560
 
1540
1561
  /**
@@ -1546,7 +1567,7 @@ class CollaborateSelectionAwarenessDelegate {
1546
1567
  * 协作光标绘制类
1547
1568
  */
1548
1569
  let CollaborateCursor = class CollaborateCursor {
1549
- constructor(injector, nativeSelection, scheduler, selection, awarenessDelegate) {
1570
+ constructor(textbus, nativeSelection, scheduler, selection, awarenessDelegate) {
1550
1571
  this.nativeSelection = nativeSelection;
1551
1572
  this.scheduler = scheduler;
1552
1573
  this.selection = selection;
@@ -1599,7 +1620,7 @@ let CollaborateCursor = class CollaborateCursor {
1599
1620
  this.onRectsChange = new Subject();
1600
1621
  this.subscription = new Subscription();
1601
1622
  this.currentSelection = [];
1602
- this.container = injector.get(VIEW_CONTAINER);
1623
+ this.container = textbus.get(VIEW_CONTAINER);
1603
1624
  this.canvasContainer.append(this.canvas);
1604
1625
  this.host.append(this.canvasContainer, this.tooltips);
1605
1626
  this.container.prepend(this.host);
@@ -1791,7 +1812,7 @@ let CollaborateCursor = class CollaborateCursor {
1791
1812
  CollaborateCursor = __decorate([
1792
1813
  Injectable(),
1793
1814
  __param(4, Optional()),
1794
- __metadata("design:paramtypes", [Injector,
1815
+ __metadata("design:paramtypes", [Textbus,
1795
1816
  SelectionBridge,
1796
1817
  Scheduler,
1797
1818
  Selection,
@@ -1912,7 +1933,7 @@ let NativeInput = class NativeInput extends Input {
1912
1933
  get disabled() {
1913
1934
  return this._disabled;
1914
1935
  }
1915
- constructor(injector, parser, scheduler, selection, keyboard, domAdapter, commander, controller) {
1936
+ constructor(textbus, parser, scheduler, selection, keyboard, domAdapter, commander, controller) {
1916
1937
  super();
1917
1938
  this.parser = parser;
1918
1939
  this.scheduler = scheduler;
@@ -1933,7 +1954,7 @@ let NativeInput = class NativeInput extends Input {
1933
1954
  this.isMac = isMac();
1934
1955
  this.isMobileBrowser = isMobileBrowser();
1935
1956
  this.ignoreComposition = false; // 有 bug 版本搜狗拼音
1936
- this.documentView = injector.get(VIEW_DOCUMENT);
1957
+ this.documentView = textbus.get(VIEW_DOCUMENT);
1937
1958
  if (!controller.readonly) {
1938
1959
  this.documentView.contentEditable = 'true';
1939
1960
  }
@@ -2014,9 +2035,9 @@ let NativeInput = class NativeInput extends Input {
2014
2035
  document.body.appendChild(div);
2015
2036
  div.focus();
2016
2037
  setTimeout(() => {
2017
- const html = div.innerHTML;
2018
- this.handlePaste(html, text);
2019
2038
  document.body.removeChild(div);
2039
+ div.style.cssText = '';
2040
+ this.handlePaste(div, text);
2020
2041
  });
2021
2042
  }));
2022
2043
  }
@@ -2264,7 +2285,7 @@ let NativeInput = class NativeInput extends Input {
2264
2285
  };
2265
2286
  NativeInput = __decorate([
2266
2287
  Injectable(),
2267
- __metadata("design:paramtypes", [Injector,
2288
+ __metadata("design:paramtypes", [Textbus,
2268
2289
  Parser,
2269
2290
  Scheduler,
2270
2291
  Selection,
@@ -2310,11 +2331,29 @@ class BrowserModule {
2310
2331
  }, {
2311
2332
  provide: DomAdapter,
2312
2333
  useValue: config.adapter
2334
+ }, {
2335
+ provide: FocusManager,
2336
+ useFactory: (input) => {
2337
+ const focusEvent = new Subject();
2338
+ const blurEvent = new Subject();
2339
+ input.caret.onPositionChange.pipe(map(p => !!p), distinctUntilChanged()).subscribe(b => {
2340
+ if (b) {
2341
+ focusEvent.next();
2342
+ }
2343
+ else {
2344
+ blurEvent.next();
2345
+ }
2346
+ });
2347
+ return {
2348
+ onFocus: focusEvent,
2349
+ onBlur: blurEvent
2350
+ };
2351
+ },
2352
+ deps: [Input]
2313
2353
  },
2314
2354
  Parser,
2315
2355
  SelectionBridge,
2316
- CollaborateCursor
2317
- ];
2356
+ CollaborateCursor];
2318
2357
  this.workbench = wrapper;
2319
2358
  this.host.append(wrapper);
2320
2359
  }
package/bundles/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  require('reflect-metadata');
4
4
  var core$1 = require('@textbus/core');
5
- var core = require('@viewfly/core');
6
5
  var stream = require('@tanbo/stream');
6
+ var core = require('@viewfly/core');
7
7
 
8
8
  function createElement(tagName, options = {}) {
9
9
  const el = document.createElement(tagName);
@@ -168,9 +168,9 @@ exports.Parser = Parser_1 = class Parser {
168
168
  static parseHTML(html) {
169
169
  return new DOMParser().parseFromString(html, 'text/html').body;
170
170
  }
171
- constructor(options, injector) {
171
+ constructor(options, textbus) {
172
172
  this.options = options;
173
- this.injector = injector;
173
+ this.textbus = textbus;
174
174
  const componentLoaders = [
175
175
  ...(options.componentLoaders || [])
176
176
  ];
@@ -189,23 +189,23 @@ exports.Parser = Parser_1 = class Parser {
189
189
  this.attributeLoaders = attributeLoaders;
190
190
  }
191
191
  /**
192
- * 使用指定的组件加载器解析一段 HTML 字符串
192
+ * 使用指定的组件加载器解析一段 HTML 字符串或 DOM 元素
193
193
  * @param html
194
194
  * @param rootComponentLoader
195
195
  */
196
196
  parseDoc(html, rootComponentLoader) {
197
- const element = Parser_1.parseHTML(html);
198
- return rootComponentLoader.read(element, this.injector, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
197
+ const element = typeof html === 'string' ? Parser_1.parseHTML(html) : html;
198
+ return rootComponentLoader.read(element, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
199
199
  return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
200
200
  });
201
201
  }
202
202
  /**
203
- * 将一段 HTML 解析到指定插槽
203
+ * 将一段 HTML 或 DOM 元素解析到指定插槽
204
204
  * @param html
205
205
  * @param rootSlot
206
206
  */
207
207
  parse(html, rootSlot) {
208
- const element = Parser_1.parseHTML(html);
208
+ const element = typeof html === 'string' ? Parser_1.parseHTML(html) : html;
209
209
  return this.readFormats(element, rootSlot);
210
210
  }
211
211
  readComponent(el, slot) {
@@ -216,7 +216,7 @@ exports.Parser = Parser_1 = class Parser {
216
216
  }
217
217
  for (const t of this.componentLoaders) {
218
218
  if (t.match(el)) {
219
- const result = t.read(el, this.injector, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
219
+ const result = t.read(el, this.textbus, (childSlot, slotRootElement, slotContentHostElement = slotRootElement) => {
220
220
  return this.readSlot(childSlot, slotRootElement, slotContentHostElement);
221
221
  });
222
222
  if (!result) {
@@ -296,14 +296,14 @@ exports.Parser = Parser_1 = class Parser {
296
296
  exports.Parser = Parser_1 = __decorate([
297
297
  core.Injectable(),
298
298
  __param(0, core.Inject(EDITOR_OPTIONS)),
299
- __metadata("design:paramtypes", [Object, core.Injector])
299
+ __metadata("design:paramtypes", [Object, core$1.Textbus])
300
300
  ], exports.Parser);
301
301
 
302
302
  class Input {
303
303
  }
304
304
 
305
305
  /**
306
- * Textbus PC 端浏览器渲染能力实现
306
+ * Textbus PC 端浏览器渲染能力桥接器抽象类,提供了 DOM 元素查询能力,具体渲染能力由各前端框架实现相应桥接
307
307
  */
308
308
  class DomAdapter extends core$1.ViewAdapter {
309
309
  constructor(mount) {
@@ -324,7 +324,9 @@ class DomAdapter extends core$1.ViewAdapter {
324
324
  id: 'textbus-' + Number((Math.random() + '').substring(2)).toString(16)
325
325
  }
326
326
  });
327
- this.componentRootElementCaches = new WeakMap();
327
+ this.componentRootElementCaches = core$1.createBidirectionalMapping(a => {
328
+ return a instanceof core$1.ComponentInstance;
329
+ });
328
330
  this.slotRootNativeElementCaches = core$1.createBidirectionalMapping(a => {
329
331
  return a instanceof core$1.Slot;
330
332
  });
@@ -337,12 +339,34 @@ class DomAdapter extends core$1.ViewAdapter {
337
339
  copy() {
338
340
  document.execCommand('copy');
339
341
  }
342
+ /**
343
+ * 根据组件获取组件的根 DOM 节点
344
+ * @param component
345
+ */
340
346
  getNativeNodeByComponent(component) {
341
347
  return this.componentRootElementCaches.get(component) || null;
342
348
  }
349
+ /**
350
+ * 根据 DOM 节点,获对对应的组件根节点,如传入的 DOM 节点不为组件的根节点,则返回 null
351
+ * @param node
352
+ */
353
+ getComponentByNativeNode(node) {
354
+ return this.componentRootElementCaches.get(node) || null;
355
+ }
356
+ /**
357
+ * 根据插槽获取插槽的根 DOM 节点
358
+ * @param slot
359
+ */
343
360
  getNativeNodeBySlot(slot) {
344
361
  return this.slotRootNativeElementCaches.get(slot) || null;
345
362
  }
363
+ /**
364
+ * 根据 DOM 节点,获对对应的插槽根节点,如传入的 DOM 节点不为插槽的根节点,则返回 null
365
+ * @param node
366
+ */
367
+ getSlotByNativeNode(node) {
368
+ return this.slotRootNativeElementCaches.get(node) || null;
369
+ }
346
370
  /**
347
371
  * 获取插槽内容节点集合
348
372
  * @param slot
@@ -427,7 +451,7 @@ class DomAdapter extends core$1.ViewAdapter {
427
451
  * Textbus PC 端选区桥接实现
428
452
  */
429
453
  exports.SelectionBridge = class SelectionBridge {
430
- constructor(config, injector, controller, selection, rootComponentRef, input, domAdapter) {
454
+ constructor(config, textbus, controller, selection, rootComponentRef, input, domAdapter) {
431
455
  this.config = config;
432
456
  this.selection = selection;
433
457
  this.rootComponentRef = rootComponentRef;
@@ -439,7 +463,7 @@ exports.SelectionBridge = class SelectionBridge {
439
463
  this.connector = null;
440
464
  this.ignoreSelectionChange = false;
441
465
  this.changeFromUser = false;
442
- this.docContainer = injector.get(VIEW_DOCUMENT);
466
+ this.docContainer = textbus.get(VIEW_DOCUMENT);
443
467
  this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(stream.filter(() => {
444
468
  return !controller.readonly;
445
469
  }));
@@ -968,7 +992,7 @@ exports.SelectionBridge = class SelectionBridge {
968
992
  exports.SelectionBridge = __decorate([
969
993
  core.Injectable(),
970
994
  __param(0, core.Inject(EDITOR_OPTIONS)),
971
- __metadata("design:paramtypes", [Object, core.Injector,
995
+ __metadata("design:paramtypes", [Object, core$1.Textbus,
972
996
  core$1.Controller,
973
997
  core$1.Selection,
974
998
  core$1.RootComponentRef,
@@ -1224,7 +1248,7 @@ exports.MagicInput = class MagicInput extends Input {
1224
1248
  get disabled() {
1225
1249
  return this._disabled;
1226
1250
  }
1227
- constructor(parser, keyboard, commander, selection, controller, scheduler, injector) {
1251
+ constructor(parser, keyboard, commander, selection, controller, scheduler, textbus) {
1228
1252
  super();
1229
1253
  this.parser = parser;
1230
1254
  this.keyboard = keyboard;
@@ -1232,10 +1256,10 @@ exports.MagicInput = class MagicInput extends Input {
1232
1256
  this.selection = selection;
1233
1257
  this.controller = controller;
1234
1258
  this.scheduler = scheduler;
1235
- this.injector = injector;
1259
+ this.textbus = textbus;
1236
1260
  this.composition = false;
1237
1261
  this.compositionState = null;
1238
- this.caret = new ExperimentalCaret(this.scheduler, this.injector.get(VIEW_MASK));
1262
+ this.caret = new ExperimentalCaret(this.scheduler, this.textbus.get(VIEW_MASK));
1239
1263
  this.isSafari = isSafari();
1240
1264
  this.isFirefox = isFirefox();
1241
1265
  this.isMac = isMac();
@@ -1366,17 +1390,14 @@ exports.MagicInput = class MagicInput extends Input {
1366
1390
  this.doc.body.appendChild(div);
1367
1391
  div.focus();
1368
1392
  setTimeout(() => {
1369
- let html = div.innerHTML;
1370
- if (!html && text && this.isFirefox) {
1371
- html = text;
1372
- }
1373
- this.handlePaste(html, text);
1374
1393
  this.doc.body.removeChild(div);
1394
+ div.style.cssText = '';
1395
+ this.handlePaste(div, text);
1375
1396
  });
1376
1397
  }));
1377
1398
  }
1378
- handlePaste(html, text) {
1379
- const slot = this.parser.parse(html, new core$1.Slot([
1399
+ handlePaste(dom, text) {
1400
+ const slot = this.parser.parse(dom, new core$1.Slot([
1380
1401
  core$1.ContentType.BlockComponent,
1381
1402
  core$1.ContentType.InlineComponent,
1382
1403
  core$1.ContentType.Text
@@ -1536,7 +1557,7 @@ exports.MagicInput = __decorate([
1536
1557
  core$1.Selection,
1537
1558
  core$1.Controller,
1538
1559
  core$1.Scheduler,
1539
- core.Injector])
1560
+ core$1.Textbus])
1540
1561
  ], exports.MagicInput);
1541
1562
 
1542
1563
  /**
@@ -1548,7 +1569,7 @@ class CollaborateSelectionAwarenessDelegate {
1548
1569
  * 协作光标绘制类
1549
1570
  */
1550
1571
  exports.CollaborateCursor = class CollaborateCursor {
1551
- constructor(injector, nativeSelection, scheduler, selection, awarenessDelegate) {
1572
+ constructor(textbus, nativeSelection, scheduler, selection, awarenessDelegate) {
1552
1573
  this.nativeSelection = nativeSelection;
1553
1574
  this.scheduler = scheduler;
1554
1575
  this.selection = selection;
@@ -1601,7 +1622,7 @@ exports.CollaborateCursor = class CollaborateCursor {
1601
1622
  this.onRectsChange = new stream.Subject();
1602
1623
  this.subscription = new stream.Subscription();
1603
1624
  this.currentSelection = [];
1604
- this.container = injector.get(VIEW_CONTAINER);
1625
+ this.container = textbus.get(VIEW_CONTAINER);
1605
1626
  this.canvasContainer.append(this.canvas);
1606
1627
  this.host.append(this.canvasContainer, this.tooltips);
1607
1628
  this.container.prepend(this.host);
@@ -1793,7 +1814,7 @@ exports.CollaborateCursor = class CollaborateCursor {
1793
1814
  exports.CollaborateCursor = __decorate([
1794
1815
  core.Injectable(),
1795
1816
  __param(4, core.Optional()),
1796
- __metadata("design:paramtypes", [core.Injector,
1817
+ __metadata("design:paramtypes", [core$1.Textbus,
1797
1818
  exports.SelectionBridge,
1798
1819
  core$1.Scheduler,
1799
1820
  core$1.Selection,
@@ -1914,7 +1935,7 @@ let NativeInput = class NativeInput extends Input {
1914
1935
  get disabled() {
1915
1936
  return this._disabled;
1916
1937
  }
1917
- constructor(injector, parser, scheduler, selection, keyboard, domAdapter, commander, controller) {
1938
+ constructor(textbus, parser, scheduler, selection, keyboard, domAdapter, commander, controller) {
1918
1939
  super();
1919
1940
  this.parser = parser;
1920
1941
  this.scheduler = scheduler;
@@ -1935,7 +1956,7 @@ let NativeInput = class NativeInput extends Input {
1935
1956
  this.isMac = isMac();
1936
1957
  this.isMobileBrowser = isMobileBrowser();
1937
1958
  this.ignoreComposition = false; // 有 bug 版本搜狗拼音
1938
- this.documentView = injector.get(VIEW_DOCUMENT);
1959
+ this.documentView = textbus.get(VIEW_DOCUMENT);
1939
1960
  if (!controller.readonly) {
1940
1961
  this.documentView.contentEditable = 'true';
1941
1962
  }
@@ -2016,9 +2037,9 @@ let NativeInput = class NativeInput extends Input {
2016
2037
  document.body.appendChild(div);
2017
2038
  div.focus();
2018
2039
  setTimeout(() => {
2019
- const html = div.innerHTML;
2020
- this.handlePaste(html, text);
2021
2040
  document.body.removeChild(div);
2041
+ div.style.cssText = '';
2042
+ this.handlePaste(div, text);
2022
2043
  });
2023
2044
  }));
2024
2045
  }
@@ -2266,7 +2287,7 @@ let NativeInput = class NativeInput extends Input {
2266
2287
  };
2267
2288
  NativeInput = __decorate([
2268
2289
  core.Injectable(),
2269
- __metadata("design:paramtypes", [core.Injector,
2290
+ __metadata("design:paramtypes", [core$1.Textbus,
2270
2291
  exports.Parser,
2271
2292
  core$1.Scheduler,
2272
2293
  core$1.Selection,
@@ -2312,11 +2333,29 @@ class BrowserModule {
2312
2333
  }, {
2313
2334
  provide: DomAdapter,
2314
2335
  useValue: config.adapter
2336
+ }, {
2337
+ provide: core$1.FocusManager,
2338
+ useFactory: (input) => {
2339
+ const focusEvent = new stream.Subject();
2340
+ const blurEvent = new stream.Subject();
2341
+ input.caret.onPositionChange.pipe(stream.map(p => !!p), stream.distinctUntilChanged()).subscribe(b => {
2342
+ if (b) {
2343
+ focusEvent.next();
2344
+ }
2345
+ else {
2346
+ blurEvent.next();
2347
+ }
2348
+ });
2349
+ return {
2350
+ onFocus: focusEvent,
2351
+ onBlur: blurEvent
2352
+ };
2353
+ },
2354
+ deps: [Input]
2315
2355
  },
2316
2356
  exports.Parser,
2317
2357
  exports.SelectionBridge,
2318
- exports.CollaborateCursor
2319
- ];
2358
+ exports.CollaborateCursor];
2320
2359
  this.workbench = wrapper;
2321
2360
  this.host.append(wrapper);
2322
2361
  }
@@ -1,6 +1,5 @@
1
1
  import { Observable } from '@tanbo/stream';
2
- import { Injector } from '@viewfly/core';
3
- import { Commander, Controller, Keyboard, Scheduler, Selection } from '@textbus/core';
2
+ import { Commander, Controller, Keyboard, Scheduler, Selection, Textbus } from '@textbus/core';
4
3
  import { Parser } from './parser';
5
4
  import { Caret, CaretPosition, CompositionState, Input, Scroller } from './types';
6
5
  interface CaretStyle {
@@ -48,7 +47,7 @@ export declare class MagicInput extends Input {
48
47
  private selection;
49
48
  private controller;
50
49
  private scheduler;
51
- private injector;
50
+ private textbus;
52
51
  composition: boolean;
53
52
  compositionState: CompositionState | null;
54
53
  onReady: Promise<void>;
@@ -67,7 +66,7 @@ export declare class MagicInput extends Input {
67
66
  private isFocus;
68
67
  private nativeFocus;
69
68
  private ignoreComposition;
70
- constructor(parser: Parser, keyboard: Keyboard, commander: Commander, selection: Selection, controller: Controller, scheduler: Scheduler, injector: Injector);
69
+ constructor(parser: Parser, keyboard: Keyboard, commander: Commander, selection: Selection, controller: Controller, scheduler: Scheduler, textbus: Textbus);
71
70
  focus(range: Range, restart: boolean): void;
72
71
  blur(): void;
73
72
  destroy(): void;
@@ -1,6 +1,5 @@
1
- import { Injector } from '@viewfly/core';
2
1
  import { Observable } from '@tanbo/stream';
3
- import { Commander, Controller, Keyboard, Scheduler, Selection } from '@textbus/core';
2
+ import { Commander, Controller, Keyboard, Scheduler, Selection, Textbus } from '@textbus/core';
4
3
  import { Caret, CaretPosition, CompositionState, Input, Scroller } from './types';
5
4
  import { Parser } from './parser';
6
5
  import { DomAdapter } from './dom-adapter';
@@ -42,7 +41,7 @@ export declare class NativeInput extends Input {
42
41
  private isMac;
43
42
  private isMobileBrowser;
44
43
  private ignoreComposition;
45
- constructor(injector: Injector, parser: Parser, scheduler: Scheduler, selection: Selection, keyboard: Keyboard, domAdapter: DomAdapter<any, any>, commander: Commander, controller: Controller);
44
+ constructor(textbus: Textbus, parser: Parser, scheduler: Scheduler, selection: Selection, keyboard: Keyboard, domAdapter: DomAdapter<any, any>, commander: Commander, controller: Controller);
46
45
  focus(nativeRange: Range): void;
47
46
  blur(): void;
48
47
  destroy(): void;
@@ -1,5 +1,4 @@
1
- import { Injector } from '@viewfly/core';
2
- import { Attribute, ComponentInstance, Formatter, FormatValue, Slot } from '@textbus/core';
1
+ import { Attribute, ComponentInstance, Formatter, FormatValue, Slot, Textbus } from '@textbus/core';
3
2
  import { ViewOptions } from './browser-module';
4
3
  /**
5
4
  * 插槽解析器
@@ -21,16 +20,16 @@ export interface ComponentLoader {
21
20
  /** 识别组件的匹配方法 */
22
21
  match(element: HTMLElement): boolean;
23
22
  /** 读取组件内容的方法 */
24
- read(element: HTMLElement, injector: Injector, slotParser: SlotParser): ComponentInstance | Slot | void;
23
+ read(element: HTMLElement, textbus: Textbus, slotParser: SlotParser): ComponentInstance | Slot | void;
25
24
  }
26
- export interface FormatLoaderReadResult<T extends FormatValue> {
25
+ export interface FormatLoaderReadResult<T = FormatValue> {
27
26
  formatter: Formatter<T>;
28
27
  value: T;
29
28
  }
30
29
  /**
31
30
  * 格式加载器
32
31
  */
33
- export interface FormatLoader<T extends FormatValue> {
32
+ export interface FormatLoader<T = FormatValue> {
34
33
  /**
35
34
  * 匹配一个 DOM 节点是否是某个格式节点
36
35
  * @param element
@@ -42,14 +41,14 @@ export interface FormatLoader<T extends FormatValue> {
42
41
  */
43
42
  read(element: HTMLElement): FormatLoaderReadResult<T>;
44
43
  }
45
- export interface AttributeLoaderReadResult<T extends FormatValue> {
44
+ export interface AttributeLoaderReadResult<T = FormatValue> {
46
45
  attribute: Attribute<T>;
47
46
  value: T;
48
47
  }
49
48
  /**
50
49
  * 属性加载器
51
50
  */
52
- export interface AttributeLoader<T extends FormatValue> {
51
+ export interface AttributeLoader<T = FormatValue> {
53
52
  /**
54
53
  * 匹配一个 DOM 节点是否是某个属性节点
55
54
  * @param element
@@ -66,24 +65,24 @@ export interface AttributeLoader<T extends FormatValue> {
66
65
  */
67
66
  export declare class Parser {
68
67
  private options;
69
- private injector;
68
+ private textbus;
70
69
  static parseHTML(html: string): HTMLElement;
71
70
  componentLoaders: ComponentLoader[];
72
71
  formatLoaders: FormatLoader<any>[];
73
72
  attributeLoaders: AttributeLoader<any>[];
74
- constructor(options: ViewOptions, injector: Injector);
73
+ constructor(options: ViewOptions, textbus: Textbus);
75
74
  /**
76
- * 使用指定的组件加载器解析一段 HTML 字符串
75
+ * 使用指定的组件加载器解析一段 HTML 字符串或 DOM 元素
77
76
  * @param html
78
77
  * @param rootComponentLoader
79
78
  */
80
- parseDoc(html: string, rootComponentLoader: ComponentLoader): void | Slot<any> | ComponentInstance<unknown, unknown, unknown>;
79
+ parseDoc(html: string | HTMLElement, rootComponentLoader: ComponentLoader): void | Slot<any> | ComponentInstance<unknown, unknown, unknown>;
81
80
  /**
82
- * 将一段 HTML 解析到指定插槽
81
+ * 将一段 HTML 或 DOM 元素解析到指定插槽
83
82
  * @param html
84
83
  * @param rootSlot
85
84
  */
86
- parse(html: string, rootSlot: Slot): Slot<any>;
85
+ parse(html: string | HTMLElement, rootSlot: Slot): Slot<any>;
87
86
  private readComponent;
88
87
  private readText;
89
88
  private readFormats;
@@ -1,6 +1,5 @@
1
1
  import { Observable } from '@tanbo/stream';
2
- import { Injector } from '@viewfly/core';
3
- import { NativeSelectionBridge, NativeSelectionConnector, SelectionPosition, AbstractSelection, RootComponentRef, Controller, Selection } from '@textbus/core';
2
+ import { NativeSelectionBridge, NativeSelectionConnector, SelectionPosition, AbstractSelection, RootComponentRef, Controller, Selection, Textbus } from '@textbus/core';
4
3
  import { Rect } from './_utils/uikit';
5
4
  import { Input } from './types';
6
5
  import { DomAdapter } from './dom-adapter';
@@ -25,7 +24,7 @@ export declare class SelectionBridge implements NativeSelectionBridge {
25
24
  private docContainer;
26
25
  private cacheCaretPositionTimer;
27
26
  private oldCaretPosition;
28
- constructor(config: ViewOptions, injector: Injector, controller: Controller, selection: Selection, rootComponentRef: RootComponentRef, input: Input, domAdapter: DomAdapter<any, any>);
27
+ constructor(config: ViewOptions, textbus: Textbus, controller: Controller, selection: Selection, rootComponentRef: RootComponentRef, input: Input, domAdapter: DomAdapter<any, any>);
29
28
  connect(connector: NativeSelectionConnector): void;
30
29
  disConnect(): void;
31
30
  getRect(location: SelectionPosition): Rect | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textbus/platform-browser",
3
- "version": "4.0.0-alpha.0",
3
+ "version": "4.0.0-alpha.10",
4
4
  "description": "Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.",
5
5
  "main": "./bundles/index.js",
6
6
  "module": "./bundles/index.esm.js",
@@ -26,8 +26,8 @@
26
26
  ],
27
27
  "dependencies": {
28
28
  "@tanbo/stream": "^1.2.0",
29
- "@textbus/core": "^4.0.0-alpha.0",
30
- "@viewfly/core": "^0.1.0",
29
+ "@textbus/core": "^4.0.0-alpha.10",
30
+ "@viewfly/core": "^0.3.0",
31
31
  "reflect-metadata": "^0.1.13"
32
32
  },
33
33
  "devDependencies": {
@@ -48,5 +48,5 @@
48
48
  "bugs": {
49
49
  "url": "https://github.com/textbus/textbus.git/issues"
50
50
  },
51
- "gitHead": "cf4fd289b73bc777124a32fe42bb58eba05a34f1"
51
+ "gitHead": "f3e2e1a0075d4355c954939dae1d267f8571fb7f"
52
52
  }