vue-formless 0.1.0 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -1,123 +1,88 @@
1
- import { AllowedComponentProps } from 'vue';
2
1
  import { Component } from 'vue';
3
- import { ComponentCustomProps } from 'vue';
4
- import { ComponentOptionsBase } from 'vue';
5
2
  import { ComponentOptionsMixin } from 'vue';
6
3
  import { ComponentProvideOptions } from 'vue';
7
- import { CreateComponentPublicInstanceWithMixins } from 'vue';
8
4
  import { DefineComponent } from 'vue';
9
5
  import { ExtractPropTypes } from 'vue';
10
- import { GlobalComponents } from 'vue';
11
- import { GlobalDirectives } from 'vue';
12
- import { InjectionKey } from 'vue';
13
6
  import { PropType } from 'vue';
14
7
  import { PublicProps } from 'vue';
15
- import { Slot } from 'vue';
16
8
  import { VNodeChild } from 'vue';
17
- import { VNodeProps } from 'vue';
18
9
 
19
- export declare function applyControlBinding(formModel: unknown, binding: ResolvedControlBinding, update: (prop: string, value: unknown) => void): Record<string, unknown>;
20
-
21
- /** Slice a multi-port binding to one v-model port (ADR-013 `useFormItem('start')`). */
22
- export declare function bindingForPort(binding: ResolvedControlBinding, port: string): ResolvedControlBinding;
23
-
24
- export declare type CamelToPascal<S extends string> = S extends `${infer F}${infer R}` ? `${Uppercase<F>}${R}` : S;
25
-
26
- /** `name` → `Name`, `idCard` → `IdCard` */
27
- export declare function camelToPascal(key: string): string;
28
-
29
- export declare type ColPlace = 'auto' | 'start' | 'end';
30
-
31
- /** Author-facing Col width: omit / `'Nx'` / `'max'` / absolute 1–24. */
32
- export declare type ColSpanSpec = string | number;
10
+ declare type ColSpan = IntRange<1, typeof GRID_TOTAL>;
33
11
 
34
12
  /**
35
13
  * Public `$props` of a Vue constructor, functional component, or SFC.
36
- * `never` / omitted widgets yield `{}` (`never extends Constructor` is true in TS).
14
+ * `never` / omitted controls yield `{}` (`never extends Constructor` is true in TS).
37
15
  */
38
- export declare type ComponentPublicProps<C> = [C] extends [never] ? {} : [C] extends [undefined] ? {} : C extends abstract new (...args: any) => {
16
+ declare type ComponentPublicProps<C> = [C] extends [never] ? {} : [C] extends [undefined] ? {} : C extends abstract new (...args: any) => {
39
17
  $props: infer P;
40
18
  } ? P : C extends (props: infer P, ...args: any) => unknown ? P : C extends {
41
19
  $props: infer P;
42
20
  } ? P : {};
43
21
 
44
- export declare interface ControlBindingOverrides {
45
- prop?: ControlProp;
46
- }
47
-
48
- export declare type ControlItemSetting = boolean | 'self';
49
-
50
- export declare type ControlProp = string | readonly string[];
51
-
52
22
  /**
53
- * Control identity. Adapter extras (e.g. `label`) via `declare module 'vue-formless'`.
54
- * Extra keys become `ItemFl` fields and optional `fl:*` tag props.
23
+ * One field's declaration: the input of `createFormField` / each entry of
24
+ * `createFormFields` (design.md §11). It extends the augmentable
25
+ * `FormFieldCustomOptions`, so a consumer's `label` / `validation` joins the
26
+ * same shape and flows into the `FormFieldFormless` snapshot and the `fl:*` tag
27
+ * props.
28
+ *
29
+ * `component` stays `unknown` on purpose: a field table can pass any control
30
+ * and let the tag infer that control's public props.
55
31
  */
56
- export declare interface ControlSchema {
32
+ export declare interface CreateFormFieldOptions extends FormFieldCustomOptions {
57
33
  /**
58
- * Input widget only (no FormItem). Receives v-model bindings from formless.
59
- * Widget may also declare static `formless: { model, item }`.
34
+ * The control only (no host Item). Receives v-model bindings from formless.
35
+ * A control may also declare static `formless: { model, item, field }`.
60
36
  */
61
- component?: Component;
62
- /** Input defaults: static object, or derived from the cell snapshot. */
63
- props?: HostProps<ItemFl>;
37
+ component?: unknown;
38
+ /** Control defaults: static object, or derived from the field snapshot. */
39
+ props?: HostProps<FormFieldFormless>;
64
40
  /**
65
- * v-model names on the widget (ADR-011). Default `'modelValue'`.
66
- * Locked with the component; tag cannot override. Prefer widget `formless.model`.
41
+ * v-model names on the control (design.md §7.1). Default `'modelValue'`.
42
+ * Locked with the component; tag cannot override. Prefer control `formless.model`.
67
43
  */
68
- model?: ControlVModel;
44
+ model?: FormFieldVModelRaw;
69
45
  /**
70
- * Location(s) from FormView root (ADR-011). Default: control key.
46
+ * Location(s) from FormView root (design.md §7.1). Default: field key.
71
47
  * Overridable via `:fl:prop`. Empty string is illegal.
72
48
  * Nested: `buyers[0].name`, `` `buyers[${$index}].name` ``.
73
49
  */
74
- prop?: ControlProp;
50
+ prop?: FormFieldPropRaw;
51
+ /**
52
+ * Host Item shell for this field: FormView default, then this, then tag `:fl:item`.
53
+ * Boolean only (design.md §9). Not “skip FormField”.
54
+ */
55
+ item?: boolean;
75
56
  /**
76
- * Outer wrap: FormView default, then this, then tag `:fl:item`.
77
- * `'self'`: widget lays out cells via `useFormItem(port)` (ADR-017).
57
+ * Assembly placement (design.md §8). Omit = `'auto'`.
58
+ * Schema and tag merge by nearest-wins; the control is **not** a merge layer —
59
+ * its static `formless.field` is read at render and folded in (`auto`).
78
60
  */
79
- item?: boolean | 'self';
61
+ field?: FormFieldFormlessFieldRaw;
62
+ /** Debug-only component name; `createFormFields` injects the table key. */
63
+ name?: string;
80
64
  }
81
65
 
82
- /** Adapter fields on ControlSchema (everything except kernel keys). */
83
- declare type ControlSchemaExtras = Omit<ControlSchema, ControlSchemaKernelKey>;
84
-
85
- /**
86
- * Constraint for `createFormControls` only. `component` stays `unknown` so object
87
- * literals keep `typeof ElInput` instead of widening to Vue's `Component`.
88
- */
89
- declare type ControlSchemaInput = Omit<ControlSchema, 'component'> & {
90
- component?: unknown;
91
- };
92
-
93
- /** Kernel-owned ControlSchema keys. Not extras; tags already have matching `fl:*` where allowed. */
94
- declare type ControlSchemaKernelKey = 'component' | 'props' | 'model' | 'prop' | 'item';
95
-
96
- export declare type ControlVModel = string | readonly string[];
97
-
98
66
  /**
99
- * Build a static namespaced control table (ADR-009).
100
- * Schema keys are camelCase control names → `<User.TimeRange />`.
67
+ * Build a static namespaced field table (design.md §11).
68
+ * Schema keys are camelCase (`timeRange`) or kebab-case (`time-range`) field
69
+ * names; only the **tag name** is normalized (`<User.TimeRange />`). The key as
70
+ * written still supplies the default `prop`.
101
71
  */
102
- export declare function createFormControls<const S extends {
103
- [K in keyof S]: ControlSchemaInput;
104
- }>(schema: S, options?: CreateFormControlsOptions): NamespacedControls<S>;
105
-
106
- export declare interface CreateFormControlsOptions {
107
- /** Defaults for every control in this cluster (static or from the cell snapshot). */
108
- props?: HostProps<ItemFl>;
109
- }
72
+ export declare function createFormFields<const S extends {
73
+ [K in keyof S]: CreateFormFieldOptions;
74
+ }>(options: S): FormFields<S>;
110
75
 
111
76
  /**
112
- * Bind host layout / form / item once; returns a FormView (ADR-008 / ADR-016).
77
+ * Bind host layout / form / item once; returns a FormView (design.md §10).
113
78
  *
114
- * Host shells stay in this closure. Cells go through `useFormItem` / `FormView.Item`.
79
+ * Host shells stay in this closure. Ad-hoc fields use `FormField`.
115
80
  *
116
81
  * @example
117
82
  * ```ts
118
83
  * export const FormView = createFormView({
119
- * layout: { Row: ElRow, Col: ElCol, column: 2, gutter: 16 },
120
- * form: { component: ElForm, props: (fl) => ({ model: fl.modelValue }) },
84
+ * layout: { Row: ElRow, Col: ElCol, props: { column: 2 } },
85
+ * form: { component: MyForm }, // MyForm declares `modelValue` → ElForm `model`
121
86
  * item: { component: ElFormItem, props: toEpItemProps },
122
87
  * })
123
88
  * ```
@@ -125,381 +90,296 @@ export declare interface CreateFormControlsOptions {
125
90
  export declare function createFormView(options?: CreateFormViewOptions): FormViewComponent;
126
91
 
127
92
  export declare interface CreateFormViewOptions {
128
- /** Row + Col for hosted grid, plus optional project density. */
93
+ /** Row + Col for the hosted grid, plus optional LayoutView props (density etc.). */
129
94
  layout?: FormViewLayoutBind;
130
95
  /** Host form shell. Omit or `:fl:form="false"` skips wrapping. */
131
- form?: FormViewHostBind<FormFl>;
132
- /** Host item shell. `props` are defaults (static or from the cell snapshot). */
133
- item?: FormViewHostBind<ItemFl>;
96
+ form?: FormViewFormBind;
97
+ /** Host item shell. `props` are defaults (static or from the field snapshot). */
98
+ item?: FormViewItemBind;
134
99
  }
135
100
 
136
- /**
137
- * Bind host Row/Col once. Returns LayoutView.
138
- * LayoutItem is not exported; cells call `useLayoutItem()`.
139
- */
101
+ /** Bind host Row/Col once. Returns LayoutView; items are `LayoutItem`. */
140
102
  export declare function createLayoutView(options?: CreateLayoutViewOptions): Component;
141
103
 
142
- export declare interface CreateLayoutViewOptions {
104
+ declare interface CreateLayoutViewOptions {
143
105
  Row?: Component;
144
106
  Col?: Component;
145
107
  column?: number;
146
108
  }
147
109
 
148
- /** Kernel column when factory and the LayoutView `column` prop are omitted. */
149
- export declare const DEFAULT_LAYOUT: {
150
- readonly column: 1;
151
- };
152
-
153
- /** Tag attrs: `label?: string` → `'fl:label'?: string`. Always optional (override, not required). */
154
- declare type FlExtraProps<T> = {
155
- [K in keyof T as K extends string ? `fl:${K}` : never]+?: T[K];
156
- };
110
+ declare type _Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N ? Acc[number] : _Enumerate<N, [...Acc, Acc['length']]>;
157
111
 
158
- export declare interface FormContext {
159
- /** Current FormView `modelValue` (parent snapshot; do not mutate). */
160
- model: unknown;
161
- /** Report a field write; FormView patches and emits `update:modelValue`. */
162
- update: (prop: string, value: unknown) => void;
112
+ /** Static `formless` bag a control may declare on `ComponentCustomOptions`. */
113
+ export declare interface FormControlFormless {
114
+ /** v-model ports on the control (design.md §7.1). */
115
+ model?: FormFieldVModelRaw;
163
116
  /**
164
- * FormView-owned Item shell.
165
- * Used by `useFormItem` / `FormView.Item`, not by widget authors.
117
+ * Composite marker (design.md §8): "my inner `<FormField>`s need an inner
118
+ * LayoutView whenever this field is boxed". Only `'embed'` is meaningful —
119
+ * it is the control's **nature**, not a placement, so it is read at render
120
+ * (`FormFieldCore`) and folded into `fl:field`, never merged as a preset layer.
166
121
  */
167
- wrap: WrapControl;
168
- /** This FormView layer's Item switch. */
169
- isItemEnabled: () => boolean;
170
- /** This FormView's `:fl:layout` switch (page, not nearest LayoutView). */
171
- isLayoutEnabled: () => boolean;
172
- /** Factory `createLayoutView` result; reused for gear-4 inner host. */
173
- LayoutView: Component;
174
- /** `createFormView({ layout })` density; inner extra rows use this, not the page `:row:*`. */
175
- factoryColumn: number;
176
- factoryGutter: number;
122
+ field?: 'embed';
177
123
  }
178
124
 
179
- export declare const formContextKey: InjectionKey<FormContext>;
125
+ /** Control props that may appear on `<User.Xxx />` (v-model ports stripped). */
126
+ declare type FormControlProps<Def> = Def extends {
127
+ component?: infer C;
128
+ } ? [Exclude<C, undefined>] extends [never] ? {} : Omit<ComponentPublicProps<Exclude<C, undefined>>, LockedKeysForDef<Def>> : {};
180
129
 
181
- export declare type FormControlComponent<P = {}> = DefineComponent<FormControlProps & P>;
130
+ export declare const FormField: FormFieldComponent<{}>;
182
131
 
183
- /** Kernel `fl:` / `col:` / `row:` keys on `<User.Xxx />`. Schema extras are prefixed automatically. */
184
- export declare type FormControlProps = {
185
- 'fl:prop'?: string | string[];
186
- 'fl:item'?: boolean;
187
- 'col:span'?: string | number;
188
- 'col:place'?: 'auto' | 'start' | 'end';
189
- 'row:column'?: number;
190
- 'row:gutter'?: number;
191
- } & FlExtraProps<ControlSchemaExtras>;
192
-
193
- /** Loose schema bag. Prefer inferring `S` from an object literal via `createFormControls`. */
194
- export declare type FormControlsSchema = Record<string, ControlSchema>;
195
-
196
- export declare interface FormFl {
197
- layout: boolean;
198
- form: boolean;
132
+ export declare type FormFieldComponent<P = {}> = DefineComponent<FormFieldProps & P>;
133
+
134
+ /**
135
+ * The single module-augmentation anchor for field extras (design.md §18), and
136
+ * the extras domain itself: `CreateFormFieldOptions` extends it, and both the
137
+ * `FormFieldFormless` snapshot and the `fl:*` tag props take their extras
138
+ * straight from it. Mirrors Vue's `ComponentCustomOptions`: declare `label` /
139
+ * `validation` here, and they land on the factory input, the snapshot, and the
140
+ * tag props. There is no separate `Omit`-derived "extras" alias — kernel keys
141
+ * live on `CreateFormFieldOptions` only.
142
+ */
143
+ export declare interface FormFieldCustomOptions {
144
+ }
145
+
146
+ /**
147
+ * The **normalized** per-field snapshot (design.md §10.1 / §16.3): what
148
+ * `item.props` / field `props` functions receive, and what `FormField` hands
149
+ * down to its host `FormItem`. Not passed as a host component prop.
150
+ *
151
+ * Adapters declare their extras once, on `FormFieldCustomOptions` (module
152
+ * augmentation, design.md §18); `extends FormFieldCustomOptions` pulls them in,
153
+ * so a declared `label` is `fl.label` in the snapshot **and** `:fl:label` on the
154
+ * tag.
155
+ *
156
+ * `model` / `prop` are this field's **normalized** binding arrays — index-aligned
157
+ * (`model[i] ↔ prop[i]`), never empty and `prop` no longer than `model`. The
158
+ * kernel sends no identity **name**: a host Item `prop` is the adapter's own
159
+ * encoding, so an adapter that cannot encode the field simply leaves it unbound.
160
+ *
161
+ * **Only the keys the kernel resolves are declared here** — `model` / `prop` /
162
+ * `field` / `item` — plus the consumer's declared extras, pulled in by
163
+ * `extends FormFieldCustomOptions`. The raw bag's other keys (a raw `component`,
164
+ * an unread `props`) still ride through at runtime, but they are **not** part of
165
+ * this type: the snapshot is a closed shape, so reading an undeclared key is a
166
+ * compile error rather than a silent `unknown`.
167
+ */
168
+ export declare interface FormFieldFormless extends FormFieldCustomOptions {
169
+ /** This field's v-model ports, index-aligned with `prop`. */
170
+ model: FormFieldVModel;
171
+ /** This field's locations, index-aligned with `model`; `undefined` when nothing bound them. */
172
+ prop: FormFieldProp | undefined;
173
+ /** Assembled placement (design.md §8). `'auto'` is resolved away, never sent. */
174
+ field: FormFieldFormlessField;
175
+ /**
176
+ * Host Item shell switch (design.md §9): the nearest FormView's page `fl:item`
177
+ * default ← this cell's `fl:item`, near wins; a bare attr (`''`) counts as
178
+ * `true`. Resolved in one place (`FormFieldCore`), so it is **always a boolean**
179
+ * and every consumer — the adapter `item.props` and the control `props`
180
+ * alike — sees the same value.
181
+ */
199
182
  item: boolean;
200
- /** FormView write-model; map to the host via `form.props` (e.g. `{ model: fl.modelValue }`). */
201
- modelValue: unknown;
202
183
  }
203
184
 
204
- export declare type FormFormProp = boolean | 'auto';
185
+ /** The **assembled** placement. `'auto'` is resolved away, never sent. */
186
+ declare type FormFieldFormlessField = 'wrap' | 'embed' | 'wrap-embed';
205
187
 
206
- /** Dot-notation path for ElFormItem `prop` (e.g. `buyers.0.name`). */
207
- export declare function formItemProp(prop: string): string;
188
+ /**
189
+ * The **written** placement — the three writable values. Omit = `'auto'`: defer
190
+ * to the control's static `formless.field`. A leaf resolves to `'wrap'`, but
191
+ * that value is never writable.
192
+ */
193
+ declare type FormFieldFormlessFieldRaw = 'auto' | 'embed' | 'wrap-embed';
208
194
 
209
- /** Factory `layout.column` overlay; gutter lives on `FormViewLayoutBind`. */
210
- export declare type FormLayoutOptions = Pick<FormViewLayoutBind, 'column'>;
195
+ declare type FormFieldProp = (string | undefined)[];
211
196
 
212
- /** FormView `:fl:layout` is a boolean switch. Density is factory / `:row:*`. */
213
- export declare type FormLayoutProp = boolean;
197
+ declare type FormFieldPropRaw = string | readonly string[];
214
198
 
215
199
  /**
216
- * Context-only FormView (no Row/Col/Form/Item). Prefer `createFormView({ layout: { Row, Col } })`.
200
+ * Public props of `<FormField>` / `<User.Xxx />`. Kernel `fl:` / `item:` /
201
+ * `layout:` / `layout-item:` keys on `<FormField>` / `<User.Xxx />`.
202
+ * Schema extras are prefixed automatically.
203
+ * `fl:model` declares the v-model ports at the identity root and selects one declared port inside it.
204
+ * `layout:column` is formless density; other `layout:*` (e.g. gutter) stay attrs and fall through to LayoutView → Row.
205
+ *
206
+ * The declaration's own keys are **mapped**, not retyped: every core key has a
207
+ * matching `fl:` tag key (design.md §6), so the tag cannot drift from
208
+ * `CreateFormFieldOptions`. Two keys stay off the tag — `props` (it may be a
209
+ * snapshot function, which cannot ride in an attr: it enters as the first
210
+ * `control` layer instead, §16.3) and `name` (factory-private). `component` is
211
+ * the one key whose *type* changes: the declaration keeps `unknown` so a field
212
+ * table can pass any control, while the tag supplies a real `Component`
213
+ * (ad-hoc field / factory preset, §7.4). Extras need no separate term — they
214
+ * are part of the declaration, so the same mapper prefixes them.
215
+ *
216
+ * The `layout:` / `layout-item:` keys are not retyped either: they are mapped
217
+ * from `@vue-formless/layout`'s own prop bags by the same `ToFormlessProps` —
218
+ * `LayoutViewProps` under `'layout:'` and `LayoutItemProps` under
219
+ * `'layout-item:'` — so density and placement have a single source and cannot
220
+ * drift from the layout package. Nothing is `Omit`-ed: the kernel overrides
221
+ * `layout:disabled` at the page it forwards the bag to (spread first, then
222
+ * `disabled={!fl:layout}`), not by dropping it from the channel.
217
223
  */
218
- export declare const FormView: {
219
- new (...args: any[]): CreateComponentPublicInstanceWithMixins<Readonly<ExtractPropTypes< {
220
- modelValue: {
221
- type: PropType<unknown>;
222
- default: undefined;
223
- };
224
- 'fl:layout': {
225
- type: PropType<FormLayoutProp>;
226
- default: boolean;
227
- };
228
- 'row:column': {
229
- type: NumberConstructor;
230
- default: undefined;
231
- };
232
- 'row:gutter': {
233
- type: NumberConstructor;
234
- default: undefined;
235
- };
236
- 'fl:form': {
237
- type: PropType<FormFormProp>;
238
- default: string;
239
- };
240
- 'fl:item': {
241
- type: BooleanConstructor;
242
- default: boolean;
243
- };
244
- }>> & Readonly<{
245
- "onUpdate:modelValue"?: ((_value: unknown) => any) | undefined;
246
- }>, () => VNodeChild, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
247
- 'update:modelValue': (_value: unknown) => boolean;
248
- }, PublicProps, {
249
- modelValue: undefined;
250
- 'fl:layout': boolean;
251
- 'row:column': number;
252
- 'row:gutter': number;
253
- 'fl:form': FormFormProp;
254
- 'fl:item': boolean;
255
- }, true, {}, {}, GlobalComponents, GlobalDirectives, string, {}, any, ComponentProvideOptions, {
256
- P: {};
257
- B: {};
258
- D: {};
259
- C: {};
260
- M: {};
261
- Defaults: {};
262
- }, Readonly<ExtractPropTypes< {
263
- modelValue: {
264
- type: PropType<unknown>;
265
- default: undefined;
266
- };
267
- 'fl:layout': {
268
- type: PropType<FormLayoutProp>;
269
- default: boolean;
270
- };
271
- 'row:column': {
272
- type: NumberConstructor;
273
- default: undefined;
274
- };
275
- 'row:gutter': {
276
- type: NumberConstructor;
277
- default: undefined;
278
- };
279
- 'fl:form': {
280
- type: PropType<FormFormProp>;
281
- default: string;
282
- };
283
- 'fl:item': {
284
- type: BooleanConstructor;
285
- default: boolean;
286
- };
287
- }>> & Readonly<{
288
- "onUpdate:modelValue"?: ((_value: unknown) => any) | undefined;
289
- }>, () => VNodeChild, {}, {}, {}, {
290
- modelValue: undefined;
291
- 'fl:layout': boolean;
292
- 'row:column': number;
293
- 'row:gutter': number;
294
- 'fl:form': FormFormProp;
295
- 'fl:item': boolean;
296
- }>;
297
- __isFragment?: never;
298
- __isTeleport?: never;
299
- __isSuspense?: never;
300
- } & ComponentOptionsBase<Readonly<ExtractPropTypes< {
301
- modelValue: {
302
- type: PropType<unknown>;
303
- default: undefined;
304
- };
305
- 'fl:layout': {
306
- type: PropType<FormLayoutProp>;
307
- default: boolean;
308
- };
309
- 'row:column': {
310
- type: NumberConstructor;
311
- default: undefined;
312
- };
313
- 'row:gutter': {
314
- type: NumberConstructor;
315
- default: undefined;
316
- };
317
- 'fl:form': {
318
- type: PropType<FormFormProp>;
319
- default: string;
320
- };
321
- 'fl:item': {
322
- type: BooleanConstructor;
323
- default: boolean;
324
- };
325
- }>> & Readonly<{
326
- "onUpdate:modelValue"?: ((_value: unknown) => any) | undefined;
327
- }>, () => VNodeChild, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
328
- 'update:modelValue': (_value: unknown) => boolean;
329
- }, string, {
330
- modelValue: undefined;
331
- 'fl:layout': boolean;
332
- 'row:column': number;
333
- 'row:gutter': number;
334
- 'fl:form': FormFormProp;
335
- 'fl:item': boolean;
336
- }, {}, string, {}, GlobalComponents, GlobalDirectives, string, ComponentProvideOptions> & VNodeProps & AllowedComponentProps & ComponentCustomProps & {
337
- Item: typeof FormViewItem;
338
- };
224
+ export declare type FormFieldProps = ToFormlessProps<Omit<CreateFormFieldOptions, 'props' | 'name' | 'component'> & {
225
+ component?: Component;
226
+ }> & ToFormlessProps<LayoutViewProps, 'layout:'> & ToFormlessProps<LayoutItemProps, 'layout-item:'>;
339
227
 
340
- export declare type FormViewComponent = Component & {
341
- Item: typeof FormViewItem;
228
+ /**
229
+ * PascalCase field tags. `S` must not be `Record<string, _>` or `keyof` collapses
230
+ * to `string` and Volar/TS lose `User.Name` / `User.IdCard` as named keys.
231
+ * Tag props are `fl:*` plus the control's public props (v-model ports locked).
232
+ *
233
+ * Keys may be camelCase (`timeRange`) or kebab-case (`time-range`); both land on
234
+ * the same tag (`<User.TimeRange />`). The runtime normalizes via
235
+ * `upperFirst(toCamel(key))`, so the type must mirror it exactly. Normalizing
236
+ * the tag is all this mapping does — `prop` is not derived from the tag name.
237
+ */
238
+ export declare type FormFields<S> = {
239
+ [K in keyof S & string as UpperFirst<ToCamel<K>>]: FormFieldComponent<FormControlProps<S[K]>>;
342
240
  };
343
241
 
344
- export declare interface FormViewHostBind<TFl> {
345
- component: Component;
346
- props?: HostProps<TFl>;
242
+ export declare interface FormFieldSlotProps {
243
+ $bindings: Record<string, unknown>;
347
244
  }
348
245
 
349
- /** Page-level anonymous cell. Same wrap as `useFormItem()`. */
350
- export declare const FormViewItem: FormViewItemComponent;
246
+ /**
247
+ * The **normalized** binding arrays. A port that is not a string stays as an
248
+ * `undefined` placeholder so the `model[i] ↔ prop[i]` alignment survives.
249
+ */
250
+ declare type FormFieldVModel = (string | undefined)[];
351
251
 
352
- declare type FormViewItemComponent = DefineComponent<FormViewItemProps>;
252
+ /**
253
+ * Field binding (design.md §7.1):
254
+ * - `model` — v-model names on the control (identity). Default `'modelValue'`.
255
+ * - `prop` — location(s) from FormView root (`name`, `buyers[0].name`).
256
+ *
257
+ * `prop` array pairs with `model` (prefix-aligned). Extra model ports are unbound.
258
+ */
259
+ declare type FormFieldVModelRaw = string | readonly string[];
353
260
 
354
- /** Kernel keys on `FormView.Item` / `useFormItem()`. Schema extras are prefixed automatically. */
355
- export declare type FormViewItemProps = {
356
- 'fl:prop'?: string | string[];
357
- 'col:span'?: string | number;
358
- 'col:place'?: 'auto' | 'start' | 'end';
359
- } & FlExtraProps<ControlSchemaExtras>;
261
+ export declare type FormViewComponent = DefineComponent<FormViewProps>;
360
262
 
361
- export declare interface FormViewItemSlotProps {
362
- /** `applyControlBinding` result (`modelValue` + `onUpdate:modelValue` for a single leaf). */
363
- field: Record<string, unknown>;
263
+ /**
264
+ * Host Form shell. `props` are **static** defaults: formless has no form-level
265
+ * snapshot to project from — the write model reaches the host as the bare
266
+ * `modelValue` attr (no-prefix rule, design.md §10.1). A library whose model
267
+ * port is named differently (`ElForm` → `model`) wraps the host in its own
268
+ * component that declares `modelValue` and forwards it (`MyForm`).
269
+ */
270
+ declare interface FormViewFormBind {
271
+ component: Component;
272
+ props?: Record<string, unknown>;
364
273
  }
365
274
 
366
- export declare interface FormViewLayoutBind {
275
+ declare type FormViewFormProp = boolean | 'auto';
276
+
277
+ /**
278
+ * Host item shell. `props` are defaults, static or mapped from the field
279
+ * snapshot (`HostProps<FormFieldFormless>`); `createFormItem` does the
280
+ * projection.
281
+ */
282
+ declare interface FormViewItemBind {
283
+ component: Component;
284
+ props?: HostProps<FormFieldFormless>;
285
+ }
286
+
287
+ declare interface FormViewLayoutBind {
367
288
  Row: Component;
368
289
  Col: Component;
369
- column?: number;
370
- gutter?: number;
290
+ /**
291
+ * Default LayoutView props (density etc.): a static object. Tag `:layout:*`
292
+ * overlays them (near wins). `disabled` stays kernel-owned: always the
293
+ * `fl:layout` polarity flip.
294
+ */
295
+ props?: Record<string, unknown>;
371
296
  }
372
297
 
298
+ /** FormView `:fl:layout` is a boolean switch. Density is factory `layout.props` / `:layout:*`. */
299
+ declare type FormViewLayoutProp = boolean;
300
+
373
301
  export declare interface FormViewProps {
302
+ /**
303
+ * FormView write model (the DTO). It is **not** a declared prop: the bare key
304
+ * stays in `attrs` and, per the no-prefix rule, also reaches the host Form — a
305
+ * host that wants it declares `modelValue` itself (wrap ElForm in a `MyForm`;
306
+ * design.md §10.1). The `onUpdate:modelValue` listener is read here
307
+ * (`useFormViewValue`) and also rides through to the host Form.
308
+ */
374
309
  modelValue?: unknown;
375
310
  /**
376
311
  * Grid hosting switch. Default `false`.
377
- * Density: this FormView's factory `layout.column/gutter` plus `:row:column` / `:row:gutter`.
378
- * Nested FormView / extra rows do not inherit this instance overlay.
312
+ * Density: factory `layout.props` plus `:layout:*` for **this** page LayoutView only.
313
+ * wrap-embed inner LayoutView inherits neither. Other `:layout:*` fall through to the host Row.
379
314
  */
380
- 'fl:layout'?: FormLayoutProp;
381
- 'row:column'?: number;
382
- 'row:gutter'?: number;
315
+ 'fl:layout'?: FormViewLayoutProp;
316
+ 'layout:column'?: LayoutViewProps['column'];
383
317
  /**
384
318
  * Wrap the factory `form`. Default `'auto'`: on at the root, off when nested.
385
319
  * Explicit `true` / `false` win.
386
320
  */
387
- 'fl:form'?: FormFormProp;
388
- /** Wrap the factory `item` per cell (default `true` when `item.component` is bound). */
321
+ 'fl:form'?: FormViewFormProp;
322
+ /** Wrap the factory `item` per field (default `true` when `item.component` is bound). */
389
323
  'fl:item'?: boolean;
390
324
  }
391
325
 
392
- export declare function getIn(root: unknown, prop: string): unknown;
393
-
394
326
  /** Host Col span modulus (Element / Ant Design 24-grid). */
395
- export declare const GRID_TOTAL = 24;
327
+ declare const GRID_TOTAL = 24;
396
328
 
397
329
  /** Static host props, or derived from that layer's snapshot. */
398
- export declare type HostProps<TFl> = Record<string, unknown> | ((fl: TFl) => Record<string, unknown> | undefined);
330
+ declare type HostProps<TFl> = Record<string, unknown> | ((fl: TFl) => Record<string, unknown> | undefined);
399
331
 
400
332
  /**
401
- * Snapshot for `item.props` / control `props` functions.
402
- * Kernel wiring + ControlSchema extras. Not passed as a host component prop.
333
+ * 通用整数范围类型工具
334
+ * @example IntRange<1, 24> 会生成 1 到 24 的联合类型
403
335
  */
404
- export declare type ItemFl = {
405
- controlKey: string;
406
- binding: ResolvedControlBinding;
407
- getValues: () => unknown[];
408
- [extra: string]: unknown;
409
- } & ControlSchemaExtras;
410
-
411
- export declare interface LayoutItemProps {
412
- span?: ColSpanSpec;
413
- place?: ColPlace;
414
- }
415
-
416
- export declare interface LayoutViewProps {
417
- disabled?: boolean;
418
- column?: number;
419
- }
420
-
421
- declare type LockedKeysForDef<Def> = LockedVModelKeys<SchemaModel<Def> extends ControlVModel | undefined ? SchemaModel<Def> : undefined>;
422
-
423
- export declare type LockedVModelKeys<M extends ControlVModel | undefined> = ModelPortNames<M> | `onUpdate:${ModelPortNames<M>}`;
424
-
425
- /** Widget `formless.item` vs schema `item`. `'self'` vs `false` is illegal. */
426
- export declare function mergeInternalItem(widget?: ControlItemSetting, schema?: ControlItemSetting): ControlItemSetting | undefined;
336
+ declare type IntRange<F extends number, T extends number> = Exclude<_Enumerate<T>, _Enumerate<F>> | T;
427
337
 
428
- declare type ModelPortNames<M> = [M] extends [undefined] ? 'modelValue' : M extends string ? M : M extends readonly [infer F extends string, ...infer R] ? F | ModelPortNames<R> : 'modelValue';
429
-
430
- /**
431
- * PascalCase control tags. `S` must not be `Record<string, _>` or `keyof` collapses
432
- * to `string` and Volar/TS lose `User.Name` / `User.IdCard` as named keys.
433
- * Tag props are `fl:*` plus the widget's public props (v-model ports locked).
434
- */
435
- export declare type NamespacedControls<S> = {
436
- [K in keyof S & string as CamelToPascal<K>]: FormControlComponent<WidgetTagProps<S[K]>>;
338
+ export declare const LayoutItem: DefineComponent<ExtractPropTypes< {
339
+ span: {
340
+ type: PropType<LayoutItemSpan>;
341
+ default: undefined;
437
342
  };
438
-
439
- /** Later layers win. `undefined` does not override. Empty string is a value. */
440
- export declare function overlayProps(...layers: Array<Record<string, unknown> | undefined>): Record<string, unknown>;
441
-
442
- export declare function parsePath(prop: string): PathSegment[];
443
-
444
- /** `Name` → `name`, `IdCard` → `idCard` */
445
- export declare function pascalToCamel(key: string): string;
446
-
447
- /**
448
- * Parse `prop` location strings: object keys and `[index]` segments.
449
- * Examples: `name`, `buyers[0].name`, `[2].title`
450
- */
451
- export declare type PathSegment = {
452
- type: 'key';
453
- key: string;
454
- } | {
455
- type: 'index';
456
- index: number;
343
+ place: {
344
+ type: PropType<LayoutItemPlace>;
345
+ default: undefined;
346
+ };
347
+ }>, () => VNodeChild, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes< {
348
+ span: {
349
+ type: PropType<LayoutItemSpan>;
350
+ default: undefined;
351
+ };
352
+ place: {
353
+ type: PropType<LayoutItemPlace>;
354
+ default: undefined;
457
355
  };
356
+ }>> & Readonly<{}>, {
357
+ place: LayoutItemPlace;
358
+ span: LayoutItemSpan;
359
+ }, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
458
360
 
459
- export declare function resolveControlBinding(controlKey: string, options?: {
460
- model?: ControlVModel;
461
- prop?: ControlProp;
462
- }, overrides?: ControlBindingOverrides): ResolvedControlBinding;
361
+ declare type LayoutItemPlace = 'auto' | 'start' | 'end';
463
362
 
464
- /**
465
- * FormView < internal < tag (ADR-017).
466
- * Pure `'self'` skips outer Item+Col. `'self'` + explicit tag item true
467
- * wraps outer Item (and Col when page layout is on) and asks for an inner Row.
468
- * Col follows the Row host only; no control/cell layout switch.
469
- */
470
- export declare function resolveControlShell(input: ResolveControlShellInput): ResolvedControlShell;
471
-
472
- declare interface ResolveControlShellInput {
473
- pageItem: boolean;
474
- pageLayoutOn: boolean;
475
- internalItem?: ControlItemSetting;
476
- /** Tag `:fl:item`. Omitted ≠ `true`. */
477
- tagItem?: boolean;
363
+ declare interface LayoutItemProps {
364
+ span?: LayoutItemSpan;
365
+ place?: LayoutItemPlace;
478
366
  }
479
367
 
480
- export declare interface ResolvedControlBinding {
481
- models: string[];
482
- props: string[];
483
- }
368
+ declare type LayoutItemSpan = ColSpan | `${ColSpan}` | `${ColSpan}x` | 'max';
484
369
 
485
- export declare interface ResolvedControlShell {
486
- wrapItem: boolean;
487
- wrapCol: boolean;
488
- extraRow: boolean;
489
- self: boolean;
370
+ declare interface LayoutViewProps {
371
+ disabled?: boolean;
372
+ column?: number;
490
373
  }
491
374
 
492
- /**
493
- * Optional Element-style encoding: one location → dotted path; several →
494
- * control key. Kernel does not put this on Item `fl` — the adapter Item
495
- * calls this (or encodes another way) when mapping to host `prop`.
496
- */
497
- export declare function resolveFormItemProp(binding: ResolvedControlBinding, controlKey: string): string;
375
+ declare type LockedKeysForDef<Def> = LockedVModelKeys<SchemaModel<Def> extends FormFieldVModelRaw | undefined ? SchemaModel<Def> : undefined>;
498
376
 
499
- export declare function resolveProps<TFl>(spec: HostProps<TFl> | undefined, fl: TFl): Record<string, unknown>;
377
+ declare type LockedVModelKeys<M extends FormFieldVModelRaw | undefined> = ModelPortNames<M> | `onUpdate:${ModelPortNames<M>}`;
378
+
379
+ declare type ModelPortNames<M> = [M] extends [undefined] ? 'modelValue' : M extends string ? M : M extends readonly [infer F extends string, ...infer R] ? F | ModelPortNames<R> : 'modelValue';
500
380
 
501
381
  /**
502
- * v-model ports locked on the tag (ADR-011). Schema `model` wins;
382
+ * v-model ports locked on the tag (design.md §7.2 / §17). Schema `model` wins;
503
383
  * omitted `model` locks the default `'modelValue'`.
504
384
  * Widened `string[]` is not treated as port names (would Omit every string key).
505
385
  */
@@ -507,55 +387,31 @@ declare type SchemaModel<Def> = Def extends {
507
387
  model: infer M;
508
388
  } ? M : undefined;
509
389
 
510
- /** Immutable write at a full `prop` location (`buyers[0].name`). Arrays are cloned. */
511
- export declare function setIn(root: unknown, prop: string, value: unknown): unknown;
512
-
513
- export declare function toBindingList(value: ControlVModel | ControlProp): string[];
514
-
515
- export declare function useFormContext(): FormContext;
390
+ /** Type-level `toCamel` (built-in `Capitalize`): `'layout-item'` → `'layoutItem'`. */
391
+ declare type ToCamel<S extends string> = S extends `${infer Head}-${infer Tail}` ? `${Head}${Capitalize<ToCamel<Tail>>}` : S;
516
392
 
517
393
  /**
518
- * One cell: LayoutItem? → Item? → (inner LayoutView?) → default slot.
519
- * No arg: this control's full binding (factory outer wrap).
520
- * Port: slice one v-model mouth.
521
- * Outer wrap follows the merged shell (ADR-017); binding stays on the frame.
394
+ * Any declaration bag → its optional tag props under `Prefix` (`label` →
395
+ * `'fl:label'` under the default prefix). Always optional (override, not
396
+ * required).
397
+ *
398
+ * The prefix is the kernel channel the keys land on: `fl:` for the field
399
+ * declaration, `layout:` / `layout-item:` for the layout package's prop bags.
400
+ * One mapper therefore produces every prefixed tag key space instead of each
401
+ * one being retyped.
522
402
  */
523
- export declare function useFormItem(port?: string): FormViewItemComponent;
524
-
525
- /** Nearest LayoutView's cell component; identity when no LayoutView. */
526
- export declare function useLayoutItem(): Component;
527
-
528
- export declare interface WidgetFormless {
529
- model?: string | string[];
530
- item?: boolean | 'self';
531
- }
532
-
533
- /** Widget props that may appear on `<User.Xxx />` (v-model ports stripped). */
534
- export declare type WidgetTagProps<Def> = Def extends {
535
- component?: infer C;
536
- } ? [Exclude<C, undefined>] extends [never] ? {} : Omit<ComponentPublicProps<Exclude<C, undefined>>, LockedKeysForDef<Def>> : {};
537
-
538
- /** FormView-owned Item shell. Col is owned by LayoutView / useLayoutItem. */
539
- export declare type WrapControl = (body: VNodeChild, meta: WrapControlMeta) => VNodeChild;
403
+ declare type ToFormlessProps<T, Prefix extends string = 'fl:'> = {
404
+ [K in keyof T as K extends string ? `${Prefix}${K}` : never]+?: T[K];
405
+ };
540
406
 
541
- /** Input Control hands to FormView's wrap: Item snapshot + host fallthrough. */
542
- export declare interface WrapControlMeta {
543
- /**
544
- * This wrap only. `false` skips Item even when FormView `item` is on.
545
- * `true` forces Item even when FormView `item` is off. Omit = follow FormView.
546
- */
547
- item?: boolean;
548
- fl: ItemFl;
549
- itemAttrs: Record<string, unknown>;
550
- itemOn: Record<string, unknown>;
551
- itemSlots: Record<string, Slot>;
552
- }
407
+ /** Type-level `upperFirst`: `'name'` → `'Name'` (design.md §11). */
408
+ declare type UpperFirst<S extends string> = S extends `${infer F}${infer R}` ? `${Uppercase<F>}${R}` : S;
553
409
 
554
410
  export { }
555
411
 
556
412
 
557
413
  declare module 'vue' {
558
414
  interface ComponentCustomOptions {
559
- formless?: WidgetFormless;
415
+ formless?: FormControlFormless;
560
416
  }
561
417
  }