From 2bb8c81a05ee7abe4c362aec230f24b61eccf4e4 Mon Sep 17 00:00:00 2001 From: Anthony Frehner Date: Tue, 14 Feb 2023 11:05:26 -0700 Subject: [PATCH] Migrate cartlinequantityadjustbutton (#180) * Start migration of CartLineQuantityAdjustButton Co-Authored-By: Jason Kurian <2642545+JaKXz@users.noreply.github.com> * Get CartLineQuantity typescript tests passing * Update tests for CartLineQuantityAdjustButton to use RTL Fix a couple instances of unnecessary "data-testId" camel casing. Update BuyNowButton tests to use a shared utility Co-Authored-By: Jason Kurian <2642545+JaKXz@users.noreply.github.com> * Add attributes to linesUpdate() so that they're not lost When adjusting the quantity. * Add documentation for the components * Update docs icons * Small update to PR template * Allow dev to disable manually if they want. * Make the typing DX better for these keys --------- Co-authored-by: Jason Kurian <2642545+JaKXz@users.noreply.github.com> --- .changeset/sharp-walls-perform.md | 13 + .github/PULL_REQUEST_TEMPLATE.md | 4 +- .../docs/generated/generated_docs_data.json | 491 +++++++++++------- packages/react/src/BuyNowButton.test.tsx | 34 +- packages/react/src/CartLineProvider.test.tsx | 5 +- packages/react/src/CartLineProvider.tsx | 9 +- packages/react/src/CartLineQuantity.doc.ts | 50 ++ .../react/src/CartLineQuantity.example.jsx | 9 + .../react/src/CartLineQuantity.example.tsx | 10 + packages/react/src/CartLineQuantity.test.tsx | 59 +++ packages/react/src/CartLineQuantity.tsx | 32 ++ .../src/CartLineQuantityAdjustButton.doc.ts | 50 ++ .../CartLineQuantityAdjustButton.example.jsx | 23 + .../CartLineQuantityAdjustButton.example.tsx | 24 + .../src/CartLineQuantityAdjustButton.test.tsx | 198 +++++++ .../src/CartLineQuantityAdjustButton.tsx | 75 +++ .../react/src/CartProvider.test.helpers.ts | 21 + packages/react/src/Video.test.tsx | 4 +- packages/react/src/index.ts | 2 + .../src/storefront-api-response.types.ts | 3 +- 20 files changed, 902 insertions(+), 214 deletions(-) create mode 100644 .changeset/sharp-walls-perform.md create mode 100644 packages/react/src/CartLineQuantity.doc.ts create mode 100644 packages/react/src/CartLineQuantity.example.jsx create mode 100644 packages/react/src/CartLineQuantity.example.tsx create mode 100644 packages/react/src/CartLineQuantity.test.tsx create mode 100644 packages/react/src/CartLineQuantity.tsx create mode 100644 packages/react/src/CartLineQuantityAdjustButton.doc.ts create mode 100644 packages/react/src/CartLineQuantityAdjustButton.example.jsx create mode 100644 packages/react/src/CartLineQuantityAdjustButton.example.tsx create mode 100644 packages/react/src/CartLineQuantityAdjustButton.test.tsx create mode 100644 packages/react/src/CartLineQuantityAdjustButton.tsx diff --git a/.changeset/sharp-walls-perform.md b/.changeset/sharp-walls-perform.md new file mode 100644 index 0000000000..6c75401795 --- /dev/null +++ b/.changeset/sharp-walls-perform.md @@ -0,0 +1,13 @@ +--- +'@shopify/hydrogen-react': patch +--- + +Adding `` and `` + +The `` and `` components have been added / migrated over from Hydrogen v1. + +Additionally, fixed a bug when using `` that caused CartLine Attributes to be erased. CartLine Attributes should now be persisted when using that component. + +## `useCartLine()` TypeScript types update + +`useCartLine()`'s TypeScript type originally returned a `CartLine`. It has now been updated to be `PartialDeep`, which makes all the properties optional instead of required. This matches with the rest of hydrogen-react in that we can't know or guarnatee what properties exist on certain objects so we reflect that state in the TypeScript types. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c64adfb312..b199c2bc8e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,7 +12,7 @@ ### Before submitting the PR, please make sure you do the following: -- [ ] Read the [Contributing Guidelines](https://github.com/shopify/hydrogen-react/blob/main/contributing.md) +- [ ] Read the [Contributing Guidelines](https://github.com/Shopify/hydrogen-react/blob/main/CONTRIBUTING.md) - [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`) -- [ ] Update docs in this repository according to your change +- [ ] Update docs in this repository according to your change, and run `yarn build-docs` in the `packages/react` folder. - [ ] Run `yarn changeset add` if this PR cause a version bump based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). If you have a breaking change, it will need to wait until the next major version release. Otherwise, use patch updates even for new features. Read [more about Hydrogen React's versioning](https://github.com/shopify/hydrogen-react/blob/main/readme.md#versioning). diff --git a/packages/react/docs/generated/generated_docs_data.json b/packages/react/docs/generated/generated_docs_data.json index b233d4aab8..2e9825e5ef 100644 --- a/packages/react/docs/generated/generated_docs_data.json +++ b/packages/react/docs/generated/generated_docs_data.json @@ -544,7 +544,7 @@ "filePath": "/CartLineProvider.tsx", "syntaxKind": "TypeAliasDeclaration", "name": "CartLineProviderProps", - "value": "{\n /** Any `ReactNode` elements. */\n children: ReactNode;\n /** A cart line object. */\n line: CartLine;\n}", + "value": "{\n /** Any `ReactNode` elements. */\n children: ReactNode;\n /** A cart line object. */\n line: CartLinePartialDeep;\n}", "description": "", "members": [ { @@ -558,7 +558,7 @@ "filePath": "/CartLineProvider.tsx", "syntaxKind": "PropertySignature", "name": "line", - "value": "CartLine", + "value": "PartialObjectDeep", "description": "A cart line object." } ] @@ -567,6 +567,130 @@ } ] }, + { + "name": "CartLineQuantity", + "category": "components", + "isVisualComponent": false, + "related": [ + { + "name": "useCartLine", + "type": "gear", + "url": "/api/hydrogen-react/hooks/useCartLine" + }, + { + "name": "CartLineQuantityAdjustButton", + "type": "component", + "url": "/api/hydrogen-react/components/CartLineQuantityAdjustButton" + } + ], + "description": "\n The `` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.\n\nIt must be a descendent of a `` component, and uses the `useCartLine()` hook internally.\n ", + "type": "component", + "defaultExample": { + "description": "I am the default example", + "codeblock": { + "tabs": [ + { + "title": "JavaScript", + "code": "import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react';\n\nexport function Example({line}) {\n return (\n \n \n \n );\n}\n", + "language": "jsx" + }, + { + "title": "TypeScript", + "code": "import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react';\nimport type {CartLine} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function Example({line}: {line: CartLine}) {\n return (\n \n \n \n );\n}\n", + "language": "tsx" + } + ], + "title": "Example code" + } + }, + "definitions": [ + { + "title": "Props", + "description": "", + "type": "CartLineQuantityBaseProps", + "typeDefinitions": { + "CartLineQuantityBaseProps": { + "filePath": "/CartLineQuantity.tsx", + "name": "CartLineQuantityBaseProps", + "description": "", + "members": [ + { + "filePath": "/CartLineQuantity.tsx", + "syntaxKind": "PropertySignature", + "name": "as", + "value": "ComponentGeneric", + "description": "An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`.", + "isOptional": true + } + ], + "value": "interface CartLineQuantityBaseProps<\n ComponentGeneric extends ElementType = 'span'\n> {\n /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`. */\n as?: ComponentGeneric;\n}" + } + } + } + ] + }, + { + "name": "CartLineQuantityAdjustButton", + "category": "components", + "isVisualComponent": false, + "related": [ + { + "name": "useCartLine", + "type": "gear", + "url": "/api/hydrogen-react/hooks/useCartLine" + }, + { + "name": "CartLineQuantity", + "type": "component", + "url": "/api/hydrogen-react/components/CartLineQuantity" + } + ], + "description": "\n The `` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.\n\nIt must be a descendent of a `` component, and uses the `useCartLine()` hook internally.\n ", + "type": "component", + "defaultExample": { + "description": "I am the default example", + "codeblock": { + "tabs": [ + { + "title": "JavaScript", + "code": "import {\n CartLineQuantityAdjustButton,\n CartLineProvider,\n CartProvider,\n} from '@shopify/hydrogen-react';\n\nexport function Example({line}) {\n return (\n \n \n \n Increase\n \n \n Decrease\n \n \n Remove\n \n \n \n );\n}\n", + "language": "jsx" + }, + { + "title": "TypeScript", + "code": "import {\n CartLineQuantityAdjustButton,\n CartLineProvider,\n CartProvider,\n} from '@shopify/hydrogen-react';\nimport type {CartLine} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function Example({line}: {line: CartLine}) {\n return (\n \n \n \n Increase\n \n \n Decrease\n \n \n Remove\n \n \n \n );\n}\n", + "language": "tsx" + } + ], + "title": "Example code" + } + }, + "definitions": [ + { + "title": "Props", + "description": "", + "type": "CartLineQuantityAdjustButtonBaseProps", + "typeDefinitions": { + "CartLineQuantityAdjustButtonBaseProps": { + "filePath": "/CartLineQuantityAdjustButton.tsx", + "name": "CartLineQuantityAdjustButtonBaseProps", + "description": "", + "members": [ + { + "filePath": "/CartLineQuantityAdjustButton.tsx", + "syntaxKind": "PropertySignature", + "name": "adjust", + "value": "\"remove\" | \"increase\" | \"decrease\"", + "description": "The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`.", + "isOptional": true + } + ], + "value": "interface CartLineQuantityAdjustButtonBaseProps {\n /** The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`. */\n adjust?: 'increase' | 'decrease' | 'remove';\n}" + } + } + } + ] + }, { "name": "CartProvider", "category": "components", @@ -1294,7 +1418,7 @@ "filePath": "/Image.tsx", "syntaxKind": "PropertySignature", "name": "crop", - "value": "\"top\" | \"bottom\" | \"left\" | \"right\" | \"center\"", + "value": "\"center\" | \"top\" | \"bottom\" | \"left\" | \"right\"", "description": "", "isOptional": true }, @@ -1302,7 +1426,7 @@ "filePath": "/Image.tsx", "syntaxKind": "PropertySignature", "name": "scale", - "value": "3 | 2", + "value": "2 | 3", "description": "", "isOptional": true }, @@ -1764,7 +1888,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "inputMode", - "value": "\"url\" | \"none\" | \"search\" | \"text\" | \"tel\" | \"email\" | \"numeric\" | \"decimal\"", + "value": "\"text\" | \"url\" | \"none\" | \"search\" | \"tel\" | \"email\" | \"numeric\" | \"decimal\"", "description": "Hints at the type of data that might be entered by the user while editing the element or its contents", "isOptional": true }, @@ -1852,7 +1976,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "aria-current", - "value": "boolean | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\" | \"time\"", + "value": "boolean | \"time\" | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\"", "description": "Indicates the element that represents the current item within a container or set of related elements.", "isOptional": true }, @@ -1926,7 +2050,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "aria-haspopup", - "value": "boolean | \"true\" | \"false\" | \"dialog\" | \"grid\" | \"listbox\" | \"menu\" | \"tree\"", + "value": "boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\"", "description": "Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element.", "isOptional": true }, @@ -2182,7 +2306,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCopy", - "value": "ClipboardEventHandler", + "value": "ClipboardEventHandler", "description": "", "isOptional": true }, @@ -2190,7 +2314,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCopyCapture", - "value": "ClipboardEventHandler", + "value": "ClipboardEventHandler", "description": "", "isOptional": true }, @@ -2198,7 +2322,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCut", - "value": "ClipboardEventHandler", + "value": "ClipboardEventHandler", "description": "", "isOptional": true }, @@ -2206,7 +2330,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCutCapture", - "value": "ClipboardEventHandler", + "value": "ClipboardEventHandler", "description": "", "isOptional": true }, @@ -2214,7 +2338,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPaste", - "value": "ClipboardEventHandler", + "value": "ClipboardEventHandler", "description": "", "isOptional": true }, @@ -2222,7 +2346,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPasteCapture", - "value": "ClipboardEventHandler", + "value": "ClipboardEventHandler", "description": "", "isOptional": true }, @@ -2230,7 +2354,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCompositionEnd", - "value": "CompositionEventHandler", + "value": "CompositionEventHandler", "description": "", "isOptional": true }, @@ -2238,7 +2362,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCompositionEndCapture", - "value": "CompositionEventHandler", + "value": "CompositionEventHandler", "description": "", "isOptional": true }, @@ -2246,7 +2370,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCompositionStart", - "value": "CompositionEventHandler", + "value": "CompositionEventHandler", "description": "", "isOptional": true }, @@ -2254,7 +2378,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCompositionStartCapture", - "value": "CompositionEventHandler", + "value": "CompositionEventHandler", "description": "", "isOptional": true }, @@ -2262,7 +2386,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCompositionUpdate", - "value": "CompositionEventHandler", + "value": "CompositionEventHandler", "description": "", "isOptional": true }, @@ -2270,7 +2394,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCompositionUpdateCapture", - "value": "CompositionEventHandler", + "value": "CompositionEventHandler", "description": "", "isOptional": true }, @@ -2278,7 +2402,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onFocus", - "value": "FocusEventHandler", + "value": "FocusEventHandler", "description": "", "isOptional": true }, @@ -2286,7 +2410,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onFocusCapture", - "value": "FocusEventHandler", + "value": "FocusEventHandler", "description": "", "isOptional": true }, @@ -2294,7 +2418,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onBlur", - "value": "FocusEventHandler", + "value": "FocusEventHandler", "description": "", "isOptional": true }, @@ -2302,7 +2426,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onBlurCapture", - "value": "FocusEventHandler", + "value": "FocusEventHandler", "description": "", "isOptional": true }, @@ -2310,7 +2434,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onChange", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2318,7 +2442,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onChangeCapture", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2326,7 +2450,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onBeforeInput", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2334,7 +2458,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onBeforeInputCapture", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2342,7 +2466,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onInput", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2350,7 +2474,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onInputCapture", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2358,7 +2482,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onReset", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2366,7 +2490,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onResetCapture", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2374,7 +2498,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSubmit", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2382,7 +2506,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSubmitCapture", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2390,7 +2514,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onInvalid", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2398,7 +2522,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onInvalidCapture", - "value": "FormEventHandler", + "value": "FormEventHandler", "description": "", "isOptional": true }, @@ -2406,7 +2530,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoad", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2414,7 +2538,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2422,7 +2546,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onError", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2430,7 +2554,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onErrorCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2438,7 +2562,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onKeyDown", - "value": "KeyboardEventHandler", + "value": "KeyboardEventHandler", "description": "", "isOptional": true }, @@ -2446,7 +2570,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onKeyDownCapture", - "value": "KeyboardEventHandler", + "value": "KeyboardEventHandler", "description": "", "isOptional": true }, @@ -2454,7 +2578,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onKeyPress", - "value": "KeyboardEventHandler", + "value": "KeyboardEventHandler", "description": "", "isOptional": true, "deprecationMessage": "Deprecated" @@ -2463,7 +2587,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onKeyPressCapture", - "value": "KeyboardEventHandler", + "value": "KeyboardEventHandler", "description": "", "isOptional": true, "deprecationMessage": "Deprecated" @@ -2472,7 +2596,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onKeyUp", - "value": "KeyboardEventHandler", + "value": "KeyboardEventHandler", "description": "", "isOptional": true }, @@ -2480,7 +2604,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onKeyUpCapture", - "value": "KeyboardEventHandler", + "value": "KeyboardEventHandler", "description": "", "isOptional": true }, @@ -2488,7 +2612,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAbort", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2496,7 +2620,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAbortCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2504,7 +2628,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCanPlay", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2512,7 +2636,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCanPlayCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2520,7 +2644,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCanPlayThrough", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2528,7 +2652,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onCanPlayThroughCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2536,7 +2660,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDurationChange", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2544,7 +2668,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDurationChangeCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2552,7 +2676,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onEmptied", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2560,7 +2684,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onEmptiedCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2568,7 +2692,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onEncrypted", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2576,7 +2700,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onEncryptedCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2584,7 +2708,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onEnded", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2592,7 +2716,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onEndedCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2600,7 +2724,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadedData", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2608,7 +2732,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadedDataCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2616,7 +2740,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadedMetadata", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2624,7 +2748,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadedMetadataCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2632,7 +2756,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadStart", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2640,7 +2764,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLoadStartCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2648,7 +2772,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPause", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2656,7 +2780,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPauseCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2664,7 +2788,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPlay", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2672,7 +2796,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPlayCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2680,7 +2804,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPlaying", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2688,7 +2812,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPlayingCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2696,7 +2820,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onProgress", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2704,7 +2828,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onProgressCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2712,7 +2836,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onRateChange", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2720,7 +2844,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onRateChangeCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2728,7 +2852,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onResize", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2736,7 +2860,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onResizeCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2744,7 +2868,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSeeked", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2752,7 +2876,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSeekedCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2760,7 +2884,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSeeking", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2768,7 +2892,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSeekingCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2776,7 +2900,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onStalled", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2784,7 +2908,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onStalledCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2792,7 +2916,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSuspend", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2800,7 +2924,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSuspendCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2808,7 +2932,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTimeUpdate", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2816,7 +2940,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTimeUpdateCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2824,7 +2948,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onVolumeChange", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2832,7 +2956,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onVolumeChangeCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2840,7 +2964,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onWaiting", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2848,7 +2972,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onWaitingCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -2856,7 +2980,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAuxClick", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2864,7 +2988,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAuxClickCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2872,7 +2996,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onClick", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2880,7 +3004,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onClickCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2888,7 +3012,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onContextMenu", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2896,7 +3020,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onContextMenuCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2904,7 +3028,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDoubleClick", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2912,7 +3036,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDoubleClickCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -2920,7 +3044,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDrag", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2928,7 +3052,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2936,7 +3060,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragEnd", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2944,7 +3068,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragEndCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2952,7 +3076,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragEnter", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2960,7 +3084,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragEnterCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2968,7 +3092,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragExit", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2976,7 +3100,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragExitCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2984,7 +3108,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragLeave", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -2992,7 +3116,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragLeaveCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3000,7 +3124,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragOver", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3008,7 +3132,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragOverCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3016,7 +3140,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragStart", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3024,7 +3148,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDragStartCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3032,7 +3156,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDrop", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3040,7 +3164,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onDropCapture", - "value": "DragEventHandler", + "value": "DragEventHandler", "description": "", "isOptional": true }, @@ -3048,7 +3172,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseDown", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3056,7 +3180,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseDownCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3064,7 +3188,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseEnter", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3072,7 +3196,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseLeave", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3080,7 +3204,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseMove", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3088,7 +3212,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseMoveCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3096,7 +3220,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseOut", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3104,7 +3228,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseOutCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3112,7 +3236,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseOver", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3120,7 +3244,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseOverCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3128,7 +3252,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseUp", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3136,7 +3260,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onMouseUpCapture", - "value": "MouseEventHandler", + "value": "MouseEventHandler", "description": "", "isOptional": true }, @@ -3144,7 +3268,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSelect", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -3152,7 +3276,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onSelectCapture", - "value": "ReactEventHandler", + "value": "ReactEventHandler", "description": "", "isOptional": true }, @@ -3160,7 +3284,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchCancel", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3168,7 +3292,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchCancelCapture", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3176,7 +3300,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchEnd", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3184,7 +3308,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchEndCapture", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3192,7 +3316,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchMove", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3200,7 +3324,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchMoveCapture", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3208,7 +3332,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchStart", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3216,7 +3340,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTouchStartCapture", - "value": "TouchEventHandler", + "value": "TouchEventHandler", "description": "", "isOptional": true }, @@ -3224,7 +3348,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerDown", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3232,7 +3356,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerDownCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3240,7 +3364,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerMove", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3248,7 +3372,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerMoveCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3256,7 +3380,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerUp", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3264,7 +3388,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerUpCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3272,7 +3396,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerCancel", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3280,7 +3404,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerCancelCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3288,7 +3412,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerEnter", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3296,7 +3420,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerEnterCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3304,7 +3428,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerLeave", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3312,7 +3436,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerLeaveCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3320,7 +3444,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerOver", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3328,7 +3452,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerOverCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3336,7 +3460,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerOut", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3344,7 +3468,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onPointerOutCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3352,7 +3476,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onGotPointerCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3360,7 +3484,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onGotPointerCaptureCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3368,7 +3492,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLostPointerCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3376,7 +3500,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onLostPointerCaptureCapture", - "value": "PointerEventHandler", + "value": "PointerEventHandler", "description": "", "isOptional": true }, @@ -3384,7 +3508,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onScroll", - "value": "UIEventHandler", + "value": "UIEventHandler", "description": "", "isOptional": true }, @@ -3392,7 +3516,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onScrollCapture", - "value": "UIEventHandler", + "value": "UIEventHandler", "description": "", "isOptional": true }, @@ -3400,7 +3524,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onWheel", - "value": "WheelEventHandler", + "value": "WheelEventHandler", "description": "", "isOptional": true }, @@ -3408,7 +3532,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onWheelCapture", - "value": "WheelEventHandler", + "value": "WheelEventHandler", "description": "", "isOptional": true }, @@ -3416,7 +3540,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAnimationStart", - "value": "AnimationEventHandler", + "value": "AnimationEventHandler", "description": "", "isOptional": true }, @@ -3424,7 +3548,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAnimationStartCapture", - "value": "AnimationEventHandler", + "value": "AnimationEventHandler", "description": "", "isOptional": true }, @@ -3432,7 +3556,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAnimationEnd", - "value": "AnimationEventHandler", + "value": "AnimationEventHandler", "description": "", "isOptional": true }, @@ -3440,7 +3564,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAnimationEndCapture", - "value": "AnimationEventHandler", + "value": "AnimationEventHandler", "description": "", "isOptional": true }, @@ -3448,7 +3572,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAnimationIteration", - "value": "AnimationEventHandler", + "value": "AnimationEventHandler", "description": "", "isOptional": true }, @@ -3456,7 +3580,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onAnimationIterationCapture", - "value": "AnimationEventHandler", + "value": "AnimationEventHandler", "description": "", "isOptional": true }, @@ -3464,7 +3588,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTransitionEnd", - "value": "TransitionEventHandler", + "value": "TransitionEventHandler", "description": "", "isOptional": true }, @@ -3472,7 +3596,7 @@ "filePath": "/MediaFile.tsx", "syntaxKind": "PropertySignature", "name": "onTransitionEndCapture", - "value": "TransitionEventHandler", + "value": "TransitionEventHandler", "description": "", "isOptional": true } @@ -3593,7 +3717,7 @@ "filePath": "/Image.tsx", "syntaxKind": "PropertySignature", "name": "crop", - "value": "\"top\" | \"bottom\" | \"left\" | \"right\" | \"center\"", + "value": "\"center\" | \"top\" | \"bottom\" | \"left\" | \"right\"", "description": "", "isOptional": true }, @@ -3601,7 +3725,7 @@ "filePath": "/Image.tsx", "syntaxKind": "PropertySignature", "name": "scale", - "value": "3 | 2", + "value": "2 | 3", "description": "", "isOptional": true }, @@ -4885,7 +5009,7 @@ "filePath": "/Image.tsx", "syntaxKind": "PropertySignature", "name": "crop", - "value": "\"top\" | \"bottom\" | \"left\" | \"right\" | \"center\"", + "value": "\"center\" | \"top\" | \"bottom\" | \"left\" | \"right\"", "description": "", "isOptional": true }, @@ -4893,7 +5017,7 @@ "filePath": "/Image.tsx", "syntaxKind": "PropertySignature", "name": "scale", - "value": "3 | 2", + "value": "2 | 3", "description": "", "isOptional": true }, @@ -4993,6 +5117,7 @@ "name": "shopDomain", "description": "- The Online Store domain to sent Shopify analytics under the same\ntop level domain.", "value": "string", + "isOptional": true, "filePath": "/analytics.ts" } ], @@ -6149,7 +6274,7 @@ { "name": "metafield", "description": "", - "value": "PartialDeep", + "value": "PartialObjectDeep", "filePath": "/parse-metafield.ts" } ], @@ -6247,7 +6372,7 @@ "name": "StorefrontClientReturn", "value": "StorefrontClientReturn" }, - "value": "export function createStorefrontClient(\n props: StorefrontClientProps\n): StorefrontClientReturn {\n const {\n storeDomain,\n privateStorefrontToken,\n publicStorefrontToken,\n storefrontApiVersion,\n contentType,\n } = props;\n\n if (storefrontApiVersion !== SFAPI_VERSION) {\n warnOnce(\n `StorefrontClient: The Storefront API version that you're using is different than the version this build of Hydrogen React is targeting. You may run into unexpected errors if these versions don't match. Received verion: \"${storefrontApiVersion}\"; expected version \"${SFAPI_VERSION}\"`\n );\n }\n\n // only warn if not in a browser environment\n if (__HYDROGEN_DEV__ && !privateStorefrontToken && !globalThis.document) {\n warnOnce(\n `StorefrontClient: Using a private storefront token is recommended for server environments. Refer to the authentication https://shopify.dev/api/storefront#authentication documentation for more details.`\n );\n }\n\n // only warn if in a browser environment and you're using the privateStorefrontToken\n if (__HYDROGEN_DEV__ && privateStorefrontToken && globalThis) {\n warnOnce(\n `StorefrontClient: You are attempting to use a private token in an environment where it can be easily accessed by anyone. This is a security risk; please use the public token and the 'publicStorefrontToken' prop`\n );\n }\n\n return {\n getShopifyDomain(overrideProps) {\n return overrideProps?.storeDomain ?? storeDomain;\n },\n getStorefrontApiUrl(overrideProps) {\n const finalDomainUrl = overrideProps?.storeDomain ?? storeDomain;\n return `${finalDomainUrl}${finalDomainUrl.endsWith('/') ? '' : '/'}api/${\n overrideProps?.storefrontApiVersion ?? storefrontApiVersion\n }/graphql.json`;\n },\n getPrivateTokenHeaders(overrideProps) {\n if (!privateStorefrontToken && !overrideProps?.privateStorefrontToken) {\n throw new Error(\n `StorefrontClient: You did not pass in a 'privateStorefrontToken' while using 'getPrivateTokenHeaders()'`\n );\n }\n\n if (__HYDROGEN_DEV__ && !overrideProps?.buyerIp) {\n warnOnce(\n `StorefrontClient: it is recommended to pass in the 'buyerIp' property which improves analytics and data in the admin.`\n );\n }\n\n const finalContentType = overrideProps?.contentType ?? contentType;\n\n return {\n // default to json\n 'content-type':\n finalContentType === 'graphql'\n ? 'application/graphql'\n : 'application/json',\n 'X-SDK-Variant': 'hydrogen-react',\n 'X-SDK-Variant-Source': 'react',\n 'X-SDK-Version': storefrontApiVersion,\n 'Shopify-Storefront-Private-Token':\n overrideProps?.privateStorefrontToken ?? privateStorefrontToken ?? '',\n ...(overrideProps?.buyerIp\n ? {'Shopify-Storefront-Buyer-IP': overrideProps.buyerIp}\n : {}),\n };\n },\n getPublicTokenHeaders(overrideProps) {\n if (!publicStorefrontToken && !overrideProps?.publicStorefrontToken) {\n throw new Error(\n `StorefrontClient: You did not pass in a 'publicStorefrontToken' while using 'getPublicTokenHeaders()'`\n );\n }\n\n const finalContentType =\n overrideProps?.contentType ?? contentType ?? 'json';\n\n return getPublicTokenHeadersRaw(\n finalContentType,\n storefrontApiVersion,\n overrideProps?.publicStorefrontToken ?? publicStorefrontToken ?? ''\n );\n },\n };\n}" + "value": "export function createStorefrontClient(\n props: StorefrontClientProps\n): StorefrontClientReturn {\n const {\n storeDomain,\n privateStorefrontToken,\n publicStorefrontToken,\n storefrontApiVersion,\n contentType,\n } = props;\n\n if (storefrontApiVersion !== SFAPI_VERSION) {\n warnOnce(\n `StorefrontClient: The Storefront API version that you're using is different than the version this build of Hydrogen React is targeting. You may run into unexpected errors if these versions don't match. Received verion: \"${storefrontApiVersion}\"; expected version \"${SFAPI_VERSION}\"`\n );\n }\n\n // only warn if not in a browser environment\n if (__HYDROGEN_DEV__ && !privateStorefrontToken && !globalThis.document) {\n warnOnce(\n `StorefrontClient: Using a private storefront token is recommended for server environments. Refer to the authentication https://shopify.dev/api/storefront#authentication documentation for more details.`\n );\n }\n\n // only warn if in a browser environment and you're using the privateStorefrontToken\n if (__HYDROGEN_DEV__ && privateStorefrontToken && globalThis) {\n warnOnce(\n `StorefrontClient: You are attempting to use a private token in an environment where it can be easily accessed by anyone. This is a security risk; please use the public token and the 'publicStorefrontToken' prop`\n );\n }\n\n return {\n getShopifyDomain(overrideProps): string {\n return overrideProps?.storeDomain ?? storeDomain;\n },\n getStorefrontApiUrl(overrideProps): string {\n const finalDomainUrl = overrideProps?.storeDomain ?? storeDomain;\n return `${finalDomainUrl}${finalDomainUrl.endsWith('/') ? '' : '/'}api/${\n overrideProps?.storefrontApiVersion ?? storefrontApiVersion\n }/graphql.json`;\n },\n getPrivateTokenHeaders(overrideProps): Record {\n if (!privateStorefrontToken && !overrideProps?.privateStorefrontToken) {\n throw new Error(\n `StorefrontClient: You did not pass in a 'privateStorefrontToken' while using 'getPrivateTokenHeaders()'`\n );\n }\n\n if (__HYDROGEN_DEV__ && !overrideProps?.buyerIp) {\n warnOnce(\n `StorefrontClient: it is recommended to pass in the 'buyerIp' property which improves analytics and data in the admin.`\n );\n }\n\n const finalContentType = overrideProps?.contentType ?? contentType;\n\n return {\n // default to json\n 'content-type':\n finalContentType === 'graphql'\n ? 'application/graphql'\n : 'application/json',\n 'X-SDK-Variant': 'hydrogen-react',\n 'X-SDK-Variant-Source': 'react',\n 'X-SDK-Version': storefrontApiVersion,\n 'Shopify-Storefront-Private-Token':\n overrideProps?.privateStorefrontToken ?? privateStorefrontToken ?? '',\n ...(overrideProps?.buyerIp\n ? {'Shopify-Storefront-Buyer-IP': overrideProps.buyerIp}\n : {}),\n };\n },\n getPublicTokenHeaders(overrideProps): Record {\n if (!publicStorefrontToken && !overrideProps?.publicStorefrontToken) {\n throw new Error(\n `StorefrontClient: You did not pass in a 'publicStorefrontToken' while using 'getPublicTokenHeaders()'`\n );\n }\n\n const finalContentType =\n overrideProps?.contentType ?? contentType ?? 'json';\n\n return getPublicTokenHeadersRaw(\n finalContentType,\n storefrontApiVersion,\n overrideProps?.publicStorefrontToken ?? publicStorefrontToken ?? ''\n );\n },\n };\n}" }, "StorefrontClientProps": { "filePath": "/storefront-client.ts", @@ -6560,10 +6685,17 @@ "returns": { "filePath": "/CartLineProvider.tsx", "description": "", - "name": "CartLine", - "value": "CartLine" + "name": "CartLinePartialDeep", + "value": "CartLinePartialDeep" }, - "value": "export function useCartLine(): CartLine {\n const context = useContext(CartLineContext);\n\n if (context == null) {\n throw new Error('Expected a cart line context but none was found');\n }\n\n return context;\n}" + "value": "export function useCartLine(): CartLinePartialDeep {\n const context = useContext(CartLineContext);\n\n if (context == null) {\n throw new Error('Expected a cart line context but none was found');\n }\n\n return context;\n}" + }, + "CartLinePartialDeep": { + "filePath": "/CartLineProvider.tsx", + "syntaxKind": "TypeAliasDeclaration", + "name": "CartLinePartialDeep", + "value": "PartialDeep", + "description": "" } } } @@ -6623,7 +6755,7 @@ "name": "UseMoneyValue", "value": "UseMoneyValue" }, - "value": "export function useMoney(money: MoneyV2): UseMoneyValue {\n const {countryIsoCode, languageIsoCode} = useShop();\n const locale = `${languageIsoCode}-${countryIsoCode}`;\n\n if (!locale) {\n throw new Error(\n `useMoney(): Unable to get 'locale' from 'useShop()', which means that 'locale' was not passed to ''. 'locale' is required for 'useMoney()' to work`\n );\n }\n\n const amount = parseFloat(money.amount);\n\n const options = useMemo(\n () => ({\n style: 'currency',\n currency: money.currencyCode,\n }),\n [money.currencyCode]\n );\n\n const defaultFormatter = useLazyFormatter(locale, options);\n\n const nameFormatter = useLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'name',\n });\n\n const narrowSymbolFormatter = useLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'narrowSymbol',\n });\n\n const withoutTrailingZerosFormatter = useLazyFormatter(locale, {\n ...options,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n });\n\n const withoutCurrencyFormatter = useLazyFormatter(locale);\n\n const withoutTrailingZerosOrCurrencyFormatter = useLazyFormatter(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n });\n\n const isPartCurrency = (part: Intl.NumberFormatPart) =>\n part.type === 'currency';\n\n // By wrapping these properties in functions, we only\n // create formatters if they are going to be used.\n const lazyFormatters = useMemo(\n () => ({\n original: () => money,\n currencyCode: () => money.currencyCode,\n\n localizedString: () => defaultFormatter().format(amount),\n\n parts: () => defaultFormatter().formatToParts(amount),\n\n withoutTrailingZeros: () =>\n amount % 1 === 0\n ? withoutTrailingZerosFormatter().format(amount)\n : defaultFormatter().format(amount),\n\n withoutTrailingZerosAndCurrency: () =>\n amount % 1 === 0\n ? withoutTrailingZerosOrCurrencyFormatter().format(amount)\n : withoutCurrencyFormatter().format(amount),\n\n currencyName: () =>\n nameFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"US dollars\"\n\n currencySymbol: () =>\n defaultFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"USD\"\n\n currencyNarrowSymbol: () =>\n narrowSymbolFormatter().formatToParts(amount).find(isPartCurrency)\n ?.value ?? '', // e.g. \"$\"\n\n amount: () =>\n defaultFormatter()\n .formatToParts(amount)\n .filter((part) =>\n ['decimal', 'fraction', 'group', 'integer', 'literal'].includes(\n part.type\n )\n )\n .map((part) => part.value)\n .join(''),\n }),\n [\n money,\n amount,\n nameFormatter,\n defaultFormatter,\n narrowSymbolFormatter,\n withoutCurrencyFormatter,\n withoutTrailingZerosFormatter,\n withoutTrailingZerosOrCurrencyFormatter,\n ]\n );\n\n // Call functions automatically when the properties are accessed\n // to keep these functions as an implementation detail.\n return useMemo(\n () =>\n new Proxy(lazyFormatters as unknown as UseMoneyValue, {\n get: (target, key) => Reflect.get(target, key)?.call(null),\n }),\n [lazyFormatters]\n );\n}" + "value": "export function useMoney(money: MoneyV2): UseMoneyValue {\n const {countryIsoCode, languageIsoCode} = useShop();\n const locale = `${languageIsoCode}-${countryIsoCode}`;\n\n if (!locale) {\n throw new Error(\n `useMoney(): Unable to get 'locale' from 'useShop()', which means that 'locale' was not passed to ''. 'locale' is required for 'useMoney()' to work`\n );\n }\n\n const amount = parseFloat(money.amount);\n\n const options = useMemo(\n () => ({\n style: 'currency',\n currency: money.currencyCode,\n }),\n [money.currencyCode]\n );\n\n const defaultFormatter = useLazyFormatter(locale, options);\n\n const nameFormatter = useLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'name',\n });\n\n const narrowSymbolFormatter = useLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'narrowSymbol',\n });\n\n const withoutTrailingZerosFormatter = useLazyFormatter(locale, {\n ...options,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n });\n\n const withoutCurrencyFormatter = useLazyFormatter(locale);\n\n const withoutTrailingZerosOrCurrencyFormatter = useLazyFormatter(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n });\n\n const isPartCurrency = (part: Intl.NumberFormatPart): boolean =>\n part.type === 'currency';\n\n // By wrapping these properties in functions, we only\n // create formatters if they are going to be used.\n const lazyFormatters = useMemo(\n () => ({\n original: () => money,\n currencyCode: () => money.currencyCode,\n\n localizedString: () => defaultFormatter().format(amount),\n\n parts: () => defaultFormatter().formatToParts(amount),\n\n withoutTrailingZeros: () =>\n amount % 1 === 0\n ? withoutTrailingZerosFormatter().format(amount)\n : defaultFormatter().format(amount),\n\n withoutTrailingZerosAndCurrency: () =>\n amount % 1 === 0\n ? withoutTrailingZerosOrCurrencyFormatter().format(amount)\n : withoutCurrencyFormatter().format(amount),\n\n currencyName: () =>\n nameFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"US dollars\"\n\n currencySymbol: () =>\n defaultFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"USD\"\n\n currencyNarrowSymbol: () =>\n narrowSymbolFormatter().formatToParts(amount).find(isPartCurrency)\n ?.value ?? '', // e.g. \"$\"\n\n amount: () =>\n defaultFormatter()\n .formatToParts(amount)\n .filter((part) =>\n ['decimal', 'fraction', 'group', 'integer', 'literal'].includes(\n part.type\n )\n )\n .map((part) => part.value)\n .join(''),\n }),\n [\n money,\n amount,\n nameFormatter,\n defaultFormatter,\n narrowSymbolFormatter,\n withoutCurrencyFormatter,\n withoutTrailingZerosFormatter,\n withoutTrailingZerosOrCurrencyFormatter,\n ]\n );\n\n // Call functions automatically when the properties are accessed\n // to keep these functions as an implementation detail.\n return useMemo(\n () =>\n new Proxy(lazyFormatters as unknown as UseMoneyValue, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call\n get: (target, key) => Reflect.get(target, key)?.call(null),\n }),\n [lazyFormatters]\n );\n}" }, "UseMoneyValue": { "filePath": "/useMoney.tsx", @@ -6960,6 +7092,7 @@ "name": "options", "description": "", "value": "UseShopifyCookiesOptions", + "isOptional": true, "filePath": "/useShopifyCookies.tsx" } ], diff --git a/packages/react/src/BuyNowButton.test.tsx b/packages/react/src/BuyNowButton.test.tsx index 5117a08add..10f58c9d78 100644 --- a/packages/react/src/BuyNowButton.test.tsx +++ b/packages/react/src/BuyNowButton.test.tsx @@ -3,24 +3,10 @@ import {render, screen} from '@testing-library/react'; import {vi} from 'vitest'; import userEvent from '@testing-library/user-event'; import {BuyNowButton} from './BuyNowButton.js'; +import {getCartWithActionsMock} from './CartProvider.test.helpers.js'; vi.mock('./CartProvider'); -const defaultCart = { - buyerIdentityUpdate: vi.fn(), - cartAttributesUpdate: vi.fn(), - cartCreate: vi.fn(), - cartFragment: '', - checkoutUrl: '', - discountCodesUpdate: vi.fn(), - linesAdd: vi.fn(), - linesRemove: vi.fn(), - linesUpdate: vi.fn(), - noteUpdate: vi.fn(), - status: 'idle' as const, - totalQuantity: 0, -}; - describe('', () => { it('renders a button', () => { render(Buy now, { @@ -59,10 +45,11 @@ describe('', () => { it('uses useCartCreateCallback with the correct arguments', async () => { const mockCartCreate = vi.fn(); - vi.mocked(useCart).mockImplementation(() => ({ - ...defaultCart, - cartCreate: mockCartCreate, - })); + vi.mocked(useCart).mockImplementation(() => + getCartWithActionsMock({ + cartCreate: mockCartCreate, + }) + ); const user = userEvent.setup(); @@ -133,10 +120,11 @@ describe('', () => { }); it('redirects to checkout', () => { - vi.mocked(useCart).mockImplementation(() => ({ - ...defaultCart, - checkoutUrl: '/checkout?id=123', - })); + vi.mocked(useCart).mockImplementation(() => + getCartWithActionsMock({ + checkoutUrl: '/checkout?id=123', + }) + ); render(Buy now, { wrapper: CartProvider, diff --git a/packages/react/src/CartLineProvider.test.tsx b/packages/react/src/CartLineProvider.test.tsx index 0e2f298719..3b37fb95e1 100644 --- a/packages/react/src/CartLineProvider.test.tsx +++ b/packages/react/src/CartLineProvider.test.tsx @@ -1,5 +1,4 @@ import {renderHook} from '@testing-library/react'; -import {CartLine} from './storefront-api-types.js'; import {useCartLine, CartLineProvider} from './CartLineProvider.js'; import {getCartLineMock} from './CartProvider.test.helpers.js'; @@ -8,9 +7,7 @@ it('provides a hook to access cart line data', () => { const {result} = renderHook(() => useCartLine(), { wrapper: ({children}) => ( - - {children} - + {children} ), }); diff --git a/packages/react/src/CartLineProvider.tsx b/packages/react/src/CartLineProvider.tsx index b035ef0c73..9e5bbe2d7a 100644 --- a/packages/react/src/CartLineProvider.tsx +++ b/packages/react/src/CartLineProvider.tsx @@ -1,12 +1,15 @@ import {useContext, createContext, type ReactNode} from 'react'; import type {CartLine} from './storefront-api-types.js'; +import type {PartialDeep} from 'type-fest'; -export const CartLineContext = createContext(null); +type CartLinePartialDeep = PartialDeep; + +export const CartLineContext = createContext(null); /** * The `useCartLine` hook provides access to the [CartLine object](https://shopify.dev/api/storefront/unstable/objects/cartline) from the Storefront API. It must be a descendent of a `CartProvider` component. */ -export function useCartLine(): CartLine { +export function useCartLine(): CartLinePartialDeep { const context = useContext(CartLineContext); if (context == null) { @@ -20,7 +23,7 @@ type CartLineProviderProps = { /** Any `ReactNode` elements. */ children: ReactNode; /** A cart line object. */ - line: CartLine; + line: CartLinePartialDeep; }; /** diff --git a/packages/react/src/CartLineQuantity.doc.ts b/packages/react/src/CartLineQuantity.doc.ts new file mode 100644 index 0000000000..da0ab0ada7 --- /dev/null +++ b/packages/react/src/CartLineQuantity.doc.ts @@ -0,0 +1,50 @@ +import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'; + +const data: ReferenceEntityTemplateSchema = { + name: 'CartLineQuantity', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'useCartLine', + type: 'gear', + url: '/api/hydrogen-react/hooks/useCartLine', + }, + { + name: 'CartLineQuantityAdjustButton', + type: 'component', + url: '/api/hydrogen-react/components/CartLineQuantityAdjustButton', + }, + ], + description: ` + The \`\` component renders a \`span\` (or another element / component that can be customized by the \`as\` prop) with the cart line's quantity.\n\nIt must be a descendent of a \`\` component, and uses the \`useCartLine()\` hook internally. + `, + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: './CartLineQuantity.example.jsx', + language: 'jsx', + }, + { + title: 'TypeScript', + code: './CartLineQuantity.example.tsx', + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + type: 'CartLineQuantityBaseProps', + description: '', + }, + ], +}; + +export default data; diff --git a/packages/react/src/CartLineQuantity.example.jsx b/packages/react/src/CartLineQuantity.example.jsx new file mode 100644 index 0000000000..5755b89283 --- /dev/null +++ b/packages/react/src/CartLineQuantity.example.jsx @@ -0,0 +1,9 @@ +import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react'; + +export function Example({line}) { + return ( + + + + ); +} diff --git a/packages/react/src/CartLineQuantity.example.tsx b/packages/react/src/CartLineQuantity.example.tsx new file mode 100644 index 0000000000..d9b5409d60 --- /dev/null +++ b/packages/react/src/CartLineQuantity.example.tsx @@ -0,0 +1,10 @@ +import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react'; +import type {CartLine} from '@shopify/hydrogen-react/storefront-api-types'; + +export function Example({line}: {line: CartLine}) { + return ( + + + + ); +} diff --git a/packages/react/src/CartLineQuantity.test.tsx b/packages/react/src/CartLineQuantity.test.tsx new file mode 100644 index 0000000000..dea7fbcd31 --- /dev/null +++ b/packages/react/src/CartLineQuantity.test.tsx @@ -0,0 +1,59 @@ +import {render, screen} from '@testing-library/react'; +import {CartLineProvider} from './CartLineProvider.js'; +import {CartLineQuantity} from './CartLineQuantity.js'; +import {CART_LINE} from './CartProvider.test.helpers.js'; + +describe('', () => { + it('displays the quantity', () => { + render( + + + + ); + + expect(screen.getByText(CART_LINE?.quantity ?? '')).toBeInTheDocument(); + }); + + it('allows a custom tag', () => { + render( + + + + ); + + const quantity = screen.getByText(CART_LINE?.quantity ?? ''); + + expect(quantity).toBeInTheDocument(); + expect(quantity.tagName).toBe('P'); + }); + + describe(`typescript validation`, () => { + it.skip(`validates props for a component passed to the 'as' prop`, () => { + expect.assertions(0); + render( + + + + ); + }); + + it.skip(`typescript validation: validates props for a component passed to the 'as' prop`, () => { + expect.assertions(0); + render( + + + + ); + }); + }); +}); + +const FAKECOMPONENTID = 'fake-component'; + +function FakeComponentWithRequiredProp({testing}: {testing: boolean}) { + return
{testing}
; +} diff --git a/packages/react/src/CartLineQuantity.tsx b/packages/react/src/CartLineQuantity.tsx new file mode 100644 index 0000000000..a25c88097a --- /dev/null +++ b/packages/react/src/CartLineQuantity.tsx @@ -0,0 +1,32 @@ +import type {ComponentPropsWithoutRef, ElementType} from 'react'; +import {useCartLine} from './CartLineProvider.js'; + +interface CartLineQuantityBaseProps< + ComponentGeneric extends ElementType = 'span' +> { + /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`. */ + as?: ComponentGeneric; +} + +export type CartLineQuantityProps = + CartLineQuantityBaseProps & + Omit< + ComponentPropsWithoutRef, + keyof CartLineQuantityBaseProps + >; + +/** + * The `` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity. + * + * It must be a descendent of a `` component, and uses the `useCartLine()` hook internally. + */ +export function CartLineQuantity( + props: CartLineQuantityProps +): JSX.Element { + const cartLine = useCartLine(); + const {as, ...passthroughProps} = props; + + const Wrapper = as ? as : 'span'; + + return {cartLine.quantity}; +} diff --git a/packages/react/src/CartLineQuantityAdjustButton.doc.ts b/packages/react/src/CartLineQuantityAdjustButton.doc.ts new file mode 100644 index 0000000000..a7a66cca23 --- /dev/null +++ b/packages/react/src/CartLineQuantityAdjustButton.doc.ts @@ -0,0 +1,50 @@ +import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'; + +const data: ReferenceEntityTemplateSchema = { + name: 'CartLineQuantityAdjustButton', + category: 'components', + isVisualComponent: false, + related: [ + { + name: 'useCartLine', + type: 'gear', + url: '/api/hydrogen-react/hooks/useCartLine', + }, + { + name: 'CartLineQuantity', + type: 'component', + url: '/api/hydrogen-react/components/CartLineQuantity', + }, + ], + description: ` + The \`\` component renders a \`span\` (or another element / component that can be customized by the \`as\` prop) with the cart line's quantity.\n\nIt must be a descendent of a \`\` component, and uses the \`useCartLine()\` hook internally. + `, + type: 'component', + defaultExample: { + description: 'I am the default example', + codeblock: { + tabs: [ + { + title: 'JavaScript', + code: './CartLineQuantityAdjustButton.example.jsx', + language: 'jsx', + }, + { + title: 'TypeScript', + code: './CartLineQuantityAdjustButton.example.tsx', + language: 'tsx', + }, + ], + title: 'Example code', + }, + }, + definitions: [ + { + title: 'Props', + type: 'CartLineQuantityAdjustButtonBaseProps', + description: '', + }, + ], +}; + +export default data; diff --git a/packages/react/src/CartLineQuantityAdjustButton.example.jsx b/packages/react/src/CartLineQuantityAdjustButton.example.jsx new file mode 100644 index 0000000000..10e6e09dec --- /dev/null +++ b/packages/react/src/CartLineQuantityAdjustButton.example.jsx @@ -0,0 +1,23 @@ +import { + CartLineQuantityAdjustButton, + CartLineProvider, + CartProvider, +} from '@shopify/hydrogen-react'; + +export function Example({line}) { + return ( + + + + Increase + + + Decrease + + + Remove + + + + ); +} diff --git a/packages/react/src/CartLineQuantityAdjustButton.example.tsx b/packages/react/src/CartLineQuantityAdjustButton.example.tsx new file mode 100644 index 0000000000..55ae3cdecb --- /dev/null +++ b/packages/react/src/CartLineQuantityAdjustButton.example.tsx @@ -0,0 +1,24 @@ +import { + CartLineQuantityAdjustButton, + CartLineProvider, + CartProvider, +} from '@shopify/hydrogen-react'; +import type {CartLine} from '@shopify/hydrogen-react/storefront-api-types'; + +export function Example({line}: {line: CartLine}) { + return ( + + + + Increase + + + Decrease + + + Remove + + + + ); +} diff --git a/packages/react/src/CartLineQuantityAdjustButton.test.tsx b/packages/react/src/CartLineQuantityAdjustButton.test.tsx new file mode 100644 index 0000000000..bd4c47cc9a --- /dev/null +++ b/packages/react/src/CartLineQuantityAdjustButton.test.tsx @@ -0,0 +1,198 @@ +import {render, screen} from '@testing-library/react'; +import {useCart, CartProvider} from './CartProvider.js'; +import { + CART_LINE, + CART_WITH_LINES_FLATTENED, + getCartWithActionsMock, +} from './CartProvider.test.helpers.js'; +import {CartLineProvider} from './CartLineProvider.js'; +import {CartLineQuantity} from './CartLineQuantity.js'; +import {CartLineQuantityAdjustButton} from './CartLineQuantityAdjustButton.js'; +import userEvent from '@testing-library/user-event'; + +vi.mock('./CartProvider'); + +describe('CartLineQuantityAdjustButton', () => { + it('increases quantity', async () => { + const linesUpdateMock = vi.fn(); + + vi.mocked(useCart).mockImplementation(() => + getCartWithActionsMock({ + linesUpdate: linesUpdateMock, + lines: [CART_LINE], + }) + ); + + const user = userEvent.setup(); + + render( + + + Increase + + , + { + wrapper: CartProvider, + } + ); + + expect(screen.getByTestId(QUANTITY_TEST_ID)).toHaveTextContent( + (CART_LINE?.quantity ?? 0).toString() + ); + + await user.click(screen.getByRole('button')); + + expect(linesUpdateMock).toHaveBeenCalledWith([ + { + id: CART_LINE.id, + quantity: 2, + attributes: [ + { + key: 'color', + value: 'red', + }, + ], + }, + ]); + }); + + it('decreases quantity when quantity >= 2', async () => { + const linesUpdateMock = vi.fn(); + const user = userEvent.setup(); + + const tempCartLine = { + ...CART_WITH_LINES_FLATTENED['lines'][0], + quantity: 2, + }; + + vi.mocked(useCart).mockImplementation(() => + getCartWithActionsMock({ + linesUpdate: linesUpdateMock, + lines: [tempCartLine], + }) + ); + + render( + + + Increase + + , + { + wrapper: CartProvider, + } + ); + + expect(screen.getByTestId(QUANTITY_TEST_ID)).toHaveTextContent( + (tempCartLine?.quantity ?? 0).toString() + ); + + await user.click(screen.getByRole('button')); + + expect(linesUpdateMock).toHaveBeenCalledWith([ + { + id: CART_LINE.id, + quantity: 1, + attributes: [ + { + key: 'color', + value: 'red', + }, + ], + }, + ]); + }); + + it('decreases quantity and removes the line when quantity === 1', async () => { + const linesRemoveMock = vi.fn(); + const user = userEvent.setup(); + + const tempCartLine = { + ...CART_WITH_LINES_FLATTENED['lines'][0], + quantity: 1, + }; + + vi.mocked(useCart).mockImplementation(() => + getCartWithActionsMock({ + linesRemove: linesRemoveMock, + lines: [tempCartLine], + }) + ); + + render( + + + Increase + + , + { + wrapper: CartProvider, + } + ); + + expect(screen.getByTestId(QUANTITY_TEST_ID)).toHaveTextContent( + (tempCartLine?.quantity ?? 0).toString() + ); + + await user.click(screen.getByRole('button')); + + expect(linesRemoveMock).toHaveBeenCalledWith([CART_LINE.id]); + }); + + it('removes the line', async () => { + const linesRemoveMock = vi.fn(); + const user = userEvent.setup(); + + vi.mocked(useCart).mockImplementation(() => + getCartWithActionsMock({ + linesRemove: linesRemoveMock, + lines: [CART_LINE], + }) + ); + + render( + + + Increase + + , + { + wrapper: CartProvider, + } + ); + + expect(screen.getByTestId(QUANTITY_TEST_ID)).toHaveTextContent( + (CART_LINE?.quantity ?? 0).toString() + ); + + await user.click(screen.getByRole('button')); + + expect(linesRemoveMock).toHaveBeenCalledWith([CART_LINE.id]); + }); +}); + +const QUANTITY_TEST_ID = 'quantity'; + +function Cart({children}: {children: React.ReactNode}) { + const {lines} = useCart(); + + if (!lines) { + throw new Error('No lines found in cart.'); + } + + return ( +
    + {lines.map((line) => { + if (!line) throw new Error('no line'); + return ( +
  • + + + {children} + +
  • + ); + })} +
+ ); +} diff --git a/packages/react/src/CartLineQuantityAdjustButton.tsx b/packages/react/src/CartLineQuantityAdjustButton.tsx new file mode 100644 index 0000000000..76abea3d8a --- /dev/null +++ b/packages/react/src/CartLineQuantityAdjustButton.tsx @@ -0,0 +1,75 @@ +import {useCallback} from 'react'; +import {useCart} from './CartProvider.js'; +import {useCartLine} from './CartLineProvider.js'; +import {BaseButton, type BaseButtonProps} from './BaseButton.js'; +import type {CartLineUpdateInput} from './storefront-api-types.js'; + +interface CartLineQuantityAdjustButtonBaseProps { + /** The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`. */ + adjust?: 'increase' | 'decrease' | 'remove'; +} + +type CartLineQuantityAdjustButtonProps< + AsType extends React.ElementType = 'button' +> = BaseButtonProps & CartLineQuantityAdjustButtonBaseProps; + +/** + * The `` component renders a button that adjusts the cart line's quantity when pressed. + * + * It must be a descendent of `` and ``. + */ +export function CartLineQuantityAdjustButton< + AsType extends React.ElementType = 'button' +>(props: CartLineQuantityAdjustButtonProps): JSX.Element { + const {status, linesRemove, linesUpdate} = useCart(); + const cartLine = useCartLine(); + const {children, adjust, onClick, ...passthroughProps} = props; + + const handleAdjust = useCallback(() => { + if (adjust === 'remove') { + linesRemove([cartLine?.id ?? '']); + return; + } + + const quantity = + adjust === 'decrease' + ? (cartLine?.quantity ?? 0) - 1 + : (cartLine?.quantity ?? 0) + 1; + + if (quantity <= 0) { + linesRemove([cartLine?.id ?? '']); + return; + } + + const lineUpdate = { + id: cartLine?.id ?? '', + quantity, + attributes: (cartLine?.attributes ?? + []) as CartLineUpdateInput['attributes'], + } satisfies CartLineUpdateInput; + + linesUpdate([lineUpdate]); + }, [ + adjust, + cartLine?.attributes, + cartLine?.id, + cartLine?.quantity, + linesRemove, + linesUpdate, + ]); + + return ( + + {children} + + ); +} diff --git a/packages/react/src/CartProvider.test.helpers.ts b/packages/react/src/CartProvider.test.helpers.ts index 05e2162b73..e22b429d9b 100644 --- a/packages/react/src/CartProvider.test.helpers.ts +++ b/packages/react/src/CartProvider.test.helpers.ts @@ -6,6 +6,7 @@ import type { CartLineConnection, } from './storefront-api-types.js'; import type {PartialDeep} from 'type-fest'; +import type {CartWithActions} from './cart-types.js'; export const CART_LINE: PartialDeep = { attributes: [{key: 'color', value: 'red'}], @@ -117,6 +118,26 @@ export function getCartLinesMock( } as CartLineConnection; } +export function getCartWithActionsMock( + mockOptions?: PartialDeep +): CartWithActions { + return { + buyerIdentityUpdate: vi.fn(), + cartAttributesUpdate: vi.fn(), + cartCreate: vi.fn(), + cartFragment: '', + checkoutUrl: '', + discountCodesUpdate: vi.fn(), + linesAdd: vi.fn(), + linesRemove: vi.fn(), + linesUpdate: vi.fn(), + noteUpdate: vi.fn(), + status: 'idle' as const, + totalQuantity: 0, + ...mockOptions, + }; +} + /** * Performs a deep merge of `source` into `target`. * creating a new object. diff --git a/packages/react/src/Video.test.tsx b/packages/react/src/Video.test.tsx index fc987b7c41..1d5a79c379 100644 --- a/packages/react/src/Video.test.tsx +++ b/packages/react/src/Video.test.tsx @@ -21,7 +21,7 @@ const VIDEO_PROPS = { describe('