diff --git a/.changeset/7087-disabled-twin-symmetry.md b/.changeset/7087-disabled-twin-symmetry.md new file mode 100644 index 000000000..a4e1a68a5 --- /dev/null +++ b/.changeset/7087-disabled-twin-symmetry.md @@ -0,0 +1,35 @@ +--- +'@object-ui/types': minor +--- + +`disabled` accepts a predicate string — `boolean | string`, the `BaseSchema` union — on +the 18 concrete schemas that used to narrow it back to `boolean` (objectui#7087, +maintainer ruling 2026-09-01: option 1, scoped to `disabled`). + +`visible` and `disabled` are twins: objectui#4581 widened both on `BaseSchema` on the same +evidence — `SchemaRenderer` evaluates both through `evaluator.evaluateCondition` rather +than reading either as a boolean. After that widening, 0 of the 124 `extends BaseSchema` +interfaces redeclared `visible`, while 18 still carried a pre-widening +`disabled?: boolean` of their own, with matching `z.boolean()` mirrors. So +`disabled: "${data.status === 'locked'}"` — the capability the renderer implements and +the base type advertises — was a type error and a zod refusal on `ButtonSchema`, +`InputSchema`, `TextareaSchema`, `SelectSchema`, `CheckboxSchema`, `RadioGroupSchema`, +`SwitchSchema`, `ToggleSchema`, `SliderSchema`, `FileUploadSchema`, `DatePickerSchema`, +`CalendarSchema`, `InputOTPSchema`, `FormSchema`, `ComboboxSchema`, `ActionSchema`, +`CollapsibleSchema` and `ToggleGroupSchema`. + +Those 18 redeclarations are removed, on both faces. The interfaces inherit +`BaseSchema.disabled` the way they always inherited `visible`; the zod mirrors inherit +`base.zod.ts`'s `z.union([z.boolean(), z.string()])` through `.extend()`'s merged +`.shape`, so there is no second spelling of the union to drift from — the route +`ChatbotSchema` took in objectui#6169. + +**Additive for authors**: a predicate string is now accepted where it was refused; every +boolean that parsed before parses unchanged, and a number is still refused at path +`disabled`. Runtime behaviour does not change — the renderer already evaluated both twins. + +**Out of scope, per the ruling**: `label` (29 narrowings) and `description` (32) carry +`string | I18nLabel` i18n semantics and wait for their own ruling; the independent +`disabled?: boolean` declarations on shapes that do not extend `BaseSchema` +(`SelectOption`, `RadioOption`, `FormField`, `ComboboxOption`, `AccordionItem`, +`ToggleGroupItem`, and the rest of that family) are not narrowings and are untouched. diff --git a/packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts b/packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts new file mode 100644 index 000000000..9a00a7da7 --- /dev/null +++ b/packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts @@ -0,0 +1,264 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * `disabled` is inherited from `BaseSchema` — `boolean | string` — on every + * concrete schema that used to narrow it back to `boolean` (objectui#7087, + * maintainer ruling 2026-09-01: option 1, scoped to `disabled`). + * + * ## What was narrowed, and why the narrowing was wrong + * + * `visible` and `disabled` are twins. objectui#4581 widened both on `BaseSchema` + * to `boolean | string` on the same evidence: `SchemaRenderer` reads neither key + * as a boolean, it routes both through `evaluator.evaluateCondition`, declared + * `(condition: string | boolean | undefined, context?) => boolean`. After that + * widening, 0 of the 124 `extends BaseSchema` interfaces redeclared `visible`, + * while 18 kept a pre-widening `disabled?: boolean` of their own — 15 in + * `form.ts`, `ActionSchema` in `crud.ts`, `CollapsibleSchema` and + * `ToggleGroupSchema` in `disclosure.ts` — and their zod mirrors carried the + * matching `z.boolean()`. So `disabled: "${data.status === 'locked'}"`, the + * capability the renderer implements and `BaseSchema` advertises, was a type + * error and a zod refusal on exactly the schemas an author reaches for first. + * + * The fix is the one `ChatbotSchema` already took (objectui#6169): the key is + * not redeclared at all. The TS interface inherits the member the way `visible` + * always has, and `.extend()`'s `.shape` merges the parent's fields into the + * child's, so the base union reaches every mirror without a second spelling of + * it that could drift. + * + * ## What this file pins + * + * 1. Type level — for each of the 18, `X['disabled']` is EXACTLY + * `boolean | string | undefined`, invariantly. `Equal`, not `extends`: the + * narrow `boolean` is assignable to the wide union, so a one-way check + * stays green on a narrowing that was never removed; and `BaseSchema`'s + * `[key: string]: any` index signature means an interface that LOST the + * member reads `any`, which a one-way check also accepts. `visible` is + * asserted beside it as the twin control, so a schema that dropped both + * keys cannot pass vacuously. + * 2. Runtime — each of the 18 zod mirrors `safeParse`s a predicate string on + * `disabled` (and on `visible`, the control) and still refuses a number at + * path `disabled`. Every fixture is also parsed WITHOUT `disabled`, so a + * refusal cannot be a broken fixture wearing a green. + * 3. Scope guard — the ruling widens NARROWINGS, not independent + * declarations. The six `disabled?: boolean` shapes in these same three + * files that do not extend `BaseSchema` (`SelectOption`, `RadioOption`, + * `FormField`, `ComboboxOption`, `AccordionItem`, `ToggleGroupItem`) stay + * `boolean` on both faces. + * + * ## Predictions, written before the first run (red-first) + * + * With any one of the three source files (or its mirror) reverted to its + * `origin/main` @ `67dadd602` blob and this file in place: + * + * - TS face reverted: `tsc -p packages/types/tsconfig.test.json` reports + * TS2344 on `assertionDisabledIsBaseUnion`, naming that file's interfaces; + * `assertionVisibleTwinControl` stays clean (nothing narrowed `visible`). + * - zod face reverted: the `accepts a predicate string on disabled` cases for + * that file's mirrors fail (`success: false`), the `visible` control and the + * `refuses a number` cases for the same mirrors stay green, and every other + * file's cases stay green. + * + * The measured counts are in the PR that landed this file. + */ + +import { describe, expect, it } from 'vitest'; +import type { ZodType } from 'zod'; + +import type { BaseSchema } from '../base'; +import type { ActionSchema } from '../crud'; +import type { + AccordionItem, + CollapsibleSchema, + ToggleGroupItem, + ToggleGroupSchema, +} from '../disclosure'; +import type { + ButtonSchema, + CalendarSchema, + CheckboxSchema, + ComboboxOption, + ComboboxSchema, + DatePickerSchema, + FileUploadSchema, + FormField, + FormSchema, + InputOTPSchema, + InputSchema, + RadioGroupSchema, + RadioOption, + SelectOption, + SelectSchema, + SliderSchema, + SwitchSchema, + TextareaSchema, + ToggleSchema, +} from '../form'; + +import { ActionSchema as ActionMirror } from '../zod/crud.zod'; +import { + AccordionItemSchema, + CollapsibleSchema as CollapsibleMirror, + ToggleGroupItemSchema, + ToggleGroupSchema as ToggleGroupMirror, +} from '../zod/disclosure.zod'; +import * as FormMirrors from '../zod/form.zod'; + +/* ── Type-level helpers ──────────────────────────────────────────────────── */ + +/** Invariant equality — `extends` both ways would accept a narrowing. */ +type Equal< A, B > = + (< T >() => T extends A ? 1 : 2) extends (< T >() => T extends B ? 1 : 2) ? true : false; +type Expect< T extends true > = T; + +/** The union both twins carry on `BaseSchema`, pinned here so the checks below cannot drift from it. */ +type BasePredicate = boolean | string | undefined; +export type assertionBaseDisabled = Expect< Equal< BaseSchema['disabled'], BasePredicate > >; +export type assertionBaseVisible = Expect< Equal< BaseSchema['visible'], BasePredicate > >; + +/* ── 1. The 18 formerly-narrowed interfaces inherit the base union ───────── */ + +type InScope = { + ButtonSchema: ButtonSchema; + InputSchema: InputSchema; + TextareaSchema: TextareaSchema; + SelectSchema: SelectSchema; + CheckboxSchema: CheckboxSchema; + RadioGroupSchema: RadioGroupSchema; + SwitchSchema: SwitchSchema; + ToggleSchema: ToggleSchema; + SliderSchema: SliderSchema; + FileUploadSchema: FileUploadSchema; + DatePickerSchema: DatePickerSchema; + CalendarSchema: CalendarSchema; + InputOTPSchema: InputOTPSchema; + FormSchema: FormSchema; + ComboboxSchema: ComboboxSchema; + ActionSchema: ActionSchema; + CollapsibleSchema: CollapsibleSchema; + ToggleGroupSchema: ToggleGroupSchema; +}; + +/** The names whose `K` is NOT exactly the base union — `never` when nothing narrows (or loses) it. */ +type NotBaseUnion< K extends 'disabled' | 'visible' > = { + [N in keyof InScope]: Equal< InScope[N][K], BasePredicate > extends true ? never : N; +}[keyof InScope]; + +export type assertionDisabledIsBaseUnion = Expect< Equal< NotBaseUnion<'disabled'>, never > >; +/** Twin control: `visible` was never narrowed, so this stays `never` on every tree. */ +export type assertionVisibleTwinControl = Expect< Equal< NotBaseUnion<'visible'>, never > >; + +/* ── 3. Scope guard: independent declarations are not narrowings ─────────── */ + +type OutOfScope = { + SelectOption: SelectOption; + RadioOption: RadioOption; + FormField: FormField; + ComboboxOption: ComboboxOption; + AccordionItem: AccordionItem; + ToggleGroupItem: ToggleGroupItem; +}; + +type Widened = { + [N in keyof OutOfScope]: Equal< OutOfScope[N]['disabled'], boolean | undefined > extends true ? never : N; +}[keyof OutOfScope]; + +export type assertionIndependentDeclarationsStayBoolean = Expect< Equal< Widened, never > >; + +/* ── 2. Runtime: the zod mirrors accept what the interfaces now declare ──── */ + +const PREDICATE = '${data.status === "locked"}'; + +interface MirrorCase { + name: string; + mirror: ZodType; + /** Minimal valid node — every required key, nothing optional. */ + fixture: Record; +} + +const IN_SCOPE: readonly MirrorCase[] = [ + { name: 'form.zod.ts#ButtonSchema', mirror: FormMirrors.ButtonSchema, fixture: { type: 'button' } }, + { name: 'form.zod.ts#InputSchema', mirror: FormMirrors.InputSchema, fixture: { type: 'input' } }, + { name: 'form.zod.ts#TextareaSchema', mirror: FormMirrors.TextareaSchema, fixture: { type: 'textarea' } }, + { name: 'form.zod.ts#SelectSchema', mirror: FormMirrors.SelectSchema, fixture: { type: 'select', options: [{ label: 'A', value: 'a' }] } }, + { name: 'form.zod.ts#CheckboxSchema', mirror: FormMirrors.CheckboxSchema, fixture: { type: 'checkbox' } }, + { name: 'form.zod.ts#RadioGroupSchema', mirror: FormMirrors.RadioGroupSchema, fixture: { type: 'radio-group', options: [{ label: 'A', value: 'a' }] } }, + { name: 'form.zod.ts#SwitchSchema', mirror: FormMirrors.SwitchSchema, fixture: { type: 'switch' } }, + { name: 'form.zod.ts#ToggleSchema', mirror: FormMirrors.ToggleSchema, fixture: { type: 'toggle' } }, + { name: 'form.zod.ts#SliderSchema', mirror: FormMirrors.SliderSchema, fixture: { type: 'slider' } }, + { name: 'form.zod.ts#FileUploadSchema', mirror: FormMirrors.FileUploadSchema, fixture: { type: 'file-upload' } }, + { name: 'form.zod.ts#DatePickerSchema', mirror: FormMirrors.DatePickerSchema, fixture: { type: 'date-picker' } }, + { name: 'form.zod.ts#CalendarSchema', mirror: FormMirrors.CalendarSchema, fixture: { type: 'calendar' } }, + { name: 'form.zod.ts#InputOTPSchema', mirror: FormMirrors.InputOTPSchema, fixture: { type: 'input-otp' } }, + { name: 'form.zod.ts#FormSchema', mirror: FormMirrors.FormSchema, fixture: { type: 'form', fields: [{ name: 'title', type: 'text' }] } }, + { name: 'form.zod.ts#ComboboxSchema', mirror: FormMirrors.ComboboxSchema, fixture: { type: 'combobox', options: [{ label: 'A', value: 'a' }] } }, + { name: 'crud.zod.ts#ActionSchema', mirror: ActionMirror, fixture: { type: 'action', label: 'Save' } }, + { + name: 'disclosure.zod.ts#CollapsibleSchema', + mirror: CollapsibleMirror, + fixture: { type: 'collapsible', trigger: { type: 'button', label: 'More' }, content: { type: 'text', text: 'Body' } }, + }, + { name: 'disclosure.zod.ts#ToggleGroupSchema', mirror: ToggleGroupMirror, fixture: { type: 'toggle-group' } }, +]; + +const OUT_OF_SCOPE: readonly MirrorCase[] = [ + { name: 'form.zod.ts#SelectOptionSchema', mirror: FormMirrors.SelectOptionSchema, fixture: { label: 'A', value: 'a' } }, + { name: 'form.zod.ts#RadioOptionSchema', mirror: FormMirrors.RadioOptionSchema, fixture: { label: 'A', value: 'a' } }, + { name: 'form.zod.ts#ComboboxOptionSchema', mirror: FormMirrors.ComboboxOptionSchema, fixture: { label: 'A', value: 'a' } }, + { name: 'form.zod.ts#FormFieldSchema', mirror: FormMirrors.FormFieldSchema, fixture: { name: 'title' } }, + { name: 'disclosure.zod.ts#AccordionItemSchema', mirror: AccordionItemSchema, fixture: { value: 'a', title: 'A', content: { type: 'text', text: 'Body' } } }, + { name: 'disclosure.zod.ts#ToggleGroupItemSchema', mirror: ToggleGroupItemSchema, fixture: { value: 'a', label: 'A' } }, +]; + +const issuePaths = (r: ReturnType): string[] => + r.success ? [] : r.error.issues.map((issue) => issue.path.map(String).join('.')); + +describe('`disabled` is the BaseSchema union on every formerly-narrowed mirror (objectui#7087)', () => { + it('covers the 18 interfaces the ruling names, and the 6 it excludes', () => { + // The census the ruling was made on. A mirror added to or dropped from + // either list changes the ruling's population and must say so here. + expect(IN_SCOPE).toHaveLength(18); + expect(OUT_OF_SCOPE).toHaveLength(6); + }); + + it.each(IN_SCOPE)('$name: the fixture is valid on its own', ({ mirror, fixture }) => { + // Control: the refusals below are about `disabled`, not about a fixture + // that never parsed. + expect(issuePaths(mirror.safeParse(fixture))).toEqual([]); + }); + + it.each(IN_SCOPE)('$name accepts a predicate string on `disabled`', ({ mirror, fixture }) => { + const r = mirror.safeParse({ ...fixture, disabled: PREDICATE }); + expect(issuePaths(r)).toEqual([]); + expect(r.success && (r.data as { disabled?: unknown }).disabled).toBe(PREDICATE); + }); + + it.each(IN_SCOPE)('$name accepts a predicate string on `visible` — the twin control', ({ mirror, fixture }) => { + const r = mirror.safeParse({ ...fixture, visible: PREDICATE }); + expect(issuePaths(r)).toEqual([]); + expect(r.success && (r.data as { visible?: unknown }).visible).toBe(PREDICATE); + }); + + it.each(IN_SCOPE)('$name still refuses a number on `disabled`', ({ mirror, fixture }) => { + const r = mirror.safeParse({ ...fixture, disabled: 1 }); + expect(r.success).toBe(false); + expect(issuePaths(r)).toContain('disabled'); + }); + + it.each(IN_SCOPE)('$name still accepts the boolean form — a widening, not a replacement', ({ mirror, fixture }) => { + const r = mirror.safeParse({ ...fixture, disabled: true }); + expect(issuePaths(r)).toEqual([]); + expect(r.success && (r.data as { disabled?: unknown }).disabled).toBe(true); + }); +}); + +describe('scope guard: the independent `disabled?: boolean` declarations are not narrowings (objectui#7087)', () => { + it.each(OUT_OF_SCOPE)('$name: the fixture is valid on its own', ({ mirror, fixture }) => { + expect(issuePaths(mirror.safeParse(fixture))).toEqual([]); + }); + + it.each(OUT_OF_SCOPE)('$name still refuses a string on `disabled`', ({ mirror, fixture }) => { + const r = mirror.safeParse({ ...fixture, disabled: PREDICATE }); + expect(r.success).toBe(false); + expect(issuePaths(r)).toContain('disabled'); + }); +}); diff --git a/packages/types/src/crud.ts b/packages/types/src/crud.ts index 86d04b9b3..a12b46c38 100644 --- a/packages/types/src/crud.ts +++ b/packages/types/src/crud.ts @@ -83,10 +83,6 @@ export interface ActionSchema extends BaseSchema { * Action variant */ variant?: 'default' | 'outline' | 'ghost' | 'link'; - /** - * Whether action is disabled - */ - disabled?: boolean; /** * Action type * Enhanced in Phase 2 with 'ajax', 'confirm', 'dialog' diff --git a/packages/types/src/disclosure.ts b/packages/types/src/disclosure.ts index 6e0636905..ef7d8b2da 100644 --- a/packages/types/src/disclosure.ts +++ b/packages/types/src/disclosure.ts @@ -103,10 +103,6 @@ export interface CollapsibleSchema extends BaseSchema { * Controlled open state */ open?: boolean; - /** - * Whether collapsible is disabled - */ - disabled?: boolean; /** * Open state change handler */ @@ -167,10 +163,6 @@ export interface ToggleGroupSchema extends BaseSchema { * Controlled selected value(s) */ value?: string | string[]; - /** - * Whether toggle group is disabled - */ - disabled?: boolean; /** * Change handler */ diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 6cfbfc28a..0da4d8669 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -36,10 +36,6 @@ export interface ButtonSchema extends BaseSchema { * @default 'default' */ size?: 'default' | 'sm' | 'lg' | 'icon'; - /** - * Whether button is disabled - */ - disabled?: boolean; /** * Whether button is in loading state */ @@ -102,10 +98,6 @@ export interface InputSchema extends BaseSchema { * Whether field is required */ required?: boolean; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Whether field is readonly */ @@ -181,10 +173,6 @@ export interface TextareaSchema extends BaseSchema { * Whether field is required */ required?: boolean; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Whether field is readonly */ @@ -240,10 +228,6 @@ export interface SelectSchema extends BaseSchema { * Whether field is required */ required?: boolean; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -333,10 +317,6 @@ export interface CheckboxSchema extends BaseSchema { * Controlled checked state */ checked?: boolean; - /** - * Whether checkbox is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -393,10 +373,6 @@ export interface RadioGroupSchema extends BaseSchema { * @default 'vertical' */ orientation?: 'horizontal' | 'vertical'; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -456,10 +432,6 @@ export interface SwitchSchema extends BaseSchema { * Controlled checked state */ checked?: boolean; - /** - * Whether switch is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -487,10 +459,6 @@ export interface ToggleSchema extends BaseSchema { * Controlled pressed state */ pressed?: boolean; - /** - * Whether toggle is disabled - */ - disabled?: boolean; /** * Toggle variant * @default 'default' @@ -547,10 +515,6 @@ export interface SliderSchema extends BaseSchema { * @default 1 */ step?: number; - /** - * Whether slider is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -592,10 +556,6 @@ export interface FileUploadSchema extends BaseSchema { * Maximum number of files (for multiple) */ maxFiles?: number; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -663,10 +623,6 @@ export interface DatePickerSchema extends BaseSchema { * @default 'PPP' */ format?: string; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -707,10 +663,6 @@ export interface CalendarSchema extends BaseSchema { * Maximum selectable date */ maxDate?: Date | string; - /** - * Whether calendar is disabled - */ - disabled?: boolean; /** * Change handler */ @@ -743,10 +695,6 @@ export interface InputOTPSchema extends BaseSchema { * Controlled value */ value?: string; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Help text or description */ @@ -1211,10 +1159,6 @@ export interface FormSchema extends BaseSchema { * @default false */ resetOnSubmit?: boolean; - /** - * Whether form is disabled - */ - disabled?: boolean; /** * Form mode * @default 'edit' @@ -1381,10 +1325,6 @@ export interface ComboboxSchema extends BaseSchema { * Controlled value */ value?: string; - /** - * Whether field is disabled - */ - disabled?: boolean; /** * Help text or description */ diff --git a/packages/types/src/zod/crud.zod.ts b/packages/types/src/zod/crud.zod.ts index ea87942fc..381981172 100644 --- a/packages/types/src/zod/crud.zod.ts +++ b/packages/types/src/zod/crud.zod.ts @@ -73,7 +73,6 @@ export const ActionSchema: z.ZodType = z.lazy(() => BaseSchema.extend({ level: z.enum(['primary', 'secondary', 'success', 'warning', 'danger', 'info', 'default']).optional().default('default').describe('Action type/level'), icon: z.string().optional().describe('Icon to display (lucide-react icon name)'), variant: z.enum(['default', 'outline', 'ghost', 'link']).optional().describe('Action variant'), - disabled: z.boolean().optional().describe('Whether action is disabled'), actionType: z.enum(['button', 'link', 'dropdown', 'ajax', 'confirm', 'dialog']).optional().describe('Action type'), api: z.string().optional().describe('API endpoint to call (for ajax actions)'), method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional().default('POST').describe('HTTP method'), diff --git a/packages/types/src/zod/disclosure.zod.ts b/packages/types/src/zod/disclosure.zod.ts index c13bde9d6..2be0e9644 100644 --- a/packages/types/src/zod/disclosure.zod.ts +++ b/packages/types/src/zod/disclosure.zod.ts @@ -52,7 +52,6 @@ export const CollapsibleSchema = BaseSchema.extend({ content: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).describe('Collapsible content'), defaultOpen: z.boolean().optional().describe('Default open state'), open: z.boolean().optional().describe('Controlled open state'), - disabled: z.boolean().optional().describe('Whether collapsible is disabled'), onOpenChange: z.function().optional().describe('Open change handler'), }); @@ -76,7 +75,6 @@ export const ToggleGroupSchema = BaseSchema.extend({ items: z.array(ToggleGroupItemSchema).optional().describe('Toggle group items'), defaultValue: z.union([z.string(), z.array(z.string())]).optional().describe('Default value(s)'), value: z.union([z.string(), z.array(z.string())]).optional().describe('Controlled value(s)'), - disabled: z.boolean().optional().describe('Whether toggle group is disabled'), onValueChange: z.function().optional().describe('Value change handler'), }); diff --git a/packages/types/src/zod/form.zod.ts b/packages/types/src/zod/form.zod.ts index cdb8efcee..59fc851e2 100644 --- a/packages/types/src/zod/form.zod.ts +++ b/packages/types/src/zod/form.zod.ts @@ -172,7 +172,6 @@ export const ButtonSchema = BaseSchema.extend({ .optional() .default('default') .describe('Button size'), - disabled: z.boolean().optional().describe('Whether button is disabled'), loading: z.boolean().optional().describe('Whether button is in loading state'), icon: z.string().optional().describe('Icon to display (lucide-react icon name)'), iconPosition: z.enum(['left', 'right']).optional().default('left').describe('Icon position'), @@ -204,7 +203,6 @@ export const InputSchema = BaseSchema.extend({ defaultValue: z.union([z.string(), z.number()]).optional().describe('Default value'), value: z.union([z.string(), z.number()]).optional().describe('Controlled value'), required: z.boolean().optional().describe('Whether field is required'), - disabled: z.boolean().optional().describe('Whether field is disabled'), readOnly: z.boolean().optional().describe('Whether field is read-only'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), @@ -228,7 +226,6 @@ export const TextareaSchema = BaseSchema.extend({ value: z.string().optional().describe('Controlled value'), rows: z.number().optional().describe('Number of visible rows'), required: z.boolean().optional().describe('Whether field is required'), - disabled: z.boolean().optional().describe('Whether field is disabled'), readOnly: z.boolean().optional().describe('Whether field is read-only'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), @@ -248,7 +245,6 @@ export const SelectSchema = BaseSchema.extend({ value: z.union([z.string(), z.number(), z.boolean()]).optional().describe('Controlled value'), options: z.array(SelectOptionSchema).describe('Select options'), required: z.boolean().optional().describe('Whether field is required'), - disabled: z.boolean().optional().describe('Whether field is disabled'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), onChange: z.function().optional().describe('Change handler'), @@ -263,7 +259,6 @@ export const CheckboxSchema = BaseSchema.extend({ label: z.string().optional().describe('Checkbox label'), defaultChecked: z.boolean().optional().describe('Default checked state'), checked: z.boolean().optional().describe('Controlled checked state'), - disabled: z.boolean().optional().describe('Whether checkbox is disabled'), required: z.boolean().optional() .describe("Required affordance, read at renderers/form/checkbox.tsx:45 (`required=` on the Radix Checkbox) and :49 (gates the label's `*` marker) (objectui#6150)"), description: z.string().optional().describe('Help text'), @@ -282,7 +277,6 @@ export const RadioGroupSchema = BaseSchema.extend({ value: z.union([z.string(), z.number()]).optional().describe('Controlled value'), options: z.array(RadioOptionSchema).describe('Radio options'), orientation: z.enum(['horizontal', 'vertical']).optional().describe('Layout orientation'), - disabled: z.boolean().optional().describe('Whether radio group is disabled'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), onChange: z.function().optional().describe('Change handler'), @@ -297,7 +291,6 @@ export const SwitchSchema = BaseSchema.extend({ label: z.string().optional().describe('Switch label'), defaultChecked: z.boolean().optional().describe('Default checked state'), checked: z.boolean().optional().describe('Controlled checked state'), - disabled: z.boolean().optional().describe('Whether switch is disabled'), description: z.string().optional().describe('Help text'), onChange: z.function().optional().describe('Change handler'), }); @@ -310,7 +303,6 @@ export const ToggleSchema = BaseSchema.extend({ label: z.string().optional().describe('Toggle label'), defaultPressed: z.boolean().optional().describe('Default pressed state'), pressed: z.boolean().optional().describe('Controlled pressed state'), - disabled: z.boolean().optional().describe('Whether toggle is disabled'), variant: z.enum(['default', 'outline']).optional().describe('Toggle variant'), size: z.enum(['default', 'sm', 'lg']).optional().describe('Toggle size'), onChange: z.function().optional().describe('Change handler'), @@ -329,7 +321,6 @@ export const SliderSchema = BaseSchema.extend({ min: z.number().optional().describe('Minimum value'), max: z.number().optional().describe('Maximum value'), step: z.number().optional().describe('Step value'), - disabled: z.boolean().optional().describe('Whether slider is disabled'), description: z.string().optional().describe('Help text'), onChange: z.function().optional().describe('Change handler'), }); @@ -349,7 +340,6 @@ export const FileUploadSchema = BaseSchema.extend({ multiple: z.boolean().optional().describe('Allow multiple files'), maxSize: z.number().optional().describe('Maximum file size (bytes)'), maxFiles: z.number().optional().describe('Maximum number of files'), - disabled: z.boolean().optional().describe('Whether upload is disabled'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), onChange: z.function().optional().describe('Change handler'), @@ -368,7 +358,6 @@ export const DatePickerSchema = BaseSchema.extend({ minDate: z.union([z.string(), z.date()]).optional().describe('Minimum date'), maxDate: z.union([z.string(), z.date()]).optional().describe('Maximum date'), format: z.string().optional().describe('Date format string'), - disabled: z.boolean().optional().describe('Whether date picker is disabled'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), onChange: z.function().optional().describe('Change handler'), @@ -384,7 +373,6 @@ export const CalendarSchema = BaseSchema.extend({ mode: z.enum(['single', 'multiple', 'range']).optional().describe('Selection mode'), minDate: z.union([z.string(), z.date()]).optional().describe('Minimum date'), maxDate: z.union([z.string(), z.date()]).optional().describe('Maximum date'), - disabled: z.boolean().optional().describe('Whether calendar is disabled'), onChange: z.function().optional().describe('Change handler'), }); @@ -398,7 +386,6 @@ export const InputOTPSchema = BaseSchema.extend({ length: z.number().optional().describe('Number of OTP digits'), defaultValue: z.string().optional().describe('Default value'), value: z.string().optional().describe('Controlled value'), - disabled: z.boolean().optional().describe('Whether OTP input is disabled'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), onChange: z.function().optional().describe('Change handler'), @@ -416,7 +403,6 @@ export const ComboboxSchema = BaseSchema.extend({ options: z.array(ComboboxOptionSchema).describe('Combobox options'), defaultValue: z.string().optional().describe('Default value'), value: z.string().optional().describe('Controlled value'), - disabled: z.boolean().optional().describe('Whether combobox is disabled'), description: z.string().optional().describe('Help text'), error: z.string().optional().describe('Error message'), onChange: z.function().optional().describe('Change handler'), @@ -646,7 +632,6 @@ export const FormSchema = BaseSchema.extend({ columns: z.number().optional().describe('Number of columns (for grid layout)'), validationMode: z.enum(['onSubmit', 'onChange', 'onBlur', 'onTouched', 'all']).optional().describe('Validation mode'), resetOnSubmit: z.boolean().optional().describe('Reset form on successful submit'), - disabled: z.boolean().optional().describe('Disable entire form'), mode: z.enum(['create', 'edit', 'view']).optional().describe('Form mode'), actions: z.array(z.any()).optional().describe('Custom actions'), onSubmit: z.function().optional().describe('Submit handler'),