Skip to content

chore: release 1.48.0 #220

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

Merged
merged 8 commits into from
Jul 2, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ describe('InternalNumberControl', () => {
`);

const input = control.renderRoot.querySelector('input')!;
expect(input).to.not.have.attribute('step');
expect(input).to.have.attribute('step', 'any');

control.step = 5;
await control.requestUpdate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class InternalNumberControl extends InternalEditableControl {
'font-medium': !this.readonly,
})}
type="number"
step=${ifDefined(this.step ?? void 0)}
step=${this.step ?? 'any'}
min=${ifDefined(this.min ?? void 0)}
max=${ifDefined(this.max ?? void 0)}
id="input"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,12 @@ describe('ExperimentalAddToCartBuilder', () => {
sub_enddate_format: 'duration',
sub_enddate: '2y',
custom_options: [
{ name: 'Option1', value: 'Option1DefaultValue', value_configurable: true },
{
name: 'Option1',
value: 'Option1DefaultValue',
value_configurable: true,
required: true,
},
],
},
{
Expand Down Expand Up @@ -1332,7 +1337,7 @@ describe('ExperimentalAddToCartBuilder', () => {
</label>
<label>
<span>Option1:</span>
<input name="Option1" value="Option1DefaultValue">
<input name="Option1" value="Option1DefaultValue" placeholder="preview.required" required>
</label>
</fieldset>
<fieldset>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ export class ExperimentalAddToCartBuilder extends Base<Data> {
const optionIndex = product.custom_options.indexOf(group[0]);
const itemCategory = this.__getItemCategoryLoader(productIndex, optionIndex)?.data;
const modifiers = this.__getOptionModifiers(group[0], itemCategory ?? null, currencyCode);
const value = `${group[0].value}${modifiers}`;
const value = `${group[0].value ?? ''}${modifiers}`;
const name = `${prefix}${optionName}`;

if (group[0].value_configurable) {
Expand All @@ -769,9 +769,15 @@ export class ExperimentalAddToCartBuilder extends Base<Data> {
output += `${newline()}<input name="${encodeAttributeValue(name, isHmacOn)}" `;

if (store.use_cart_validation) {
output += `value="--OPEN--" data-replace="${encodeAttributeValue(value, isHmacOn)}">`;
output += `value="--OPEN--" data-replace="${encodeAttributeValue(value, isHmacOn)}"`;
} else {
output += `value="${encodeAttributeValue(value, isHmacOn)}">`;
output += `value="${encodeAttributeValue(value, isHmacOn)}"`;
}

if (group[0].required) {
output += ` placeholder="${encode(this.t('preview.required'))}" required>`;
} else {
output += '>';
}

level--;
Expand All @@ -790,7 +796,10 @@ export class ExperimentalAddToCartBuilder extends Base<Data> {
const optionIndex = product.custom_options.indexOf(option);
const itemCategory = this.__getItemCategoryLoader(productIndex, optionIndex)?.data;
const modifiers = this.__getOptionModifiers(option, itemCategory ?? null, currencyCode);
const encodedValue = encodeAttributeValue(`${option.value}${modifiers}`, isHmacOn);
const encodedValue = encodeAttributeValue(
`${option.value ?? ''}${modifiers}`,
isHmacOn
);
const encodedCaption = encode(option.value ?? '');
output += `${newline()}<option value="${encodedValue}">${encodedCaption}</option>`;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,12 @@ describe('ExperimentalAddToCartBuilder', () => {
});
});

it('makes option basics group readonly if href is set', () => {
it('makes configurable value toggle readonly if name matches an existing option', () => {
const form = new Form();
expect(form.readonlySelector.matches('basics-group', true)).to.be.false;
form.href = 'https://demo.api/virtual/empty';
expect(form.readonlySelector.matches('basics-group', true)).to.be.true;
});

it('makes configurable value toggle disabled if name matches an existing option', () => {
const form = new Form();
expect(form.disabledSelector.matches('basics-group:value-configurable', true)).to.be.false;
expect(form.readonlySelector.matches('basics-group:value-configurable', true)).to.be.false;
form.existingOptions = [{ name: 'foo', value: 'bar' }];
form.edit({ name: 'foo' });
expect(form.disabledSelector.matches('basics-group:value-configurable', true)).to.be.true;
expect(form.readonlySelector.matches('basics-group:value-configurable', true)).to.be.true;
});

it('hides price, weight, code and category groups if value is configurable', () => {
Expand All @@ -100,6 +93,19 @@ describe('ExperimentalAddToCartBuilder', () => {
expect(form.hiddenSelector.matches('category-group', true)).to.be.true;
});

it('hides Required switch if value is not configurable', () => {
const form = new Form();
expect(form.hiddenSelector.matches('basics-group:required', true)).to.be.true;

form.edit({ value_configurable: true });
expect(form.hiddenSelector.matches('basics-group:required', true)).to.be.false;
});

it('always hides timestamps', () => {
const form = new Form();
expect(form.hiddenSelector.matches('timestamps', true)).to.be.true;
});

it('renders a summary control for the basics group', async () => {
const form = await fixture<Form>(
html`<foxy-internal-experimental-add-to-cart-builder-custom-option-form></foxy-internal-experimental-add-to-cart-builder-custom-option-form>`
Expand All @@ -114,14 +120,29 @@ describe('ExperimentalAddToCartBuilder', () => {

it('renders text control for option name inside of the basics group', async () => {
const form = await fixture<Form>(
html`<foxy-internal-experimental-add-to-cart-builder-custom-option-form></foxy-internal-experimental-add-to-cart-builder-custom-option-form>`
html`
<foxy-internal-experimental-add-to-cart-builder-custom-option-form
.existingOptions=${[
{ name: 'foo', value: 'bar' },
{ name: 'baz', value: 'qux' },
{ name: 'quux', value: 'corge' },
]}
>
</foxy-internal-experimental-add-to-cart-builder-custom-option-form>
`
);

const control = form.renderRoot.querySelector(
'[infer="basics-group"] foxy-internal-text-control[infer="name"]'
);

expect(control).to.exist;

control?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
expect(form.form.name).to.equal('quux');

control?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
expect(form.form.name).to.equal('foo');
});

it('renders text control for option value inside of the basics group if value is not configurable', async () => {
Expand Down Expand Up @@ -161,6 +182,25 @@ describe('ExperimentalAddToCartBuilder', () => {
'[infer="basics-group"] foxy-internal-switch-control[infer="value-configurable"]'
);

expect(control).to.exist;
expect(control).to.have.attribute('helper-text-as-tooltip');
expect(control).to.have.attribute('helper-text', '');

form.edit({ name: 'foo' });
form.existingOptions = [{ name: 'foo', value: 'bar' }];
await form.requestUpdate();
expect(control).to.not.have.attribute('helper-text');
});

it('renders switch control for required toggle inside of the basics group', async () => {
const form = await fixture<Form>(
html`<foxy-internal-experimental-add-to-cart-builder-custom-option-form></foxy-internal-experimental-add-to-cart-builder-custom-option-form>`
);

const control = form.renderRoot.querySelector(
'[infer="basics-group"] foxy-internal-switch-control[infer="required"]'
);

expect(control).to.exist;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,24 @@ export class InternalExperimentalAddToCartBuilderCustomOptionForm extends Base<D

currencyCode: string | null = null;

private readonly __nameSetValue = (newValue: string) => {
this.edit({ name: newValue });
if (this.__isAlternative) this.edit({ value_configurable: false, required: false });
};

get readonlySelector(): BooleanSelector {
const alwaysMatch = [super.readonlySelector.toString()];
if (this.href) alwaysMatch.unshift('basics-group');
return new BooleanSelector(alwaysMatch.join(' ').trim());
}

get disabledSelector(): BooleanSelector {
const alwaysMatch = [super.disabledSelector.toString()];

if (!this.href && this.existingOptions.some(o => o.name === this.form.name)) {
alwaysMatch.unshift('basics-group:value-configurable');
}

if (this.__isAlternative) alwaysMatch.unshift('basics-group:value-configurable');
return new BooleanSelector(alwaysMatch.join(' ').trim());
}

get hiddenSelector(): BooleanSelector {
const alwaysMatch = [super.hiddenSelector.toString()];
const alwaysMatch = ['timestamps', super.hiddenSelector.toString()];

if (this.form.value_configurable) {
alwaysMatch.unshift('price-group', 'weight-group', 'code-group', 'category-group');
} else {
alwaysMatch.unshift('basics-group:required');
}

return new BooleanSelector(alwaysMatch.join(' ').trim());
Expand All @@ -61,7 +58,28 @@ export class InternalExperimentalAddToCartBuilderCustomOptionForm extends Base<D
renderBody(): TemplateResult {
return html`
<foxy-internal-summary-control infer="basics-group">
<foxy-internal-text-control layout="summary-item" infer="name"></foxy-internal-text-control>
<foxy-internal-text-control
layout="summary-item"
infer="name"
.setValue=${this.__nameSetValue}
@keydown=${(evt: KeyboardEvent) => {
if (!['ArrowUp', 'ArrowDown'].includes(evt.key)) return;
evt.preventDefault();

const existingNames = this.existingOptions
.map(option => option.name)
.filter((v, i, a) => a.indexOf(v) === i);

const currentIndex = existingNames.indexOf(this.form.name ?? '');
let nextIndex = evt.key === 'ArrowUp' ? currentIndex - 1 : currentIndex + 1;
if (nextIndex < 0) nextIndex = existingNames.length - 1;
if (nextIndex >= existingNames.length) nextIndex = 0;

const nextName = existingNames[nextIndex];
if (nextName && nextName !== this.form.name) this.edit({ name: nextName });
}}
>
</foxy-internal-text-control>

<foxy-internal-text-control
property="value"
Expand All @@ -70,7 +88,15 @@ export class InternalExperimentalAddToCartBuilderCustomOptionForm extends Base<D
>
</foxy-internal-text-control>

<foxy-internal-switch-control infer="value-configurable"></foxy-internal-switch-control>
<foxy-internal-switch-control
helper-text=${ifDefined(this.__isAlternative ? void 0 : '')}
infer="value-configurable"
helper-text-as-tooltip
>
</foxy-internal-switch-control>

<foxy-internal-switch-control infer="required" helper-text-as-tooltip>
</foxy-internal-switch-control>
</foxy-internal-summary-control>

<foxy-internal-summary-control infer="price-group">
Expand Down Expand Up @@ -117,6 +143,8 @@ export class InternalExperimentalAddToCartBuilderCustomOptionForm extends Base<D
@update=${() => this.requestUpdate()}
>
</foxy-nucleon>

${super.renderBody()}
`;
}

Expand All @@ -139,4 +167,9 @@ export class InternalExperimentalAddToCartBuilderCustomOptionForm extends Base<D
const loader = this.renderRoot.querySelector<Loader>('#itemCategoryLoader');
return loader?.data?.default_weight_unit ?? this.defaultWeightUnit;
}

private get __isAlternative() {
const existing = this.existingOptions.filter(o => o.name === this.form.name);
return this.href ? existing.length > 1 : existing.length > 0;
}
}
1 change: 1 addition & 0 deletions src/elements/public/ExperimentalAddToCartBuilder/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface ExperimentalAddToCartSnippet extends Graph {
name: string;
value?: string;
value_configurable?: boolean;
required?: boolean;
price?: number;
replace_price?: boolean;
weight?: number;
Expand Down
Loading