mirror of https://github.com/halo-dev/halo
refactor: refactor the repeater component using the schema approach (#4702)
#### What type of PR is this? /kind improvement #### What this PR does / why we need it: 对 Formkit Repeater 组件使用 schema 的方式进行重构,重构后的 Repeater 组件将支持条件判断,按照下述方式定义即可。 ``` const formSchema = [ { $formkit: "repeater", name: "testing", min: 1, max: 2, addLabel: "添加", children: [ { $formkit: "select", name: "things", id: "things", label: "Things", placeholder: "Select", options: ["Something", "Else"], }, { $formkit: "number", if: "$value.things === Something", name: "show_1", id: "show_something_1", label: "Show something", }, { $formkit: "number", if: "$value.things === Something", name: "show_2", id: "show_something_2", label: "Also show something", }, ], }, ]; ``` 同时额外增加了对 `addLabel`、`addButton`、`upControl`、`downControl`、`insertControl`、`removeControl` 属性的支持。 #### How to test it? - 测试原有使用 `Repeater` 组件可否正常使用。 - 对 `Repeater` 条件判断功能进行测试。 - 查看保存的数据格式是否正确 #### Which issue(s) this PR fixes: Fixes #4603 #### Does this PR introduce a user-facing change? ```release-note 重构 Repeater 组件,使其支持条件判断 ```pull/4717/head
parent
767aa53a22
commit
9e33a81e2d
|
@ -19,6 +19,12 @@
|
||||||
- 参数
|
- 参数
|
||||||
1. min: 最小数量,默认为 `0`
|
1. min: 最小数量,默认为 `0`
|
||||||
2. max: 最大数量,默认为 `Infinity`,即无限制。
|
2. max: 最大数量,默认为 `Infinity`,即无限制。
|
||||||
|
3. addLabel: 添加按钮的文本,默认为 `添加`
|
||||||
|
4. addButton: 是否显示添加按钮,默认为 `true`
|
||||||
|
5. upControl: 是否显示上移按钮,默认为 `true`
|
||||||
|
6. downControl: 是否显示下移按钮,默认为 `true`
|
||||||
|
7. insertControl: 是否显示插入按钮,默认为 `true`
|
||||||
|
8. removeControl: 是否显示删除按钮,默认为 `true`
|
||||||
- `menuCheckbox`:选择一组菜单
|
- `menuCheckbox`:选择一组菜单
|
||||||
- `menuRadio`:选择一个菜单
|
- `menuRadio`:选择一个菜单
|
||||||
- `menuItemSelect`:选择菜单项
|
- `menuItemSelect`:选择菜单项
|
||||||
|
@ -37,7 +43,7 @@
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const postName = ref("")
|
const postName = ref("");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -67,12 +73,15 @@ Repeater 是一个集合类型的输入组件,可以让使用者可视化的
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const users = ref([])
|
const users = ref([]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="users"
|
v-model="users"
|
||||||
|
:min="1"
|
||||||
|
:max="3"
|
||||||
|
addLabel="Add User"
|
||||||
type="repeater"
|
type="repeater"
|
||||||
label="Users"
|
label="Users"
|
||||||
>
|
>
|
||||||
|
@ -98,6 +107,9 @@ const users = ref([])
|
||||||
- $formkit: repeater
|
- $formkit: repeater
|
||||||
name: users
|
name: users
|
||||||
label: Users
|
label: Users
|
||||||
|
addLabel: Add User
|
||||||
|
min: 1
|
||||||
|
max: 3
|
||||||
items:
|
items:
|
||||||
- $formkit: text
|
- $formkit: text
|
||||||
name: full_name
|
name: full_name
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { zh, en } from "@formkit/i18n";
|
||||||
import type { DefaultConfigOptions } from "@formkit/vue";
|
import type { DefaultConfigOptions } from "@formkit/vue";
|
||||||
import { form } from "./inputs/form";
|
import { form } from "./inputs/form";
|
||||||
import { group } from "./inputs/group";
|
import { group } from "./inputs/group";
|
||||||
|
import { group as nativeGroup } from "@formkit/inputs";
|
||||||
import { attachment } from "./inputs/attachment";
|
import { attachment } from "./inputs/attachment";
|
||||||
import { code } from "./inputs/code";
|
import { code } from "./inputs/code";
|
||||||
import { repeater } from "./inputs/repeater";
|
import { repeater } from "./inputs/repeater";
|
||||||
|
@ -42,6 +43,7 @@ const config: DefaultConfigOptions = {
|
||||||
form,
|
form,
|
||||||
password,
|
password,
|
||||||
group,
|
group,
|
||||||
|
nativeGroup,
|
||||||
attachment,
|
attachment,
|
||||||
code,
|
code,
|
||||||
repeater,
|
repeater,
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { VButton, IconAddCircle } from "@halo-dev/components";
|
||||||
|
import type { FormKitFrameworkContext } from "@formkit/core";
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
context: {
|
||||||
|
type: Object as PropType<FormKitFrameworkContext>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="context.classes.add">
|
||||||
|
<VButton :disabled="disabled" type="secondary">
|
||||||
|
<template #icon>
|
||||||
|
<IconAddCircle class="h-full w-full" />
|
||||||
|
</template>
|
||||||
|
{{ context.addLabel || $t("core.common.buttons.add") }}
|
||||||
|
</VButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -1,147 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
VButton,
|
|
||||||
IconAddCircle,
|
|
||||||
IconCloseCircle,
|
|
||||||
IconArrowUpCircleLine,
|
|
||||||
IconArrowDownCircleLine,
|
|
||||||
} from "@halo-dev/components";
|
|
||||||
import type { FormKitFrameworkContext } from "@formkit/core";
|
|
||||||
import { group } from "@formkit/inputs";
|
|
||||||
import type { PropType } from "vue";
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
context: {
|
|
||||||
type: Object as PropType<FormKitFrameworkContext>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const min = Number(props.context.min) || 0;
|
|
||||||
const max = Number(props.context.max) || Infinity;
|
|
||||||
|
|
||||||
const handleAppend = (index: number) => {
|
|
||||||
const value = cloneDeep(props.context._value);
|
|
||||||
value.splice(index + 1, 0, {});
|
|
||||||
props.context.node.input(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemove = (index: number) => {
|
|
||||||
if (props.context._value?.length <= min) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = cloneDeep(props.context._value);
|
|
||||||
value.splice(index, 1);
|
|
||||||
props.context.node.input(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMoveUp = (index: number) => {
|
|
||||||
if (index === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = cloneDeep(props.context._value);
|
|
||||||
value.splice(index - 1, 0, value.splice(index, 1)[0]);
|
|
||||||
props.context.node.input(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMoveDown = (index: number) => {
|
|
||||||
if (index === props.context._value.length - 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = cloneDeep(props.context._value);
|
|
||||||
value.splice(index + 1, 0, value.splice(index, 1)[0]);
|
|
||||||
props.context.node.input(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const value = cloneDeep(props.context._value);
|
|
||||||
const differenceCount = min - value?.length || 0;
|
|
||||||
|
|
||||||
if (differenceCount > 0) {
|
|
||||||
for (let i = 0; i < differenceCount; i++) {
|
|
||||||
value.push({});
|
|
||||||
}
|
|
||||||
props.context.node.input(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ul :class="context.classes.items">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in context._value"
|
|
||||||
:key="index"
|
|
||||||
:class="context.classes.item"
|
|
||||||
>
|
|
||||||
<div :class="context.classes.content">
|
|
||||||
<FormKit
|
|
||||||
:id="`${context.node.name}-group-${index}`"
|
|
||||||
:key="`${context.node.name}-group-${index}`"
|
|
||||||
:model-value="item"
|
|
||||||
:type="group"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</FormKit>
|
|
||||||
</div>
|
|
||||||
<div :class="context.classes.controls">
|
|
||||||
<ul class="flex flex-col items-center justify-center gap-1.5 py-2">
|
|
||||||
<li
|
|
||||||
class="cursor-pointer text-gray-500 transition-all hover:text-primary"
|
|
||||||
:class="{
|
|
||||||
'!cursor-not-allowed opacity-50 hover:!text-gray-500':
|
|
||||||
index === 0,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<IconArrowUpCircleLine
|
|
||||||
class="h-5 w-5"
|
|
||||||
@click="handleMoveUp(index)"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="cursor-pointer text-gray-500 transition-all hover:text-primary"
|
|
||||||
:class="{
|
|
||||||
'!cursor-not-allowed opacity-50 hover:!text-gray-500':
|
|
||||||
context._value?.length <= min,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<IconCloseCircle class="h-5 w-5" @click="handleRemove(index)" />
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="cursor-pointer text-gray-500 transition-all hover:text-primary"
|
|
||||||
>
|
|
||||||
<IconAddCircle class="h-5 w-5" @click="handleAppend(index)" />
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="cursor-pointer text-gray-500 transition-all hover:text-primary"
|
|
||||||
:class="{
|
|
||||||
'!cursor-not-allowed opacity-50 hover:!text-gray-500':
|
|
||||||
index === context._value.length - 1,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<IconArrowDownCircleLine
|
|
||||||
class="h-5 w-5"
|
|
||||||
@click="handleMoveDown(index)"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div :class="context.classes.add">
|
|
||||||
<VButton
|
|
||||||
:disabled="context._value?.length >= max"
|
|
||||||
type="secondary"
|
|
||||||
@click="handleAppend(context._value.length)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<IconAddCircle class="h-full w-full" />
|
|
||||||
</template>
|
|
||||||
{{ $t("core.common.buttons.add") }}
|
|
||||||
</VButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import type { FormKitNode } from "@formkit/core";
|
||||||
|
import { undefine } from "@formkit/utils";
|
||||||
|
|
||||||
|
export const repeats = function (node: FormKitNode) {
|
||||||
|
node._c.sync = true;
|
||||||
|
node.on("created", repeaterFeature.bind(null, node));
|
||||||
|
};
|
||||||
|
|
||||||
|
type FnType = (index: number) => object;
|
||||||
|
|
||||||
|
function createValue(num: number, fn: FnType) {
|
||||||
|
return new Array(num).fill("").map((value, index) => fn(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeaterFeature(node: FormKitNode) {
|
||||||
|
node.props.removeControl = node.props.removeControl ?? true;
|
||||||
|
node.props.upControl = node.props.upControl ?? true;
|
||||||
|
node.props.downControl = node.props.downControl ?? true;
|
||||||
|
node.props.insertControl = node.props.insertControl ?? true;
|
||||||
|
node.props.addButton = node.props.addButton ?? true;
|
||||||
|
node.props.addLabel = node.props.addLabel ?? false;
|
||||||
|
node.props.addAttrs = node.props.addAttrs ?? {};
|
||||||
|
node.props.min = node.props.min ? Number(node.props.min) : 0;
|
||||||
|
node.props.max = node.props.max ? Number(node.props.max) : 1 / 0;
|
||||||
|
if (node.props.min > node.props.max) {
|
||||||
|
throw Error("Repeater: min must be less than max");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("disabled" in node.props) {
|
||||||
|
node.props.disabled = undefine(node.props.disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(node.value)) {
|
||||||
|
if (node.value.length < node.props.min) {
|
||||||
|
const value = createValue(node.props.min - node.value.length, () => ({}));
|
||||||
|
node.input(node.value.concat(value), false);
|
||||||
|
} else {
|
||||||
|
if (node.value.length > node.props.max) {
|
||||||
|
node.input(node.value.slice(0, node.props.max), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.input(
|
||||||
|
createValue(node.props.min, () => ({})),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.context) {
|
||||||
|
const fns = node.context.fns;
|
||||||
|
fns.createShift = (index: number, offset: number) => () => {
|
||||||
|
const value = node._value as unknown[];
|
||||||
|
value.splice(index + offset, 0, value.splice(index, 1)[0]),
|
||||||
|
node.input(value, false);
|
||||||
|
};
|
||||||
|
fns.createInsert = (index: number) => () => {
|
||||||
|
const value = node._value as unknown[];
|
||||||
|
value.splice(index + 1, 0, {}), node.input(value, false);
|
||||||
|
};
|
||||||
|
fns.createAppend = () => () => {
|
||||||
|
const value = node._value as unknown[];
|
||||||
|
value.push({}), node.input(value, false);
|
||||||
|
};
|
||||||
|
fns.createRemover = (index: number) => () => {
|
||||||
|
const value = node._value as unknown[];
|
||||||
|
value.splice(index, 1), node.input(value, false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,47 @@
|
||||||
import type { FormKitTypeDefinition } from "@formkit/core";
|
import type { FormKitTypeDefinition } from "@formkit/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
outer,
|
||||||
fieldset,
|
fieldset,
|
||||||
|
legend,
|
||||||
help,
|
help,
|
||||||
inner,
|
inner,
|
||||||
legend,
|
|
||||||
message,
|
|
||||||
messages,
|
|
||||||
outer,
|
|
||||||
prefix,
|
prefix,
|
||||||
|
$if,
|
||||||
suffix,
|
suffix,
|
||||||
|
messages,
|
||||||
|
message,
|
||||||
} from "@formkit/inputs";
|
} from "@formkit/inputs";
|
||||||
import { repeaterItems } from "./sections";
|
import { repeats } from "./features/repeats";
|
||||||
import Repeater from "./Repeater.vue";
|
import {
|
||||||
import type { FormKitInputs } from "@formkit/inputs";
|
addButton,
|
||||||
|
content,
|
||||||
|
controls,
|
||||||
|
down,
|
||||||
|
downControl,
|
||||||
|
downIcon,
|
||||||
|
empty,
|
||||||
|
group,
|
||||||
|
insert,
|
||||||
|
insertControl,
|
||||||
|
insertIcon,
|
||||||
|
item,
|
||||||
|
items,
|
||||||
|
remove,
|
||||||
|
removeControl,
|
||||||
|
removeIcon,
|
||||||
|
up,
|
||||||
|
upControl,
|
||||||
|
upIcon,
|
||||||
|
} from "./sections";
|
||||||
|
import {
|
||||||
|
IconAddCircle,
|
||||||
|
IconCloseCircle,
|
||||||
|
IconArrowUpCircleLine,
|
||||||
|
IconArrowDownCircleLine,
|
||||||
|
} from "@halo-dev/components";
|
||||||
|
import AddButton from "./AddButton.vue";
|
||||||
|
import { i18n } from "@/locales";
|
||||||
|
|
||||||
declare module "@formkit/inputs" {
|
declare module "@formkit/inputs" {
|
||||||
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
|
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
|
||||||
|
@ -23,18 +52,71 @@ declare module "@formkit/inputs" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input definition for a repeater input.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export const repeater: FormKitTypeDefinition = {
|
export const repeater: FormKitTypeDefinition = {
|
||||||
|
/**
|
||||||
|
* The actual schema of the input, or a function that returns the schema.
|
||||||
|
*/
|
||||||
schema: outer(
|
schema: outer(
|
||||||
fieldset(
|
fieldset(
|
||||||
legend("$label"),
|
legend("$label"),
|
||||||
help("$help"),
|
help("$help"),
|
||||||
inner(prefix(), repeaterItems("$slots.default"), suffix())
|
|
||||||
|
inner(
|
||||||
|
prefix(),
|
||||||
|
$if(
|
||||||
|
"$value.length === 0",
|
||||||
|
$if("$slots.empty", empty()),
|
||||||
|
$if(
|
||||||
|
"$slots.default",
|
||||||
|
items(
|
||||||
|
item(
|
||||||
|
content(group("$slots.default")),
|
||||||
|
controls(
|
||||||
|
up(upControl(upIcon())),
|
||||||
|
remove(removeControl(removeIcon())),
|
||||||
|
insert(insertControl(insertIcon())),
|
||||||
|
down(downControl(downIcon()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
suffix()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
addButton(`$addLabel || (${i18n.global.t("core.common.buttons.add")})`)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
messages(message("$message.value"))
|
messages(message("$message.value"))
|
||||||
),
|
),
|
||||||
|
/**
|
||||||
|
* The type of node, can be a list, group, or input.
|
||||||
|
*/
|
||||||
type: "list",
|
type: "list",
|
||||||
props: ["min", "max"],
|
/**
|
||||||
|
* An array of extra props to accept for this input.
|
||||||
|
*/
|
||||||
|
props: [
|
||||||
|
"min",
|
||||||
|
"max",
|
||||||
|
"upControl",
|
||||||
|
"downControl",
|
||||||
|
"removeControl",
|
||||||
|
"insertControl",
|
||||||
|
"addLabel",
|
||||||
|
"addButton",
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* Additional features that make this input work.
|
||||||
|
*/
|
||||||
|
features: [repeats],
|
||||||
library: {
|
library: {
|
||||||
Repeater: Repeater,
|
IconAddCircle,
|
||||||
|
IconCloseCircle,
|
||||||
|
IconArrowUpCircleLine,
|
||||||
|
IconArrowDownCircleLine,
|
||||||
|
AddButton,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import {
|
||||||
|
isComponent,
|
||||||
|
isDOM,
|
||||||
|
type FormKitSchemaNode,
|
||||||
|
type FormKitExtendableSchemaRoot,
|
||||||
|
type FormKitSchemaCondition,
|
||||||
|
} from "@formkit/core";
|
||||||
|
import {
|
||||||
|
extendSchema,
|
||||||
|
type FormKitSchemaExtendableSection,
|
||||||
|
type FormKitSection,
|
||||||
|
} from "@formkit/inputs";
|
||||||
|
|
||||||
|
export function createRepeaterSection() {
|
||||||
|
return (
|
||||||
|
section: string,
|
||||||
|
el: string | null | (() => FormKitSchemaNode),
|
||||||
|
fragment = false
|
||||||
|
) => {
|
||||||
|
return createSection(
|
||||||
|
section,
|
||||||
|
el,
|
||||||
|
fragment
|
||||||
|
) as FormKitSection<FormKitSchemaExtendableSection>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSection(
|
||||||
|
section: string,
|
||||||
|
el: string | null | (() => FormKitSchemaNode),
|
||||||
|
fragment = false
|
||||||
|
): FormKitSection<
|
||||||
|
FormKitExtendableSchemaRoot | FormKitSchemaExtendableSection
|
||||||
|
> {
|
||||||
|
return (
|
||||||
|
...children: Array<
|
||||||
|
FormKitSchemaExtendableSection | string | FormKitSchemaCondition
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const extendable = (
|
||||||
|
extensions: Record<string, Partial<FormKitSchemaNode>>
|
||||||
|
) => {
|
||||||
|
const node = !el || typeof el === "string" ? { $el: el } : el();
|
||||||
|
if ("string" != typeof node) {
|
||||||
|
if (isDOM(node) || isComponent(node) || "$formkit" in node) {
|
||||||
|
if (children.length && !node.children) {
|
||||||
|
node.children = [
|
||||||
|
...children.map((child) =>
|
||||||
|
typeof child === "function" ? child(extensions) : child
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (!node.meta) {
|
||||||
|
node.meta = { section };
|
||||||
|
}
|
||||||
|
if (isDOM(node)) {
|
||||||
|
node.attrs = {
|
||||||
|
class: `$classes.${section}`,
|
||||||
|
...(node.attrs || {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("$formkit" in node) {
|
||||||
|
node.outerClass = `$classes.${section}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
if: `$slots.${section}`,
|
||||||
|
then: `$slots.${section}`,
|
||||||
|
else:
|
||||||
|
section in extensions
|
||||||
|
? extendSchema(node as FormKitSchemaNode, extensions[section])
|
||||||
|
: node,
|
||||||
|
} as FormKitSchemaCondition;
|
||||||
|
};
|
||||||
|
extendable._s = section;
|
||||||
|
return fragment ? createRoot(extendable) : extendable;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns an extendable schema root node.
|
||||||
|
*
|
||||||
|
* @param rootSection - Creates the root node.
|
||||||
|
*
|
||||||
|
* @returns {@link @formkit/core#FormKitExtendableSchemaRoot | FormKitExtendableSchemaRoot}
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function createRoot(
|
||||||
|
rootSection: FormKitSchemaExtendableSection
|
||||||
|
): FormKitExtendableSchemaRoot {
|
||||||
|
return (extensions: Record<string, Partial<FormKitSchemaNode>>) => {
|
||||||
|
return [rootSection(extensions)];
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,8 +1,134 @@
|
||||||
import { createSection } from "@formkit/inputs";
|
import { createRepeaterSection } from "../repeaterSection";
|
||||||
|
|
||||||
export const repeaterItems = createSection("repeaterItems", () => ({
|
const repeaterSection = createRepeaterSection();
|
||||||
$cmp: "Repeater",
|
|
||||||
|
export const addButton = repeaterSection("addButton", () => ({
|
||||||
|
$cmp: "AddButton",
|
||||||
props: {
|
props: {
|
||||||
|
onClick: "$fns.createAppend()",
|
||||||
|
disabled: "$value.length >= $max",
|
||||||
context: "$node.context",
|
context: "$node.context",
|
||||||
},
|
},
|
||||||
|
bind: "$addAttrs",
|
||||||
|
if: "$addButton",
|
||||||
|
type: "button",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const content = repeaterSection("content", "div");
|
||||||
|
|
||||||
|
export const controlLabel = repeaterSection("controlLabel", "span");
|
||||||
|
|
||||||
|
export const controls = repeaterSection("controls", () => ({
|
||||||
|
$el: "ul",
|
||||||
|
if: "$removeControl || $insertControl || $upControl || $downControl",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const down = repeaterSection("down", () => ({
|
||||||
|
$el: "li",
|
||||||
|
if: "$downControl",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const downControl = repeaterSection("downControl", () => ({
|
||||||
|
$el: "button",
|
||||||
|
attrs: {
|
||||||
|
disabled: "$index >= $value.length - 1",
|
||||||
|
onClick: "$fns.createShift($index, 1)",
|
||||||
|
type: "button",
|
||||||
|
class: `$classes.control`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const downIcon = repeaterSection("downIcon", () => ({
|
||||||
|
$cmp: "IconArrowDownCircleLine",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const empty = repeaterSection("empty", () => ({
|
||||||
|
$el: "div",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const fieldset = repeaterSection("fieldset", () => ({
|
||||||
|
$el: "fieldset",
|
||||||
|
attrs: {
|
||||||
|
id: "$id",
|
||||||
|
disabled: "$disabled",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const group = repeaterSection("group", () => ({
|
||||||
|
$formkit: "nativeGroup",
|
||||||
|
index: "$index",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const insert = repeaterSection("insert", () => ({
|
||||||
|
$el: "li",
|
||||||
|
if: "$insertControl",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const insertControl = repeaterSection("insertControl", () => ({
|
||||||
|
$el: "button",
|
||||||
|
attrs: {
|
||||||
|
disabled: "$value.length >= $max",
|
||||||
|
onClick: "$fns.createInsert($index)",
|
||||||
|
type: "button",
|
||||||
|
class: `$classes.control`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const insertIcon = repeaterSection("insertIcon", () => ({
|
||||||
|
$cmp: "IconAddCircle",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const item = repeaterSection("item", () => ({
|
||||||
|
$el: "li",
|
||||||
|
for: ["item", "index", "$items"],
|
||||||
|
attrs: {
|
||||||
|
role: "listitem",
|
||||||
|
key: "$item",
|
||||||
|
"data-index": "$index",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const items = repeaterSection("items", () => ({
|
||||||
|
$el: "ul",
|
||||||
|
attrs: {
|
||||||
|
role: "list",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const remove = repeaterSection("remove", () => ({
|
||||||
|
$el: "li",
|
||||||
|
if: "$removeControl",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const removeControl = repeaterSection("removeControl", () => ({
|
||||||
|
$el: "button",
|
||||||
|
attrs: {
|
||||||
|
disabled: "$value.length <= $min",
|
||||||
|
onClick: "$fns.createRemover($index)",
|
||||||
|
type: "button",
|
||||||
|
class: `$classes.control`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const removeIcon = repeaterSection("removeIcon", () => ({
|
||||||
|
$cmp: "IconCloseCircle",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const up = repeaterSection("up", () => ({
|
||||||
|
$el: "li",
|
||||||
|
if: "$upControl",
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const upControl = repeaterSection("upControl", () => ({
|
||||||
|
$el: "button",
|
||||||
|
attrs: {
|
||||||
|
disabled: "$index <= 0",
|
||||||
|
onClick: "$fns.createShift($index, -1)",
|
||||||
|
type: "button",
|
||||||
|
class: `$classes.control`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const upIcon = repeaterSection("upIcon", () => ({
|
||||||
|
$cmp: "IconArrowUpCircleLine",
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -89,7 +89,10 @@ const theme: Record<string, Record<string, string>> = {
|
||||||
items: "flex flex-col w-full gap-2 rounded-base",
|
items: "flex flex-col w-full gap-2 rounded-base",
|
||||||
item: "border rounded-base grid grid-cols-12 focus-within:border-primary transition-all overflow-visible focus-within:shadow-sm",
|
item: "border rounded-base grid grid-cols-12 focus-within:border-primary transition-all overflow-visible focus-within:shadow-sm",
|
||||||
content: "flex-1 p-2 col-span-11 divide-y divide-gray-100",
|
content: "flex-1 p-2 col-span-11 divide-y divide-gray-100",
|
||||||
controls: "bg-gray-200 col-span-1 flex items-center justify-center",
|
controls:
|
||||||
|
"flex flex-col items-center justify-center gap-1.5 py-2 bg-gray-200 col-span-1 flex items-center justify-center",
|
||||||
|
control:
|
||||||
|
"cursor-pointer text-gray-500 transition-all hover:text-primary disabled:!cursor-not-allowed disabled:opacity-50 disabled:hover:!text-gray-500",
|
||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
label: textClassification.label,
|
label: textClassification.label,
|
||||||
|
|
Loading…
Reference in New Issue