diff --git a/custom-elements.json b/custom-elements.json index 7b6a7ac1..3951c2a3 100644 --- a/custom-elements.json +++ b/custom-elements.json @@ -16812,13 +16812,11 @@ "attributes": [ { "name": "customer-addresses", - "type": "string", "deprecatedMessage": "Link to the collection of customer addresses that can be used with this item." }, { "name": "item-categories", - "description": "Link to the collection of item categories that can be used with this item.", - "type": "string" + "description": "Link to the collection of item categories that can be used with this item." }, { "name": "locale-codes", @@ -16826,9 +16824,12 @@ }, { "name": "coupons", - "type": "string", "deprecatedMessage": "Link to the collection of coupons that can be used with this item." }, + { + "name": "store", + "description": "Link to `fx:store` this item belongs to." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -16919,14 +16920,12 @@ { "name": "customerAddresses", "attribute": "customer-addresses", - "type": "string", "deprecatedMessage": "Link to the collection of customer addresses that can be used with this item." }, { "name": "itemCategories", "attribute": "item-categories", - "description": "Link to the collection of item categories that can be used with this item.", - "type": "string" + "description": "Link to the collection of item categories that can be used with this item." }, { "name": "localeCodes", @@ -16936,9 +16935,13 @@ { "name": "coupons", "attribute": "coupons", - "type": "string", "deprecatedMessage": "Link to the collection of coupons that can be used with this item." }, + { + "name": "store", + "attribute": "store", + "description": "Link to `fx:store` this item belongs to." + }, { "name": "simplifyNsLoading", "attribute": "simplify-ns-loading", @@ -21153,6 +21156,14 @@ "path": "./src/elements/public/PaymentsApiPaymentMethodForm/index.ts", "description": "Form element for the `fx:payment_method` resource of Payments API.\n\n_Payments API is a client-side virtual API layer built on top of hAPI\nin an attempt to streamline access to stores' payment method settings\nthat is currently a bit quirky due to the legacy functionality. To use\nthis element with hAPI, wrap it into a foxy-payments-api node._", "attributes": [ + { + "name": "payment-preset", + "description": "URL of the linked `fx:payment_preset` resource from the virtual Payments API." + }, + { + "name": "store", + "description": "URL of the linked `fx:store` resource." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -21240,10 +21251,20 @@ } ], "properties": [ + { + "name": "paymentPreset", + "attribute": "payment-preset", + "description": "URL of the linked `fx:payment_preset` resource from the virtual Payments API." + }, { "name": "getImageSrc", "description": "A function that returns a URL of a payment method icon based on the given type." }, + { + "name": "store", + "attribute": "store", + "description": "URL of the linked `fx:store` resource." + }, { "name": "simplifyNsLoading", "attribute": "simplify-ns-loading", @@ -24519,19 +24540,23 @@ }, { "name": "store-versions", - "description": "URL of the `fx:store_versions` property helper resource." + "description": "URL of the `fx:store_versions` property helper resource.", + "deprecatedMessage": "All elements in this library are designed to work with store version 2.0." }, { "name": "checkout-types", - "description": "URL of the `fx:checkout_types` property helper resource." + "description": "URL of the `fx:checkout_types` property helper resource.", + "deprecatedMessage": "Checkout type is effectively controlled by the default template config." }, { "name": "locale-codes", - "description": "URL of the `fx:locale_codes` property helper resource." + "description": "URL of the `fx:locale_codes` property helper resource.", + "deprecatedMessage": "Default locale code is effectively controlled by the active template set." }, { "name": "languages", - "description": "URL of the `fx:languages` property helper resource." + "description": "URL of the `fx:languages` property helper resource.", + "deprecatedMessage": "Default language is effectively controlled by the active template set." }, { "name": "timezones", @@ -24645,22 +24670,26 @@ { "name": "storeVersions", "attribute": "store-versions", - "description": "URL of the `fx:store_versions` property helper resource." + "description": "URL of the `fx:store_versions` property helper resource.", + "deprecatedMessage": "All elements in this library are designed to work with store version 2.0." }, { "name": "checkoutTypes", "attribute": "checkout-types", - "description": "URL of the `fx:checkout_types` property helper resource." + "description": "URL of the `fx:checkout_types` property helper resource.", + "deprecatedMessage": "Checkout type is effectively controlled by the default template config." }, { "name": "localeCodes", "attribute": "locale-codes", - "description": "URL of the `fx:locale_codes` property helper resource." + "description": "URL of the `fx:locale_codes` property helper resource.", + "deprecatedMessage": "Default locale code is effectively controlled by the active template set." }, { "name": "languages", "attribute": "languages", - "description": "URL of the `fx:languages` property helper resource." + "description": "URL of the `fx:languages` property helper resource.", + "deprecatedMessage": "Default language is effectively controlled by the active template set." }, { "name": "timezones", @@ -28290,6 +28319,10 @@ "name": "payment-gateways-helper", "description": "URL of the `fx:payment_gateways` property helper resource." }, + { + "name": "locale-codes", + "description": "Link to the `fx:locale_codes` property helper for currency formatting." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -28387,6 +28420,11 @@ "attribute": "payment-gateways-helper", "description": "URL of the `fx:payment_gateways` property helper resource." }, + { + "name": "localeCodes", + "attribute": "locale-codes", + "description": "Link to the `fx:locale_codes` property helper for currency formatting." + }, { "name": "getSubscriptionPageHref" }, @@ -30292,6 +30330,10 @@ "path": "./src/elements/public/WebhookCard/index.ts", "description": "Basic card displaying webhook (`fx:webhook`) info.", "attributes": [ + { + "name": "resource-uri", + "description": "Optional URI of a transaction, customer or subscription. When provided,\nthe form will display logs and statuses for that particular resource only." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -30342,6 +30384,11 @@ } ], "properties": [ + { + "name": "resourceUri", + "attribute": "resource-uri", + "description": "Optional URI of a transaction, customer or subscription. When provided,\nthe form will display logs and statuses for that particular resource only." + }, { "name": "simplifyNsLoading", "attribute": "simplify-ns-loading", @@ -30463,6 +30510,10 @@ "path": "./src/elements/public/WebhookForm/index.ts", "description": "Form element for creating or editing webhooks (`fx:webhook`).", "attributes": [ + { + "name": "resource-uri", + "description": "Optional URI of a transaction, customer or subscription. When provided,\nthe form will display logs and statuses for that particular resource only." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -30550,6 +30601,11 @@ } ], "properties": [ + { + "name": "resourceUri", + "attribute": "resource-uri", + "description": "Optional URI of a transaction, customer or subscription. When provided,\nthe form will display logs and statuses for that particular resource only." + }, { "name": "simplifyNsLoading", "attribute": "simplify-ns-loading", diff --git a/src/elements/internal/InternalAsyncListControl/InternalAsyncListControl.ts b/src/elements/internal/InternalAsyncListControl/InternalAsyncListControl.ts index 7fc8e7c5..13a36f6c 100644 --- a/src/elements/internal/InternalAsyncListControl/InternalAsyncListControl.ts +++ b/src/elements/internal/InternalAsyncListControl/InternalAsyncListControl.ts @@ -307,8 +307,8 @@ export class InternalAsyncListControl extends InternalEditableControl { > `} -
- +
+ ${this.label && this.label !== 'label' ? this.label : ''} diff --git a/src/elements/internal/InternalAsyncResourceLinkListControl/InternalAsyncResourceLinkListControl.ts b/src/elements/internal/InternalAsyncResourceLinkListControl/InternalAsyncResourceLinkListControl.ts index 8db3c432..54efb6df 100644 --- a/src/elements/internal/InternalAsyncResourceLinkListControl/InternalAsyncResourceLinkListControl.ts +++ b/src/elements/internal/InternalAsyncResourceLinkListControl/InternalAsyncResourceLinkListControl.ts @@ -2,10 +2,10 @@ import type { CSSResultArray, PropertyDeclarations } from 'lit-element'; import type { CheckboxElement } from '@vaadin/vaadin-checkbox'; import type { TemplateResult } from 'lit-html'; import type { ItemRenderer } from '../../public/CollectionPage/types'; +import type { Collection } from './types'; import { InternalEditableControl } from '../InternalEditableControl/InternalEditableControl'; import { NucleonElement } from '../../public/NucleonElement/NucleonElement'; -import { getResourceId } from '@foxy.io/sdk/core'; import { ifDefined } from 'lit-html/directives/if-defined'; import { classMap } from '../../../utils/class-map'; import { html } from 'lit-html'; @@ -85,53 +85,31 @@ export class InternalAsyncResourceLinkListControl extends InternalEditableContro `; if (!ctx.href || ctx.href.startsWith('foxy://')) return wrap(render(ctx)); - let linkHref: string | undefined; - const id = getResourceId(ctx.data?._links.self.href ?? ''); + if (this.readonly) return wrap(render(ctx)); - try { - const url = new URL(this.linksHref ?? ''); - url.searchParams.set(this.foreignKeyForId ?? '', String(id ?? '')); - url.searchParams.set('limit', '1'); - linkHref = url.toString(); - } catch { - linkHref = undefined; - } - - const content = html` - this.requestUpdate()} - > - ${render(ctx)} - - `; - - if (this.readonly) return wrap(content); + const foreignKeyForUri = this.foreignKeyForUri; + const linkResource = foreignKeyForUri + ? this.__allLinks?.find(link => link[foreignKeyForUri] === ctx.href) + : undefined; - const nucleon = this.renderRoot.querySelector(`#link-${id}`) as NucleonElement | null; - const checked = !!nucleon?.data?.returned_items; - const isDisabled = this.disabled || !nucleon?.in('idle') || this.__isFetching; + const isDisabled = this.disabled || !this.__allLinks || this.__isFetching; return wrap(html` { const target = evt.currentTarget as CheckboxElement; if (target.checked) { this.__insertLink(ctx.data?._links.self.href ?? ''); } else { - this.__deleteLink( - nucleon?.data?._embedded?.[this.embedKey ?? '']?.[0]?._links.self.href ?? '' - ); + this.__deleteLink(linkResource?._links.self.href ?? ''); } }} >
- ${content} + ${render(ctx)}
`); @@ -150,11 +128,7 @@ export class InternalAsyncResourceLinkListControl extends InternalEditableContro firstHref = undefined; } - const nucleons = [ - ...(this.renderRoot.querySelectorAll('foxy-nucleon') as NodeListOf>), - ]; - - const isStatusVisible = this.__isFetching || nucleons.some(n => !n.in('idle')); + const isStatusVisible = this.__isFetching || !this.__allLinks; return html`
@@ -203,6 +177,8 @@ export class InternalAsyncResourceLinkListControl extends InternalEditableContro > ${this._errorMessage}
+ + ${this.__renderLinkResourceLoaders()}
`; } @@ -256,4 +232,52 @@ export class InternalAsyncResourceLinkListControl extends InternalEditableContro this.__isFetching = false; } + + private __renderLinkResourceLoaders() { + const maxApiLimit = 200; + const firstPage = this.renderRoot.querySelector>('[data-link-page]'); + const totalItems = Number(firstPage?.data?.total_items ?? maxApiLimit); // sometimes total_items is a string in hAPI + const links: string[] = []; + + try { + for (let i = 0; i < Math.max(1, Math.ceil(totalItems / maxApiLimit)); i++) { + const url = new URL(this.linksHref ?? ''); + url.searchParams.set('offset', String(i * maxApiLimit)); + url.searchParams.set('limit', String(maxApiLimit)); + links.push(url.toString()); + } + } catch { + // Do nothing. + } + + return links.map( + href => html` + + ` + ); + } + + private get __allLinks() { + const embedKey = this.embedKey; + if (!embedKey) return null; + + type Loader = NucleonElement; + const loaders = this.renderRoot.querySelectorAll('[data-link-page]'); + const allLinks: any[] = []; + + for (const loader of loaders) { + const embedded = loader.data?._embedded?.[embedKey]; + if (!embedded) return null; + allLinks.push(...embedded); + } + + return allLinks; + } } diff --git a/src/elements/internal/InternalAsyncResourceLinkListControl/types.ts b/src/elements/internal/InternalAsyncResourceLinkListControl/types.ts new file mode 100644 index 00000000..2ac877d1 --- /dev/null +++ b/src/elements/internal/InternalAsyncResourceLinkListControl/types.ts @@ -0,0 +1,21 @@ +import type { Graph, Resource } from '@foxy.io/sdk/core'; + +import type { + CollectionGraphLinks, + CollectionGraphProps, +} from '@foxy.io/sdk/dist/types/core/defaults'; + +interface CollectionResourceItem extends Graph { + curie: string; + props: Record; + links: { self: CollectionResourceItem; [key: string]: any }; +} + +interface CollectionResource extends Graph { + curie: string; + props: CollectionGraphProps; + links: CollectionGraphLinks; + child: CollectionResourceItem; +} + +export type Collection = Resource; diff --git a/src/elements/internal/InternalPasswordControl/InternalPasswordControl.test.ts b/src/elements/internal/InternalPasswordControl/InternalPasswordControl.test.ts index 3e9667b6..755d710a 100644 --- a/src/elements/internal/InternalPasswordControl/InternalPasswordControl.test.ts +++ b/src/elements/internal/InternalPasswordControl/InternalPasswordControl.test.ts @@ -76,6 +76,11 @@ describe('InternalTextControl', () => { expect(new Control()).to.have.property('showGenerator', false); }); + it('has a reactive property "layout"', () => { + expect(Control).to.have.deep.nested.property('properties.layout', {}); + expect(new Control()).to.have.property('layout', null); + }); + it('extends InternalEditableControl', () => { expect(new Control()).to.be.instanceOf(InternalEditableControl); }); @@ -140,6 +145,17 @@ describe('InternalTextControl', () => { expect(field).to.have.property('label', 'test label'); }); + it('sets "theme" on vaadin-password-field from "layout" on itself', async () => { + const layout = html``; + const control = await fixture(layout); + const field = control.renderRoot.querySelector('vaadin-password-field')!; + expect(field).to.not.have.attribute('theme'); + + control.layout = 'summary-item'; + await control.requestUpdate(); + expect(field).to.have.attribute('theme', 'summary-item'); + }); + it('sets "disabled" on vaadin-password-field from "disabled" on itself', async () => { const layout = html``; const control = await fixture(layout); diff --git a/src/elements/internal/InternalPasswordControl/InternalPasswordControl.ts b/src/elements/internal/InternalPasswordControl/InternalPasswordControl.ts index 0e75c7f5..5ec6a72e 100644 --- a/src/elements/internal/InternalPasswordControl/InternalPasswordControl.ts +++ b/src/elements/internal/InternalPasswordControl/InternalPasswordControl.ts @@ -20,6 +20,7 @@ export class InternalPasswordControl extends InternalEditableControl { ...super.properties, generatorOptions: { type: Object, attribute: 'generator-options' }, showGenerator: { type: Boolean, attribute: 'show-generator' }, + layout: {}, }; } @@ -28,6 +29,8 @@ export class InternalPasswordControl extends InternalEditableControl { /** If true, renders the password generator button. */ showGenerator = false; + layout: 'standalone' | 'summary-item' | null = null; + renderControl(): TemplateResult { return html` ${this.label}

-
-

${this.helperText}

+
+

${this.label}

+

${this.helperText}

+
+
+ +
`; } } diff --git a/src/elements/internal/InternalTextControl/InternalTextControl.test.ts b/src/elements/internal/InternalTextControl/InternalTextControl.test.ts index ee4318e5..a106239f 100644 --- a/src/elements/internal/InternalTextControl/InternalTextControl.test.ts +++ b/src/elements/internal/InternalTextControl/InternalTextControl.test.ts @@ -349,4 +349,22 @@ describe('InternalTextControl', () => { submitMethod.restore(); }); + + it('renders prefix text in summary item layout', async () => { + const control = await fixture(html` + + + `); + + expect(control.renderRoot).to.include.text('Test Prefix'); + }); + + it('renders suffix text in summary item layout', async () => { + const control = await fixture(html` + + + `); + + expect(control.renderRoot).to.include.text('Test Suffix'); + }); }); diff --git a/src/elements/internal/InternalTextControl/InternalTextControl.ts b/src/elements/internal/InternalTextControl/InternalTextControl.ts index ccd3e2b7..6d4f9cb5 100644 --- a/src/elements/internal/InternalTextControl/InternalTextControl.ts +++ b/src/elements/internal/InternalTextControl/InternalTextControl.ts @@ -102,6 +102,8 @@ export class InternalTextControl extends InternalEditableControl {
+ ${this.prefix ? html`
${this.prefix}
` : ''} + + ${this.suffix ? html`
${this.suffix}
` : ''} +