版博士V2.0程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

437 lines
14 KiB

  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. const Event_1 = __importDefault(require("../event/Event"));
  7. const DOMException_1 = __importDefault(require("../exception/DOMException"));
  8. const DOMExceptionNameEnum_1 = __importDefault(require("../exception/DOMExceptionNameEnum"));
  9. const NodeTypeEnum_1 = __importDefault(require("../nodes/node/NodeTypeEnum"));
  10. const NodeUtility_1 = __importDefault(require("../nodes/node/NodeUtility"));
  11. const Range_1 = __importDefault(require("../range/Range"));
  12. const RangeUtility_1 = __importDefault(require("../range/RangeUtility"));
  13. const SelectionDirectionEnum_1 = __importDefault(require("./SelectionDirectionEnum"));
  14. /**
  15. * Selection.
  16. *
  17. * Based on logic from:
  18. * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/selection/Selection-impl.js
  19. *
  20. * Reference:
  21. * https://developer.mozilla.org/en-US/docs/Web/API/Selection.
  22. */
  23. class Selection {
  24. /**
  25. * Constructor.
  26. *
  27. * @param ownerDocument Owner document.
  28. */
  29. constructor(ownerDocument) {
  30. this._ownerDocument = null;
  31. this._range = null;
  32. this._direction = SelectionDirectionEnum_1.default.directionless;
  33. this._ownerDocument = ownerDocument;
  34. }
  35. /**
  36. * Returns range count.
  37. *
  38. * @see https://w3c.github.io/selection-api/#dom-selection-rangecount
  39. * @returns Range count.
  40. */
  41. get rangeCount() {
  42. return this._range ? 1 : 0;
  43. }
  44. /**
  45. * Returns collapsed state.
  46. *
  47. * @see https://w3c.github.io/selection-api/#dom-selection-iscollapsed
  48. * @returns "true" if collapsed.
  49. */
  50. get isCollapsed() {
  51. return this._range === null || this._range.collapsed;
  52. }
  53. /**
  54. * Returns type.
  55. *
  56. * @see https://w3c.github.io/selection-api/#dom-selection-type
  57. * @returns Type.
  58. */
  59. get type() {
  60. if (!this._range) {
  61. return 'None';
  62. }
  63. else if (this._range.collapsed) {
  64. return 'Caret';
  65. }
  66. return 'Range';
  67. }
  68. /**
  69. * Returns anchor node.
  70. *
  71. * @see https://w3c.github.io/selection-api/#dom-selection-anchornode
  72. * @returns Node.
  73. */
  74. get anchorNode() {
  75. if (!this._range) {
  76. return null;
  77. }
  78. return this._direction === SelectionDirectionEnum_1.default.forwards
  79. ? this._range.startContainer
  80. : this._range.endContainer;
  81. }
  82. /**
  83. * Returns anchor offset.
  84. *
  85. * @see https://w3c.github.io/selection-api/#dom-selection-anchoroffset
  86. * @returns Node.
  87. */
  88. get anchorOffset() {
  89. if (!this._range) {
  90. return null;
  91. }
  92. return this._direction === SelectionDirectionEnum_1.default.forwards
  93. ? this._range.startOffset
  94. : this._range.endOffset;
  95. }
  96. /**
  97. * Returns anchor node.
  98. *
  99. * @deprecated
  100. * @alias anchorNode
  101. * @returns Node.
  102. */
  103. get baseNode() {
  104. return this.anchorNode;
  105. }
  106. /**
  107. * Returns anchor offset.
  108. *
  109. * @deprecated
  110. * @alias anchorOffset
  111. * @returns Node.
  112. */
  113. get baseOffset() {
  114. return this.anchorOffset;
  115. }
  116. /**
  117. * Returns focus node.
  118. *
  119. * @see https://w3c.github.io/selection-api/#dom-selection-focusnode
  120. * @returns Node.
  121. */
  122. get focusNode() {
  123. return this.anchorNode;
  124. }
  125. /**
  126. * Returns focus offset.
  127. *
  128. * @see https://w3c.github.io/selection-api/#dom-selection-focusoffset
  129. * @returns Node.
  130. */
  131. get focusOffset() {
  132. return this.anchorOffset;
  133. }
  134. /**
  135. * Returns focus node.
  136. *
  137. * @deprecated
  138. * @alias focusNode
  139. * @returns Node.
  140. */
  141. get extentNode() {
  142. return this.focusNode;
  143. }
  144. /**
  145. * Returns focus offset.
  146. *
  147. * @deprecated
  148. * @alias focusOffset
  149. * @returns Node.
  150. */
  151. get extentOffset() {
  152. return this.focusOffset;
  153. }
  154. /**
  155. * Adds a range.
  156. *
  157. * @see https://w3c.github.io/selection-api/#dom-selection-addrange
  158. * @param newRange Range.
  159. */
  160. addRange(newRange) {
  161. if (!newRange) {
  162. throw new Error('Failed to execute addRange on Selection. Parameter 1 is not of type Range.');
  163. }
  164. if (!this._range && newRange._ownerDocument === this._ownerDocument) {
  165. this._associateRange(newRange);
  166. }
  167. }
  168. /**
  169. * Returns Range.
  170. *
  171. * @see https://w3c.github.io/selection-api/#dom-selection-getrangeat
  172. * @param index Index.
  173. * @returns Range.
  174. */
  175. getRangeAt(index) {
  176. if (!this._range || index !== 0) {
  177. throw new DOMException_1.default('Invalid range index.', DOMExceptionNameEnum_1.default.indexSizeError);
  178. }
  179. return this._range;
  180. }
  181. /**
  182. * Removes a range from a selection.
  183. *
  184. * @see https://w3c.github.io/selection-api/#dom-selection-removerange
  185. * @param range Range.
  186. */
  187. removeRange(range) {
  188. if (this._range !== range) {
  189. throw new DOMException_1.default('Invalid range.', DOMExceptionNameEnum_1.default.notFoundError);
  190. }
  191. this._associateRange(null);
  192. }
  193. /**
  194. * Removes all ranges.
  195. */
  196. removeAllRanges() {
  197. this._associateRange(null);
  198. }
  199. /**
  200. * Removes all ranges.
  201. *
  202. * @alias removeAllRanges()
  203. */
  204. empty() {
  205. this.removeAllRanges();
  206. }
  207. /**
  208. * Collapses the current selection to a single point.
  209. *
  210. * @see https://w3c.github.io/selection-api/#dom-selection-collapse
  211. * @param node Node.
  212. * @param offset Offset.
  213. */
  214. collapse(node, offset) {
  215. if (node === null) {
  216. this.removeAllRanges();
  217. return;
  218. }
  219. if (node.nodeType === NodeTypeEnum_1.default.documentTypeNode) {
  220. throw new DOMException_1.default("DocumentType Node can't be used as boundary point.", DOMExceptionNameEnum_1.default.invalidNodeTypeError);
  221. }
  222. if (offset > NodeUtility_1.default.getNodeLength(node)) {
  223. throw new DOMException_1.default('Invalid range index.', DOMExceptionNameEnum_1.default.indexSizeError);
  224. }
  225. if (node.ownerDocument !== this._ownerDocument) {
  226. return;
  227. }
  228. const newRange = new Range_1.default();
  229. newRange._start.node = node;
  230. newRange._start.offset = offset;
  231. newRange._end.node = node;
  232. newRange._end.offset = offset;
  233. this._associateRange(newRange);
  234. }
  235. /**
  236. * Collapses the current selection to a single point.
  237. *
  238. * @see https://w3c.github.io/selection-api/#dom-selection-setposition
  239. * @alias collapse()
  240. * @param node Node.
  241. * @param offset Offset.
  242. */
  243. setPosition(node, offset) {
  244. this.collapse(node, offset);
  245. }
  246. /**
  247. * Collapses the selection to the end.
  248. *
  249. * @see https://w3c.github.io/selection-api/#dom-selection-collapsetoend
  250. */
  251. collapseToEnd() {
  252. if (this._range === null) {
  253. throw new DOMException_1.default('There is no selection to collapse.', DOMExceptionNameEnum_1.default.invalidStateError);
  254. }
  255. const { node, offset } = this._range._end;
  256. const newRange = new Range_1.default();
  257. newRange._start.node = node;
  258. newRange._start.offset = offset;
  259. newRange._end.node = node;
  260. newRange._end.offset = offset;
  261. this._associateRange(newRange);
  262. }
  263. /**
  264. * Collapses the selection to the start.
  265. *
  266. * @see https://w3c.github.io/selection-api/#dom-selection-collapsetostart
  267. */
  268. collapseToStart() {
  269. if (!this._range) {
  270. throw new DOMException_1.default('There is no selection to collapse.', DOMExceptionNameEnum_1.default.invalidStateError);
  271. }
  272. const { node, offset } = this._range._start;
  273. const newRange = new Range_1.default();
  274. newRange._start.node = node;
  275. newRange._start.offset = offset;
  276. newRange._end.node = node;
  277. newRange._end.offset = offset;
  278. this._associateRange(newRange);
  279. }
  280. /**
  281. * Indicates whether a specified node is part of the selection.
  282. *
  283. * @see https://w3c.github.io/selection-api/#dom-selection-containsnode
  284. * @param node Node.
  285. * @param [allowPartialContainment] Set to "true" to allow partial containment.
  286. * @returns Always returns "true" for now.
  287. */
  288. containsNode(node, allowPartialContainment = false) {
  289. if (!this._range || node.ownerDocument !== this._ownerDocument) {
  290. return false;
  291. }
  292. const { _start, _end } = this._range;
  293. const startIsBeforeNode = RangeUtility_1.default.compareBoundaryPointsPosition(_start, { node, offset: 0 }) === -1;
  294. const endIsAfterNode = RangeUtility_1.default.compareBoundaryPointsPosition(_end, {
  295. node,
  296. offset: NodeUtility_1.default.getNodeLength(node)
  297. }) === 1;
  298. return allowPartialContainment
  299. ? startIsBeforeNode || endIsAfterNode
  300. : startIsBeforeNode && endIsAfterNode;
  301. }
  302. /**
  303. * Deletes the selected text from the document's DOM.
  304. *
  305. * @see https://w3c.github.io/selection-api/#dom-selection-deletefromdocument
  306. */
  307. deleteFromDocument() {
  308. if (this._range) {
  309. this._range.deleteContents();
  310. }
  311. }
  312. /**
  313. * Moves the focus of the selection to a specified point.
  314. *
  315. * @see https://w3c.github.io/selection-api/#dom-selection-extend
  316. * @param node Node.
  317. * @param offset Offset.
  318. */
  319. extend(node, offset) {
  320. if (node.ownerDocument !== this._ownerDocument) {
  321. return;
  322. }
  323. if (!this._range) {
  324. throw new DOMException_1.default('There is no selection to extend.', DOMExceptionNameEnum_1.default.invalidStateError);
  325. }
  326. const anchorNode = this.anchorNode;
  327. const anchorOffset = this.anchorOffset;
  328. const newRange = new Range_1.default();
  329. newRange._start.node = node;
  330. newRange._start.offset = 0;
  331. newRange._end.node = node;
  332. newRange._end.offset = 0;
  333. if (node.ownerDocument !== this._range._ownerDocument) {
  334. newRange._start.offset = offset;
  335. newRange._end.offset = offset;
  336. }
  337. else if (RangeUtility_1.default.compareBoundaryPointsPosition({ node: anchorNode, offset: anchorOffset }, { node, offset }) <= 0) {
  338. newRange._start.node = anchorNode;
  339. newRange._start.offset = anchorOffset;
  340. newRange._end.node = node;
  341. newRange._end.offset = offset;
  342. }
  343. else {
  344. newRange._start.node = node;
  345. newRange._start.offset = offset;
  346. newRange._end.node = anchorNode;
  347. newRange._end.offset = anchorOffset;
  348. }
  349. this._associateRange(newRange);
  350. this._direction =
  351. RangeUtility_1.default.compareBoundaryPointsPosition({ node, offset }, { node: anchorNode, offset: anchorOffset }) === -1
  352. ? SelectionDirectionEnum_1.default.backwards
  353. : SelectionDirectionEnum_1.default.forwards;
  354. }
  355. /**
  356. * Selects all children.
  357. *
  358. * @see https://w3c.github.io/selection-api/#dom-selection-selectallchildren
  359. * @param node
  360. * @param _parentNode Parent node.
  361. */
  362. selectAllChildren(node) {
  363. if (node.nodeType === NodeTypeEnum_1.default.documentTypeNode) {
  364. throw new DOMException_1.default("DocumentType Node can't be used as boundary point.", DOMExceptionNameEnum_1.default.invalidNodeTypeError);
  365. }
  366. if (node.ownerDocument !== this._ownerDocument) {
  367. return;
  368. }
  369. const length = node.childNodes.length;
  370. const newRange = new Range_1.default();
  371. newRange._start.node = node;
  372. newRange._start.offset = 0;
  373. newRange._end.node = node;
  374. newRange._end.offset = length;
  375. this._associateRange(newRange);
  376. }
  377. /**
  378. * Sets the selection to be a range including all or parts of two specified DOM nodes, and any content located between them.
  379. *
  380. * @see https://w3c.github.io/selection-api/#dom-selection-setbaseandextent
  381. * @param anchorNode Anchor node.
  382. * @param anchorOffset Anchor offset.
  383. * @param focusNode Focus node.
  384. * @param focusOffset Focus offset.
  385. */
  386. setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset) {
  387. if (anchorOffset > NodeUtility_1.default.getNodeLength(anchorNode) ||
  388. focusOffset > NodeUtility_1.default.getNodeLength(focusNode)) {
  389. throw new DOMException_1.default('Invalid anchor or focus offset.', DOMExceptionNameEnum_1.default.indexSizeError);
  390. }
  391. if (anchorNode.ownerDocument !== this._ownerDocument ||
  392. focusNode.ownerDocument !== this._ownerDocument) {
  393. return;
  394. }
  395. const anchor = { node: anchorNode, offset: anchorOffset };
  396. const focus = { node: focusNode, offset: focusOffset };
  397. const newRange = new Range_1.default();
  398. if (RangeUtility_1.default.compareBoundaryPointsPosition(anchor, focus) === -1) {
  399. newRange._start = anchor;
  400. newRange._end = focus;
  401. }
  402. else {
  403. newRange._start = focus;
  404. newRange._end = anchor;
  405. }
  406. this._associateRange(newRange);
  407. this._direction =
  408. RangeUtility_1.default.compareBoundaryPointsPosition(focus, anchor) === -1
  409. ? SelectionDirectionEnum_1.default.backwards
  410. : SelectionDirectionEnum_1.default.forwards;
  411. }
  412. /**
  413. * Returns string currently being represented by the selection object.
  414. *
  415. * @returns Selection as string.
  416. */
  417. toString() {
  418. return this._range ? this._range.toString() : '';
  419. }
  420. /**
  421. * Sets the current range.
  422. *
  423. * @param range Range.
  424. */
  425. _associateRange(range) {
  426. const oldRange = this._range;
  427. this._range = range;
  428. this._direction =
  429. range === null ? SelectionDirectionEnum_1.default.directionless : SelectionDirectionEnum_1.default.forwards;
  430. if (oldRange !== this._range) {
  431. // https://w3c.github.io/selection-api/#selectionchange-event
  432. this._ownerDocument.dispatchEvent(new Event_1.default('selectionchange'));
  433. }
  434. }
  435. }
  436. exports.default = Selection;
  437. //# sourceMappingURL=Selection.js.map