@fanfare-io/fanfare-sdk-solid 0.16.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
@@ -40,6 +40,63 @@ Solid applications can use `FanfareProvider`, `ExperienceWidget`, and `useExperi
40
40
 
41
41
  `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. Solid `ExperienceWidget` accepts both `class` and `className`.
42
42
 
43
+ ## Selection
44
+
45
+ Some experiences capture which product and variant a consumer gets. In this release the Solid
46
+ package exports the two building blocks of that picker for you to compose in a granted slot; the
47
+ widget-integrated flow, where `ExperienceWidget` renders the picker on its own, ships in a later
48
+ release.
49
+
50
+ - `ProductSelector` — loads a catalogue through its `loadOptions` callback, renders the product
51
+ rows, the chosen product's option chips and the resolved variant, and reports a complete
52
+ `{ productId, variantId }` pair through `onSelectionChange`. Anything less than a complete pair
53
+ reports `null`. It never submits a selection itself.
54
+ - `SelectionSummary` — a presentational read-out of a pick that has already been captured, with an
55
+ optional change affordance and the locked and unavailable copy.
56
+
57
+ Prices arrive from the catalogue as exact strings and render in the widget's locale and the
58
+ catalogue's `currencyCode`: on a product row when every variant of that product agrees on one
59
+ amount, and on the resolved variant's own line beneath the chips. The formatter is internal — the
60
+ public surface is exactly these two components plus their prop and handle types.
61
+
62
+ Selection is configured per sequence, under either checkout arrangement. With merchant
63
+ (`external`) checkout the pick is the host's to cart: the slot that composed `ProductSelector` holds
64
+ the resolved `{ productId, variantId }` pair and the catalogue it came from, and carries it into its
65
+ own checkout. With Fanfare (`internal`) checkout the granted panel continues into Fanfare's own
66
+ checkout instead, under both checkout modes, `at_end` and `pre_auth`. Entry-time picking (choosing
67
+ before you enter) is not yet available. The React adapter's widget-integrated hand-off — the
68
+ `onCheckoutClick(details?: SelectionCheckoutDetails)` callback — ships with the widget-integrated
69
+ flow, so Solid exports no equivalent type yet.
70
+
71
+ Three integration tiers, in increasing specificity:
72
+
73
+ 1. **Slot override, keeping our pieces.** Override the `enterable`, `participating` or `granted`
74
+ slot with a render function and compose `ProductSelector`, `SelectionSummary` and `GrantedPanel`
75
+ directly. The selection methods live on the sequence the slot hands you as `sequence`, not on
76
+ `view` — `sequence.loadSelectionOptions()` fetches the catalogue and `sequence.select(choice)`
77
+ confirms. Not every arm of the sequence union carries them, so narrow with
78
+ `"loadSelectionOptions" in sequence` first. The view is rebuilt on every journey emission, so
79
+ `sequence.loadSelectionOptions` is a different function each time and passing it straight to
80
+ `loadOptions` re-fetches the catalogue; so is an arrow written inline. Create the loader once,
81
+ outside the JSX, and have it read the journey's current sequence when it is called.
82
+ 2. **Standalone on your own page.** Import `ProductSelector` / `SelectionSummary` from the package
83
+ root and render them anywhere, driven by `useExperienceJourney`'s view. Both are self-contained
84
+ blocks themed through the ambient `--ff-*` variables and `ThemeProvider`, like every other
85
+ exported component.
86
+ 3. **Fully custom picker.** Render anything you like, then still call `view.sequence.select(choice)`
87
+ for a granted pick, or pass `selection` to `enter` / `bid` for an entry pick. `resolveVariantFromOptions`
88
+ and `isOptionValueSelectable` are importable from `@fanfare-io/fanfare-sdk-core/selections`, so a
89
+ custom UI resolves a choice exactly as the server does.
90
+
91
+ The row and option-grid units inside `ProductSelector` are internal; a host that wants different
92
+ row rendering uses tier 3.
93
+
94
+ `loadOptions` must be referentially stable — a new function identity is a new loader and re-fetches
95
+ the catalogue, so define it outside the JSX rather than inline. Solid has no `forwardRef`, so
96
+ `ProductSelector` hands its `ProductSelectorHandle` (`focus()`, `refresh()`) to a `ref` callback
97
+ prop; `refresh()` is what a host calls after a sold-out failure. Both components accept `class`
98
+ alongside `className`.
99
+
43
100
  ## Documentation
44
101
 
45
102
  - [Core SDK page observation](https://www.npmjs.com/package/@fanfare-io/fanfare-sdk-core): use `useExperienceJourney` for Solid state and `useSdkEvent` for typed SDK events. For surrounding CSS/non-framework scripts, install core's `createDomBridge` on a stable mounted target and dispose it before destroying the journey or SDK; Solid has no `useFanfareDomBridge` hook.
@@ -13,3 +13,5 @@ export { DrawModule, type DrawModuleProps } from './draw-module';
13
13
  export { QueueModule, type QueueModuleProps } from './queue-module';
14
14
  export { TimedReleaseModule, type TimedReleaseModuleProps } from './timed-release-module';
15
15
  export { UpcomingModule, type UpcomingModuleProps } from './upcoming-module';
16
+ export { ProductSelector, SelectionSummary } from './selection';
17
+ export type { ProductSelectorHandle, ProductSelectorProps, SelectionLockedReason, SelectionSummaryProps, } from './selection';
@@ -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
+ export interface ProductSelectorRowProps {
3
+ product: SelectionProduct;
4
+ currencyCode: string;
5
+ selected: boolean;
6
+ onSelect(productId: string): void;
7
+ disabled?: boolean;
8
+ class?: string;
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(props: ProductSelectorRowProps): import("solid-js").JSX.Element;
13
+ export declare namespace ProductSelectorRow {
14
+ var displayName: string;
15
+ }
@@ -0,0 +1,51 @@
1
+ import { SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ export interface ProductSelectorHandle {
3
+ /** Moves focus to the first interactive element. */
4
+ focus(): void;
5
+ /** Re-fetches options; the host triggers this after a sold-out failure. */
6
+ refresh(): void;
7
+ }
8
+ export interface ProductSelectorProps {
9
+ /**
10
+ * Fetch-on-open; the caller binds the view's option loader. Must be referentially
11
+ * stable — a new identity is a new loader and re-fetches the catalogue.
12
+ */
13
+ loadOptions(): Promise<SelectionOptions>;
14
+ /**
15
+ * Restrict rendering to one assigned product's subtree. Sourced from the journey's
16
+ * selection arm — the options payload carries no assignment of its own.
17
+ */
18
+ assignedProductId?: string;
19
+ /** Pre-select a previously recorded or pinned choice. */
20
+ initialChoice?: SelectionChoice | null;
21
+ /**
22
+ * Bubbled so the parent drives the confirm, enter or bid action — the selector never
23
+ * submits a selection itself. A complete choice sends the pair; anything less sends null.
24
+ */
25
+ onSelectionChange(choice: SelectionChoice | null): void;
26
+ /**
27
+ * Fires on every local transition with the exact draft. Product-only picks are
28
+ * representable here and not through `onSelectionChange`, and this never drives a CTA.
29
+ */
30
+ onDraftChange?(draft: SelectionDraft): void;
31
+ /** Fires after each successful fetch, so hosts derive display strings without a second request. */
32
+ onOptionsLoaded?(options: SelectionOptions): void;
33
+ /** Already-translated error from the parent's action, rendered inline beneath the grid. */
34
+ selectError?: string | null;
35
+ disabled?: boolean;
36
+ /** Receives the imperative handle once, at mount. */
37
+ ref?: (handle: ProductSelectorHandle) => void;
38
+ class?: string;
39
+ className?: string;
40
+ }
41
+ /**
42
+ * The picker: product rows, the selected product's option chips, and the resolved price.
43
+ *
44
+ * Resolution and selectability are the core module's, never re-derived here, so the
45
+ * adapter and the server agree on what a choice means. The component owns no action:
46
+ * it reports a complete pair upward and the host decides what to do with it.
47
+ */
48
+ export declare function ProductSelector(props: ProductSelectorProps): import("solid-js").JSX.Element;
49
+ export declare namespace ProductSelector {
50
+ var displayName: string;
51
+ }
@@ -0,0 +1,30 @@
1
+ import { SelectionAvailability, SelectionChoice } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ export type SelectionLockedReason = "checkout" | "final" | "bid";
3
+ export interface SelectionSummaryProps {
4
+ /** The captured pair being displayed. Presentational only — no fetching. */
5
+ choice: SelectionChoice;
6
+ /** Already-translated eyebrow above the pick; the generic one stands when absent. */
7
+ label?: string | null;
8
+ /** Host-derived display strings. Absent renders the generic label with no item detail. */
9
+ productName?: string | null;
10
+ variantLabel?: string | null;
11
+ /** Live availability of the displayed pick; an unavailable pick shows the stale note. */
12
+ availability?: SelectionAvailability;
13
+ /** Suppresses the change affordance and shows the matching locked copy. Wins over onChangeRequest. */
14
+ lockedReason?: SelectionLockedReason | null;
15
+ /** The consumer asked to change the pick; the host decides what that opens. */
16
+ onChangeRequest?(): void;
17
+ class?: string;
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(props: SelectionSummaryProps): import("solid-js").JSX.Element;
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
+ export interface VariantRunGridProps {
3
+ product: SelectionProduct;
4
+ /** optionId → valueId for the options chosen so far. */
5
+ chosenValues: Record<string, string>;
6
+ onValueSelect(optionId: string, valueId: string): void;
7
+ disabled?: boolean;
8
+ class?: string;
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(props: VariantRunGridProps): import("solid-js").JSX.Element;
22
+ export declare namespace VariantRunGrid {
23
+ var displayName: string;
24
+ }
@@ -0,0 +1,23 @@
1
+ import { SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
2
+ import { Accessor } from 'solid-js';
3
+ export interface UseSelectionOptionsResult {
4
+ options: Accessor<SelectionOptions | null>;
5
+ isLoading: Accessor<boolean>;
6
+ error: Accessor<Error | null>;
7
+ /** Re-fetch; used by the sold-out auto-refetch and the handle's refresh(). */
8
+ refresh(): Promise<void>;
9
+ }
10
+ /**
11
+ * Fetches a selection catalogue on demand, once per loader identity.
12
+ *
13
+ * Nothing is cached across mounts, persisted, or deduped between callers: a
14
+ * catalogue carries live availability, so a second consumer of a stale copy would
15
+ * be shown a pick that is already gone. The primitive never polls.
16
+ *
17
+ * A refetch keeps the previously loaded catalogue in `options` so the caller can
18
+ * hold its rendered grid rather than flashing back to a skeleton, and a standing
19
+ * `error` survives until the retry settles so the failure view does not flash the
20
+ * stale grid on its way back. Resolutions and rejections from a superseded
21
+ * request, or from one still in flight after disposal, are dropped.
22
+ */
23
+ export declare function useSelectionOptions(load: Accessor<(() => Promise<SelectionOptions>) | undefined>): UseSelectionOptionsResult;
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export { InfoPanel, PanelBody, PanelFooter, PanelHeading, PanelLayout, normalize
20
20
  export type { InfoPanelProps, OutcomeReason, PanelBodyProps, PanelFooterProps, PanelHeadingProps, PanelHeadingVariant, PanelLayoutProps, } from './components/module';
21
21
  export { AccessCodeForm, ChallengeGate, EndedModule, ErrorView, GrantedPanel, OutcomePanel, } from './components/compositions';
22
22
  export type { AccessCodeFormProps, ChallengeGateProps, EndedModuleProps, ErrorViewProps, GrantedPanelProps, OutcomePanelProps, } from './components/compositions';
23
+ export { ProductSelector, SelectionSummary } from './components/internal/selection';
24
+ export type { ProductSelectorHandle, ProductSelectorProps, SelectionLockedReason, SelectionSummaryProps, } from './components/internal/selection';
23
25
  export { ExperienceWidget } from './components/widgets';
24
26
  export type { AccessCodeSlotProps, AuthSlotProps, ChallengeSlotProps, CheckoutProcessingSlotProps, EndedSlotProps, EnterableSlotProps, ErrorSlotProps, ExperienceRenderProps, ExperienceWidgetProps, ExperienceWidgetSlots, ExpiredSlotProps, GrantedSlotProps, LoadingSlotProps, ParticipatingSlotProps, PaymentCollectionSlotProps, ReceiptSlotProps, SlotProps, StartSlotProps, UpcomingSlotProps, WaitlistSlotProps, } from './components/widgets';
25
27
  export { CheckoutProcessingPanel, PaymentCollectionPanel, ReceiptPanel } from './components/checkout';