@djangocfg/ui-tools 2.1.320 → 2.1.322
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/JsonSchemaForm-OSPUUUHM.cjs +13 -0
- package/dist/{JsonSchemaForm-IIYKSH6X.cjs.map → JsonSchemaForm-OSPUUUHM.cjs.map} +1 -1
- package/dist/JsonSchemaForm-TSLX2GRO.mjs +4 -0
- package/dist/{JsonSchemaForm-RN3XWSWX.mjs.map → JsonSchemaForm-TSLX2GRO.mjs.map} +1 -1
- package/dist/{chunk-L37FZYJU.cjs → chunk-4IW7GZFQ.cjs} +189 -74
- package/dist/chunk-4IW7GZFQ.cjs.map +1 -0
- package/dist/{chunk-JUGQNNDC.mjs → chunk-EXGXUK2N.mjs} +190 -76
- package/dist/chunk-EXGXUK2N.mjs.map +1 -0
- package/dist/index.cjs +28 -24
- package/dist/index.d.cts +240 -206
- package/dist/index.d.ts +240 -206
- package/dist/index.mjs +2 -2
- package/package.json +6 -6
- package/src/index.ts +15 -0
- package/src/tools/JsonForm/JsonForm.story.tsx +217 -1
- package/src/tools/JsonForm/JsonSchemaForm.tsx +15 -4
- package/src/tools/JsonForm/README.md +268 -0
- package/src/tools/JsonForm/index.ts +22 -1
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +28 -5
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +110 -3
- package/src/tools/JsonForm/types.ts +37 -5
- package/src/tools/JsonForm/utils.ts +25 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +6 -11
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +20 -12
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +9 -5
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +6 -10
- package/src/tools/JsonForm/widgets/_useWidgetEnv.ts +43 -0
- package/dist/JsonSchemaForm-IIYKSH6X.cjs +0 -13
- package/dist/JsonSchemaForm-RN3XWSWX.mjs +0 -4
- package/dist/chunk-JUGQNNDC.mjs.map +0 -1
- package/dist/chunk-L37FZYJU.cjs.map +0 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { ChevronDown } from 'lucide-react';
|
|
3
|
+
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
4
4
|
import React, { useState } from 'react';
|
|
5
5
|
|
|
6
6
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@djangocfg/ui-core/components';
|
|
7
7
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
8
8
|
import { ObjectFieldTemplateProps } from '@rjsf/utils';
|
|
9
9
|
|
|
10
|
+
import type { JsonFormDensity, UiGroup } from '../types';
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Object field template for JSON Schema Form
|
|
12
14
|
*
|
|
@@ -34,12 +36,16 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|
|
34
36
|
required,
|
|
35
37
|
uiSchema,
|
|
36
38
|
} = props;
|
|
39
|
+
const formContext = (props as { formContext?: { density?: JsonFormDensity } }).formContext;
|
|
37
40
|
|
|
38
41
|
// UI options
|
|
39
42
|
const isCollapsible = uiSchema?.['ui:collapsible'] === true;
|
|
40
43
|
const defaultCollapsed = uiSchema?.['ui:collapsed'] === true;
|
|
41
44
|
const gridCols = uiSchema?.['ui:grid'] as number | undefined;
|
|
42
45
|
const className = uiSchema?.['ui:className'] as string | undefined;
|
|
46
|
+
const uiGroups = uiSchema?.['ui:groups'] as readonly UiGroup[] | undefined;
|
|
47
|
+
const density = (formContext?.density as JsonFormDensity | undefined) ?? 'comfortable';
|
|
48
|
+
const compact = density === 'compact';
|
|
43
49
|
|
|
44
50
|
// Collapsible state
|
|
45
51
|
const [isOpen, setIsOpen] = useState(!defaultCollapsed);
|
|
@@ -50,10 +56,18 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|
|
50
56
|
// Grid class based on columns
|
|
51
57
|
const gridClass = gridCols
|
|
52
58
|
? `grid gap-4 grid-cols-${gridCols}`
|
|
53
|
-
:
|
|
59
|
+
: compact
|
|
60
|
+
? 'space-y-2'
|
|
61
|
+
: 'space-y-4';
|
|
62
|
+
|
|
63
|
+
// When `ui:groups` is specified, render listed fields inside collapsible
|
|
64
|
+
// sub-sections; remaining (ungrouped) fields render flat above the groups.
|
|
65
|
+
const groupedContent = uiGroups
|
|
66
|
+
? renderUiGroups({ properties, uiGroups, gridClass, className, compact })
|
|
67
|
+
: null;
|
|
54
68
|
|
|
55
69
|
// Content wrapper
|
|
56
|
-
const content = (
|
|
70
|
+
const content = groupedContent ?? (
|
|
57
71
|
<div className={cn(gridClass, className)}>
|
|
58
72
|
{properties.map((element) => (
|
|
59
73
|
<div key={element.name} className="property-wrapper">
|
|
@@ -114,3 +128,96 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|
|
114
128
|
</div>
|
|
115
129
|
);
|
|
116
130
|
}
|
|
131
|
+
|
|
132
|
+
interface RenderUiGroupsOptions {
|
|
133
|
+
properties: ObjectFieldTemplateProps['properties'];
|
|
134
|
+
uiGroups: readonly UiGroup[];
|
|
135
|
+
gridClass: string;
|
|
136
|
+
className?: string;
|
|
137
|
+
compact: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function renderUiGroups({
|
|
141
|
+
properties,
|
|
142
|
+
uiGroups,
|
|
143
|
+
gridClass,
|
|
144
|
+
className,
|
|
145
|
+
compact,
|
|
146
|
+
}: RenderUiGroupsOptions) {
|
|
147
|
+
const propsByName = new Map(properties.map((p) => [p.name, p]));
|
|
148
|
+
const groupedFieldNames = new Set(uiGroups.flatMap((g) => g.fields));
|
|
149
|
+
// Fields not listed in any group render flat above the groups.
|
|
150
|
+
const ungrouped = properties.filter((p) => !groupedFieldNames.has(p.name));
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className={cn(compact ? 'space-y-2' : 'space-y-3', className)}>
|
|
154
|
+
{ungrouped.length > 0 ? (
|
|
155
|
+
<div className={gridClass}>
|
|
156
|
+
{ungrouped.map((element) => (
|
|
157
|
+
<div key={element.name} className="property-wrapper">
|
|
158
|
+
{element.content}
|
|
159
|
+
</div>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
) : null}
|
|
163
|
+
|
|
164
|
+
{uiGroups.map((group) => {
|
|
165
|
+
const groupProps = group.fields
|
|
166
|
+
.map((name) => propsByName.get(name))
|
|
167
|
+
.filter((p): p is ObjectFieldTemplateProps['properties'][number] => Boolean(p));
|
|
168
|
+
if (groupProps.length === 0) return null;
|
|
169
|
+
return (
|
|
170
|
+
<UiGroupSection
|
|
171
|
+
key={group.title}
|
|
172
|
+
group={group}
|
|
173
|
+
gridClass={gridClass}
|
|
174
|
+
compact={compact}
|
|
175
|
+
>
|
|
176
|
+
{groupProps.map((element) => (
|
|
177
|
+
<div key={element.name} className="property-wrapper">
|
|
178
|
+
{element.content}
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</UiGroupSection>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface UiGroupSectionProps {
|
|
189
|
+
group: UiGroup;
|
|
190
|
+
gridClass: string;
|
|
191
|
+
compact: boolean;
|
|
192
|
+
children: React.ReactNode;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function UiGroupSection({ group, gridClass, compact, children }: UiGroupSectionProps) {
|
|
196
|
+
const [open, setOpen] = useState(group.defaultOpen ?? true);
|
|
197
|
+
return (
|
|
198
|
+
<Collapsible open={open} onOpenChange={setOpen}>
|
|
199
|
+
<CollapsibleTrigger
|
|
200
|
+
className={cn(
|
|
201
|
+
'flex w-full items-center gap-1.5 py-1 transition-colors hover:text-foreground',
|
|
202
|
+
compact
|
|
203
|
+
? 'text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70'
|
|
204
|
+
: 'text-xs font-semibold uppercase tracking-wide text-muted-foreground',
|
|
205
|
+
)}
|
|
206
|
+
>
|
|
207
|
+
<ChevronRight
|
|
208
|
+
className={cn(
|
|
209
|
+
'h-3 w-3 shrink-0 transition-transform duration-200',
|
|
210
|
+
open && 'rotate-90',
|
|
211
|
+
)}
|
|
212
|
+
aria-hidden
|
|
213
|
+
/>
|
|
214
|
+
<span>{group.title}</span>
|
|
215
|
+
</CollapsibleTrigger>
|
|
216
|
+
<CollapsibleContent className="overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down">
|
|
217
|
+
<div className={cn('border-l border-border/40 pl-3 pb-2 pt-1', gridClass)}>
|
|
218
|
+
{children}
|
|
219
|
+
</div>
|
|
220
|
+
</CollapsibleContent>
|
|
221
|
+
</Collapsible>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import type { RJSFSchema, UiSchema } from '@rjsf/utils';
|
|
2
2
|
import type { IChangeEvent, FormProps } from '@rjsf/core';
|
|
3
|
+
import type {
|
|
4
|
+
CustomJsonSchema7,
|
|
5
|
+
CustomJsonUiSchema7,
|
|
6
|
+
CustomJsonUiDisabledWhenRule,
|
|
7
|
+
CustomJsonUiGroup,
|
|
8
|
+
} from '@djangocfg/ui-core/lib';
|
|
9
|
+
|
|
10
|
+
/** Visual density for form controls. */
|
|
11
|
+
export type JsonFormDensity = 'comfortable' | 'compact';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Aliases that point to the portable schema types in `@djangocfg/ui-core/lib`.
|
|
15
|
+
* Kept under their old short names for back-compat — new code can import the
|
|
16
|
+
* `CustomJson*` originals directly from ui-core.
|
|
17
|
+
*/
|
|
18
|
+
export type DisabledWhenRule = CustomJsonUiDisabledWhenRule;
|
|
19
|
+
export type UiGroup = CustomJsonUiGroup;
|
|
20
|
+
|
|
21
|
+
/** What we put into RJSF's `formContext` so widgets/templates can react to global form state. */
|
|
22
|
+
export interface JsonFormContext {
|
|
23
|
+
density: JsonFormDensity;
|
|
24
|
+
/** Latest form data — used by `evaluateDisabledWhen`. */
|
|
25
|
+
formData: unknown;
|
|
26
|
+
}
|
|
3
27
|
|
|
4
28
|
/**
|
|
5
29
|
* JSON Schema Form props interface
|
|
6
30
|
*/
|
|
7
|
-
export interface JsonSchemaFormProps<T = any>
|
|
8
|
-
|
|
9
|
-
|
|
31
|
+
export interface JsonSchemaFormProps<T = any>
|
|
32
|
+
extends Omit<Partial<FormProps<T>>, 'schema' | 'uiSchema'> {
|
|
33
|
+
/**
|
|
34
|
+
* JSON Schema that defines the form structure. Accepts either RJSF's
|
|
35
|
+
* `RJSFSchema` directly or our portable `CustomJsonSchema7` from
|
|
36
|
+
* `@djangocfg/ui-core/lib` — both are cast to RJSF internally.
|
|
37
|
+
*/
|
|
38
|
+
schema: RJSFSchema | CustomJsonSchema7;
|
|
10
39
|
|
|
11
|
-
/** UI Schema for customizing the form appearance */
|
|
12
|
-
uiSchema?: UiSchema;
|
|
40
|
+
/** UI Schema for customizing the form appearance. Same dual-shape acceptance as `schema`. */
|
|
41
|
+
uiSchema?: UiSchema | CustomJsonUiSchema7;
|
|
13
42
|
|
|
14
43
|
/** Initial form data */
|
|
15
44
|
formData?: T;
|
|
@@ -43,6 +72,9 @@ export interface JsonSchemaFormProps<T = any> extends Partial<FormProps<T>> {
|
|
|
43
72
|
|
|
44
73
|
/** Submit button text */
|
|
45
74
|
submitButtonText?: string;
|
|
75
|
+
|
|
76
|
+
/** Visual density preset. `'compact'` shrinks rows and hides description text (moves it to label `title=` tooltip). */
|
|
77
|
+
density?: JsonFormDensity;
|
|
46
78
|
}
|
|
47
79
|
|
|
48
80
|
/**
|
|
@@ -212,3 +212,28 @@ export function validateRequiredFields(
|
|
|
212
212
|
function getNestedValue(obj: any, path: string): any {
|
|
213
213
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
214
214
|
}
|
|
215
|
+
|
|
216
|
+
import type { DisabledWhenRule } from './types';
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Evaluates a `ui:disabledWhen` rule against form data. Returns `true` when the
|
|
220
|
+
* field should be disabled. If `rule` is undefined, returns `false`.
|
|
221
|
+
*
|
|
222
|
+
* Supported rule shapes:
|
|
223
|
+
* { path, eq } | { path, notEq } | { path, in } | { path, notIn }
|
|
224
|
+
* | { path, truthy: true } | { path, falsy: true }
|
|
225
|
+
*/
|
|
226
|
+
export function evaluateDisabledWhen(
|
|
227
|
+
rule: DisabledWhenRule | undefined,
|
|
228
|
+
formData: unknown,
|
|
229
|
+
): boolean {
|
|
230
|
+
if (!rule) return false;
|
|
231
|
+
const value = getNestedValue(formData, rule.path);
|
|
232
|
+
if ('eq' in rule) return value === rule.eq;
|
|
233
|
+
if ('notEq' in rule) return value !== rule.notEq;
|
|
234
|
+
if ('in' in rule) return rule.in.includes(value);
|
|
235
|
+
if ('notIn' in rule) return !rule.notIn.includes(value);
|
|
236
|
+
if ('truthy' in rule) return Boolean(value);
|
|
237
|
+
if ('falsy' in rule) return !value;
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
@@ -3,21 +3,15 @@ import React from 'react';
|
|
|
3
3
|
import { Checkbox } from '@djangocfg/ui-core/components';
|
|
4
4
|
import { WidgetProps } from '@rjsf/utils';
|
|
5
5
|
|
|
6
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Checkbox widget for JSON Schema Form
|
|
8
10
|
* Handles boolean fields
|
|
9
11
|
*/
|
|
10
12
|
export function CheckboxWidget(props: WidgetProps) {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
value,
|
|
14
|
-
disabled,
|
|
15
|
-
readonly,
|
|
16
|
-
autofocus,
|
|
17
|
-
onChange,
|
|
18
|
-
onBlur,
|
|
19
|
-
onFocus,
|
|
20
|
-
} = props;
|
|
13
|
+
const { id, value, autofocus, onChange, onBlur, onFocus } = props;
|
|
14
|
+
const { disabled, tooltipText } = useWidgetEnv(props);
|
|
21
15
|
|
|
22
16
|
const handleChange = (checked: boolean) => {
|
|
23
17
|
onChange(checked);
|
|
@@ -27,11 +21,12 @@ export function CheckboxWidget(props: WidgetProps) {
|
|
|
27
21
|
<Checkbox
|
|
28
22
|
id={id}
|
|
29
23
|
checked={value || false}
|
|
30
|
-
disabled={disabled
|
|
24
|
+
disabled={disabled}
|
|
31
25
|
autoFocus={autofocus}
|
|
32
26
|
onCheckedChange={handleChange}
|
|
33
27
|
onBlur={() => onBlur(id, value)}
|
|
34
28
|
onFocus={() => onFocus(id, value)}
|
|
29
|
+
title={tooltipText}
|
|
35
30
|
/>
|
|
36
31
|
);
|
|
37
32
|
}
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from '@djangocfg/ui-core/components';
|
|
8
8
|
import { WidgetProps } from '@rjsf/utils';
|
|
9
9
|
|
|
10
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Select dropdown widget for JSON Schema Form
|
|
12
14
|
* Handles enum fields
|
|
@@ -17,8 +19,6 @@ export function SelectWidget(props: WidgetProps) {
|
|
|
17
19
|
options,
|
|
18
20
|
value,
|
|
19
21
|
required,
|
|
20
|
-
disabled,
|
|
21
|
-
readonly,
|
|
22
22
|
autofocus,
|
|
23
23
|
onChange,
|
|
24
24
|
onBlur,
|
|
@@ -26,6 +26,7 @@ export function SelectWidget(props: WidgetProps) {
|
|
|
26
26
|
placeholder,
|
|
27
27
|
rawErrors,
|
|
28
28
|
} = props;
|
|
29
|
+
const { disabled, compact, tooltipText } = useWidgetEnv(props);
|
|
29
30
|
|
|
30
31
|
// Safely extract and validate enum options
|
|
31
32
|
const enumOptions = useMemo(() => {
|
|
@@ -38,7 +39,9 @@ export function SelectWidget(props: WidgetProps) {
|
|
|
38
39
|
return rawErrors && rawErrors.length > 0;
|
|
39
40
|
}, [rawErrors]);
|
|
40
41
|
|
|
41
|
-
//
|
|
42
|
+
// Empty-string enum values are handled by the ui-core <Select> wrapper
|
|
43
|
+
// (which substitutes a Radix-safe sentinel internally), so we can pass
|
|
44
|
+
// values through verbatim here.
|
|
42
45
|
const safeValue = useMemo(() => {
|
|
43
46
|
if (value === null || value === undefined) return '';
|
|
44
47
|
return String(value);
|
|
@@ -60,8 +63,9 @@ export function SelectWidget(props: WidgetProps) {
|
|
|
60
63
|
onChange={(e) => onChange(e.target.value)}
|
|
61
64
|
onBlur={(e) => onBlur(id, e.target.value)}
|
|
62
65
|
onFocus={(e) => onFocus(id, e.target.value)}
|
|
63
|
-
disabled={disabled
|
|
64
|
-
readOnly={
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
readOnly={disabled}
|
|
68
|
+
title={tooltipText}
|
|
65
69
|
className={`flex h-10 w-full rounded-md border ${
|
|
66
70
|
hasError ? 'border-destructive' : 'border-input'
|
|
67
71
|
} bg-transparent px-3 py-2 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm`}
|
|
@@ -74,23 +78,27 @@ export function SelectWidget(props: WidgetProps) {
|
|
|
74
78
|
<Select
|
|
75
79
|
value={safeValue}
|
|
76
80
|
onValueChange={handleChange}
|
|
77
|
-
disabled={disabled
|
|
81
|
+
disabled={disabled}
|
|
78
82
|
required={required}
|
|
79
83
|
>
|
|
80
84
|
<SelectTrigger
|
|
81
85
|
id={id}
|
|
82
|
-
className={hasError ? 'border-destructive' : ''}
|
|
86
|
+
className={`${hasError ? 'border-destructive' : ''} ${compact ? 'h-7 text-xs' : ''}`.trim()}
|
|
83
87
|
autoFocus={autofocus}
|
|
84
88
|
onFocus={() => onFocus(id, value)}
|
|
89
|
+
title={tooltipText}
|
|
85
90
|
>
|
|
86
91
|
<SelectValue placeholder={placeholder || 'Select an option'} />
|
|
87
92
|
</SelectTrigger>
|
|
88
93
|
<SelectContent>
|
|
89
|
-
{enumOptions.map((option: any) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
{enumOptions.map((option: any) => {
|
|
95
|
+
const optValue = String(option.value);
|
|
96
|
+
return (
|
|
97
|
+
<SelectItem key={optValue || '__empty__'} value={optValue}>
|
|
98
|
+
{option.label || optValue}
|
|
99
|
+
</SelectItem>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
94
102
|
</SelectContent>
|
|
95
103
|
</Select>
|
|
96
104
|
);
|
|
@@ -6,6 +6,8 @@ import { Input, Slider } from '@djangocfg/ui-core/components';
|
|
|
6
6
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
7
7
|
import { WidgetProps } from '@rjsf/utils';
|
|
8
8
|
|
|
9
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Slider widget for JSON Schema Form
|
|
11
13
|
*
|
|
@@ -33,14 +35,13 @@ import { WidgetProps } from '@rjsf/utils';
|
|
|
33
35
|
export function SliderWidget(props: WidgetProps) {
|
|
34
36
|
const {
|
|
35
37
|
id,
|
|
36
|
-
disabled,
|
|
37
|
-
readonly,
|
|
38
38
|
value,
|
|
39
39
|
onChange,
|
|
40
40
|
schema,
|
|
41
41
|
options,
|
|
42
42
|
rawErrors,
|
|
43
43
|
} = props;
|
|
44
|
+
const { disabled, tooltipText } = useWidgetEnv(props);
|
|
44
45
|
|
|
45
46
|
// Extract configuration
|
|
46
47
|
const config = useMemo(() => {
|
|
@@ -112,11 +113,14 @@ export function SliderWidget(props: WidgetProps) {
|
|
|
112
113
|
}, [numericValue, config.unit]);
|
|
113
114
|
|
|
114
115
|
return (
|
|
115
|
-
<div
|
|
116
|
+
<div
|
|
117
|
+
className={cn('flex items-center gap-3', hasError && 'text-destructive')}
|
|
118
|
+
title={tooltipText}
|
|
119
|
+
>
|
|
116
120
|
{/* Slider */}
|
|
117
121
|
<Slider
|
|
118
122
|
id={id}
|
|
119
|
-
disabled={disabled
|
|
123
|
+
disabled={disabled}
|
|
120
124
|
value={[numericValue]}
|
|
121
125
|
onValueChange={handleSliderChange}
|
|
122
126
|
min={config.min}
|
|
@@ -132,7 +136,7 @@ export function SliderWidget(props: WidgetProps) {
|
|
|
132
136
|
value={displayValue}
|
|
133
137
|
onChange={handleInputChange}
|
|
134
138
|
disabled={disabled}
|
|
135
|
-
readOnly={
|
|
139
|
+
readOnly={disabled}
|
|
136
140
|
className={cn(
|
|
137
141
|
'w-20 text-center font-mono text-sm',
|
|
138
142
|
hasError && 'border-destructive'
|
|
@@ -3,20 +3,15 @@ import React from 'react';
|
|
|
3
3
|
import { Switch } from '@djangocfg/ui-core/components';
|
|
4
4
|
import { WidgetProps } from '@rjsf/utils';
|
|
5
5
|
|
|
6
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Switch toggle widget for JSON Schema Form
|
|
8
10
|
* Alternative boolean input
|
|
9
11
|
*/
|
|
10
12
|
export function SwitchWidget(props: WidgetProps) {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
value,
|
|
14
|
-
disabled,
|
|
15
|
-
readonly,
|
|
16
|
-
onChange,
|
|
17
|
-
onBlur,
|
|
18
|
-
onFocus,
|
|
19
|
-
} = props;
|
|
13
|
+
const { id, value, onChange, onBlur, onFocus } = props;
|
|
14
|
+
const { disabled, tooltipText } = useWidgetEnv(props);
|
|
20
15
|
|
|
21
16
|
const handleChange = (checked: boolean) => {
|
|
22
17
|
onChange(checked);
|
|
@@ -26,10 +21,11 @@ export function SwitchWidget(props: WidgetProps) {
|
|
|
26
21
|
<Switch
|
|
27
22
|
id={id}
|
|
28
23
|
checked={value || false}
|
|
29
|
-
disabled={disabled
|
|
24
|
+
disabled={disabled}
|
|
30
25
|
onCheckedChange={handleChange}
|
|
31
26
|
onBlur={() => onBlur(id, value)}
|
|
32
27
|
onFocus={() => onFocus(id, value)}
|
|
28
|
+
title={tooltipText}
|
|
33
29
|
/>
|
|
34
30
|
);
|
|
35
31
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helper that resolves widget-level environment from RJSF props:
|
|
3
|
+
* - density (from formContext, default 'comfortable')
|
|
4
|
+
* - effective `disabled` flag (combines props.disabled with `ui:disabledWhen`)
|
|
5
|
+
*
|
|
6
|
+
* Keeps each widget terse — no need to repeat the formContext + disabledWhen
|
|
7
|
+
* boilerplate.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { WidgetProps } from '@rjsf/utils';
|
|
11
|
+
|
|
12
|
+
import { evaluateDisabledWhen } from '../utils';
|
|
13
|
+
import type { DisabledWhenRule, JsonFormDensity } from '../types';
|
|
14
|
+
|
|
15
|
+
export interface WidgetEnv {
|
|
16
|
+
density: JsonFormDensity;
|
|
17
|
+
compact: boolean;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
/** Title-attribute text — in compact mode the description moves into a tooltip on the label/control. */
|
|
20
|
+
tooltipText?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useWidgetEnv(props: WidgetProps): WidgetEnv {
|
|
24
|
+
const { disabled, readonly, formContext, uiSchema, schema } = props;
|
|
25
|
+
const density: JsonFormDensity =
|
|
26
|
+
(formContext?.density as JsonFormDensity | undefined) ?? 'comfortable';
|
|
27
|
+
const compact = density === 'compact';
|
|
28
|
+
|
|
29
|
+
const disabledWhen = uiSchema?.['ui:disabledWhen'] as DisabledWhenRule | undefined;
|
|
30
|
+
const disabledByRule = evaluateDisabledWhen(disabledWhen, formContext?.formData);
|
|
31
|
+
|
|
32
|
+
const tooltipText = compact
|
|
33
|
+
? (uiSchema?.['ui:description'] as string | undefined)
|
|
34
|
+
?? (schema?.description as string | undefined)
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
density,
|
|
39
|
+
compact,
|
|
40
|
+
disabled: Boolean(disabled || readonly || disabledByRule),
|
|
41
|
+
tooltipText,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkL37FZYJU_cjs = require('./chunk-L37FZYJU.cjs');
|
|
4
|
-
require('./chunk-WGEGR3DF.cjs');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Object.defineProperty(exports, "JsonSchemaForm", {
|
|
9
|
-
enumerable: true,
|
|
10
|
-
get: function () { return chunkL37FZYJU_cjs.JsonSchemaForm; }
|
|
11
|
-
});
|
|
12
|
-
//# sourceMappingURL=JsonSchemaForm-IIYKSH6X.cjs.map
|
|
13
|
-
//# sourceMappingURL=JsonSchemaForm-IIYKSH6X.cjs.map
|