editor.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import { Point } from '@antv/x6-geometry';
  2. import { Dom, FunctionExt, NumberExt, ObjectExt } from '@antv/x6-common';
  3. import { ToolsView } from '../../view/tool';
  4. import { Util } from '../../util';
  5. export class CellEditor extends ToolsView.ToolItem {
  6. constructor() {
  7. super(...arguments);
  8. this.labelIndex = -1;
  9. this.distance = 0.5;
  10. this.dblClick = this.onCellDblClick.bind(this);
  11. }
  12. onRender() {
  13. const cellView = this.cellView;
  14. if (cellView) {
  15. cellView.on('cell:dblclick', this.dblClick);
  16. }
  17. }
  18. createElement() {
  19. const classNames = [
  20. this.prefixClassName(`${this.cell.isEdge() ? 'edge' : 'node'}-tool-editor`),
  21. this.prefixClassName('cell-tool-editor'),
  22. ];
  23. this.editor = ToolsView.createElement('div', false);
  24. this.addClass(classNames, this.editor);
  25. this.editor.contentEditable = 'true';
  26. this.container.appendChild(this.editor);
  27. }
  28. removeElement() {
  29. this.undelegateDocumentEvents();
  30. if (this.editor) {
  31. this.container.removeChild(this.editor);
  32. this.editor = null;
  33. }
  34. }
  35. updateEditor() {
  36. const { cell, editor } = this;
  37. if (!editor) {
  38. return;
  39. }
  40. const { style } = editor;
  41. if (cell.isNode()) {
  42. this.updateNodeEditorTransform();
  43. }
  44. else if (cell.isEdge()) {
  45. this.updateEdgeEditorTransform();
  46. }
  47. // set font style
  48. const { attrs } = this.options;
  49. style.fontSize = `${attrs.fontSize}px`;
  50. style.fontFamily = attrs.fontFamily;
  51. style.color = attrs.color;
  52. style.backgroundColor = attrs.backgroundColor;
  53. // set init value
  54. const text = this.getCellText() || '';
  55. editor.innerText = text;
  56. this.setCellText(''); // clear display value when edit status because char ghosting.
  57. return this;
  58. }
  59. updateNodeEditorTransform() {
  60. const { graph, cell, editor } = this;
  61. if (!editor) {
  62. return;
  63. }
  64. let pos = Point.create();
  65. let minWidth = 20;
  66. let translate = '';
  67. let { x, y } = this.options;
  68. const { width, height } = this.options;
  69. if (typeof x !== 'undefined' && typeof y !== 'undefined') {
  70. const bbox = cell.getBBox();
  71. x = NumberExt.normalizePercentage(x, bbox.width);
  72. y = NumberExt.normalizePercentage(y, bbox.height);
  73. pos = bbox.topLeft.translate(x, y);
  74. minWidth = bbox.width - x * 2;
  75. }
  76. else {
  77. const bbox = cell.getBBox();
  78. pos = bbox.center;
  79. minWidth = bbox.width - 4;
  80. translate = 'translate(-50%, -50%)';
  81. }
  82. const scale = graph.scale();
  83. const { style } = editor;
  84. pos = graph.localToGraph(pos);
  85. style.left = `${pos.x}px`;
  86. style.top = `${pos.y}px`;
  87. style.transform = `scale(${scale.sx}, ${scale.sy}) ${translate}`;
  88. style.minWidth = `${minWidth}px`;
  89. if (typeof width === 'number') {
  90. style.width = `${width}px`;
  91. }
  92. if (typeof height === 'number') {
  93. style.height = `${height}px`;
  94. }
  95. }
  96. updateEdgeEditorTransform() {
  97. if (!this.event) {
  98. return;
  99. }
  100. const { graph, editor } = this;
  101. if (!editor) {
  102. return;
  103. }
  104. let pos = Point.create();
  105. let minWidth = 20;
  106. const { style } = editor;
  107. const target = this.event.target;
  108. const parent = target.parentElement;
  109. const isEdgeLabel = parent && Dom.hasClass(parent, this.prefixClassName('edge-label'));
  110. if (isEdgeLabel) {
  111. const index = parent.getAttribute('data-index') || '0';
  112. this.labelIndex = parseInt(index, 10);
  113. const matrix = parent.getAttribute('transform');
  114. const { translation } = Dom.parseTransformString(matrix);
  115. pos = new Point(translation.tx, translation.ty);
  116. minWidth = Util.getBBox(target).width;
  117. }
  118. else {
  119. if (!this.options.labelAddable) {
  120. return this;
  121. }
  122. pos = graph.clientToLocal(Point.create(this.event.clientX, this.event.clientY));
  123. const view = this.cellView;
  124. const d = view.path.closestPointLength(pos);
  125. this.distance = d;
  126. this.labelIndex = -1;
  127. }
  128. pos = graph.localToGraph(pos);
  129. const scale = graph.scale();
  130. style.left = `${pos.x}px`;
  131. style.top = `${pos.y}px`;
  132. style.minWidth = `${minWidth}px`;
  133. style.transform = `scale(${scale.sx}, ${scale.sy}) translate(-50%, -50%)`;
  134. }
  135. onDocumentMouseUp(e) {
  136. if (this.editor && e.target !== this.editor) {
  137. const value = this.editor.innerText.replace(/\n$/, '') || '';
  138. // set value, when value is null, we will remove label in edge
  139. this.setCellText(value !== '' ? value : null);
  140. // remove tool
  141. this.removeElement();
  142. }
  143. }
  144. onCellDblClick({ e }) {
  145. if (!this.editor) {
  146. e.stopPropagation();
  147. this.removeElement();
  148. this.event = e;
  149. this.createElement();
  150. this.updateEditor();
  151. this.autoFocus();
  152. this.delegateDocumentEvents(this.options.documentEvents);
  153. }
  154. }
  155. onMouseDown(e) {
  156. e.stopPropagation();
  157. }
  158. autoFocus() {
  159. setTimeout(() => {
  160. if (this.editor) {
  161. this.editor.focus();
  162. this.selectText();
  163. }
  164. });
  165. }
  166. selectText() {
  167. if (window.getSelection && this.editor) {
  168. const range = document.createRange();
  169. const selection = window.getSelection();
  170. range.selectNodeContents(this.editor);
  171. selection.removeAllRanges();
  172. selection.addRange(range);
  173. }
  174. }
  175. getCellText() {
  176. const { getText } = this.options;
  177. if (typeof getText === 'function') {
  178. return FunctionExt.call(getText, this.cellView, {
  179. cell: this.cell,
  180. index: this.labelIndex,
  181. });
  182. }
  183. if (typeof getText === 'string') {
  184. if (this.cell.isNode()) {
  185. return this.cell.attr(getText);
  186. }
  187. if (this.cell.isEdge()) {
  188. if (this.labelIndex !== -1) {
  189. return this.cell.prop(`labels/${this.labelIndex}/attrs/${getText}`);
  190. }
  191. }
  192. }
  193. }
  194. setCellText(value) {
  195. const setText = this.options.setText;
  196. if (typeof setText === 'function') {
  197. FunctionExt.call(setText, this.cellView, {
  198. cell: this.cell,
  199. value,
  200. index: this.labelIndex,
  201. distance: this.distance,
  202. });
  203. return;
  204. }
  205. if (typeof setText === 'string') {
  206. if (this.cell.isNode()) {
  207. if (value !== null) {
  208. this.cell.attr(setText, value);
  209. }
  210. return;
  211. }
  212. if (this.cell.isEdge()) {
  213. const edge = this.cell;
  214. if (this.labelIndex === -1) {
  215. if (value) {
  216. const newLabel = {
  217. position: {
  218. distance: this.distance,
  219. },
  220. attrs: {},
  221. };
  222. ObjectExt.setByPath(newLabel, `attrs/${setText}`, value);
  223. edge.appendLabel(newLabel);
  224. }
  225. }
  226. else {
  227. if (value !== null) {
  228. edge.prop(`labels/${this.labelIndex}/attrs/${setText}`, value);
  229. }
  230. else if (typeof this.labelIndex === 'number') {
  231. edge.removeLabelAt(this.labelIndex);
  232. }
  233. }
  234. }
  235. }
  236. }
  237. onRemove() {
  238. const cellView = this.cellView;
  239. if (cellView) {
  240. cellView.off('cell:dblclick', this.dblClick);
  241. }
  242. this.removeElement();
  243. }
  244. }
  245. (function (CellEditor) {
  246. CellEditor.config({
  247. tagName: 'div',
  248. isSVGElement: false,
  249. events: {
  250. mousedown: 'onMouseDown',
  251. touchstart: 'onMouseDown',
  252. },
  253. documentEvents: {
  254. mouseup: 'onDocumentMouseUp',
  255. touchend: 'onDocumentMouseUp',
  256. touchcancel: 'onDocumentMouseUp',
  257. },
  258. });
  259. })(CellEditor || (CellEditor = {}));
  260. (function (CellEditor) {
  261. CellEditor.NodeEditor = CellEditor.define({
  262. attrs: {
  263. fontSize: 14,
  264. fontFamily: 'Arial, helvetica, sans-serif',
  265. color: '#000',
  266. backgroundColor: '#fff',
  267. },
  268. getText: 'text/text',
  269. setText: 'text/text',
  270. });
  271. CellEditor.EdgeEditor = CellEditor.define({
  272. attrs: {
  273. fontSize: 14,
  274. fontFamily: 'Arial, helvetica, sans-serif',
  275. color: '#000',
  276. backgroundColor: '#fff',
  277. },
  278. labelAddable: true,
  279. getText: 'label/text',
  280. setText: 'label/text',
  281. });
  282. })(CellEditor || (CellEditor = {}));
  283. //# sourceMappingURL=editor.js.map