@fanfare-io/fanfare-sdk-react 0.17.0 → 0.18.0

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 CHANGED
@@ -44,6 +44,72 @@ Customization has four levels of increasing specificity: `theme`, `variant`, `sl
44
44
 
45
45
  `slots` override targeted states while keeping the default widget flow. `children` receives `{ journey, view, snapshot, error, start, isStarting }` when you want to render the full state yourself.
46
46
 
47
+ ## Selection
48
+
49
+ Some experiences capture which product and variant a consumer gets. `ExperienceWidget` renders that
50
+ picker on its own, and the two building blocks behind it are exported so you can place them
51
+ yourself:
52
+
53
+ - `ProductSelector` — loads a catalogue through its `loadOptions` callback, renders the product
54
+ rows, the chosen product's option chips and the resolved variant, and reports a complete
55
+ `{ productId, variantId }` pair through `onSelectionChange`. Anything less than a complete pair
56
+ reports `null`. It never submits a selection itself.
57
+ - `SelectionSummary` — a presentational read-out of a pick that has already been captured, with an
58
+ optional change affordance and the locked and unavailable copy.
59
+
60
+ Prices arrive from the catalogue as exact strings and render in the widget's locale and the
61
+ catalogue's `currencyCode`: on a product row when every variant of that product agrees on one
62
+ amount, and on the resolved variant's own line beneath the chips. The formatter is internal — the
63
+ public surface is exactly these two components plus their prop and handle types.
64
+
65
+ Selection is configured per sequence, under either checkout arrangement. With merchant
66
+ (`external`) checkout the widget records the pick and hands it to `onCheckoutClick` for the host to
67
+ cart; with Fanfare (`internal`) checkout the granted panel continues into Fanfare's own checkout
68
+ instead, under both checkout modes, `at_end` and `pre_auth`. Entry-time picking (choosing before you
69
+ enter) is not yet available.
70
+
71
+ `onCheckoutClick?: (details?: SelectionCheckoutDetails) => void | Promise<void>` carries the pick
72
+ the grant settled on — `SelectionCheckoutDetails` is a root export and reads
73
+ `{ selection, product, variant, currencyCode }`, the catalogue objects behind the pair so a host can
74
+ map it onto its own catalogue by handle, SKU or option values. The argument is optional: a grant
75
+ that settles without the widget having loaded the catalogue passes none, so a `() => void` handler
76
+ still assigns and a `(details) => …` handler has to handle `details === undefined`.
77
+ `GrantedPanelProps.onCtaClick` takes the same shape. Returning a promise is supported — a rejection
78
+ returns the consumer to the checkout CTA with SDK-owned inline copy and reports the cause through
79
+ `onError`, so a hand-off that never happened is never shown as started.
80
+
81
+ Four integration tiers, in increasing specificity:
82
+
83
+ 1. **Default.** `ExperienceWidget` renders the modules, and the selection UI appears wherever the
84
+ experience captures a pick.
85
+ 2. **Slot override, keeping our pieces.** Override the `enterable`, `participating` or `granted`
86
+ slot with a render function and compose `ProductSelector`, `SelectionSummary` and `GrantedPanel`
87
+ directly. The selection methods live on the sequence the slot hands you as `sequence`, not on
88
+ `view` — `sequence.loadSelectionOptions()` fetches the catalogue and `sequence.select(choice)`
89
+ confirms. Not every arm of the sequence union carries them, so narrow with
90
+ `"loadSelectionOptions" in sequence` first. The view is rebuilt on every journey emission, so
91
+ `sequence.loadSelectionOptions` is a different function each time and passing it straight to
92
+ `loadOptions` re-fetches the catalogue; so is an arrow written inline in the slot function.
93
+ Render the slot through a named component (`granted: (props) => <GrantedSlot {...props} />`) and
94
+ hold a `useCallback` loader there that reads the journey's current sequence when it is called —
95
+ the widget wires its own picker exactly that way.
96
+ 3. **Standalone on your own page.** Import `ProductSelector` / `SelectionSummary` from the package
97
+ root and render them anywhere, driven by `useExperienceJourney`'s view. Both are self-contained
98
+ blocks themed through the ambient `--ff-*` variables and `ThemeProvider`, like every other
99
+ exported component.
100
+ 4. **Fully custom picker.** Render anything you like, then still call `view.sequence.select(choice)`
101
+ for a granted pick, or pass `selection` to `enter` / `bid` for an entry pick. `resolveVariantFromOptions`
102
+ and `isOptionValueSelectable` are importable from `@fanfare-io/fanfare-sdk-core/selections`, so a
103
+ custom UI resolves a choice exactly as the server does.
104
+
105
+ The row and option-grid units inside `ProductSelector` are internal; a host that wants different
106
+ row rendering uses tier 4.
107
+
108
+ `loadOptions` must be referentially stable — a new function identity is a new loader and re-fetches
109
+ the catalogue, so wrap it in `useCallback` or hoist it out of the render. `ProductSelector` forwards
110
+ a `ProductSelectorHandle` (`focus()`, `refresh()`) through `ref`; `refresh()` is what a host calls
111
+ after a sold-out failure.
112
+
47
113
  ## Error handling
48
114
 
49
115
  The widget classifies every failure into one of four **dispositions** and surfaces it accordingly, so a recoverable error stays in context instead of replacing the screen:
@@ -1,30 +1,6 @@
1
- /**
2
- * GrantedPanel
3
- *
4
- * Shared granted composition for distribution modules. Renders the
5
- * winning-state panel: status badge, expiry countdown (or fallback message
6
- * when no expiry is known), and — only when the caller supplies a
7
- * destination — a checkout CTA. Used by queue / draw / auction /
8
- * timed-release modules — the JSX is structurally identical across all
9
- * four; only the i18n strings differ.
10
- *
11
- * A granted frame with no `onCtaClick` renders no button: the grant is
12
- * already settled where the consumer stands, so an inert affordance would
13
- * promise a hand-off that does not exist.
14
- *
15
- * <GrantedPanel
16
- * label="Congratulations!"
17
- * fallbackMessage="Proceed to checkout"
18
- * ctaLabel="Continue"
19
- * expiresAt={grantExpiry}
20
- * onCtaClick={openCheckout}
21
- * />
22
- *
23
- * `useGrantCelebration` fires the celebration animation against the
24
- * hero region — must be invoked unconditionally per React hook rules,
25
- * which is why this lives in its own component (each module's granted
26
- * branch returns `<GrantedPanel ... />` instead of inlining the JSX).
27
- */
1
+ import { ClaimableSettlement, PreAuthSettlement } from '@fanfare-io/fanfare-sdk-core/experiences';
2
+ import { GrantedSelection, SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
3
+ import { SelectionCheckoutDetails, SelectionDisplay } from '../../lib/selection-display';
28
4
  import * as React from "react";
29
5
  export interface GrantedPanelProps {
30
6
  label: React.ReactNode;
@@ -37,10 +13,57 @@ export interface GrantedPanelProps {
37
13
  * payment countdown states what the consumer owes, so a synthesised one is worse than none.
38
14
  */
39
15
  detail?: React.ReactNode;
40
- onCtaClick?: () => void;
16
+ /**
17
+ * Activating the checkout CTA. Receives the pick the grant settled on, with the catalogue
18
+ * objects behind it, whenever the panel has loaded the catalogue that carries them.
19
+ */
20
+ onCtaClick?: (details?: SelectionCheckoutDetails) => void | Promise<void>;
41
21
  className?: string;
22
+ /** Granted-state selection: drives the pick, reveal, wait and summary states. */
23
+ selection?: GrantedSelection | null;
24
+ /**
25
+ * How the win settles. Selects the confirm label and which affordance sits beneath a resolved
26
+ * summary; absent leaves the CTA exactly as the caller wired it.
27
+ */
28
+ settlement?: ClaimableSettlement | PreAuthSettlement;
29
+ /**
30
+ * Parent binds the view's option loader; fetch-on-open only. Must be referentially stable — a
31
+ * new identity re-fetches the catalogue.
32
+ */
33
+ loadSelectionOptions?: () => Promise<SelectionOptions>;
34
+ /**
35
+ * Parent binds the view's selection submit; resolves when the pin is written and rejects on
36
+ * failure — the panel keys its pick UI on settle and shows the failure through `selectError`.
37
+ *
38
+ * The promise must resolve only once the journey atom carries the new selection: the pick
39
+ * surface collapses on the `selection` prop flip, so a promise that settles ahead of the atom
40
+ * re-enables the confirm control over a pick that has already been taken.
41
+ */
42
+ onConfirmSelection?: (choice: SelectionChoice) => Promise<void>;
43
+ /**
44
+ * Reports every local pick-surface transition, product-only drafts included, so the widget can
45
+ * emit the page-bridge selection-change event; `onConfirmSelection` stays the only settled-pair
46
+ * channel.
47
+ */
48
+ onDraftChange?: (draft: SelectionDraft) => void;
49
+ /** Heading copy for the pick state; the generic selection title stands when absent. */
50
+ selectionTitle?: React.ReactNode;
51
+ /** Helper copy under the pick heading; a reveal supplies its own prompt when absent. */
52
+ selectionDescription?: React.ReactNode;
53
+ /**
54
+ * Display strings for the resolved summary. Absent on a consumer-chosen resolution renders no
55
+ * summary at all: a locked line with no item detail states less than nothing.
56
+ */
57
+ selectionDisplay?: SelectionDisplay | null;
58
+ /**
59
+ * Locked copy for a resolved summary; modules pass "checkout" once checkout has started. Passing
60
+ * it also freezes the pick: the summary reads out locked with this reason and offers no change.
61
+ */
62
+ selectionLockedReason?: "checkout" | "final";
63
+ /** Inline error from the parent's selection submit. */
64
+ selectError?: string | null;
42
65
  }
43
- export declare function GrantedPanel({ label, fallbackMessage, ctaLabel, expiresAt, detail, onCtaClick, className, }: GrantedPanelProps): import("react/jsx-runtime").JSX.Element;
66
+ export declare function GrantedPanel({ label, fallbackMessage, ctaLabel, expiresAt, detail, onCtaClick, className, selection, settlement, loadSelectionOptions, onConfirmSelection, onDraftChange, selectionTitle, selectionDescription, selectionDisplay, selectionLockedReason, selectError, }: GrantedPanelProps): import("react/jsx-runtime").JSX.Element;
44
67
  export declare namespace GrantedPanel {
45
68
  var displayName: string;
46
69
  }
@@ -1,4 +1,24 @@
1
1
  import { OutcomeReason } from '../module';
2
+ /**
3
+ * OutcomePanel
4
+ *
5
+ * Shared outcome composition for distribution modules. Renders a status
6
+ * panel showing why the user can't proceed, with an optional re-enter CTA
7
+ * for expired admission grants. Used by queue / draw / auction /
8
+ * timed-release modules — the JSX is structurally identical across all four.
9
+ *
10
+ * <OutcomePanel
11
+ * outcomeReason="expired"
12
+ * onReenter={handleReenter}
13
+ * isReentering={false}
14
+ * />
15
+ *
16
+ * Each outcome reason renders its own heading + body via `OUTCOME_COPY`;
17
+ * `expired` additionally shows a mechanism-specific re-enter CTA. A completed
18
+ * purchase renders success copy, a non-selection its own copy, and unknown
19
+ * terminals fall back to the generic "ended" panel.
20
+ */
21
+ import type * as React from "react";
2
22
  /**
3
23
  * The distribution mechanism a module owns. Drives mechanism-specific copy
4
24
  * (e.g. the expired re-enter button label) so each module shows terminology
@@ -25,9 +45,18 @@ export interface OutcomePanelProps {
25
45
  isReentering?: boolean;
26
46
  /** Secondary action offered once the outcome is final. */
27
47
  action?: OutcomeAction;
48
+ /**
49
+ * Already-translated copy replacing the reason's own. A terminal the caller knows more about
50
+ * than the taxonomy does — a drop that ended because the items ran out — says that instead of
51
+ * the generic line, and supplies both halves or neither.
52
+ */
53
+ copyOverride?: {
54
+ title: React.ReactNode;
55
+ body: React.ReactNode;
56
+ };
28
57
  className?: string;
29
58
  }
30
- export declare function OutcomePanel({ outcomeReason, participationType, onReenter, isReentering, action, className, }: OutcomePanelProps): import("react/jsx-runtime").JSX.Element;
59
+ export declare function OutcomePanel({ outcomeReason, participationType, onReenter, isReentering, action, copyOverride, className, }: OutcomePanelProps): import("react/jsx-runtime").JSX.Element;
31
60
  export declare namespace OutcomePanel {
32
61
  var displayName: string;
33
62
  }
@@ -1,6 +1,7 @@
1
1
  import { BotMitigationState, RoutingChallenge } from '@fanfare-io/fanfare-sdk-core/challenges';
2
2
  import { SDKEvents } from '@fanfare-io/fanfare-sdk-core/events';
3
3
  import { CheckoutNextAction, FanfareCheckoutResult, JourneyHandle, JourneySnapshot, JourneyView, PaymentInput, PaymentMethodSummary, SequenceView } from '@fanfare-io/fanfare-sdk-core/experiences';
4
+ import { SelectionCheckoutDetails } from '../../lib/selection-display';
4
5
  import { BrandTheme, WidgetVariant } from '../../theme';
5
6
  import { AuthInputMode } from '../auth/auth-input';
6
7
  import { OutcomeAction } from '../compositions/outcome-panel';
@@ -194,6 +195,13 @@ export interface ExperienceWidgetProps {
194
195
  children?: (props: ExperienceRenderProps) => React.ReactNode;
195
196
  /** Checkout URL for granted state */
196
197
  checkoutUrl?: string;
198
+ /**
199
+ * Called when the consumer activates the granted checkout CTA, with the pick the grant settled
200
+ * on and the catalogue objects behind it whenever the widget has loaded them. Takes precedence
201
+ * over `checkoutUrl`. A rejected promise returns the consumer to the CTA with generic inline
202
+ * copy, so a hand-off that never happened is not reported as started.
203
+ */
204
+ onCheckoutClick?: (details?: SelectionCheckoutDetails) => void | Promise<void>;
197
205
  /**
198
206
  * Action offered on terminal panels (not selected, sold out, closed), such as
199
207
  * a link back to what is still available. Omit to show none.
@@ -1,7 +1,9 @@
1
1
  import { AuctionBidInput } from '@fanfare-io/fanfare-sdk-core/auctions';
2
- import { AuctionConsumerStatus } from '@fanfare-io/fanfare-sdk-core/experiences';
2
+ import { AuctionConsumerStatus, ClaimableSettlement, PreAuthSettlement } from '@fanfare-io/fanfare-sdk-core/experiences';
3
3
  import { AuctionDisplayState } from '@fanfare-io/fanfare-sdk-core/internals';
4
+ import { GrantedSelection, SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
4
5
  import { ReadableAtom } from 'nanostores';
6
+ import { SelectionCheckoutDetails } from '../../../lib/selection-display';
5
7
  import { OutcomeAction } from '../../compositions/outcome-panel';
6
8
  import { OutcomeReason } from '../../module';
7
9
  export interface AuctionModuleProps {
@@ -28,7 +30,32 @@ export interface AuctionModuleProps {
28
30
  */
29
31
  auctionType?: "english" | "dutch";
30
32
  checkoutUrl?: string;
31
- onCheckoutClick?: () => void;
33
+ /** Override checkout click handler (granted); carries the pair the grant resolved to. */
34
+ /**
35
+ * Activating the checkout CTA. Receives the pick the grant settled on, with the catalogue
36
+ * objects behind it, whenever the panel has loaded the catalogue that carries them.
37
+ */
38
+ onCheckoutClick?: (details?: SelectionCheckoutDetails) => void | Promise<void>;
39
+ /** The auction captures a product choice with the bid, so the bid surface renders the picker. */
40
+ selectionRequired?: boolean;
41
+ /** Bound by the widget against the journey; must be referentially stable. */
42
+ loadSelectionOptions?: () => Promise<SelectionOptions>;
43
+ /** The pick a standing English bid is already for; it cannot be changed under that bid. */
44
+ lockedSelection?: SelectionChoice | null;
45
+ /** Granted-state selection arm; drives the panel's pick, reveal and summary states. */
46
+ grantedSelection?: GrantedSelection;
47
+ /** How the win settles; selects the granted confirm label and payment affordance. */
48
+ settlement?: ClaimableSettlement | PreAuthSettlement;
49
+ /** Submits the granted pick or re-pick. */
50
+ onSelect?: (choice: SelectionChoice) => Promise<void>;
51
+ /** Locked copy for a resolved granted summary; "checkout" once checkout has started. */
52
+ selectionLockedReason?: "checkout" | "final";
53
+ /** Already-translated selection failure from the widget, rendered beside the bid controls. */
54
+ selectError?: string | null;
55
+ /** Every local pick-surface transition, product-only drafts included; feeds the page-bridge event. */
56
+ onDraftChange?: (draft: SelectionDraft) => void;
57
+ /** The terminal was reached because the stock ran out, not because this bidder lost. */
58
+ stockDenied?: boolean;
32
59
  outcomeReason?: OutcomeReason;
33
60
  /** Host-provided action shown on the terminal panel. */
34
61
  outcomeAction?: OutcomeAction;
@@ -1,5 +1,8 @@
1
+ import { ClaimableSettlement, PreAuthSettlement } from '@fanfare-io/fanfare-sdk-core/experiences';
1
2
  import { DrawDisplayState } from '@fanfare-io/fanfare-sdk-core/internals';
3
+ import { GrantedSelection, SelectionAvailability, SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
2
4
  import { ReadableAtom } from 'nanostores';
5
+ import { SelectionCheckoutDetails } from '../../../lib/selection-display';
3
6
  import { OutcomeAction } from '../../compositions/outcome-panel';
4
7
  import { OutcomeReason } from '../../module';
5
8
  export interface DrawModuleProps {
@@ -11,8 +14,8 @@ export interface DrawModuleProps {
11
14
  canEnter: boolean;
12
15
  /** Whether user can leave (participating only) */
13
16
  canLeave: boolean;
14
- /** Enter action handler */
15
- onEnter: () => Promise<void>;
17
+ /** Enter action handler; carries the entry pick on a selection-bearing draw. */
18
+ onEnter: (selection?: SelectionChoice) => Promise<void>;
16
19
  /** Leave action handler */
17
20
  onLeave: () => Promise<void>;
18
21
  /** Whether entering is in progress */
@@ -27,8 +30,36 @@ export interface DrawModuleProps {
27
30
  expiresAt?: number | Date;
28
31
  /** Checkout URL — opens in a new tab when CTA clicked (granted) */
29
32
  checkoutUrl?: string;
30
- /** Override checkout click handler (granted) */
31
- onCheckoutClick?: () => void;
33
+ /** Override checkout click handler (granted); carries the pair the grant resolved to. */
34
+ /**
35
+ * Activating the checkout CTA. Receives the pick the grant settled on, with the catalogue
36
+ * objects behind it, whenever the panel has loaded the catalogue that carries them.
37
+ */
38
+ onCheckoutClick?: (details?: SelectionCheckoutDetails) => void | Promise<void>;
39
+ /** The draw captures a product choice at entry, so the entry branch renders the picker. */
40
+ selectionRequired?: boolean;
41
+ /** Bound by the widget against the journey; must be referentially stable. */
42
+ loadSelectionOptions?: () => Promise<SelectionOptions>;
43
+ /** The choice already recorded for this entry, shown while the consumer waits. */
44
+ recordedSelection?: SelectionChoice | null;
45
+ /** Live availability of the recorded pick, so a sold-out choice is said rather than discovered. */
46
+ selectionAvailability?: SelectionAvailability;
47
+ /** Submits a changed choice while waiting. */
48
+ onSelectionChange?: (choice: SelectionChoice) => Promise<void>;
49
+ /** Granted-state selection arm; drives the panel's pick, reveal and summary states. */
50
+ grantedSelection?: GrantedSelection;
51
+ /** How the win settles; selects the granted confirm label and payment affordance. */
52
+ settlement?: ClaimableSettlement | PreAuthSettlement;
53
+ /** Submits the granted pick or re-pick. */
54
+ onSelect?: (choice: SelectionChoice) => Promise<void>;
55
+ /** Locked copy for a resolved granted summary; "checkout" once checkout has started. */
56
+ selectionLockedReason?: "checkout" | "final";
57
+ /** Already-translated selection failure from the widget, rendered beside the acting control. */
58
+ selectError?: string | null;
59
+ /** Every local pick-surface transition, product-only drafts included; feeds the page-bridge event. */
60
+ onDraftChange?: (draft: SelectionDraft) => void;
61
+ /** The terminal was reached because the stock ran out, not because this entrant lost. */
62
+ stockDenied?: boolean;
32
63
  /** Denial reason (denied) */
33
64
  outcomeReason?: OutcomeReason;
34
65
  /** Host-provided action shown on the terminal panel. */
@@ -40,7 +71,7 @@ export interface DrawModuleProps {
40
71
  /** Additional class name */
41
72
  className?: string;
42
73
  }
43
- export declare function DrawModule({ display$, sequencePhase, canEnter, canLeave, onEnter, onLeave, isEntering, isLeaving, countdownTo, grant: _grant, expiresAt, checkoutUrl, onCheckoutClick, outcomeReason, outcomeAction, onReenter, isReentering, className, }: DrawModuleProps): import("react/jsx-runtime").JSX.Element | null;
74
+ export declare function DrawModule({ display$, sequencePhase, canEnter, canLeave, onEnter, onLeave, isEntering, isLeaving, countdownTo, grant: _grant, expiresAt, checkoutUrl, onCheckoutClick, outcomeReason, outcomeAction, onReenter, isReentering, className, selectionRequired, loadSelectionOptions, recordedSelection, selectionAvailability, onSelectionChange, grantedSelection, settlement, onSelect, selectionLockedReason, selectError, stockDenied, onDraftChange, }: DrawModuleProps): import("react/jsx-runtime").JSX.Element | null;
44
75
  export declare namespace DrawModule {
45
76
  var displayName: string;
46
77
  }
@@ -10,6 +10,8 @@ export { DrawModule, type DrawModuleProps } from './draw-module';
10
10
  export { QueueModule, type QueueModuleProps } from './queue-module';
11
11
  export { TimedReleaseModule, type TimedReleaseModuleProps } from './timed-release-module';
12
12
  export { UpcomingModule, type UpcomingModuleProps } from './upcoming-module';
13
+ export { ProductSelector, SelectionSummary } from './selection';
14
+ export type { ProductSelectorHandle, ProductSelectorProps, SelectionLockedReason, SelectionSummaryProps, } from './selection';
13
15
  export { LoadingView, type LoadingViewProps } from './loading-view';
14
16
  export { StartView, type StartViewProps } from './start-view';
15
17
  export { WaitlistView, type WaitlistViewProps } from './waitlist-view';
@@ -1,5 +1,8 @@
1
+ import { ClaimableSettlement, PreAuthSettlement } from '@fanfare-io/fanfare-sdk-core/experiences';
1
2
  import { QueueDisplayState } from '@fanfare-io/fanfare-sdk-core/internals';
3
+ import { GrantedSelection, SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
2
4
  import { ReadableAtom } from 'nanostores';
5
+ import { SelectionCheckoutDetails } from '../../../lib/selection-display';
3
6
  import { OutcomeAction } from '../../compositions/outcome-panel';
4
7
  import { OutcomeReason } from '../../module';
5
8
  export interface QueueModuleProps {
@@ -16,7 +19,30 @@ export interface QueueModuleProps {
16
19
  grant?: string;
17
20
  expiresAt?: number | Date;
18
21
  checkoutUrl?: string;
19
- onCheckoutClick?: () => void;
22
+ /** Override checkout click handler (granted); carries the pair the grant resolved to. */
23
+ /**
24
+ * Activating the checkout CTA. Receives the pick the grant settled on, with the catalogue
25
+ * objects behind it, whenever the panel has loaded the catalogue that carries them.
26
+ */
27
+ onCheckoutClick?: (details?: SelectionCheckoutDetails) => void | Promise<void>;
28
+ /** Admissions are paused upstream; the waiting line says so rather than looking stalled. */
29
+ admissionsPaused?: boolean;
30
+ /** Bound by the widget against the journey; must be referentially stable. */
31
+ loadSelectionOptions?: () => Promise<SelectionOptions>;
32
+ /** Granted-state selection arm; drives the panel's pick, reveal and summary states. */
33
+ grantedSelection?: GrantedSelection;
34
+ /** How the win settles; selects the granted confirm label and payment affordance. */
35
+ settlement?: ClaimableSettlement | PreAuthSettlement;
36
+ /** Submits the granted pick or re-pick. */
37
+ onSelect?: (choice: SelectionChoice) => Promise<void>;
38
+ /** Locked copy for a resolved granted summary; "checkout" once checkout has started. */
39
+ selectionLockedReason?: "checkout" | "final";
40
+ /** Already-translated selection failure from the widget, rendered beside the acting control. */
41
+ selectError?: string | null;
42
+ /** Every local pick-surface transition, product-only drafts included; feeds the page-bridge event. */
43
+ onDraftChange?: (draft: SelectionDraft) => void;
44
+ /** The terminal was reached because the stock ran out, not because this entrant lost. */
45
+ stockDenied?: boolean;
20
46
  outcomeReason?: OutcomeReason;
21
47
  /** Host-provided action shown on the terminal panel. */
22
48
  outcomeAction?: OutcomeAction;
@@ -24,7 +50,7 @@ export interface QueueModuleProps {
24
50
  isReentering?: boolean;
25
51
  className?: string;
26
52
  }
27
- export declare function QueueModule({ display$, sequencePhase, canEnter, canLeave, onEnter, onLeave, isEntering, isLeaving, closeAt, grant: _grant, expiresAt, checkoutUrl, onCheckoutClick, outcomeReason, outcomeAction, onReenter, isReentering, className, }: QueueModuleProps): import("react/jsx-runtime").JSX.Element | null;
53
+ export declare function QueueModule({ display$, sequencePhase, canEnter, canLeave, onEnter, onLeave, isEntering, isLeaving, closeAt, grant: _grant, expiresAt, checkoutUrl, onCheckoutClick, outcomeReason, outcomeAction, onReenter, isReentering, className, admissionsPaused, loadSelectionOptions, grantedSelection, settlement, onSelect, selectionLockedReason, selectError, stockDenied, onDraftChange, }: QueueModuleProps): import("react/jsx-runtime").JSX.Element | null;
28
54
  export declare namespace QueueModule {
29
55
  var displayName: string;
30
56
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Selection
3
+ *
4
+ * The product picker family and the read-out of a captured pick. `ProductSelectorRow`
5
+ * and `VariantRunGrid` are rendering units of `ProductSelector` alone and stay inside
6
+ * this directory.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export { ProductSelector } from './product-selector';
11
+ export type { ProductSelectorHandle, ProductSelectorProps } from './product-selector';
12
+ export { SelectionSummary } from './selection-summary';
13
+ export type { SelectionLockedReason, SelectionSummaryProps } from './selection-summary';
@@ -0,0 +1,15 @@
1
+ import { SelectionProduct } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ import * as React from "react";
3
+ export interface ProductSelectorRowProps {
4
+ product: SelectionProduct;
5
+ currencyCode: string;
6
+ selected: boolean;
7
+ onSelect(productId: string): void;
8
+ disabled?: boolean;
9
+ className?: string;
10
+ }
11
+ /** A full-width product row: thumbnail, name and description, and the product's single price. */
12
+ export declare function ProductSelectorRow({ product, currencyCode, selected, onSelect, disabled, className, }: ProductSelectorRowProps): React.ReactElement;
13
+ export declare namespace ProductSelectorRow {
14
+ var displayName: string;
15
+ }
@@ -0,0 +1,46 @@
1
+ import { SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ import * as React from "react";
3
+ export interface ProductSelectorHandle {
4
+ /** Moves focus to the first interactive element. */
5
+ focus(): void;
6
+ /** Re-fetches options; the host triggers this after a sold-out failure. */
7
+ refresh(): void;
8
+ }
9
+ export interface ProductSelectorProps {
10
+ /**
11
+ * Fetch-on-open; the caller binds the view's option loader. Must be referentially
12
+ * stable — a new identity is a new loader and re-fetches the catalogue.
13
+ */
14
+ loadOptions(): Promise<SelectionOptions>;
15
+ /**
16
+ * Restrict rendering to one assigned product's subtree. Sourced from the journey's
17
+ * selection arm — the options payload carries no assignment of its own.
18
+ */
19
+ assignedProductId?: string;
20
+ /** Pre-select a previously recorded or pinned choice. */
21
+ initialChoice?: SelectionChoice | null;
22
+ /**
23
+ * Bubbled so the parent drives the confirm, enter or bid action — the selector never
24
+ * submits a selection itself. A complete choice sends the pair; anything less sends null.
25
+ */
26
+ onSelectionChange(choice: SelectionChoice | null): void;
27
+ /**
28
+ * Fires on every local transition with the exact draft. Product-only picks are
29
+ * representable here and not through `onSelectionChange`, and this never drives a CTA.
30
+ */
31
+ onDraftChange?(draft: SelectionDraft): void;
32
+ /** Fires after each successful fetch, so hosts derive display strings without a second request. */
33
+ onOptionsLoaded?(options: SelectionOptions): void;
34
+ /** Already-translated error from the parent's action, rendered inline beneath the grid. */
35
+ selectError?: string | null;
36
+ disabled?: boolean;
37
+ className?: string;
38
+ }
39
+ /**
40
+ * The picker: product rows, the selected product's option chips, and the resolved price.
41
+ *
42
+ * Resolution and selectability are the core module's, never re-derived here, so the
43
+ * adapter and the server agree on what a choice means. The component owns no action:
44
+ * it reports a complete pair upward and the host decides what to do with it.
45
+ */
46
+ export declare const ProductSelector: React.ForwardRefExoticComponent<ProductSelectorProps & React.RefAttributes<ProductSelectorHandle>>;
@@ -0,0 +1,30 @@
1
+ import { SelectionAvailability, SelectionChoice } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ import * as React from "react";
3
+ export type SelectionLockedReason = "checkout" | "final" | "bid";
4
+ export interface SelectionSummaryProps {
5
+ /** The captured pair being displayed. Presentational only — no fetching. */
6
+ choice: SelectionChoice;
7
+ /** Already-translated eyebrow above the pick; the generic one stands when absent. */
8
+ label?: string | null;
9
+ /** Host-derived display strings. Absent renders the generic label with no item detail. */
10
+ productName?: string | null;
11
+ variantLabel?: string | null;
12
+ /** Live availability of the displayed pick; an unavailable pick shows the stale note. */
13
+ availability?: SelectionAvailability;
14
+ /** Suppresses the change affordance and shows the matching locked copy. Wins over onChangeRequest. */
15
+ lockedReason?: SelectionLockedReason | null;
16
+ /** The consumer asked to change the pick; the host decides what that opens. */
17
+ onChangeRequest?(): void;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * A compact read-out of a pick that has already been captured.
22
+ *
23
+ * Purely presentational: it never fetches, never opens a picker, and never mutates
24
+ * the pick. A pick that has gone unavailable is said so here rather than left to be
25
+ * discovered at checkout.
26
+ */
27
+ export declare function SelectionSummary({ choice, label, productName, variantLabel, availability, lockedReason, onChangeRequest, className, }: SelectionSummaryProps): React.ReactElement;
28
+ export declare namespace SelectionSummary {
29
+ var displayName: string;
30
+ }
@@ -0,0 +1,24 @@
1
+ import { SelectionProduct } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ import * as React from "react";
3
+ export interface VariantRunGridProps {
4
+ product: SelectionProduct;
5
+ /** optionId → valueId for the options chosen so far. */
6
+ chosenValues: Record<string, string>;
7
+ onValueSelect(optionId: string, valueId: string): void;
8
+ disabled?: boolean;
9
+ className?: string;
10
+ }
11
+ /**
12
+ * One fieldset per product option, each a wrapping run of value chips.
13
+ *
14
+ * Server order is the only order: options and values render exactly as the catalogue
15
+ * states them. Availability belongs to a whole variant rather than to a value, so a
16
+ * chip is selectable only when some variant carrying it survives the other choices
17
+ * already fixed — a value exhausted under one combination stays enabled under
18
+ * another. A value no combination can reach is shown disabled and described rather
19
+ * than removed, so the grid keeps telling the truth about what the drop offered.
20
+ */
21
+ export declare function VariantRunGrid({ product, chosenValues, onValueSelect, disabled, className, }: VariantRunGridProps): React.ReactElement;
22
+ export declare namespace VariantRunGrid {
23
+ var displayName: string;
24
+ }
@@ -0,0 +1,46 @@
1
+ import { SelectionAvailability, SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ import { ProductSelectorHandle } from './selection';
3
+ import * as React from "react";
4
+ /** Widened so a selection-bearing panel stacks its rows instead of clipping them at the card cap. */
5
+ export declare const SELECTION_PANEL_CLASS = "ff:max-h-none ff:overflow-visible";
6
+ export interface EntrySelectionSurfaceProps {
7
+ loadOptions: () => Promise<SelectionOptions>;
8
+ /** Already-translated heading above the picker. */
9
+ title: React.ReactNode;
10
+ /** The consumer's current complete pair, or null while the pick is incomplete. */
11
+ onDraftSelection: (choice: SelectionChoice | null) => void;
12
+ onOptionsLoaded: (options: SelectionOptions) => void;
13
+ /** Every local transition, product-only drafts included; feeds the page-bridge event. */
14
+ onDraftChange?: (draft: SelectionDraft) => void;
15
+ /** Set by the CTA when it was clicked on an incomplete pick. */
16
+ missingChoice: boolean;
17
+ selectError?: string | null;
18
+ disabled?: boolean;
19
+ }
20
+ /**
21
+ * The entry picker: statically present in the branch, with no open or collapse of its own.
22
+ *
23
+ * The CTA below it stays enabled with no choice made, so the consumer finds out what is missing
24
+ * by acting rather than by a control that refuses to be pressed.
25
+ */
26
+ export declare const EntrySelectionSurface: React.ForwardRefExoticComponent<EntrySelectionSurfaceProps & React.RefAttributes<ProductSelectorHandle>>;
27
+ export interface RecordedSelectionSurfaceProps {
28
+ choice: SelectionChoice;
29
+ availability?: SelectionAvailability;
30
+ loadOptions?: () => Promise<SelectionOptions>;
31
+ /** Submits the new pair; rejects when the server refuses, which keeps the picker open. */
32
+ onSelectionChange?: (choice: SelectionChoice) => Promise<void>;
33
+ /** Options already in memory from a pick made earlier in this mount. */
34
+ loadedOptions: SelectionOptions | null;
35
+ onOptionsLoaded: (options: SelectionOptions) => void;
36
+ /** Every local transition, product-only drafts included; feeds the page-bridge event. */
37
+ onDraftChange?: (draft: SelectionDraft) => void;
38
+ selectError?: string | null;
39
+ }
40
+ /**
41
+ * The recorded pick and its re-pick, for a consumer already waiting on a result.
42
+ *
43
+ * A fresh mount has no catalogue in memory and makes no request for one: the summary states the
44
+ * pick generically rather than spending an entrant's request budget on prettier names.
45
+ */
46
+ export declare function RecordedSelectionSurface({ choice, availability, loadOptions, onSelectionChange, loadedOptions, onOptionsLoaded, onDraftChange, selectError, }: RecordedSelectionSurfaceProps): React.ReactElement;