@shopify/hydrogen 2026.10.0-preview.2 → 2026.10.0-preview.4
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 +34 -3
- package/dist/cli/index.mjs +578 -85
- package/dist/client/client.mjs +2 -2
- package/dist/client/errors.d.mts +19 -19
- package/dist/client/errors.mjs +19 -16
- package/dist/client/errors.mjs.map +1 -1
- package/dist/client/types.d.mts +91 -10
- package/dist/core/analytics/cart-tracker.d.mts +14 -0
- package/dist/core/analytics/cart-tracker.mjs +15 -1
- package/dist/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/core/analytics/custom-consent.mjs +12 -0
- package/dist/core/analytics/custom-consent.mjs.map +1 -0
- package/dist/core/analytics/events.d.mts +7 -0
- package/dist/core/analytics/events.mjs +7 -0
- package/dist/core/analytics/events.mjs.map +1 -1
- package/dist/core/analytics/index.d.mts +1 -1
- package/dist/core/analytics/types.d.mts +152 -16
- package/dist/core/cart/actions.d.mts +47 -3
- package/dist/core/cart/actions.mjs +29 -0
- package/dist/core/cart/actions.mjs.map +1 -1
- package/dist/core/cart/attach-quantity-input.d.mts +26 -0
- package/dist/core/cart/attach-quantity-input.mjs +26 -0
- package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/core/cart/cart.d.mts +106 -6
- package/dist/core/cart/cart.mjs +57 -6
- package/dist/core/cart/cart.mjs.map +1 -1
- package/dist/core/cart/cookie.d.mts +15 -0
- package/dist/core/cart/cookie.mjs +15 -0
- package/dist/core/cart/cookie.mjs.map +1 -1
- package/dist/core/cart/form.d.mts +61 -0
- package/dist/core/cart/form.mjs +18 -0
- package/dist/core/cart/form.mjs.map +1 -1
- package/dist/core/cart/get-cart.d.mts +43 -2
- package/dist/core/cart/get-cart.mjs +33 -0
- package/dist/core/cart/get-cart.mjs.map +1 -1
- package/dist/core/cart/index.d.mts +1 -1
- package/dist/core/cart/quantity.d.mts +17 -0
- package/dist/core/cart/quantity.mjs +17 -0
- package/dist/core/cart/quantity.mjs.map +1 -1
- package/dist/core/cart/queries.d.mts +402 -369
- package/dist/core/cart/queries.mjs +2 -1
- package/dist/core/cart/queries.mjs.map +1 -1
- package/dist/core/cart/server-handlers.d.mts +69 -0
- package/dist/core/cart/server-handlers.mjs +2 -10
- package/dist/core/cart/server-handlers.mjs.map +1 -1
- package/dist/core/cart/state.d.mts +133 -0
- package/dist/core/cart/state.mjs +8 -0
- package/dist/core/cart/state.mjs.map +1 -1
- package/dist/core/collection/collection.d.mts +18 -2
- package/dist/core/collection/collection.mjs +11 -0
- package/dist/core/collection/collection.mjs.map +1 -1
- package/dist/core/collection/index.d.mts +1 -1
- package/dist/core/collection/reconciler.d.mts +13 -4
- package/dist/core/collection/reconciler.mjs.map +1 -1
- package/dist/core/collection/state.d.mts +7 -2
- package/dist/core/collection/state.mjs.map +1 -1
- package/dist/core/collection/url.d.mts +3 -0
- package/dist/core/collection/url.mjs.map +1 -1
- package/dist/core/constants.d.mts +1 -1
- package/dist/core/constants.mjs +2 -2
- package/dist/core/constants.mjs.map +1 -1
- package/dist/core/headers.mjs +2 -2
- package/dist/core/headers.mjs.map +1 -1
- package/dist/core/index.d.mts +5 -5
- package/dist/core/logging/logging.mjs.map +1 -1
- package/dist/core/money/format.mjs +6 -3
- package/dist/core/money/format.mjs.map +1 -1
- package/dist/core/observable.mjs +0 -3
- package/dist/core/observable.mjs.map +1 -1
- package/dist/core/predictive-search/form.d.mts +1 -0
- package/dist/core/predictive-search/form.mjs +1 -0
- package/dist/core/predictive-search/form.mjs.map +1 -1
- package/dist/core/predictive-search/index.d.mts +1 -1
- package/dist/core/predictive-search/queries.d.mts +40 -4
- package/dist/core/predictive-search/queries.mjs +7 -1
- package/dist/core/predictive-search/queries.mjs.map +1 -1
- package/dist/core/predictive-search/search.d.mts +43 -10
- package/dist/core/predictive-search/search.mjs +34 -0
- package/dist/core/predictive-search/search.mjs.map +1 -1
- package/dist/core/predictive-search/server-handlers.d.mts +31 -5
- package/dist/core/predictive-search/server-handlers.mjs +5 -3
- package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/core/predictive-search/store.d.mts +3 -1
- package/dist/core/predictive-search/store.mjs.map +1 -1
- package/dist/core/predictive-search/url.d.mts +50 -8
- package/dist/core/predictive-search/url.mjs +9 -0
- package/dist/core/predictive-search/url.mjs.map +1 -1
- package/dist/core/product/form.d.mts +21 -2
- package/dist/core/product/form.mjs +13 -0
- package/dist/core/product/form.mjs.map +1 -1
- package/dist/core/product/index.d.mts +1 -1
- package/dist/core/product/options.d.mts +2 -2
- package/dist/core/product/options.mjs +2 -2
- package/dist/core/product/options.mjs.map +1 -1
- package/dist/core/product/product-form.d.mts +33 -1
- package/dist/core/product/product-form.mjs +30 -0
- package/dist/core/product/product-form.mjs.map +1 -1
- package/dist/core/product/state.d.mts +41 -0
- package/dist/core/product/url.d.mts +6 -0
- package/dist/core/product/url.mjs.map +1 -1
- package/dist/core/request-context.d.mts +8 -5
- package/dist/core/request-context.mjs +17 -39
- package/dist/core/request-context.mjs.map +1 -1
- package/dist/core/request-routing/handle-shopify-routes.mjs +4 -1
- package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ajax-api.mjs +1 -1
- package/dist/core/request-routing/interceptors/api-proxy.mjs +1 -1
- package/dist/core/request-routing/interceptors/checkout.mjs +22 -6
- package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
- package/dist/core/request-routing/interceptors/proxy.mjs +36 -10
- package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
- package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/core/shopify-scripts/analytics.mjs +2 -2
- package/dist/core/shopify-scripts/analytics.mjs.map +1 -1
- package/dist/core/shopify-scripts/constants.mjs +4 -2
- package/dist/core/shopify-scripts/constants.mjs.map +1 -1
- package/dist/core/shopify-scripts/global.mjs +9 -4
- package/dist/core/shopify-scripts/global.mjs.map +1 -1
- package/dist/core/shopify-scripts/index.d.mts +1 -0
- package/dist/core/shopify-scripts/index.mjs +14 -12
- package/dist/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/core/shopify-scripts/initialize.d.mts +1 -0
- package/dist/core/shopify-scripts/initialize.mjs +10 -3
- package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/core/shopify-scripts/types.d.mts +6 -0
- package/dist/core/url.mjs +21 -2
- package/dist/core/url.mjs.map +1 -1
- package/dist/customer-account/client.d.mts +128 -4
- package/dist/customer-account/client.mjs +27 -1
- package/dist/customer-account/client.mjs.map +1 -1
- package/dist/customer-account/errors.d.mts +46 -0
- package/dist/customer-account/errors.mjs +46 -0
- package/dist/customer-account/errors.mjs.map +1 -1
- package/dist/customer-account/graphql.d.mts +68 -0
- package/dist/customer-account/graphql.mjs +47 -0
- package/dist/customer-account/graphql.mjs.map +1 -1
- package/dist/customer-account/session.d.mts +114 -23
- package/dist/customer-account/session.mjs +27 -12
- package/dist/customer-account/session.mjs.map +1 -1
- package/dist/customer-account/type-resolver.d.mts +23 -0
- package/dist/customer-account-api-types.d.ts +570 -374
- package/dist/customer-account.schema.json +2981 -1527
- package/dist/development/client/client.mjs +2 -2
- package/dist/development/client/errors.d.mts +19 -19
- package/dist/development/client/errors.mjs +20 -20
- package/dist/development/client/errors.mjs.map +1 -1
- package/dist/development/client/types.d.mts +91 -10
- package/dist/development/core/analytics/cart-tracker.d.mts +14 -0
- package/dist/development/core/analytics/cart-tracker.mjs +15 -1
- package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/development/core/analytics/custom-consent.mjs +12 -0
- package/dist/development/core/analytics/custom-consent.mjs.map +1 -0
- package/dist/development/core/analytics/events.d.mts +7 -0
- package/dist/development/core/analytics/events.mjs +7 -0
- package/dist/development/core/analytics/events.mjs.map +1 -1
- package/dist/development/core/analytics/index.d.mts +1 -1
- package/dist/development/core/analytics/types.d.mts +152 -16
- package/dist/development/core/cart/actions.d.mts +47 -3
- package/dist/development/core/cart/actions.mjs +29 -0
- package/dist/development/core/cart/actions.mjs.map +1 -1
- package/dist/development/core/cart/attach-quantity-input.d.mts +26 -0
- package/dist/development/core/cart/attach-quantity-input.mjs +26 -0
- package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/development/core/cart/cart.d.mts +106 -6
- package/dist/development/core/cart/cart.mjs +57 -6
- package/dist/development/core/cart/cart.mjs.map +1 -1
- package/dist/development/core/cart/cookie.d.mts +15 -0
- package/dist/development/core/cart/cookie.mjs +15 -0
- package/dist/development/core/cart/cookie.mjs.map +1 -1
- package/dist/development/core/cart/form.d.mts +61 -0
- package/dist/development/core/cart/form.mjs +18 -0
- package/dist/development/core/cart/form.mjs.map +1 -1
- package/dist/development/core/cart/get-cart.d.mts +43 -2
- package/dist/development/core/cart/get-cart.mjs +33 -0
- package/dist/development/core/cart/get-cart.mjs.map +1 -1
- package/dist/development/core/cart/index.d.mts +1 -1
- package/dist/development/core/cart/quantity.d.mts +17 -0
- package/dist/development/core/cart/quantity.mjs +17 -0
- package/dist/development/core/cart/quantity.mjs.map +1 -1
- package/dist/development/core/cart/queries.d.mts +345 -312
- package/dist/development/core/cart/queries.mjs +2 -1
- package/dist/development/core/cart/queries.mjs.map +1 -1
- package/dist/development/core/cart/server-handlers.d.mts +69 -0
- package/dist/development/core/cart/server-handlers.mjs +2 -10
- package/dist/development/core/cart/server-handlers.mjs.map +1 -1
- package/dist/development/core/cart/state.d.mts +133 -0
- package/dist/development/core/cart/state.mjs +8 -0
- package/dist/development/core/cart/state.mjs.map +1 -1
- package/dist/development/core/collection/collection.d.mts +18 -2
- package/dist/development/core/collection/collection.mjs +11 -0
- package/dist/development/core/collection/collection.mjs.map +1 -1
- package/dist/development/core/collection/index.d.mts +1 -1
- package/dist/development/core/collection/reconciler.d.mts +13 -4
- package/dist/development/core/collection/reconciler.mjs.map +1 -1
- package/dist/development/core/collection/state.d.mts +7 -2
- package/dist/development/core/collection/state.mjs.map +1 -1
- package/dist/development/core/collection/url.d.mts +3 -0
- package/dist/development/core/collection/url.mjs.map +1 -1
- package/dist/development/core/constants.mjs +1 -1
- package/dist/development/core/constants.mjs.map +1 -1
- package/dist/development/core/development.d.mts +5 -5
- package/dist/development/core/headers.mjs +2 -2
- package/dist/development/core/headers.mjs.map +1 -1
- package/dist/development/core/index.d.mts +4 -4
- package/dist/development/core/logging/logging.mjs.map +1 -1
- package/dist/development/core/money/format.mjs +6 -3
- package/dist/development/core/money/format.mjs.map +1 -1
- package/dist/development/core/observable.mjs +0 -3
- package/dist/development/core/observable.mjs.map +1 -1
- package/dist/development/core/predictive-search/form.d.mts +1 -0
- package/dist/development/core/predictive-search/form.mjs +1 -0
- package/dist/development/core/predictive-search/form.mjs.map +1 -1
- package/dist/development/core/predictive-search/index.d.mts +1 -1
- package/dist/development/core/predictive-search/queries.d.mts +38 -2
- package/dist/development/core/predictive-search/queries.mjs +7 -1
- package/dist/development/core/predictive-search/queries.mjs.map +1 -1
- package/dist/development/core/predictive-search/search.d.mts +43 -10
- package/dist/development/core/predictive-search/search.mjs +34 -0
- package/dist/development/core/predictive-search/search.mjs.map +1 -1
- package/dist/development/core/predictive-search/server-handlers.d.mts +31 -5
- package/dist/development/core/predictive-search/server-handlers.mjs +5 -3
- package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/development/core/predictive-search/store.d.mts +3 -1
- package/dist/development/core/predictive-search/store.mjs.map +1 -1
- package/dist/development/core/predictive-search/url.d.mts +50 -8
- package/dist/development/core/predictive-search/url.mjs +9 -0
- package/dist/development/core/predictive-search/url.mjs.map +1 -1
- package/dist/development/core/product/form.d.mts +21 -2
- package/dist/development/core/product/form.mjs +13 -0
- package/dist/development/core/product/form.mjs.map +1 -1
- package/dist/development/core/product/index.d.mts +1 -1
- package/dist/development/core/product/options.d.mts +2 -2
- package/dist/development/core/product/options.mjs +2 -2
- package/dist/development/core/product/options.mjs.map +1 -1
- package/dist/development/core/product/product-form.d.mts +33 -1
- package/dist/development/core/product/product-form.mjs +30 -0
- package/dist/development/core/product/product-form.mjs.map +1 -1
- package/dist/development/core/product/state.d.mts +41 -0
- package/dist/development/core/product/url.d.mts +6 -0
- package/dist/development/core/product/url.mjs.map +1 -1
- package/dist/development/core/request-context.d.mts +8 -5
- package/dist/development/core/request-context.mjs +17 -39
- package/dist/development/core/request-context.mjs.map +1 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs +4 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ajax-api.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/api-proxy.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/checkout.mjs +22 -6
- package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/proxy.mjs +36 -10
- package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
- package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/development/core/shopify-scripts/analytics.mjs +2 -2
- package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/constants.mjs +4 -2
- package/dist/development/core/shopify-scripts/constants.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/global.mjs +9 -4
- package/dist/development/core/shopify-scripts/global.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/index.d.mts +1 -0
- package/dist/development/core/shopify-scripts/index.mjs +14 -12
- package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/initialize.d.mts +1 -0
- package/dist/development/core/shopify-scripts/initialize.mjs +10 -3
- package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/types.d.mts +6 -0
- package/dist/development/core/url.mjs +21 -2
- package/dist/development/core/url.mjs.map +1 -1
- package/dist/development/customer-account/session.d.mts +69 -13
- package/dist/development/customer-account/session.mjs +4 -0
- package/dist/development/customer-account/session.mjs.map +1 -1
- package/dist/development/globals.d.mts +6 -5
- package/dist/development/graphql/generated/graphql-env.d.mts +459 -16
- package/dist/development/graphql/generated/storefront-api-types.d.mts +1 -1
- package/dist/development/graphql/graphql.d.mts +33 -0
- package/dist/development/graphql/graphql.mjs +30 -0
- package/dist/development/graphql/graphql.mjs.map +1 -1
- package/dist/development/graphql/type-resolver.d.mts +14 -0
- package/dist/development/react/cart.d.mts +85 -14
- package/dist/development/react/cart.mjs +123 -3
- package/dist/development/react/cart.mjs.map +1 -1
- package/dist/development/react/collection.d.mts +2 -4
- package/dist/development/react/collection.mjs.map +1 -1
- package/dist/development/react/index.d.mts +8 -6
- package/dist/development/react/index.mjs +2 -2
- package/dist/development/react/predictive-search.d.mts +68 -7
- package/dist/development/react/predictive-search.mjs +54 -12
- package/dist/development/react/predictive-search.mjs.map +1 -1
- package/dist/development/react/product.d.mts +1 -0
- package/dist/development/react/product.mjs +5 -2
- package/dist/development/react/product.mjs.map +1 -1
- package/dist/development/react/shopify-scripts.mjs +1 -0
- package/dist/development/react/shopify-scripts.mjs.map +1 -1
- package/dist/development/vue/cart.d.mts +85 -6
- package/dist/development/vue/cart.mjs +95 -0
- package/dist/development/vue/cart.mjs.map +1 -1
- package/dist/development/vue/collection.d.mts +83 -5
- package/dist/development/vue/collection.mjs +52 -0
- package/dist/development/vue/collection.mjs.map +1 -1
- package/dist/development/vue/index.d.mts +6 -4
- package/dist/development/vue/predictive-search.d.mts +53 -6
- package/dist/development/vue/predictive-search.mjs +33 -0
- package/dist/development/vue/predictive-search.mjs.map +1 -1
- package/dist/development/vue/product.d.mts +6 -2
- package/dist/development/vue/product.mjs.map +1 -1
- package/dist/development/vue/shopify-scripts.d.mts +30 -3
- package/dist/development/vue/shopify-scripts.mjs +6 -0
- package/dist/development/vue/shopify-scripts.mjs.map +1 -1
- package/dist/globals.d.mts +6 -5
- package/dist/graphql/generated/customer-account-graphql-env.d.mts +1717 -1090
- package/dist/graphql/generated/graphql-env.d.mts +459 -16
- package/dist/graphql/generated/storefront-api-types.d.mts +1 -1
- package/dist/graphql/graphql.d.mts +33 -0
- package/dist/graphql/graphql.mjs +30 -0
- package/dist/graphql/graphql.mjs.map +1 -1
- package/dist/graphql/type-resolver.d.mts +14 -0
- package/dist/react/cart.d.mts +85 -14
- package/dist/react/cart.mjs +123 -3
- package/dist/react/cart.mjs.map +1 -1
- package/dist/react/collection.d.mts +2 -4
- package/dist/react/collection.mjs.map +1 -1
- package/dist/react/index.d.mts +8 -6
- package/dist/react/index.mjs +2 -2
- package/dist/react/predictive-search.d.mts +68 -7
- package/dist/react/predictive-search.mjs +54 -12
- package/dist/react/predictive-search.mjs.map +1 -1
- package/dist/react/product.d.mts +1 -0
- package/dist/react/product.mjs +5 -2
- package/dist/react/product.mjs.map +1 -1
- package/dist/react/shopify-scripts.mjs +1 -0
- package/dist/react/shopify-scripts.mjs.map +1 -1
- package/dist/storefront-api-types.d.ts +165 -20
- package/dist/storefront.schema.json +782 -57
- package/dist/vite/customer-account.mjs +191 -0
- package/dist/vite/customer-account.mjs.map +1 -0
- package/dist/vite/local-https.mjs +13 -29
- package/dist/vite/local-https.mjs.map +1 -1
- package/dist/vite/mkcert.mjs +1 -1
- package/dist/vue/cart.d.mts +85 -6
- package/dist/vue/cart.mjs +95 -0
- package/dist/vue/cart.mjs.map +1 -1
- package/dist/vue/collection.d.mts +83 -5
- package/dist/vue/collection.mjs +52 -0
- package/dist/vue/collection.mjs.map +1 -1
- package/dist/vue/index.d.mts +6 -4
- package/dist/vue/predictive-search.d.mts +53 -6
- package/dist/vue/predictive-search.mjs +33 -0
- package/dist/vue/predictive-search.mjs.map +1 -1
- package/dist/vue/product.d.mts +6 -2
- package/dist/vue/product.mjs.map +1 -1
- package/dist/vue/shopify-scripts.d.mts +31 -4
- package/dist/vue/shopify-scripts.mjs +6 -0
- package/dist/vue/shopify-scripts.mjs.map +1 -1
- package/package.json +5 -2
- package/skills/hydrogen-analytics/SKILL.md +6 -5
- package/skills/hydrogen-analytics/references/custom-consent.md +52 -0
- package/skills/hydrogen-analytics/references/react.md +1 -1
- package/skills/hydrogen-cart-metafields/SKILL.md +133 -0
- package/skills/hydrogen-cart-ui/SKILL.md +2 -1
- package/skills/hydrogen-customer-account/SKILL.md +3 -3
- package/skills/hydrogen-local-https/SKILL.md +22 -7
- package/skills/hydrogen-request-handlers/SKILL.md +5 -5
- package/skills/hydrogen-request-handlers/references/nuxt.md +0 -6
- package/skills/hydrogen-setup/references/analytics.md +24 -23
- package/skills/hydrogen-setup/steps/1-inspect-app.md +1 -1
- package/skills/hydrogen-setup/steps/11-verify.md +1 -0
- package/skills/hydrogen-setup/steps/2-scaffold.md +22 -1
- package/skills/hydrogen-smoke-test/SKILL.md +2 -2
- package/skills/hydrogen-storefront-client/SKILL.md +4 -2
- package/skills/hydrogen-variant-form/SKILL.md +10 -4
- package/ts-plugin/package.json +5 -0
- package/dist/core/shopify-scripts/consent.mjs +0 -17
- package/dist/core/shopify-scripts/consent.mjs.map +0 -1
- package/dist/core/shopify-scripts/deprecated-cookies.mjs +0 -97
- package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/tracking-values.mjs +0 -65
- package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/uuid.mjs +0 -17
- package/dist/core/shopify-scripts/utils/uuid.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/consent.mjs +0 -17
- package/dist/development/core/shopify-scripts/consent.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +0 -97
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +0 -65
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/uuid.mjs +0 -17
- package/dist/development/core/shopify-scripts/utils/uuid.mjs.map +0 -1
|
@@ -1,32 +1,57 @@
|
|
|
1
1
|
import { PredictiveSearchFormRegister as PredictiveSearchFormRegister$1 } from "../core/predictive-search/form.mjs";
|
|
2
2
|
import { PredictiveSearchData } from "../core/predictive-search/search.mjs";
|
|
3
|
-
import { CreatePredictiveSearchStoreOptions, PredictiveSearchState, PredictiveSearchStore } from "../core/predictive-search/store.mjs";
|
|
3
|
+
import { CreatePredictiveSearchStoreOptions, PredictiveSearchActions, PredictiveSearchState, PredictiveSearchStore } from "../core/predictive-search/store.mjs";
|
|
4
4
|
import * as _$react from "react";
|
|
5
5
|
import { ChangeEvent, FormHTMLAttributes, InputHTMLAttributes, ReactNode, SubmitEvent } from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/react/predictive-search.d.ts
|
|
8
8
|
type PredictiveSearchContextValue = {
|
|
9
9
|
store: PredictiveSearchStore;
|
|
10
|
+
actions: PredictiveSearchActions;
|
|
10
11
|
searchAction?: string;
|
|
11
12
|
};
|
|
13
|
+
/** Props for the {@link PredictiveSearchProvider} component. */
|
|
12
14
|
type PredictiveSearchProviderProps = CreatePredictiveSearchStoreOptions & {
|
|
13
|
-
children?: ReactNode;
|
|
15
|
+
children?: ReactNode; /** Form action URL for progressive enhancement. Used by {@link usePredictiveSearchForm} to set the form's `action` attribute so the search works without JavaScript. Falls back to `"/search"` when omitted. */
|
|
14
16
|
searchAction?: string;
|
|
15
17
|
};
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Options for the form props builder returned by {@link usePredictiveSearchForm}.
|
|
20
|
+
*
|
|
21
|
+
* Accepts all standard form HTML attributes except `onSubmit`, which is
|
|
22
|
+
* replaced by a version that provides the extracted search term.
|
|
23
|
+
*/
|
|
17
24
|
type PredictiveSearchFormPropsOptions = Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & {
|
|
18
|
-
preventDefault?: boolean;
|
|
25
|
+
/** When `true`, prevents the native form submission and triggers a client-side search instead. */preventDefault?: boolean; /** Called on submit with the submit event and the extracted search term. Calling `event.preventDefault()` cancels both the native submission and Hydrogen's client-side search. */
|
|
19
26
|
onSubmit?: (event: SubmitEvent<HTMLFormElement>, term: string) => void;
|
|
20
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Options for the query input props builder returned by
|
|
30
|
+
* {@link usePredictiveSearchForm}'s `register` method.
|
|
31
|
+
*
|
|
32
|
+
* Accepts all standard input HTML attributes except those controlled by the
|
|
33
|
+
* form registration.
|
|
34
|
+
*/
|
|
21
35
|
type PredictiveSearchQueryInputPropsOptions = Omit<InputHTMLAttributes<HTMLInputElement>, "autoCapitalize" | "autoComplete" | "name" | "onChange" | "spellCheck" | "type"> & {
|
|
22
|
-
onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;
|
|
36
|
+
/** Called on change with the change event and the current input value. Call `event.preventDefault()` to skip the automatic search trigger. */onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;
|
|
23
37
|
};
|
|
24
38
|
type PredictiveSearchFormField = Parameters<PredictiveSearchFormRegister$1>[0];
|
|
39
|
+
/** Generates input element attributes for a named form field. */
|
|
25
40
|
type PredictiveSearchFormRegister = (field: PredictiveSearchFormField, options?: PredictiveSearchQueryInputPropsOptions) => InputHTMLAttributes<HTMLInputElement>;
|
|
41
|
+
/** Return type of {@link usePredictiveSearchForm}, providing methods to build a progressively-enhanced search form. */
|
|
26
42
|
type PredictiveSearchFormResult = {
|
|
27
|
-
formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>;
|
|
43
|
+
/** Generates form element attributes including the search action and submit handler. */formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>; /** Generates input element attributes for a named form field and wires up the search trigger. */
|
|
28
44
|
register: PredictiveSearchFormRegister;
|
|
29
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Creates and manages a predictive search store, providing it to descendant
|
|
48
|
+
* hooks via React context.
|
|
49
|
+
*
|
|
50
|
+
* Recreates the store when configuration props change. Connects the store
|
|
51
|
+
* on mount and destroys it on unmount.
|
|
52
|
+
*
|
|
53
|
+
* @throws {Error} When no `fetch` implementation is available (neither passed as a prop nor available on `globalThis`).
|
|
54
|
+
*/
|
|
30
55
|
declare function PredictiveSearchProvider({
|
|
31
56
|
children,
|
|
32
57
|
predictiveSearchEndpoint,
|
|
@@ -40,10 +65,46 @@ declare function PredictiveSearchProvider({
|
|
|
40
65
|
searchableFields,
|
|
41
66
|
unavailableProducts
|
|
42
67
|
}: PredictiveSearchProviderProps): _$react.FunctionComponentElement<_$react.ProviderProps<PredictiveSearchContextValue | null>>;
|
|
68
|
+
/**
|
|
69
|
+
* Subscribes to the predictive search store's state.
|
|
70
|
+
*
|
|
71
|
+
* Without arguments, returns the full {@link PredictiveSearchState}. With a
|
|
72
|
+
* `selector`, returns a derived value that only triggers re-renders when the
|
|
73
|
+
* selected value changes (reference equality by default, or a custom
|
|
74
|
+
* `isEqual`).
|
|
75
|
+
*
|
|
76
|
+
* Must be used inside a {@link PredictiveSearchProvider}.
|
|
77
|
+
*
|
|
78
|
+
* @throws {Error} When called outside a PredictiveSearchProvider.
|
|
79
|
+
*/
|
|
43
80
|
declare function usePredictiveSearch<TData extends PredictiveSearchData = PredictiveSearchData>(): PredictiveSearchState<TData>;
|
|
44
81
|
declare function usePredictiveSearch<TData extends PredictiveSearchData = PredictiveSearchData, S = PredictiveSearchState<TData>>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): S;
|
|
82
|
+
/**
|
|
83
|
+
* Returns `search` and `clear` methods that always target the provider's
|
|
84
|
+
* current store. Their identity is stable across re-renders, even
|
|
85
|
+
* when a config prop change recreates the store.
|
|
86
|
+
*
|
|
87
|
+
* Must be used inside a {@link PredictiveSearchProvider}.
|
|
88
|
+
*
|
|
89
|
+
* @throws {Error} When called outside a PredictiveSearchProvider.
|
|
90
|
+
*/
|
|
45
91
|
declare function usePredictiveSearchActions(): PredictiveSearchActions;
|
|
92
|
+
/**
|
|
93
|
+
* Returns `formProps` and `register` for building a progressively-enhanced
|
|
94
|
+
* search form.
|
|
95
|
+
*
|
|
96
|
+
* `formProps()` generates form element attributes including the search
|
|
97
|
+
* action URL. `register("query")` generates input attributes and calls the
|
|
98
|
+
* store's search action on every change event. The store debounces requests
|
|
99
|
+
* and resets state for terms shorter than `minTermLength`.
|
|
100
|
+
* Both support an optional callback that receives the event and extracted
|
|
101
|
+
* term, and respect `event.preventDefault()` to cancel the automatic behavior.
|
|
102
|
+
*
|
|
103
|
+
* Must be used inside a {@link PredictiveSearchProvider}.
|
|
104
|
+
*
|
|
105
|
+
* @throws {Error} When called outside a PredictiveSearchProvider.
|
|
106
|
+
*/
|
|
46
107
|
declare function usePredictiveSearchForm(): PredictiveSearchFormResult;
|
|
47
108
|
//#endregion
|
|
48
|
-
export {
|
|
109
|
+
export { PredictiveSearchFormPropsOptions, PredictiveSearchFormRegister, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchProviderProps, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm };
|
|
49
110
|
//# sourceMappingURL=predictive-search.d.mts.map
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import { createPredictiveSearchFormRegister, getPredictiveSearchFormAttributes, readPredictiveSearchFormTerm } from "../core/predictive-search/form.mjs";
|
|
4
4
|
import { createPredictiveSearchStore } from "../core/predictive-search/store.mjs";
|
|
5
|
-
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
5
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useInsertionEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/react/predictive-search.tsx
|
|
8
8
|
const CONFIG_ARRAY_SEPARATOR = "\0";
|
|
9
9
|
const PredictiveSearchContext = createContext(null);
|
|
10
|
+
/**
|
|
11
|
+
* Creates and manages a predictive search store, providing it to descendant
|
|
12
|
+
* hooks via React context.
|
|
13
|
+
*
|
|
14
|
+
* Recreates the store when configuration props change. Connects the store
|
|
15
|
+
* on mount and destroys it on unmount.
|
|
16
|
+
*
|
|
17
|
+
* @throws {Error} When no `fetch` implementation is available (neither passed as a prop nor available on `globalThis`).
|
|
18
|
+
*/
|
|
10
19
|
function PredictiveSearchProvider({ children, predictiveSearchEndpoint, searchAction, debounceInMs, minTermLength, fetch, limit, limitScope, types, searchableFields, unavailableProducts }) {
|
|
11
20
|
const store = useMemo(() => createPredictiveSearchStore({
|
|
12
21
|
predictiveSearchEndpoint,
|
|
@@ -29,14 +38,27 @@ function PredictiveSearchProvider({ children, predictiveSearchEndpoint, searchAc
|
|
|
29
38
|
getArrayKey(searchableFields),
|
|
30
39
|
unavailableProducts
|
|
31
40
|
]);
|
|
41
|
+
const storeRef = useRef(store);
|
|
42
|
+
useInsertionEffect(() => {
|
|
43
|
+
storeRef.current = store;
|
|
44
|
+
}, [store]);
|
|
45
|
+
const actions = useMemo(() => ({
|
|
46
|
+
search: (term) => storeRef.current.search(term),
|
|
47
|
+
clear: () => storeRef.current.clear()
|
|
48
|
+
}), []);
|
|
32
49
|
useEffect(() => {
|
|
33
50
|
store.connect();
|
|
34
51
|
return () => store.destroy();
|
|
35
52
|
}, [store]);
|
|
36
53
|
const contextValue = useMemo(() => ({
|
|
37
54
|
store,
|
|
55
|
+
actions,
|
|
38
56
|
searchAction
|
|
39
|
-
}), [
|
|
57
|
+
}), [
|
|
58
|
+
store,
|
|
59
|
+
actions,
|
|
60
|
+
searchAction
|
|
61
|
+
]);
|
|
40
62
|
return createElement(PredictiveSearchContext.Provider, { value: contextValue }, children);
|
|
41
63
|
}
|
|
42
64
|
function getArrayKey(values) {
|
|
@@ -84,15 +106,35 @@ function usePredictiveSearch(selector, isEqual) {
|
|
|
84
106
|
};
|
|
85
107
|
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
86
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Returns `search` and `clear` methods that always target the provider's
|
|
111
|
+
* current store. Their identity is stable across re-renders, even
|
|
112
|
+
* when a config prop change recreates the store.
|
|
113
|
+
*
|
|
114
|
+
* Must be used inside a {@link PredictiveSearchProvider}.
|
|
115
|
+
*
|
|
116
|
+
* @throws {Error} When called outside a PredictiveSearchProvider.
|
|
117
|
+
*/
|
|
87
118
|
function usePredictiveSearchActions() {
|
|
88
|
-
|
|
89
|
-
return useMemo(() => ({
|
|
90
|
-
search: store.search,
|
|
91
|
-
clear: store.clear
|
|
92
|
-
}), [store]);
|
|
119
|
+
return useRequiredContext("usePredictiveSearchActions").actions;
|
|
93
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns `formProps` and `register` for building a progressively-enhanced
|
|
123
|
+
* search form.
|
|
124
|
+
*
|
|
125
|
+
* `formProps()` generates form element attributes including the search
|
|
126
|
+
* action URL. `register("query")` generates input attributes and calls the
|
|
127
|
+
* store's search action on every change event. The store debounces requests
|
|
128
|
+
* and resets state for terms shorter than `minTermLength`.
|
|
129
|
+
* Both support an optional callback that receives the event and extracted
|
|
130
|
+
* term, and respect `event.preventDefault()` to cancel the automatic behavior.
|
|
131
|
+
*
|
|
132
|
+
* Must be used inside a {@link PredictiveSearchProvider}.
|
|
133
|
+
*
|
|
134
|
+
* @throws {Error} When called outside a PredictiveSearchProvider.
|
|
135
|
+
*/
|
|
94
136
|
function usePredictiveSearchForm() {
|
|
95
|
-
const { searchAction,
|
|
137
|
+
const { searchAction, actions } = useRequiredContext("usePredictiveSearchForm");
|
|
96
138
|
const coreRegister = useMemo(() => createPredictiveSearchFormRegister(), []);
|
|
97
139
|
const register = useCallback((field, props = {}) => {
|
|
98
140
|
const { onChange, ...attributes } = props;
|
|
@@ -104,10 +146,10 @@ function usePredictiveSearchForm() {
|
|
|
104
146
|
const term = event.currentTarget.value;
|
|
105
147
|
onChange?.(event, term);
|
|
106
148
|
if (event.defaultPrevented) return;
|
|
107
|
-
|
|
149
|
+
actions.search(term);
|
|
108
150
|
}
|
|
109
151
|
};
|
|
110
|
-
}, [coreRegister,
|
|
152
|
+
}, [coreRegister, actions]);
|
|
111
153
|
return {
|
|
112
154
|
formProps: useCallback((props = {}) => {
|
|
113
155
|
const { onSubmit, preventDefault, ...attributes } = props;
|
|
@@ -120,10 +162,10 @@ function usePredictiveSearchForm() {
|
|
|
120
162
|
if (event.defaultPrevented) return;
|
|
121
163
|
if (!preventDefault) return;
|
|
122
164
|
event.preventDefault();
|
|
123
|
-
|
|
165
|
+
actions.search(term);
|
|
124
166
|
}
|
|
125
167
|
};
|
|
126
|
-
}, [searchAction,
|
|
168
|
+
}, [searchAction, actions]),
|
|
127
169
|
register
|
|
128
170
|
};
|
|
129
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"predictive-search.mjs","names":[],"sources":["../../../src/react/predictive-search.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type ChangeEvent,\n type FormHTMLAttributes,\n type InputHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport {\n createPredictiveSearchFormRegister,\n createPredictiveSearchStore,\n getPredictiveSearchFormAttributes,\n readPredictiveSearchFormTerm,\n type CreatePredictiveSearchStoreOptions,\n type PredictiveSearchFormRegister as CorePredictiveSearchFormRegister,\n type PredictiveSearchData,\n type PredictiveSearchState,\n type PredictiveSearchStore,\n} from \"../core/predictive-search\";\n\nconst CONFIG_ARRAY_SEPARATOR = \"\\u0000\";\n\ntype PredictiveSearchContextValue = {\n store: PredictiveSearchStore;\n searchAction?: string;\n};\n\nconst PredictiveSearchContext = createContext<PredictiveSearchContextValue | null>(null);\n\nexport type PredictiveSearchProviderProps = CreatePredictiveSearchStoreOptions & {\n children?: ReactNode;\n searchAction?: string;\n};\n\nexport type PredictiveSearchActions = Pick<PredictiveSearchStore, \"search\" | \"clear\">;\n\nexport type PredictiveSearchFormPropsOptions = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"onSubmit\"\n> & {\n preventDefault?: boolean;\n onSubmit?: (event: SubmitEvent<HTMLFormElement>, term: string) => void;\n};\n\nexport type PredictiveSearchQueryInputPropsOptions = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"autoCapitalize\" | \"autoComplete\" | \"name\" | \"onChange\" | \"spellCheck\" | \"type\"\n> & {\n onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;\n};\n\ntype PredictiveSearchFormField = Parameters<CorePredictiveSearchFormRegister>[0];\n\nexport type PredictiveSearchFormRegister = (\n field: PredictiveSearchFormField,\n options?: PredictiveSearchQueryInputPropsOptions,\n) => InputHTMLAttributes<HTMLInputElement>;\n\nexport type PredictiveSearchFormResult = {\n formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>;\n register: PredictiveSearchFormRegister;\n};\n\nexport function PredictiveSearchProvider({\n children,\n predictiveSearchEndpoint,\n searchAction,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n}: PredictiveSearchProviderProps) {\n const typesKey = getArrayKey(types);\n const searchableFieldsKey = getArrayKey(searchableFields);\n\n const store = useMemo(\n () =>\n createPredictiveSearchStore({\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n }),\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- array keys are the semantic dependencies\n [\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n typesKey,\n searchableFieldsKey,\n unavailableProducts,\n ],\n );\n\n useEffect(() => {\n store.connect();\n return () => store.destroy();\n }, [store]);\n\n const contextValue = useMemo(() => ({ store, searchAction }), [store, searchAction]);\n\n return createElement(PredictiveSearchContext.Provider, { value: contextValue }, children);\n}\n\nfunction getArrayKey(values: readonly string[] | undefined): string {\n return values?.join(CONFIG_ARRAY_SEPARATOR) ?? \"\";\n}\n\nfunction useRequiredStore<TData extends PredictiveSearchData>(\n hookName: string,\n): PredictiveSearchStore<TData> {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n // oxlint-disable-next-line @typescript-eslint/consistent-type-assertions -- context stores the runtime object while hooks preserve caller-provided result typing\n return context.store as PredictiveSearchStore<TData>;\n}\n\nfunction useRequiredContext(hookName: string): PredictiveSearchContextValue {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n return context;\n}\n\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(): PredictiveSearchState<TData>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): S;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(\n selector?: (state: PredictiveSearchState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): PredictiveSearchState<TData> | S {\n const store = useRequiredStore<TData>(\"usePredictiveSearch\");\n const cachedRef = useRef<{\n state: unknown;\n selector: typeof selector;\n value: PredictiveSearchState<TData> | S;\n } | null>(null);\n\n const getSnapshot = () => {\n const state = store.getState();\n\n if (\n cachedRef.current &&\n cachedRef.current.state === state &&\n cachedRef.current.selector === selector\n ) {\n return cachedRef.current.value;\n }\n\n if (!selector) {\n cachedRef.current = { state, selector, value: state };\n return state;\n }\n\n const next = selector(state);\n if (cachedRef.current && isEqual?.(cachedRef.current.value as S, next)) {\n cachedRef.current = { state, selector, value: cachedRef.current.value };\n return cachedRef.current.value;\n }\n\n cachedRef.current = { state, selector, value: next };\n return next;\n };\n\n return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\nexport function usePredictiveSearchActions(): PredictiveSearchActions {\n const store = useRequiredStore(\"usePredictiveSearchActions\");\n\n return useMemo(\n () => ({\n search: store.search,\n clear: store.clear,\n }),\n [store],\n );\n}\n\nexport function usePredictiveSearchForm(): PredictiveSearchFormResult {\n const { searchAction, store } = useRequiredContext(\"usePredictiveSearchForm\");\n const coreRegister = useMemo(() => createPredictiveSearchFormRegister(), []);\n\n const register = useCallback<PredictiveSearchFormRegister>(\n (field, props = {}) => {\n const { onChange, ...attributes } = props;\n const coreAttributes = coreRegister(field);\n\n return {\n ...attributes,\n ...coreAttributes,\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n const term = event.currentTarget.value;\n onChange?.(event, term);\n if (event.defaultPrevented) return;\n void store.search(term);\n },\n };\n },\n [coreRegister, store],\n );\n\n const formProps = useCallback(\n (props: PredictiveSearchFormPropsOptions = {}): FormHTMLAttributes<HTMLFormElement> => {\n const { onSubmit, preventDefault, ...attributes } = props;\n\n return {\n ...getPredictiveSearchFormAttributes(searchAction),\n ...attributes,\n onSubmit: (event: SubmitEvent<HTMLFormElement>) => {\n const term = readPredictiveSearchFormTerm(new FormData(event.currentTarget));\n onSubmit?.(event, term);\n if (event.defaultPrevented) return;\n if (!preventDefault) return;\n event.preventDefault();\n void store.search(term);\n },\n };\n },\n [searchAction, store],\n );\n\n return { formProps, register };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"predictive-search.mjs","names":[],"sources":["../../../src/react/predictive-search.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type ChangeEvent,\n type FormHTMLAttributes,\n type InputHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport {\n createPredictiveSearchFormRegister,\n createPredictiveSearchStore,\n getPredictiveSearchFormAttributes,\n readPredictiveSearchFormTerm,\n type CreatePredictiveSearchStoreOptions,\n type PredictiveSearchActions,\n type PredictiveSearchFormRegister as CorePredictiveSearchFormRegister,\n type PredictiveSearchData,\n type PredictiveSearchState,\n type PredictiveSearchStore,\n} from \"../core/predictive-search\";\n\nconst CONFIG_ARRAY_SEPARATOR = \"\\u0000\";\n\ntype PredictiveSearchContextValue = {\n store: PredictiveSearchStore;\n actions: PredictiveSearchActions;\n searchAction?: string;\n};\n\nconst PredictiveSearchContext = createContext<PredictiveSearchContextValue | null>(null);\n\n/** Props for the {@link PredictiveSearchProvider} component. */\nexport type PredictiveSearchProviderProps = CreatePredictiveSearchStoreOptions & {\n children?: ReactNode;\n /** Form action URL for progressive enhancement. Used by {@link usePredictiveSearchForm} to set the form's `action` attribute so the search works without JavaScript. Falls back to `\"/search\"` when omitted. */\n searchAction?: string;\n};\n\nexport type { PredictiveSearchActions };\n\n/**\n * Options for the form props builder returned by {@link usePredictiveSearchForm}.\n *\n * Accepts all standard form HTML attributes except `onSubmit`, which is\n * replaced by a version that provides the extracted search term.\n */\nexport type PredictiveSearchFormPropsOptions = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"onSubmit\"\n> & {\n /** When `true`, prevents the native form submission and triggers a client-side search instead. */\n preventDefault?: boolean;\n /** Called on submit with the submit event and the extracted search term. Calling `event.preventDefault()` cancels both the native submission and Hydrogen's client-side search. */\n onSubmit?: (event: SubmitEvent<HTMLFormElement>, term: string) => void;\n};\n\n/**\n * Options for the query input props builder returned by\n * {@link usePredictiveSearchForm}'s `register` method.\n *\n * Accepts all standard input HTML attributes except those controlled by the\n * form registration.\n */\nexport type PredictiveSearchQueryInputPropsOptions = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"autoCapitalize\" | \"autoComplete\" | \"name\" | \"onChange\" | \"spellCheck\" | \"type\"\n> & {\n /** Called on change with the change event and the current input value. Call `event.preventDefault()` to skip the automatic search trigger. */\n onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;\n};\n\ntype PredictiveSearchFormField = Parameters<CorePredictiveSearchFormRegister>[0];\n\n/** Generates input element attributes for a named form field. */\nexport type PredictiveSearchFormRegister = (\n field: PredictiveSearchFormField,\n options?: PredictiveSearchQueryInputPropsOptions,\n) => InputHTMLAttributes<HTMLInputElement>;\n\n/** Return type of {@link usePredictiveSearchForm}, providing methods to build a progressively-enhanced search form. */\nexport type PredictiveSearchFormResult = {\n /** Generates form element attributes including the search action and submit handler. */\n formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>;\n /** Generates input element attributes for a named form field and wires up the search trigger. */\n register: PredictiveSearchFormRegister;\n};\n\n/**\n * Creates and manages a predictive search store, providing it to descendant\n * hooks via React context.\n *\n * Recreates the store when configuration props change. Connects the store\n * on mount and destroys it on unmount.\n *\n * @throws {Error} When no `fetch` implementation is available (neither passed as a prop nor available on `globalThis`).\n */\nexport function PredictiveSearchProvider({\n children,\n predictiveSearchEndpoint,\n searchAction,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n}: PredictiveSearchProviderProps) {\n const typesKey = getArrayKey(types);\n const searchableFieldsKey = getArrayKey(searchableFields);\n\n const store = useMemo(\n () =>\n createPredictiveSearchStore({\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n }),\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- array keys are the semantic dependencies\n [\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n typesKey,\n searchableFieldsKey,\n unavailableProducts,\n ],\n );\n\n const storeRef = useRef(store);\n // Actions outlive store swaps by reading the committed store at call time. Only an insertion\n // effect is both commit-only and early enough: a render-phase write can point actions at a\n // store from a render React never commits, and layout and passive effects run child-first,\n // so a child effect searching in the same commit would still see the old store.\n useInsertionEffect(() => {\n storeRef.current = store;\n }, [store]);\n const actions = useMemo<PredictiveSearchActions>(\n () => ({\n search: (term) => storeRef.current.search(term),\n clear: () => storeRef.current.clear(),\n }),\n [],\n );\n\n useEffect(() => {\n store.connect();\n return () => store.destroy();\n }, [store]);\n\n const contextValue = useMemo(\n () => ({ store, actions, searchAction }),\n [store, actions, searchAction],\n );\n\n return createElement(PredictiveSearchContext.Provider, { value: contextValue }, children);\n}\n\nfunction getArrayKey(values: readonly string[] | undefined): string {\n return values?.join(CONFIG_ARRAY_SEPARATOR) ?? \"\";\n}\n\nfunction useRequiredStore<TData extends PredictiveSearchData>(\n hookName: string,\n): PredictiveSearchStore<TData> {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n // oxlint-disable-next-line @typescript-eslint/consistent-type-assertions -- context stores the runtime object while hooks preserve caller-provided result typing\n return context.store as PredictiveSearchStore<TData>;\n}\n\nfunction useRequiredContext(hookName: string): PredictiveSearchContextValue {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n return context;\n}\n\n/**\n * Subscribes to the predictive search store's state.\n *\n * Without arguments, returns the full {@link PredictiveSearchState}. With a\n * `selector`, returns a derived value that only triggers re-renders when the\n * selected value changes (reference equality by default, or a custom\n * `isEqual`).\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(): PredictiveSearchState<TData>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): S;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(\n selector?: (state: PredictiveSearchState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): PredictiveSearchState<TData> | S {\n const store = useRequiredStore<TData>(\"usePredictiveSearch\");\n const cachedRef = useRef<{\n state: unknown;\n selector: typeof selector;\n value: PredictiveSearchState<TData> | S;\n } | null>(null);\n\n const getSnapshot = () => {\n const state = store.getState();\n\n if (\n cachedRef.current &&\n cachedRef.current.state === state &&\n cachedRef.current.selector === selector\n ) {\n return cachedRef.current.value;\n }\n\n if (!selector) {\n cachedRef.current = { state, selector, value: state };\n return state;\n }\n\n const next = selector(state);\n if (cachedRef.current && isEqual?.(cachedRef.current.value as S, next)) {\n cachedRef.current = { state, selector, value: cachedRef.current.value };\n return cachedRef.current.value;\n }\n\n cachedRef.current = { state, selector, value: next };\n return next;\n };\n\n return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Returns `search` and `clear` methods that always target the provider's\n * current store. Their identity is stable across re-renders, even\n * when a config prop change recreates the store.\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearchActions(): PredictiveSearchActions {\n return useRequiredContext(\"usePredictiveSearchActions\").actions;\n}\n\n/**\n * Returns `formProps` and `register` for building a progressively-enhanced\n * search form.\n *\n * `formProps()` generates form element attributes including the search\n * action URL. `register(\"query\")` generates input attributes and calls the\n * store's search action on every change event. The store debounces requests\n * and resets state for terms shorter than `minTermLength`.\n * Both support an optional callback that receives the event and extracted\n * term, and respect `event.preventDefault()` to cancel the automatic behavior.\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearchForm(): PredictiveSearchFormResult {\n const { searchAction, actions } = useRequiredContext(\"usePredictiveSearchForm\");\n const coreRegister = useMemo(() => createPredictiveSearchFormRegister(), []);\n\n const register = useCallback<PredictiveSearchFormRegister>(\n (field, props = {}) => {\n const { onChange, ...attributes } = props;\n const coreAttributes = coreRegister(field);\n\n return {\n ...attributes,\n ...coreAttributes,\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n const term = event.currentTarget.value;\n onChange?.(event, term);\n if (event.defaultPrevented) return;\n void actions.search(term);\n },\n };\n },\n [coreRegister, actions],\n );\n\n const formProps = useCallback(\n (props: PredictiveSearchFormPropsOptions = {}): FormHTMLAttributes<HTMLFormElement> => {\n const { onSubmit, preventDefault, ...attributes } = props;\n\n return {\n ...getPredictiveSearchFormAttributes(searchAction),\n ...attributes,\n onSubmit: (event: SubmitEvent<HTMLFormElement>) => {\n const term = readPredictiveSearchFormTerm(new FormData(event.currentTarget));\n onSubmit?.(event, term);\n if (event.defaultPrevented) return;\n if (!preventDefault) return;\n event.preventDefault();\n void actions.search(term);\n },\n };\n },\n [searchAction, actions],\n );\n\n return { formProps, register };\n}\n"],"mappings":"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"}
|
|
@@ -20,6 +20,7 @@ interface UseProductFormResult<TProduct extends ProductInput> {
|
|
|
20
20
|
}) => FormHTMLAttributes<HTMLFormElement>;
|
|
21
21
|
errors: ProductFormErrors;
|
|
22
22
|
matchedLineItem: CartLine | null;
|
|
23
|
+
/** Whether a form submission is currently in flight. */
|
|
23
24
|
pending: boolean;
|
|
24
25
|
selectOption: (name: string, value: string) => VariantSelectionResult<ProductVariantFrom<TProduct>>;
|
|
25
26
|
}
|
|
@@ -75,9 +75,12 @@ function createProductComponents() {
|
|
|
75
75
|
return ctx;
|
|
76
76
|
}
|
|
77
77
|
function ProductProvider({ product, onSelect, children }) {
|
|
78
|
-
const cartStore = useCartStore();
|
|
78
|
+
const cartStore = useCartStore("ProductProvider");
|
|
79
79
|
const store = useMemo(() => createProductFormStore(product, cartStore), []);
|
|
80
|
-
useEffect(() =>
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
store.connect();
|
|
82
|
+
return () => store.destroy();
|
|
83
|
+
}, [store]);
|
|
81
84
|
const productKey = `${product.id}:${product.selectedOrFirstAvailableVariant?.id ?? ""}`;
|
|
82
85
|
const mountedRef = useRef(false);
|
|
83
86
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product.mjs","names":[],"sources":["../../../src/react/product.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport type { CartLine } from \"../core/cart/state\";\nimport { getLogger } from \"../core/logging\";\nimport {\n createProductFormRegister,\n createProductFormStore,\n type ProductFormErrors,\n type ProductFormOptions,\n type ProductFormRegister,\n type ProductFormStore,\n type ProductInput,\n type ProductVariantFrom,\n type ValidProductSelectionResult,\n type VariantSelectionResult,\n} from \"../core/product\";\nimport { getCartEndpoint, useCartStore } from \"./cart\";\n\nconst log = getLogger(\"product\");\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport type { ValidProductSelectionResult } from \"../core/product\";\n\n/** Options for {@link useProductForm}. */\nexport interface UseProductFormOptions<TProduct extends ProductInput> {\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n}\n\n/** Return value of {@link useProductForm}. */\nexport interface UseProductFormResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n register: ProductFormRegister;\n formProps: (opts?: {\n beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }) => FormHTMLAttributes<HTMLFormElement>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n pending: boolean;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\n/** Props for the `ProductProvider` returned by {@link createProductComponents}. */\nexport interface ProductProviderProps<TProduct extends ProductInput> {\n product: TProduct;\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n children?: ReactNode;\n}\n\n/** Return value of the `useProduct` hook from {@link createProductComponents}. */\nexport interface UseProductResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Standalone hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribes to a {@link ProductFormStore} and returns form-ready state.\n *\n * This is a pure subscription hook — it does **not** manage store lifecycle.\n * Create the store with `createProductFormStore` and manage its lifecycle\n * (hydration, destruction) yourself, or use `createProductComponents` for a\n * provider-based approach.\n */\nexport function useProductForm<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n options?: UseProductFormOptions<TProduct>,\n): UseProductFormResult<TProduct> {\n const onSelectRef = useRef(options?.onSelect);\n onSelectRef.current = options?.onSelect;\n\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n const [pending, setPending] = useState(false);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store],\n );\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- state is the full reactive snapshot; selectOption is stable\n const register = useMemo(\n () => createProductFormRegister(state.selectedVariant, selectOption),\n [state, selectOption],\n );\n\n const formProps = useCallback(\n (opts?: {\n beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }): FormHTMLAttributes<HTMLFormElement> => ({\n onSubmit: (e: SubmitEvent<HTMLFormElement>) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n setPending(true);\n store\n .handleFormSubmit(e.nativeEvent)\n .then(\n () => opts?.afterSubmit?.(e),\n (error: unknown) => {\n // Cart user/network errors are surfaced via the reactive `errors`\n // state. Thrown exceptions (missing Shopify script, invalid form\n // target) are programming errors — log them for dev visibility.\n log.error(\"form submission error\", { error });\n },\n )\n .finally(() => setPending(false));\n },\n method: \"post\",\n action: getCartEndpoint(),\n }),\n [store],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n register,\n formProps,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n pending,\n selectOption,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\ninterface ProductContextValue<TProduct extends ProductInput> {\n store: ProductFormStore<TProduct>;\n onSelectRef: React.RefObject<\n ((result: ValidProductSelectionResult<TProduct>) => void) | undefined\n >;\n}\n\n/**\n * Creates a typed set of product components bound to a specific product type.\n *\n * Returns `{ ProductProvider, useProduct, useProductForm }` where:\n * - `ProductProvider` manages store lifecycle (creation, hydration, cleanup)\n * - `useProduct` provides read-only state and variant selection\n * - `useProductForm` provides form-binding utilities (register, formProps, pending)\n *\n * Requires a `<CartProvider>` ancestor.\n *\n * @example\n * ```ts\n * const { ProductProvider, useProduct, useProductForm } =\n * createProductComponents<MyProductType>();\n * ```\n */\nexport function createProductComponents<TProduct extends ProductInput>(): {\n ProductProvider: (props: ProductProviderProps<TProduct>) => ReactNode;\n useProduct: () => UseProductResult<TProduct>;\n useProductForm: () => UseProductFormResult<TProduct>;\n} {\n const Context = createContext<ProductContextValue<TProduct> | null>(null);\n\n function useProductContext(hookName: string): ProductContextValue<TProduct> {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `${hookName} must be used inside a <ProductProvider>. ` +\n \"Wrap your component tree with the ProductProvider from createProductComponents(), \" +\n \"or use the standalone useProductForm(store) hook instead.\",\n );\n }\n return ctx;\n }\n\n function ProductProvider({ product, onSelect, children }: ProductProviderProps<TProduct>) {\n const cartStore = useCartStore();\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store is intentionally created once\n const store = useMemo(() => createProductFormStore<TProduct>(product, cartStore), []);\n\n useEffect(() => () => store.destroy(), [store]);\n\n const productKey = `${product.id}:${product.selectedOrFirstAvailableVariant?.id ?? \"\"}`;\n const mountedRef = useRef(false);\n useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n store.hydrate(product);\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- productKey is the semantic dep\n }, [productKey]);\n\n const onSelectRef = useRef(onSelect);\n onSelectRef.current = onSelect;\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- onSelectRef is a stable ref object\n const value = useMemo<ProductContextValue<TProduct>>(() => ({ store, onSelectRef }), [store]);\n\n return createElement(Context.Provider, { value }, children);\n }\n\n function useProductHook(): UseProductResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProduct\");\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store, onSelectRef],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n selectOption,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n };\n }\n\n function useProductFormHook(): UseProductFormResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProductForm\");\n return useProductForm<TProduct>(store, { onSelect: onSelectRef.current });\n }\n\n return {\n ProductProvider,\n useProduct: useProductHook,\n useProductForm: useProductFormHook,\n };\n}\n"],"mappings":";;;;;;;;;AAiCA,MAAM,MAAM,UAAU,UAAU;;;;;;;;;AAkEhC,SAAgB,eACd,OACA,SACgC;CAChC,MAAM,cAAc,OAAO,SAAS,SAAS;AAC7C,aAAY,UAAU,SAAS;CAE/B,MAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,UAAU,MAAM,SAAS;CACnF,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAE7C,MAAM,eAAe,aAClB,MAAc,UAAkB;EAC/B,MAAM,SAAS,MAAM,aAAa,MAAM,MAAM;AAC9C,MAAI,OAAO,WAAW,UACpB,aAAY,UAAU,OAAO;AAE/B,SAAO;IAET,CAAC,MAAM,CACR;CAGD,MAAM,WAAW,cACT,0BAA0B,MAAM,iBAAiB,aAAa,EACpE,CAAC,OAAO,aAAa,CACtB;CAED,MAAM,YAAY,aACf,UAG2C;EAC1C,WAAW,MAAoC;AAC7C,SAAM,eAAe,EAAE;AACvB,OAAI,EAAE,iBAAkB;AACxB,KAAE,gBAAgB;AAClB,cAAW,KAAK;AAChB,SACG,iBAAiB,EAAE,YAAY,CAC/B,WACO,MAAM,cAAc,EAAE,GAC3B,UAAmB;AAIlB,QAAI,MAAM,yBAAyB,EAAE,OAAO,CAAC;KAEhD,CACA,cAAc,WAAW,MAAM,CAAC;;EAErC,QAAQ;EACR,QAAQ,iBAAiB;EAC1B,GACD,CAAC,MAAM,CACR;AAED,QAAO;EACL,SAAS,MAAM;EACf,iBAAiB,MAAM;EACvB;EACA;EACA,QAAQ,MAAM;EACd,iBAAiB,MAAM;EACvB;EACA;EACD;;;;;;;;;;;;;;;;;;AA8BH,SAAgB,0BAId;CACA,MAAM,UAAU,cAAoD,KAAK;CAEzE,SAAS,kBAAkB,UAAiD;EAC1E,MAAM,MAAM,WAAW,QAAQ;AAC/B,MAAI,CAAC,IACH,OAAM,IAAI,MACR,GAAG,SAAS,uLAGb;AAEH,SAAO;;CAGT,SAAS,gBAAgB,EAAE,SAAS,UAAU,YAA4C;EACxF,MAAM,YAAY,cAAc;EAGhC,MAAM,QAAQ,cAAc,uBAAiC,SAAS,UAAU,EAAE,EAAE,CAAC;AAErF,wBAAsB,MAAM,SAAS,EAAE,CAAC,MAAM,CAAC;EAE/C,MAAM,aAAa,GAAG,QAAQ,GAAG,GAAG,QAAQ,iCAAiC,MAAM;EACnF,MAAM,aAAa,OAAO,MAAM;AAChC,kBAAgB;AACd,OAAI,CAAC,WAAW,SAAS;AACvB,eAAW,UAAU;AACrB;;AAEF,SAAM,QAAQ,QAAQ;KAErB,CAAC,WAAW,CAAC;EAEhB,MAAM,cAAc,OAAO,SAAS;AACpC,cAAY,UAAU;EAGtB,MAAM,QAAQ,eAA8C;GAAE;GAAO;GAAa,GAAG,CAAC,MAAM,CAAC;AAE7F,SAAO,cAAc,QAAQ,UAAU,EAAE,OAAO,EAAE,SAAS;;CAG7D,SAAS,iBAA6C;EACpD,MAAM,EAAE,OAAO,gBAAgB,kBAAkB,aAAa;EAC9D,MAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,UAAU,MAAM,SAAS;EAEnF,MAAM,eAAe,aAClB,MAAc,UAAkB;GAC/B,MAAM,SAAS,MAAM,aAAa,MAAM,MAAM;AAC9C,OAAI,OAAO,WAAW,UACpB,aAAY,UAAU,OAAO;AAE/B,UAAO;KAET,CAAC,OAAO,YAAY,CACrB;AAED,SAAO;GACL,SAAS,MAAM;GACf,iBAAiB,MAAM;GACvB;GACA,QAAQ,MAAM;GACd,iBAAiB,MAAM;GACxB;;CAGH,SAAS,qBAAqD;EAC5D,MAAM,EAAE,OAAO,gBAAgB,kBAAkB,iBAAiB;AAClE,SAAO,eAAyB,OAAO,EAAE,UAAU,YAAY,SAAS,CAAC;;AAG3E,QAAO;EACL;EACA,YAAY;EACZ,gBAAgB;EACjB"}
|
|
1
|
+
{"version":3,"file":"product.mjs","names":[],"sources":["../../../src/react/product.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport type { CartLine } from \"../core/cart/state\";\nimport { getLogger } from \"../core/logging\";\nimport {\n createProductFormRegister,\n createProductFormStore,\n type ProductFormErrors,\n type ProductFormOptions,\n type ProductFormRegister,\n type ProductFormStore,\n type ProductInput,\n type ProductVariantFrom,\n type ValidProductSelectionResult,\n type VariantSelectionResult,\n} from \"../core/product\";\nimport { getCartEndpoint, useCartStore } from \"./cart\";\n\nconst log = getLogger(\"product\");\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport type { ValidProductSelectionResult } from \"../core/product\";\n\n/** Options for {@link useProductForm}. */\nexport interface UseProductFormOptions<TProduct extends ProductInput> {\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n}\n\n/** Return value of {@link useProductForm}. */\nexport interface UseProductFormResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n register: ProductFormRegister;\n formProps: (opts?: {\n beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }) => FormHTMLAttributes<HTMLFormElement>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n /** Whether a form submission is currently in flight. */\n pending: boolean;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\n/** Props for the `ProductProvider` returned by {@link createProductComponents}. */\nexport interface ProductProviderProps<TProduct extends ProductInput> {\n product: TProduct;\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n children?: ReactNode;\n}\n\n/** Return value of the `useProduct` hook from {@link createProductComponents}. */\nexport interface UseProductResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Standalone hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribes to a {@link ProductFormStore} and returns form-ready state.\n *\n * This is a pure subscription hook — it does **not** manage store lifecycle.\n * Create the store with `createProductFormStore` and manage its lifecycle\n * (hydration, destruction) yourself, or use `createProductComponents` for a\n * provider-based approach.\n */\nexport function useProductForm<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n options?: UseProductFormOptions<TProduct>,\n): UseProductFormResult<TProduct> {\n const onSelectRef = useRef(options?.onSelect);\n onSelectRef.current = options?.onSelect;\n\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n const [pending, setPending] = useState(false);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store],\n );\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- state is the full reactive snapshot; selectOption is stable\n const register = useMemo(\n () => createProductFormRegister(state.selectedVariant, selectOption),\n [state, selectOption],\n );\n\n const formProps = useCallback(\n (opts?: {\n beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }): FormHTMLAttributes<HTMLFormElement> => ({\n onSubmit: (e: SubmitEvent<HTMLFormElement>) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n setPending(true);\n store\n .handleFormSubmit(e.nativeEvent)\n .then(\n () => opts?.afterSubmit?.(e),\n (error: unknown) => {\n // Cart user/network errors are surfaced via the reactive `errors`\n // state. Thrown exceptions (missing Shopify script, invalid form\n // target) are programming errors — log them for dev visibility.\n log.error(\"form submission error\", { error });\n },\n )\n .finally(() => setPending(false));\n },\n method: \"post\",\n action: getCartEndpoint(),\n }),\n [store],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n register,\n formProps,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n pending,\n selectOption,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\ninterface ProductContextValue<TProduct extends ProductInput> {\n store: ProductFormStore<TProduct>;\n onSelectRef: React.RefObject<\n ((result: ValidProductSelectionResult<TProduct>) => void) | undefined\n >;\n}\n\n/**\n * Creates a typed set of product components bound to a specific product type.\n *\n * Returns `{ ProductProvider, useProduct, useProductForm }` where:\n * - `ProductProvider` manages store lifecycle (creation, hydration, cleanup)\n * - `useProduct` provides read-only state and variant selection\n * - `useProductForm` provides form-binding utilities (register, formProps, pending)\n *\n * Requires a `<CartProvider>` ancestor.\n *\n * @example\n * ```ts\n * const { ProductProvider, useProduct, useProductForm } =\n * createProductComponents<MyProductType>();\n * ```\n */\nexport function createProductComponents<TProduct extends ProductInput>(): {\n ProductProvider: (props: ProductProviderProps<TProduct>) => ReactNode;\n useProduct: () => UseProductResult<TProduct>;\n useProductForm: () => UseProductFormResult<TProduct>;\n} {\n const Context = createContext<ProductContextValue<TProduct> | null>(null);\n\n function useProductContext(hookName: string): ProductContextValue<TProduct> {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `${hookName} must be used inside a <ProductProvider>. ` +\n \"Wrap your component tree with the ProductProvider from createProductComponents(), \" +\n \"or use the standalone useProductForm(store) hook instead.\",\n );\n }\n return ctx;\n }\n\n function ProductProvider({ product, onSelect, children }: ProductProviderProps<TProduct>) {\n const cartStore = useCartStore(\"ProductProvider\");\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store is intentionally created once\n const store = useMemo(() => createProductFormStore<TProduct>(product, cartStore), []);\n\n useEffect(() => {\n store.connect();\n return () => store.destroy();\n }, [store]);\n\n const productKey = `${product.id}:${product.selectedOrFirstAvailableVariant?.id ?? \"\"}`;\n const mountedRef = useRef(false);\n useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n store.hydrate(product);\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- productKey is the semantic dep\n }, [productKey]);\n\n const onSelectRef = useRef(onSelect);\n onSelectRef.current = onSelect;\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- onSelectRef is a stable ref object\n const value = useMemo<ProductContextValue<TProduct>>(() => ({ store, onSelectRef }), [store]);\n\n return createElement(Context.Provider, { value }, children);\n }\n\n function useProductHook(): UseProductResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProduct\");\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store, onSelectRef],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n selectOption,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n };\n }\n\n function useProductFormHook(): UseProductFormResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProductForm\");\n return useProductForm<TProduct>(store, { onSelect: onSelectRef.current });\n }\n\n return {\n ProductProvider,\n useProduct: useProductHook,\n useProductForm: useProductFormHook,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../../src/react/shopify-scripts.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, Fragment, useEffect } from \"react\";\nimport type * as React from \"react\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyRoutesOptions,\n type ShopifyScriptTagsOptions,\n} from \"../core/shopify-scripts\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ndeclare module \"react\" {\n namespace JSX {\n interface IntrinsicElements {\n \"shopify-chat\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;\n }\n }\n}\n\nexport function ShopifyScripts(options: ShopifyScriptsProps) {\n const { consent, navigate, routes, webMcp = true, ...scriptOptions } = options;\n\n useEffect(() => {\n void initializeShopifyScripts({ navigate, routes, webMcp });\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- ShopifyScripts browser startup is initialized once from initial props.\n }, []);\n\n return createElement(\n Fragment,\n null,\n getShopifyScriptTags({ ...scriptOptions, consent }).tags.map(\n ({ tagName, attributes, innerHTML }, index) =>\n createElement(tagName, {\n key: index,\n ...getReactAttributes(attributes),\n ...(innerHTML ? { dangerouslySetInnerHTML: { __html: innerHTML } } : {}),\n }),\n ),\n );\n}\n\nfunction getReactAttributes(attributes: Record<string, string | boolean> = {}) {\n const reactAttributes = Object.fromEntries(\n Object.entries(attributes).map(([name, value]) => [\n name === \"crossorigin\" ? \"crossOrigin\" : name,\n value,\n ]),\n );\n\n if (attributes.nonce !== undefined) {\n // Browsers intentionally hide nonce content attributes from getAttribute(),\n // which can make React report a false hydration mismatch for SSR scripts.\n reactAttributes.suppressHydrationWarning = true;\n }\n\n if (attributes.async === true && typeof attributes.src === \"string\") {\n // React hoists async scripts without handlers, which can let them execute before\n // the inline Shopify bootstrap scripts that configure their globals.\n return { ...reactAttributes, onLoad: disableReactScriptHoisting };\n }\n\n return reactAttributes;\n}\n\nconst disableReactScriptHoisting = () => {};\n"],"mappings":";;;;;;;AA0BA,SAAgB,eAAe,SAA8B;CAC3D,MAAM,EAAE,SAAS,UAAU,QAAQ,SAAS,MAAM,GAAG,kBAAkB;AAEvE,iBAAgB;AACd,EAAK,yBAAyB;GAAE;GAAU;GAAQ;GAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../../src/react/shopify-scripts.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, Fragment, useEffect } from \"react\";\nimport type * as React from \"react\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyRoutesOptions,\n type ShopifyScriptTagsOptions,\n} from \"../core/shopify-scripts\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ndeclare module \"react\" {\n namespace JSX {\n interface IntrinsicElements {\n \"shopify-chat\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;\n }\n }\n}\n\nexport function ShopifyScripts(options: ShopifyScriptsProps) {\n const { consent, navigate, routes, webMcp = true, ...scriptOptions } = options;\n\n useEffect(() => {\n void initializeShopifyScripts({ consent, navigate, routes, webMcp });\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- ShopifyScripts browser startup is initialized once from initial props.\n }, []);\n\n return createElement(\n Fragment,\n null,\n getShopifyScriptTags({ ...scriptOptions, consent }).tags.map(\n ({ tagName, attributes, innerHTML }, index) =>\n createElement(tagName, {\n key: index,\n ...getReactAttributes(attributes),\n ...(innerHTML ? { dangerouslySetInnerHTML: { __html: innerHTML } } : {}),\n }),\n ),\n );\n}\n\nfunction getReactAttributes(attributes: Record<string, string | boolean> = {}) {\n const reactAttributes = Object.fromEntries(\n Object.entries(attributes).map(([name, value]) => [\n name === \"crossorigin\" ? \"crossOrigin\" : name,\n value,\n ]),\n );\n\n if (attributes.nonce !== undefined) {\n // Browsers intentionally hide nonce content attributes from getAttribute(),\n // which can make React report a false hydration mismatch for SSR scripts.\n reactAttributes.suppressHydrationWarning = true;\n }\n\n if (attributes.async === true && typeof attributes.src === \"string\") {\n // React hoists async scripts without handlers, which can let them execute before\n // the inline Shopify bootstrap scripts that configure their globals.\n return { ...reactAttributes, onLoad: disableReactScriptHoisting };\n }\n\n return reactAttributes;\n}\n\nconst disableReactScriptHoisting = () => {};\n"],"mappings":";;;;;;;AA0BA,SAAgB,eAAe,SAA8B;CAC3D,MAAM,EAAE,SAAS,UAAU,QAAQ,SAAS,MAAM,GAAG,kBAAkB;AAEvE,iBAAgB;AACd,EAAK,yBAAyB;GAAE;GAAS;GAAU;GAAQ;GAAQ,CAAC;IAEnE,EAAE,CAAC;AAEN,QAAO,cACL,UACA,MACA,qBAAqB;EAAE,GAAG;EAAe;EAAS,CAAC,CAAC,KAAK,KACtD,EAAE,SAAS,YAAY,aAAa,UACnC,cAAc,SAAS;EACrB,KAAK;EACL,GAAG,mBAAmB,WAAW;EACjC,GAAI,YAAY,EAAE,yBAAyB,EAAE,QAAQ,WAAW,EAAE,GAAG,EAAE;EACxE,CAAC,CACL,CACF;;AAGH,SAAS,mBAAmB,aAA+C,EAAE,EAAE;CAC7E,MAAM,kBAAkB,OAAO,YAC7B,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,MAAM,WAAW,CAChD,SAAS,gBAAgB,gBAAgB,MACzC,MACD,CAAC,CACH;AAED,KAAI,WAAW,UAAU,OAGvB,iBAAgB,2BAA2B;AAG7C,KAAI,WAAW,UAAU,QAAQ,OAAO,WAAW,QAAQ,SAGzD,QAAO;EAAE,GAAG;EAAiB,QAAQ;EAA4B;AAGnE,QAAO;;AAGT,MAAM,mCAAmC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CartData, CartState } from "../core/cart/state.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { CartActions, CreateCartStoreOptions } from "../core/cart/cart.mjs";
|
|
3
3
|
import { CartFormRegister } from "../core/cart/form.mjs";
|
|
4
4
|
import { CartDataFromHandlers } from "../core/cart/server-handlers.mjs";
|
|
5
5
|
import { ComputedRef, PropType, ShallowRef } from "vue";
|
|
@@ -18,8 +18,6 @@ type TypedCartProvider<TData extends CartData> = {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
|
-
/** Actions for reconciling cart state after updates outside Standard Actions. */
|
|
22
|
-
type CartActions = Pick<CartStore, "refresh">;
|
|
23
21
|
type TypedCartComponents<TData extends CartData> = {
|
|
24
22
|
CartProvider: TypedCartProvider<TData>;
|
|
25
23
|
useCart: TypedUseCart<TData>;
|
|
@@ -27,12 +25,73 @@ type TypedCartComponents<TData extends CartData> = {
|
|
|
27
25
|
useCartActions: typeof useCartActions;
|
|
28
26
|
useCartForm: typeof useCartForm;
|
|
29
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Returns cart actions for reconciling state after out-of-band mutations.
|
|
30
|
+
*
|
|
31
|
+
* Currently exposes {@link CartStore.refresh} — call it after server-side cart
|
|
32
|
+
* mutations that bypass the form system.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```vue
|
|
36
|
+
* <script setup>
|
|
37
|
+
* const { refresh } = useCartActions();
|
|
38
|
+
*
|
|
39
|
+
* async function handleServerAction() {
|
|
40
|
+
* await createCartOnServer();
|
|
41
|
+
* refresh();
|
|
42
|
+
* }
|
|
43
|
+
* </script>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
30
46
|
declare function useCartActions(): CartActions;
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes the {@link CartStore} to the analytics event dispatcher.
|
|
49
|
+
*
|
|
50
|
+
* Call once near the root of your app. Starts tracking on mount via `onMounted`
|
|
51
|
+
* and cleans up via `onScopeDispose`.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```vue
|
|
55
|
+
* <script setup>
|
|
56
|
+
* useCartAnalytics();
|
|
57
|
+
* </script>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
31
60
|
declare function useCartAnalytics(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Returns form props, a field register function, and reactive pending state
|
|
63
|
+
* for building cart forms in Vue.
|
|
64
|
+
*
|
|
65
|
+
* Unlike the React hook, the Vue variant includes `isPending` — a reactive
|
|
66
|
+
* object with `initial` (`ComputedRef<boolean>`) for the initial cart load
|
|
67
|
+
* and `lines(lineId?)` for checking per-line or any-line pending state.
|
|
68
|
+
*
|
|
69
|
+
* Note: `interactive: true` returns numeric input attributes but does **not**
|
|
70
|
+
* auto-submit on change — that behavior requires `attachQuantityInput`, which
|
|
71
|
+
* is only wired by the React adapter.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```vue
|
|
75
|
+
* <script setup>
|
|
76
|
+
* const { formProps, register, isPending } = useCartForm();
|
|
77
|
+
* </script>
|
|
78
|
+
*
|
|
79
|
+
* <template>
|
|
80
|
+
* <form v-bind="formProps()">
|
|
81
|
+
* <input v-bind="register('lineId', { value: line.id })" />
|
|
82
|
+
* <input v-bind="register('quantity', { value: line.quantity })" />
|
|
83
|
+
* <button v-bind="register('set')" />
|
|
84
|
+
* <button v-bind="register('increase')">+</button>
|
|
85
|
+
* <button v-bind="register('decrease')">−</button>
|
|
86
|
+
* <span v-if="isPending.lines(line.id)">Updating…</span>
|
|
87
|
+
* </form>
|
|
88
|
+
* </template>
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
32
91
|
declare function useCartForm(): {
|
|
33
92
|
formProps: (opts?: {
|
|
34
|
-
beforeSubmit?: (e:
|
|
35
|
-
afterSubmit?: (e:
|
|
93
|
+
beforeSubmit?: (e: SubmitEvent) => void;
|
|
94
|
+
afterSubmit?: (e: SubmitEvent) => void;
|
|
36
95
|
}) => Record<string, unknown>;
|
|
37
96
|
register: CartFormRegister;
|
|
38
97
|
isPending: {
|
|
@@ -40,7 +99,27 @@ declare function useCartForm(): {
|
|
|
40
99
|
lines: (lineId?: string) => boolean;
|
|
41
100
|
};
|
|
42
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* Factory that returns typed cart components and composables matched to your
|
|
104
|
+
* server handler's cart query shape.
|
|
105
|
+
*
|
|
106
|
+
* The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
|
|
107
|
+
* call, so every composable's {@link CartState} carries your custom cart fields.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* import type { cartServerHandlers } from "./server/cart.server";
|
|
112
|
+
*
|
|
113
|
+
* const {
|
|
114
|
+
* CartProvider,
|
|
115
|
+
* useCart,
|
|
116
|
+
* useOptionalCart,
|
|
117
|
+
* useCartActions,
|
|
118
|
+
* useCartForm,
|
|
119
|
+
* } = createCartComponents<typeof cartServerHandlers>();
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
43
122
|
declare function createCartComponents<THandlers>(): TypedCartComponents<CartDataFromHandlers<THandlers>>;
|
|
44
123
|
//#endregion
|
|
45
|
-
export {
|
|
124
|
+
export { createCartComponents, useCartActions, useCartAnalytics };
|
|
46
125
|
//# sourceMappingURL=cart.d.mts.map
|
|
@@ -25,6 +25,20 @@ function useCartStore(composableName = "useCartStore") {
|
|
|
25
25
|
function useOptionalCartStore() {
|
|
26
26
|
return inject(CartStoreKey, null);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Vue component that creates and manages a {@link CartStore} instance.
|
|
30
|
+
*
|
|
31
|
+
* Calls {@link CartStore.connect} on mount and {@link CartStore.destroy} on unmount.
|
|
32
|
+
* All cart composables (`useCart`, `useCartForm`, `useCartActions`) must be
|
|
33
|
+
* descendants of this component.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```vue
|
|
37
|
+
* <CartProvider :initialData="{ cart: loaderData.cart }">
|
|
38
|
+
* <App />
|
|
39
|
+
* </CartProvider>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
28
42
|
const CartProvider = defineComponent({
|
|
29
43
|
name: "CartProvider",
|
|
30
44
|
props: { initialData: {
|
|
@@ -47,9 +61,40 @@ const CartProvider = defineComponent({
|
|
|
47
61
|
function useCart(selector, isEqual) {
|
|
48
62
|
return useCartSelector(useCartStore("useCart"), selector ?? ((state) => state), isEqual);
|
|
49
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns cart actions for reconciling state after out-of-band mutations.
|
|
66
|
+
*
|
|
67
|
+
* Currently exposes {@link CartStore.refresh} — call it after server-side cart
|
|
68
|
+
* mutations that bypass the form system.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```vue
|
|
72
|
+
* <script setup>
|
|
73
|
+
* const { refresh } = useCartActions();
|
|
74
|
+
*
|
|
75
|
+
* async function handleServerAction() {
|
|
76
|
+
* await createCartOnServer();
|
|
77
|
+
* refresh();
|
|
78
|
+
* }
|
|
79
|
+
* <\/script>
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
50
82
|
function useCartActions() {
|
|
51
83
|
return { refresh: useCartStore("useCartActions").refresh };
|
|
52
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Subscribes the {@link CartStore} to the analytics event dispatcher.
|
|
87
|
+
*
|
|
88
|
+
* Call once near the root of your app. Starts tracking on mount via `onMounted`
|
|
89
|
+
* and cleans up via `onScopeDispose`.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```vue
|
|
93
|
+
* <script setup>
|
|
94
|
+
* useCartAnalytics();
|
|
95
|
+
* <\/script>
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
53
98
|
function useCartAnalytics() {
|
|
54
99
|
const store = useCartStore("useCartAnalytics");
|
|
55
100
|
let stopTracking;
|
|
@@ -76,6 +121,36 @@ function useCartSelector(store, selector, isEqual) {
|
|
|
76
121
|
}));
|
|
77
122
|
return selected;
|
|
78
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Returns form props, a field register function, and reactive pending state
|
|
126
|
+
* for building cart forms in Vue.
|
|
127
|
+
*
|
|
128
|
+
* Unlike the React hook, the Vue variant includes `isPending` — a reactive
|
|
129
|
+
* object with `initial` (`ComputedRef<boolean>`) for the initial cart load
|
|
130
|
+
* and `lines(lineId?)` for checking per-line or any-line pending state.
|
|
131
|
+
*
|
|
132
|
+
* Note: `interactive: true` returns numeric input attributes but does **not**
|
|
133
|
+
* auto-submit on change — that behavior requires `attachQuantityInput`, which
|
|
134
|
+
* is only wired by the React adapter.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```vue
|
|
138
|
+
* <script setup>
|
|
139
|
+
* const { formProps, register, isPending } = useCartForm();
|
|
140
|
+
* <\/script>
|
|
141
|
+
*
|
|
142
|
+
* <template>
|
|
143
|
+
* <form v-bind="formProps()">
|
|
144
|
+
* <input v-bind="register('lineId', { value: line.id })" />
|
|
145
|
+
* <input v-bind="register('quantity', { value: line.quantity })" />
|
|
146
|
+
* <button v-bind="register('set')" />
|
|
147
|
+
* <button v-bind="register('increase')">+</button>
|
|
148
|
+
* <button v-bind="register('decrease')">−</button>
|
|
149
|
+
* <span v-if="isPending.lines(line.id)">Updating…</span>
|
|
150
|
+
* </form>
|
|
151
|
+
* </template>
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
79
154
|
function useCartForm() {
|
|
80
155
|
const store = useCartStore("useCartForm");
|
|
81
156
|
const loading = useCart((s) => s.loading);
|
|
@@ -101,6 +176,26 @@ function useCartForm() {
|
|
|
101
176
|
}
|
|
102
177
|
};
|
|
103
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Factory that returns typed cart components and composables matched to your
|
|
181
|
+
* server handler's cart query shape.
|
|
182
|
+
*
|
|
183
|
+
* The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
|
|
184
|
+
* call, so every composable's {@link CartState} carries your custom cart fields.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* import type { cartServerHandlers } from "./server/cart.server";
|
|
189
|
+
*
|
|
190
|
+
* const {
|
|
191
|
+
* CartProvider,
|
|
192
|
+
* useCart,
|
|
193
|
+
* useOptionalCart,
|
|
194
|
+
* useCartActions,
|
|
195
|
+
* useCartForm,
|
|
196
|
+
* } = createCartComponents<typeof cartServerHandlers>();
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
104
199
|
function createCartComponents() {
|
|
105
200
|
const TypedCartProvider = createTypedCartProvider();
|
|
106
201
|
const useTypedCart = ((selector, isEqual) => {
|