until.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * @license
  3. * Copyright 2017 Google LLC
  4. * SPDX-License-Identifier: BSD-3-Clause
  5. */
  6. import { noChange } from '../lit-html.js';
  7. import { directive } from '../directive.js';
  8. import { isPrimitive } from '../directive-helpers.js';
  9. import { AsyncDirective } from '../async-directive.js';
  10. import { Pauser, PseudoWeakRef } from './private-async-helpers.js';
  11. const isPromise = (x) => {
  12. return !isPrimitive(x) && typeof x.then === 'function';
  13. };
  14. // Effectively infinity, but a SMI.
  15. const _infinity = 0x3fffffff;
  16. export class UntilDirective extends AsyncDirective {
  17. constructor() {
  18. super(...arguments);
  19. this.__lastRenderedIndex = _infinity;
  20. this.__values = [];
  21. this.__weakThis = new PseudoWeakRef(this);
  22. this.__pauser = new Pauser();
  23. }
  24. render(...args) {
  25. var _a;
  26. return (_a = args.find((x) => !isPromise(x))) !== null && _a !== void 0 ? _a : noChange;
  27. }
  28. update(_part, args) {
  29. const previousValues = this.__values;
  30. let previousLength = previousValues.length;
  31. this.__values = args;
  32. const weakThis = this.__weakThis;
  33. const pauser = this.__pauser;
  34. // If our initial render occurs while disconnected, ensure that the pauser
  35. // and weakThis are in the disconnected state
  36. if (!this.isConnected) {
  37. this.disconnected();
  38. }
  39. for (let i = 0; i < args.length; i++) {
  40. // If we've rendered a higher-priority value already, stop.
  41. if (i > this.__lastRenderedIndex) {
  42. break;
  43. }
  44. const value = args[i];
  45. // Render non-Promise values immediately
  46. if (!isPromise(value)) {
  47. this.__lastRenderedIndex = i;
  48. // Since a lower-priority value will never overwrite a higher-priority
  49. // synchronous value, we can stop processing now.
  50. return value;
  51. }
  52. // If this is a Promise we've already handled, skip it.
  53. if (i < previousLength && value === previousValues[i]) {
  54. continue;
  55. }
  56. // We have a Promise that we haven't seen before, so priorities may have
  57. // changed. Forget what we rendered before.
  58. this.__lastRenderedIndex = _infinity;
  59. previousLength = 0;
  60. // Note, the callback avoids closing over `this` so that the directive
  61. // can be gc'ed before the promise resolves; instead `this` is retrieved
  62. // from `weakThis`, which can break the hard reference in the closure when
  63. // the directive disconnects
  64. Promise.resolve(value).then(async (result) => {
  65. // If we're disconnected, wait until we're (maybe) reconnected
  66. // The while loop here handles the case that the connection state
  67. // thrashes, causing the pauser to resume and then get re-paused
  68. while (pauser.get()) {
  69. await pauser.get();
  70. }
  71. // If the callback gets here and there is no `this`, it means that the
  72. // directive has been disconnected and garbage collected and we don't
  73. // need to do anything else
  74. const _this = weakThis.deref();
  75. if (_this !== undefined) {
  76. const index = _this.__values.indexOf(value);
  77. // If state.values doesn't contain the value, we've re-rendered without
  78. // the value, so don't render it. Then, only render if the value is
  79. // higher-priority than what's already been rendered.
  80. if (index > -1 && index < _this.__lastRenderedIndex) {
  81. _this.__lastRenderedIndex = index;
  82. _this.setValue(result);
  83. }
  84. }
  85. });
  86. }
  87. return noChange;
  88. }
  89. disconnected() {
  90. this.__weakThis.disconnect();
  91. this.__pauser.pause();
  92. }
  93. reconnected() {
  94. this.__weakThis.reconnect(this);
  95. this.__pauser.resume();
  96. }
  97. }
  98. /**
  99. * Renders one of a series of values, including Promises, to a Part.
  100. *
  101. * Values are rendered in priority order, with the first argument having the
  102. * highest priority and the last argument having the lowest priority. If a
  103. * value is a Promise, low-priority values will be rendered until it resolves.
  104. *
  105. * The priority of values can be used to create placeholder content for async
  106. * data. For example, a Promise with pending content can be the first,
  107. * highest-priority, argument, and a non_promise loading indicator template can
  108. * be used as the second, lower-priority, argument. The loading indicator will
  109. * render immediately, and the primary content will render when the Promise
  110. * resolves.
  111. *
  112. * Example:
  113. *
  114. * ```js
  115. * const content = fetch('./content.txt').then(r => r.text());
  116. * html`${until(content, html`<span>Loading...</span>`)}`
  117. * ```
  118. */
  119. export const until = directive(UntilDirective);
  120. /**
  121. * The type of the class that powers this directive. Necessary for naming the
  122. * directive's return type.
  123. */
  124. // export type {UntilDirective};
  125. //# sourceMappingURL=until.js.map