@sunboard/react 0.1.3 → 0.2.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/dist/index.mjs CHANGED
@@ -142,7 +142,10 @@ function isAllowedLocalPreviewUrl(url) {
142
142
  function isSunboardSpecLike(value) {
143
143
  if (!value || typeof value !== "object") return false;
144
144
  const candidate = value;
145
- return typeof candidate.key === "string" && typeof candidate.name === "string" && typeof candidate.type === "string" && typeof candidate.checklist === "object" && Array.isArray(candidate.checklist?.steps);
145
+ if (typeof candidate.key !== "string" || typeof candidate.name !== "string" || typeof candidate.type !== "string") return false;
146
+ const hasChecklist = typeof candidate.checklist === "object" && Array.isArray(candidate.checklist?.steps);
147
+ const hasSurvey = typeof candidate.survey === "object" && Array.isArray(candidate.survey?.steps);
148
+ return hasChecklist || hasSurvey;
146
149
  }
147
150
 
148
151
  //#endregion
@@ -163,27 +166,44 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
163
166
  sdkVersion: SDK_VERSION
164
167
  });
165
168
  }, [apiUrl, publishableKey]);
169
+ const [bootstrapNonce, setBootstrapNonce] = useState(0);
170
+ const refreshBootstrap = useCallback(() => {
171
+ setBootstrapNonce((nonce) => nonce + 1);
172
+ }, []);
166
173
  const bootstrap = useSunboardBootstrap(client, {
167
174
  enabled: !specProp && preview.source !== "preview" && !preview.loading,
168
175
  environment,
176
+ nonce: bootstrapNonce,
169
177
  origin: currentOrigin,
170
178
  route: currentRoute,
171
179
  user
172
180
  });
173
181
  const activeExperience = bootstrap.experience;
174
- const hostedSpec = activeExperience?.spec ?? null;
182
+ const activeSurvey = bootstrap.survey;
183
+ const hostedSpec = activeSurvey?.spec ?? activeExperience?.spec ?? null;
175
184
  const resolvedSpec = specProp ?? preview.spec ?? hostedSpec;
176
185
  const resolvedLoading = loadingProp ?? (preview.loading || bootstrap.loading);
177
186
  const resolvedMode = modeProp ?? (preview.source === "preview" ? "preview" : "production");
178
187
  const hostedMode = client !== null && !specProp && preview.source !== "preview";
188
+ const ownedAttributes = bootstrap.attributes;
189
+ const effectiveUser = useMemo(() => {
190
+ if (!user || !ownedAttributes || Object.keys(ownedAttributes).length === 0) return user;
191
+ return {
192
+ ...user,
193
+ properties: {
194
+ ...user.properties ?? {},
195
+ ...ownedAttributes
196
+ }
197
+ };
198
+ }, [user, ownedAttributes]);
179
199
  const [completedStepKeys, setCompletedStepKeys] = useState(() => /* @__PURE__ */ new Set());
180
200
  const [completedTourKeys, setCompletedTourKeys] = useState(() => /* @__PURE__ */ new Set());
181
201
  const [activeTourState, setActiveTourState] = useState(null);
182
202
  const [tourError, setTourError] = useState(null);
183
203
  const lastNavigatedTourStep = useRef(null);
184
204
  const storageKey = !hostedMode && resolvedSpec && user ? completedToursStorageKey(resolvedSpec.key, user.id) : null;
185
- const experienceKey = activeExperience?.key ?? resolvedSpec?.key ?? null;
186
- const versionId = activeExperience?.version.id ?? null;
205
+ const experienceKey = activeSurvey?.key ?? activeExperience?.key ?? resolvedSpec?.key ?? null;
206
+ const versionId = activeSurvey?.version.id ?? activeExperience?.version.id ?? null;
187
207
  useEffect(() => {
188
208
  setCompletedStepKeys(/* @__PURE__ */ new Set());
189
209
  setActiveTourState(null);
@@ -221,7 +241,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
221
241
  ]);
222
242
  useEffect(() => {
223
243
  if (!resolvedSpec) return;
224
- const tourCompletedStepKeys = resolvedSpec.checklist.steps.filter((step) => step.completeWhen?.tour && effectiveCompletedTourKeys.has(step.completeWhen.tour)).map((step) => step.key);
244
+ const tourCompletedStepKeys = (resolvedSpec.checklist?.steps ?? []).filter((step) => step.completeWhen?.tour && effectiveCompletedTourKeys.has(step.completeWhen.tour)).map((step) => step.key);
225
245
  if (tourCompletedStepKeys.length === 0) return;
226
246
  setCompletedStepKeys((current) => mergeKeys(current, tourCompletedStepKeys));
227
247
  }, [effectiveCompletedTourKeys, resolvedSpec]);
@@ -270,8 +290,8 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
270
290
  hostedMode,
271
291
  user?.id
272
292
  ]);
273
- const totalSteps = resolvedSpec?.checklist.steps.length ?? 0;
274
- const completedCount = resolvedSpec?.checklist.steps.filter((step) => effectiveCompletedStepKeys.has(step.key)).length ?? 0;
293
+ const totalSteps = resolvedSpec?.checklist?.steps.length ?? 0;
294
+ const completedCount = resolvedSpec?.checklist?.steps.filter((step) => effectiveCompletedStepKeys.has(step.key)).length ?? 0;
275
295
  const completeTour = useCallback((tourKey) => {
276
296
  setCompletedTourKeys((current) => {
277
297
  const next = new Set(current);
@@ -289,7 +309,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
289
309
  ok: false,
290
310
  reason: "missing-user"
291
311
  };
292
- const matchingStepKeys = resolvedSpec.checklist.steps.filter((step) => step.completeWhen?.event === eventName).map((step) => step.key);
312
+ const matchingStepKeys = (resolvedSpec.checklist?.steps ?? []).filter((step) => step.completeWhen?.event === eventName).map((step) => step.key);
293
313
  if (matchingStepKeys.length > 0) setCompletedStepKeys((current) => mergeKeys(current, matchingStepKeys));
294
314
  let networkOk = true;
295
315
  if (hostedMode && client && experienceKey) try {
@@ -332,6 +352,56 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
332
352
  user,
333
353
  versionId
334
354
  ]);
355
+ const submitSurvey = useCallback(async (answers) => {
356
+ if (resolvedLoading || !resolvedSpec) return {
357
+ ok: false,
358
+ reason: "not-ready"
359
+ };
360
+ if (!user) return {
361
+ ok: false,
362
+ reason: "missing-user"
363
+ };
364
+ if (resolvedSpec.type !== "survey" || !resolvedSpec.survey) return {
365
+ ok: false,
366
+ reason: "not-survey"
367
+ };
368
+ if (resolvedMode === "preview") return { ok: true };
369
+ if (!hostedMode || !client || !experienceKey) return {
370
+ ok: false,
371
+ reason: "not-ready"
372
+ };
373
+ try {
374
+ await client.submitSurvey({
375
+ answers,
376
+ environment,
377
+ experienceKey,
378
+ origin: currentOrigin,
379
+ route: currentRoute,
380
+ user: toRuntimeUser(user),
381
+ versionId: versionId ?? void 0
382
+ });
383
+ } catch {
384
+ return {
385
+ ok: false,
386
+ reason: "network-error"
387
+ };
388
+ }
389
+ refreshBootstrap();
390
+ return { ok: true };
391
+ }, [
392
+ client,
393
+ currentOrigin,
394
+ currentRoute,
395
+ environment,
396
+ experienceKey,
397
+ hostedMode,
398
+ refreshBootstrap,
399
+ resolvedLoading,
400
+ resolvedMode,
401
+ resolvedSpec,
402
+ user,
403
+ versionId
404
+ ]);
335
405
  const navigateToStep = useCallback(async (step) => {
336
406
  if (!step.route) return;
337
407
  await navigateWithFallback(step.route, navigate);
@@ -486,7 +556,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
486
556
  }, []);
487
557
  const value = useMemo(() => ({
488
558
  spec: resolvedSpec,
489
- user,
559
+ user: effectiveUser,
490
560
  loading: resolvedLoading,
491
561
  mode: resolvedMode,
492
562
  route: currentRoute,
@@ -497,6 +567,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
497
567
  activeTour,
498
568
  tourError,
499
569
  track,
570
+ submitSurvey,
500
571
  startTour,
501
572
  dismissTour,
502
573
  advanceTour,
@@ -506,7 +577,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
506
577
  visibleHotspots
507
578
  }), [
508
579
  resolvedSpec,
509
- user,
580
+ effectiveUser,
510
581
  resolvedLoading,
511
582
  resolvedMode,
512
583
  currentRoute,
@@ -517,6 +588,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
517
588
  activeTour,
518
589
  tourError,
519
590
  track,
591
+ submitSurvey,
520
592
  startTour,
521
593
  dismissTour,
522
594
  advanceTour,
@@ -531,24 +603,30 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, mode: mo
531
603
  });
532
604
  }
533
605
  function useSunboardBootstrap(client, options) {
534
- const { enabled, environment, origin, route, user } = options;
606
+ const { enabled, environment, nonce, origin, route, user } = options;
535
607
  const [state, setState] = useState({
608
+ attributes: null,
536
609
  experience: null,
537
- loading: false
610
+ loading: false,
611
+ survey: null
538
612
  });
539
613
  const warnedSdkAdvisory = useRef(false);
540
614
  useEffect(() => {
541
- if (!client || !enabled || !user?.id) {
615
+ if (!client || !enabled || !user?.id || isPreviewPage()) {
542
616
  setState({
617
+ attributes: null,
543
618
  experience: null,
544
- loading: false
619
+ loading: false,
620
+ survey: null
545
621
  });
546
622
  return;
547
623
  }
548
624
  const controller = new AbortController();
549
625
  setState({
626
+ attributes: null,
550
627
  experience: null,
551
- loading: true
628
+ loading: true,
629
+ survey: null
552
630
  });
553
631
  client.bootstrap({
554
632
  environment,
@@ -557,21 +635,25 @@ function useSunboardBootstrap(client, options) {
557
635
  user: toRuntimeUser(user)
558
636
  }).then((response) => {
559
637
  if (controller.signal.aborted) return;
560
- if (response.experiences.length === 0 && isDevEnvironment()) console.warn(`[sunboard] bootstrap returned no active experiences for route "${route}" in environment "${environment}". Check the dashboard for an enabled deployment whose trigger.route matches.`);
638
+ if (response.experiences.length === 0 && !response.survey && isDevEnvironment()) console.warn(`[sunboard] bootstrap returned no active experiences for route "${route}" in environment "${environment}". Check the dashboard for an enabled deployment whose trigger.route matches.`);
561
639
  if (response.sdkAdvisory && isDevEnvironment() && !warnedSdkAdvisory.current) {
562
640
  warnedSdkAdvisory.current = true;
563
641
  console.warn(`[sunboard] ${response.sdkAdvisory.message}`);
564
642
  }
565
643
  setState({
644
+ attributes: response.attributes ?? null,
566
645
  experience: response.experiences[0] ?? null,
567
- loading: false
646
+ loading: false,
647
+ survey: response.survey ?? null
568
648
  });
569
649
  }).catch((error) => {
570
650
  if (controller.signal.aborted) return;
571
651
  if (isDevEnvironment()) console.warn("[sunboard] bootstrap failed", error);
572
652
  setState({
653
+ attributes: null,
573
654
  experience: null,
574
- loading: false
655
+ loading: false,
656
+ survey: null
575
657
  });
576
658
  });
577
659
  return () => {
@@ -581,6 +663,7 @@ function useSunboardBootstrap(client, options) {
581
663
  client,
582
664
  enabled,
583
665
  environment,
666
+ nonce,
584
667
  origin,
585
668
  user?.id
586
669
  ]);
@@ -601,6 +684,10 @@ function isDevEnvironment() {
601
684
  if (typeof process === "undefined" || !process.env) return false;
602
685
  return process.env.NODE_ENV !== "production";
603
686
  }
687
+ function isPreviewPage() {
688
+ if (typeof window === "undefined") return false;
689
+ return new URLSearchParams(window.location.search).get("sunboard_preview") === "1";
690
+ }
604
691
  function toRuntimeUser(user) {
605
692
  return {
606
693
  email: user.email ?? void 0,
@@ -690,7 +777,7 @@ function actionLabelFor(step) {
690
777
  if (step.action?.type === "startTour") return "Start";
691
778
  if (step.action || step.route) return "Open";
692
779
  }
693
- function interpolateOptional(text, user) {
780
+ function interpolateOptional$1(text, user) {
694
781
  if (!text) return void 0;
695
782
  return interpolateTokens(text, user) || void 0;
696
783
  }
@@ -712,17 +799,17 @@ function useChecklist() {
712
799
  const sawIncompleteFirst = observationRef.current?.sawIncomplete ?? false;
713
800
  const dismissed = dismissedKey !== null && dismissedKey === observationKey;
714
801
  const rawSteps = useMemo(() => spec?.checklist?.steps ?? [], [spec]);
715
- const title = useMemo(() => interpolateOptional(spec?.title, user ?? null), [spec?.title, user]);
716
- const subtitle = useMemo(() => interpolateOptional(spec?.subtitle, user ?? null), [spec?.subtitle, user]);
802
+ const title = useMemo(() => interpolateOptional$1(spec?.title, user ?? null), [spec?.title, user]);
803
+ const subtitle = useMemo(() => interpolateOptional$1(spec?.subtitle, user ?? null), [spec?.subtitle, user]);
717
804
  const completion = useMemo(() => {
718
805
  const raw = spec?.completion;
719
806
  if (!raw) return void 0;
720
- const interpolatedTitle = interpolateOptional(raw.title, user ?? null);
807
+ const interpolatedTitle = interpolateOptional$1(raw.title, user ?? null);
721
808
  if (!interpolatedTitle) return void 0;
722
809
  return {
723
810
  title: interpolatedTitle,
724
- subtitle: interpolateOptional(raw.subtitle, user ?? null),
725
- message: interpolateOptional(raw.message, user ?? null),
811
+ subtitle: interpolateOptional$1(raw.subtitle, user ?? null),
812
+ message: interpolateOptional$1(raw.message, user ?? null),
726
813
  dismissAfterMs: raw.dismissAfterMs
727
814
  };
728
815
  }, [spec?.completion, user]);
@@ -768,6 +855,140 @@ function useChecklist() {
768
855
  };
769
856
  }
770
857
 
858
+ //#endregion
859
+ //#region src/hooks/useSurvey.ts
860
+ function interpolateOptional(text, user) {
861
+ if (!text) return void 0;
862
+ return interpolateTokens(text, user) || void 0;
863
+ }
864
+ function answered(value) {
865
+ if (value === void 0 || value === null) return false;
866
+ if (typeof value === "string") return value.trim().length > 0;
867
+ if (Array.isArray(value)) return value.length > 0;
868
+ return true;
869
+ }
870
+ function useSurvey() {
871
+ const { spec, user, loading, submitSurvey } = useSunboard();
872
+ const [answers, setAnswers] = useState({});
873
+ const [submitting, setSubmitting] = useState(false);
874
+ const [submitted, setSubmitted] = useState(false);
875
+ const [error, setError] = useState(null);
876
+ const [stepIndex, setStepIndex] = useState(0);
877
+ const isSurvey = Boolean(spec && spec.type === "survey" && spec.survey);
878
+ const ready = !loading && !!user && isSurvey;
879
+ const title = useMemo(() => interpolateOptional(spec?.title, user ?? null), [spec?.title, user]);
880
+ const subtitle = useMemo(() => interpolateOptional(spec?.subtitle, user ?? null), [spec?.subtitle, user]);
881
+ const steps = useMemo(() => {
882
+ return (spec?.survey?.steps ?? []).map((step) => ({
883
+ title: interpolateOptional(step.title, user ?? null),
884
+ subtitle: interpolateOptional(step.subtitle, user ?? null),
885
+ questions: step.questions.map((question) => ({
886
+ key: question.key,
887
+ prompt: interpolateTokens(question.prompt, user ?? null),
888
+ type: question.type,
889
+ required: question.required,
890
+ options: (question.options ?? []).map((option) => ({
891
+ value: option.value,
892
+ label: interpolateTokens(option.label, user ?? null)
893
+ }))
894
+ }))
895
+ }));
896
+ }, [spec?.survey, user]);
897
+ const questions = useMemo(() => steps.flatMap((step) => step.questions), [steps]);
898
+ const signature = questions.map((question) => question.key).join("|");
899
+ const signatureRef = useRef(signature);
900
+ if (signatureRef.current !== signature) {
901
+ signatureRef.current = signature;
902
+ if (stepIndex !== 0) setStepIndex(0);
903
+ }
904
+ const totalSteps = steps.length;
905
+ const safeIndex = totalSteps > 0 ? Math.min(stepIndex, totalSteps - 1) : 0;
906
+ const currentStep = steps[safeIndex];
907
+ const setAnswer = useCallback((key, value) => {
908
+ setAnswers((current) => ({
909
+ ...current,
910
+ [key]: value
911
+ }));
912
+ }, []);
913
+ const toggleAnswer = useCallback((key, value) => {
914
+ setAnswers((current) => {
915
+ const existing = Array.isArray(current[key]) ? current[key] : [];
916
+ const next$1 = existing.includes(value) ? existing.filter((item) => item !== value) : [...existing, value];
917
+ return {
918
+ ...current,
919
+ [key]: next$1
920
+ };
921
+ });
922
+ }, []);
923
+ const isAnswered = useCallback((key) => answered(answers[key]), [answers]);
924
+ const canSubmit = useMemo(() => {
925
+ if (!ready || submitting) return false;
926
+ return questions.filter((question) => question.required).every((question) => answered(answers[question.key]));
927
+ }, [
928
+ answers,
929
+ questions,
930
+ ready,
931
+ submitting
932
+ ]);
933
+ const canAdvance = useMemo(() => {
934
+ if (!currentStep) return false;
935
+ return currentStep.questions.filter((question) => question.required).every((question) => answered(answers[question.key]));
936
+ }, [answers, currentStep]);
937
+ const submit = useCallback(async () => {
938
+ if (!canSubmit) return;
939
+ setSubmitting(true);
940
+ setError(null);
941
+ const result = await submitSurvey(answers);
942
+ setSubmitting(false);
943
+ if (result.ok) {
944
+ setSubmitted(true);
945
+ return;
946
+ }
947
+ setError(result.reason === "network-error" ? "Something went wrong. Please try again." : "Survey is not ready.");
948
+ }, [
949
+ answers,
950
+ canSubmit,
951
+ submitSurvey
952
+ ]);
953
+ const isLastStep = totalSteps === 0 || safeIndex >= totalSteps - 1;
954
+ const isFirstStep = safeIndex === 0;
955
+ const next = useCallback(() => {
956
+ if (isLastStep) submit();
957
+ else setStepIndex(safeIndex + 1);
958
+ }, [
959
+ isLastStep,
960
+ safeIndex,
961
+ submit
962
+ ]);
963
+ const back = useCallback(() => {
964
+ setStepIndex((current) => Math.max(0, current - 1));
965
+ }, []);
966
+ return {
967
+ visible: ready && !submitted,
968
+ title,
969
+ subtitle,
970
+ questions,
971
+ steps,
972
+ stepIndex: safeIndex,
973
+ totalSteps,
974
+ currentStep,
975
+ progress: totalSteps > 0 ? (safeIndex + 1) / totalSteps * 100 : 0,
976
+ isFirstStep,
977
+ isLastStep,
978
+ canAdvance,
979
+ next,
980
+ back,
981
+ answers,
982
+ setAnswer,
983
+ toggleAnswer,
984
+ isAnswered,
985
+ canSubmit,
986
+ submitting,
987
+ error,
988
+ submit
989
+ };
990
+ }
991
+
771
992
  //#endregion
772
993
  //#region src/icons/index.tsx
773
994
  const CheckIcon = () => {
@@ -1254,12 +1475,130 @@ function SunboardChecklist() {
1254
1475
  ]
1255
1476
  });
1256
1477
  }
1478
+ function SunboardSurvey() {
1479
+ const { visible, title, subtitle, currentStep, stepIndex, totalSteps, progress, isFirstStep, isLastStep, canAdvance, canSubmit, next, back, answers, setAnswer, toggleAnswer, submitting, error } = useSurvey();
1480
+ if (!visible || !title || !currentStep) return null;
1481
+ const showProgress = totalSteps > 1;
1482
+ return /* @__PURE__ */ jsx("div", {
1483
+ className: "sunboard-survey-overlay",
1484
+ children: /* @__PURE__ */ jsxs("section", {
1485
+ className: "sunboard-survey-card",
1486
+ role: "dialog",
1487
+ "aria-modal": "true",
1488
+ "aria-label": `${title} survey`,
1489
+ children: [
1490
+ /* @__PURE__ */ jsxs("div", {
1491
+ className: "sunboard-survey-head",
1492
+ children: [subtitle ? /* @__PURE__ */ jsx("p", {
1493
+ className: "sunboard-eyebrow",
1494
+ children: subtitle
1495
+ }) : null, /* @__PURE__ */ jsx("h2", {
1496
+ className: "sunboard-title",
1497
+ children: title
1498
+ })]
1499
+ }),
1500
+ showProgress ? /* @__PURE__ */ jsxs("div", {
1501
+ className: "sunboard-survey-progress-row",
1502
+ children: [/* @__PURE__ */ jsxs("p", {
1503
+ className: "sunboard-survey-stepcount",
1504
+ children: [
1505
+ "Step ",
1506
+ stepIndex + 1,
1507
+ " of ",
1508
+ totalSteps
1509
+ ]
1510
+ }), /* @__PURE__ */ jsx("div", {
1511
+ className: "sunboard-progress",
1512
+ role: "progressbar",
1513
+ "aria-label": `${title} progress`,
1514
+ "aria-valuemin": 1,
1515
+ "aria-valuemax": totalSteps,
1516
+ "aria-valuenow": stepIndex + 1,
1517
+ children: /* @__PURE__ */ jsx("div", {
1518
+ className: "sunboard-progress-fill",
1519
+ style: { width: `${progress}%` }
1520
+ })
1521
+ })]
1522
+ }) : null,
1523
+ /* @__PURE__ */ jsxs("form", {
1524
+ className: "sunboard-survey-form",
1525
+ onSubmit: (event) => {
1526
+ event.preventDefault();
1527
+ next();
1528
+ },
1529
+ children: [
1530
+ currentStep.title ? /* @__PURE__ */ jsx("p", {
1531
+ className: "sunboard-survey-step-title",
1532
+ children: currentStep.title
1533
+ }) : null,
1534
+ /* @__PURE__ */ jsx("ol", {
1535
+ className: "sunboard-survey-questions",
1536
+ children: currentStep.questions.map((question) => /* @__PURE__ */ jsxs("li", {
1537
+ className: "sunboard-survey-question",
1538
+ children: [/* @__PURE__ */ jsxs("p", {
1539
+ className: "sunboard-survey-prompt",
1540
+ children: [question.prompt, question.required ? /* @__PURE__ */ jsx("span", {
1541
+ "aria-hidden": "true",
1542
+ className: "sunboard-survey-required",
1543
+ children: " *"
1544
+ }) : null]
1545
+ }), question.type === "text" ? /* @__PURE__ */ jsx("input", {
1546
+ className: "sunboard-survey-input",
1547
+ type: "text",
1548
+ value: typeof answers[question.key] === "string" ? answers[question.key] : "",
1549
+ required: question.required,
1550
+ "aria-label": question.prompt,
1551
+ onChange: (event) => setAnswer(question.key, event.target.value)
1552
+ }) : /* @__PURE__ */ jsx("div", {
1553
+ className: "sunboard-survey-options",
1554
+ role: "group",
1555
+ "aria-label": question.prompt,
1556
+ children: question.options.map((option) => {
1557
+ const selected = question.type === "multi-select" ? Array.isArray(answers[question.key]) && answers[question.key].includes(option.value) : answers[question.key] === option.value;
1558
+ return /* @__PURE__ */ jsx("button", {
1559
+ className: "sunboard-survey-option",
1560
+ type: "button",
1561
+ "aria-pressed": selected,
1562
+ "data-selected": selected ? "true" : "false",
1563
+ onClick: () => question.type === "multi-select" ? toggleAnswer(question.key, option.value) : setAnswer(question.key, option.value),
1564
+ children: option.label
1565
+ }, option.value);
1566
+ })
1567
+ })]
1568
+ }, question.key))
1569
+ }),
1570
+ error ? /* @__PURE__ */ jsx("p", {
1571
+ className: "sunboard-survey-error",
1572
+ role: "alert",
1573
+ children: error
1574
+ }) : null,
1575
+ /* @__PURE__ */ jsxs("div", {
1576
+ className: "sunboard-survey-nav",
1577
+ children: [!isFirstStep ? /* @__PURE__ */ jsx("button", {
1578
+ className: "sunboard-survey-back",
1579
+ type: "button",
1580
+ onClick: back,
1581
+ children: "Back"
1582
+ }) : /* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsx("button", {
1583
+ className: "sunboard-survey-submit",
1584
+ type: "submit",
1585
+ disabled: isLastStep ? !canSubmit : !canAdvance,
1586
+ children: isLastStep ? submitting ? "Submitting…" : "Finish" : "Continue"
1587
+ })]
1588
+ })
1589
+ ]
1590
+ })
1591
+ ]
1592
+ })
1593
+ });
1594
+ }
1257
1595
  const Sunboard = Object.assign(SunboardChecklist, {
1258
1596
  Checklist: SunboardChecklist,
1597
+ Survey: SunboardSurvey,
1259
1598
  Hotspots: SunboardHotspots,
1260
1599
  Tour: SunboardTour
1261
1600
  });
1262
1601
 
1263
1602
  //#endregion
1264
- export { Sunboard, SunboardChecklist, SunboardHotspots, SunboardProvider, SunboardRuntimeApiError, SunboardRuntimeClient, SunboardTour, useChecklist, useSunboard, useSunboardPreviewSpec };
1603
+ export { Sunboard, SunboardChecklist, SunboardHotspots, SunboardProvider, SunboardRuntimeApiError, SunboardRuntimeClient, SunboardSurvey, SunboardTour, useChecklist, useSunboard, useSunboardPreviewSpec, useSurvey };
1265
1604
  //# sourceMappingURL=index.mjs.map