Skip to content

Commit 73963b7

Browse files
committed
feat: add slot layout
1 parent 4224f3b commit 73963b7

16 files changed

Lines changed: 504 additions & 56 deletions

dev/vue/TheApp.vue

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { createInput, useDisabledLayout, createForm, useMultiLayout, useCheckLayout, useRepeatLayout, useUnionLayout, useStepLayout, useSectionLayout } from "@V";
2+
import { createInput, useDisabledLayout, createForm, useMultiLayout, useCheckLayout, useRepeatLayout, useUnionLayout, useStepLayout, useSectionLayout, useSlotLayout } from "@V";
33
import { createGridTemplates } from "@V/templates/grid";
44
import { TheCheckbox, DateInput, FileInput, NumberInput, RadioGroup, RangeInput, PrimaryButton, TextareaInput, TextInput, TimeInput, DualRangeInput, CheckboxPolicy, RangeDateInput, RangeTimeInput, templateFormRemoveButton, templateFormResetButton, templateFormNextButton, templateFormPreviousButton, templateFormSelect, templateFormAddButton } from "@V/designSystem";
55
import { ref } from "vue";
@@ -235,16 +235,22 @@ const { component: Form, currentValue, check } = useForm(
235235
[
236236
"Choice two",
237237
useMultiLayout({
238-
comment: useTextArea({
239-
label: "Comment",
240-
defaultValue: "Another default value",
241-
}),
238+
comment: useSlotLayout(
239+
"test",
240+
useTextArea({
241+
label: "Comment",
242+
defaultValue: "Another default value",
243+
}),
244+
),
242245
subscribe: useCheckbox({
243246
label: "Newsletter",
244247
}),
245-
policy: useCheckboxPolicy({
246-
label: "Policy",
247-
}),
248+
policy: useSlotLayout(
249+
"test",
250+
useCheckboxPolicy({
251+
label: "Policy",
252+
}),
253+
),
248254
notificationFrequency: useRadioGroup({
249255
label: "Frequency",
250256
}),
@@ -298,6 +304,11 @@ const { component: Form, currentValue, check } = useForm(
298304
type="submit"
299305
label="Submit"
300306
/>
307+
308+
<template #test="params">
309+
test
310+
<component :is="params.formField" />
311+
</template>
301312
</Form>
302313

303314
<button

scripts/vue/form.ts

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type FunctionalComponent, h, type HTMLAttributes, normalizeClass, type Ref, ref } from "vue";
2-
import { type GetFormFieldCheckedValue, type GetFormFieldValue, type FormField, type FormFieldInstance } from "./formField";
2+
import { type GetFormFieldCheckedValue, type GetFormFieldValue, type FormField, type FormFieldInstance, type GetFormFieldSlots, type FormFieldSlots } from "./formField";
33
import type * as EE from "@duplojs/utils/either";
4-
import { simpleClone, type Unwrap } from "@duplojs/utils";
4+
import { type AnyFunction, simpleClone, type SimplifyTopLevel, type Unwrap } from "@duplojs/utils";
55
import { type Templates } from "./template";
66
import { type VueComponent } from "./types";
77

@@ -34,7 +34,16 @@ export interface FormProperties<
3434
component: FunctionalComponent<
3535
HTMLAttributes,
3636
{},
37-
{ default?(): any }
37+
SimplifyTopLevel<
38+
& { default?(): any }
39+
& (
40+
GetFormFieldSlots<GenericFormField> extends infer InferredSlots extends FormFieldSlots
41+
? {
42+
[Prop in keyof InferredSlots]: (params: InferredSlots[Prop]) => any
43+
}
44+
: {}
45+
)
46+
>
3847
>;
3948
}
4049

@@ -59,11 +68,13 @@ export function createForm(templates: Templates) {
5968
const templateForm = params.template ?? templates.form;
6069

6170
const currentValue = ref(simpleClone(formField.defaultValue));
71+
const formSlots = ref<Record<string, AnyFunction<[any]>> | null>(null);
6272

6373
const formFieldInstance = formField.new(
6474
currentValue,
6575
key,
6676
templates,
77+
(name, params) => formSlots.value?.[name]?.(params) ?? null,
6778
);
6879

6980
const check = () => formFieldInstance.check();
@@ -79,21 +90,25 @@ export function createForm(templates: Templates) {
7990

8091
const formFieldVNode = formFieldInstance.getVNode();
8192

82-
const component: FormProperties["component"] = (props, { slots }) => h(
83-
() => templateForm.getVNode(
84-
{
85-
...props,
86-
class: normalizeClass([props.class, params.class]),
87-
fieldKey: key,
88-
onSubmit: () => {},
89-
getCurrentValue,
90-
},
91-
{
92-
submitter: slots.default ?? (() => null),
93-
formField: () => formFieldVNode,
94-
},
95-
),
96-
);
93+
const component: FormProperties["component"] = (props, { slots }) => {
94+
formSlots.value = slots;
95+
96+
return h(
97+
() => templateForm.getVNode(
98+
{
99+
...props,
100+
class: normalizeClass([props.class, params.class]),
101+
fieldKey: key,
102+
onSubmit: () => {},
103+
getCurrentValue,
104+
},
105+
{
106+
submitter: slots.default ?? (() => null),
107+
formField: () => formFieldVNode,
108+
},
109+
),
110+
);
111+
};
97112

98113
return {
99114
currentValue,

scripts/vue/formField.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
1-
import { type AnyTuple, type Kind } from "@duplojs/utils";
1+
import { type UnionToIntersection, type AnyTuple, type Kind } from "@duplojs/utils";
22
import type * as DP from "@duplojs/utils/dataParser";
33
import type * as EE from "@duplojs/utils/either";
44
import { createVueFormKind } from "./kind";
55
import { type Ref, type VNode } from "vue";
66
import { type Templates } from "./template";
77

8+
export interface FormFieldSlotParams<
9+
GenericValue extends unknown = unknown,
10+
> {
11+
fieldKey: string;
12+
value: GenericValue;
13+
update(value: GenericValue): void;
14+
formField?(): VNode;
15+
}
16+
17+
export type FormFieldSlots = Record<
18+
string,
19+
FormFieldSlotParams
20+
>;
21+
822
export type FormFieldInstanceParams<
923
GenericValue extends unknown = unknown,
1024
> = [
1125
modelValue: Ref<GenericValue>,
1226
parentKey: string,
1327
templates: Templates,
28+
getSlot: (
29+
name: string,
30+
params: FormFieldSlotParams
31+
) => VNode | null,
1432
];
1533

1634
export interface ErrorProperties {
@@ -30,9 +48,11 @@ export interface FormFieldInstance<
3048
export interface FormFieldProperties<
3149
GenericValue extends unknown = unknown,
3250
GenericCheckedValue extends unknown = unknown,
51+
GenericSlots extends FormFieldSlots = FormFieldSlots,
3352
> {
3453
value: GenericValue;
3554
checkedValue: GenericCheckedValue;
55+
slots: GenericSlots;
3656
}
3757

3858
export const formFieldKind = createVueFormKind<
@@ -43,11 +63,13 @@ export const formFieldKind = createVueFormKind<
4363
export interface FormField<
4464
GenericValue extends unknown = unknown,
4565
GenericCheckedValue extends unknown = unknown,
66+
GenericSlots extends FormFieldSlots = FormFieldSlots,
4667
> extends Kind<
4768
typeof formFieldKind.definition,
4869
FormFieldProperties<
4970
GenericValue,
50-
GenericCheckedValue
71+
GenericCheckedValue,
72+
GenericSlots
5173
>
5274
> {
5375
"new"(
@@ -59,6 +81,7 @@ export interface FormField<
5981
export function createFormField<
6082
GenericValue extends unknown,
6183
GenericCheckedValue extends unknown,
84+
GenericSlots extends FormFieldSlots,
6285
>(
6386
theFunction: (
6487
...args: FormFieldInstanceParams<GenericValue>
@@ -68,7 +91,8 @@ export function createFormField<
6891
defaultValue: NoInfer<GenericValue>,
6992
): FormField<
7093
GenericValue,
71-
GenericCheckedValue
94+
GenericCheckedValue,
95+
GenericSlots
7296
> {
7397
return formFieldKind.setTo(
7498
{
@@ -90,3 +114,23 @@ export type GetFormFieldCheckedValue<
90114
> = GenericFormField extends FormField<any, infer InferredCheckedValue>
91115
? InferredCheckedValue
92116
: never;
117+
118+
export type GetFormFieldSlots<
119+
GenericFormField extends FormField,
120+
> = GenericFormField extends FormField<any, any, infer InferredSlots>
121+
? InferredSlots
122+
: never;
123+
124+
export type MergeFormFieldSlots<
125+
GenericFormField extends FormField,
126+
> = Extract<
127+
keyof UnionToIntersection<GetFormFieldSlots<GenericFormField>> extends infer InferredKeyof extends string
128+
? {
129+
[Prop in InferredKeyof]: Extract<
130+
GetFormFieldSlots<GenericFormField>,
131+
Record<Prop, unknown>
132+
>[Prop]
133+
}
134+
: never,
135+
FormFieldSlots
136+
>;

scripts/vue/input.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ export type UseInput<
127127
GetVueInputComponentValue<GenericInputComponentInstance>,
128128
IsEqual<GenericDataParser, never> extends true
129129
? GetVueInputComponentCheckedValue<GenericInputComponentInstance>
130-
: DP.Output<GenericDataParser>
130+
: DP.Output<GenericDataParser>,
131+
{}
131132
>;
132133

133134
export function createInput<

scripts/vue/layouts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from "./useRepeatLayout";
55
export * from "./useUnionLayout";
66
export * from "./useStepLayout";
77
export * from "./useSectionLayout";
8+
export * from "./useSlotLayout";

scripts/vue/layouts/useCheckLayout.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createFormField, type GetFormFieldCheckedValue, type FormField, type GetFormFieldValue } from "@V/formField";
1+
import { createFormField, type GetFormFieldCheckedValue, type FormField, type GetFormFieldValue, type GetFormFieldSlots } from "@V/formField";
22
import * as EE from "@duplojs/utils/either";
33
import type * as DP from "@duplojs/utils/dataParser";
44
import { type IsEqual, unwrap } from "@duplojs/utils";
@@ -45,22 +45,24 @@ export function useCheckLayout<
4545
GetFormFieldValue<GenericFormField>,
4646
IsEqual<GenericDataParser, never> extends true
4747
? GetFormFieldCheckedValue<GenericFormField>
48-
: DP.Output<GenericDataParser>
48+
: DP.Output<GenericDataParser>,
49+
GetFormFieldSlots<GenericFormField>
4950
>;
5051

5152
export function useCheckLayout(
5253
formField: FormField,
5354
params: UseCheckLayoutParams,
5455
): FormField {
5556
return createFormField(
56-
(modelValue, parentKey, templates) => {
57+
(modelValue, parentKey, templates, getSlot) => {
5758
const key = `${parentKey}_CHK`;
5859
const template = params?.template ?? templates.check;
5960

6061
const formFieldInstance = formField.new(
6162
modelValue,
6263
key,
6364
templates,
65+
getSlot,
6466
);
6567

6668
const scope = effectScope();

scripts/vue/layouts/useDisabledLayout.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createFormField, type FormField, type GetFormFieldCheckedValue, type GetFormFieldValue } from "@V/formField";
1+
import { createFormField, type GetFormFieldSlots, type FormField, type GetFormFieldCheckedValue, type GetFormFieldValue } from "@V/formField";
22
import * as EE from "@duplojs/utils/either";
33
import { h } from "vue";
44

@@ -13,21 +13,23 @@ export function useDisabledLayout<
1313
params: UseDisabledLayoutParams
1414
): FormField<
1515
GetFormFieldValue<GenericFormField>,
16-
GetFormFieldCheckedValue<GenericFormField> | undefined
16+
GetFormFieldCheckedValue<GenericFormField> | undefined,
17+
GetFormFieldSlots<GenericFormField>
1718
>;
1819

1920
export function useDisabledLayout(
2021
formField: FormField,
2122
params: UseDisabledLayoutParams,
2223
): FormField {
2324
return createFormField(
24-
(modelValue, parentKey, templates) => {
25+
(modelValue, parentKey, templates, getSlot) => {
2526
const key = `${parentKey}_DIS`;
2627

2728
const formFieldInstance = formField.new(
2829
modelValue,
2930
key,
3031
templates,
32+
getSlot,
3133
);
3234

3335
const check = () => {

scripts/vue/layouts/useMultiLayout.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-disable @typescript-eslint/prefer-for-of */
2-
import { type GetFormFieldCheckedValue, type GetFormFieldValue, type FormField, createFormField, type ErrorProperties } from "@V/formField";
2+
import { type GetFormFieldCheckedValue, type GetFormFieldValue, type FormField, createFormField, type ErrorProperties, type GetFormFieldSlots, type FormFieldSlots, type MergeFormFieldSlots } from "@V/formField";
33
import { computed, effectScope, h, type VNode } from "vue";
44
import * as EE from "@duplojs/utils/either";
55
import * as AA from "@duplojs/utils/array";
6-
import { unwrap } from "@duplojs/utils";
6+
import { type SimplifyTopLevel, type UnionToIntersection, unwrap } from "@duplojs/utils";
77
import { type VueComponent } from "@V/types";
88
import { type Templates } from "@V/template";
99

@@ -49,7 +49,10 @@ export function useMultiLayout<
4949
},
5050
{
5151
[Prop in keyof GenericFormFieldWrapper]: GetFormFieldCheckedValue<GenericFormFieldWrapper[Prop]>
52-
}
52+
},
53+
MergeFormFieldSlots<
54+
GenericFormFieldWrapper[keyof GenericFormFieldWrapper]
55+
>
5356
>;
5457

5558
export function useMultiLayout<
@@ -63,7 +66,10 @@ export function useMultiLayout<
6366
},
6467
{
6568
[Entry in GenericFormFieldEntry as string]: GetFormFieldCheckedValue<Entry[1]>
66-
}
69+
},
70+
MergeFormFieldSlots<
71+
GenericFormFieldEntry[1]
72+
>
6773
>;
6874

6975
export function useMultiLayout(
@@ -78,7 +84,7 @@ export function useMultiLayout(
7884
: Object.entries(formFields);
7985

8086
return createFormField(
81-
(modelValue, parentKey, templates) => {
87+
(modelValue, parentKey, templates, getSlot) => {
8288
const key = `${parentKey}_MUL`;
8389

8490
const template = params?.template ?? templates.multi;
@@ -98,6 +104,7 @@ export function useMultiLayout(
98104
}),
99105
`${key}-${subKey}`,
100106
templates,
107+
getSlot,
101108
),
102109
] as const,
103110
);

0 commit comments

Comments
 (0)