styling.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { caretDownEmptyIcon } from '@jupyterlab/ui-components';
  4. /**
  5. * A namespace for node styling.
  6. */
  7. export namespace Styling {
  8. /**
  9. * Style a node and its child elements with the default tag names.
  10. *
  11. * @param node - The base node.
  12. *
  13. * @param className - The optional CSS class to add to styled nodes.
  14. */
  15. export function styleNode(node: HTMLElement, className = ''): void {
  16. styleNodeByTag(node, 'select', className);
  17. styleNodeByTag(node, 'textarea', className);
  18. styleNodeByTag(node, 'input', className);
  19. styleNodeByTag(node, 'button', className);
  20. }
  21. /**
  22. * Style a node and its elements that have a given tag name.
  23. *
  24. * @param node - The base node.
  25. *
  26. * @param tagName - The html tag name to style.
  27. *
  28. * @param className - The optional CSS class to add to styled nodes.
  29. */
  30. export function styleNodeByTag(
  31. node: HTMLElement,
  32. tagName: string,
  33. className = ''
  34. ): void {
  35. if (node.localName === tagName) {
  36. node.classList.add('jp-mod-styled');
  37. }
  38. if (node.localName === 'select') {
  39. wrapSelect(node as HTMLSelectElement);
  40. }
  41. const nodes = node.getElementsByTagName(tagName);
  42. for (let i = 0; i < nodes.length; i++) {
  43. const child = nodes[i];
  44. child.classList.add('jp-mod-styled');
  45. if (className) {
  46. child.classList.add(className);
  47. }
  48. if (tagName === 'select') {
  49. wrapSelect(child as HTMLSelectElement);
  50. }
  51. }
  52. }
  53. /**
  54. * Wrap a select node.
  55. */
  56. export function wrapSelect(node: HTMLSelectElement): HTMLElement {
  57. const wrapper = document.createElement('div');
  58. wrapper.classList.add('jp-select-wrapper');
  59. node.addEventListener('focus', Private.onFocus);
  60. node.addEventListener('blur', Private.onFocus);
  61. node.classList.add('jp-mod-styled');
  62. if (node.parentElement) {
  63. node.parentElement.replaceChild(wrapper, node);
  64. }
  65. wrapper.appendChild(node);
  66. // add the icon node
  67. wrapper.appendChild(
  68. caretDownEmptyIcon.element({
  69. tag: 'span',
  70. stylesheet: 'select',
  71. right: '8px',
  72. top: '5px',
  73. width: '18px'
  74. })
  75. );
  76. return wrapper;
  77. }
  78. }
  79. /**
  80. * The namespace for module private data.
  81. */
  82. namespace Private {
  83. /**
  84. * Handle a focus event on a styled select.
  85. */
  86. export function onFocus(event: FocusEvent): void {
  87. const target = event.target as Element;
  88. const parent = target.parentElement;
  89. if (!parent) {
  90. return;
  91. }
  92. if (event.type === 'focus') {
  93. parent.classList.add('jp-mod-focused');
  94. } else {
  95. parent.classList.remove('jp-mod-focused');
  96. }
  97. }
  98. }