Skip to content

Commit

Permalink
fix(micro-journeys): make two-character-layout more robust
Browse files Browse the repository at this point in the history
  • Loading branch information
glassdimly committed Dec 5, 2019
1 parent 10e2510 commit 26f7d9d
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 71 deletions.
@@ -0,0 +1,14 @@
{% set content %}
<bolt-interactive-pathways theme="{{ theme ? theme }}">
<bolt-text subheadline font-size="xxlarge" slot="interactive-pathways-lead-text">How Pega technology resolves</bolt-text>
<bolt-interactive-pathway pathway-title="Billing Inquires">
<bolt-interactive-step tab-title="Proactive service that meets or exeeds customer needs and offers a high return on investment to customers">
{% include "./billing-micro-journey-fragments/_step-one.twig" %}
</bolt-interactive-step>
</bolt-interactive-pathway>
</bolt-interactive-pathways>
{% endset %}

{% include '@bolt-components-editor/editor-in-pl.twig' with {
content: content,
} only %}
4 changes: 4 additions & 0 deletions packages/micro-journeys/src/character.scss
Expand Up @@ -9,6 +9,10 @@ bolt-svg-animations {
width: 100%;
}

bolt-animate[slot="connection"] {
display: flex;
}

.c-bolt-character {
display: flex;
flex-wrap: wrap;
Expand Down
198 changes: 127 additions & 71 deletions packages/micro-journeys/src/two-character-layout.js
Expand Up @@ -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);
Expand All @@ -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 },
Expand All @@ -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(
Expand All @@ -113,29 +58,34 @@ 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(
new CustomEvent(`${BoltTwoCharacterLayout.is}:animation-initialized`, {
bubbles: true,
}),
);
this.isInitialRender = false;
};

/**
* Set the width of `bolt-connection` if present so it spans from one `bolt-character` to
* `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}`,
Expand All @@ -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<unknown>}
*/
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<Promise|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,
Expand All @@ -174,6 +199,37 @@ class BoltTwoCharacterLayout extends withLitHtml() {
</div>
`;
}

/**
* 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 };
Expand Up @@ -6,6 +6,8 @@
* @prop {HTMLElement} paddingEqualizationTarget
*/

export const equalizeRelativeHeightsClass = 'isEqualized';

/**
*
* @param {EqualizeRelativeHeightArgItem} item
Expand Down Expand Up @@ -83,6 +85,7 @@ export const equalizeRelativeHeights = (
paddingEqualizationTarget.style.paddingTop = `calc(${items[
indexOfLongest
].relativeOffsetTop - relativeOffsetTop}px + ${previousPadding})`;
paddingEqualizationTarget.classList.add(equalizeRelativeHeightsClass);
}
});
if (callback) {
Expand Down

0 comments on commit 26f7d9d

Please sign in to comment.