Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MWPW-167306 [Plans Milo] Quantity Selector & Badge #3683

Open
wants to merge 9 commits into
base: MWPW-164492
Choose a base branch
from
8 changes: 4 additions & 4 deletions libs/deps/mas/merch-quantity-select.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import{html as i,LitElement as c}from"../lit-all.min.js";import{css as h}from"../lit-all.min.js";var r=h`
import{html as i,LitElement as E}from"../lit-all.min.js";import{css as u}from"../lit-all.min.js";var n=u`
:host {
box-sizing: border-box;
--background-color: var(--qs-background-color, #f6f6f6);
Expand Down Expand Up @@ -128,7 +128,7 @@ import{html as i,LitElement as c}from"../lit-all.min.js";import{css as h}from"..
.item.highlighted {
background-color: var(--background-color);
}
`;var[m,x,n,a,d,_]=["ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Enter","Tab"];var l="merch-quantity-selector:change";var s=class extends c{static get properties(){return{closed:{type:Boolean,reflect:!0},selected:{type:Number},min:{type:Number},max:{type:Number},step:{type:Number},maxInput:{type:Number,attribute:"max-input"},defaultValue:{type:Number,attribute:"default-value",reflect:!0},title:{type:String}}}static get styles(){return r}constructor(){super(),this.options=[],this.title="",this.closed=!0,this.min=0,this.max=0,this.step=0,this.maxInput=void 0,this.defaultValue=void 0,this.selectedValue=0,this.highlightedIndex=0,this.toggleMenu=this.toggleMenu.bind(this),this.handleClickOutside=this.handleClickOutside.bind(this),this.boundKeydownListener=this.handleKeydown.bind(this),this.addEventListener("keydown",this.boundKeydownListener),window.addEventListener("mousedown",this.handleClickOutside)}handleKeyup(){this.handleInput(),this.sendEvent()}handleKeydown(e){switch(e.key){case a:this.closed||(e.preventDefault(),this.highlightedIndex=(this.highlightedIndex+1)%this.options.length,this.requestUpdate());break;case n:this.closed||(e.preventDefault(),this.highlightedIndex=(this.highlightedIndex-1+this.options.length)%this.options.length,this.requestUpdate());break;case d:if(this.closed)this.closePopover(),this.blur();else{let t=this.options[this.highlightedIndex];if(!t)break;this.selectedValue=t,this.handleMenuOption(this.selectedValue),this.toggleMenu()}break}e.composedPath().includes(this)&&e.stopPropagation()}handleInput(){let e=this.shadowRoot.querySelector(".text-field-input"),t=parseInt(e.value);if(!isNaN(t)&&t>0&&t!==this.selectedValue){let o=this.maxInput&&t>this.maxInput?this.maxInput:t;this.selectedValue=o,e.value=o,this.highlightedIndex=this.options.indexOf(o)}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("mousedown",this.handleClickOutside),this.removeEventListener("keydown",this.boundKeydownListener)}generateOptionsArray(){let e=[];if(this.step>0)for(let t=this.min;t<=this.max;t+=this.step)e.push(t);return e}updated(e){(e.has("min")||e.has("max")||e.has("step")||e.has("defaultValue"))&&(this.options=this.generateOptionsArray(),this.highlightedIndex=this.defaultValue?this.options.indexOf(this.defaultValue):0,this.handleMenuOption(this.defaultValue?this.defaultValue:this.options[0]),this.requestUpdate())}handleClickOutside(e){e.composedPath().includes(this)||this.closePopover()}toggleMenu(){this.closed=!this.closed}handleMouseEnter(e){this.highlightedIndex=e,this.requestUpdate()}handleMenuOption(e){e===this.max&&this.shadowRoot.querySelector(".text-field-input")?.focus(),this.selectedValue=e,this.sendEvent(),this.closePopover()}sendEvent(){let e=new CustomEvent(l,{detail:{option:this.selectedValue},bubbles:!0});this.dispatchEvent(e)}closePopover(){this.closed||this.toggleMenu()}get offerSelect(){return this.querySelector("merch-offer-select")}get popover(){return i` <div class="popover ${this.closed?"closed":"open"}">
`;function a(s,e){let t;return function(){let o=this,p=arguments;clearTimeout(t),t=setTimeout(()=>s.apply(o,p),e)}}var[b,g,d,l,c,_]=["ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Enter","Tab"];var h="merch-quantity-selector:change";var r=class extends E{static get properties(){return{closed:{type:Boolean,reflect:!0},selected:{type:Number},min:{type:Number},max:{type:Number},step:{type:Number},maxInput:{type:Number,attribute:"max-input"},defaultValue:{type:Number,attribute:"default-value",reflect:!0},title:{type:String}}}static get styles(){return n}constructor(){super(),this.options=[],this.title="",this.closed=!0,this.min=0,this.max=0,this.step=0,this.maxInput=void 0,this.defaultValue=void 0,this.selectedValue=0,this.highlightedIndex=0,this.toggleMenu=this.toggleMenu.bind(this),this.handleClickOutside=this.handleClickOutside.bind(this),this.boundKeydownListener=this.handleKeydown.bind(this),this.addEventListener("keydown",this.boundKeydownListener),window.addEventListener("mousedown",this.handleClickOutside),this.handleKeyupDebounced=a(this.handleKeyup.bind(this),500)}handleKeyup(){this.handleInput(),this.sendEvent()}handleKeydown(e){switch(e.key){case l:this.closed||(e.preventDefault(),this.highlightedIndex=(this.highlightedIndex+1)%this.options.length,this.requestUpdate());break;case d:this.closed||(e.preventDefault(),this.highlightedIndex=(this.highlightedIndex-1+this.options.length)%this.options.length,this.requestUpdate());break;case c:if(this.closed)this.closePopover(),this.blur();else{let t=this.options[this.highlightedIndex];if(!t)break;this.selectedValue=t,this.handleMenuOption(this.selectedValue),this.toggleMenu()}break}e.composedPath().includes(this)&&e.stopPropagation()}handleInput(){let e=this.shadowRoot.querySelector(".text-field-input"),t=parseInt(e.value);if(!isNaN(t)&&t>0&&t!==this.selectedValue){let o=this.maxInput&&t>this.maxInput?this.maxInput:t;o=this.min&&o<this.min?this.min:o,this.selectedValue=o,e.value=o,this.highlightedIndex=this.options.indexOf(o)}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("mousedown",this.handleClickOutside),this.removeEventListener("keydown",this.boundKeydownListener)}generateOptionsArray(){let e=[];if(this.step>0)for(let t=this.min;t<=this.max;t+=this.step)e.push(t);return e}updated(e){(e.has("min")||e.has("max")||e.has("step")||e.has("defaultValue"))&&(this.options=this.generateOptionsArray(),this.highlightedIndex=this.defaultValue?this.options.indexOf(this.defaultValue):0,this.handleMenuOption(this.defaultValue?this.defaultValue:this.options[0]),this.requestUpdate())}handleClickOutside(e){e.composedPath().includes(this)||this.closePopover()}toggleMenu(){this.closed=!this.closed}handleMouseEnter(e){this.highlightedIndex=e,this.requestUpdate()}handleMenuOption(e){e===this.max&&this.shadowRoot.querySelector(".text-field-input")?.focus(),this.selectedValue=e,this.sendEvent(),this.closePopover()}sendEvent(){let e=new CustomEvent(h,{detail:{option:this.selectedValue},bubbles:!0});this.dispatchEvent(e)}closePopover(){this.closed||this.toggleMenu()}get offerSelect(){return this.querySelector("merch-offer-select")}get popover(){return i` <div class="popover ${this.closed?"closed":"open"}">
${this.options.map((e,t)=>i`
<div
class="item ${t===this.highlightedIndex?"highlighted":""}"
Expand All @@ -147,7 +147,7 @@ import{html as i,LitElement as c}from"../lit-all.min.js";import{css as h}from"..
.value="${this.selectedValue}"
type="number"
@keydown="${this.handleKeydown}"
@keyup="${this.handleKeyup}"
@keyup="${this.handleKeyupDebounced}"
/>
<button class="picker-button" @click="${this.toggleMenu}">
<div
Expand All @@ -156,4 +156,4 @@ import{html as i,LitElement as c}from"../lit-all.min.js";import{css as h}from"..
</button>
${this.popover}
</div>
`}};customElements.define("merch-quantity-select",s);export{s as MerchQuantitySelect};
`}};customElements.define("merch-quantity-select",r);export{r as MerchQuantitySelect};
14 changes: 8 additions & 6 deletions libs/features/mas/src/global.css.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,16 @@ merch-card [slot='callout-content'] {
}

merch-card [slot='callout-content'] .icon-button {
height: 16px;
padding: 0;
border: 0;
min-inline-size: 16px;
position: relative;
top: 3px;
}

merch-card [slot='callout-content'] .icon-button:hover {
background-color: transparent;
merch-card [slot='callout-content'] .icon-button:before {
display: inline-block;
content: '';
width: 14px;
height: 14px;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 14 14" width="14"><title>InfoSmall</title><rect id="ToDelete" fill="%23ff13dc" opacity="0" width="14" height="14" /><path d="M7,.77778A6.22222,6.22222,0,1,0,13.22222,7,6.22222,6.22222,0,0,0,7,.77778ZM6.88333,2.45a1.057,1.057,0,0,1,1.11308.99778q.00273.05018.0007.10044A1.036,1.036,0,0,1,6.88333,4.662,1.05229,1.05229,0,0,1,5.76956,3.54744,1.057,1.057,0,0,1,6.7837,2.44926Q6.83352,2.44728,6.88333,2.45ZM8.55556,10.5a.38889.38889,0,0,1-.38889.38889H5.83333A.38889.38889,0,0,1,5.44444,10.5V9.72222a.3889.3889,0,0,1,.38889-.38889h.38889V7H5.83333a.38889.38889,0,0,1-.38889-.38889V5.83333a.3889.3889,0,0,1,.38889-.38889H7.38889a.38889.38889,0,0,1,.38889.38889v3.5h.38889a.3889.3889,0,0,1,.38889.38889Z" /></svg>')
}

merch-card [slot='detail-s'] {
Expand Down
13 changes: 12 additions & 1 deletion libs/features/mas/src/hydrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,21 @@ export function processPrices(fields, merchCard, mapping) {
appendSlot('prices', fields, merchCard, mapping);
}

function isEmpty(html) {
if (!html) return true;

const parser = new DOMParser();
const dom = parser.parseFromString(html, 'text/html');
return dom.body.textContent.trim() === '';
}

export function processDescription(fields, merchCard, mapping) {
appendSlot('promoText', fields, merchCard, mapping);
appendSlot('description', fields, merchCard, mapping);
appendSlot('callout', fields, merchCard, mapping);
if (!isEmpty(fields['callout'])) {
appendSlot('callout', fields, merchCard, mapping);
}
appendSlot('quantitySelect', fields, merchCard, mapping);
}

export function processStockOffersAndSecureLabel(fields, merchCard, aemFragmentMapping, settings) {
Expand Down
1 change: 1 addition & 0 deletions libs/features/mas/src/mas.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '../../../utils/lana.js';
import './merch-card.js';
import './merch-icon.js';
import './merch-quantity-select.js';
import './aem-fragment.js';
import { updateConfig } from './lana.js';
updateConfig({ sampleRate: 1 });
Expand Down
8 changes: 6 additions & 2 deletions libs/features/mas/src/merch-quantity-select.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { html, LitElement } from 'lit';
import { styles } from './merch-quantity-select.css.js';
import { debounce } from './utils.js';

import { ARROW_DOWN, ARROW_UP, ENTER } from './focus.js';
import { EVENT_MERCH_QUANTITY_SELECTOR_CHANGE } from './constants.js';
Expand Down Expand Up @@ -43,6 +44,7 @@ export class MerchQuantitySelect extends LitElement {
this.boundKeydownListener = this.handleKeydown.bind(this);
this.addEventListener('keydown', this.boundKeydownListener);
window.addEventListener('mousedown', this.handleClickOutside);
this.handleKeyupDebounced = debounce(this.handleKeyup.bind(this), 500);
}

handleKeyup() {
Expand Down Expand Up @@ -93,10 +95,12 @@ export class MerchQuantitySelect extends LitElement {
inputValue > 0 &&
inputValue !== this.selectedValue
) {
const adjustedInputValue =
let adjustedInputValue =
this.maxInput && inputValue > this.maxInput
? this.maxInput
: inputValue;
adjustedInputValue = this.min && adjustedInputValue < this.min
? this.min : adjustedInputValue;
Comment on lines +98 to +103
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let adjustedInputValue =
this.maxInput && inputValue > this.maxInput
? this.maxInput
: inputValue;
adjustedInputValue = this.min && adjustedInputValue < this.min
? this.min : adjustedInputValue;
let adjustedInputValue = inputValue;
if (this.maxInput && inputValue > this.maxInput)
adjustedInputValue = this.maxInput;
if (this.min && adjustedInputValue < this.min)
adjustedInputValue = this.min;

I think this makes it a bit more readable, I found the reassignments and ternaries to be a bit hard to read with the compound conditionals ahead of them. Let me know what you think.

this.selectedValue = adjustedInputValue;
inputField.value = adjustedInputValue;
this.highlightedIndex = this.options.indexOf(adjustedInputValue);
Expand Down Expand Up @@ -210,7 +214,7 @@ export class MerchQuantitySelect extends LitElement {
.value="${this.selectedValue}"
type="number"
@keydown="${this.handleKeydown}"
@keyup="${this.handleKeyup}"
@keyup="${this.handleKeyupDebounced}"
/>
<button class="picker-button" @click="${this.toggleMenu}">
<div
Expand Down
2 changes: 2 additions & 0 deletions libs/features/mas/src/variants/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ export const PLANS_AEM_FRAGMENT_MAPPING = {
description: { tag: 'div', slot: 'body-xs' },
mnemonics: { size: 'l' },
callout: {tag: 'div', slot: 'callout-content'},
quantitySelect: { tag: 'div', slot: 'quantity-select' },
stockOffer: true,
secureLabel: true,
badge: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Card editor reads this mapping to decide which authoring fields will be displayed for 'plans' cards. For card badge (the yellow thing in the upper right corner) we don't have slots but it needs to be mentioned here to display that Badge text field in card editor. The same as for stock checkbox.

ctas: { slot: 'footer', size: 'm' },
style: 'consonant'
};
Expand Down
2 changes: 1 addition & 1 deletion libs/features/mas/test/merch-card.test.html.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ runTests(async () => {
});
event.composedPath = () => [quantitySelect];
inputField.dispatchEvent(event);
await delay(100);
await delay(600);
expect(quantitySelect.selectedValue).to.equal(3);
const button = plansCard.querySelector('.con-button');
expect(button.getAttribute('data-quantity')).to.equal('3');
Expand Down
6 changes: 3 additions & 3 deletions libs/features/mas/test/merch-quantity-select.test.html.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ runTests(async () => {
});
event.composedPath = () => [quantitySelect];
inputField.dispatchEvent(event);
await delay();
await delay(600);
expect(quantitySelect.selectedValue).to.equal(3);
expect(popOver.classList.contains('closed')).to.be.true;
});
Expand All @@ -155,7 +155,7 @@ runTests(async () => {
});
event.composedPath = () => [quantitySelect];
inputField.dispatchEvent(event);
await delay();
await delay(600);
expect(quantitySelect.selectedValue).to.equal(3);
expect(popOver.classList.contains('closed')).to.be.true;
});
Expand Down Expand Up @@ -189,7 +189,7 @@ runTests(async () => {
});
event.composedPath = () => [quantitySelect];
inputField.dispatchEvent(event);
await delay();
await delay(600);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In regards to all of these, is 600 the minimum ms you can delay? If i am reading this correctly, this will add 2.4s to our testing suite.

expect(quantitySelect.selectedValue).to.equal(250);
});
});
Expand Down
Loading