@saastro/forms 0.5.0 → 0.6.1

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.d.ts CHANGED
@@ -3025,6 +3025,69 @@ declare global {
3025
3025
  */
3026
3026
  declare function recaptchaPlugin(config: RecaptchaPluginConfig): FormPlugin;
3027
3027
 
3028
+ /**
3029
+ * ============================================
3030
+ * TURNSTILE PLUGIN - Cloudflare Turnstile
3031
+ * ============================================
3032
+ *
3033
+ * Mirrors `recaptchaPlugin` but for Cloudflare Turnstile. Unlike
3034
+ * reCAPTCHA v3 (purely programmatic), Turnstile is widget-based: it must
3035
+ * be rendered into a DOM element. This plugin renders the widget in
3036
+ * explicit + `execution: 'execute'` mode (so it only challenges at submit
3037
+ * time) into a container it manages, captures the token via the success
3038
+ * callback, and attaches it to every submission via `transformValues`.
3039
+ *
3040
+ * The token field defaults to `_captchaToken` — the canonical field the
3041
+ * Hub submit helper (`createHubFormSubmit`) extracts and forwards to the
3042
+ * worker as `captchaToken`.
3043
+ */
3044
+ interface TurnstilePluginConfig {
3045
+ /** Cloudflare Turnstile site key. */
3046
+ siteKey: string;
3047
+ /** Field name for the token in submitted values (default: '_captchaToken'). */
3048
+ tokenField?: string;
3049
+ /**
3050
+ * Widget appearance. 'interaction-only' (default) keeps it invisible
3051
+ * unless Cloudflare decides a challenge is needed — closest to the
3052
+ * reCAPTCHA v3 UX. 'always' renders a visible widget.
3053
+ */
3054
+ appearance?: 'always' | 'execute' | 'interaction-only';
3055
+ /**
3056
+ * CSS selector for the element to render the widget into. Defaults to
3057
+ * `[data-saastro-turnstile]` (rendered inline by `HubForm`). If no such
3058
+ * element exists, the plugin creates a fixed, bottom-right container so
3059
+ * an interactive challenge can still surface.
3060
+ */
3061
+ container?: string;
3062
+ }
3063
+ interface TurnstileApi {
3064
+ render: (el: HTMLElement | string, options: Record<string, unknown>) => string;
3065
+ execute: (widgetId?: string, options?: Record<string, unknown>) => void;
3066
+ reset: (widgetId?: string) => void;
3067
+ remove: (widgetId?: string) => void;
3068
+ getResponse: (widgetId?: string) => string | undefined;
3069
+ }
3070
+ declare global {
3071
+ interface Window {
3072
+ turnstile?: TurnstileApi;
3073
+ }
3074
+ }
3075
+ /**
3076
+ * Cloudflare Turnstile plugin.
3077
+ *
3078
+ * @example
3079
+ * ```tsx
3080
+ * import { turnstilePlugin, PluginManager, FormBuilder } from '@saastro/forms';
3081
+ *
3082
+ * const pm = new PluginManager();
3083
+ * pm.register(turnstilePlugin({ siteKey: '0x4AAAAAAA...' }));
3084
+ * ```
3085
+ *
3086
+ * With `HubForm`, this is wired automatically when the form schema
3087
+ * declares `meta.captchaProvider === 'turnstile'`.
3088
+ */
3089
+ declare function turnstilePlugin(config: TurnstilePluginConfig): FormPlugin;
3090
+
3028
3091
  /**
3029
3092
  * ============================================
3030
3093
  * SHADCN/UI PRESET - Component Registry
@@ -3376,6 +3439,22 @@ declare function useFormLayout(layout?: FormLayout): {
3376
3439
  gridStyle: React$1.CSSProperties;
3377
3440
  };
3378
3441
 
3442
+ /**
3443
+ * Minimal "value is present" schema for a field that declares
3444
+ * `required: true` at the top level but ships NO explicit `schema`.
3445
+ *
3446
+ * The builder stores `required` at the top level (it drives the asterisk +
3447
+ * the native `required` attribute). Until now `useFormState` only built
3448
+ * validation from `field.schema`, so a `required: true` field without a
3449
+ * schema skipped validation entirely — empty submissions sailed through.
3450
+ *
3451
+ * Kept value-type-agnostic (`z.any()` + refine) on purpose so it never
3452
+ * fights the stored value shape: number inputs hold strings, checkboxes
3453
+ * hold booleans, multi-selects hold arrays. It only enforces presence; rich
3454
+ * per-type rules (email format, min/max, …) still come from an explicit
3455
+ * `schema`/`ValidationRules`.
3456
+ */
3457
+ declare function buildRequiredOnlySchema(fieldType: string): z.ZodTypeAny;
3379
3458
  declare function useFormState({ config: rawConfig, fields: rawFields, steps: rawSteps, }: {
3380
3459
  config: FormConfig;
3381
3460
  fields: Fields;
@@ -3870,4 +3949,4 @@ declare function getFieldClass(formLayout?: {
3870
3949
  */
3871
3950
  declare function getHiddenClasses(hidden?: Partial<Record<Breakpoint, 'visible' | 'hidden'>>): string;
3872
3951
 
3873
- export { type AccordionContentProps, type AccordionItemProps, type AccordionProps, type AccordionTriggerProps, BUILD_METHODS, BUILTIN_RESOLVERS, type BaseFieldProps, type Breakpoint$1 as Breakpoint, type BuiltinTransform, type ButtonCardFieldProps, type ButtonCardOption, type ButtonCheckboxFieldProps, type ButtonConfig, type ButtonProps, type ButtonRadioFieldProps, COMMON_CLASSES, COMMON_METHODS, COMMON_STRINGS, type CalendarProps, type CheckboxFieldProps, type CheckboxGroupFieldProps, type CheckboxProps, type ColumnsValue, type ComboboxFieldProps, type CommandEmptyProps, type CommandFieldProps, type CommandGroupProps, type CommandInputProps, type CommandItemProps, type CommandListProps, type CommandProps, type ComponentName, type ComponentOverrides, ComponentProvider, type ComponentProviderProps, type ComponentRegistry, type ComponentRegistryInput, ComponentResolver, type ComponentResolverConfig, type Condition, type ConditionGroup, type ConditionOperator, type CreateHubFormSubmitOptions, type CurrencyFieldProps, type CustomSubmitAction, type CustomSubmitConfig, DEFAULT_HUB_URL, type DatabowlConfig, type DateFieldProps, type DateRangeFieldProps, type DefaultSubmitConfig, type DefinePlugin, type DialogContentProps, type DialogDescriptionProps, type DialogHeaderProps, type DialogProps, type DialogTitleProps, type DialogTriggerProps, type EmailProvider, type EmailSubmitAction, type EndpointConfig, FIELD_DEFAULTS, FieldBuilder, type FieldConfig, type FieldDescriptionProps, type FieldErrorProps, type FieldLabelProps, type FieldLayoutConfig, type FieldMapEntry, type FieldMapping, type FieldMappingConfig, type FieldProps, FieldRenderer, type FieldResolver, type FieldTransformFn, type Fields, type FileFieldProps, Form, FormBuilder, type FormButtonProps, type FormButtons, FormComponentsProvider, type FormComponentsProviderProps, type FormConfig, type FormControlProps, type FormFieldProps, type FormLayout, type FormPlugin, type FormProps, type FormRef, type FormStepsInfo, type GapValue, type GlobModules, type HiddenFieldProps, type HtmlFieldProps, type HttpAuthConfig, type HttpBodyConfig, type HttpEndpointConfig, type HttpRetryConfig, type HttpSubmitAction, HubForm, type HubFormProps, type InputGroupFieldProps, type InputOTPGroupProps, type InputOTPProps, type InputOTPSlotProps, type InputProps, type InputSize, type IntegrationSubmitAction, InternalComponentProvider, type InternalComponentProviderProps, type LabelProps, MissingComponentFallback, type MissingComponentFallbackProps, type NativeSelectFieldProps, type NativeSelectProps, OPTION_BASED_TYPES, type Option, type OtpFieldProps, type PartialComponentRegistry, PluginManager, type PopoverContentProps, type PopoverProps, type PopoverTriggerProps, type PropertyMapping, type RadioFieldProps, type RadioGroupItemProps, type RadioGroupProps, type RangeFieldProps, type RecaptchaConfig, type RecaptchaPluginConfig, type RepeaterFieldProps, type ResolvedComponents, SUSPENSE_CONFIG, type SchemaType, type SelectContentProps, type SelectFieldProps, type SelectItemProps, type SelectProps, type SelectTriggerProps, type SelectValueProps, type SeparatorProps, type SerializableFieldResolver, type SerializationHint, type SliderFieldProps, type SliderProps, type Step, type StepCondition, type StepInfo, type StepStatus, type Steps, StepsAccordion, StepsNavigation, StepsProgress, type SubmitAction, type SubmitActionCondition, type SubmitActionNode, type SubmitActionResult, type SubmitActionType, type SubmitActionsResult, type SubmitConfig, type SubmitConfirmationConfig, type SubmitExecutionConfig, type SubmitTrigger, type SubmitTriggerType, type SubmitType, type SwitchFieldProps, type SwitchGroupFieldProps, type SwitchProps, TYPE_SPECIFIC_PROPERTIES, type TestDataLocale, type TestDataOptions, type TextFieldProps, type TextareaFieldProps, type TextareaProps, type TooltipContentProps, type TooltipProps, type TooltipProviderProps, type TooltipTriggerProps, type UseSubmitConfirmationReturn, VALIDATION_METHODS, type ValidateComponentRegistry, type ValidationContext, type ValidationPresetMeta, type ValidationRules, type WebhookSubmitAction, analyticsPlugin, applyBuiltinTransform, applyFieldMapping, applyFieldMappingSync, applyFieldTransforms, applyTransform, autosavePlugin, compileValidationRules, configureComponents, coreComponents, createComponentRegistry, createHubFormSubmit, createMissingComponentPlaceholder, createShadcnRegistry, databowlAction, databowlPlugin, defaultSubmit, definePlugin, detectLocale, executeCustomAction, executeEmailAction, executeHttpAction, executeSubmitAction, executeSubmitActions, executeSubmitActionsByTrigger, executeWebhookAction, fieldTypeComponents, generateFieldValue, generateTestData, getActionsByTrigger, getAllKnownMethods, getAvailablePresets, getComponentResolver, getFieldClass, getFormGridClass, getHiddenClasses, getInstallCommand, getMinDelayMs, getMissingComponents, getRequiredComponents, globalPluginManager, groupMissingByPackage, iconVariants, iconVariantsConfig, inputVariants, inputVariantsConfig, isAdvancedMapping, isValidationRules, isZodSchema, localStoragePlugin, mergeComponentRegistries, parseGlobModules, pxToRem, recaptchaPlugin, registerPreset, resolvePreset, resolveValue, resolveValueSync, textareaVariants, textareaVariantsConfig, useComponentMode, useComponents, useComputedFields, useFormLayout, useFormState, useFormStepsInfo, useHasComponentProvider, useHiddenFieldResolvers, usePartialComponents, useRecaptcha, useSubmitActionTriggers, useSubmitConfirmation, withComponents };
3952
+ export { type AccordionContentProps, type AccordionItemProps, type AccordionProps, type AccordionTriggerProps, BUILD_METHODS, BUILTIN_RESOLVERS, type BaseFieldProps, type Breakpoint$1 as Breakpoint, type BuiltinTransform, type ButtonCardFieldProps, type ButtonCardOption, type ButtonCheckboxFieldProps, type ButtonConfig, type ButtonProps, type ButtonRadioFieldProps, COMMON_CLASSES, COMMON_METHODS, COMMON_STRINGS, type CalendarProps, type CheckboxFieldProps, type CheckboxGroupFieldProps, type CheckboxProps, type ColumnsValue, type ComboboxFieldProps, type CommandEmptyProps, type CommandFieldProps, type CommandGroupProps, type CommandInputProps, type CommandItemProps, type CommandListProps, type CommandProps, type ComponentName, type ComponentOverrides, ComponentProvider, type ComponentProviderProps, type ComponentRegistry, type ComponentRegistryInput, ComponentResolver, type ComponentResolverConfig, type Condition, type ConditionGroup, type ConditionOperator, type CreateHubFormSubmitOptions, type CurrencyFieldProps, type CustomSubmitAction, type CustomSubmitConfig, DEFAULT_HUB_URL, type DatabowlConfig, type DateFieldProps, type DateRangeFieldProps, type DefaultSubmitConfig, type DefinePlugin, type DialogContentProps, type DialogDescriptionProps, type DialogHeaderProps, type DialogProps, type DialogTitleProps, type DialogTriggerProps, type EmailProvider, type EmailSubmitAction, type EndpointConfig, FIELD_DEFAULTS, FieldBuilder, type FieldConfig, type FieldDescriptionProps, type FieldErrorProps, type FieldLabelProps, type FieldLayoutConfig, type FieldMapEntry, type FieldMapping, type FieldMappingConfig, type FieldProps, FieldRenderer, type FieldResolver, type FieldTransformFn, type Fields, type FileFieldProps, Form, FormBuilder, type FormButtonProps, type FormButtons, FormComponentsProvider, type FormComponentsProviderProps, type FormConfig, type FormControlProps, type FormFieldProps, type FormLayout, type FormPlugin, type FormProps, type FormRef, type FormStepsInfo, type GapValue, type GlobModules, type HiddenFieldProps, type HtmlFieldProps, type HttpAuthConfig, type HttpBodyConfig, type HttpEndpointConfig, type HttpRetryConfig, type HttpSubmitAction, HubForm, type HubFormProps, type InputGroupFieldProps, type InputOTPGroupProps, type InputOTPProps, type InputOTPSlotProps, type InputProps, type InputSize, type IntegrationSubmitAction, InternalComponentProvider, type InternalComponentProviderProps, type LabelProps, MissingComponentFallback, type MissingComponentFallbackProps, type NativeSelectFieldProps, type NativeSelectProps, OPTION_BASED_TYPES, type Option, type OtpFieldProps, type PartialComponentRegistry, PluginManager, type PopoverContentProps, type PopoverProps, type PopoverTriggerProps, type PropertyMapping, type RadioFieldProps, type RadioGroupItemProps, type RadioGroupProps, type RangeFieldProps, type RecaptchaConfig, type RecaptchaPluginConfig, type RepeaterFieldProps, type ResolvedComponents, SUSPENSE_CONFIG, type SchemaType, type SelectContentProps, type SelectFieldProps, type SelectItemProps, type SelectProps, type SelectTriggerProps, type SelectValueProps, type SeparatorProps, type SerializableFieldResolver, type SerializationHint, type SliderFieldProps, type SliderProps, type Step, type StepCondition, type StepInfo, type StepStatus, type Steps, StepsAccordion, StepsNavigation, StepsProgress, type SubmitAction, type SubmitActionCondition, type SubmitActionNode, type SubmitActionResult, type SubmitActionType, type SubmitActionsResult, type SubmitConfig, type SubmitConfirmationConfig, type SubmitExecutionConfig, type SubmitTrigger, type SubmitTriggerType, type SubmitType, type SwitchFieldProps, type SwitchGroupFieldProps, type SwitchProps, TYPE_SPECIFIC_PROPERTIES, type TestDataLocale, type TestDataOptions, type TextFieldProps, type TextareaFieldProps, type TextareaProps, type TooltipContentProps, type TooltipProps, type TooltipProviderProps, type TooltipTriggerProps, type TurnstilePluginConfig, type UseSubmitConfirmationReturn, VALIDATION_METHODS, type ValidateComponentRegistry, type ValidationContext, type ValidationPresetMeta, type ValidationRules, type WebhookSubmitAction, analyticsPlugin, applyBuiltinTransform, applyFieldMapping, applyFieldMappingSync, applyFieldTransforms, applyTransform, autosavePlugin, buildRequiredOnlySchema, compileValidationRules, configureComponents, coreComponents, createComponentRegistry, createHubFormSubmit, createMissingComponentPlaceholder, createShadcnRegistry, databowlAction, databowlPlugin, defaultSubmit, definePlugin, detectLocale, executeCustomAction, executeEmailAction, executeHttpAction, executeSubmitAction, executeSubmitActions, executeSubmitActionsByTrigger, executeWebhookAction, fieldTypeComponents, generateFieldValue, generateTestData, getActionsByTrigger, getAllKnownMethods, getAvailablePresets, getComponentResolver, getFieldClass, getFormGridClass, getHiddenClasses, getInstallCommand, getMinDelayMs, getMissingComponents, getRequiredComponents, globalPluginManager, groupMissingByPackage, iconVariants, iconVariantsConfig, inputVariants, inputVariantsConfig, isAdvancedMapping, isValidationRules, isZodSchema, localStoragePlugin, mergeComponentRegistries, parseGlobModules, pxToRem, recaptchaPlugin, registerPreset, resolvePreset, resolveValue, resolveValueSync, textareaVariants, textareaVariantsConfig, turnstilePlugin, useComponentMode, useComponents, useComputedFields, useFormLayout, useFormState, useFormStepsInfo, useHasComponentProvider, useHiddenFieldResolvers, usePartialComponents, useRecaptcha, useSubmitActionTriggers, useSubmitConfirmation, withComponents };
package/dist/index.js CHANGED
@@ -2912,6 +2912,27 @@ function useComputedFields(methods, fields) {
2912
2912
  }
2913
2913
 
2914
2914
  // src/hooks/useFormState.ts
2915
+ function buildRequiredOnlySchema(fieldType) {
2916
+ const message = "This field is required";
2917
+ if (fieldType === "checkbox" || fieldType === "switch") {
2918
+ return z2.any().refine((v) => v === true, {
2919
+ message: "You must accept this"
2920
+ });
2921
+ }
2922
+ if (fieldType === "checkbox-group" || fieldType === "switch-group" || fieldType === "button-checkbox" || fieldType === "button-card") {
2923
+ return z2.any().refine((v) => Array.isArray(v) && v.length > 0, { message });
2924
+ }
2925
+ if (fieldType === "daterange") {
2926
+ return z2.any().refine(
2927
+ (v) => !!v && typeof v === "object" && !!v.from,
2928
+ { message }
2929
+ );
2930
+ }
2931
+ return z2.any().refine(
2932
+ (v) => v !== void 0 && v !== null && v !== "" && !(typeof v === "number" && Number.isNaN(v)),
2933
+ { message }
2934
+ );
2935
+ }
2915
2936
  function useFormState({
2916
2937
  config: rawConfig,
2917
2938
  fields: rawFields,
@@ -2967,6 +2988,8 @@ function useFormState({
2967
2988
  });
2968
2989
  }
2969
2990
  schema[name] = fieldSchema;
2991
+ } else if (cfg.required) {
2992
+ schema[name] = buildRequiredOnlySchema(cfg.type);
2970
2993
  }
2971
2994
  defaults[name] = cfg.value ?? (() => {
2972
2995
  switch (cfg.type) {
@@ -6071,6 +6094,156 @@ function recaptchaPlugin(config) {
6071
6094
  });
6072
6095
  }
6073
6096
 
6097
+ // src/plugins/turnstile.ts
6098
+ var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
6099
+ var TOKEN_TIMEOUT_MS = 2e4;
6100
+ function waitForTurnstile(timeoutMs = 1e4) {
6101
+ return new Promise((resolve, reject) => {
6102
+ if (typeof window === "undefined") {
6103
+ reject(new Error("turnstile: no window"));
6104
+ return;
6105
+ }
6106
+ if (window.turnstile) {
6107
+ resolve(window.turnstile);
6108
+ return;
6109
+ }
6110
+ const start = Date.now();
6111
+ const tick = () => {
6112
+ if (window.turnstile) {
6113
+ resolve(window.turnstile);
6114
+ } else if (Date.now() - start > timeoutMs) {
6115
+ reject(new Error("turnstile: script did not load"));
6116
+ } else {
6117
+ setTimeout(tick, 100);
6118
+ }
6119
+ };
6120
+ tick();
6121
+ });
6122
+ }
6123
+ function turnstilePlugin(config) {
6124
+ const {
6125
+ siteKey,
6126
+ tokenField = "_captchaToken",
6127
+ appearance = "interaction-only",
6128
+ container
6129
+ } = config;
6130
+ let scriptElement = null;
6131
+ let createdContainer = null;
6132
+ let widgetId;
6133
+ let lastToken = null;
6134
+ let pending = null;
6135
+ const resolveContainer = () => {
6136
+ if (container) {
6137
+ const found = document.querySelector(container);
6138
+ if (found) return found;
6139
+ }
6140
+ const byData = document.querySelector("[data-saastro-turnstile]");
6141
+ if (byData) return byData;
6142
+ if (!createdContainer) {
6143
+ createdContainer = document.createElement("div");
6144
+ createdContainer.setAttribute("data-saastro-turnstile-fallback", "");
6145
+ createdContainer.style.position = "fixed";
6146
+ createdContainer.style.bottom = "12px";
6147
+ createdContainer.style.right = "12px";
6148
+ createdContainer.style.zIndex = "2147483647";
6149
+ document.body.appendChild(createdContainer);
6150
+ }
6151
+ return createdContainer;
6152
+ };
6153
+ const ensureWidget = async () => {
6154
+ const api = await waitForTurnstile();
6155
+ if (widgetId === void 0) {
6156
+ const el = resolveContainer();
6157
+ widgetId = api.render(el, {
6158
+ sitekey: siteKey,
6159
+ appearance,
6160
+ execution: "execute",
6161
+ callback: (token) => {
6162
+ lastToken = token;
6163
+ pending?.resolve(token);
6164
+ pending = null;
6165
+ },
6166
+ "error-callback": () => {
6167
+ pending?.reject(new Error("turnstile: challenge failed"));
6168
+ pending = null;
6169
+ return true;
6170
+ },
6171
+ "expired-callback": () => {
6172
+ lastToken = null;
6173
+ }
6174
+ });
6175
+ }
6176
+ return api;
6177
+ };
6178
+ return definePlugin({
6179
+ name: "turnstile",
6180
+ version: "1.0.0",
6181
+ description: "Cloudflare Turnstile \u2014 widget render and token generation",
6182
+ onFormInit() {
6183
+ if (typeof window === "undefined") return;
6184
+ if (!window.turnstile && !document.querySelector('script[src*="turnstile/v0/api.js"]')) {
6185
+ scriptElement = document.createElement("script");
6186
+ scriptElement.src = SCRIPT_SRC;
6187
+ scriptElement.async = true;
6188
+ scriptElement.defer = true;
6189
+ document.body.appendChild(scriptElement);
6190
+ }
6191
+ void ensureWidget().catch(() => {
6192
+ });
6193
+ },
6194
+ async transformValues(values) {
6195
+ if (typeof window === "undefined") return values;
6196
+ try {
6197
+ const api = await ensureWidget();
6198
+ const token = await new Promise((resolve, reject) => {
6199
+ pending = { resolve, reject };
6200
+ const timer = setTimeout(() => {
6201
+ if (pending) {
6202
+ pending = null;
6203
+ reject(new Error("turnstile: token timeout"));
6204
+ }
6205
+ }, TOKEN_TIMEOUT_MS);
6206
+ const wrappedResolve = (t) => {
6207
+ clearTimeout(timer);
6208
+ resolve(t);
6209
+ };
6210
+ const wrappedReject = (e) => {
6211
+ clearTimeout(timer);
6212
+ reject(e);
6213
+ };
6214
+ pending = { resolve: wrappedResolve, reject: wrappedReject };
6215
+ lastToken = null;
6216
+ api.reset(widgetId);
6217
+ api.execute(widgetId);
6218
+ });
6219
+ return { ...values, [tokenField]: token };
6220
+ } catch (error) {
6221
+ console.error("turnstilePlugin: failed to get token:", error);
6222
+ return values;
6223
+ }
6224
+ },
6225
+ cleanup() {
6226
+ if (typeof window !== "undefined" && window.turnstile && widgetId !== void 0) {
6227
+ try {
6228
+ window.turnstile.remove(widgetId);
6229
+ } catch {
6230
+ }
6231
+ }
6232
+ widgetId = void 0;
6233
+ lastToken = null;
6234
+ pending = null;
6235
+ if (createdContainer?.parentNode) {
6236
+ createdContainer.parentNode.removeChild(createdContainer);
6237
+ createdContainer = null;
6238
+ }
6239
+ if (scriptElement?.parentNode) {
6240
+ scriptElement.parentNode.removeChild(scriptElement);
6241
+ scriptElement = null;
6242
+ }
6243
+ }
6244
+ });
6245
+ }
6246
+
6074
6247
  // src/presets/shadcn.ts
6075
6248
  function createComponentRegistry(components) {
6076
6249
  return {
@@ -6901,6 +7074,13 @@ function createHubFormSubmit(opts) {
6901
7074
  // src/components/HubForm.tsx
6902
7075
  import { useEffect as useEffect8, useMemo as useMemo7, useState as useState5 } from "react";
6903
7076
  import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
7077
+ function readCaptchaMeta(schema) {
7078
+ const meta = schema.meta;
7079
+ return {
7080
+ captchaProvider: meta?.captchaProvider,
7081
+ captchaSiteKey: meta?.captchaSiteKey
7082
+ };
7083
+ }
6904
7084
  function isRenderableSchema(value) {
6905
7085
  if (!value || typeof value !== "object") return false;
6906
7086
  const schema = value;
@@ -6970,6 +7150,18 @@ function HubForm({
6970
7150
  }
6971
7151
  };
6972
7152
  }, [hubUrl, siteId, formSlug, onSuccess, onError]);
7153
+ const captcha = useMemo7(() => {
7154
+ if (state.status !== "ready") return void 0;
7155
+ const { captchaProvider, captchaSiteKey } = readCaptchaMeta(state.schema);
7156
+ if (!captchaProvider || !captchaSiteKey) return void 0;
7157
+ const pm = new PluginManager();
7158
+ if (captchaProvider === "turnstile") {
7159
+ pm.register(turnstilePlugin({ siteKey: captchaSiteKey, tokenField: "_captchaToken" }));
7160
+ } else {
7161
+ pm.register(recaptchaPlugin({ siteKey: captchaSiteKey, tokenField: "_captchaToken" }));
7162
+ }
7163
+ return { pm, provider: captchaProvider };
7164
+ }, [state]);
6973
7165
  if (state.status === "loading") {
6974
7166
  return loadingFallback ?? /* @__PURE__ */ jsx14("div", { "data-saastro-hubform-loading": true, style: loadingStyle, children: "Loading\u2026" });
6975
7167
  }
@@ -6990,9 +7182,13 @@ function HubForm({
6990
7182
  }
6991
7183
  const config = {
6992
7184
  ...state.schema,
6993
- submit: wrappedSubmit
7185
+ submit: wrappedSubmit,
7186
+ ...captcha ? { pluginManager: captcha.pm } : {}
6994
7187
  };
6995
- return /* @__PURE__ */ jsx14(Form, { ...formProps, config });
7188
+ return /* @__PURE__ */ jsxs11(Fragment3, { children: [
7189
+ /* @__PURE__ */ jsx14(Form, { ...formProps, config }),
7190
+ captcha?.provider === "turnstile" ? /* @__PURE__ */ jsx14("div", { "data-saastro-turnstile": true }) : null
7191
+ ] });
6996
7192
  }
6997
7193
  var loadingStyle = {
6998
7194
  padding: "2rem 1rem",
@@ -7279,6 +7475,7 @@ export {
7279
7475
  applyFieldTransforms,
7280
7476
  applyTransform,
7281
7477
  autosavePlugin,
7478
+ buildRequiredOnlySchema,
7282
7479
  compileValidationRules,
7283
7480
  configureComponents,
7284
7481
  coreComponents,
@@ -7332,6 +7529,7 @@ export {
7332
7529
  resolveValueSync,
7333
7530
  textareaVariants,
7334
7531
  textareaVariantsConfig,
7532
+ turnstilePlugin,
7335
7533
  useComponentMode,
7336
7534
  useComponents,
7337
7535
  useComputedFields,