diff --git a/docs-site/src/pages/pattern-lab/_patterns/06-experiments/editor/25-editor-with-two-character-layout.twig b/docs-site/src/pages/pattern-lab/_patterns/06-experiments/editor/25-editor-with-two-character-layout.twig new file mode 100644 index 0000000000..1213b2a3bc --- /dev/null +++ b/docs-site/src/pages/pattern-lab/_patterns/06-experiments/editor/25-editor-with-two-character-layout.twig @@ -0,0 +1,14 @@ +{% set content %} + + How Pega technology resolves + + + {% include "./billing-micro-journey-fragments/_step-one.twig" %} + + + +{% endset %} + +{% include '@bolt-components-editor/editor-in-pl.twig' with { + content: content, +} only %} diff --git a/packages/micro-journeys/src/character.scss b/packages/micro-journeys/src/character.scss index e75e3c8408..7fa2b6ec17 100644 --- a/packages/micro-journeys/src/character.scss +++ b/packages/micro-journeys/src/character.scss @@ -9,6 +9,10 @@ bolt-svg-animations { width: 100%; } +bolt-animate[slot="connection"] { + display: flex; +} + .c-bolt-character { display: flex; flex-wrap: wrap; diff --git a/packages/micro-journeys/src/two-character-layout.js b/packages/micro-journeys/src/two-character-layout.js index 345ac3b716..2742f4faca 100644 --- a/packages/micro-journeys/src/two-character-layout.js +++ b/packages/micro-journeys/src/two-character-layout.js @@ -7,7 +7,10 @@ import { } from '@bolt/micro-journeys/src/character'; import { boltConnectionIs } from '@bolt/micro-journeys/src/connection'; import { triggerAnims } from '@bolt/components-animate/utils'; -import { equalizeRelativeHeights } from './utils/equalize-relative-heights'; +import { + equalizeRelativeHeights, + equalizeRelativeHeightsClass, +} from './utils/equalize-relative-heights'; import styles from './two-character-layout.scss'; let cx = classNames.bind(styles); @@ -23,6 +26,7 @@ class BoltTwoCharacterLayout extends withLitHtml() { ...props.boolean, ...{ default: false }, }, + // Has the parent `bolt-interactive-step` attempted its animation of its children in? parentAnimationsTriggered: { ...props.boolean, ...{ default: false }, @@ -33,78 +37,19 @@ class BoltTwoCharacterLayout extends withLitHtml() { constructor(self) { self = super(self); self.useShadow = hasNativeShadowDomSupport; - self.hasConnection = !!this.querySelector(boltConnectionIs); - self.requiredComponentCount = self.hasConnection ? 3 : 2; - self.renderedComponentCount = 0; + self.connection = this.querySelector(boltConnectionIs); + self.characters = [...this.querySelectorAll(boltCharacterIs)]; self.isInitialRender = true; return self; } - /** - * Callback for when child components report ready. Figure out if they're - * characters and count them if so. Then attempt to initialize. - * - * @param event - */ - handleChildrenReady(event) { - if ( - event.detail.name !== boltCharacterIs && - event.detail.name !== boltConnectionIs - ) { - return; - } - this.renderedComponentCount++; - if (this.isConnected) { - this.attemptCharactersAreReadyInitialization(); - } - } - - /** - * Make sure that the component is rendering for the first time and `bolt-character`s are ready. - * Both characters have to be on the dom with width and height before this can run. - * The problem is that this component connects and components report as rendered - * before content is slotted. Thus, we have to wait for the parent to tell us - * that it has attempted to animate content as a sigh that we can actually - * perform calculations against slotted elements that have height/width. - * We only do this on initial render after we're told the parent has animated. - * After this, bolt-interactive-step is in charge of animating this in and out. - */ - attemptCharactersAreReadyInitialization() { - const charsAreReady = - this.renderedComponentCount >= this.requiredComponentCount; - if ( - this.parentAnimationsTriggered && - charsAreReady && - this.isInitialRender - ) { - setTimeout(() => { - window.requestAnimationFrame(() => { - this.charactersAreReadyInitialization(); - this.isInitialRender = false; - }); - }); - } - } - - connected() { - this.addEventListener('ready', this.handleChildrenReady); - } - - disconnecting() { - if (this.parentAnimationsTriggered && !this.isInitialRender) { - this.removeEventListener('ready', this.handleChildrenReady); - } - } - /** * Make sure both pathway main images are at exactly the same height relative * to one another so a connection can be centered between them, while * preserving document flow by avoiding absolute positioning. */ - charactersAreReadyInitialization = async () => { - const anims = this.querySelectorAll('bolt-animate'); - this.boltCharacters = [...this.querySelectorAll(boltCharacterIs)]; - const eqHeightArgs = this.boltCharacters.map(character => { + equalizeCharactersAndStyleConnection = () => { + const eqHeightArgs = this.characters.map(character => { return { container: character, elToEqualize: character.renderRoot.querySelector( @@ -113,12 +58,16 @@ class BoltTwoCharacterLayout extends withLitHtml() { paddingEqualizationTarget: character, }; }); + equalizeRelativeHeights( eqHeightArgs, - this.hasConnection ? this.setConnectionWidth : null, + this.connection ? this.setConnectionWidth : null, ); this.triggerUpdate(); + }; + animateContentIn = () => { + const anims = this.querySelectorAll('bolt-animate'); triggerAnims({ animEls: anims, stage: 'IN' }); // Tell the parent step that it can now normally animate this element. this.dispatchEvent( @@ -126,6 +75,7 @@ class BoltTwoCharacterLayout extends withLitHtml() { bubbles: true, }), ); + this.isInitialRender = false; }; /** @@ -133,9 +83,9 @@ class BoltTwoCharacterLayout extends withLitHtml() { * `bolt-character`. Note: requires page refresh. */ setConnectionWidth = () => { - this.boltCharacters.forEach((e, i) => { - const connection = e.querySelector(boltConnectionIs); - const nextCharacter = this.boltCharacters[i + 1]; + this.characters.forEach((e, i) => { + const connection = e.querySelector('bolt-animate[slot="connection"'); + const nextCharacter = this.characters[i + 1]; if (connection && nextCharacter) { const nextCharacterCenter = nextCharacter.renderRoot.querySelector( `.${boltCharacterCenterClass}`, @@ -144,14 +94,89 @@ class BoltTwoCharacterLayout extends withLitHtml() { .left - connection.getBoundingClientRect().left}px + 50%)`; connection.renderRoot.querySelector('.c-bolt-connection__main-image'); } + // Hack for chrome which gets confused by [slot='connection'] + // e.querySelector('bolt-animate[slot="connection"').style.display = 'flex'; + }); + }; + + /** + * Create a delay and return a promise. + * + * @param {int} timeoutAmount in MS + * @return {Promise} + */ + delay = async timeoutAmount => + new Promise(resolve => { + setTimeout(resolve, timeoutAmount); + }); + + /** + * Ensure with recursive promises that the `bolt-character`s and `bolt-connection` + * are rendered. This is necessary because bolt element ready + * event fires after JS render but before render to the actual DOM. + * + * @param attemptCount + * @return {Promise} + */ + ensureComponentsRendered = async (attemptCount = 0) => { + const attemptMax = 15; + const attemptTimeout = 900; + return new Promise((resolve, reject) => { + if (attemptMax <= attemptCount) { + return reject( + new Error( + "Uh oh. Characters didn't render to the dom in a timely fashion.", + ), + ); + } + if (this.areComponentsRendered()) { + return resolve(true); + } else { + // I realize this seems primitive, but it is the best way. + this.delay(attemptTimeout).then(() => { + return this.ensureComponentsRendered(attemptCount + 1); + }); + } + }); + }; + + /** + * Check the `bolt-character`s and `bolt-connection` to see if they have + * offsetHeight, that is, if they are painted in the browser yet. + * + * @return {boolean} if all required components have offsetHeight. + */ + areComponentsRendered = () => { + this.characters.forEach(character => { + if (!character.offsetHeight) { + return false; + } + }); + if (this.connection) { + if (!this.connection.offsetHeight) { + return false; + } + } + return true; + }; + + /** + * Check to see if `bolt-character`s have had their heights equalized. + * + * @return {boolean} true if equalized else false. + */ + areCharactersEqualized = () => { + let isEqualized = true; + this.characters.forEach(character => { + if (!character.classList.contains(equalizeRelativeHeightsClass)) { + isEqualized = false; + } }); + return isEqualized; }; render() { const props = this.validateProps(this.props); - if (this.isInitialRender) { - this.attemptCharactersAreReadyInitialization(); - } const classes = cx('c-bolt-two-character-layout', { 'c-bolt-two-character-layout__initial': !props.parentAnimationsTriggered && this.isInitialRender, @@ -174,6 +199,37 @@ class BoltTwoCharacterLayout extends withLitHtml() { `; } + + /** + * Make sure that the component is rendering for the first time and `bolt-character`s are ready. + * Both characters have to be on the dom with width and height before this can run. + * The problem is that this component connects and components report as rendered + * before content is slotted. Thus, we have to wait for the parent to tell us + * that it has attempted to animate content as a sign that we can actually + * perform calculations against slotted elements that have height/width. + * We only do this on initial render after we're told the parent has animated. + * After this, `bolt-interactive-step` is in charge of animating `this` in and out. + */ + rendered() { + super.rendered(); + // The editor disconnects the old component and copies the HTML to an iframe. + // Don't perform animations or calculations on an unconnected component. + if (!this.isConnected) { + return; + } + if (this.isInitialRender && this.parentAnimationsTriggered) { + if (!this.areCharactersEqualized()) { + this.ensureComponentsRendered() + .then(() => { + this.equalizeCharactersAndStyleConnection(); + this.animateContentIn(); + }) + .catch(e => console.error(e)); + } else { + this.animateContentIn(); + } + } + } } export { BoltTwoCharacterLayout, boltTwoCharacterLayoutIs }; diff --git a/packages/micro-journeys/src/utils/equalize-relative-heights.js b/packages/micro-journeys/src/utils/equalize-relative-heights.js index 9c06cfa378..0b7faacc0d 100644 --- a/packages/micro-journeys/src/utils/equalize-relative-heights.js +++ b/packages/micro-journeys/src/utils/equalize-relative-heights.js @@ -6,6 +6,8 @@ * @prop {HTMLElement} paddingEqualizationTarget */ +export const equalizeRelativeHeightsClass = 'isEqualized'; + /** * * @param {EqualizeRelativeHeightArgItem} item @@ -83,6 +85,7 @@ export const equalizeRelativeHeights = ( paddingEqualizationTarget.style.paddingTop = `calc(${items[ indexOfLongest ].relativeOffsetTop - relativeOffsetTop}px + ${previousPadding})`; + paddingEqualizationTarget.classList.add(equalizeRelativeHeightsClass); } }); if (callback) {