@react-typed-forms/schemas 16.2.2 → 17.0.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.
@@ -0,0 +1,781 @@
1
+ # Form Extensions Guide
2
+
3
+ ## Overview
4
+
5
+ This document describes how to create custom form extensions in the ServiceTas application. Form extensions allow you to add custom renderers for inputs, groups, adornments, labels, and display components that integrate seamlessly with the AppForms system.
6
+
7
+ ## Extension Types
8
+
9
+ The form rendering system supports five types of extensions:
10
+
11
+ 1. **Data Renderers** - Custom input/data entry controls
12
+ 2. **Group Renderers** - Custom layouts for groups of fields
13
+ 3. **Adornment Renderers** - Decorative or functional elements added to controls
14
+ 4. **Label Renderers** - Custom rendering for field labels
15
+ 5. **Display Renderers** - Custom read-only display components
16
+
17
+ ## Architecture
18
+
19
+ Form extensions consist of three main parts:
20
+
21
+ 1. **Extension Definition** (`formExtensions.ts`) - Defines configuration options
22
+ 2. **Renderer Implementation** (`renderer.tsx` or separate files) - Implements the visual component
23
+ 3. **Registration** (`renderer.tsx`) - Registers the renderer with the form system
24
+
25
+ ## Creating a Form Extension
26
+
27
+ ### Step 1: Define Extension Configuration
28
+
29
+ Create a `CustomRenderOptions` object in `formExtensions.ts`:
30
+
31
+ ```typescript
32
+ import {
33
+ buildSchema,
34
+ CustomRenderOptions,
35
+ boolField,
36
+ stringField,
37
+ intField,
38
+ stringOptionsField
39
+ } from "@react-typed-forms/schemas";
40
+
41
+ // Define the configuration interface
42
+ export interface MyExtensionOptions {
43
+ displayLabel: boolean;
44
+ customColor?: string;
45
+ size?: number;
46
+ }
47
+
48
+ // Create the extension definition
49
+ export const MyExtensionOptions: CustomRenderOptions = {
50
+ value: "MyExtension", // Unique identifier (used in renderType)
51
+ name: "My Extension", // Display name (shown in form editor)
52
+ fields: buildSchema<MyExtensionOptions>({
53
+ displayLabel: boolField("Display Label"),
54
+ customColor: stringField("Custom Color"),
55
+ size: intField("Size")
56
+ })
57
+ };
58
+ ```
59
+
60
+ **Field Types Available:**
61
+ - `boolField(label)` - Boolean checkbox
62
+ - `stringField(label)` - Text input
63
+ - `intField(label)` - Numeric input
64
+ - `stringOptionsField(label, ...options)` - Dropdown selector
65
+ - Custom field types from `@react-typed-forms/schemas`
66
+
67
+ **Important Notes:**
68
+ - `value` must be unique across all extensions
69
+ - `fields` is optional - use empty array `[]` if no configuration needed
70
+ - For extensions that extend existing types (like `DataRenderType.Dropdown`), use the existing type as the `value`
71
+
72
+ ### Step 2: Create the Renderer Component
73
+
74
+ #### Data Renderer (Input Controls)
75
+
76
+ Data renderers are for custom input controls. Examples: Switch, AddressFinder, Chart, Map.
77
+
78
+ **File location:** `renderer.tsx` or `renderer/MyControl.tsx`
79
+
80
+ ```typescript
81
+ import { createDataRenderer } from "@react-typed-forms/schemas";
82
+ import { MyExtensionOptions } from "./formExtensions";
83
+
84
+ export const MyDataRenderer = createDataRenderer(
85
+ (props, renderer) => {
86
+ const { control, renderOptions, className, id } = props;
87
+ const options = renderOptions as MyExtensionOptions & RenderOptions;
88
+
89
+ return (
90
+ <div className={className}>
91
+ <input
92
+ type="text"
93
+ value={control.value ?? ''}
94
+ onChange={(e) => control.setValue(e.target.value)}
95
+ disabled={control.disabled}
96
+ />
97
+ {options.displayLabel && <span>{control.value}</span>}
98
+ </div>
99
+ );
100
+ },
101
+ {
102
+ renderType: MyExtensionOptions.value, // Must match the value from Step 1
103
+ }
104
+ );
105
+ ```
106
+
107
+ **Key Props Available:**
108
+ - `control` - The form control (has `.value`, `.setValue()`, `.disabled`, `.fields`)
109
+ - `renderOptions` - Configuration options from the extension definition
110
+ - `className` - CSS classes to apply
111
+ - `id` - Unique identifier for the field
112
+ - `dataNode` - Access to parent/child nodes in the form tree
113
+ - `renderer` - Access to the FormRenderer for rendering nested elements
114
+
115
+ **Example: Switch Renderer (from renderer.tsx:94-121)**
116
+
117
+ ```typescript
118
+ const SwitchRenderer = createDataRenderer(
119
+ (props, renderer) => {
120
+ const { renderOptions, control } = props;
121
+ const { displayLabel } = renderOptions as ExtendedSwitch & RenderOptions;
122
+
123
+ return (
124
+ <label className="inline-flex items-center cursor-pointer">
125
+ <input
126
+ type="checkbox"
127
+ checked={control.value ?? false}
128
+ onChange={() => control.setValue((x) => !x)}
129
+ className="sr-only peer"
130
+ disabled={control.disabled}
131
+ />
132
+ <div className="..." />
133
+ {displayLabel && (
134
+ <span className="ms-3 subhead">
135
+ {control.value ?? false ? "On" : "Off"}
136
+ </span>
137
+ )}
138
+ </label>
139
+ );
140
+ },
141
+ {
142
+ renderType: SwitchOptions.value,
143
+ }
144
+ );
145
+ ```
146
+
147
+ #### Group Renderer (Layout Components)
148
+
149
+ Group renderers control how groups of fields are laid out. Example: TopLevelGroup.
150
+
151
+ ```typescript
152
+ import { createGroupRenderer, GroupRendererProps } from "@react-typed-forms/schemas";
153
+
154
+ export const MyGroupRenderer = createGroupRenderer(
155
+ (props, renderers) => {
156
+ const { className, style, definition, dataContext, renderChild, formNode } = props;
157
+
158
+ return (
159
+ <div className={className} style={style}>
160
+ {formNode.children.map((child, i) => renderChild(child))}
161
+ </div>
162
+ );
163
+ },
164
+ {
165
+ renderType: MyGroupOptions.value,
166
+ }
167
+ );
168
+ ```
169
+
170
+ **Key Props Available:**
171
+ - `definition` - The group definition
172
+ - `dataContext` - Data context including parentNode
173
+ - `renderChild` - Function to render child fields
174
+ - `formNode` - The form node with children array
175
+ - `className`, `style` - Styling properties
176
+
177
+ **Example: TopLevelGroup Renderer (from renderer.tsx:176-213)**
178
+
179
+ ```typescript
180
+ const topLevelGroupRenderer = createGroupRenderer(
181
+ (p, renderers) => (
182
+ <div className={rendererClass(className, DefaultRenderOptions.group?.standardClassName)}>
183
+ <AllErrors
184
+ definition={definition}
185
+ dataNode={dataContext.parentNode}
186
+ labelRenderer={renderers.renderLabelText}
187
+ />
188
+ {formNode.children.map((c, i) => renderChild(c))}
189
+ </div>
190
+ ),
191
+ {
192
+ renderType: TopLevelGroupOption.value,
193
+ }
194
+ );
195
+ ```
196
+
197
+ #### Adornment Renderer (Decorations)
198
+
199
+ Adornment renderers add decorative or functional elements to controls. Examples: HelpText, Tooltip.
200
+
201
+ ```typescript
202
+ import {
203
+ createAdornmentRenderer,
204
+ wrapMarkup,
205
+ appendMarkupAt,
206
+ AdornmentPlacement,
207
+ ControlAdornment
208
+ } from "@react-typed-forms/schemas";
209
+
210
+ export const MyAdornmentRenderer = createAdornmentRenderer(
211
+ (props, renderers) => {
212
+ const options = props.adornment as MyAdornmentOptions & ControlAdornment;
213
+
214
+ return {
215
+ apply: wrapMarkup("children", (children) => (
216
+ <div className="relative">
217
+ {children}
218
+ <span className="adornment">{options.adornmentText}</span>
219
+ </div>
220
+ )),
221
+ priority: 0, // Lower numbers render first (outer layers)
222
+ adornment: props.adornment,
223
+ };
224
+ },
225
+ {
226
+ adornmentType: ControlAdornmentType.MyAdornment,
227
+ }
228
+ );
229
+ ```
230
+
231
+ **Markup Manipulation Functions:**
232
+ - `wrapMarkup(target, wrapper)` - Wraps the target element
233
+ - Targets: `"children"`, `"label"`, `"control"`, etc.
234
+ - `appendMarkupAt(placement, element)` - Adds element at specific position
235
+ - Placements: `AdornmentPlacement.LabelEnd`, `AdornmentPlacement.ControlEnd`, etc.
236
+
237
+ **Example: HelpText Renderer (from renderer.tsx:237-282)**
238
+
239
+ ```typescript
240
+ const createHelpTextRenderer = (container: HTMLElement | null) => {
241
+ return createAdornmentRenderer(
242
+ (p, renderers) => {
243
+ const label = (p.adornment as ExtendedHelpText).helpLabel;
244
+ const helpText = (p.adornment as HelpTextAdornment).helpText;
245
+
246
+ return {
247
+ apply: appendMarkupAt(
248
+ (p.adornment as HelpTextAdornment).placement ?? AdornmentPlacement.LabelEnd,
249
+ <Popover.Root>
250
+ <Popover.Trigger asChild>
251
+ <button className="...">
252
+ <i className="fa fa-info-circle mr-2" />
253
+ {renderers.renderLabelText(label)}
254
+ </button>
255
+ </Popover.Trigger>
256
+ <Popover.Portal container={container}>
257
+ <Popover.Content className="...">
258
+ <Div className="..." html={helpText} />
259
+ </Popover.Content>
260
+ </Popover.Portal>
261
+ </Popover.Root>
262
+ ),
263
+ priority: 0,
264
+ adornment: p.adornment,
265
+ };
266
+ },
267
+ {
268
+ adornmentType: ControlAdornmentType.HelpText,
269
+ }
270
+ );
271
+ };
272
+ ```
273
+
274
+ #### Label Renderer (Custom Labels)
275
+
276
+ Label renderers customize how field labels are displayed. Example: HtmlLabel.
277
+
278
+ ```typescript
279
+ import { createLabelRenderer, LabelType } from "@react-typed-forms/schemas";
280
+
281
+ export const MyLabelRenderer = createLabelRenderer(
282
+ (props) => {
283
+ return <span className="custom-label">{props.label}</span>;
284
+ },
285
+ {
286
+ labelType: LabelType.Text,
287
+ }
288
+ );
289
+ ```
290
+
291
+ **Example: Html Label Renderer (from renderer.tsx:159-174)**
292
+
293
+ ```typescript
294
+ const HtmlLabelRenderer = createLabelRenderer(
295
+ (p) => <HtmlLabel label={p.label} />,
296
+ { labelType: LabelType.Text }
297
+ );
298
+
299
+ function HtmlLabel({ label }: { label: ReactNode }) {
300
+ const labelText = useMemo(() => {
301
+ if (typeof label === "string") {
302
+ return parse(label); // Parse HTML string
303
+ }
304
+ return label;
305
+ }, [label]);
306
+ return labelText;
307
+ }
308
+ ```
309
+
310
+ #### Display Renderer (Read-only Display)
311
+
312
+ Display renderers show read-only data. Example: Html display.
313
+
314
+ ```typescript
315
+ import {
316
+ createDisplayRenderer,
317
+ DisplayDataType,
318
+ HtmlDisplay
319
+ } from "@react-typed-forms/schemas";
320
+
321
+ export const MyDisplayRenderer = createDisplayRenderer(
322
+ (props) => {
323
+ const data = props.data as MyDisplayData;
324
+ return <div className="display">{data.content}</div>;
325
+ },
326
+ {
327
+ renderType: DisplayDataType.MyDisplay,
328
+ }
329
+ );
330
+ ```
331
+
332
+ **Example: Html Display Renderer (from renderer.tsx:123-138)**
333
+
334
+ ```typescript
335
+ export function createHtmlRenderer(
336
+ makeOnClick: (actionId: string, data: any) => () => void
337
+ ) {
338
+ return createDisplayRenderer(
339
+ (props) => {
340
+ return (
341
+ <HtmlDisplayRenderer
342
+ {...props}
343
+ html={(props.data as HtmlDisplay).html ?? ""}
344
+ makeOnClick={makeOnClick}
345
+ />
346
+ );
347
+ },
348
+ { renderType: DisplayDataType.Html }
349
+ );
350
+ }
351
+ ```
352
+
353
+ ### Step 3: Register the Extension
354
+
355
+ #### Register in EditorExtension
356
+
357
+ Add your extension to `EditorExtension` in `formExtensions.ts`:
358
+
359
+ ```typescript
360
+ export const EditorExtension: ControlDefinitionExtension = {
361
+ GroupRenderOptions: TopLevelGroupOption, // Single group renderer (optional)
362
+ ControlAdornment: [ // Array of adornment options
363
+ HelpTextOptions,
364
+ SpotlightOptions,
365
+ MyAdornmentOptions // Add your adornment here
366
+ ],
367
+ RenderOptions: [ // Array of data/display renderers
368
+ MapOptions,
369
+ ChartDefinition,
370
+ SwitchOptions,
371
+ MyExtensionOptions // Add your data renderer here
372
+ ],
373
+ };
374
+ ```
375
+
376
+ **Extension Categories:**
377
+ - `GroupRenderOptions` - Single group renderer option
378
+ - `ControlAdornment` - Array of adornment options
379
+ - `RenderOptions` - Array of data renderer options
380
+
381
+ #### Register the Renderer
382
+
383
+ Add your renderer to the `createStdRenderer` function in `renderer.tsx`:
384
+
385
+ ```typescript
386
+ export function createStdRenderer(
387
+ defaults: DefaultRendererOptions,
388
+ options: StdRenderOptions,
389
+ ...others: RendererRegistration[]
390
+ ) {
391
+ return createFormRenderer(
392
+ [
393
+ ...others,
394
+ HtmlLabelRenderer,
395
+ MapRenderer,
396
+ SwitchRenderer,
397
+ MyDataRenderer, // Add your renderer here
398
+ MyAdornmentRenderer(options.container),
399
+ topLevelGroupRenderer,
400
+ // ... other renderers
401
+ ],
402
+ createDefaultRenderers(defaults)
403
+ );
404
+ }
405
+ ```
406
+
407
+ **Registration Order:**
408
+ - Renderers are processed in order
409
+ - More specific renderers should come before more general ones
410
+ - Adornments with lower priority numbers render first (outer layers)
411
+
412
+ ## Complete Examples
413
+
414
+ ### Example 1: Simple Switch Control
415
+
416
+ **formExtensions.ts:**
417
+ ```typescript
418
+ export interface ExtendedSwitch {
419
+ displayLabel: boolean;
420
+ }
421
+
422
+ export const SwitchOptions: CustomRenderOptions = {
423
+ name: "Switch",
424
+ value: "Switch",
425
+ fields: buildSchema<ExtendedSwitch>({
426
+ displayLabel: boolField("Display Label"),
427
+ }),
428
+ };
429
+ ```
430
+
431
+ **renderer.tsx:**
432
+ ```typescript
433
+ const SwitchRenderer = createDataRenderer(
434
+ (props, renderer) => {
435
+ const { renderOptions, control } = props;
436
+ const { displayLabel } = renderOptions as ExtendedSwitch & RenderOptions;
437
+
438
+ return (
439
+ <label className="inline-flex items-center cursor-pointer">
440
+ <input
441
+ type="checkbox"
442
+ checked={control.value ?? false}
443
+ onChange={() => control.setValue((x) => !x)}
444
+ className="sr-only peer"
445
+ disabled={control.disabled}
446
+ />
447
+ <div className="relative w-11 h-6 bg-[#e4e4e7] peer-checked:bg-accent ..." />
448
+ {displayLabel && (
449
+ <span className="ms-3 subhead">
450
+ {control.value ?? false ? "On" : "Off"}
451
+ </span>
452
+ )}
453
+ </label>
454
+ );
455
+ },
456
+ {
457
+ renderType: SwitchOptions.value,
458
+ }
459
+ );
460
+ ```
461
+
462
+ ### Example 2: Chart Renderer with Configuration
463
+
464
+ **ChartRendererConfigs.ts:**
465
+ ```typescript
466
+ export enum ChartType {
467
+ Doughnut = "Doughnut",
468
+ }
469
+
470
+ export interface ChartOptions {
471
+ chartType?: string;
472
+ ringColor?: string;
473
+ ringBackgroundColor?: string;
474
+ displayText?: boolean;
475
+ }
476
+
477
+ const ChartFields = buildSchema<ChartOptions>({
478
+ chartType: stringOptionsField("Type", {
479
+ name: "Doughnut",
480
+ value: ChartType.Doughnut,
481
+ }),
482
+ ringColor: stringField("Ring Color"),
483
+ ringBackgroundColor: stringField("Ring Background Color"),
484
+ displayText: boolField("Display Text"),
485
+ });
486
+
487
+ export const ChartDefinition: CustomRenderOptions = {
488
+ name: "Chart",
489
+ value: "Chart",
490
+ fields: ChartFields,
491
+ };
492
+ ```
493
+
494
+ **ChartRenderer.tsx:**
495
+ ```typescript
496
+ export function createChartRenderer(options: ChartOptions = {}) {
497
+ return createDataRenderer(
498
+ (p) => {
499
+ const { id, renderOptions, dataNode } = p;
500
+ const chartOptions = mergeObjects(
501
+ renderOptions as ChartOptions & RenderOptions,
502
+ options
503
+ ) ?? {};
504
+
505
+ const { chartType } = chartOptions;
506
+ const fields = dataNode.control.fields;
507
+
508
+ switch (chartType) {
509
+ case ChartType.Doughnut:
510
+ return (
511
+ <DoughnutChart
512
+ key={id}
513
+ progress={fields.activePoints as Control<number>}
514
+ total={fields.totalPoints as Control<number>}
515
+ ringBackgroundColor={chartOptions.ringBackgroundColor}
516
+ ringColor={chartOptions.ringColor}
517
+ displayText={chartOptions.displayText ?? false}
518
+ />
519
+ );
520
+ default:
521
+ return null;
522
+ }
523
+ },
524
+ {
525
+ renderType: ChartDefinition.value,
526
+ }
527
+ );
528
+ }
529
+ ```
530
+
531
+ ### Example 3: Address Finder with External Service
532
+
533
+ **formExtensions.ts:**
534
+ ```typescript
535
+ export interface AddressExtraOptions {
536
+ disablePostalAddress?: boolean;
537
+ }
538
+
539
+ export const AddressFinderOptions: CustomRenderOptions = {
540
+ name: "AddressFinder",
541
+ value: "AddressFinder",
542
+ fields: buildSchema<AddressExtraOptions>({
543
+ disablePostalAddress: boolField("Disable Postal Address"),
544
+ }),
545
+ };
546
+ ```
547
+
548
+ **AddressFinderControl.tsx:**
549
+ ```typescript
550
+ export function createAddressFinderRenderer() {
551
+ return createDataRenderer(
552
+ (dataProps, renderer) => (
553
+ <AddressFinderRenderer renderer={renderer} dataProps={dataProps} />
554
+ ),
555
+ {
556
+ renderType: AddressFinderOptions.value,
557
+ }
558
+ );
559
+ }
560
+
561
+ function AddressFinderRenderer({
562
+ renderer,
563
+ dataProps,
564
+ }: {
565
+ dataProps: DataRendererProps;
566
+ renderer: FormRenderer;
567
+ }) {
568
+ const {
569
+ addressSuggestions,
570
+ addressSearchControl,
571
+ getSuggestions,
572
+ selectedAddress,
573
+ } = useAddressFinder({
574
+ dataProps,
575
+ key: "API_KEY_HERE",
576
+ });
577
+
578
+ return (
579
+ <AutocompleteInput
580
+ options={addressSuggestions.value}
581
+ getOptionText={(x) => x.fullAddress}
582
+ selectedControl={selectedAddress}
583
+ textControl={addressSearchControl as Control<string>}
584
+ onInputChange={(_, v) => {
585
+ addressSearchControl.value = v;
586
+ getSuggestions(v);
587
+ }}
588
+ />
589
+ );
590
+ }
591
+ ```
592
+
593
+ ## Best Practices
594
+
595
+ ### 1. Naming Conventions
596
+ - Use descriptive names: `SwitchRenderer`, `ChartRenderer`, not `CustomRenderer1`
597
+ - Interface names should end with "Options": `ChartOptions`, `SwitchOptions`
598
+ - Renderer constants should end with "Renderer": `MapRenderer`, `SwitchRenderer`
599
+
600
+ ### 2. Configuration Design
601
+ - Only add configuration options that are truly needed
602
+ - Use appropriate field types for the data (bool, string, int, options)
603
+ - Provide sensible defaults in the renderer implementation
604
+ - Use empty `fields: []` if no configuration is needed
605
+
606
+ ### 3. Component Structure
607
+ - Keep renderer logic minimal - delegate to separate components
608
+ - Use hooks for complex logic (see `useAddressFinder`)
609
+ - Handle undefined/null values gracefully
610
+ - Always respect `control.disabled` state
611
+
612
+ ### 4. Styling
613
+ - Use `rendererClass()` to merge CSS classes properly
614
+ - Respect the `className` prop passed to your renderer
615
+ - Use Tailwind classes for consistency
616
+ - Consider responsive design (mobile vs desktop)
617
+
618
+ ### 5. Accessibility
619
+ - Add proper ARIA attributes
620
+ - Support keyboard navigation where appropriate
621
+ - Use semantic HTML elements
622
+ - Provide proper labels and error messages
623
+
624
+ ### 6. Performance
625
+ - Use `useMemo` for expensive computations
626
+ - Use `useControlEffect` instead of `useEffect` for control changes
627
+ - Avoid unnecessary re-renders
628
+ - Consider virtualization for long lists
629
+
630
+ ### 7. Type Safety
631
+ - Always define TypeScript interfaces for options
632
+ - Use proper type assertions with `as` operator
633
+ - Leverage Control<T> types from `@react-typed-forms/core`
634
+
635
+ ### 8. Testing Considerations
636
+ - Test with disabled state
637
+ - Test with null/undefined values
638
+ - Test validation errors
639
+ - Test responsive behavior
640
+
641
+ ## Common Patterns
642
+
643
+ ### Accessing Form Data
644
+
645
+ ```typescript
646
+ // Current control value
647
+ const value = control.value;
648
+
649
+ // Set value
650
+ control.setValue("new value");
651
+
652
+ // Access parent data node
653
+ const parentNode = getRootDataNode(dataNode);
654
+ const rootControl = parentNode.control;
655
+
656
+ // Access child fields (for object controls)
657
+ const fields = control.fields;
658
+ const childValue = fields.someField.value;
659
+ ```
660
+
661
+ ### Conditional Rendering
662
+
663
+ ```typescript
664
+ const MyRenderer = createDataRenderer(
665
+ (props) => {
666
+ const { control, renderOptions } = props;
667
+ const options = renderOptions as MyOptions;
668
+
669
+ if (!options.enabled) {
670
+ return <span>{control.value}</span>; // Read-only fallback
671
+ }
672
+
673
+ return <input ... />; // Full editing control
674
+ },
675
+ { renderType: MyOptions.value }
676
+ );
677
+ ```
678
+
679
+ ### Handling Complex State
680
+
681
+ ```typescript
682
+ function MyComplexRenderer({ dataProps }: { dataProps: DataRendererProps }) {
683
+ // Use custom hooks for complex logic
684
+ const { state, actions } = useMyCustomLogic(dataProps.control);
685
+
686
+ // Use control effects for reactive updates
687
+ useControlEffect(
688
+ () => dataProps.control.value,
689
+ (newValue) => {
690
+ // React to value changes
691
+ actions.handleValueChange(newValue);
692
+ }
693
+ );
694
+
695
+ return <div>...</div>;
696
+ }
697
+ ```
698
+
699
+ ### Accessing Renderer Utilities
700
+
701
+ ```typescript
702
+ const MyRenderer = createDataRenderer(
703
+ (props, renderer) => {
704
+ // Access HTML elements
705
+ const { Div, Span } = renderer.html;
706
+
707
+ // Render labels
708
+ const labelElement = renderer.renderLabelText("My Label");
709
+
710
+ // Render children
711
+ const children = renderer.renderChildren(...);
712
+
713
+ return <Div>...</Div>;
714
+ },
715
+ { renderType: MyOptions.value }
716
+ );
717
+ ```
718
+
719
+ ## Debugging Tips
720
+
721
+ 1. **Renderer not showing up:**
722
+ - Check that `renderType` matches the `value` in CustomRenderOptions
723
+ - Verify the renderer is registered in `createStdRenderer`
724
+ - Ensure the extension is added to `EditorExtension`
725
+
726
+ 2. **Configuration not working:**
727
+ - Check the interface matches the `buildSchema` definition
728
+ - Verify the type assertion in the renderer: `renderOptions as MyOptions`
729
+ - Check for typos in field names
730
+
731
+ 3. **Control value not updating:**
732
+ - Use `control.setValue()` not direct assignment
733
+ - Check if control is disabled
734
+ - Verify the control is not being recreated
735
+
736
+ 4. **Styling issues:**
737
+ - Use `rendererClass()` to merge classes
738
+ - Check parent container styles
739
+ - Verify Tailwind classes are valid
740
+
741
+ ## References
742
+
743
+ ### Key Imports
744
+
745
+ ```typescript
746
+ // Core schema functions
747
+ import {
748
+ createDataRenderer,
749
+ createGroupRenderer,
750
+ createAdornmentRenderer,
751
+ createLabelRenderer,
752
+ createDisplayRenderer,
753
+ createFormRenderer,
754
+ buildSchema,
755
+ CustomRenderOptions,
756
+ ControlDefinitionExtension,
757
+ } from "@react-typed-forms/schemas";
758
+
759
+ // Control management
760
+ import { Control, useControlEffect } from "@react-typed-forms/core";
761
+
762
+ // Field types
763
+ import {
764
+ boolField,
765
+ stringField,
766
+ intField,
767
+ stringOptionsField,
768
+ } from "@react-typed-forms/schemas";
769
+ ```
770
+
771
+ ### File Locations
772
+
773
+ - **Extension Definitions**: `ServiceTasAPI/NewClientApp/client-common/formExtensions.ts`
774
+ - **Main Renderer**: `ServiceTasAPI/NewClientApp/client-common/renderer.tsx`
775
+ - **Custom Renderers**: `ServiceTasAPI/NewClientApp/client-common/renderer/`
776
+
777
+ ### Related Documentation
778
+
779
+ - `@react-typed-forms/schemas` - Core form schema library
780
+ - `@react-typed-forms/core` - Form control management
781
+ - `@astroapps/schemas-*` - Pre-built renderer packages