list_focus.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // TODO: This file was created by bulk-decaffeinate.
  2. // Sanity-check the conversion and remove this comment.
  3. /*
  4. * decaffeinate suggestions:
  5. * DS002: Fix invalid constructor
  6. * DS102: Remove unnecessary code created because of implicit returns
  7. * DS206: Consider reworking classes to avoid initClass
  8. * DS207: Consider shorter variations of null checks
  9. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
  10. */
  11. app.views.ListFocus = class ListFocus extends app.View {
  12. static initClass() {
  13. this.activeClass = "focus";
  14. this.events = { click: "onClick" };
  15. this.shortcuts = {
  16. up: "onUp",
  17. down: "onDown",
  18. left: "onLeft",
  19. enter: "onEnter",
  20. superEnter: "onSuperEnter",
  21. escape: "blur",
  22. };
  23. }
  24. constructor(el) {
  25. this.blur = this.blur.bind(this);
  26. this.onDown = this.onDown.bind(this);
  27. this.onUp = this.onUp.bind(this);
  28. this.onLeft = this.onLeft.bind(this);
  29. this.onEnter = this.onEnter.bind(this);
  30. this.onSuperEnter = this.onSuperEnter.bind(this);
  31. this.onClick = this.onClick.bind(this);
  32. this.el = el;
  33. super(...arguments);
  34. this.focusOnNextFrame = $.framify(this.focus, this);
  35. }
  36. focus(el, options) {
  37. if (options == null) {
  38. options = {};
  39. }
  40. if (el && !el.classList.contains(this.constructor.activeClass)) {
  41. this.blur();
  42. el.classList.add(this.constructor.activeClass);
  43. if (options.silent !== true) {
  44. $.trigger(el, "focus");
  45. }
  46. }
  47. }
  48. blur() {
  49. let cursor;
  50. if ((cursor = this.getCursor())) {
  51. cursor.classList.remove(this.constructor.activeClass);
  52. $.trigger(cursor, "blur");
  53. }
  54. }
  55. getCursor() {
  56. return (
  57. this.findByClass(this.constructor.activeClass) ||
  58. this.findByClass(app.views.ListSelect.activeClass)
  59. );
  60. }
  61. findNext(cursor) {
  62. let next;
  63. if ((next = cursor.nextSibling)) {
  64. if (next.tagName === "A") {
  65. return next;
  66. } else if (next.tagName === "SPAN") {
  67. // pagination link
  68. $.click(next);
  69. return this.findNext(cursor);
  70. } else if (next.tagName === "DIV") {
  71. // sub-list
  72. if (cursor.className.indexOf(" open") >= 0) {
  73. return this.findFirst(next) || this.findNext(next);
  74. } else {
  75. return this.findNext(next);
  76. }
  77. } else if (next.tagName === "H6") {
  78. // title
  79. return this.findNext(next);
  80. }
  81. } else if (cursor.parentNode !== this.el) {
  82. return this.findNext(cursor.parentNode);
  83. }
  84. }
  85. findFirst(cursor) {
  86. let first;
  87. if (!(first = cursor.firstChild)) {
  88. return;
  89. }
  90. if (first.tagName === "A") {
  91. return first;
  92. } else if (first.tagName === "SPAN") {
  93. // pagination link
  94. $.click(first);
  95. return this.findFirst(cursor);
  96. }
  97. }
  98. findPrev(cursor) {
  99. let prev;
  100. if ((prev = cursor.previousSibling)) {
  101. if (prev.tagName === "A") {
  102. return prev;
  103. } else if (prev.tagName === "SPAN") {
  104. // pagination link
  105. $.click(prev);
  106. return this.findPrev(cursor);
  107. } else if (prev.tagName === "DIV") {
  108. // sub-list
  109. if (prev.previousSibling.className.indexOf("open") >= 0) {
  110. return this.findLast(prev) || this.findPrev(prev);
  111. } else {
  112. return this.findPrev(prev);
  113. }
  114. } else if (prev.tagName === "H6") {
  115. // title
  116. return this.findPrev(prev);
  117. }
  118. } else if (cursor.parentNode !== this.el) {
  119. return this.findPrev(cursor.parentNode);
  120. }
  121. }
  122. findLast(cursor) {
  123. let last;
  124. if (!(last = cursor.lastChild)) {
  125. return;
  126. }
  127. if (last.tagName === "A") {
  128. return last;
  129. } else if (last.tagName === "SPAN" || last.tagName === "H6") {
  130. // pagination link or title
  131. return this.findPrev(last);
  132. } else if (last.tagName === "DIV") {
  133. // sub-list
  134. return this.findLast(last);
  135. }
  136. }
  137. onDown() {
  138. let cursor;
  139. if ((cursor = this.getCursor())) {
  140. this.focusOnNextFrame(this.findNext(cursor));
  141. } else {
  142. this.focusOnNextFrame(this.findByTag("a"));
  143. }
  144. }
  145. onUp() {
  146. let cursor;
  147. if ((cursor = this.getCursor())) {
  148. this.focusOnNextFrame(this.findPrev(cursor));
  149. } else {
  150. this.focusOnNextFrame(this.findLastByTag("a"));
  151. }
  152. }
  153. onLeft() {
  154. const cursor = this.getCursor();
  155. if (
  156. cursor &&
  157. !cursor.classList.contains(app.views.ListFold.activeClass) &&
  158. cursor.parentNode !== this.el
  159. ) {
  160. const prev = cursor.parentNode.previousSibling;
  161. if (prev && prev.classList.contains(app.views.ListFold.targetClass)) {
  162. this.focusOnNextFrame(cursor.parentNode.previousSibling);
  163. }
  164. }
  165. }
  166. onEnter() {
  167. let cursor;
  168. if ((cursor = this.getCursor())) {
  169. $.click(cursor);
  170. }
  171. }
  172. onSuperEnter() {
  173. let cursor;
  174. if ((cursor = this.getCursor())) {
  175. $.popup(cursor);
  176. }
  177. }
  178. onClick(event) {
  179. if (event.which !== 1 || event.metaKey || event.ctrlKey) {
  180. return;
  181. }
  182. const target = $.eventTarget(event);
  183. if (target.tagName === "A") {
  184. this.focus(target, { silent: true });
  185. }
  186. }
  187. };
  188. app.views.ListFocus.initClass();