All files / frontend/src/components/SukohForm FormContext.tsx

83.33% Statements 5/6
50% Branches 1/2
100% Functions 1/1
83.33% Lines 5/6

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                                                                                                                                                        2x             4x 4x     4x           2x        
import { createContext, useContext, ReactNode } from 'react';
import type { Field } from '@quiqr/types';
 
/**
 * Reference to a file in a bundle (used by bundle-manager fields)
 */
export interface FileReference {
  src: string;
  filename?: string;
  thumbnail?: string;
}
 
/**
 * Extended field config with runtime properties added during form initialization.
 * The compositeKey is computed to create a unique path identifier for each field instance.
 *
 * Uses intersection type since Field is a discriminated union that can't be extended.
 */
export type FieldConfig = Field & {
  compositeKey: string;
};
 
/**
 * Form metadata used across the form system
 */
export interface FormMeta {
  siteKey: string;
  workspaceKey: string;
  collectionKey: string;
  collectionItemKey: string;
  prompt_templates: string[];
  pageUrl: string;
}
 
/**
 * Main form context value interface.
 *
 * The form uses nested state to match YAML/frontmatter structure.
 * Resources (files) are separated for easier management but merged when saving.
 */
export interface FormContextValue {
  // Nested document state (matches YAML structure)
  document: Record<string, unknown>;
 
  // Path-based value access
  getValueAtPath: <T>(path: string) => T | undefined;
  setValueAtPath: (path: string, value: unknown, debounce?: number) => void;
  clearValueAtPath: (path: string) => void;
 
  // Separated resources (for bundle-manager fields)
  // Keyed by compositeKey
  resources: Record<string, FileReference[]>;
  getResources: (key: string) => FileReference[];
  setResources: (key: string, files: FileReference[], markDirty?: boolean) => void;
 
  // Field configs indexed by compositeKey (preprocessed once at init)
  fieldConfigs: Map<string, FieldConfig>;
  getFieldConfig: (compositeKey: string) => FieldConfig | undefined;
 
  // Form state
  isDirty: boolean;
  isSubmitting: boolean;
 
  // Form metadata
  meta: FormMeta;
 
  // Per-field cache (for expensive computations)
  getCache: (compositeKey: string) => Record<string, unknown>;
 
  // For rendering nested fields (accordion items, etc.)
  renderFields: (parentPath: string, fields: Field[]) => ReactNode;
 
  // Save handler
  saveForm: () => Promise<void>;
}
 
const FormContext = createContext<FormContextValue | null>(null);
 
/**
 * Hook to access the form context.
 * Must be used within a FormProvider.
 */
export function useFormContext(): FormContextValue {
  const ctx = useContext(FormContext);
  Iif (!ctx) {
    throw new Error('useFormContext must be used within a FormProvider');
  }
  return ctx;
}
 
/**
 * Consumer component for class components that can't use hooks
 */
export const FormContextConsumer = FormContext.Consumer;
 
export { FormContext };
export default FormContext;