+
+
+
+
+
+ {{ context.addLabel || $t("core.common.buttons.add") }}
+
+
+
diff --git a/ui/src/formkit/inputs/list/features/lists.ts b/ui/src/formkit/inputs/list/features/lists.ts
new file mode 100644
index 000000000..86ff42f2c
--- /dev/null
+++ b/ui/src/formkit/inputs/list/features/lists.ts
@@ -0,0 +1,79 @@
+import type { FormKitNode } from "@formkit/core";
+import { undefine } from "@formkit/utils";
+
+export const lists = function (node: FormKitNode) {
+ node._c.sync = true;
+ node.on("created", listFeature.bind(null, node));
+};
+
+const fn = (node: FormKitNode): object | string | boolean | number => {
+ switch (node.props.itemType.toLocaleLowerCase()) {
+ case "object":
+ return {};
+ case "boolean":
+ return false;
+ case "number":
+ return 0;
+ default:
+ return "";
+ }
+};
+
+function createValue(num: number, node: FormKitNode) {
+ return new Array(num).fill("").map(() => fn(node));
+}
+
+function listFeature(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) : Infinity;
+ node.props.itemType = node.props.itemType ?? "string";
+ if (node.props.min > node.props.max) {
+ throw Error("list: 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);
+ 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, node), 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, fn(node)), node.input(value, false);
+ };
+ fns.createAppend = () => () => {
+ const value = node._value as unknown[];
+ console.log(fn(node));
+ value.push(fn(node)), node.input(value, false);
+ };
+ fns.createRemover = (index: number) => () => {
+ const value = node._value as unknown[];
+ value.splice(index, 1), node.input(value, false);
+ };
+ }
+}
diff --git a/ui/src/formkit/inputs/list/index.ts b/ui/src/formkit/inputs/list/index.ts
new file mode 100644
index 000000000..05631cf72
--- /dev/null
+++ b/ui/src/formkit/inputs/list/index.ts
@@ -0,0 +1,116 @@
+import type { FormKitTypeDefinition } from "@formkit/core";
+
+import {
+ disablesChildren,
+ renamesRadios,
+ fieldset,
+ messages,
+ message,
+ outer,
+ legend,
+ help,
+ inner,
+ prefix,
+ $if,
+ suffix,
+} from "@formkit/inputs";
+import {
+ addButton,
+ content,
+ controls,
+ down,
+ downControl,
+ downIcon,
+ empty,
+ insert,
+ insertControl,
+ insertIcon,
+ item,
+ items,
+ remove,
+ removeControl,
+ removeIcon,
+ up,
+ upControl,
+ upIcon,
+} from "./sections";
+import { i18n } from "@/locales";
+import {
+ IconAddCircle,
+ IconArrowDownCircleLine,
+ IconArrowUpCircleLine,
+ IconCloseCircle,
+} from "@halo-dev/components";
+import AddButton from "./AddButton.vue";
+import { lists } from "./features/lists";
+
+/**
+ * Input definition for a dynamic list input.
+ * @public
+ */
+export const list: FormKitTypeDefinition = {
+ /**
+ * The actual schema of the input, or a function that returns the schema.
+ */
+ schema: outer(
+ fieldset(
+ legend("$label"),
+ help("$help"),
+ inner(
+ prefix(),
+ $if(
+ "$value.length === 0",
+ $if("$slots.empty", empty()),
+ $if(
+ "$slots.default",
+ items(
+ item(
+ content("$slots.default"),
+ controls(
+ up(upControl(upIcon())),
+ remove(removeControl(removeIcon())),
+ insert(insertControl(insertIcon())),
+ down(downControl(downIcon()))
+ )
+ )
+ ),
+ suffix()
+ )
+ ),
+ suffix(),
+ addButton(`$addLabel || (${i18n.global.t("core.common.buttons.add")})`)
+ )
+ ),
+ messages(message("$message.value"))
+ ),
+ /**
+ * The type of node, can be a list, group, or input.
+ */
+ type: "list",
+ /**
+ * An array of extra props to accept for this input.
+ */
+ props: [
+ "min",
+ "max",
+ "upControl",
+ "downControl",
+ "removeControl",
+ "insertControl",
+ "addLabel",
+ "addButton",
+ "itemType",
+ ],
+ /**
+ * Additional features that should be added to your input
+ */
+ features: [lists, disablesChildren, renamesRadios],
+
+ library: {
+ IconAddCircle,
+ IconCloseCircle,
+ IconArrowUpCircleLine,
+ IconArrowDownCircleLine,
+ AddButton,
+ },
+};
diff --git a/ui/src/formkit/inputs/list/listSection.ts b/ui/src/formkit/inputs/list/listSection.ts
new file mode 100644
index 000000000..816c6ae01
--- /dev/null
+++ b/ui/src/formkit/inputs/list/listSection.ts
@@ -0,0 +1,97 @@
+import {
+ isComponent,
+ isDOM,
+ type FormKitSchemaNode,
+ type FormKitExtendableSchemaRoot,
+ type FormKitSchemaCondition,
+ type FormKitSectionsSchema,
+} from "@formkit/core";
+import {
+ extendSchema,
+ type FormKitSchemaExtendableSection,
+ type FormKitSection,
+} from "@formkit/inputs";
+
+export function createListSection() {
+ return (
+ section: string,
+ el: string | null | (() => FormKitSchemaNode),
+ fragment = false
+ ) => {
+ return createSection(
+ section,
+ el,
+ fragment
+ ) as FormKitSection