@vtex/faststore 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1595 -481
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +31 -7
- package/dist/components.js +35 -13
- package/dist/content-types.json +73 -0
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-access-gate.md +153 -0
- package/dist/docs/account-entry-point.md +1 -2
- package/dist/docs/account-menu.md +164 -0
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/buy-button.md +1 -1
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/cart.md +1 -1
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +1 -0
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +110 -258
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +3 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
- package/dist/matter/account-access-gate/account-access-gate.js +283 -0
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
- package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
- package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
- package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +2 -2
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +88 -0
- package/dist/matter/account-menu/account-menu.js +246 -0
- package/dist/matter/account-menu/account-menu.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +832 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +1 -1
- package/dist/matter/cart/cart.js +2 -2
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.js +5 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +107 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +263 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
- package/dist/matter/product-shelf/product-shelf.js +2 -0
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +387 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +481 -792
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +701 -2
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
- package/dist/sdk/actions/cart-action-provider.js +1 -1
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/session-action-provider.d.ts +1 -1
- package/dist/sdk/actions/session-action-provider.js +1 -1
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
- package/dist/sdk/actions/shipping-action-provider.js +1 -1
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +108 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +1 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +4 -4
- package/dist/sdk/cart/operations.js +1 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.js +1 -1
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
- package/dist/sdk/cart/use-cart-actions.js +4 -4
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/cart/write-gate.js +1 -1
- package/dist/sdk/cart/write-gate.js.map +1 -1
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/index.js +1 -0
- package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-data-provider.js +8 -4
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +6 -2
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/operations.js.map +1 -1
- package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
- package/dist/sdk/identity/use-identity-actions.js +3 -2
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/session/auth-operations.d.ts +1 -1
- package/dist/sdk/session/auth-operations.js.map +1 -1
- package/dist/sdk/session/use-session-actions.d.ts +1 -1
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +65 -0
- package/dist/sdk/session/use-session-revalidation.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
- package/dist/sdk/shipping/option-select-operations.js.map +1 -1
- package/dist/sdk/shipping/request-timeout.js +1 -1
- package/dist/sdk/shipping/request-timeout.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
- package/dist/sdk/shipping/use-shipping-actions.js +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +3 -4
- package/dist/sdk/state/cart.js +6 -7
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.js +3 -3
- package/dist/utils/safe-href.js.map +1 -1
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +2 -1
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/matter/account-access-gate/sign-out-intent.ts
|
|
4
|
+
/**
|
|
5
|
+
* The signal that tells `AccountAccessGate` a signed-out session is the shopper's own doing.
|
|
6
|
+
*
|
|
7
|
+
* Without it the two fight each other: `$actions.session.logout` resolves with the refreshed session
|
|
8
|
+
* in its payload, so the tri-state flips to anonymous *in place*, while the shopper is still standing
|
|
9
|
+
* on an account page. The gate would read that as any other signed-out shopper and redirect to
|
|
10
|
+
* sign-in, carrying a return to the account they just left — racing the sign-out's own navigation.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately not persisted. The intent authorizes exactly one transition and dies with the document
|
|
13
|
+
* that authorized it, which is why the full load to `/` needs no cleanup. The one case where the
|
|
14
|
+
* document comes back with the flag still standing is a back-forward cache restore, and the gate
|
|
15
|
+
* withdraws it there.
|
|
16
|
+
*/
|
|
17
|
+
let intended = false;
|
|
18
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
19
|
+
function notify() {
|
|
20
|
+
for (const subscriber of subscribers) subscriber();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Records that a sign-out is under way. Declared **before** the logout is awaited: the payload
|
|
24
|
+
* refreshes the session in place, and a gate that got its turn between the response landing and the
|
|
25
|
+
* navigation starting would redirect out from under the sign-out.
|
|
26
|
+
*/
|
|
27
|
+
function declareSignOutIntent() {
|
|
28
|
+
if (intended) return;
|
|
29
|
+
intended = true;
|
|
30
|
+
notify();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Takes the intent back. Called when the logout resolves with user errors — the shopper is still
|
|
34
|
+
* signed in and stays on the account page with the failure surfaced — and by the gate on a restored
|
|
35
|
+
* document, where the navigation the intent authorized has already happened.
|
|
36
|
+
*/
|
|
37
|
+
function withdrawSignOutIntent() {
|
|
38
|
+
if (!intended) return;
|
|
39
|
+
intended = false;
|
|
40
|
+
notify();
|
|
41
|
+
}
|
|
42
|
+
function subscribe(onStoreChange) {
|
|
43
|
+
subscribers.add(onStoreChange);
|
|
44
|
+
return () => {
|
|
45
|
+
subscribers.delete(onStoreChange);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function getSnapshot() {
|
|
49
|
+
return intended;
|
|
50
|
+
}
|
|
51
|
+
/** Server renders never carry an intent: nothing has been able to click sign out yet. */
|
|
52
|
+
function getServerSnapshot() {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/** Whether a sign-out is under way, re-rendering the caller when that changes. */
|
|
56
|
+
function useSignOutIntent() {
|
|
57
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { declareSignOutIntent, useSignOutIntent, withdrawSignOutIntent };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=sign-out-intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-out-intent.js","names":[],"sources":["../../../src/matter/account-access-gate/sign-out-intent.ts"],"sourcesContent":["'use client';\n\nimport { useSyncExternalStore } from 'react';\n\n/**\n * The signal that tells `AccountAccessGate` a signed-out session is the shopper's own doing.\n *\n * Without it the two fight each other: `$actions.session.logout` resolves with the refreshed session\n * in its payload, so the tri-state flips to anonymous *in place*, while the shopper is still standing\n * on an account page. The gate would read that as any other signed-out shopper and redirect to\n * sign-in, carrying a return to the account they just left — racing the sign-out's own navigation.\n *\n * Deliberately not persisted. The intent authorizes exactly one transition and dies with the document\n * that authorized it, which is why the full load to `/` needs no cleanup. The one case where the\n * document comes back with the flag still standing is a back-forward cache restore, and the gate\n * withdraws it there.\n */\nlet intended = false;\n\nconst subscribers = new Set<() => void>();\n\nfunction notify(): void {\n for (const subscriber of subscribers) {\n subscriber();\n }\n}\n\n/**\n * Records that a sign-out is under way. Declared **before** the logout is awaited: the payload\n * refreshes the session in place, and a gate that got its turn between the response landing and the\n * navigation starting would redirect out from under the sign-out.\n */\nexport function declareSignOutIntent(): void {\n if (intended) {\n return;\n }\n intended = true;\n notify();\n}\n\n/**\n * Takes the intent back. Called when the logout resolves with user errors — the shopper is still\n * signed in and stays on the account page with the failure surfaced — and by the gate on a restored\n * document, where the navigation the intent authorized has already happened.\n */\nexport function withdrawSignOutIntent(): void {\n if (!intended) {\n return;\n }\n intended = false;\n notify();\n}\n\nfunction subscribe(onStoreChange: () => void): () => void {\n subscribers.add(onStoreChange);\n return () => {\n subscribers.delete(onStoreChange);\n };\n}\n\nfunction getSnapshot(): boolean {\n return intended;\n}\n\n/** Server renders never carry an intent: nothing has been able to click sign out yet. */\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\n/** Whether a sign-out is under way, re-rendering the caller when that changes. */\nexport function useSignOutIntent(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,IAAI,WAAW;AAEf,MAAM,8BAAc,IAAI,IAAgB;AAExC,SAAS,SAAe;CACtB,KAAK,MAAM,cAAc,aACvB,WAAW;AAEf;;;;;;AAOA,SAAgB,uBAA6B;CAC3C,IAAI,UACF;CAEF,WAAW;CACX,OAAO;AACT;;;;;;AAOA,SAAgB,wBAA8B;CAC5C,IAAI,CAAC,UACH;CAEF,WAAW;CACX,OAAO;AACT;AAEA,SAAS,UAAU,eAAuC;CACxD,YAAY,IAAI,aAAa;CAC7B,aAAa;EACX,YAAY,OAAO,aAAa;CAClC;AACF;AAEA,SAAS,cAAuB;CAC9B,OAAO;AACT;;AAGA,SAAS,oBAA6B;CACpC,OAAO;AACT;;AAGA,SAAgB,mBAA4B;CAC1C,OAAO,qBAAqB,WAAW,aAAa,iBAAiB;AACvE"}
|
|
@@ -3,9 +3,9 @@ import { defineComponent } from "../../core/dist/define-component.js";
|
|
|
3
3
|
import { defineContentType } from "../../core/dist/define-content-type.js";
|
|
4
4
|
import "../../core/dist/index.js";
|
|
5
5
|
import { useSession } from "../session/session.js";
|
|
6
|
-
import { Icon } from "../../particles/icon/icon.js";
|
|
7
6
|
import { resolveSignInHref } from "../../utils/safe-href.js";
|
|
8
7
|
import { NavigationLink } from "../../particles/navigation-link/navigation-link.js";
|
|
8
|
+
import { Icon } from "../../particles/icon/icon.js";
|
|
9
9
|
import { useEffect, useState } from "react";
|
|
10
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { usePathname } from "next/navigation";
|
|
@@ -72,7 +72,7 @@ const AccountEntryPointDefinition = defineComponent({
|
|
|
72
72
|
label: "Loading Announcement"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
|
-
guideline: "Session-aware header control rendering one link: the signed-out label toward /login for anonymous and identified shoppers, the signed-in label toward the account route for logged-in ones, and a politely announced placeholder while the session resolves. Both destinations are fixed conventions, not props, so no authored value can misroute the auth flow. Must be inside a SessionRoot, normally the one hosted by the shared layout
|
|
75
|
+
guideline: "Session-aware header control rendering one link: the signed-out label toward /login for anonymous and identified shoppers, the signed-in label toward the account route for logged-in ones, and a politely announced placeholder while the session resolves. Both destinations are fixed conventions, not props, so no authored value can misroute the auth flow. Must be inside a SessionRoot, normally the one hosted by the shared layout; a page-hosted provider also works, since the server-side session fetch forwards the shopper cookie. Author the label for a state or that state renders nothing; the labels are optional in the type only because a CMS entry may supply them, and an entry overrides all four fields at runtime. Style it through data-fs-account-entry-point and switch on data-fs-account-entry-point-state."
|
|
76
76
|
});
|
|
77
77
|
function AccountEntryPoint({ signedOutLabel, signedInLabel, icon, loadingLabel, $content, className, ref, ...props }) {
|
|
78
78
|
const { state, loading } = useSession();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-entry-point.js","names":[],"sources":["../../../src/matter/account-entry-point/account-entry-point.tsx"],"sourcesContent":["'use client';\n\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport { usePathname } from 'next/navigation';\nimport { type ComponentPropsWithRef, useEffect, useState } from 'react';\nimport { Icon } from '../../particles/icon/icon.js';\nimport { NavigationLink } from '../../particles/navigation-link/navigation-link.js';\nimport { resolveSignInHref } from '../../utils/safe-href.js';\nimport { useSession } from '../session/session.js';\n\n/** The account's root. A fixed convention, never an authored value: an authored destination could\n * misroute the entry into the account. */\nconst ACCOUNT_ROUTE = '/account';\n\n// Nothing is required: an entry may author any subset, and a slice missing a required property is\n// shape-invalid, which drops the whole element rather than the one field.\nexport const AccountEntryPointContent = defineContentType({\n id: 'account-entry-point',\n title: 'Account Entry Point',\n description: 'Labels and icon for the header sign-in / account control.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n signedOutLabel: { type: 'string', minLength: 1, title: 'Signed-out label' },\n signedInLabel: { type: 'string', minLength: 1, title: 'Signed-in label' },\n icon: { type: 'string', minLength: 1, title: 'Icon' },\n loadingLabel: { type: 'string', minLength: 1, title: 'Loading announcement' },\n },\n },\n});\n\n/** Props for {@link AccountEntryPoint}. Extends native `<a>` attributes (the rendered root). */\nexport type AccountEntryPointProps = Omit<ComponentPropsWithRef<'a'>, 'href' | 'children'> & {\n /** Label for anonymous and identified shoppers. Optional: the render gate, not the type,\n * requires a resolved value. */\n signedOutLabel?: string;\n /** Label for logged-in shoppers. Same gate. */\n signedInLabel?: string;\n /** Icon reference for the glyph. Never gated: a control with no glyph is a valid configuration. */\n icon?: string;\n /** Text the live region announces while the session resolves. */\n loadingLabel?: string;\n /** Merchant-authored fields, sliced from the page/layout content entry; win over the props field\n * by field when present. Absent in a non-CMS store — the props are the fallback. */\n $content?: ContentValue<typeof AccountEntryPointContent>;\n};\n\n/** Props for {@link AccountEntryPointLoading}. Extends native `<span>` attributes. */\nexport type AccountEntryPointLoadingProps = Omit<ComponentPropsWithRef<'span'>, 'children'>;\n\nexport const AccountEntryPointDefinition = defineComponent<AccountEntryPointProps>({\n name: 'AccountEntryPoint',\n importPath: '@vtex/faststore/components',\n component: AccountEntryPoint,\n content: AccountEntryPointContent,\n editor: { label: 'Account Entry Point', category: 'identity' },\n requiresContext: 'session',\n schema: {\n signedOutLabel: { type: 'string', label: 'Signed-out Label' },\n signedInLabel: { type: 'string', label: 'Signed-in Label' },\n icon: { type: 'icon', label: 'Icon' },\n loadingLabel: { type: 'string', label: 'Loading Announcement' },\n },\n guideline:\n 'Session-aware header control rendering one link: the signed-out label toward /login for anonymous and identified shoppers, the signed-in label toward the account route for logged-in ones, and a politely announced placeholder while the session resolves. Both destinations are fixed conventions, not props, so no authored value can misroute the auth flow. Must be inside a SessionRoot, normally the one hosted by the shared layout — a page-hosted provider resolves the session server-side without the shopper cookie and sticks on the signed-out label. Author the label for a state or that state renders nothing; the labels are optional in the type only because a CMS entry may supply them, and an entry overrides all four fields at runtime. Style it through data-fs-account-entry-point and switch on data-fs-account-entry-point-state.',\n});\n\nexport function AccountEntryPoint({\n signedOutLabel,\n signedInLabel,\n icon,\n loadingLabel,\n $content,\n className,\n ref,\n ...props\n}: AccountEntryPointProps) {\n const { state, loading } = useSession();\n const pathname = usePathname();\n\n // Blank entry fields fall through, so clearing one in the Admin reads as unauthored.\n const entrySignedOutLabel = $content?.signedOutLabel?.trim() ? $content.signedOutLabel : signedOutLabel;\n const entrySignedInLabel = $content?.signedInLabel?.trim() ? $content.signedInLabel : signedInLabel;\n const entryIcon = $content?.icon?.trim() ? $content.icon : icon;\n const entryLoadingLabel = $content?.loadingLabel?.trim() ? $content.loadingLabel : loadingLabel;\n\n // Assistive technology only reports a change inside a region that was already in the tree, so a\n // region mounted together with its text announces nothing. The region therefore holds the same\n // position in both branches — React keeps it mounted across the swap — and starts empty, with the\n // label written on the pass after mount and cleared again once the session resolves.\n //\n // It trails the branch content rather than leading it so the authored swatch, which lands on\n // whichever element leads, can still reach the region through a following-sibling selector.\n const [announcement, setAnnouncement] = useState('');\n const resolvingLabel = loading ? entryLoadingLabel : undefined;\n useEffect(() => {\n setAnnouncement(resolvingLabel ?? '');\n }, [resolvingLabel]);\n\n // Gate on `loading` before `state`: an unresolved session reports `anonymous`, so reading the\n // tri-state first would flash \"Sign In\" at a shopper who is already signed in.\n if (loading) {\n return (\n <>\n <AccountEntryPointLoading {...props} className={className} />\n <AccountEntryPointAnnouncement>{announcement}</AccountEntryPointAnnouncement>\n </>\n );\n }\n\n // `identified` stays on the signed-out link: an identified but unauthenticated shopper cannot\n // reach their account yet.\n const signedIn = state === 'loggedIn';\n const label = signedIn ? entrySignedInLabel : entrySignedOutLabel;\n\n // A focusable link a screen reader announces as its bare URL is worse than no control at all.\n if (!label?.trim()) {\n return null;\n }\n\n return (\n <>\n <NavigationLink\n {...props}\n ref={ref}\n href={signedIn ? ACCOUNT_ROUTE : resolveSignInHref(pathname)}\n // The signed-out destination is a deferred auth proxy that may set a cookie or redirect, and\n // a header control is always in the viewport — the default prefetch would fire that GET on\n // every page view. The account route is an ordinary page, so it keeps the default.\n prefetch={signedIn ? undefined : false}\n className={className}\n data-fs-account-entry-point=\"\"\n data-fs-account-entry-point-state={signedIn ? 'signed-in' : 'signed-out'}\n >\n {entryIcon ? <Icon name={entryIcon} /> : null}\n <span data-fs-account-entry-point-label=\"\">{label}</span>\n </NavigationLink>\n <AccountEntryPointAnnouncement>{announcement}</AccountEntryPointAnnouncement>\n </>\n );\n}\n\n/**\n * The live region that carries the control across the wait. Separate from the visual placeholder\n * because it has to outlive it: the placeholder is only in the tree while the session resolves,\n * while the region has to be there before the text lands for the text to be announced at all.\n */\nfunction AccountEntryPointAnnouncement({ children }: { children: string }) {\n return (\n <output aria-live=\"polite\" data-fs-account-entry-point-announcement=\"\">\n {children}\n </output>\n );\n}\n\n/**\n * The visual stand-in occupying the control's slot while the session resolves. Purely visual: the\n * wait is spoken by the sibling live region, so this is hidden from assistive technology rather\n * than announced twice. Named for the state it represents, as `ShippingSimulatorLoading` is, not\n * for the bar it draws.\n */\nexport function AccountEntryPointLoading({ className, ...props }: AccountEntryPointLoadingProps) {\n return <span {...props} aria-hidden=\"true\" className={className} data-fs-account-entry-point-loading=\"\" />;\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,MAAM,gBAAgB;AAItB,MAAa,2BAA2B,kBAAkB;CACxD,IAAI;CACJ,OAAO;CACP,aAAa;CACb,QAAQ;EACN,MAAM;EACN,sBAAsB;EACtB,YAAY;GACV,gBAAgB;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAmB;GAC1E,eAAe;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAkB;GACxE,MAAM;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAO;GACpD,cAAc;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAuB;EAC9E;CACF;AACF,CAAC;AAqBD,MAAa,8BAA8B,gBAAwC;CACjF,MAAM;CACN,YAAY;CACZ,WAAW;CACX,SAAS;CACT,QAAQ;EAAE,OAAO;EAAuB,UAAU;CAAW;CAC7D,iBAAiB;CACjB,QAAQ;EACN,gBAAgB;GAAE,MAAM;GAAU,OAAO;EAAmB;EAC5D,eAAe;GAAE,MAAM;GAAU,OAAO;EAAkB;EAC1D,MAAM;GAAE,MAAM;GAAQ,OAAO;EAAO;EACpC,cAAc;GAAE,MAAM;GAAU,OAAO;EAAuB;CAChE;CACA,WACE;AACJ,CAAC;AAED,SAAgB,kBAAkB,EAChC,gBACA,eACA,MACA,cACA,UACA,WACA,KACA,GAAG,SACsB;CACzB,MAAM,EAAE,OAAO,YAAY,WAAW;CACtC,MAAM,WAAW,YAAY;CAG7B,MAAM,sBAAsB,UAAU,gBAAgB,KAAK,IAAI,SAAS,iBAAiB;CACzF,MAAM,qBAAqB,UAAU,eAAe,KAAK,IAAI,SAAS,gBAAgB;CACtF,MAAM,YAAY,UAAU,MAAM,KAAK,IAAI,SAAS,OAAO;CAC3D,MAAM,oBAAoB,UAAU,cAAc,KAAK,IAAI,SAAS,eAAe;CASnF,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CACnD,MAAM,iBAAiB,UAAU,oBAAoB,KAAA;CACrD,gBAAgB;EACd,gBAAgB,kBAAkB,EAAE;CACtC,GAAG,CAAC,cAAc,CAAC;CAInB,IAAI,SACF,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,0BAAD;EAA0B,GAAI;EAAkB;CAAY,CAAA,GAC5D,oBAAC,+BAAD,EAAA,UAAgC,aAA4C,CAAA,CAC5E,EAAA,CAAA;CAMN,MAAM,WAAW,UAAU;CAC3B,MAAM,QAAQ,WAAW,qBAAqB;CAG9C,IAAI,CAAC,OAAO,KAAK,GACf,OAAO;CAGT,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,gBAAD;EACE,GAAI;EACC;EACL,MAAM,WAAW,gBAAgB,kBAAkB,QAAQ;EAI3D,UAAU,WAAW,KAAA,IAAY;EACtB;EACX,+BAA4B;EAC5B,qCAAmC,WAAW,cAAc;EAV9D,UAAA,CAYG,YAAY,oBAAC,MAAD,EAAM,MAAM,UAAY,CAAA,IAAI,MACzC,oBAAC,QAAD;GAAM,qCAAkC;GAAI,UAAA;EAAY,CAAA,CAC1C;CAChB,CAAA,GAAA,oBAAC,+BAAD,EAAA,UAAgC,aAA4C,CAAA,CAC5E,EAAA,CAAA;AAEN;;;;;;AAOA,SAAS,8BAA8B,EAAE,YAAkC;CACzE,OACE,oBAAC,UAAD;EAAQ,aAAU;EAAS,4CAAyC;EACjE;CACK,CAAA;AAEZ;;;;;;;AAQA,SAAgB,yBAAyB,EAAE,WAAW,GAAG,SAAwC;CAC/F,OAAO,oBAAC,QAAD;EAAM,GAAI;EAAO,eAAY;EAAkB;EAAW,uCAAoC;CAAI,CAAA;AAC3G"}
|
|
1
|
+
{"version":3,"file":"account-entry-point.js","names":[],"sources":["../../../src/matter/account-entry-point/account-entry-point.tsx"],"sourcesContent":["'use client';\n\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport { usePathname } from 'next/navigation';\nimport { type ComponentPropsWithRef, useEffect, useState } from 'react';\nimport { Icon } from '../../particles/icon/icon.js';\nimport { NavigationLink } from '../../particles/navigation-link/navigation-link.js';\nimport { resolveSignInHref } from '../../utils/safe-href.js';\nimport { useSession } from '../session/session.js';\n\n/** The account's root. A fixed convention, never an authored value: an authored destination could\n * misroute the entry into the account. */\nconst ACCOUNT_ROUTE = '/account';\n\n// Nothing is required: an entry may author any subset, and a slice missing a required property is\n// shape-invalid, which drops the whole element rather than the one field.\nexport const AccountEntryPointContent = defineContentType({\n id: 'account-entry-point',\n title: 'Account Entry Point',\n description: 'Labels and icon for the header sign-in / account control.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n signedOutLabel: { type: 'string', minLength: 1, title: 'Signed-out label' },\n signedInLabel: { type: 'string', minLength: 1, title: 'Signed-in label' },\n icon: { type: 'string', minLength: 1, title: 'Icon' },\n loadingLabel: { type: 'string', minLength: 1, title: 'Loading announcement' },\n },\n },\n});\n\n/** Props for {@link AccountEntryPoint}. Extends native `<a>` attributes (the rendered root). */\nexport type AccountEntryPointProps = Omit<ComponentPropsWithRef<'a'>, 'href' | 'children'> & {\n /** Label for anonymous and identified shoppers. Optional: the render gate, not the type,\n * requires a resolved value. */\n signedOutLabel?: string;\n /** Label for logged-in shoppers. Same gate. */\n signedInLabel?: string;\n /** Icon reference for the glyph. Never gated: a control with no glyph is a valid configuration. */\n icon?: string;\n /** Text the live region announces while the session resolves. */\n loadingLabel?: string;\n /** Merchant-authored fields, sliced from the page/layout content entry; win over the props field\n * by field when present. Absent in a non-CMS store — the props are the fallback. */\n $content?: ContentValue<typeof AccountEntryPointContent>;\n};\n\n/** Props for {@link AccountEntryPointLoading}. Extends native `<span>` attributes. */\nexport type AccountEntryPointLoadingProps = Omit<ComponentPropsWithRef<'span'>, 'children'>;\n\nexport const AccountEntryPointDefinition = defineComponent<AccountEntryPointProps>({\n name: 'AccountEntryPoint',\n importPath: '@vtex/faststore/components',\n component: AccountEntryPoint,\n content: AccountEntryPointContent,\n editor: { label: 'Account Entry Point', category: 'identity' },\n requiresContext: 'session',\n schema: {\n signedOutLabel: { type: 'string', label: 'Signed-out Label' },\n signedInLabel: { type: 'string', label: 'Signed-in Label' },\n icon: { type: 'icon', label: 'Icon' },\n loadingLabel: { type: 'string', label: 'Loading Announcement' },\n },\n guideline:\n 'Session-aware header control rendering one link: the signed-out label toward /login for anonymous and identified shoppers, the signed-in label toward the account route for logged-in ones, and a politely announced placeholder while the session resolves. Both destinations are fixed conventions, not props, so no authored value can misroute the auth flow. Must be inside a SessionRoot, normally the one hosted by the shared layout; a page-hosted provider also works, since the server-side session fetch forwards the shopper cookie. Author the label for a state or that state renders nothing; the labels are optional in the type only because a CMS entry may supply them, and an entry overrides all four fields at runtime. Style it through data-fs-account-entry-point and switch on data-fs-account-entry-point-state.',\n});\n\nexport function AccountEntryPoint({\n signedOutLabel,\n signedInLabel,\n icon,\n loadingLabel,\n $content,\n className,\n ref,\n ...props\n}: AccountEntryPointProps) {\n const { state, loading } = useSession();\n const pathname = usePathname();\n\n // Blank entry fields fall through, so clearing one in the Admin reads as unauthored.\n const entrySignedOutLabel = $content?.signedOutLabel?.trim() ? $content.signedOutLabel : signedOutLabel;\n const entrySignedInLabel = $content?.signedInLabel?.trim() ? $content.signedInLabel : signedInLabel;\n const entryIcon = $content?.icon?.trim() ? $content.icon : icon;\n const entryLoadingLabel = $content?.loadingLabel?.trim() ? $content.loadingLabel : loadingLabel;\n\n // Assistive technology only reports a change inside a region that was already in the tree, so a\n // region mounted together with its text announces nothing. The region therefore holds the same\n // position in both branches — React keeps it mounted across the swap — and starts empty, with the\n // label written on the pass after mount and cleared again once the session resolves.\n //\n // It trails the branch content rather than leading it so the authored swatch, which lands on\n // whichever element leads, can still reach the region through a following-sibling selector.\n const [announcement, setAnnouncement] = useState('');\n const resolvingLabel = loading ? entryLoadingLabel : undefined;\n useEffect(() => {\n setAnnouncement(resolvingLabel ?? '');\n }, [resolvingLabel]);\n\n // Gate on `loading` before `state`: an unresolved session reports `anonymous`, so reading the\n // tri-state first would flash \"Sign In\" at a shopper who is already signed in.\n if (loading) {\n return (\n <>\n <AccountEntryPointLoading {...props} className={className} />\n <AccountEntryPointAnnouncement>{announcement}</AccountEntryPointAnnouncement>\n </>\n );\n }\n\n // `identified` stays on the signed-out link: an identified but unauthenticated shopper cannot\n // reach their account yet.\n const signedIn = state === 'loggedIn';\n const label = signedIn ? entrySignedInLabel : entrySignedOutLabel;\n\n // A focusable link a screen reader announces as its bare URL is worse than no control at all.\n if (!label?.trim()) {\n return null;\n }\n\n return (\n <>\n <NavigationLink\n {...props}\n ref={ref}\n href={signedIn ? ACCOUNT_ROUTE : resolveSignInHref(pathname)}\n // The signed-out destination is a deferred auth proxy that may set a cookie or redirect, and\n // a header control is always in the viewport — the default prefetch would fire that GET on\n // every page view. The account route is an ordinary page, so it keeps the default.\n prefetch={signedIn ? undefined : false}\n className={className}\n data-fs-account-entry-point=\"\"\n data-fs-account-entry-point-state={signedIn ? 'signed-in' : 'signed-out'}\n >\n {entryIcon ? <Icon name={entryIcon} /> : null}\n <span data-fs-account-entry-point-label=\"\">{label}</span>\n </NavigationLink>\n <AccountEntryPointAnnouncement>{announcement}</AccountEntryPointAnnouncement>\n </>\n );\n}\n\n/**\n * The live region that carries the control across the wait. Separate from the visual placeholder\n * because it has to outlive it: the placeholder is only in the tree while the session resolves,\n * while the region has to be there before the text lands for the text to be announced at all.\n */\nfunction AccountEntryPointAnnouncement({ children }: { children: string }) {\n return (\n <output aria-live=\"polite\" data-fs-account-entry-point-announcement=\"\">\n {children}\n </output>\n );\n}\n\n/**\n * The visual stand-in occupying the control's slot while the session resolves. Purely visual: the\n * wait is spoken by the sibling live region, so this is hidden from assistive technology rather\n * than announced twice. Named for the state it represents, as `ShippingSimulatorLoading` is, not\n * for the bar it draws.\n */\nexport function AccountEntryPointLoading({ className, ...props }: AccountEntryPointLoadingProps) {\n return <span {...props} aria-hidden=\"true\" className={className} data-fs-account-entry-point-loading=\"\" />;\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,MAAM,gBAAgB;AAItB,MAAa,2BAA2B,kBAAkB;CACxD,IAAI;CACJ,OAAO;CACP,aAAa;CACb,QAAQ;EACN,MAAM;EACN,sBAAsB;EACtB,YAAY;GACV,gBAAgB;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAmB;GAC1E,eAAe;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAkB;GACxE,MAAM;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAO;GACpD,cAAc;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;GAAuB;EAC9E;CACF;AACF,CAAC;AAqBD,MAAa,8BAA8B,gBAAwC;CACjF,MAAM;CACN,YAAY;CACZ,WAAW;CACX,SAAS;CACT,QAAQ;EAAE,OAAO;EAAuB,UAAU;CAAW;CAC7D,iBAAiB;CACjB,QAAQ;EACN,gBAAgB;GAAE,MAAM;GAAU,OAAO;EAAmB;EAC5D,eAAe;GAAE,MAAM;GAAU,OAAO;EAAkB;EAC1D,MAAM;GAAE,MAAM;GAAQ,OAAO;EAAO;EACpC,cAAc;GAAE,MAAM;GAAU,OAAO;EAAuB;CAChE;CACA,WACE;AACJ,CAAC;AAED,SAAgB,kBAAkB,EAChC,gBACA,eACA,MACA,cACA,UACA,WACA,KACA,GAAG,SACsB;CACzB,MAAM,EAAE,OAAO,YAAY,WAAW;CACtC,MAAM,WAAW,YAAY;CAG7B,MAAM,sBAAsB,UAAU,gBAAgB,KAAK,IAAI,SAAS,iBAAiB;CACzF,MAAM,qBAAqB,UAAU,eAAe,KAAK,IAAI,SAAS,gBAAgB;CACtF,MAAM,YAAY,UAAU,MAAM,KAAK,IAAI,SAAS,OAAO;CAC3D,MAAM,oBAAoB,UAAU,cAAc,KAAK,IAAI,SAAS,eAAe;CASnF,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CACnD,MAAM,iBAAiB,UAAU,oBAAoB,KAAA;CACrD,gBAAgB;EACd,gBAAgB,kBAAkB,EAAE;CACtC,GAAG,CAAC,cAAc,CAAC;CAInB,IAAI,SACF,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,0BAAD;EAA0B,GAAI;EAAkB;CAAY,CAAA,GAC5D,oBAAC,+BAAD,EAAA,UAAgC,aAA4C,CAAA,CAC5E,EAAA,CAAA;CAMN,MAAM,WAAW,UAAU;CAC3B,MAAM,QAAQ,WAAW,qBAAqB;CAG9C,IAAI,CAAC,OAAO,KAAK,GACf,OAAO;CAGT,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,gBAAD;EACE,GAAI;EACC;EACL,MAAM,WAAW,gBAAgB,kBAAkB,QAAQ;EAI3D,UAAU,WAAW,KAAA,IAAY;EACtB;EACX,+BAA4B;EAC5B,qCAAmC,WAAW,cAAc;EAV9D,UAAA,CAYG,YAAY,oBAAC,MAAD,EAAM,MAAM,UAAY,CAAA,IAAI,MACzC,oBAAC,QAAD;GAAM,qCAAkC;GAAI,UAAA;EAAY,CAAA,CAC1C;CAChB,CAAA,GAAA,oBAAC,+BAAD,EAAA,UAAgC,aAA4C,CAAA,CAC5E,EAAA,CAAA;AAEN;;;;;;AAOA,SAAS,8BAA8B,EAAE,YAAkC;CACzE,OACE,oBAAC,UAAD;EAAQ,aAAU;EAAS,4CAAyC;EACjE;CACK,CAAA;AAEZ;;;;;;;AAQA,SAAgB,yBAAyB,EAAE,WAAW,GAAG,SAAwC;CAC/F,OAAO,oBAAC,QAAD;EAAM,GAAI;EAAO,eAAY;EAAkB;EAAW,uCAAoC;CAAI,CAAA;AAC3G"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ContentValue } from "../../core/dist/define-content-type.js";
|
|
2
|
+
import { ComponentDefinition } from "../../core/dist/define-component.js";
|
|
3
|
+
import "../../core/dist/index.js";
|
|
4
|
+
import { ComponentPropsWithRef } from "react";
|
|
5
|
+
//#region src/matter/account-menu/account-menu.d.ts
|
|
6
|
+
export declare const AccountMenuContent: {
|
|
7
|
+
readonly id: "account-menu";
|
|
8
|
+
readonly title: "Account Menu";
|
|
9
|
+
readonly description: "The ordered list of the account's sections, editable without a deployment.";
|
|
10
|
+
readonly schema: {
|
|
11
|
+
readonly type: "object";
|
|
12
|
+
readonly additionalProperties: false;
|
|
13
|
+
readonly properties: {
|
|
14
|
+
readonly items: {
|
|
15
|
+
readonly type: "array";
|
|
16
|
+
readonly title: "Sections";
|
|
17
|
+
readonly minItems: 1;
|
|
18
|
+
readonly items: {
|
|
19
|
+
readonly type: "object";
|
|
20
|
+
readonly additionalProperties: false;
|
|
21
|
+
readonly required: readonly ["label", "href"];
|
|
22
|
+
readonly properties: {
|
|
23
|
+
readonly label: {
|
|
24
|
+
readonly type: "string";
|
|
25
|
+
readonly minLength: 1;
|
|
26
|
+
readonly title: "Label";
|
|
27
|
+
};
|
|
28
|
+
readonly href: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly minLength: 1;
|
|
31
|
+
readonly title: "URL";
|
|
32
|
+
readonly format: "uri-reference";
|
|
33
|
+
readonly pattern: "^(?:https?://.*|mailto:.+|tel:.+|/(?:[^/].*)?)$";
|
|
34
|
+
};
|
|
35
|
+
readonly external: {
|
|
36
|
+
readonly type: "boolean";
|
|
37
|
+
readonly title: "Leaves the account";
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
type AccountMenuContentValue = ContentValue<typeof AccountMenuContent>;
|
|
46
|
+
/** One authored section. Derived from the model so the prop and content channels cannot drift. */
|
|
47
|
+
export type AccountMenuSection = NonNullable<AccountMenuContentValue['items']>[number];
|
|
48
|
+
/** A section resolved for render. A null `href` is a destination the allowlist refused. */
|
|
49
|
+
export type AccountMenuResolvedSection = {
|
|
50
|
+
label: string;
|
|
51
|
+
href: string | null;
|
|
52
|
+
external: boolean;
|
|
53
|
+
};
|
|
54
|
+
export type AccountMenuCurrent = {
|
|
55
|
+
/** Index into the resolved list. */
|
|
56
|
+
index: number;
|
|
57
|
+
/** `exact` when the path is the section; `ancestor` when the shopper is inside it. */
|
|
58
|
+
match: 'exact' | 'ancestor';
|
|
59
|
+
};
|
|
60
|
+
export type AccountMenuContextValue = {
|
|
61
|
+
/** Resolved, in the merchant's order, with blank-label sections dropped. */
|
|
62
|
+
items: readonly AccountMenuResolvedSection[];
|
|
63
|
+
/** The current section, or null when the path matches none. */
|
|
64
|
+
current: AccountMenuCurrent | null;
|
|
65
|
+
};
|
|
66
|
+
export type AccountMenuRootProps = ComponentPropsWithRef<'div'> & {
|
|
67
|
+
/** Authored fallback for the section list, used when no CMS entry supplies one. */
|
|
68
|
+
items?: readonly AccountMenuSection[];
|
|
69
|
+
/** Injected by the compiler, sliced from the page's or layout's composite content entry. */
|
|
70
|
+
$content?: AccountMenuContentValue;
|
|
71
|
+
};
|
|
72
|
+
/** The landmark renders one item per section, so it takes no children. */
|
|
73
|
+
export type AccountMenuListProps = Omit<ComponentPropsWithRef<'nav'>, 'children'> & {
|
|
74
|
+
/** Accessible name of the navigation landmark. */
|
|
75
|
+
label?: string;
|
|
76
|
+
};
|
|
77
|
+
export declare const AccountMenuRootDefinition: ComponentDefinition<AccountMenuRootProps>;
|
|
78
|
+
export declare const AccountMenuListDefinition: ComponentDefinition<AccountMenuListProps>;
|
|
79
|
+
/** The resolved section list and the current section. */
|
|
80
|
+
export declare function useAccountMenu(): AccountMenuContextValue;
|
|
81
|
+
export declare function AccountMenuRoot({ items, $content, className, children, ...props }: AccountMenuRootProps): import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
export declare function AccountMenuList({ label, className, ...props }: AccountMenuListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
83
|
+
export declare const AccountMenu: {
|
|
84
|
+
Root: typeof AccountMenuRoot;
|
|
85
|
+
List: typeof AccountMenuList;
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
//# sourceMappingURL=account-menu.d.ts.map
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { defineComponent } from "../../core/dist/define-component.js";
|
|
3
|
+
import { defineContentType } from "../../core/dist/define-content-type.js";
|
|
4
|
+
import "../../core/dist/index.js";
|
|
5
|
+
import { resolveSafeHref, resolveSiteRelativeHref } from "../../utils/safe-href.js";
|
|
6
|
+
import { NavigationLink } from "../../particles/navigation-link/navigation-link.js";
|
|
7
|
+
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
import { usePathname } from "next/navigation";
|
|
10
|
+
//#region src/matter/account-menu/account-menu.tsx
|
|
11
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
12
|
+
const AccountMenuContent = defineContentType({
|
|
13
|
+
id: "account-menu",
|
|
14
|
+
title: "Account Menu",
|
|
15
|
+
description: "The ordered list of the account's sections, editable without a deployment.",
|
|
16
|
+
schema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
properties: { items: {
|
|
20
|
+
type: "array",
|
|
21
|
+
title: "Sections",
|
|
22
|
+
minItems: 1,
|
|
23
|
+
items: {
|
|
24
|
+
type: "object",
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
required: ["label", "href"],
|
|
27
|
+
properties: {
|
|
28
|
+
label: {
|
|
29
|
+
type: "string",
|
|
30
|
+
minLength: 1,
|
|
31
|
+
title: "Label"
|
|
32
|
+
},
|
|
33
|
+
href: {
|
|
34
|
+
type: "string",
|
|
35
|
+
minLength: 1,
|
|
36
|
+
title: "URL",
|
|
37
|
+
format: "uri-reference",
|
|
38
|
+
pattern: "^(?:https?://.*|mailto:.+|tel:.+|/(?:[^/].*)?)$"
|
|
39
|
+
},
|
|
40
|
+
external: {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
title: "Leaves the account"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const AccountMenuRootDefinition = defineComponent({
|
|
50
|
+
name: "AccountMenuRoot",
|
|
51
|
+
importPath: "@vtex/faststore/components",
|
|
52
|
+
component: AccountMenuRoot,
|
|
53
|
+
content: AccountMenuContent,
|
|
54
|
+
providesContext: "account-menu",
|
|
55
|
+
editor: {
|
|
56
|
+
label: "Account Menu Root",
|
|
57
|
+
category: "navigation"
|
|
58
|
+
},
|
|
59
|
+
schema: { items: {
|
|
60
|
+
type: "array",
|
|
61
|
+
label: "Sections"
|
|
62
|
+
} },
|
|
63
|
+
guideline: "The account menu's provider, and deliberately not its landmark: it resolves the section list, derives which section the shopper is reading, publishes both, and renders its children. Compose it in the my-account layout around both AccountMenuList and the Slot holding the section's own page — its context has to reach the consumers inside the slot, and a nav stretched that far would announce every section's content as navigation. The list comes from a published entry, else the authored items prop, else the library's five defaults; a channel supplying an empty list resolves to empty rather than falling through, because a merchant who cleared the list cleared it. Style it through data-fs-account-menu."
|
|
64
|
+
});
|
|
65
|
+
const AccountMenuListDefinition = defineComponent({
|
|
66
|
+
name: "AccountMenuList",
|
|
67
|
+
importPath: "@vtex/faststore/components",
|
|
68
|
+
component: AccountMenuList,
|
|
69
|
+
requiresContext: "account-menu",
|
|
70
|
+
editor: {
|
|
71
|
+
label: "Account Menu List",
|
|
72
|
+
category: "navigation"
|
|
73
|
+
},
|
|
74
|
+
schema: { label: {
|
|
75
|
+
type: "string",
|
|
76
|
+
default: "Account",
|
|
77
|
+
label: "Aria Label"
|
|
78
|
+
} },
|
|
79
|
+
guideline: "The account menu's navigation landmark and its items, and nothing else — it surrounds the items alone so the section's own content stays outside the navigation region. Author the label: a landmark with no accessible name is one a screen-reader user cannot tell from any other. The current section carries aria-current, page for the section itself and true for a page inside it, with data-fs-account-menu-item-current beside it as a style target and never as the only signal. When the list scrolls, the current item is brought into view on mount and on every route change. Must be a descendant of AccountMenuRoot."
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* The library's default set, the five sections the PRD names. Labels are English as an interim, until
|
|
83
|
+
* the Localization cluster's catalog resolves them per locale. Their destinations only resolve in a
|
|
84
|
+
* store that authored the account's route family.
|
|
85
|
+
*/
|
|
86
|
+
const DEFAULT_SECTIONS = [
|
|
87
|
+
{
|
|
88
|
+
label: "Profile",
|
|
89
|
+
href: "/account/profile"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: "Orders",
|
|
93
|
+
href: "/account/orders"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: "Address",
|
|
97
|
+
href: "/account/address"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: "Payments",
|
|
101
|
+
href: "/account/payments"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "Security",
|
|
105
|
+
href: "/account/security"
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
function resolveSections(sections) {
|
|
109
|
+
const resolved = [];
|
|
110
|
+
sections.forEach((section, index) => {
|
|
111
|
+
if (section.label.trim().length === 0) return;
|
|
112
|
+
const destination = resolveSafeHref(section.href);
|
|
113
|
+
if (!destination.safe) console.warn("[AccountMenu] section destination rejected", {
|
|
114
|
+
index,
|
|
115
|
+
reason: destination.reason
|
|
116
|
+
});
|
|
117
|
+
resolved.push({
|
|
118
|
+
label: section.label,
|
|
119
|
+
href: destination.safe ? destination.href : null,
|
|
120
|
+
external: section.external ?? false
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
/** What a match runs against: no query, no hash, and no trailing slash except at the root. */
|
|
126
|
+
function normalizePath(value) {
|
|
127
|
+
const separator = value.search(/[?#]/);
|
|
128
|
+
const trimmed = (separator === -1 ? value : value.slice(0, separator)).replace(/\/+$/, "");
|
|
129
|
+
return trimmed.length === 0 ? "/" : trimmed;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The current section, matched on segment boundaries. Legacy matched by prefix and marked every
|
|
133
|
+
* section whose path was a prefix of the current one; the longest match wins here, which keeps
|
|
134
|
+
* exactly one section current and makes an exact match beat every ancestor without a second rule.
|
|
135
|
+
*/
|
|
136
|
+
function findCurrent(sections, pathname) {
|
|
137
|
+
if (pathname === null) return null;
|
|
138
|
+
const path = normalizePath(pathname);
|
|
139
|
+
let current = null;
|
|
140
|
+
let matchedLength = -1;
|
|
141
|
+
for (const [index, section] of sections.entries()) {
|
|
142
|
+
if (section.external || section.href === null || !resolveSiteRelativeHref(section.href).safe) continue;
|
|
143
|
+
const href = normalizePath(section.href);
|
|
144
|
+
if (href.length <= matchedLength) continue;
|
|
145
|
+
const exact = path === href;
|
|
146
|
+
const ancestor = href !== "/" && path.startsWith(`${href}/`);
|
|
147
|
+
if (!exact && !ancestor) continue;
|
|
148
|
+
current = {
|
|
149
|
+
index,
|
|
150
|
+
match: exact ? "exact" : "ancestor"
|
|
151
|
+
};
|
|
152
|
+
matchedLength = href.length;
|
|
153
|
+
}
|
|
154
|
+
return current;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* `page` for the section itself, `true` for a page inside it. The distinction is kept rather than
|
|
158
|
+
* collapsed because the sibling breadcrumb names the deep page as the current one, and two surfaces
|
|
159
|
+
* announcing "current page" for two different things is worse than the menu claiming only its set.
|
|
160
|
+
*/
|
|
161
|
+
function ariaCurrentFor(match) {
|
|
162
|
+
return match === "exact" ? "page" : "true";
|
|
163
|
+
}
|
|
164
|
+
function overflows(element) {
|
|
165
|
+
if (!element) return false;
|
|
166
|
+
return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
|
|
167
|
+
}
|
|
168
|
+
const AccountMenuContext = createContext(null);
|
|
169
|
+
/** The resolved section list and the current section. */
|
|
170
|
+
function useAccountMenu() {
|
|
171
|
+
const value = useContext(AccountMenuContext);
|
|
172
|
+
if (!value) throw new Error("AccountMenu parts must be used inside AccountMenuRoot.");
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function AccountMenuRoot({ items, $content, className, children, ...props }) {
|
|
176
|
+
const pathname = usePathname();
|
|
177
|
+
const sections = $content?.items ?? items ?? DEFAULT_SECTIONS;
|
|
178
|
+
const value = useMemo(() => {
|
|
179
|
+
const resolved = resolveSections(sections);
|
|
180
|
+
return {
|
|
181
|
+
items: resolved,
|
|
182
|
+
current: findCurrent(resolved, pathname)
|
|
183
|
+
};
|
|
184
|
+
}, [sections, pathname]);
|
|
185
|
+
return /* @__PURE__ */ jsx(AccountMenuContext, {
|
|
186
|
+
value,
|
|
187
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
188
|
+
...props,
|
|
189
|
+
className,
|
|
190
|
+
"data-fs-account-menu": "",
|
|
191
|
+
children
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function AccountMenuList({ label = "Account", className, ...props }) {
|
|
196
|
+
const { items, current } = useAccountMenu();
|
|
197
|
+
const currentRef = useRef(null);
|
|
198
|
+
const currentIndex = current?.index ?? null;
|
|
199
|
+
useIsomorphicLayoutEffect(() => {
|
|
200
|
+
if (currentIndex === null) return;
|
|
201
|
+
const item = currentRef.current;
|
|
202
|
+
if (!item || typeof item.scrollIntoView !== "function") return;
|
|
203
|
+
const list = item.parentElement;
|
|
204
|
+
if (!overflows(list) && !overflows(list?.parentElement)) return;
|
|
205
|
+
item.scrollIntoView({
|
|
206
|
+
block: "nearest",
|
|
207
|
+
inline: "nearest"
|
|
208
|
+
});
|
|
209
|
+
}, [currentIndex]);
|
|
210
|
+
if (items.length === 0) return null;
|
|
211
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
212
|
+
...props,
|
|
213
|
+
"aria-label": label,
|
|
214
|
+
className,
|
|
215
|
+
"data-fs-account-menu-list": "",
|
|
216
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
217
|
+
"data-fs-account-menu-items": "",
|
|
218
|
+
children: items.map((item, index) => {
|
|
219
|
+
const isCurrent = index === currentIndex;
|
|
220
|
+
return /* @__PURE__ */ jsx("li", {
|
|
221
|
+
ref: isCurrent ? currentRef : null,
|
|
222
|
+
"data-fs-account-menu-item": "",
|
|
223
|
+
"data-fs-account-menu-item-current": isCurrent ? "" : void 0,
|
|
224
|
+
children: item.href === null ? /* @__PURE__ */ jsx("span", {
|
|
225
|
+
"data-fs-text": "",
|
|
226
|
+
"data-fs-account-menu-text": "",
|
|
227
|
+
children: item.label
|
|
228
|
+
}) : /* @__PURE__ */ jsx(NavigationLink, {
|
|
229
|
+
href: item.href,
|
|
230
|
+
"aria-current": current && isCurrent ? ariaCurrentFor(current.match) : void 0,
|
|
231
|
+
"data-fs-account-menu-link": "",
|
|
232
|
+
children: item.label
|
|
233
|
+
})
|
|
234
|
+
}, index);
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const AccountMenu = {
|
|
240
|
+
Root: AccountMenuRoot,
|
|
241
|
+
List: AccountMenuList
|
|
242
|
+
};
|
|
243
|
+
//#endregion
|
|
244
|
+
export { AccountMenu, AccountMenuContent, AccountMenuList, AccountMenuListDefinition, AccountMenuRoot, AccountMenuRootDefinition, useAccountMenu };
|
|
245
|
+
|
|
246
|
+
//# sourceMappingURL=account-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-menu.js","names":[],"sources":["../../../src/matter/account-menu/account-menu.tsx"],"sourcesContent":["'use client';\n\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport { usePathname } from 'next/navigation';\nimport {\n type ComponentPropsWithRef,\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n} from 'react';\nimport { NavigationLink } from '../../particles/navigation-link/navigation-link.js';\nimport { resolveSafeHref, resolveSiteRelativeHref } from '../../utils/safe-href.js';\n\n// The scroll has to land before paint, or the list shows its top for a frame and then jumps.\n// `useEffect` on the server silences React 19's \"useLayoutEffect does nothing on the server\", as\n// `matter/variant-selector` does for its own pre-paint measurement.\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n// ---------------------------------------------------------------------------\n// Content model\n// ---------------------------------------------------------------------------\n\nexport const AccountMenuContent = defineContentType({\n id: 'account-menu',\n title: 'Account Menu',\n description: \"The ordered list of the account's sections, editable without a deployment.\",\n schema: {\n type: 'object',\n additionalProperties: false,\n // `items` is not required, for the reason NavigationLinkList records: a missing element key\n // already omits the component, so requiring it would only cost the prop fallback its shape\n // validity.\n properties: {\n items: {\n type: 'array',\n title: 'Sections',\n minItems: 1,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['label', 'href'],\n properties: {\n label: { type: 'string', minLength: 1, title: 'Label' },\n href: {\n type: 'string',\n minLength: 1,\n title: 'URL',\n format: 'uri-reference',\n // Author-time guidance only, and lookahead-free so the Admin's RE2-family engine takes it.\n pattern: '^(?:https?://.*|mailto:.+|tel:.+|/(?:[^/].*)?)$',\n },\n external: { type: 'boolean', title: 'Leaves the account' },\n },\n },\n },\n },\n },\n});\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype AccountMenuContentValue = ContentValue<typeof AccountMenuContent>;\n\n/** One authored section. Derived from the model so the prop and content channels cannot drift. */\nexport type AccountMenuSection = NonNullable<AccountMenuContentValue['items']>[number];\n\n/** A section resolved for render. A null `href` is a destination the allowlist refused. */\nexport type AccountMenuResolvedSection = {\n label: string;\n href: string | null;\n external: boolean;\n};\n\nexport type AccountMenuCurrent = {\n /** Index into the resolved list. */\n index: number;\n /** `exact` when the path is the section; `ancestor` when the shopper is inside it. */\n match: 'exact' | 'ancestor';\n};\n\nexport type AccountMenuContextValue = {\n /** Resolved, in the merchant's order, with blank-label sections dropped. */\n items: readonly AccountMenuResolvedSection[];\n /** The current section, or null when the path matches none. */\n current: AccountMenuCurrent | null;\n};\n\nexport type AccountMenuRootProps = ComponentPropsWithRef<'div'> & {\n /** Authored fallback for the section list, used when no CMS entry supplies one. */\n items?: readonly AccountMenuSection[];\n /** Injected by the compiler, sliced from the page's or layout's composite content entry. */\n $content?: AccountMenuContentValue;\n};\n\n/** The landmark renders one item per section, so it takes no children. */\nexport type AccountMenuListProps = Omit<ComponentPropsWithRef<'nav'>, 'children'> & {\n /** Accessible name of the navigation landmark. */\n label?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const AccountMenuRootDefinition = defineComponent<AccountMenuRootProps>({\n name: 'AccountMenuRoot',\n importPath: '@vtex/faststore/components',\n component: AccountMenuRoot,\n content: AccountMenuContent,\n providesContext: 'account-menu',\n editor: { label: 'Account Menu Root', category: 'navigation' },\n schema: {\n items: { type: 'array', label: 'Sections' },\n },\n guideline:\n \"The account menu's provider, and deliberately not its landmark: it resolves the section list, derives which section the shopper is reading, publishes both, and renders its children. Compose it in the my-account layout around both AccountMenuList and the Slot holding the section's own page — its context has to reach the consumers inside the slot, and a nav stretched that far would announce every section's content as navigation. The list comes from a published entry, else the authored items prop, else the library's five defaults; a channel supplying an empty list resolves to empty rather than falling through, because a merchant who cleared the list cleared it. Style it through data-fs-account-menu.\",\n});\n\nexport const AccountMenuListDefinition = defineComponent<AccountMenuListProps>({\n name: 'AccountMenuList',\n importPath: '@vtex/faststore/components',\n component: AccountMenuList,\n requiresContext: 'account-menu',\n editor: { label: 'Account Menu List', category: 'navigation' },\n schema: {\n label: { type: 'string', default: 'Account', label: 'Aria Label' },\n },\n guideline:\n \"The account menu's navigation landmark and its items, and nothing else — it surrounds the items alone so the section's own content stays outside the navigation region. Author the label: a landmark with no accessible name is one a screen-reader user cannot tell from any other. The current section carries aria-current, page for the section itself and true for a page inside it, with data-fs-account-menu-item-current beside it as a style target and never as the only signal. When the list scrolls, the current item is brought into view on mount and on every route change. Must be a descendant of AccountMenuRoot.\",\n});\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * The library's default set, the five sections the PRD names. Labels are English as an interim, until\n * the Localization cluster's catalog resolves them per locale. Their destinations only resolve in a\n * store that authored the account's route family.\n */\nconst DEFAULT_SECTIONS: readonly AccountMenuSection[] = [\n { label: 'Profile', href: '/account/profile' },\n { label: 'Orders', href: '/account/orders' },\n { label: 'Address', href: '/account/address' },\n { label: 'Payments', href: '/account/payments' },\n { label: 'Security', href: '/account/security' },\n];\n\nfunction resolveSections(sections: readonly AccountMenuSection[]): AccountMenuResolvedSection[] {\n const resolved: AccountMenuResolvedSection[] = [];\n\n sections.forEach((section, index) => {\n // `minLength` is enforced by neither the runtime slice nor the preflight, so a blank label\n // reaches the component. Drop the section: an anchor with no accessible name is a WCAG failure.\n if (section.label.trim().length === 0) {\n return;\n }\n const destination = resolveSafeHref(section.href);\n if (!destination.safe) {\n console.warn('[AccountMenu] section destination rejected', { index, reason: destination.reason });\n }\n resolved.push({\n label: section.label,\n href: destination.safe ? destination.href : null,\n external: section.external ?? false,\n });\n });\n\n return resolved;\n}\n\n/** What a match runs against: no query, no hash, and no trailing slash except at the root. */\nfunction normalizePath(value: string): string {\n const separator = value.search(/[?#]/);\n const path = separator === -1 ? value : value.slice(0, separator);\n const trimmed = path.replace(/\\/+$/, '');\n return trimmed.length === 0 ? '/' : trimmed;\n}\n\n/**\n * The current section, matched on segment boundaries. Legacy matched by prefix and marked every\n * section whose path was a prefix of the current one; the longest match wins here, which keeps\n * exactly one section current and makes an exact match beat every ancestor without a second rule.\n */\nfunction findCurrent(\n sections: readonly AccountMenuResolvedSection[],\n pathname: string | null,\n): AccountMenuCurrent | null {\n if (pathname === null) {\n return null;\n }\n\n const path = normalizePath(pathname);\n let current: AccountMenuCurrent | null = null;\n let matchedLength = -1;\n\n for (const [index, section] of sections.entries()) {\n // An external section leaves the account and is outside the current-section contract, and an\n // absolute destination could never be a path in the first place.\n if (section.external || section.href === null || !resolveSiteRelativeHref(section.href).safe) {\n continue;\n }\n const href = normalizePath(section.href);\n if (href.length <= matchedLength) {\n continue;\n }\n // A root href is an ancestor of every account path, so a merchant listing the storefront's home\n // beside the sections would otherwise see it current on every page. It matches exactly or not at all.\n const exact = path === href;\n const ancestor = href !== '/' && path.startsWith(`${href}/`);\n if (!exact && !ancestor) {\n continue;\n }\n current = { index, match: exact ? 'exact' : 'ancestor' };\n matchedLength = href.length;\n }\n\n return current;\n}\n\n/**\n * `page` for the section itself, `true` for a page inside it. The distinction is kept rather than\n * collapsed because the sibling breadcrumb names the deep page as the current one, and two surfaces\n * announcing \"current page\" for two different things is worse than the menu claiming only its set.\n */\nfunction ariaCurrentFor(match: AccountMenuCurrent['match']): 'page' | 'true' {\n return match === 'exact' ? 'page' : 'true';\n}\n\nfunction overflows(element: Element | null | undefined): boolean {\n if (!element) {\n return false;\n }\n return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst AccountMenuContext = createContext<AccountMenuContextValue | null>(null);\n\n/** The resolved section list and the current section. */\nexport function useAccountMenu(): AccountMenuContextValue {\n const value = useContext(AccountMenuContext);\n if (!value) {\n throw new Error('AccountMenu parts must be used inside AccountMenuRoot.');\n }\n return value;\n}\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function AccountMenuRoot({ items, $content, className, children, ...props }: AccountMenuRootProps) {\n const pathname = usePathname();\n // Presence decides, not truthiness: a channel supplying an empty list resolves to empty rather than\n // falling through, so the defaults never override a list the merchant published deliberately.\n // `minItems` guides the Admin form only, so that empty list still reaches this component.\n const sections = $content?.items ?? items ?? DEFAULT_SECTIONS;\n\n const value = useMemo<AccountMenuContextValue>(() => {\n const resolved = resolveSections(sections);\n return { items: resolved, current: findCurrent(resolved, pathname) };\n }, [sections, pathname]);\n\n return (\n <AccountMenuContext value={value}>\n <div {...props} className={className} data-fs-account-menu=\"\">\n {children}\n </div>\n </AccountMenuContext>\n );\n}\n\nexport function AccountMenuList({ label = 'Account', className, ...props }: AccountMenuListProps) {\n const { items, current } = useAccountMenu();\n const currentRef = useRef<HTMLLIElement>(null);\n const currentIndex = current?.index ?? null;\n\n useIsomorphicLayoutEffect(() => {\n if (currentIndex === null) {\n return;\n }\n const item = currentRef.current;\n if (!item || typeof item.scrollIntoView !== 'function') {\n return;\n }\n // Only when the row actually scrolls. An unconditional call scrolls whatever ancestor can move,\n // which on a menu that fits is the page. The containers are the item's `ul` and its `nav`.\n const list = item.parentElement;\n if (!overflows(list) && !overflows(list?.parentElement)) {\n return;\n }\n // `nearest` on both axes is a no-op once the item is visible, so a route change inside the\n // current section does not jolt the row.\n item.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }, [currentIndex]);\n\n if (items.length === 0) {\n return null;\n }\n\n return (\n <nav {...props} aria-label={label} className={className} data-fs-account-menu-list=\"\">\n <ul data-fs-account-menu-items=\"\">\n {items.map((item, index) => {\n const isCurrent = index === currentIndex;\n return (\n <li\n key={index}\n ref={isCurrent ? currentRef : null}\n data-fs-account-menu-item=\"\"\n data-fs-account-menu-item-current={isCurrent ? '' : undefined}\n >\n {item.href === null ? (\n <span data-fs-text=\"\" data-fs-account-menu-text=\"\">\n {item.label}\n </span>\n ) : (\n <NavigationLink\n href={item.href}\n aria-current={current && isCurrent ? ariaCurrentFor(current.match) : undefined}\n data-fs-account-menu-link=\"\"\n >\n {item.label}\n </NavigationLink>\n )}\n </li>\n );\n })}\n </ul>\n </nav>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const AccountMenu = {\n Root: AccountMenuRoot,\n List: AccountMenuList,\n};\n"],"mappings":"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"}
|