index.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import {
  2. Basecoat,
  3. ModifierKey,
  4. CssLoader,
  5. Dom,
  6. Cell,
  7. EventArgs,
  8. Graph,
  9. } from '@antv/x6'
  10. import { SelectionImpl } from './selection'
  11. // import { content } from './style/raw'
  12. import './api'
  13. import './index.less'
  14. export class Selection
  15. extends Basecoat<SelectionImpl.EventArgs>
  16. implements Graph.Plugin
  17. {
  18. public name = 'selection'
  19. private graph: Graph
  20. private selectionImpl: SelectionImpl
  21. private readonly options: Selection.Options
  22. private movedMap = new WeakMap<Cell, boolean>()
  23. private unselectMap = new WeakMap<Cell, boolean>()
  24. get rubberbandDisabled() {
  25. return this.options.enabled !== true || this.options.rubberband !== true
  26. }
  27. get disabled() {
  28. return this.options.enabled !== true
  29. }
  30. get length() {
  31. return this.selectionImpl.length
  32. }
  33. get cells() {
  34. return this.selectionImpl.cells
  35. }
  36. constructor(options: Selection.Options = {}) {
  37. super()
  38. this.options = {
  39. enabled: true,
  40. ...Selection.defaultOptions,
  41. ...options,
  42. }
  43. // CssLoader.ensure(this.name, content)
  44. }
  45. public init(graph: Graph) {
  46. this.graph = graph
  47. this.selectionImpl = new SelectionImpl({
  48. ...this.options,
  49. graph,
  50. })
  51. this.setup()
  52. this.startListening()
  53. }
  54. // #region api
  55. isEnabled() {
  56. return !this.disabled
  57. }
  58. enable() {
  59. if (this.disabled) {
  60. this.options.enabled = true
  61. }
  62. }
  63. disable() {
  64. if (!this.disabled) {
  65. this.options.enabled = false
  66. }
  67. }
  68. toggleEnabled(enabled?: boolean) {
  69. if (enabled != null) {
  70. if (enabled !== this.isEnabled()) {
  71. if (enabled) {
  72. this.enable()
  73. } else {
  74. this.disable()
  75. }
  76. }
  77. } else if (this.isEnabled()) {
  78. this.disable()
  79. } else {
  80. this.enable()
  81. }
  82. return this
  83. }
  84. isMultipleSelection() {
  85. return this.isMultiple()
  86. }
  87. enableMultipleSelection() {
  88. this.enableMultiple()
  89. return this
  90. }
  91. disableMultipleSelection() {
  92. this.disableMultiple()
  93. return this
  94. }
  95. toggleMultipleSelection(multiple?: boolean) {
  96. if (multiple != null) {
  97. if (multiple !== this.isMultipleSelection()) {
  98. if (multiple) {
  99. this.enableMultipleSelection()
  100. } else {
  101. this.disableMultipleSelection()
  102. }
  103. }
  104. } else if (this.isMultipleSelection()) {
  105. this.disableMultipleSelection()
  106. } else {
  107. this.enableMultipleSelection()
  108. }
  109. return this
  110. }
  111. isSelectionMovable() {
  112. return this.options.movable !== false
  113. }
  114. enableSelectionMovable() {
  115. this.selectionImpl.options.movable = true
  116. return this
  117. }
  118. disableSelectionMovable() {
  119. this.selectionImpl.options.movable = false
  120. return this
  121. }
  122. toggleSelectionMovable(movable?: boolean) {
  123. if (movable != null) {
  124. if (movable !== this.isSelectionMovable()) {
  125. if (movable) {
  126. this.enableSelectionMovable()
  127. } else {
  128. this.disableSelectionMovable()
  129. }
  130. }
  131. } else if (this.isSelectionMovable()) {
  132. this.disableSelectionMovable()
  133. } else {
  134. this.enableSelectionMovable()
  135. }
  136. return this
  137. }
  138. isRubberbandEnabled() {
  139. return !this.rubberbandDisabled
  140. }
  141. enableRubberband() {
  142. if (this.rubberbandDisabled) {
  143. this.options.rubberband = true
  144. }
  145. return this
  146. }
  147. disableRubberband() {
  148. if (!this.rubberbandDisabled) {
  149. this.options.rubberband = false
  150. }
  151. return this
  152. }
  153. toggleRubberband(enabled?: boolean) {
  154. if (enabled != null) {
  155. if (enabled !== this.isRubberbandEnabled()) {
  156. if (enabled) {
  157. this.enableRubberband()
  158. } else {
  159. this.disableRubberband()
  160. }
  161. }
  162. } else if (this.isRubberbandEnabled()) {
  163. this.disableRubberband()
  164. } else {
  165. this.enableRubberband()
  166. }
  167. return this
  168. }
  169. isStrictRubberband() {
  170. return this.selectionImpl.options.strict === true
  171. }
  172. enableStrictRubberband() {
  173. this.selectionImpl.options.strict = true
  174. return this
  175. }
  176. disableStrictRubberband() {
  177. this.selectionImpl.options.strict = false
  178. return this
  179. }
  180. toggleStrictRubberband(strict?: boolean) {
  181. if (strict != null) {
  182. if (strict !== this.isStrictRubberband()) {
  183. if (strict) {
  184. this.enableStrictRubberband()
  185. } else {
  186. this.disableStrictRubberband()
  187. }
  188. }
  189. } else if (this.isStrictRubberband()) {
  190. this.disableStrictRubberband()
  191. } else {
  192. this.enableStrictRubberband()
  193. }
  194. return this
  195. }
  196. setRubberbandModifiers(modifiers?: string | ModifierKey[] | null) {
  197. this.setModifiers(modifiers)
  198. }
  199. setSelectionFilter(filter?: Selection.Filter) {
  200. this.setFilter(filter)
  201. return this
  202. }
  203. setSelectionDisplayContent(content?: Selection.Content) {
  204. this.setContent(content)
  205. return this
  206. }
  207. isEmpty() {
  208. return this.length <= 0
  209. }
  210. clean(options: Selection.SetOptions = {}) {
  211. this.selectionImpl.clean(options)
  212. return this
  213. }
  214. reset(
  215. cells?: Cell | string | (Cell | string)[],
  216. options: Selection.SetOptions = {},
  217. ) {
  218. this.selectionImpl.reset(cells ? this.getCells(cells) : [], options)
  219. return this
  220. }
  221. getSelectedCells() {
  222. return this.cells
  223. }
  224. getSelectedCellCount() {
  225. return this.length
  226. }
  227. isSelected(cell: Cell | string) {
  228. return this.selectionImpl.isSelected(cell)
  229. }
  230. select(
  231. cells: Cell | string | (Cell | string)[],
  232. options: Selection.AddOptions = {},
  233. ) {
  234. const selected = this.getCells(cells)
  235. if (selected.length) {
  236. if (this.isMultiple()) {
  237. this.selectionImpl.select(selected, options)
  238. } else {
  239. this.reset(selected.slice(0, 1), options)
  240. }
  241. }
  242. return this
  243. }
  244. unselect(
  245. cells: Cell | string | (Cell | string)[],
  246. options: Selection.RemoveOptions = {},
  247. ) {
  248. this.selectionImpl.unselect(this.getCells(cells), options)
  249. return this
  250. }
  251. // #endregion
  252. protected setup() {
  253. this.selectionImpl.on('*', (name, args) => {
  254. this.trigger(name, args)
  255. this.graph.trigger(name, args)
  256. })
  257. }
  258. protected startListening() {
  259. this.graph.on('blank:mousedown', this.onBlankMouseDown, this)
  260. this.graph.on('node:mousedown', this.onBlankMouseDown, this)
  261. this.graph.on('blank:click', this.onBlankClick, this)
  262. this.graph.on('node:click', this.onBlankClick, this)
  263. this.graph.on('cell:mousemove', this.onCellMouseMove, this)
  264. this.graph.on('cell:mouseup', this.onCellMouseUp, this)
  265. this.selectionImpl.on('box:mousedown', this.onBoxMouseDown, this)
  266. }
  267. protected stopListening() {
  268. this.graph.off('blank:mousedown', this.onBlankMouseDown, this)
  269. this.graph.off('node:mousedown', this.onBlankMouseDown, this)
  270. this.graph.off('blank:click', this.onBlankClick, this)
  271. this.graph.off('node:click', this.onBlankClick, this)
  272. this.graph.off('cell:mousemove', this.onCellMouseMove, this)
  273. this.graph.off('cell:mouseup', this.onCellMouseUp, this)
  274. this.selectionImpl.off('box:mousedown', this.onBoxMouseDown, this)
  275. }
  276. protected onBlankMouseDown({ e, node }: EventArgs['node:mousedown']) {
  277. if (node && !node?.data?.isPage) {
  278. return
  279. }
  280. if (!this.allowBlankMouseDown(e)) {
  281. return
  282. }
  283. const allowGraphPanning = this.graph.panning.allowPanning(e, true)
  284. const scroller = this.graph.getPlugin<any>('scroller')
  285. const allowScrollerPanning = scroller && scroller.allowPanning(e, true)
  286. if (
  287. this.allowRubberband(e, true) ||
  288. (this.allowRubberband(e) && !allowScrollerPanning && !allowGraphPanning)
  289. ) {
  290. this.startRubberband(e)
  291. }
  292. }
  293. protected allowBlankMouseDown(e: Dom.MouseDownEvent) {
  294. const eventTypes = this.options.eventTypes
  295. return (
  296. (eventTypes?.includes('leftMouseDown') && e.button === 0) ||
  297. (eventTypes?.includes('mouseWheelDown') && e.button === 1)
  298. )
  299. }
  300. protected onBlankClick(args: EventArgs['node:click']) {
  301. if(!args?.cell || args?.cell?.data?.isPage) {
  302. this.clean();
  303. }
  304. }
  305. protected allowRubberband(e: Dom.MouseDownEvent, strict?: boolean) {
  306. return (
  307. !this.rubberbandDisabled &&
  308. ModifierKey.isMatch(e, this.options.modifiers, strict)
  309. )
  310. }
  311. protected allowMultipleSelection(e: Dom.MouseDownEvent | Dom.MouseUpEvent) {
  312. return (
  313. this.isMultiple() &&
  314. ModifierKey.isMatch(e, this.options.multipleSelectionModifiers)
  315. )
  316. }
  317. protected onCellMouseMove({ cell }: EventArgs['cell:mousemove']) {
  318. this.movedMap.set(cell, true)
  319. }
  320. protected onCellMouseUp({ e, cell }: EventArgs['cell:mouseup']) {
  321. const options = this.options
  322. let disabled = this.disabled
  323. if (!disabled && this.movedMap.has(cell)) {
  324. disabled = options.selectCellOnMoved === false
  325. if (!disabled) {
  326. disabled = options.selectNodeOnMoved === false && cell.isNode()
  327. }
  328. if (!disabled) {
  329. disabled = options.selectEdgeOnMoved === false && cell.isEdge()
  330. }
  331. }
  332. if (!disabled) {
  333. if (!this.allowMultipleSelection(e)) {
  334. this.reset(cell)
  335. } else if (this.unselectMap.has(cell)) {
  336. this.unselectMap.delete(cell)
  337. } else if (this.isSelected(cell)) {
  338. this.unselect(cell)
  339. } else {
  340. this.select(cell)
  341. }
  342. }
  343. this.movedMap.delete(cell)
  344. }
  345. protected onBoxMouseDown({
  346. e,
  347. cell,
  348. }: SelectionImpl.EventArgs['box:mousedown']) {
  349. if (!this.disabled) {
  350. if (this.allowMultipleSelection(e)) {
  351. this.unselect(cell)
  352. this.unselectMap.set(cell, true)
  353. }
  354. }
  355. }
  356. protected getCells(cells: Cell | string | (Cell | string)[]) {
  357. return (Array.isArray(cells) ? cells : [cells])
  358. .map((cell) =>
  359. typeof cell === 'string' ? this.graph.getCellById(cell) : cell,
  360. )
  361. .filter((cell) => cell != null)
  362. }
  363. protected startRubberband(e: Dom.MouseDownEvent) {
  364. if (!this.rubberbandDisabled) {
  365. this.selectionImpl.startSelecting(e)
  366. }
  367. return this
  368. }
  369. protected isMultiple() {
  370. return this.options.multiple !== false
  371. }
  372. protected enableMultiple() {
  373. this.options.multiple = true
  374. return this
  375. }
  376. protected disableMultiple() {
  377. this.options.multiple = false
  378. return this
  379. }
  380. protected setModifiers(modifiers?: string | ModifierKey[] | null) {
  381. this.options.modifiers = modifiers
  382. return this
  383. }
  384. protected setContent(content?: Selection.Content) {
  385. this.selectionImpl.setContent(content)
  386. return this
  387. }
  388. protected setFilter(filter?: Selection.Filter) {
  389. this.selectionImpl.setFilter(filter)
  390. return this
  391. }
  392. @Basecoat.dispose()
  393. dispose() {
  394. this.stopListening()
  395. this.off()
  396. this.selectionImpl.dispose()
  397. CssLoader.clean(this.name)
  398. }
  399. }
  400. export namespace Selection {
  401. export interface EventArgs extends SelectionImpl.EventArgs {}
  402. export interface Options extends SelectionImpl.CommonOptions {
  403. enabled?: boolean
  404. }
  405. export type Filter = SelectionImpl.Filter
  406. export type Content = SelectionImpl.Content
  407. export type SetOptions = SelectionImpl.SetOptions
  408. export type AddOptions = SelectionImpl.AddOptions
  409. export type RemoveOptions = SelectionImpl.RemoveOptions
  410. export const defaultOptions: Partial<SelectionImpl.Options> = {
  411. rubberband: false,
  412. rubberNode: true,
  413. rubberEdge: false, // next version will set to true
  414. pointerEvents: 'auto',
  415. multiple: true,
  416. multipleSelectionModifiers: ['ctrl', 'meta'],
  417. movable: true,
  418. strict: false,
  419. selectCellOnMoved: false,
  420. selectNodeOnMoved: false,
  421. selectEdgeOnMoved: false,
  422. following: true,
  423. content: null,
  424. eventTypes: ['leftMouseDown', 'mouseWheelDown'],
  425. }
  426. }