@shopify/hydrogen 2026.4.5 → 2026.10.0-preview.2
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/README.md +73 -13
- package/bin/hydrogen.mjs +7 -0
- package/dist/cli/index.mjs +647 -0
- package/dist/client/client.d.mts +34 -0
- package/dist/client/client.mjs +206 -0
- package/dist/client/client.mjs.map +1 -0
- package/dist/client/errors.d.mts +48 -0
- package/dist/client/errors.mjs +52 -0
- package/dist/client/errors.mjs.map +1 -0
- package/dist/client/index.d.mts +4 -0
- package/dist/client/types.d.mts +147 -0
- package/dist/core/analytics/cart-tracker.d.mts +8 -0
- package/dist/core/analytics/cart-tracker.mjs +144 -0
- package/dist/core/analytics/cart-tracker.mjs.map +1 -0
- package/dist/core/analytics/events.d.mts +15 -0
- package/dist/core/analytics/events.mjs +15 -0
- package/dist/core/analytics/events.mjs.map +1 -0
- package/dist/core/analytics/index.d.mts +3 -0
- package/dist/core/analytics/types.d.mts +143 -0
- package/dist/core/analytics/utils/flatten-connection.d.mts +19 -0
- package/dist/core/analytics/utils/flatten-connection.mjs +25 -0
- package/dist/core/analytics/utils/flatten-connection.mjs.map +1 -0
- package/dist/core/cache/cache-status.mjs +17 -0
- package/dist/core/cache/cache-status.mjs.map +1 -0
- package/dist/core/cache/fetch-with-cache.d.mts +43 -0
- package/dist/core/cache/fetch-with-cache.mjs +188 -0
- package/dist/core/cache/fetch-with-cache.mjs.map +1 -0
- package/dist/core/cache/key.d.mts +6 -0
- package/dist/core/cache/key.mjs +27 -0
- package/dist/core/cache/key.mjs.map +1 -0
- package/dist/core/cache/run-with-cache.d.mts +36 -0
- package/dist/core/cache/run-with-cache.mjs +114 -0
- package/dist/core/cache/run-with-cache.mjs.map +1 -0
- package/dist/core/cache/store.d.mts +19 -0
- package/dist/core/cache/store.mjs +62 -0
- package/dist/core/cache/store.mjs.map +1 -0
- package/dist/core/cache/strategies.d.mts +39 -0
- package/dist/core/cache/strategies.mjs +70 -0
- package/dist/core/cache/strategies.mjs.map +1 -0
- package/dist/core/cart/actions.d.mts +50 -0
- package/dist/core/cart/actions.mjs +240 -0
- package/dist/core/cart/actions.mjs.map +1 -0
- package/dist/core/cart/attach-quantity-input.d.mts +5 -0
- package/dist/core/cart/attach-quantity-input.mjs +17 -0
- package/dist/core/cart/attach-quantity-input.mjs.map +1 -0
- package/dist/core/cart/buyer-identity-sync.d.mts +23 -0
- package/dist/core/cart/buyer-identity-sync.mjs +9 -0
- package/dist/core/cart/buyer-identity-sync.mjs.map +1 -0
- package/dist/core/cart/cart.d.mts +37 -0
- package/dist/core/cart/cart.mjs +1707 -0
- package/dist/core/cart/cart.mjs.map +1 -0
- package/dist/core/cart/cookie.d.mts +5 -0
- package/dist/core/cart/cookie.mjs +28 -0
- package/dist/core/cart/cookie.mjs.map +1 -0
- package/dist/core/cart/form.d.mts +130 -0
- package/dist/core/cart/form.mjs +75 -0
- package/dist/core/cart/form.mjs.map +1 -0
- package/dist/core/cart/get-cart.d.mts +25 -0
- package/dist/core/cart/get-cart.mjs +32 -0
- package/dist/core/cart/get-cart.mjs.map +1 -0
- package/dist/core/cart/index.d.mts +10 -0
- package/dist/core/cart/quantity.d.mts +10 -0
- package/dist/core/cart/quantity.mjs +14 -0
- package/dist/core/cart/quantity.mjs.map +1 -0
- package/dist/core/cart/queries.d.mts +4296 -0
- package/dist/core/cart/queries.mjs +70 -0
- package/dist/core/cart/queries.mjs.map +1 -0
- package/dist/core/cart/server-handlers.d.mts +82 -0
- package/dist/core/cart/server-handlers.mjs +322 -0
- package/dist/core/cart/server-handlers.mjs.map +1 -0
- package/dist/core/cart/state.d.mts +137 -0
- package/dist/core/cart/state.mjs +76 -0
- package/dist/core/cart/state.mjs.map +1 -0
- package/dist/core/cart/sync-quantity-inputs.mjs +16 -0
- package/dist/core/cart/sync-quantity-inputs.mjs.map +1 -0
- package/dist/core/collection/collection.d.mts +134 -0
- package/dist/core/collection/collection.mjs +170 -0
- package/dist/core/collection/collection.mjs.map +1 -0
- package/dist/core/collection/index.d.mts +6 -0
- package/dist/core/collection/reconciler.d.mts +32 -0
- package/dist/core/collection/reconciler.mjs +89 -0
- package/dist/core/collection/reconciler.mjs.map +1 -0
- package/dist/core/collection/state.d.mts +66 -0
- package/dist/core/collection/state.mjs +20 -0
- package/dist/core/collection/state.mjs.map +1 -0
- package/dist/core/collection/url.d.mts +71 -0
- package/dist/core/collection/url.mjs +376 -0
- package/dist/core/collection/url.mjs.map +1 -0
- package/dist/core/constants.d.mts +5 -0
- package/dist/core/constants.mjs +8 -0
- package/dist/core/constants.mjs.map +1 -0
- package/dist/core/headers.mjs +43 -0
- package/dist/core/headers.mjs.map +1 -0
- package/dist/core/index.d.mts +56 -0
- package/dist/core/index.mjs +45 -0
- package/dist/core/logging/logging.d.mts +21 -0
- package/dist/core/logging/logging.mjs +99 -0
- package/dist/core/logging/logging.mjs.map +1 -0
- package/dist/core/logging/types.d.mts +40 -0
- package/dist/core/logging/types.mjs +18 -0
- package/dist/core/logging/types.mjs.map +1 -0
- package/dist/core/money/cache.mjs +25 -0
- package/dist/core/money/cache.mjs.map +1 -0
- package/dist/core/money/format.d.mts +15 -0
- package/dist/core/money/format.mjs +243 -0
- package/dist/core/money/format.mjs.map +1 -0
- package/dist/core/money/types.d.mts +47 -0
- package/dist/core/observable.mjs +46 -0
- package/dist/core/observable.mjs.map +1 -0
- package/dist/core/predictive-search/constants.d.mts +5 -0
- package/dist/core/predictive-search/constants.mjs +6 -0
- package/dist/core/predictive-search/constants.mjs.map +1 -0
- package/dist/core/predictive-search/form.d.mts +31 -0
- package/dist/core/predictive-search/form.mjs +31 -0
- package/dist/core/predictive-search/form.mjs.map +1 -0
- package/dist/core/predictive-search/index.d.mts +6 -0
- package/dist/core/predictive-search/queries.d.mts +118 -0
- package/dist/core/predictive-search/queries.mjs +81 -0
- package/dist/core/predictive-search/queries.mjs.map +1 -0
- package/dist/core/predictive-search/search.d.mts +32 -0
- package/dist/core/predictive-search/search.mjs +74 -0
- package/dist/core/predictive-search/search.mjs.map +1 -0
- package/dist/core/predictive-search/server-handlers.d.mts +36 -0
- package/dist/core/predictive-search/server-handlers.mjs +113 -0
- package/dist/core/predictive-search/server-handlers.mjs.map +1 -0
- package/dist/core/predictive-search/store.d.mts +44 -0
- package/dist/core/predictive-search/store.mjs +224 -0
- package/dist/core/predictive-search/store.mjs.map +1 -0
- package/dist/core/predictive-search/url.d.mts +41 -0
- package/dist/core/predictive-search/url.mjs +71 -0
- package/dist/core/predictive-search/url.mjs.map +1 -0
- package/dist/core/product/accept-variant-id.mjs +98 -0
- package/dist/core/product/accept-variant-id.mjs.map +1 -0
- package/dist/core/product/form.d.mts +63 -0
- package/dist/core/product/form.mjs +44 -0
- package/dist/core/product/form.mjs.map +1 -0
- package/dist/core/product/index.d.mts +5 -0
- package/dist/core/product/options.d.mts +43 -0
- package/dist/core/product/options.mjs +296 -0
- package/dist/core/product/options.mjs.map +1 -0
- package/dist/core/product/product-form.d.mts +72 -0
- package/dist/core/product/product-form.mjs +158 -0
- package/dist/core/product/product-form.mjs.map +1 -0
- package/dist/core/product/state.d.mts +68 -0
- package/dist/core/product/url.d.mts +47 -0
- package/dist/core/product/url.mjs +69 -0
- package/dist/core/product/url.mjs.map +1 -0
- package/dist/core/request-context.d.mts +75 -0
- package/dist/core/request-context.mjs +141 -0
- package/dist/core/request-context.mjs.map +1 -0
- package/dist/core/request-routing/handle-shopify-redirects.d.mts +17 -0
- package/dist/core/request-routing/handle-shopify-redirects.mjs +31 -0
- package/dist/core/request-routing/handle-shopify-redirects.mjs.map +1 -0
- package/dist/core/request-routing/handle-shopify-routes.d.mts +14 -0
- package/dist/core/request-routing/handle-shopify-routes.mjs +42 -0
- package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/admin-redirect.mjs +12 -0
- package/dist/core/request-routing/interceptors/admin-redirect.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/ajax-api.mjs +16 -0
- package/dist/core/request-routing/interceptors/ajax-api.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/api-proxy.mjs +20 -0
- package/dist/core/request-routing/interceptors/api-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/checkout.mjs +58 -0
- package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/mcp-proxy.mjs +23 -0
- package/dist/core/request-routing/interceptors/mcp-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/proxy.mjs +73 -0
- package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs +22 -0
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +27 -0
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/standard-routes.mjs +19 -0
- package/dist/core/request-routing/interceptors/standard-routes.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/url-redirects.mjs +27 -0
- package/dist/core/request-routing/interceptors/url-redirects.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/well-known.mjs +22 -0
- package/dist/core/request-routing/interceptors/well-known.mjs.map +1 -0
- package/dist/core/request-routing/registered-routes.d.mts +7 -0
- package/dist/core/request-routing/registered-routes.mjs +72 -0
- package/dist/core/request-routing/registered-routes.mjs.map +1 -0
- package/dist/core/request-routing/route-types.d.mts +56 -0
- package/dist/core/request-routing/safe-apply-response-headers.mjs +16 -0
- package/dist/core/request-routing/safe-apply-response-headers.mjs.map +1 -0
- package/dist/core/shop-pay/shop-pay.d.mts +102 -0
- package/dist/core/shop-pay/shop-pay.mjs +289 -0
- package/dist/core/shop-pay/shop-pay.mjs.map +1 -0
- package/dist/core/shopify-scripts/analytics.mjs +26 -0
- package/dist/core/shopify-scripts/analytics.mjs.map +1 -0
- package/dist/core/shopify-scripts/consent.mjs +17 -0
- package/dist/core/shopify-scripts/consent.mjs.map +1 -0
- package/dist/core/shopify-scripts/constants.mjs +19 -0
- package/dist/core/shopify-scripts/constants.mjs.map +1 -0
- package/dist/core/shopify-scripts/deprecated-cookies.mjs +97 -0
- package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +1 -0
- package/dist/core/shopify-scripts/global.d.mts +1 -0
- package/dist/core/shopify-scripts/global.mjs +86 -0
- package/dist/core/shopify-scripts/global.mjs.map +1 -0
- package/dist/core/shopify-scripts/index.d.mts +27 -0
- package/dist/core/shopify-scripts/index.mjs +135 -0
- package/dist/core/shopify-scripts/index.mjs.map +1 -0
- package/dist/core/shopify-scripts/initialize.d.mts +18 -0
- package/dist/core/shopify-scripts/initialize.mjs +27 -0
- package/dist/core/shopify-scripts/initialize.mjs.map +1 -0
- package/dist/core/shopify-scripts/page-view.mjs +49 -0
- package/dist/core/shopify-scripts/page-view.mjs.map +1 -0
- package/dist/core/shopify-scripts/perfkit.mjs +33 -0
- package/dist/core/shopify-scripts/perfkit.mjs.map +1 -0
- package/dist/core/shopify-scripts/render.d.mts +1 -0
- package/dist/core/shopify-scripts/render.mjs +23 -0
- package/dist/core/shopify-scripts/render.mjs.map +1 -0
- package/dist/core/shopify-scripts/types.d.mts +76 -0
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs +43 -0
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +1 -0
- package/dist/core/shopify-scripts/utils/inline-script.mjs +8 -0
- package/dist/core/shopify-scripts/utils/inline-script.mjs.map +1 -0
- package/dist/core/shopify-scripts/utils/navigation.mjs +46 -0
- package/dist/core/shopify-scripts/utils/navigation.mjs.map +1 -0
- package/dist/core/shopify-scripts/utils/tracking-values.mjs +65 -0
- package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +1 -0
- package/dist/core/shopify-scripts/utils/uuid.mjs +17 -0
- package/dist/core/shopify-scripts/utils/uuid.mjs.map +1 -0
- package/dist/core/shopify-scripts/webmcp.mjs +22 -0
- package/dist/core/shopify-scripts/webmcp.mjs.map +1 -0
- package/dist/core/standard-routes/build.d.mts +34 -0
- package/dist/core/standard-routes/build.mjs +54 -0
- package/dist/core/standard-routes/build.mjs.map +1 -0
- package/dist/core/standard-routes/defaults.mjs +31 -0
- package/dist/core/standard-routes/defaults.mjs.map +1 -0
- package/dist/core/standard-routes/match.mjs +108 -0
- package/dist/core/standard-routes/match.mjs.map +1 -0
- package/dist/core/standard-routes/path.mjs +58 -0
- package/dist/core/standard-routes/path.mjs.map +1 -0
- package/dist/core/standard-routes/redirects.mjs +51 -0
- package/dist/core/standard-routes/redirects.mjs.map +1 -0
- package/dist/core/standard-routes/types.d.mts +95 -0
- package/dist/core/url.mjs +42 -0
- package/dist/core/url.mjs.map +1 -0
- package/dist/core/utils/load-script.mjs +34 -0
- package/dist/core/utils/load-script.mjs.map +1 -0
- package/dist/core/utils/parse-gid.mjs +33 -0
- package/dist/core/utils/parse-gid.mjs.map +1 -0
- package/dist/core/utils/record.mjs +8 -0
- package/dist/core/utils/record.mjs.map +1 -0
- package/dist/customer-account/client.d.mts +50 -0
- package/dist/customer-account/client.mjs +239 -0
- package/dist/customer-account/client.mjs.map +1 -0
- package/dist/customer-account/errors.d.mts +28 -0
- package/dist/customer-account/errors.mjs +39 -0
- package/dist/customer-account/errors.mjs.map +1 -0
- package/dist/customer-account/graphql.d.mts +29 -0
- package/dist/customer-account/graphql.mjs +39 -0
- package/dist/customer-account/graphql.mjs.map +1 -0
- package/dist/customer-account/index.d.mts +7 -0
- package/dist/customer-account/index.mjs +7 -0
- package/dist/customer-account/session.d.mts +117 -0
- package/dist/customer-account/session.mjs +733 -0
- package/dist/customer-account/session.mjs.map +1 -0
- package/dist/customer-account/type-resolver.d.mts +11 -0
- package/dist/customer-account-api-types.d.ts +3269 -3270
- package/dist/customer-account.schema.json +50753 -1
- package/dist/development/_virtual/_@oxc-project_runtime@0.127.0/helpers/taggedTemplateLiteral.mjs +7 -0
- package/dist/development/client/client.d.mts +34 -0
- package/dist/development/client/client.mjs +206 -0
- package/dist/development/client/client.mjs.map +1 -0
- package/dist/development/client/errors.d.mts +48 -0
- package/dist/development/client/errors.mjs +57 -0
- package/dist/development/client/errors.mjs.map +1 -0
- package/dist/development/client/index.d.mts +4 -0
- package/dist/development/client/types.d.mts +147 -0
- package/dist/development/core/analytics/cart-tracker.d.mts +8 -0
- package/dist/development/core/analytics/cart-tracker.mjs +144 -0
- package/dist/development/core/analytics/cart-tracker.mjs.map +1 -0
- package/dist/development/core/analytics/events.d.mts +15 -0
- package/dist/development/core/analytics/events.mjs +15 -0
- package/dist/development/core/analytics/events.mjs.map +1 -0
- package/dist/development/core/analytics/index.d.mts +3 -0
- package/dist/development/core/analytics/types.d.mts +143 -0
- package/dist/development/core/analytics/utils/flatten-connection.d.mts +19 -0
- package/dist/development/core/analytics/utils/flatten-connection.mjs +25 -0
- package/dist/development/core/analytics/utils/flatten-connection.mjs.map +1 -0
- package/dist/development/core/cache/cache-status.mjs +17 -0
- package/dist/development/core/cache/cache-status.mjs.map +1 -0
- package/dist/development/core/cache/fetch-with-cache.d.mts +43 -0
- package/dist/development/core/cache/fetch-with-cache.mjs +188 -0
- package/dist/development/core/cache/fetch-with-cache.mjs.map +1 -0
- package/dist/development/core/cache/key.d.mts +6 -0
- package/dist/development/core/cache/key.mjs +27 -0
- package/dist/development/core/cache/key.mjs.map +1 -0
- package/dist/development/core/cache/run-with-cache.d.mts +36 -0
- package/dist/development/core/cache/run-with-cache.mjs +114 -0
- package/dist/development/core/cache/run-with-cache.mjs.map +1 -0
- package/dist/development/core/cache/store.d.mts +19 -0
- package/dist/development/core/cache/store.mjs +62 -0
- package/dist/development/core/cache/store.mjs.map +1 -0
- package/dist/development/core/cache/strategies.d.mts +39 -0
- package/dist/development/core/cache/strategies.mjs +70 -0
- package/dist/development/core/cache/strategies.mjs.map +1 -0
- package/dist/development/core/cart/actions.d.mts +50 -0
- package/dist/development/core/cart/actions.mjs +240 -0
- package/dist/development/core/cart/actions.mjs.map +1 -0
- package/dist/development/core/cart/attach-quantity-input.d.mts +5 -0
- package/dist/development/core/cart/attach-quantity-input.mjs +17 -0
- package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -0
- package/dist/development/core/cart/buyer-identity-sync.d.mts +19 -0
- package/dist/development/core/cart/buyer-identity-sync.mjs +6 -0
- package/dist/development/core/cart/buyer-identity-sync.mjs.map +1 -0
- package/dist/development/core/cart/cart.d.mts +37 -0
- package/dist/development/core/cart/cart.mjs +1707 -0
- package/dist/development/core/cart/cart.mjs.map +1 -0
- package/dist/development/core/cart/cookie.d.mts +5 -0
- package/dist/development/core/cart/cookie.mjs +28 -0
- package/dist/development/core/cart/cookie.mjs.map +1 -0
- package/dist/development/core/cart/form.d.mts +130 -0
- package/dist/development/core/cart/form.mjs +75 -0
- package/dist/development/core/cart/form.mjs.map +1 -0
- package/dist/development/core/cart/get-cart.d.mts +25 -0
- package/dist/development/core/cart/get-cart.mjs +32 -0
- package/dist/development/core/cart/get-cart.mjs.map +1 -0
- package/dist/development/core/cart/index.d.mts +10 -0
- package/dist/development/core/cart/quantity.d.mts +10 -0
- package/dist/development/core/cart/quantity.mjs +14 -0
- package/dist/development/core/cart/quantity.mjs.map +1 -0
- package/dist/development/core/cart/queries.d.mts +4296 -0
- package/dist/development/core/cart/queries.mjs +70 -0
- package/dist/development/core/cart/queries.mjs.map +1 -0
- package/dist/development/core/cart/server-handlers.d.mts +82 -0
- package/dist/development/core/cart/server-handlers.mjs +322 -0
- package/dist/development/core/cart/server-handlers.mjs.map +1 -0
- package/dist/development/core/cart/state.d.mts +137 -0
- package/dist/development/core/cart/state.mjs +76 -0
- package/dist/development/core/cart/state.mjs.map +1 -0
- package/dist/development/core/cart/sync-quantity-inputs.mjs +16 -0
- package/dist/development/core/cart/sync-quantity-inputs.mjs.map +1 -0
- package/dist/development/core/collection/collection.d.mts +134 -0
- package/dist/development/core/collection/collection.mjs +170 -0
- package/dist/development/core/collection/collection.mjs.map +1 -0
- package/dist/development/core/collection/index.d.mts +6 -0
- package/dist/development/core/collection/reconciler.d.mts +32 -0
- package/dist/development/core/collection/reconciler.mjs +89 -0
- package/dist/development/core/collection/reconciler.mjs.map +1 -0
- package/dist/development/core/collection/state.d.mts +66 -0
- package/dist/development/core/collection/state.mjs +20 -0
- package/dist/development/core/collection/state.mjs.map +1 -0
- package/dist/development/core/collection/url.d.mts +71 -0
- package/dist/development/core/collection/url.mjs +376 -0
- package/dist/development/core/collection/url.mjs.map +1 -0
- package/dist/development/core/constants.mjs +7 -0
- package/dist/development/core/constants.mjs.map +1 -0
- package/dist/development/core/development.d.mts +56 -0
- package/dist/development/core/development.mjs +45 -0
- package/dist/development/core/headers.mjs +43 -0
- package/dist/development/core/headers.mjs.map +1 -0
- package/dist/development/core/index.d.mts +54 -0
- package/dist/development/core/logging/logging.d.mts +21 -0
- package/dist/development/core/logging/logging.mjs +99 -0
- package/dist/development/core/logging/logging.mjs.map +1 -0
- package/dist/development/core/logging/types.d.mts +40 -0
- package/dist/development/core/logging/types.mjs +18 -0
- package/dist/development/core/logging/types.mjs.map +1 -0
- package/dist/development/core/money/cache.mjs +25 -0
- package/dist/development/core/money/cache.mjs.map +1 -0
- package/dist/development/core/money/format.d.mts +15 -0
- package/dist/development/core/money/format.mjs +243 -0
- package/dist/development/core/money/format.mjs.map +1 -0
- package/dist/development/core/money/types.d.mts +47 -0
- package/dist/development/core/observable.mjs +46 -0
- package/dist/development/core/observable.mjs.map +1 -0
- package/dist/development/core/predictive-search/constants.d.mts +5 -0
- package/dist/development/core/predictive-search/constants.mjs +6 -0
- package/dist/development/core/predictive-search/constants.mjs.map +1 -0
- package/dist/development/core/predictive-search/form.d.mts +31 -0
- package/dist/development/core/predictive-search/form.mjs +31 -0
- package/dist/development/core/predictive-search/form.mjs.map +1 -0
- package/dist/development/core/predictive-search/index.d.mts +6 -0
- package/dist/development/core/predictive-search/queries.d.mts +118 -0
- package/dist/development/core/predictive-search/queries.mjs +81 -0
- package/dist/development/core/predictive-search/queries.mjs.map +1 -0
- package/dist/development/core/predictive-search/search.d.mts +32 -0
- package/dist/development/core/predictive-search/search.mjs +74 -0
- package/dist/development/core/predictive-search/search.mjs.map +1 -0
- package/dist/development/core/predictive-search/server-handlers.d.mts +36 -0
- package/dist/development/core/predictive-search/server-handlers.mjs +113 -0
- package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -0
- package/dist/development/core/predictive-search/store.d.mts +44 -0
- package/dist/development/core/predictive-search/store.mjs +224 -0
- package/dist/development/core/predictive-search/store.mjs.map +1 -0
- package/dist/development/core/predictive-search/url.d.mts +41 -0
- package/dist/development/core/predictive-search/url.mjs +71 -0
- package/dist/development/core/predictive-search/url.mjs.map +1 -0
- package/dist/development/core/product/accept-variant-id.mjs +98 -0
- package/dist/development/core/product/accept-variant-id.mjs.map +1 -0
- package/dist/development/core/product/form.d.mts +63 -0
- package/dist/development/core/product/form.mjs +44 -0
- package/dist/development/core/product/form.mjs.map +1 -0
- package/dist/development/core/product/index.d.mts +5 -0
- package/dist/development/core/product/options.d.mts +43 -0
- package/dist/development/core/product/options.mjs +296 -0
- package/dist/development/core/product/options.mjs.map +1 -0
- package/dist/development/core/product/product-form.d.mts +72 -0
- package/dist/development/core/product/product-form.mjs +158 -0
- package/dist/development/core/product/product-form.mjs.map +1 -0
- package/dist/development/core/product/state.d.mts +68 -0
- package/dist/development/core/product/url.d.mts +47 -0
- package/dist/development/core/product/url.mjs +69 -0
- package/dist/development/core/product/url.mjs.map +1 -0
- package/dist/development/core/request-context.d.mts +75 -0
- package/dist/development/core/request-context.mjs +141 -0
- package/dist/development/core/request-context.mjs.map +1 -0
- package/dist/development/core/request-routing/handle-shopify-redirects.d.mts +17 -0
- package/dist/development/core/request-routing/handle-shopify-redirects.mjs +31 -0
- package/dist/development/core/request-routing/handle-shopify-redirects.mjs.map +1 -0
- package/dist/development/core/request-routing/handle-shopify-routes.d.mts +1 -0
- package/dist/development/core/request-routing/handle-shopify-routes.development.d.mts +11 -0
- package/dist/development/core/request-routing/handle-shopify-routes.development.mjs +16 -0
- package/dist/development/core/request-routing/handle-shopify-routes.development.mjs.map +1 -0
- package/dist/development/core/request-routing/handle-shopify-routes.mjs +42 -0
- package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/admin-redirect.mjs +12 -0
- package/dist/development/core/request-routing/interceptors/admin-redirect.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/ajax-api.mjs +16 -0
- package/dist/development/core/request-routing/interceptors/ajax-api.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/api-proxy.mjs +20 -0
- package/dist/development/core/request-routing/interceptors/api-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/checkout.mjs +58 -0
- package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/graphiql.mjs +46 -0
- package/dist/development/core/request-routing/interceptors/graphiql.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +23 -0
- package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/proxy.mjs +73 -0
- package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +22 -0
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +27 -0
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/standard-routes.mjs +19 -0
- package/dist/development/core/request-routing/interceptors/standard-routes.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/url-redirects.mjs +27 -0
- package/dist/development/core/request-routing/interceptors/url-redirects.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/well-known.mjs +22 -0
- package/dist/development/core/request-routing/interceptors/well-known.mjs.map +1 -0
- package/dist/development/core/request-routing/registered-routes.d.mts +7 -0
- package/dist/development/core/request-routing/registered-routes.mjs +72 -0
- package/dist/development/core/request-routing/registered-routes.mjs.map +1 -0
- package/dist/development/core/request-routing/route-types.d.mts +56 -0
- package/dist/development/core/request-routing/safe-apply-response-headers.mjs +16 -0
- package/dist/development/core/request-routing/safe-apply-response-headers.mjs.map +1 -0
- package/dist/development/core/shop-pay/shop-pay.d.mts +102 -0
- package/dist/development/core/shop-pay/shop-pay.mjs +289 -0
- package/dist/development/core/shop-pay/shop-pay.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/analytics.mjs +26 -0
- package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/consent.mjs +17 -0
- package/dist/development/core/shopify-scripts/consent.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/constants.mjs +19 -0
- package/dist/development/core/shopify-scripts/constants.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +97 -0
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/global.d.mts +1 -0
- package/dist/development/core/shopify-scripts/global.mjs +86 -0
- package/dist/development/core/shopify-scripts/global.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/index.d.mts +27 -0
- package/dist/development/core/shopify-scripts/index.mjs +145 -0
- package/dist/development/core/shopify-scripts/index.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/initialize.d.mts +18 -0
- package/dist/development/core/shopify-scripts/initialize.mjs +27 -0
- package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/page-view.mjs +49 -0
- package/dist/development/core/shopify-scripts/page-view.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/perfkit.mjs +33 -0
- package/dist/development/core/shopify-scripts/perfkit.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/render.d.mts +1 -0
- package/dist/development/core/shopify-scripts/render.mjs +23 -0
- package/dist/development/core/shopify-scripts/render.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/types.d.mts +76 -0
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +43 -0
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/utils/inline-script.mjs +8 -0
- package/dist/development/core/shopify-scripts/utils/inline-script.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/utils/navigation.mjs +46 -0
- package/dist/development/core/shopify-scripts/utils/navigation.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +65 -0
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/utils/uuid.mjs +17 -0
- package/dist/development/core/shopify-scripts/utils/uuid.mjs.map +1 -0
- package/dist/development/core/shopify-scripts/webmcp.mjs +22 -0
- package/dist/development/core/shopify-scripts/webmcp.mjs.map +1 -0
- package/dist/development/core/standard-routes/build.d.mts +34 -0
- package/dist/development/core/standard-routes/build.mjs +54 -0
- package/dist/development/core/standard-routes/build.mjs.map +1 -0
- package/dist/development/core/standard-routes/defaults.mjs +31 -0
- package/dist/development/core/standard-routes/defaults.mjs.map +1 -0
- package/dist/development/core/standard-routes/match.mjs +108 -0
- package/dist/development/core/standard-routes/match.mjs.map +1 -0
- package/dist/development/core/standard-routes/path.mjs +58 -0
- package/dist/development/core/standard-routes/path.mjs.map +1 -0
- package/dist/development/core/standard-routes/redirects.mjs +51 -0
- package/dist/development/core/standard-routes/redirects.mjs.map +1 -0
- package/dist/development/core/standard-routes/types.d.mts +95 -0
- package/dist/development/core/types.d.mts +11 -0
- package/dist/development/core/url.mjs +42 -0
- package/dist/development/core/url.mjs.map +1 -0
- package/dist/development/core/utils/load-script.mjs +34 -0
- package/dist/development/core/utils/load-script.mjs.map +1 -0
- package/dist/development/core/utils/parse-gid.mjs +33 -0
- package/dist/development/core/utils/parse-gid.mjs.map +1 -0
- package/dist/development/customer-account/session.d.mts +46 -0
- package/dist/development/customer-account/session.mjs +21 -0
- package/dist/development/customer-account/session.mjs.map +1 -0
- package/dist/development/globals.d.mts +64 -0
- package/dist/development/graphql/generated/customer-account-api-types.d.mts +13 -0
- package/dist/development/graphql/generated/graphql-env.d.mts +19280 -0
- package/dist/development/graphql/generated/storefront-api-types.d.mts +47 -0
- package/dist/development/graphql/graphql.d.mts +40 -0
- package/dist/development/graphql/graphql.mjs +16 -0
- package/dist/development/graphql/graphql.mjs.map +1 -0
- package/dist/development/graphql/index.d.mts +2 -0
- package/dist/development/graphql/scalars.d.mts +15 -0
- package/dist/development/graphql/type-resolver.d.mts +229 -0
- package/dist/development/react/cart.d.mts +44 -0
- package/dist/development/react/cart.mjs +155 -0
- package/dist/development/react/cart.mjs.map +1 -0
- package/dist/development/react/collection.d.mts +91 -0
- package/dist/development/react/collection.mjs +129 -0
- package/dist/development/react/collection.mjs.map +1 -0
- package/dist/development/react/index.d.mts +13 -0
- package/dist/development/react/index.mjs +10 -0
- package/dist/development/react/predictive-search.d.mts +49 -0
- package/dist/development/react/predictive-search.mjs +133 -0
- package/dist/development/react/predictive-search.mjs.map +1 -0
- package/dist/development/react/product.d.mts +72 -0
- package/dist/development/react/product.mjs +127 -0
- package/dist/development/react/product.mjs.map +1 -0
- package/dist/development/react/shop-pay.d.mts +9 -0
- package/dist/development/react/shop-pay.mjs +16 -0
- package/dist/development/react/shop-pay.mjs.map +1 -0
- package/dist/development/react/shopify-scripts.d.mts +20 -0
- package/dist/development/react/shopify-scripts.mjs +39 -0
- package/dist/development/react/shopify-scripts.mjs.map +1 -0
- package/dist/development/vendor/standard-actions.d.mts +134 -0
- package/dist/development/vendor/standard-events.d.mts +1107 -0
- package/dist/development/vue/cart.d.mts +46 -0
- package/dist/development/vue/cart.mjs +122 -0
- package/dist/development/vue/cart.mjs.map +1 -0
- package/dist/development/vue/collection.d.mts +46 -0
- package/dist/development/vue/collection.mjs +108 -0
- package/dist/development/vue/collection.mjs.map +1 -0
- package/dist/development/vue/index.d.mts +11 -0
- package/dist/development/vue/index.mjs +8 -0
- package/dist/development/vue/predictive-search.d.mts +124 -0
- package/dist/development/vue/predictive-search.mjs +171 -0
- package/dist/development/vue/predictive-search.mjs.map +1 -0
- package/dist/development/vue/product.d.mts +57 -0
- package/dist/development/vue/product.mjs +167 -0
- package/dist/development/vue/product.mjs.map +1 -0
- package/dist/development/vue/shop-pay.d.mts +25 -0
- package/dist/development/vue/shop-pay.mjs +35 -0
- package/dist/development/vue/shop-pay.mjs.map +1 -0
- package/dist/development/vue/shopify-scripts.d.mts +127 -0
- package/dist/development/vue/shopify-scripts.mjs +79 -0
- package/dist/development/vue/shopify-scripts.mjs.map +1 -0
- package/dist/globals.d.mts +64 -0
- package/dist/graphql/generated/customer-account-api-types.d.mts +13 -0
- package/dist/graphql/generated/customer-account-graphql-env.d.mts +22170 -0
- package/dist/graphql/generated/graphql-env.d.mts +19280 -0
- package/dist/graphql/generated/storefront-api-types.d.mts +47 -0
- package/dist/graphql/graphql.d.mts +40 -0
- package/dist/graphql/graphql.mjs +16 -0
- package/dist/graphql/graphql.mjs.map +1 -0
- package/dist/graphql/index.d.mts +2 -0
- package/dist/graphql/scalars.d.mts +26 -0
- package/dist/graphql/type-resolver.d.mts +229 -0
- package/dist/react/cart.d.mts +44 -0
- package/dist/react/cart.mjs +155 -0
- package/dist/react/cart.mjs.map +1 -0
- package/dist/react/collection.d.mts +91 -0
- package/dist/react/collection.mjs +129 -0
- package/dist/react/collection.mjs.map +1 -0
- package/dist/react/index.d.mts +13 -0
- package/dist/react/index.mjs +10 -0
- package/dist/react/predictive-search.d.mts +49 -0
- package/dist/react/predictive-search.mjs +133 -0
- package/dist/react/predictive-search.mjs.map +1 -0
- package/dist/react/product.d.mts +72 -0
- package/dist/react/product.mjs +127 -0
- package/dist/react/product.mjs.map +1 -0
- package/dist/react/shop-pay.d.mts +9 -0
- package/dist/react/shop-pay.mjs +16 -0
- package/dist/react/shop-pay.mjs.map +1 -0
- package/dist/react/shopify-scripts.d.mts +20 -0
- package/dist/react/shopify-scripts.mjs +39 -0
- package/dist/react/shopify-scripts.mjs.map +1 -0
- package/dist/storefront-api-types.d.ts +2351 -2390
- package/dist/storefront.schema.json +41534 -1
- package/dist/ts-plugin/index.cjs +68 -0
- package/dist/ts-plugin/index.cjs.map +1 -0
- package/dist/ts-plugin/index.d.cts +4 -0
- package/dist/vendor/standard-actions.d.mts +134 -0
- package/dist/vendor/standard-events.d.mts +1107 -0
- package/dist/vite/certificate-prompt.mjs +24 -0
- package/dist/vite/certificate-prompt.mjs.map +1 -0
- package/dist/vite/index.d.mts +2 -0
- package/dist/vite/index.mjs +3 -0
- package/dist/vite/local-https.d.mts +47 -0
- package/dist/vite/local-https.mjs +276 -0
- package/dist/vite/local-https.mjs.map +1 -0
- package/dist/vite/mkcert.mjs +141 -0
- package/dist/vite/mkcert.mjs.map +1 -0
- package/dist/vue/cart.d.mts +46 -0
- package/dist/vue/cart.mjs +122 -0
- package/dist/vue/cart.mjs.map +1 -0
- package/dist/vue/collection.d.mts +46 -0
- package/dist/vue/collection.mjs +108 -0
- package/dist/vue/collection.mjs.map +1 -0
- package/dist/vue/index.d.mts +11 -0
- package/dist/vue/index.mjs +8 -0
- package/dist/vue/predictive-search.d.mts +124 -0
- package/dist/vue/predictive-search.mjs +171 -0
- package/dist/vue/predictive-search.mjs.map +1 -0
- package/dist/vue/product.d.mts +57 -0
- package/dist/vue/product.mjs +167 -0
- package/dist/vue/product.mjs.map +1 -0
- package/dist/vue/shop-pay.d.mts +25 -0
- package/dist/vue/shop-pay.mjs +35 -0
- package/dist/vue/shop-pay.mjs.map +1 -0
- package/dist/vue/shopify-scripts.d.mts +127 -0
- package/dist/vue/shopify-scripts.mjs +79 -0
- package/dist/vue/shopify-scripts.mjs.map +1 -0
- package/package.json +116 -86
- package/skills/hydrogen-analytics/SKILL.md +80 -0
- package/skills/hydrogen-analytics/references/react.md +160 -0
- package/skills/hydrogen-analytics/references/vue.md +80 -0
- package/skills/hydrogen-cart-drawer/SKILL.md +248 -0
- package/skills/hydrogen-cart-drawer/references/accessibility.md +109 -0
- package/skills/hydrogen-cart-drawer/references/css.md +67 -0
- package/skills/hydrogen-cart-ui/SKILL.md +224 -0
- package/skills/hydrogen-cart-ui/references/nextjs.md +89 -0
- package/skills/hydrogen-cart-ui/references/react.md +285 -0
- package/skills/hydrogen-cart-ui/references/vue.md +228 -0
- package/skills/hydrogen-collection-browser/SKILL.md +83 -0
- package/skills/hydrogen-collection-browser/references/nextjs.md +118 -0
- package/skills/hydrogen-collection-browser/references/nuxt.md +108 -0
- package/skills/hydrogen-collection-browser/references/react.md +301 -0
- package/skills/hydrogen-customer-account/SKILL.md +107 -0
- package/skills/hydrogen-image/SKILL.md +65 -0
- package/skills/hydrogen-local-https/SKILL.md +142 -0
- package/skills/hydrogen-markets/SKILL.md +221 -0
- package/skills/hydrogen-markets/references/nextjs.md +157 -0
- package/skills/hydrogen-money/SKILL.md +67 -0
- package/skills/hydrogen-oxygen/SKILL.md +245 -0
- package/skills/hydrogen-oxygen/references/react-router.md +230 -0
- package/skills/hydrogen-predictive-search/SKILL.md +130 -0
- package/skills/hydrogen-predictive-search/references/react.md +32 -0
- package/skills/hydrogen-predictive-search/references/vue.md +40 -0
- package/skills/hydrogen-request-handlers/SKILL.md +107 -0
- package/skills/hydrogen-request-handlers/references/frameworks.md +179 -0
- package/skills/hydrogen-request-handlers/references/nextjs.md +137 -0
- package/skills/hydrogen-request-handlers/references/nuxt.md +215 -0
- package/skills/hydrogen-routing/SKILL.md +171 -0
- package/skills/hydrogen-setup/SKILL.md +50 -0
- package/skills/hydrogen-setup/references/analytics.md +624 -0
- package/skills/hydrogen-setup/steps/1-inspect-app.md +62 -0
- package/skills/hydrogen-setup/steps/10-analytics.md +10 -0
- package/skills/hydrogen-setup/steps/11-verify.md +31 -0
- package/skills/hydrogen-setup/steps/2-scaffold.md +86 -0
- package/skills/hydrogen-setup/steps/3-build-home-page.md +37 -0
- package/skills/hydrogen-setup/steps/4-collection-and-search.md +14 -0
- package/skills/hydrogen-setup/steps/5-cart.md +10 -0
- package/skills/hydrogen-setup/steps/6-product-detail-page.md +416 -0
- package/skills/hydrogen-setup/steps/7-shopify-runtime-scripts.md +69 -0
- package/skills/hydrogen-setup/steps/8-cart-drawer-and-navbar.md +35 -0
- package/skills/hydrogen-setup/steps/9-account-page.md +54 -0
- package/skills/hydrogen-shop-pay/SKILL.md +79 -0
- package/skills/hydrogen-shop-pay/references/core.md +66 -0
- package/skills/hydrogen-shop-pay/references/react.md +50 -0
- package/skills/hydrogen-shop-pay/references/vue.md +39 -0
- package/skills/hydrogen-smoke-test/SKILL.md +205 -0
- package/skills/hydrogen-storefront-client/SKILL.md +322 -0
- package/skills/hydrogen-storefront-client/references/astro.md +125 -0
- package/skills/hydrogen-storefront-client/references/caching.md +25 -0
- package/skills/hydrogen-storefront-client/references/nextjs.md +121 -0
- package/skills/hydrogen-storefront-client/references/nuxt.md +121 -0
- package/skills/hydrogen-storefront-client/references/query-validation.md +95 -0
- package/skills/hydrogen-storefront-client/references/react-router.md +81 -0
- package/skills/hydrogen-storefront-client/references/solidstart.md +131 -0
- package/skills/hydrogen-storefront-client/references/sveltekit.md +169 -0
- package/skills/hydrogen-variant-form/SKILL.md +267 -0
- package/skills/hydrogen-variant-form/references/nextjs.md +207 -0
- package/skills/hydrogen-variant-form/references/nuxt.md +95 -0
- package/skills/hydrogen-variant-form/references/react.md +71 -0
- package/dist/dev/get-virtual-routes-H72465VB.js +0 -74
- package/dist/dev/hydrogen-routes.d.ts +0 -5
- package/dist/dev/hydrogen-routes.js +0 -25
- package/dist/development/get-virtual-routes-ACFZHU42.js +0 -71
- package/dist/development/get-virtual-routes-ACFZHU42.js.map +0 -1
- package/dist/development/index.cjs +0 -6978
- package/dist/development/index.cjs.map +0 -1
- package/dist/development/index.js +0 -6631
- package/dist/development/index.js.map +0 -1
- package/dist/development/log-seo-tags-IG37ONQ2.js +0 -74
- package/dist/development/log-seo-tags-IG37ONQ2.js.map +0 -1
- package/dist/development/react-router-preset.d.ts +0 -45
- package/dist/development/react-router-preset.js +0 -49
- package/dist/development/react-router-preset.js.map +0 -1
- package/dist/index.cjs +0 -1
- package/dist/oxygen/index.d.ts +0 -197
- package/dist/oxygen/index.js +0 -107
- package/dist/production/get-virtual-routes-ZZEGQQDV.js +0 -3
- package/dist/production/get-virtual-routes-ZZEGQQDV.js.map +0 -1
- package/dist/production/index.cjs +0 -1019
- package/dist/production/index.cjs.map +0 -1
- package/dist/production/index.d.cts +0 -3260
- package/dist/production/index.d.ts +0 -3260
- package/dist/production/index.js +0 -1019
- package/dist/production/index.js.map +0 -1
- package/dist/production/log-seo-tags-TY72EQWZ.js +0 -2
- package/dist/production/log-seo-tags-TY72EQWZ.js.map +0 -1
- package/dist/production/react-router-preset.d.ts +0 -45
- package/dist/production/react-router-preset.js +0 -11
- package/dist/react-router.d.ts +0 -75
- package/dist/vite/chunk-4O5OI3OO.js +0 -127
- package/dist/vite/chunk-FAQ7TVHC.js +0 -9
- package/dist/vite/chunk-QXBQVDMY.js +0 -68
- package/dist/vite/compat-date.d.ts +0 -3
- package/dist/vite/compat-date.js +0 -6
- package/dist/vite/get-virtual-routes.d.ts +0 -21
- package/dist/vite/get-virtual-routes.js +0 -74
- package/dist/vite/hydrogen-middleware.d.ts +0 -9
- package/dist/vite/hydrogen-middleware.js +0 -7
- package/dist/vite/plugin.d.ts +0 -22
- package/dist/vite/plugin.js +0 -171
- package/dist/vite/request-events.d.ts +0 -144
- package/dist/vite/request-events.js +0 -10
- package/dist/vite/types.d.ts +0 -5
- package/dist/vite/types.js +0 -0
- package/dist/vite/virtual-routes/assets/debug-network.css +0 -609
- package/dist/vite/virtual-routes/assets/favicon-dark.svg +0 -20
- package/dist/vite/virtual-routes/assets/favicon.svg +0 -28
- package/dist/vite/virtual-routes/assets/inter-variable-font.woff2 +0 -0
- package/dist/vite/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
- package/dist/vite/virtual-routes/assets/styles.css +0 -249
- package/dist/vite/virtual-routes/components/FlameChartWrapper.jsx +0 -123
- package/dist/vite/virtual-routes/components/HydrogenLogoBaseBW.jsx +0 -32
- package/dist/vite/virtual-routes/components/HydrogenLogoBaseColor.jsx +0 -47
- package/dist/vite/virtual-routes/components/IconBanner.jsx +0 -292
- package/dist/vite/virtual-routes/components/IconClose.jsx +0 -38
- package/dist/vite/virtual-routes/components/IconDiscard.jsx +0 -44
- package/dist/vite/virtual-routes/components/IconError.jsx +0 -61
- package/dist/vite/virtual-routes/components/IconGithub.jsx +0 -23
- package/dist/vite/virtual-routes/components/IconTwitter.jsx +0 -21
- package/dist/vite/virtual-routes/components/Layout.jsx +0 -7
- package/dist/vite/virtual-routes/components/RequestDetails.jsx +0 -171
- package/dist/vite/virtual-routes/components/RequestTable.jsx +0 -91
- package/dist/vite/virtual-routes/components/RequestWaterfall.jsx +0 -151
- package/dist/vite/virtual-routes/layout.jsx +0 -33
- package/dist/vite/virtual-routes/lib/useDebugNetworkServer.jsx +0 -178
- package/dist/vite/virtual-routes/routes/[.]well-known.appspecific.com[.]chrome[.]devtools[.]json.jsx +0 -37
- package/dist/vite/virtual-routes/routes/[.]well-known.appspecific.com[.]chrome[.]devtools[.]json.tsx +0 -79
- package/dist/vite/virtual-routes/routes/graphiql.jsx +0 -5
- package/dist/vite/virtual-routes/routes/index.jsx +0 -265
- package/dist/vite/virtual-routes/routes/subrequest-profiler.jsx +0 -243
- package/dist/vite/virtual-routes/virtual-root-with-layout.jsx +0 -63
- package/dist/vite/virtual-routes/virtual-root.jsx +0 -67
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hydrogen-variant-form
|
|
3
|
+
description: >
|
|
4
|
+
Behavioral guide for building product variant selection UI with
|
|
5
|
+
@shopify/hydrogen. Use when writing, modifying, or
|
|
6
|
+
reviewing product option selectors, variant pickers, or add-to-cart forms.
|
|
7
|
+
Framework agnostic.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Product Form Primitive
|
|
11
|
+
|
|
12
|
+
The product form primitive is a client-side store that computes per-option-value existence, availability, and selection state from Shopify's encoded variant fields. Variant selection performs no network requests; it is derived from product data provided by the Storefront API, combined with live cart state for error surfacing and line-item matching. Form submission is delegated to the cart store. Framework-specific bindings are thin wrappers over the core `createProductFormStore`.
|
|
13
|
+
|
|
14
|
+
## How the store works
|
|
15
|
+
|
|
16
|
+
The store holds a `ProductFormStoreState` and notifies subscribers on change. It is initialized with a product object and a `CartStore` instance. The product includes `encodedVariantExistence`, `encodedVariantAvailability`, product options (each with `firstSelectableVariant` per option value), a sparse variant cache (`adjacentVariants`), and a nullable `selectedOrFirstAvailableVariant`.
|
|
17
|
+
|
|
18
|
+
On initialization:
|
|
19
|
+
1. The store uses the encoded fields, when present, to determine which option-value combinations **exist** and which are **available** (in stock). Matching is symmetric: every option value is evaluated against all selected options that belong to the current product option matrix, regardless of option order.
|
|
20
|
+
2. It stitches together the sparse variant cache (`adjacentVariants` + `firstSelectableVariant` per option value + `selectedOrFirstAvailableVariant`) to resolve a concrete `variant` object per option value where possible.
|
|
21
|
+
3. It sets the initial selection from `selectedOrFirstAvailableVariant` (server-resolved), falling back to explicitly provided `selectedOptions`, falling back to empty selection.
|
|
22
|
+
4. It subscribes to the `CartStore` to derive `matchedLineItem` and `errors` reactively.
|
|
23
|
+
|
|
24
|
+
On `selectOption(name, value)`:
|
|
25
|
+
1. The store validates that the option name and value exist on the product.
|
|
26
|
+
2. It validates that the combination exists when Shopify provides an encoded existence field.
|
|
27
|
+
3. It updates `selectedOptions` and recomputes the full options grid.
|
|
28
|
+
4. It returns a `VariantSelectionResult`: `resolved` (full variant matched), `unresolved` (valid, but no full variant resolved locally), or `invalid` (unknown or non-existent, with a `reason` string).
|
|
29
|
+
|
|
30
|
+
The store does **not** own URL synchronization or navigation. Add-to-cart mutation logic belongs to the cart store and is reached through `handleFormSubmit(event)`.
|
|
31
|
+
|
|
32
|
+
## State shape
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
interface ProductFormStoreState<TVariant> {
|
|
36
|
+
options: VariantOptionState<TVariant>[];
|
|
37
|
+
selectedOptions: SelectedOption[];
|
|
38
|
+
selectedVariant: TVariant | null;
|
|
39
|
+
errors: ProductFormErrors;
|
|
40
|
+
matchedLineItem: CartLine | null;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`options` is the computed grid — one entry per product option, each containing its values:
|
|
45
|
+
|
|
46
|
+
- `value.name` — the option value label (e.g. "Red", "Small").
|
|
47
|
+
- `value.selected` — whether this value is part of the current selection.
|
|
48
|
+
- `value.exists` — resolved from `encodedVariantExistence` when present. `false` means no variant exists for the current symmetric selection constraints — the control should be disabled and visually de-emphasized.
|
|
49
|
+
- `value.available` — resolved from `encodedVariantAvailability` when present. `false` means no available variant was found for the current symmetric selection constraints.
|
|
50
|
+
- `value.variant` — the resolved variant object, or `null` if the combination is not in the local cache.
|
|
51
|
+
- `value.selectedOptions` — the full option tuple that would result from selecting this value. Used for URL construction.
|
|
52
|
+
- `value.handle` — the product handle for this variant. Differs from the current product's handle in combined listings.
|
|
53
|
+
|
|
54
|
+
`selectedOptions` is the current selection in product-option order (e.g. `[{name: "Size", value: "Small"}, {name: "Color", value: "Red"}]`).
|
|
55
|
+
|
|
56
|
+
`selectedVariant` is the currently resolved variant, or `null` when the selection is incomplete or the variant is not in the local cache.
|
|
57
|
+
|
|
58
|
+
`errors` surfaces cart errors relevant to this product form:
|
|
59
|
+
- `userErrors` — merged cart-level and line-level user errors for the matched line item.
|
|
60
|
+
- `warnings` — merged cart-level and line-level warnings.
|
|
61
|
+
- `networkErrors` — cart network errors.
|
|
62
|
+
|
|
63
|
+
`matchedLineItem` is the cart line whose `merchandise.id` matches the selected variant's ID, or `null`.
|
|
64
|
+
|
|
65
|
+
## Selection results
|
|
66
|
+
|
|
67
|
+
`selectOption` returns one of three outcomes:
|
|
68
|
+
|
|
69
|
+
- **`resolved`** — a specific variant was matched. Contains `selectedVariant` with the full variant object and `selectedOptions` with the canonical option tuple from the variant.
|
|
70
|
+
- **`unresolved`** — the selection is valid, but no full variant is resolved locally because the selection is incomplete or the exact variant is absent from the bounded cache. If the buyer has not selected every option yet, ask for the remaining options. If the selection is complete but missing from the local cache, fetch or otherwise resolve the exact variant before treating it as complete.
|
|
71
|
+
- **`invalid`** — the option name or value does not exist on the product, or the combination does not exist per the encoded existence field. State is **not** updated. Contains a `reason` string explaining why the selection was rejected.
|
|
72
|
+
|
|
73
|
+
## Register API
|
|
74
|
+
|
|
75
|
+
The `register` function binds product form identity and activation handlers. It covers product-relevant fields. It intentionally does not emit option control UI props like `checked`, `disabled`, or `aria-pressed`; derive those from `options`, `selectedVariant`, and caller-owned state.
|
|
76
|
+
|
|
77
|
+
### `register("merchandiseId", opts)`
|
|
78
|
+
|
|
79
|
+
Returns `{ name: "merchandiseId", value: selectedVariantId }`. The `value` is an empty string when no variant is resolved.
|
|
80
|
+
|
|
81
|
+
### `register("quantity", opts)`
|
|
82
|
+
|
|
83
|
+
Accepts `{ value: number }` for a controlled input or `{ defaultValue: number }` for an uncontrolled input. Returns the appropriate `{ name, value }` or `{ name, defaultValue }` props, with numeric values stringified for HTML form submission.
|
|
84
|
+
|
|
85
|
+
### `register("optionValue", opts)`
|
|
86
|
+
|
|
87
|
+
Requires `{ optionName, value }`. Returns `{ name, value, onChange, onClick }` — form identity plus activation handlers. Derive caller-owned UI props from the matching option value state, such as `value.exists`, `value.available`, and `value.selected`.
|
|
88
|
+
|
|
89
|
+
### `register("addToCart", opts)`
|
|
90
|
+
|
|
91
|
+
Returns `{ name: "add-to-cart", type: "submit" }` for the add-to-cart submit button.
|
|
92
|
+
|
|
93
|
+
## Form submission
|
|
94
|
+
|
|
95
|
+
`handleFormSubmit(event)` delegates to the underlying `CartStore`'s form submission. The store does not own submission logic — it passes the `SubmitEvent` through to the cart layer, plus selected-product event detail when a variant is resolved. Cart errors from the submission are surfaced reactively via the `errors` state.
|
|
96
|
+
|
|
97
|
+
## Route Placement
|
|
98
|
+
|
|
99
|
+
When creating a product detail page, use the app's existing route convention when present; otherwise create `/products/{handle}`. Keep variant selection in query params on that product route.
|
|
100
|
+
|
|
101
|
+
## Framework References
|
|
102
|
+
|
|
103
|
+
Before building product UI, check whether this skill has a reference file for the app's framework in `references/`. If one exists, read it and use that framework binding or route pattern first.
|
|
104
|
+
|
|
105
|
+
If there is no matching reference, use `createProductFormStore` from `@shopify/hydrogen` directly, subscribe with the framework's reactivity primitive, and own hydration, URL sync, and `destroy()` yourself. Packaged bindings are thin wrappers over this same store — apply every rule and anti-pattern below; do not invent a different contract.
|
|
106
|
+
|
|
107
|
+
## Hydration
|
|
108
|
+
|
|
109
|
+
When the product data changes (e.g. after a URL navigation triggers a data refetch), the store must be hydrated with the new product — not recreated. `hydrate(product, opts?)` replaces the product data, clears the decoded variant cache, and recomputes state. The new product's `selectedOrFirstAvailableVariant` takes priority; if absent, falls back to explicitly provided `opts.selectedOptions`, then to the selection that was active before hydration.
|
|
110
|
+
|
|
111
|
+
Provider bindings should hydrate automatically when the product's semantic identity changes (`product.id` + `selectedOrFirstAvailableVariant.id`). They should skip hydration on mount to avoid double-initialization, and skip hydration when the product identity is unchanged — preserving user selections through unrelated re-renders. Standalone `useProductForm(store)` users manage hydration themselves.
|
|
112
|
+
|
|
113
|
+
## Reset
|
|
114
|
+
|
|
115
|
+
`reset()` restores the store to its initial state — the product and selected options it was created with. It clears the decoded variant cache and recomputes all derived state. Useful for "reset form" interactions.
|
|
116
|
+
|
|
117
|
+
## Extracting selected options from the URL
|
|
118
|
+
|
|
119
|
+
`getSelectedProductOptions({ searchParams, allowedOptionNames })` extracts selected options from `URLSearchParams`. Each query parameter is treated as an option name/value pair (e.g. `?Color=Red&Size=M` produces `[{name:"Color",value:"Red"},{name:"Size",value:"M"}]`).
|
|
120
|
+
|
|
121
|
+
Pass `allowedOptionNames` to filter search params to only known product option names, avoiding unrelated query parameters. Passing an empty array filters out every option.
|
|
122
|
+
|
|
123
|
+
The `variant` param is reserved for Liquid-style numeric variant ids and is never treated as an option name. Server-side, `handleShopifyRoutes({ routeTemplates })` (see the local `hydrogen-request-handlers` skill) redirects `?variant=<id>` product URLs to their canonical option-params URL before the loader runs; when both `variant` and option params are present, the variant wins.
|
|
124
|
+
|
|
125
|
+
## Building selection URLs
|
|
126
|
+
|
|
127
|
+
`buildProductSelectionSearchParams({ style?, selectedOptions, variant?, optionNames, base? })` builds the search params for a selection link. It always removes the reserved `variant` param and every param named in `optionNames`/`selectedOptions` from `base` before writing the new selection, preserving unrelated params (`?ref=campaign`). Compose it with the app's product pathname:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const params = buildProductSelectionSearchParams({
|
|
131
|
+
selectedOptions: result.selectedOptions,
|
|
132
|
+
optionNames: product.options.map((option) => option.name),
|
|
133
|
+
base: new URLSearchParams(location.search),
|
|
134
|
+
});
|
|
135
|
+
const url = `/products/${handle}${params.size ? `?${params}` : ""}`;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Pass `style: "variant"` with a resolved `variant` to emit a shareable `?variant=<numeric id>` link instead of option params. When no variant is resolved (partial selection), the variant style falls back to option params — a variant link is not constructible. Prefer option-params links for in-app navigation; `?variant=` links cost a server redirect on landing.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Rules
|
|
143
|
+
|
|
144
|
+
- **Hide the entire variant picker when no option has more than one value** (`options.every((o) => o.values.length <= 1)`). A single-variant product has nothing to choose — rendering its one option/one value is noise. This is distinct from the value-hiding rule below: it hides the *whole picker* when there's nothing to choose, not individual values based on selection.
|
|
145
|
+
|
|
146
|
+
### Existence and availability
|
|
147
|
+
|
|
148
|
+
- **Availability and existence are symmetric.** Selecting a later option can make earlier option values unavailable or non-existent. For example, if `Medium / Olive` is sold out, selecting `Color=Olive` should mark `Size=Medium` as unavailable even though `Medium / Green` is available.
|
|
149
|
+
- **ALWAYS disable and visually de-emphasize option values where `exists` is `false`.** These represent combinations that do not exist in the product's variant matrix. A non-existent combination cannot be selected — showing it as interactive is misleading.
|
|
150
|
+
- **NEVER disable option values where `exists` is `true` but `available` is `false`.** These are sold-out variants. The control must remain interactive so the buyer can see what the variant would be. Show a "Sold out" indicator instead, such as opacity plus line-through styling.
|
|
151
|
+
- **NEVER hide option values based on the current selection.** All values for an option must always be visible. Hiding values based on what's currently selected creates a confusing, collapsing UI that prevents buyers from exploring the full product matrix.
|
|
152
|
+
|
|
153
|
+
### Selection and navigation
|
|
154
|
+
|
|
155
|
+
- **With provider bindings, put URL navigation in the provider `onSelect` callback.** Do not navigate inside each same-product option button. Let `register("optionValue", ...)` call `selectOption`; the provider receives the valid selection result and owns URL sync from there.
|
|
156
|
+
- **ALWAYS use URL-based variant selection in URL-routing apps.** When the buyer selects an option, navigate to the URL representing that selection — replacing the history entry and without resetting scroll position. The URL is the durable source of truth — the data loader reads the selection from the URL, queries the Storefront API, and the response hydrates the store when a reload or revalidation happens.
|
|
157
|
+
- **Same-product option values must degrade to GET links (no JS).** In URL-routing apps, render each same-product option value as a real link (the framework's link component or `<a href>`) whose `href` is the option URL for that value, built from `value.selectedOptions` — not a bare `<button onClick>`. The `href` is the no-JS path: with scripting off, activating it issues a GET to the option URL, the loader reads the params, queries the Storefront API, and the server renders the newly selected variant. Hydration enhances the same element — the registered `register("optionValue", ...)` handler calls `selectOption` and the provider's `onSelect` replaces the URL client-side (no full reload). Because the `href` and the client navigation resolve to the same option URL, behavior is identical with or without JS. On a hydrated click the element both runs the registered handler (which calls `selectOption`) and performs its own link navigation to that same URL, so keep the provider `onSelect` idempotent — the redundant navigation is a harmless no-op. An option control that is button-/`onClick`-only renders nothing a no-JS shopper can act on, stranding them on the default variant. Non-existent combinations (`exists: false`) have no valid option URL to degrade to; render those as a disabled `<button>`, not a link. (Cross-product values are already links; this brings same-product values to parity.)
|
|
158
|
+
- **ALWAYS use the `selectOption` return value for navigation, not a reactive effect on state.** `selectOption` returns the result synchronously. Use the returned `selectedOptions` to construct the next URL immediately. Reacting to derived state (e.g. `selectedOptions` from the store) instead introduces an unnecessary update cycle and risks stale values. Framework bindings may wrap this as an `onSelect` callback — the principle is the same.
|
|
159
|
+
- **Skip data refetch only when the resolved local state is sufficient.** When `selectOption` returns `resolved`, the selected variant is already in the local cache. It is safe to skip revalidation only if the route does not need fresh loader data for other UI. When a complete selection returns `unresolved`, fetch or otherwise resolve the exact variant before treating the selection as complete.
|
|
160
|
+
- **NEVER call `selectOption` for combined-listing cross-product values.** When `value.handle !== product.handle`, the value belongs to a different product. Render it as a navigation element (anchor or link component) that navigates to the other product's URL — not a button that calls `selectOption`. The store only knows about the current product's variant matrix.
|
|
161
|
+
|
|
162
|
+
### Combined listings
|
|
163
|
+
|
|
164
|
+
- **ALWAYS check `value.handle` against the current `product.handle`.** If they differ, the option value points to a different product in a combined listing. The UI must navigate to that product (full page navigation or a link component), not call `selectOption`.
|
|
165
|
+
- **Use the framework's client-side link component for cross-product values when one exists.** Use the app's idiomatic navigation primitive. Use a raw `<a>` only when that is the app's established routing convention.
|
|
166
|
+
- **Preserve non-option query params on combined-listing links.** When constructing the URL for a combined-listing link, carry forward existing search params (e.g. `?ref=campaign`) and replace only the option params. Use `buildProductSelectionSearchParams` with the current product's option names as `optionNames` — it deletes all option params (and any stale `variant` param) first, then sets the new ones, preventing stale params when combined-listing products have different option names.
|
|
167
|
+
- **Ignore selected options that are not in the current product option matrix.** Divergent combined-listing child products can have different option names. A stale `Color=Black` param must not constrain a child product whose options are `Size` and `Mount`.
|
|
168
|
+
|
|
169
|
+
### Price display
|
|
170
|
+
|
|
171
|
+
- **ALWAYS display server-provided prices.** Use `selectedVariant.price` when a variant is resolved. Fall back to `product.priceRange.minVariantPrice` when no variant is selected. Never compute prices client-side.
|
|
172
|
+
- **Format with Hydrogen money helpers**, not string concatenation. Use the local `hydrogen-money` skill for app wrappers around `formatMoney()`.
|
|
173
|
+
|
|
174
|
+
### Add-to-cart
|
|
175
|
+
|
|
176
|
+
- **ALWAYS use `canAddToCart(product, options)` to determine if the add-to-cart button should be enabled.** This checks three conditions: a variant is selected, it is available for sale, and the product does not require a selling plan. Checking only `selectedVariant !== null` misses the selling-plan and availability constraints.
|
|
177
|
+
- **The add-to-cart form is separate from the variant selector.** Variant selection uses buttons and links — not form submissions. The add-to-cart form contains `merchandiseId` (the selected variant ID) and `quantity`. Do not put variant selection controls inside the cart form.
|
|
178
|
+
- **Use `register` to bind form fields.** `register("merchandiseId", {})` returns the hidden input props with the current variant ID. `register("quantity", { value: 1 })` returns the quantity input props. `register("addToCart", {})` returns stable add-to-cart submit button props. These stay synchronized with store state automatically.
|
|
179
|
+
- **Use the local `hydrogen-shop-pay` skill** when adding accelerated checkout near the add-to-cart form.
|
|
180
|
+
- **Show contextual CTA text.** When `canAddToCart` is `true`: "Add to cart". When no variant is selected (`selectedVariant === null`): "Select options" unless a navigation or submission is actually pending. When a variant is selected but unavailable: "Unavailable" or "Sold out".
|
|
181
|
+
- **Surface cart errors from `errors` state.** After form submission, user errors, warnings, and network errors relevant to the current product form are available on `state.errors`. Display these to the buyer.
|
|
182
|
+
|
|
183
|
+
### Store lifecycle
|
|
184
|
+
|
|
185
|
+
- **Create the store once per component mount.** Do not recreate the store when the product prop changes — use `hydrate()` instead. Recreating the store discards the decoded variant cache and any user interaction state.
|
|
186
|
+
- **Always call `destroy()` on unmount.** This unsubscribes from the `CartStore` and releases internal caches (the decoded variant field cache). Provider bindings handle this automatically; standalone store users must do it themselves.
|
|
187
|
+
- **Do not read stale state after hydration.** Hydration triggers a synchronous state update. Any code that caches the previous state reference before hydration holds a stale reference.
|
|
188
|
+
|
|
189
|
+
### Accessibility
|
|
190
|
+
|
|
191
|
+
- **Communicate selected state with the attribute that fits the element.** Same-product and cross-product option values render as links (per the GET-links and combined-listing rules), so mark the selected one with `aria-current` — `aria-pressed` is not a valid state on a link. Reserve `aria-pressed={value.selected}` for values rendered as a `<button>`, chiefly the disabled non-existent (`exists: false`) case. Derive either from the matching option value's `selected` state.
|
|
192
|
+
- **Use `aria-label` on visual-only controls** (e.g. color swatches without visible text).
|
|
193
|
+
- **Disabled controls (`exists: false`) must use the native `disabled` attribute** — not `aria-disabled` with prevented clicks. Non-existent combinations are truly non-interactive.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## User Acceptance Tests
|
|
198
|
+
|
|
199
|
+
### Initial state
|
|
200
|
+
|
|
201
|
+
1. **Pre-selected variant** — When the product has a `selectedOrFirstAvailableVariant`, the corresponding option values are marked as selected on first render. The variant's price and details are displayed. `selectedVariant` on the state is non-null.
|
|
202
|
+
2. **No pre-selected variant** — When `selectedOrFirstAvailableVariant` is `null`, no option values are selected. `selectedVariant` is `null`. The add-to-cart button shows "Select options" (or equivalent) and is disabled.
|
|
203
|
+
3. **URL-driven selection** — When the URL encodes a variant selection (e.g. via option params like `?Color=Blue&Size=Small`), the data loader resolves the selection and passes it to the Storefront API query. The returned `selectedOrFirstAvailableVariant` reflects the URL selection, and the store initializes with those options selected.
|
|
204
|
+
|
|
205
|
+
### Option selection
|
|
206
|
+
|
|
207
|
+
4. **Select a value** — Click an option value button. The value becomes selected (visually indicated). If the selection resolves to a variant, the price and variant details update immediately. `selectedVariant` updates on the state. In URL-routing apps, the URL updates to reflect the new selection without a scroll reset.
|
|
208
|
+
5. **Multi-option selection** — On a product with Size and Color options, select Size=Large then Color=Blue. Both selections are reflected in the state. The resolved variant matches Large/Blue.
|
|
209
|
+
6. **Symmetric availability** — On a product where Medium/Olive is unavailable but Medium/Green is available, selecting Color=Olive marks Size=Medium as `available: false`; selecting Color=Green marks Size=Medium as `available: true`.
|
|
210
|
+
7. **Switch within an option** — With Color=Red selected, click Color=Blue. The selection switches; Red is deselected, Blue is selected. Only one value per option is selected at a time.
|
|
211
|
+
8. **Invalid selection ignored** — Calling `selectOption` with an unknown option name or value returns `invalid` with a `reason` string and does not change state. No navigation occurs.
|
|
212
|
+
9. **Non-existent combination** — An option value where `exists: false` is disabled. Clicking it does nothing.
|
|
213
|
+
10. **Sold-out variant** — An option value where `exists: true` and `available: false` is interactive but shows a "Sold out" indicator. Selecting it updates the state and shows the variant as unavailable.
|
|
214
|
+
|
|
215
|
+
### Combined listings
|
|
216
|
+
|
|
217
|
+
11. **Cross-product value** — An option value where `value.handle !== product.handle` renders as a navigation element (anchor or link component), not a button. Clicking it navigates to the other product's page with the appropriate option params.
|
|
218
|
+
12. **Same-product value** — An option value where `value.handle === product.handle` renders as a GET link to its option URL and spreads `register("optionValue", ...)`. With JavaScript disabled the link navigates and the server resolves the variant; hydrated, the registered handler calls `selectOption` and the provider syncs the URL client-side.
|
|
219
|
+
13. **Preserved params** — When navigating via a combined-listing link, non-option query params from the current URL are preserved in the destination URL.
|
|
220
|
+
|
|
221
|
+
### Hydration
|
|
222
|
+
|
|
223
|
+
14. **Product navigation** — Navigating from Product A to Product B (different `product.id`) hydrates the store with Product B's data. The selection reflects Product B's `selectedOrFirstAvailableVariant`.
|
|
224
|
+
15. **Same product, different variant** — Navigating to the same product with a different URL-encoded selection (e.g. `?Color=Blue` instead of `?Color=Red`) hydrates with the new pre-selected variant without recreating the store.
|
|
225
|
+
16. **Unrelated re-render** — A re-render that passes the same product identity does not hydrate. User selections made since the last hydration are preserved.
|
|
226
|
+
17. **No double-init on mount** — On initial mount, the store initializes from the constructor — hydration does not fire. A user selection made immediately after mount survives a subsequent re-render with the same product.
|
|
227
|
+
|
|
228
|
+
### Cart integration
|
|
229
|
+
|
|
230
|
+
18. **Matched line item** — When the selected variant's ID matches a cart line's `merchandise.id`, `matchedLineItem` is non-null and contains the cart line data.
|
|
231
|
+
19. **Cart error surfacing** — After a failed add-to-cart submission, `errors.userErrors` contains the relevant user errors, `errors.warnings` contains warnings, and `errors.networkErrors` contains any network failures.
|
|
232
|
+
20. **Reactive cart sync** — When the cart updates externally (e.g. quantity change from a cart drawer), the `matchedLineItem` and `errors` update without any manual intervention.
|
|
233
|
+
|
|
234
|
+
### Add-to-cart
|
|
235
|
+
|
|
236
|
+
21. **Enabled state** — When `canAddToCart` returns `true` (variant selected, available, no selling plan required), the add-to-cart button is enabled and shows "Add to cart".
|
|
237
|
+
22. **Disabled — no variant** — When no variant is selected, the button is disabled with "Select options" text unless navigation or submission is actually pending.
|
|
238
|
+
23. **Disabled — sold out** — When the selected variant is not available for sale, the button is disabled with "Unavailable" or "Sold out" text.
|
|
239
|
+
24. **Disabled — selling plan required** — When `product.requiresSellingPlan` is `true`, the button is disabled regardless of variant selection.
|
|
240
|
+
25. **Variant ID in form** — `register("merchandiseId", {})` returns `{ name: "merchandiseId", value: selectedVariantId }`. When no variant is selected, `value` is an empty string.
|
|
241
|
+
26. **Form submission** — `handleFormSubmit(event)` delegates to the cart store with the submit event and selected-product detail when a variant is resolved. Cart errors surface reactively via the `errors` state.
|
|
242
|
+
|
|
243
|
+
### Register API
|
|
244
|
+
|
|
245
|
+
27. **Option value registration** — `register("optionValue", { optionName: "Color", value: "Red" })` returns `{ name, value, onChange, onClick }`. Calling `onChange` or `onClick` triggers `selectOption`.
|
|
246
|
+
28. **Caller-owned option attributes** — `register("optionValue", { optionName: "Color", value: "Red" })` returns only `{ name, value, onChange, onClick }`. Derive `disabled`, `aria-pressed`, and visual state from the matching `options` value.
|
|
247
|
+
29. **Quantity registration** — `register("quantity", { value: 1 })` returns `{ name: "quantity", value: "1" }`. `register("quantity", { defaultValue: 1 })` returns `{ name: "quantity", defaultValue: "1" }`.
|
|
248
|
+
30. **Add-to-cart registration** — `register("addToCart", {})` returns `{ name: "add-to-cart", type: "submit" }`.
|
|
249
|
+
|
|
250
|
+
### Unresolved selection
|
|
251
|
+
|
|
252
|
+
31. **Transient unresolved** — In URL-routing apps, when a complete selection returns `unresolved` because the exact variant is absent from the local cache, the app navigates to the new URL and re-fetches product data. The subsequent hydration resolves the variant. Incomplete selections should remain in selection UI until the buyer chooses the remaining options.
|
|
253
|
+
|
|
254
|
+
### Reset
|
|
255
|
+
|
|
256
|
+
32. **Reset to initial state** — Calling `reset()` restores the store to the product and selected options it was created with. All user selections are discarded.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Anti-patterns
|
|
261
|
+
|
|
262
|
+
- **Using reactive effects on state to sync selection to URL.** The `selectOption` return value provides the selection result synchronously. Reacting to `state.selectedOptions` instead introduces an extra update cycle and can fire with stale values.
|
|
263
|
+
- **Navigating inside same-product option buttons.** Do not destructure `onClick` / `onChange` from `register("optionValue", ...)`, call `navigate()`, and then call the registered handler manually. This bypasses the provider `onSelect` contract and can navigate from stale or invalid data.
|
|
264
|
+
- **Button-/onClick-only same-product option values.** A same-product option value rendered as `<button onClick={selectOption}>` with no `href` is dead without JavaScript — a no-JS shopper cannot switch variants and is stuck on the default. Render it as a GET link to the option URL and let the registered handler enhance it; the `href` is the progressive-enhancement fallback.
|
|
265
|
+
- **Using raw anchors when the framework has a client-side link component.** Raw `<a>` tags lose client-router behavior such as scroll preservation, pending navigation state, prefetching, and route transitions. Use the app's established link component unless raw anchors are the framework convention.
|
|
266
|
+
- **Putting variant selection inside the add-to-cart form.** Variant selection is button/link interactions that update store state. The add-to-cart form submits `merchandiseId` and `quantity` to the cart. Mixing them creates ambiguous form semantics and breaks progressive enhancement.
|
|
267
|
+
- **Ignoring `errors` state after form submission.** Cart user errors, warnings, and network errors are surfaced reactively on the store state. Failing to display these leaves the buyer with no feedback when something goes wrong.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Next.js App Router
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Server Page
|
|
6
|
+
- Client Details Component
|
|
7
|
+
- Same-Product And Cross-Product Values
|
|
8
|
+
- Add To Cart
|
|
9
|
+
|
|
10
|
+
Product data is fetched in the server page. Variant selection and add-to-cart live in a `"use client"` component because they use `ProductProvider`, browser routing, and cart forms.
|
|
11
|
+
|
|
12
|
+
## Server Page
|
|
13
|
+
|
|
14
|
+
In `app/products/[handle]/page.tsx`, read selected options from URL search params and query Storefront API with Hydrogen's product fields:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { getSelectedProductOptions, gql } from "@shopify/hydrogen";
|
|
18
|
+
|
|
19
|
+
export const PRODUCT_QUERY = gql(`
|
|
20
|
+
query Product($handle: String!, $selectedOptions: [SelectedOptionInput!]!) {
|
|
21
|
+
product(handle: $handle) {
|
|
22
|
+
id
|
|
23
|
+
handle
|
|
24
|
+
title
|
|
25
|
+
vendor
|
|
26
|
+
requiresSellingPlan
|
|
27
|
+
encodedVariantExistence
|
|
28
|
+
encodedVariantAvailability
|
|
29
|
+
options {
|
|
30
|
+
name
|
|
31
|
+
optionValues {
|
|
32
|
+
name
|
|
33
|
+
firstSelectableVariant {
|
|
34
|
+
id
|
|
35
|
+
title
|
|
36
|
+
availableForSale
|
|
37
|
+
selectedOptions { name value }
|
|
38
|
+
price { amount currencyCode }
|
|
39
|
+
compareAtPrice { amount currencyCode }
|
|
40
|
+
product { handle title }
|
|
41
|
+
sku
|
|
42
|
+
}
|
|
43
|
+
swatch { color image { previewImage { url } } }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
selectedOrFirstAvailableVariant(
|
|
47
|
+
selectedOptions: $selectedOptions
|
|
48
|
+
ignoreUnknownOptions: true
|
|
49
|
+
caseInsensitiveMatch: true
|
|
50
|
+
) {
|
|
51
|
+
id
|
|
52
|
+
title
|
|
53
|
+
availableForSale
|
|
54
|
+
selectedOptions { name value }
|
|
55
|
+
price { amount currencyCode }
|
|
56
|
+
compareAtPrice { amount currencyCode }
|
|
57
|
+
product { handle title }
|
|
58
|
+
sku
|
|
59
|
+
}
|
|
60
|
+
adjacentVariants(
|
|
61
|
+
selectedOptions: $selectedOptions
|
|
62
|
+
ignoreUnknownOptions: true
|
|
63
|
+
caseInsensitiveMatch: true
|
|
64
|
+
) {
|
|
65
|
+
id
|
|
66
|
+
title
|
|
67
|
+
availableForSale
|
|
68
|
+
selectedOptions { name value }
|
|
69
|
+
price { amount currencyCode }
|
|
70
|
+
compareAtPrice { amount currencyCode }
|
|
71
|
+
product { handle title }
|
|
72
|
+
sku
|
|
73
|
+
}
|
|
74
|
+
priceRange {
|
|
75
|
+
minVariantPrice { amount currencyCode }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
export default async function ProductPage({ params, searchParams }: Props) {
|
|
82
|
+
const { handle } = await params;
|
|
83
|
+
const selectedOptions = getSelectedProductOptions({
|
|
84
|
+
searchParams: toURLSearchParams(await searchParams),
|
|
85
|
+
});
|
|
86
|
+
const storefront = await getStorefrontClient();
|
|
87
|
+
const { data } = await storefront.graphql(PRODUCT_QUERY, {
|
|
88
|
+
variables: { handle, selectedOptions },
|
|
89
|
+
});
|
|
90
|
+
if (!data?.product) notFound();
|
|
91
|
+
return <ProductDetails product={data.product} />;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function toURLSearchParams(input: Record<string, string | string[] | undefined>) {
|
|
95
|
+
const params = new URLSearchParams();
|
|
96
|
+
for (const [key, value] of Object.entries(input)) {
|
|
97
|
+
if (Array.isArray(value)) {
|
|
98
|
+
for (const item of value) params.append(key, item);
|
|
99
|
+
} else if (value != null) {
|
|
100
|
+
params.set(key, value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return params;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use the route's existing search-param normalization helper if present.
|
|
108
|
+
|
|
109
|
+
## Client Details Component
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
"use client";
|
|
113
|
+
|
|
114
|
+
import { canAddToCart, type SelectedOption, type StorefrontApi } from "@shopify/hydrogen";
|
|
115
|
+
import { createProductComponents } from "@shopify/hydrogen/react";
|
|
116
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
117
|
+
import type { PRODUCT_QUERY } from "../products/[handle]/page";
|
|
118
|
+
|
|
119
|
+
type ProductQuery = StorefrontApi.ResultOf<typeof PRODUCT_QUERY>;
|
|
120
|
+
type ProductData = NonNullable<ProductQuery["product"]>;
|
|
121
|
+
|
|
122
|
+
const { ProductProvider, useProductForm } = createProductComponents<ProductData>();
|
|
123
|
+
|
|
124
|
+
export function ProductDetails({ product }: { product: ProductData }) {
|
|
125
|
+
const router = useRouter();
|
|
126
|
+
const searchParams = useSearchParams();
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<ProductProvider
|
|
130
|
+
product={product}
|
|
131
|
+
onSelect={(result) => {
|
|
132
|
+
router.replace(
|
|
133
|
+
variantUrl(product, result.selectedOptions, result.selectedVariant?.product?.handle, searchParams),
|
|
134
|
+
{ scroll: false },
|
|
135
|
+
);
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<VariantSelector product={product} />
|
|
139
|
+
<AddToCart product={product} />
|
|
140
|
+
</ProductProvider>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Wrap this tree in the app's `CartProvider` from `hydrogen-cart-ui`; `ProductProvider` reads the cart store for add-to-cart submission and product-scoped cart errors.
|
|
146
|
+
|
|
147
|
+
## Same-Product And Cross-Product Values
|
|
148
|
+
|
|
149
|
+
Render same-product option values as GET links (`next/link`) so variant selection degrades without JavaScript (the skill's GET-links rule and accessibility guidance cover the `aria-current`, idempotent-`onSelect`, and no-JS rationale). The `href` is the option URL built from `value.selectedOptions`; spread `register("optionValue", ...)` to enhance the link so a hydrated click selects client-side via the provider's `onSelect`. Keep sold-out-but-existing values interactive and derive their visual treatment from `value.available`. Render non-existent combinations (`exists: false`) as a disabled `<button>` instead of a link.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<Link
|
|
153
|
+
href={variantUrl(product, value.selectedOptions, value.handle, searchParams)}
|
|
154
|
+
replace
|
|
155
|
+
scroll={false}
|
|
156
|
+
aria-current={value.selected ? "true" : undefined}
|
|
157
|
+
data-available={value.available ? "true" : "false"}
|
|
158
|
+
{...register("optionValue", { optionName: option.name, value: value.name })}
|
|
159
|
+
>
|
|
160
|
+
{value.name}
|
|
161
|
+
{!value.available ? <span className="sr-only"> (Sold out)</span> : null}
|
|
162
|
+
</Link>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Cross-product combined-listing values point at a different `value.handle` and navigate to that product. Prefer `next/link`; if using a button for cross-product navigation, keep it clearly outside the add-to-cart form and call `router.replace(...)`. Both use the same URL helper:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { buildProductSelectionSearchParams, type SelectedOption } from "@shopify/hydrogen";
|
|
169
|
+
|
|
170
|
+
function variantUrl(
|
|
171
|
+
product: { handle: string; options: Array<{ name: string }> },
|
|
172
|
+
selectedOptions: SelectedOption[],
|
|
173
|
+
handle = product.handle,
|
|
174
|
+
base: URLSearchParams | ReturnType<typeof useSearchParams> = new URLSearchParams(),
|
|
175
|
+
) {
|
|
176
|
+
const params = buildProductSelectionSearchParams({
|
|
177
|
+
selectedOptions,
|
|
178
|
+
optionNames: product.options.map((option) => option.name),
|
|
179
|
+
base: new URLSearchParams(base),
|
|
180
|
+
});
|
|
181
|
+
const query = params.toString();
|
|
182
|
+
return `/products/${handle}${query ? `?${query}` : ""}`;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Add To Cart
|
|
187
|
+
|
|
188
|
+
Use the local `hydrogen-shop-pay` skill when adding Shop Pay. Use the local `hydrogen-money` skill for prices.
|
|
189
|
+
|
|
190
|
+
Do not put option controls inside the add-to-cart form. The form submits `merchandiseId` and `quantity`; option controls are buttons/links outside it. Register the submit button with `addToCart`.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
function AddToCart({ product }: { product: ProductData }) {
|
|
194
|
+
const { options, register, formProps, pending } = useProductForm();
|
|
195
|
+
const addable = canAddToCart(product, options);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<form {...formProps({ beforeSubmit: openCartDrawer })}>
|
|
199
|
+
<input type="hidden" {...register("merchandiseId", {})} />
|
|
200
|
+
<input {...register("quantity", { value: 1 })} />
|
|
201
|
+
<button {...register("addToCart", {})} disabled={!addable || pending}>
|
|
202
|
+
Add to cart
|
|
203
|
+
</button>
|
|
204
|
+
</form>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Nuxt
|
|
2
|
+
|
|
3
|
+
Use `@shopify/hydrogen/vue` product bindings. Server page data resolves the selected variant from URL query params; client components own option interaction and add-to-cart.
|
|
4
|
+
|
|
5
|
+
## Storefront Module
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// storefront/product.ts
|
|
9
|
+
import { createProductComponents } from "@shopify/hydrogen/vue";
|
|
10
|
+
import type { ProductData } from "./product-types";
|
|
11
|
+
|
|
12
|
+
export const { ProductProvider, useProductForm } = createProductComponents<ProductData>();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`ProductData` is app-owned. It must include Hydrogen's product form fields: `id`, `handle`, `title`, `options`, `selectedOrFirstAvailableVariant`, `adjacentVariants`, `encodedVariantExistence`, `encodedVariantAvailability`, `requiresSellingPlan`, and variant `price`/`availableForSale` fields used by the UI.
|
|
16
|
+
|
|
17
|
+
Wrap this tree in the app's `CartProvider` from `hydrogen-cart-ui`; `ProductProvider` reads the cart store for add-to-cart submission and product-scoped cart errors.
|
|
18
|
+
|
|
19
|
+
## Page
|
|
20
|
+
|
|
21
|
+
In `pages/products/[handle].vue`, pass selected options from `getSelectedProductOptions({searchParams})` into the Storefront API query. Use one reusable variant fragment for `firstSelectableVariant`, `selectedOrFirstAvailableVariant`, and `adjacentVariants`; it must include variant `price`, `availableForSale`, `selectedOptions`, and `product { handle title }` so price display and combined-listing navigation work. Use the injected server/client Storefront client from the Nuxt storefront-client recipe.
|
|
22
|
+
|
|
23
|
+
Wrap the UI:
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<template>
|
|
27
|
+
<ProductProvider
|
|
28
|
+
:product="product"
|
|
29
|
+
:on-select="
|
|
30
|
+
(result) =>
|
|
31
|
+
navigateTo(variantRoute(result.selectedOptions, result.selectedVariant?.product?.handle), {
|
|
32
|
+
replace: true,
|
|
33
|
+
})
|
|
34
|
+
"
|
|
35
|
+
>
|
|
36
|
+
<ProductVariantSelector :product="product" />
|
|
37
|
+
<ProductAddToCart :product="product" />
|
|
38
|
+
</ProductProvider>
|
|
39
|
+
</template>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Variant Selector
|
|
43
|
+
|
|
44
|
+
Same-product option values are GET links (`NuxtLink`) so selection degrades without JavaScript; non-existent combinations render as a disabled `<button>`, and cross-product combined-listing values are also links. The skill's GET-links rule and accessibility guidance cover the `aria-current`, idempotent-`onSelect`, and no-JS rationale:
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<!-- cross-product: navigate to a different product -->
|
|
48
|
+
<NuxtLink
|
|
49
|
+
v-if="value.handle !== product.handle"
|
|
50
|
+
:to="variantRoute(value.selectedOptions, value.handle)"
|
|
51
|
+
replace
|
|
52
|
+
>
|
|
53
|
+
{{ value.name }}
|
|
54
|
+
</NuxtLink>
|
|
55
|
+
|
|
56
|
+
<!-- same-product, non-existent combination: no option URL to degrade to -->
|
|
57
|
+
<button
|
|
58
|
+
v-else-if="!value.exists"
|
|
59
|
+
type="button"
|
|
60
|
+
disabled
|
|
61
|
+
:aria-pressed="value.selected"
|
|
62
|
+
>
|
|
63
|
+
{{ value.name }}
|
|
64
|
+
</button>
|
|
65
|
+
|
|
66
|
+
<!-- same-product, existing value: GET link enhanced by the registered handler -->
|
|
67
|
+
<NuxtLink
|
|
68
|
+
v-else
|
|
69
|
+
:to="variantRoute(value.selectedOptions, value.handle)"
|
|
70
|
+
replace
|
|
71
|
+
:aria-current="value.selected ? 'true' : undefined"
|
|
72
|
+
v-bind="form.register('optionValue', { optionName: option.name, value: value.name })"
|
|
73
|
+
>
|
|
74
|
+
{{ value.name }}
|
|
75
|
+
<template v-if="!value.available"> - Sold out</template>
|
|
76
|
+
</NuxtLink>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Build route query objects with `buildProductSelectionSearchParams`, passing the product option names as `optionNames`. Convert Nuxt's current route query to `URLSearchParams` for `base` by appending every scalar or array value, then convert the returned params back to a Nuxt query object while preserving repeated keys. This preserves non-option params while removing stale option params and the reserved `variant` param.
|
|
80
|
+
|
|
81
|
+
## Add To Cart
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<form v-bind="form.formProps({ beforeSubmit: openCartDrawer })">
|
|
85
|
+
<input type="hidden" v-bind="form.register('merchandiseId', {})" />
|
|
86
|
+
<input v-bind="form.register('quantity', { value: quantity })" />
|
|
87
|
+
<button v-bind="form.register('addToCart', {})" :disabled="!addable || form.pending.value">
|
|
88
|
+
Add to cart
|
|
89
|
+
</button>
|
|
90
|
+
</form>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`addable` must use `canAddToCart(product, form.options)`.
|
|
94
|
+
|
|
95
|
+
Use the local `hydrogen-shop-pay` skill for Shop Pay and `hydrogen-money` for price formatting.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# React Product Bindings
|
|
2
|
+
|
|
3
|
+
Create typed React product bindings once from `@shopify/hydrogen/react`, usually in a shared product module:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createProductComponents } from "@shopify/hydrogen/react";
|
|
7
|
+
import type { ProductData } from "./types";
|
|
8
|
+
|
|
9
|
+
export const { ProductProvider, useProductForm } =
|
|
10
|
+
createProductComponents<ProductData>();
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use the provider's `onSelect` callback for same-product URL sync:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<ProductProvider
|
|
17
|
+
product={product}
|
|
18
|
+
onSelect={(result) => {
|
|
19
|
+
void navigate(
|
|
20
|
+
toRouterLocation(
|
|
21
|
+
variantUrl(product, result.selectedOptions, result.selectedVariant?.product?.handle),
|
|
22
|
+
),
|
|
23
|
+
{
|
|
24
|
+
replace: true,
|
|
25
|
+
preventScrollReset: true,
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<ProductPurchasePanel product={product} />
|
|
31
|
+
</ProductProvider>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If a route should skip loader revalidation for locally resolved selections, use the framework's supported route-level revalidation API. Do not pass unsupported revalidation flags to `navigate()`.
|
|
35
|
+
|
|
36
|
+
Same-product option values are GET links so selection works without JavaScript (the skill's GET-links rule and accessibility guidance cover the `aria-current`, idempotent-`onSelect`, and no-JS rationale). The `to` is the option URL built from `value.selectedOptions`; spreading the registered handlers enhances the link so a hydrated click selects client-side through the provider's `onSelect`. Keep sold-out-but-existing values interactive and derive their visual treatment from `value.available`:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<Link
|
|
40
|
+
to={toRouterLocation(variantUrl(product, value.selectedOptions, value.handle))}
|
|
41
|
+
replace
|
|
42
|
+
preventScrollReset
|
|
43
|
+
aria-current={value.selected ? "true" : undefined}
|
|
44
|
+
data-available={value.available ? "true" : "false"}
|
|
45
|
+
{...register("optionValue", { optionName: option.name, value: value.name })}
|
|
46
|
+
>
|
|
47
|
+
{value.name}
|
|
48
|
+
{!value.available ? <span className="sr-only"> (Sold out)</span> : null}
|
|
49
|
+
</Link>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Non-existent combinations (`exists: false`) render as a disabled `<button>` instead of a `<Link>`:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<button type="button" disabled aria-pressed={value.selected}>
|
|
56
|
+
{value.name}
|
|
57
|
+
</button>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Cross-product option values are framework links that reuse the same URL helper:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<Link
|
|
64
|
+
to={toRouterLocation(variantUrl(product, value.selectedOptions, value.handle))}
|
|
65
|
+
preventScrollReset
|
|
66
|
+
data-available={value.available ? "true" : "false"}
|
|
67
|
+
>
|
|
68
|
+
{value.name}
|
|
69
|
+
{!value.available ? <span className="sr-only"> (Sold out)</span> : null}
|
|
70
|
+
</Link>
|
|
71
|
+
```
|