notch-controller.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { w as win } from './index6.js';
  5. import { r as raf } from './helpers.js';
  6. /**
  7. * A utility to calculate the size of an outline notch
  8. * width relative to the content passed. This is used in
  9. * components such as `ion-select` with `fill="outline"`
  10. * where we need to pass slotted HTML content. This is not
  11. * needed when rendering plaintext content because we can
  12. * render the plaintext again hidden with `opacity: 0` inside
  13. * of the notch. As a result we can rely on the intrinsic size
  14. * of the element to correctly compute the notch width. We
  15. * cannot do this with slotted content because we cannot project
  16. * it into 2 places at once.
  17. *
  18. * @internal
  19. * @param el: The host element
  20. * @param getNotchSpacerEl: A function that returns a reference to the notch spacer element inside of the component template.
  21. * @param getLabelSlot: A function that returns a reference to the slotted content.
  22. */
  23. const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
  24. let notchVisibilityIO;
  25. const needsExplicitNotchWidth = () => {
  26. const notchSpacerEl = getNotchSpacerEl();
  27. if (
  28. /**
  29. * If the notch is not being used
  30. * then we do not need to set the notch width.
  31. */
  32. notchSpacerEl === undefined ||
  33. /**
  34. * If either the label property is being
  35. * used or the label slot is not defined,
  36. * then we do not need to estimate the notch width.
  37. */
  38. el.label !== undefined ||
  39. getLabelSlot() === null) {
  40. return false;
  41. }
  42. return true;
  43. };
  44. const calculateNotchWidth = () => {
  45. if (needsExplicitNotchWidth()) {
  46. /**
  47. * Run this the frame after
  48. * the browser has re-painted the host element.
  49. * Otherwise, the label element may have a width
  50. * of 0 and the IntersectionObserver will be used.
  51. */
  52. raf(() => {
  53. setNotchWidth();
  54. });
  55. }
  56. };
  57. /**
  58. * When using a label prop we can render
  59. * the label value inside of the notch and
  60. * let the browser calculate the size of the notch.
  61. * However, we cannot render the label slot in multiple
  62. * places so we need to manually calculate the notch dimension
  63. * based on the size of the slotted content.
  64. *
  65. * This function should only be used to set the notch width
  66. * on slotted label content. The notch width for label prop
  67. * content is automatically calculated based on the
  68. * intrinsic size of the label text.
  69. */
  70. const setNotchWidth = () => {
  71. const notchSpacerEl = getNotchSpacerEl();
  72. if (notchSpacerEl === undefined) {
  73. return;
  74. }
  75. if (!needsExplicitNotchWidth()) {
  76. notchSpacerEl.style.removeProperty('width');
  77. return;
  78. }
  79. const width = getLabelSlot().scrollWidth;
  80. if (
  81. /**
  82. * If the computed width of the label is 0
  83. * and notchSpacerEl's offsetParent is null
  84. * then that means the element is hidden.
  85. * As a result, we need to wait for the element
  86. * to become visible before setting the notch width.
  87. *
  88. * We do not check el.offsetParent because
  89. * that can be null if the host element has
  90. * position: fixed applied to it.
  91. * notchSpacerEl does not have position: fixed.
  92. */
  93. width === 0 &&
  94. notchSpacerEl.offsetParent === null &&
  95. win !== undefined &&
  96. 'IntersectionObserver' in win) {
  97. /**
  98. * If there is an IO already attached
  99. * then that will update the notch
  100. * once the element becomes visible.
  101. * As a result, there is no need to create
  102. * another one.
  103. */
  104. if (notchVisibilityIO !== undefined) {
  105. return;
  106. }
  107. const io = (notchVisibilityIO = new IntersectionObserver((ev) => {
  108. /**
  109. * If the element is visible then we
  110. * can try setting the notch width again.
  111. */
  112. if (ev[0].intersectionRatio === 1) {
  113. setNotchWidth();
  114. io.disconnect();
  115. notchVisibilityIO = undefined;
  116. }
  117. },
  118. /**
  119. * Set the root to be the host element
  120. * This causes the IO callback
  121. * to be fired in WebKit as soon as the element
  122. * is visible. If we used the default root value
  123. * then WebKit would only fire the IO callback
  124. * after any animations (such as a modal transition)
  125. * finished, and there would potentially be a flicker.
  126. */
  127. { threshold: 0.01, root: el }));
  128. io.observe(notchSpacerEl);
  129. return;
  130. }
  131. /**
  132. * If the element is visible then we can set the notch width.
  133. * The notch is only visible when the label is scaled,
  134. * which is why we multiply the width by 0.75 as this is
  135. * the same amount the label element is scaled by in the host CSS.
  136. * (See $form-control-label-stacked-scale in ionic.globals.scss).
  137. */
  138. notchSpacerEl.style.setProperty('width', `${width * 0.75}px`);
  139. };
  140. const destroy = () => {
  141. if (notchVisibilityIO) {
  142. notchVisibilityIO.disconnect();
  143. notchVisibilityIO = undefined;
  144. }
  145. };
  146. return {
  147. calculateNotchWidth,
  148. destroy,
  149. };
  150. };
  151. export { createNotchController as c };