@dou-ui/form-builder 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/builder/canvas.js +695 -0
- package/dist/builder/canvas.js.map +1 -0
- package/dist/builder/completion.js +49 -0
- package/dist/builder/completion.js.map +1 -0
- package/dist/builder/condition-editor.js +6 -0
- package/dist/builder/condition-editor.js.map +1 -0
- package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js +410 -0
- package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/condition.js +159 -0
- package/dist/builder/condition.js.map +1 -0
- package/dist/builder/context.js +477 -0
- package/dist/builder/context.js.map +1 -0
- package/dist/builder/core/commands.js +373 -0
- package/dist/builder/core/commands.js.map +1 -0
- package/dist/builder/core/history.js +76 -0
- package/dist/builder/core/history.js.map +1 -0
- package/dist/builder/core/transaction.js +298 -0
- package/dist/builder/core/transaction.js.map +1 -0
- package/dist/builder/drag.js +466 -0
- package/dist/builder/drag.js.map +1 -0
- package/dist/builder/expression-box.js +19 -0
- package/dist/builder/expression-box.js.map +1 -0
- package/dist/builder/expression-input.js +6 -0
- package/dist/builder/expression-input.js.map +1 -0
- package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/extension.js +47 -0
- package/dist/builder/extension.js.map +1 -0
- package/dist/builder/form-builder.js +6 -0
- package/dist/builder/form-builder.js.map +1 -0
- package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js +450 -0
- package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/index.js +22 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/inspector.js +6 -0
- package/dist/builder/inspector.js.map +1 -0
- package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js +442 -0
- package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/json-dialog.js +6 -0
- package/dist/builder/json-dialog.js.map +1 -0
- package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js +281 -0
- package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/locale.js +1271 -0
- package/dist/builder/locale.js.map +1 -0
- package/dist/builder/logic-fields.js +18 -0
- package/dist/builder/logic-fields.js.map +1 -0
- package/dist/builder/logic.js +332 -0
- package/dist/builder/logic.js.map +1 -0
- package/dist/builder/menu.js +92 -0
- package/dist/builder/menu.js.map +1 -0
- package/dist/builder/outline-row.js +6 -0
- package/dist/builder/outline-row.js.map +1 -0
- package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/outline.js +6 -0
- package/dist/builder/outline.js.map +1 -0
- package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/palette.js +6 -0
- package/dist/builder/palette.js.map +1 -0
- package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/preview.js +6 -0
- package/dist/builder/preview.js.map +1 -0
- package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js +111 -0
- package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/builder/settings-binding.js +57 -0
- package/dist/builder/settings-binding.js.map +1 -0
- package/dist/builder/settings.js +487 -0
- package/dist/builder/settings.js.map +1 -0
- package/dist/builder/span-field.js +104 -0
- package/dist/builder/span-field.js.map +1 -0
- package/dist/code/expression-input.js +6 -0
- package/dist/code/expression-input.js.map +1 -0
- package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/extension.js +202 -0
- package/dist/code/extension.js.map +1 -0
- package/dist/code/index.js +19 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +43 -0
- package/dist/code/language.js.map +1 -0
- package/dist/collaboration/index.js +382 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/core/check.js +314 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/document.js +112 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/expression.js +854 -0
- package/dist/core/expression.js.map +1 -0
- package/dist/core/formulas.js +242 -0
- package/dist/core/formulas.js.map +1 -0
- package/dist/core/rules.js +83 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/values.js +80 -0
- package/dist/core/values.js.map +1 -0
- package/dist/fields/builtin.js +167 -0
- package/dist/fields/builtin.js.map +1 -0
- package/dist/fields/components.js +155 -0
- package/dist/fields/components.js.map +1 -0
- package/dist/fields/define.js +44 -0
- package/dist/fields/define.js.map +1 -0
- package/dist/fields/format.js +125 -0
- package/dist/fields/format.js.map +1 -0
- package/dist/fields/options.js +54 -0
- package/dist/fields/options.js.map +1 -0
- package/dist/index.js +20 -0
- package/dist/locale.js +161 -0
- package/dist/locale.js.map +1 -0
- package/dist/renderer/containers.js +430 -0
- package/dist/renderer/containers.js.map +1 -0
- package/dist/renderer/context.js +21 -0
- package/dist/renderer/context.js.map +1 -0
- package/dist/renderer/form-renderer.js +6 -0
- package/dist/renderer/form-renderer.js.map +1 -0
- package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js +272 -0
- package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/renderer/index.js +19 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/node-view.js +385 -0
- package/dist/renderer/node-view.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +27 -0
- package/dist/types/builder/canvas.d.ts +5 -0
- package/dist/types/builder/completion.d.ts +35 -0
- package/dist/types/builder/condition-editor.vue.d.ts +17 -0
- package/dist/types/builder/condition.d.ts +37 -0
- package/dist/types/builder/context.d.ts +149 -0
- package/dist/types/builder/core/commands.d.ts +74 -0
- package/dist/types/builder/core/history.d.ts +23 -0
- package/dist/types/builder/core/transaction.d.ts +108 -0
- package/dist/types/builder/drag.d.ts +52 -0
- package/dist/types/builder/expression-box.d.ts +8 -0
- package/dist/types/builder/expression-input.vue.d.ts +29 -0
- package/dist/types/builder/extension.d.ts +149 -0
- package/dist/types/builder/form-builder.vue.d.ts +24 -0
- package/dist/types/builder/index.d.ts +76 -0
- package/dist/types/builder/inspector.vue.d.ts +3 -0
- package/dist/types/builder/json-dialog.vue.d.ts +10 -0
- package/dist/types/builder/locale.d.ts +195 -0
- package/dist/types/builder/logic-fields.d.ts +2 -0
- package/dist/types/builder/logic.d.ts +57 -0
- package/dist/types/builder/menu.d.ts +23 -0
- package/dist/types/builder/outline-row.vue.d.ts +19 -0
- package/dist/types/builder/outline.vue.d.ts +3 -0
- package/dist/types/builder/palette.vue.d.ts +3 -0
- package/dist/types/builder/preview.vue.d.ts +3 -0
- package/dist/types/builder/settings-binding.d.ts +23 -0
- package/dist/types/builder/settings.d.ts +49 -0
- package/dist/types/builder/span-field.d.ts +4 -0
- package/dist/types/builder/types.d.ts +59 -0
- package/dist/types/code/expression-input.vue.d.ts +23 -0
- package/dist/types/code/extension.d.ts +45 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +6 -0
- package/dist/types/collaboration/index.d.ts +64 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/core/check.d.ts +68 -0
- package/dist/types/core/document.d.ts +36 -0
- package/dist/types/core/expression.d.ts +175 -0
- package/dist/types/core/formulas.d.ts +44 -0
- package/dist/types/core/rules.d.ts +23 -0
- package/dist/types/core/types.d.ts +211 -0
- package/dist/types/core/values.d.ts +22 -0
- package/dist/types/fields/builtin.d.ts +9 -0
- package/dist/types/fields/components.d.ts +177 -0
- package/dist/types/fields/define.d.ts +93 -0
- package/dist/types/fields/format.d.ts +76 -0
- package/dist/types/fields/options.d.ts +11 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/locale.d.ts +45 -0
- package/dist/types/renderer/containers.d.ts +20 -0
- package/dist/types/renderer/context.d.ts +186 -0
- package/dist/types/renderer/form-renderer.vue.d.ts +25 -0
- package/dist/types/renderer/index.d.ts +81 -0
- package/dist/types/renderer/node-view.d.ts +56 -0
- package/package.json +88 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"containers.js","names":[],"sources":["../../src/renderer/containers.ts"],"sourcesContent":["import {\n computed,\n defineComponent,\n h,\n inject,\n nextTick,\n onScopeDispose,\n provide,\n shallowRef,\n toRaw,\n useId,\n watch,\n} from 'vue'\nimport type { PropType, VNodeChild } from 'vue'\nimport {\n DouButton,\n DouFormItem,\n DouSortable,\n DouSortableHandle,\n DouSteps,\n DouTabPanel,\n DouTabs,\n} from 'dou-ui'\nimport type { FormItemExpose, FormRule, StepItem } from 'dou-ui'\nimport { PlusIcon, TrashIcon } from '@dou-ui/icons'\nimport { getPath, isList, setPath, valueNodes } from '../core/document'\nimport { rulesOf } from '../core/rules'\nimport type {\n FormListNode,\n FormNode,\n FormPageNode,\n FormStepsNode,\n FormTabsNode,\n} from '../core/types'\nimport { emptyItem } from '../core/values'\nimport { formatMessage } from '../locale'\nimport { rendererKey, scopeKey } from './context'\nimport type { NodeScope, RendererContext } from './context'\nimport { disabledKey, pathIn } from './context'\nimport type { Condition, NodeKit, NodeProps } from './context'\n\ntype Flag = { readonly value: boolean }\n\n/** Whether a field at `path` sits among `nodes`, inside a list item included. */\nexport function holdsPath(nodes: readonly FormNode[], path: string, prefix = ''): boolean {\n return valueNodes(nodes).some((node) => {\n const own = prefix + node.name!\n return own === path || (isList(node) && path.startsWith(`${own}.`))\n })\n}\n\n/** The paths of every field among `nodes`, the fields of each list item included. */\nexport function valuePaths(nodes: readonly FormNode[], model: unknown, prefix = ''): string[] {\n const paths: string[] = []\n for (const node of valueNodes(nodes)) {\n const own = prefix + node.name!\n paths.push(own)\n if (!isList(node)) continue\n const items = getPath(model, own)\n if (Array.isArray(items))\n items.forEach((_, index) =>\n paths.push(...valuePaths(node.children, model, `${own}.${index}.`)),\n )\n }\n return paths\n}\n\n/** The pages of tabs or steps that show, as the node's own view decides them. */\nfunction shownPages(props: NodeProps, condition: Condition) {\n return computed(() =>\n (props.node as FormTabsNode | FormStepsNode).children.filter(\n (child) => !!child && condition(child.visibleWhen, true, child),\n ),\n )\n}\n\n/** One page of tabs or steps: its description and its own grid. */\nconst pageOf = (kit: NodeKit, node: FormPageNode, props: NodeProps) =>\n h(kit.view, {\n key: node.id,\n node,\n columns: props.columns,\n inset: props.inset,\n asPage: true,\n })\n\nexport function tabsView(\n kit: NodeKit,\n props: NodeProps,\n renderer: RendererContext,\n scope: NodeScope,\n disabled: Flag,\n condition: Condition,\n) {\n provide(disabledKey, () => disabled.value)\n const pages = shownPages(props, condition)\n const chosen = shallowRef<string>()\n const selected = computed(() =>\n pages.value.some((shown) => shown.id === chosen.value) ? chosen.value : pages.value[0]?.id,\n )\n // A field with an error on another tab: switch to the tab.\n onScopeDispose(\n renderer.addRevealer((path) => {\n const found = pages.value.find((shown) => holdsPath(shown.children, path, scope.prefix()))\n if (found) chosen.value = found.id\n return !!found\n }),\n )\n return () =>\n h(\n DouTabs,\n {\n class: 'dou-form-renderer__tabs',\n modelValue: selected.value,\n 'onUpdate:modelValue': (value: string | number | undefined) => {\n if (value !== undefined) chosen.value = String(value)\n },\n },\n () =>\n pages.value.map((node) =>\n h(\n DouTabPanel,\n { key: node.id, value: node.id, label: node.title, eager: true },\n {\n default: () => pageOf(kit, node, props),\n // A page with errors gets a dot, read out as \"has errors\". Worked\n // out in the slot, so the tab list follows the errors itself.\n label: () => {\n const failed = renderer\n .errorPaths()\n .some((path) => holdsPath(node.children, path, scope.prefix()))\n return h('span', { class: 'dou-form-renderer__tab-title' }, [\n node.title,\n failed\n ? h('span', { class: 'dou-form-renderer__tab-error' }, [\n h(\n 'span',\n { class: 'dou-form-renderer__sr-only' },\n `(${renderer.messages().invalid})`,\n ),\n ])\n : null,\n ])\n },\n },\n ),\n ),\n )\n}\n\nexport function stepsView(\n kit: NodeKit,\n props: NodeProps,\n renderer: RendererContext,\n scope: NodeScope,\n disabled: Flag,\n condition: Condition,\n) {\n provide(disabledKey, () => disabled.value)\n const pages = shownPages(props, condition)\n // Read-only, there is nothing to fill in step by step: every page shows, under its title.\n if (renderer.readonly()) {\n const uid = useId()\n return () =>\n h(\n 'div',\n { class: 'dou-form-renderer__steps is-readonly' },\n pages.value.map((node) => {\n const titleId = `dou-form-step-${uid}-${node.id}`\n return h(\n 'div',\n {\n key: node.id,\n class: 'dou-form-renderer__section',\n role: 'group',\n 'aria-labelledby': titleId,\n },\n [\n h('div', { class: 'dou-form-renderer__section-header' }, [\n h('div', { id: titleId, class: 'dou-form-renderer__section-title' }, node.title),\n ]),\n pageOf(kit, node, props),\n ],\n )\n }),\n )\n }\n const chosen = shallowRef<string>()\n /** How far people got: the steps up to here can be opened again. */\n const reached = shallowRef(0)\n const failed = shallowRef(false)\n const checking = shallowRef(false)\n const current = computed(() => {\n const index = pages.value.findIndex((shown) => shown.id === chosen.value)\n return index < 0 ? 0 : index\n })\n watch(current, () => (failed.value = false))\n watch(renderer.resets, () => {\n chosen.value = undefined\n reached.value = 0\n failed.value = false\n })\n\n function go(index: number) {\n const target = pages.value[index]\n if (!target) return\n chosen.value = target.id\n reached.value = Math.max(reached.value, index)\n }\n\n // Back and Next take focus to the page they open, which names its step:\n // the button pressed may be gone, and a screen reader hears where it is.\n const stepEls = new Map<string, HTMLElement>()\n function turn(index: number) {\n go(index)\n const id = pages.value[index]?.id\n if (id) void nextTick(() => stepEls.get(id)?.focus({ preventScroll: true }))\n }\n\n async function next() {\n const shown = pages.value[current.value]\n if (!shown || checking.value) return\n checking.value = true\n try {\n const paths = valuePaths(shown.children, renderer.model(), scope.prefix())\n const { valid, errors } = await renderer.validate(paths)\n if (!valid) {\n failed.value = true\n const first = Object.keys(errors)[0]\n if (first) renderer.scrollToField(first)\n return\n }\n turn(current.value + 1)\n } finally {\n checking.value = false\n }\n }\n\n // A field with an error on another step, e.g. on submit: open its step.\n onScopeDispose(\n renderer.addRevealer((path) => {\n const index = pages.value.findIndex((page) => holdsPath(page.children, path, scope.prefix()))\n if (index >= 0) go(index)\n return index >= 0\n }),\n )\n\n return () => {\n const list = pages.value\n const index = current.value\n const messages = renderer.messages()\n const last = index >= list.length - 1\n // A step holding errors, e.g. ones set after submit, is marked like a tab.\n const errorPaths = renderer.errorPaths()\n const failing = new Set(\n list\n .filter((step) => errorPaths.some((path) => holdsPath(step.children, path, scope.prefix())))\n .map((step) => step.id),\n )\n const items: StepItem[] = list.map((step, at) => ({\n title: step.title,\n disabled: at > reached.value,\n status: failing.has(step.id) ? 'error' : undefined,\n }))\n const footer: VNodeChild[] = []\n if (index > 0)\n footer.push(\n h(\n DouButton,\n { key: 'prev', variant: 'outline', onClick: () => turn(index - 1) },\n () => messages.prev,\n ),\n )\n if (!last)\n footer.push(\n h(\n DouButton,\n { key: 'next', tone: 'primary', loading: checking.value, onClick: next },\n () => messages.next,\n ),\n )\n else if (renderer.footerOwner() === props.node.id) footer.push(renderer.footer() as VNodeChild)\n return h('div', { class: 'dou-form-renderer__steps' }, [\n // A step's status is read out with its title, \"error\" included.\n h(DouSteps, {\n items,\n current: index,\n status: failed.value ? 'error' : 'process',\n clickable: true,\n 'onUpdate:current': (at: number) => {\n if (at <= reached.value) go(at)\n },\n }),\n ...list.map((node, at) =>\n h(\n 'div',\n {\n key: node.id,\n ref: (el: unknown) => {\n if (el) stepEls.set(node.id, el as HTMLElement)\n else stepEls.delete(node.id)\n },\n class: 'dou-form-renderer__step',\n hidden: at !== index || undefined,\n role: 'group',\n tabindex: -1,\n 'aria-label': formatMessage(messages.step, {\n index: at + 1,\n count: list.length,\n title: node.title ?? '',\n }),\n },\n [pageOf(kit, node, props)],\n ),\n ),\n h('div', { class: 'dou-form-renderer__steps-footer' }, footer),\n ])\n }\n}\n\n/** Ids of list items, by the item object, so each keeps its view while the list is reordered. */\nconst itemIds = new WeakMap<object, string>()\nlet nextItem = 0\nfunction itemKey(item: unknown) {\n if (item === null || typeof item !== 'object') return `v:${String(item)}`\n const raw = toRaw(item)\n let id = itemIds.get(raw)\n if (!id) itemIds.set(raw, (id = `i${++nextItem}`))\n return id\n}\n\n/**\n * The copy of an item that follows the pointer. It sits on `body`, away\n * from the form whose width picks the columns, whose classes set the layout\n * and whose surroundings may change the gaps (the designer's settings panel\n * narrows them), so it is wrapped in an element with the form's classes and\n * every grid in it keeps the columns and gaps it has now, in pixels.\n */\nfunction listGhost(el: HTMLElement): HTMLElement {\n const clone = el.cloneNode(true) as HTMLElement\n freezeGrids(el, clone)\n const form = el.closest<HTMLElement>('.dou-form')\n if (!form) return clone\n const wrapper = document.createElement('div')\n wrapper.className = `${form.className} dou-form-renderer__list-ghost`\n // The wrapper takes the size the copy is given, not the form's container sizing.\n wrapper.style.containerType = 'normal'\n wrapper.append(clone)\n return wrapper\n}\n\n/** Writes the computed grid tracks, gaps and cell placement of `source` onto its copy. */\nfunction freezeGrids(source: HTMLElement, copy: HTMLElement) {\n const from = [source, ...source.querySelectorAll<HTMLElement>('*')]\n const to = [copy, ...copy.querySelectorAll<HTMLElement>('*')]\n from.forEach((element, index) => {\n const target = to[index]\n if (!target) return\n const style = getComputedStyle(element)\n if (style.display === 'grid' || style.display === 'inline-grid') {\n target.style.gridTemplateColumns = style.gridTemplateColumns\n target.style.columnGap = style.columnGap\n target.style.rowGap = style.rowGap\n }\n if (element.classList.contains('dou-form-renderer__cell')) {\n target.style.gridColumn = style.gridColumn\n }\n })\n}\n\n/** The rules of a list itself: its own, and its bounds on the number of items. */\nfunction listRules(node: FormListNode, renderer: RendererContext, path: () => string): FormRule[] {\n const rules = rulesOf(node, {\n functions: renderer.functions(),\n validators: renderer.validators(),\n path,\n })\n const bounds: FormRule = {}\n if (node.min !== undefined && node.min > 0) bounds.min = node.min\n if (node.max !== undefined) bounds.max = node.max\n return bounds.min === undefined && bounds.max === undefined ? rules : [...rules, bounds]\n}\n\nexport function listView(\n kit: NodeKit,\n props: NodeProps,\n renderer: RendererContext,\n scope: NodeScope,\n disabled: Flag,\n) {\n provide(disabledKey, () => disabled.value)\n const uid = useId()\n const list = () => props.node as FormListNode\n const path = () => pathIn(scope, list().name)\n const items = computed(() => {\n const value = getPath(renderer.model(), path())\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n })\n const rules = computed(() => listRules(list(), renderer, path))\n\n function write(next: Record<string, unknown>[]) {\n const name = path()\n setPath(renderer.model(), name, next)\n renderer.changed(name, getPath(renderer.model(), name))\n }\n const root = shallowRef<HTMLElement>()\n const footer = shallowRef<FormItemExpose>()\n onScopeDispose(renderer.trackError(path, () => footer.value?.error))\n\n /** The items of this list, not of lists inside it nor ones still animating out. */\n const itemElements = () =>\n [...(root.value?.querySelectorAll<HTMLElement>('.dou-form-renderer__list-item') ?? [])].filter(\n (el) =>\n el.closest('.dou-form-renderer__list') === root.value && !el.closest('[class*=\"-leave-\"]'),\n )\n /** Focus the first field of an item, or the add button when there is none. */\n async function focusItem(index: number) {\n // The sortable list renders its items a tick after the value changes.\n await nextTick()\n await nextTick()\n const elements = itemElements()\n const target = elements[Math.min(index, elements.length - 1)]\n const control = target?.querySelector<HTMLElement>(\n ':scope > .dou-form-renderer__grid :is(input, textarea, select, button, [tabindex=\"0\"]):not([disabled])',\n )\n ;(control ?? root.value?.querySelector<HTMLElement>('.dou-form-renderer__list-add'))?.focus()\n }\n function add() {\n write([...items.value, emptyItem(list(), renderer.fields())])\n void focusItem(items.value.length - 1)\n }\n function remove(index: number) {\n write(items.value.filter((_, at) => at !== index))\n void focusItem(index)\n }\n\n return () => {\n const node = list()\n const rows = items.value\n const messages = renderer.messages()\n // Read-only controls (a read-only builder's settings) show the items without the buttons too.\n const readonly = renderer.readonly() || renderer.controlsReadonly()\n const off = disabled.value\n const canAdd = !off && (node.max === undefined || rows.length < node.max)\n const canRemove = !off && rows.length > (node.min ?? 0)\n const sortable = node.sortable !== false && !off && !readonly && rows.length > 1\n const labelId = node.label ? `dou-form-list-${uid}` : undefined\n const required = node.required || rules.value.some((rule) => rule.required)\n return h(\n 'div',\n {\n ref: root,\n class: ['dou-form-renderer__list', off && 'is-disabled', readonly && 'is-readonly'],\n role: 'group',\n 'aria-labelledby': labelId,\n 'aria-disabled': off || undefined,\n },\n [\n node.label || node.help\n ? h('div', { class: 'dou-form-renderer__section-header' }, [\n node.label &&\n h(\n 'div',\n {\n id: labelId,\n class: ['dou-form-renderer__section-title', required && 'is-required'],\n },\n node.label,\n ),\n node.help && h('div', { class: 'dou-form-renderer__section-description' }, node.help),\n ])\n : null,\n rows.length\n ? h(\n DouSortable,\n {\n class: 'dou-form-renderer__list-items',\n modelValue: rows,\n 'onUpdate:modelValue': (next: unknown[]) =>\n write(next as Record<string, unknown>[]),\n itemKey,\n ghost: listGhost,\n handle: true,\n disabled: !sortable,\n itemLabel: (_item: unknown, index: number) =>\n formatMessage(messages.item, { index: index + 1 }),\n },\n {\n default: ({ item, index }: { item: Record<string, unknown>; index: number }) => {\n // While dragging, `index` is the place the item is shown at;\n // its fields still read and write the place it holds in the value.\n const raw = toRaw(item)\n const at = rows.findIndex((row) => toRaw(row) === raw)\n return h(ListItemView, {\n list: node,\n render: kit.renderNodes,\n path: path(),\n item,\n index: at < 0 ? index : at,\n position: index,\n columns: node.columns ?? props.columns,\n inset: props.inset + 1,\n sortable,\n canRemove,\n readonly,\n onRemove: () => remove(at < 0 ? index : at),\n })\n },\n },\n )\n : h(\n 'div',\n { class: 'dou-form-renderer__list-empty' },\n readonly ? messages.noValue : messages.empty,\n ),\n // After the items, so a reset puts their values back before the\n // list puts back its own items; its errors show under the button.\n readonly\n ? null\n : h(\n DouFormItem,\n {\n ref: footer,\n class: 'dou-form-renderer__list-footer',\n prop: path(),\n rules: rules.value,\n },\n () =>\n h(\n DouButton,\n {\n class: 'dou-form-renderer__list-add',\n variant: 'outline',\n disabled: !canAdd,\n onClick: add,\n },\n { leading: () => h(PlusIcon), default: () => node.addText ?? messages.add },\n ),\n ),\n ],\n )\n }\n}\n\n/** One item of a list: its fields read and write the item. */\nconst ListItemView = defineComponent({\n name: 'DouFormListItem',\n props: {\n list: { type: Object as PropType<FormListNode>, required: true },\n render: { type: Function as PropType<NodeKit['renderNodes']>, required: true },\n path: { type: String, required: true },\n item: { type: Object as PropType<Record<string, unknown>>, required: true },\n /** Where the item is in the value. */\n index: { type: Number, required: true },\n /** Where the item is shown, which differs while it is dragged. */\n position: { type: Number, required: true },\n columns: { type: Number, required: true },\n inset: { type: Number, required: true },\n sortable: Boolean,\n canRemove: Boolean,\n /** Without the remove button. */\n readonly: Boolean,\n },\n emits: ['remove'],\n setup(props, { emit }) {\n const renderer = inject(rendererKey)!\n provide(scopeKey, {\n prefix: () => `${props.path}.${props.index}.`,\n item: () => props.item,\n })\n return () => {\n const messages = renderer.messages()\n const number = props.position + 1\n return h('div', { class: 'dou-form-renderer__list-item' }, [\n h('div', { class: 'dou-form-renderer__list-item-header' }, [\n props.sortable\n ? h(DouSortableHandle, {\n class: 'dou-form-renderer__list-handle',\n label: formatMessage(messages.move, { index: number }),\n })\n : null,\n h(\n 'span',\n { class: 'dou-form-renderer__list-item-title' },\n formatMessage(messages.item, { index: number }),\n ),\n props.readonly\n ? null\n : h(\n DouButton,\n {\n class: 'dou-form-renderer__list-remove',\n variant: 'ghost',\n size: 'sm',\n disabled: !props.canRemove,\n 'aria-label': formatMessage(messages.remove, { index: number }),\n onClick: () => emit('remove'),\n },\n { leading: () => h(TrashIcon) },\n ),\n ]),\n h(\n 'div',\n { class: 'dou-form-renderer__grid', style: { '--dou-form-columns': props.columns } },\n props.render(props.list.children, props.columns, props.inset),\n ),\n ])\n }\n },\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/renderer/context.ts
|
|
2
|
+
var scopeKey = Symbol("DouFormNodeScope");
|
|
3
|
+
var rendererKey = Symbol("DouFormRenderer");
|
|
4
|
+
/**
|
|
5
|
+
* Internal: renderers inside make their controls read-only (the form's
|
|
6
|
+
* own `readonly`) instead of showing values as text, e.g. the settings of
|
|
7
|
+
* a read-only builder.
|
|
8
|
+
*/
|
|
9
|
+
var controlsReadonlyKey = Symbol("DouFormControlsReadonly");
|
|
10
|
+
/** Whether the groups around a node are disabled. */
|
|
11
|
+
var disabledKey = Symbol("DouFormNodeDisabled");
|
|
12
|
+
/** The scope of nodes outside lists. */
|
|
13
|
+
var rootScope = {
|
|
14
|
+
prefix: () => "",
|
|
15
|
+
item: () => null
|
|
16
|
+
};
|
|
17
|
+
/** The full path of a name in a scope. */
|
|
18
|
+
var pathIn = (scope, name) => scope.prefix() + name;
|
|
19
|
+
export { controlsReadonlyKey, disabledKey, pathIn, rendererKey, rootScope, scopeKey };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../src/renderer/context.ts"],"sourcesContent":["import type { Component, InjectionKey, VNode } from 'vue'\nimport type { FormErrors, FormModel, FormRules, FormValidateResult } from 'dou-ui'\nimport type { FormRendererMessages, FormRendererMessagesOverride } from '../locale'\nimport type { FormFieldType } from '../fields/define'\nimport type { FormDocument, FormNode, FormValidator } from '../core/types'\nimport type { FormFunction } from '../core/expression'\n\nexport interface FormRendererProps {\n /** The form to render: its fields, groups and layout, as plain data. */\n form: FormDocument\n /**\n * The object the fields write to, changed in place. Pass a reactive\n * object to read the values as they change; fields missing from it are\n * filled with their defaults. Defaults to an object of the renderer's own.\n */\n model?: FormModel\n /** Field types besides the built-in ones; one of the same name replaces the built-in type. */\n fields?: FormFieldType<any>[]\n /** More rules by field name, e.g. checks that need code. Added to the document's own. */\n rules?: FormRules\n /** Disable every field. */\n disabled?: boolean\n /**\n * Show the values as text instead of fields, e.g. on a detail page or an\n * approval. Lists show their items without the buttons, steps show every\n * page one after another, and the model is never written to.\n */\n readonly?: boolean\n /**\n * Functions the document's expressions can call besides the built-in\n * ones, e.g. `{ tax: (price) => price * 0.06 }`. They get plain values\n * and should not change them; one of the same name replaces a built-in function.\n */\n functions?: Record<string, FormFunction>\n /**\n * Where options come from, by the name a node's `source` gives. A source\n * gets the result of the node's `arg` expression and returns the\n * options, or a promise of them. Results are kept for each name and\n * `arg`, so fields asking the same thing share one call. `reloadSource`\n * forgets them, e.g. after the options changed on the server.\n */\n sources?: Record<string, FormOptionsLoader>\n /**\n * Checks that need code, by the name a rule's `validator` gives, e.g. one\n * asking the server whether a user name is taken. A validator may return\n * a promise; only the result of the latest check of a field is shown.\n */\n validators?: Record<string, FormValidator>\n /**\n * Submit the values of hidden fields too. By default `submit` hands over\n * a copy of the values without them (see `visibleValues`), so a field\n * that was filled in and then hidden is not sent.\n */\n keepHidden?: boolean\n /** Replaces texts of the locale, e.g. the step buttons. */\n messages?: FormRendererMessagesOverride\n}\n\n/** Loads the options of a field, given the result of its `source.arg`. */\nexport type FormOptionsLoader = (arg: any) => unknown[] | Promise<unknown[]>\n\nexport interface FormRendererEmits {\n /** A field changed its value: its `name`, and the value it now holds. */\n change: [name: string, value: unknown]\n /**\n * The form was submitted and every field passed: a copy of the values,\n * without the ones of hidden fields unless `keepHidden` is set.\n */\n submit: [values: FormModel]\n /** The form was submitted and some fields failed. */\n invalid: [errors: Record<string, string>]\n /** The form was reset to its starting values. */\n reset: []\n}\n\nexport interface FormRendererSlots {\n /**\n * After the fields, e.g. the submit and reset buttons. In a form that is\n * a series of steps, on the last step beside the back button.\n */\n default?: () => unknown\n}\n\nexport interface FormRendererExpose {\n /** The object the fields write to. */\n readonly model: FormModel\n /** Check the given fields, or every field. Never rejects. */\n validate: (names?: string | string[]) => Promise<FormValidateResult>\n /**\n * Restore the given fields, or every field, to their starting values and\n * clear their errors. Resetting every field also goes back to the first step.\n */\n resetFields: (names?: string | string[]) => void\n /** Clear the errors of the given fields, or every field. */\n clearValidate: (names?: string | string[]) => void\n /**\n * Show errors by field path, e.g. ones a server returned after submit.\n * Each stays until the field's value changes or it is checked again; an\n * empty or `undefined` message clears the field's error. Tabs and steps\n * mark the pages with errors. Returns the paths with a message that no\n * field shows, e.g. a hidden field's, to list them above the form.\n */\n setErrors: (errors: Partial<FormErrors>) => string[]\n /** Scroll a field into view and focus it. */\n scrollToField: (name: string) => void\n /** Forget the options loaded from a source, or from every source, and load them again. */\n reloadSource: (name?: string) => void\n /** A copy of the values as `submit` hands them over, without hidden fields unless `keepHidden` is set. */\n getValues: () => FormModel\n}\n\n/** What every node of a renderer reads. */\nexport interface RendererContext {\n model: () => FormModel\n fields: () => Map<string, FormFieldType>\n /** Pixel width of the label column in the horizontal layout, else `undefined`. */\n labelWidth: () => number | undefined\n /** Tell the renderer a field wrote a value. */\n changed: (name: string, value: unknown) => void\n /** The functions expressions can call besides the built-in ones. */\n functions: () => Record<string, FormFunction> | undefined\n /** The validators rules can name. */\n validators: () => Record<string, FormValidator> | undefined\n /** Whether values show as text. */\n readonly: () => boolean\n /** The ids of the formula fields on a circle, which are not worked out. */\n blockedFormulas: () => ReadonlySet<string>\n /**\n * In a read-only form, a copy of the values with every formula worked\n * out, which formula fields show; nothing writes the model itself.\n */\n settled: () => FormModel\n /** Whether the controls are read-only, as in a read-only builder's settings. */\n controlsReadonly: () => boolean\n /** The options from a source, shared by every field asking the same thing. */\n load: (name: string, arg: unknown) => Promise<unknown[]>\n /** Bumped by `reloadSource`, so fields reading it load again. */\n sourceVersion: (name: string) => number\n messages: () => FormRendererMessages\n validate: (names: string[]) => Promise<FormValidateResult>\n scrollToField: (name: string) => void\n /**\n * Add something that can bring a field into view, e.g. tabs selecting\n * the page it is on. It says whether it holds the field.\n */\n addRevealer: (reveal: (path: string) => boolean) => () => void\n /** Follow the error a form item shows, so tabs can mark the pages with errors. */\n trackError: (path: () => string | undefined, error: () => string | undefined) => () => void\n /** The paths of the fields that show an error now. */\n errorPaths: () => string[]\n /** The default slot, for the steps that show it on their last step. */\n footer: () => unknown\n /** The id of the steps that show the default slot, if the form is a series of steps. */\n footerOwner: () => string | undefined\n /** Counts the times the whole form was reset, so steps go back to the first one. */\n resets: () => number\n}\n\n/** Where a node sits: inside a list item, its fields' paths start with the item's. */\nexport interface NodeScope {\n /** Put before the names of fields, e.g. `guests.2.`. */\n prefix: () => string\n /** The item, read as `item` by expressions. */\n item: () => unknown\n}\n\nexport const scopeKey: InjectionKey<NodeScope> = Symbol('DouFormNodeScope')\n\nexport const rendererKey: InjectionKey<RendererContext> = Symbol('DouFormRenderer')\n\n/**\n * Internal: renderers inside make their controls read-only (the form's\n * own `readonly`) instead of showing values as text, e.g. the settings of\n * a read-only builder.\n */\nexport const controlsReadonlyKey: InjectionKey<() => boolean> = Symbol('DouFormControlsReadonly')\n\n/** Whether the groups around a node are disabled. */\nexport const disabledKey: InjectionKey<() => boolean> = Symbol('DouFormNodeDisabled')\n\n/** The scope of nodes outside lists. */\nexport const rootScope: NodeScope = {\n prefix: () => '',\n item: () => null,\n}\n\n/** The full path of a name in a scope. */\nexport const pathIn = (scope: NodeScope, name: string) => scope.prefix() + name\n\n/** The props of a node's view. */\nexport type NodeProps = { node: FormNode; columns: number; inset: number; asPage: boolean }\n\n/** Runs an expression of a node (the viewed one by default); `fallback` when it is unset or does not parse. */\nexport type Condition = (source: string | undefined, fallback: boolean, node?: FormNode) => boolean\n\n/** What container views draw their children with, handed in to keep the modules apart. */\nexport interface NodeKit {\n view: Component\n renderNodes: (nodes: readonly FormNode[], columns: number, inset?: number) => VNode[]\n}\n"],"mappings":";AAsKA,IAAa,WAAoC,OAAO,kBAAkB;AAE1E,IAAa,cAA6C,OAAO,iBAAiB;;;;;;AAOlF,IAAa,sBAAmD,OAAO,yBAAyB;;AAGhG,IAAa,cAA2C,OAAO,qBAAqB;;AAGpF,IAAa,YAAuB;CAClC,cAAc;CACd,YAAY;AACd;;AAGA,IAAa,UAAU,OAAkB,SAAiB,MAAM,OAAO,IAAI"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import form_renderer_vue_vue_type_script_setup_true_lang_default from "./form-renderer.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/renderer/form-renderer.vue
|
|
3
|
+
var form_renderer_default = form_renderer_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { form_renderer_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=form-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-renderer.js","names":["_sfc_main"],"sources":["../../src/renderer/form-renderer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n computed,\n effectScope,\n inject,\n nextTick,\n provide,\n reactive,\n shallowReactive,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\nimport { DouForm, showFormErrors } from 'dou-ui'\nimport type { FormErrors, FormInstance, FormModel } from 'dou-ui'\nimport { checkVersion, cloneValue, getPath, isSteps, setPath } from '../core/document'\nimport { equalValues } from '../core/expression'\nimport { fillValues, visibleValues } from '../core/values'\nimport { resolveFields } from '../fields/builtin'\nimport { useFormRendererMessages } from '../locale'\nimport { controlsReadonlyKey, rendererKey } from './context'\nimport type {\n FormRendererEmits,\n FormRendererExpose,\n FormRendererProps,\n FormRendererSlots,\n} from './context'\nimport { analyseFormulas, settleFormulas } from '../core/formulas'\nimport { FormNodeView, nodeKey } from './node-view'\n\ndefineOptions({ name: 'DouFormRenderer' })\n\nconst props = withDefaults(defineProps<FormRendererProps>(), { disabled: false, readonly: false })\nconst emit = defineEmits<FormRendererEmits>()\nconst slots = defineSlots<FormRendererSlots>()\n\nconst controlsReadonly = inject(controlsReadonlyKey, () => false)\n\nconst own = reactive<FormModel>({})\n// `reactive` hands back the same proxy for an object it has seen, and\n// writes through to the object itself.\nconst model = computed(() => reactive(props.model ?? own) as FormModel)\nconst fields = computed(() => resolveFields(props.fields))\nconst layout = computed(() => props.form.layout ?? 'vertical')\nconst columns = computed(() => Math.max(1, Math.round(props.form.columns ?? 12)))\nconst labelWidth = computed(() =>\n layout.value === 'horizontal' ? (props.form.labelWidth ?? 96) : undefined,\n)\n\nconst warnedCycles = new Set<string>()\nfunction warnCycle(cycle: string[]) {\n const key = cycle.join(' -> ')\n if (warnedCycles.has(key)) return\n warnedCycles.add(key)\n console.warn(\n `[@dou-ui/form-builder] form formulas read each other in a circle (${key}); they are not worked out`,\n )\n}\n\n// Fill in the fields the model has no value for, before the form items\n// mount and take the values as the ones to reset to. A read-only form\n// shows the values as they are, so a field left empty does not show a default.\nwatch(\n [() => props.form, model, fields, () => props.readonly],\n ([document, target, types, readonly]) => {\n checkVersion(document)\n if (!readonly) fillValues(document.nodes, target, types)\n },\n {\n immediate: true,\n },\n)\n\n// Formulas: each field with one holds its result, worked out again as\n// soon as a value it reads changes (synchronously, so a formula reading\n// another formula sees its new result). Formulas on a circle are skipped.\n// The document's formulas and the items' make one set, so a circle through\n// both is found too; each one on it is skipped, here and in the lists.\nconst analysis = computed(() => {\n const result = analyseFormulas(props.form, props.functions)\n for (const cycle of result.cycles) warnCycle(cycle.map(({ label }) => label))\n return result\n})\nwatch(\n [analysis, model, () => props.functions, () => props.readonly],\n ([formulas, target, functions, readonly], _, onCleanup) => {\n // A read-only form works formulas out as it shows them, and writes nothing.\n if (readonly) return\n // Everything at once first, so the server renders the results the page\n // will have: watchers there run only once, before the lists fill in.\n settleFormulas(props.form, target, functions, formulas)\n const scope = effectScope()\n scope.run(() => {\n for (const { name, expression } of formulas.formulas)\n watch(\n () => expression.evaluate({ values: target, value: null, functions }),\n (result) => {\n if (!equalValues(result, getPath(target, name)))\n setPath(target, name, cloneValue(result))\n },\n { immediate: true, flush: 'sync' },\n )\n })\n onCleanup(() => scope.stop())\n },\n { immediate: true },\n)\n\n// A read-only form shows formulas worked out on a copy of the values, so a\n// total reads the items' results even when the model has none of them.\nconst settled = computed(() => {\n if (!props.readonly) return model.value\n const copy = cloneValue(model.value)\n settleFormulas(props.form, copy, props.functions, analysis.value)\n return copy\n})\n\n// Options from sources, kept per source and `arg` for the renderer's\n// life; a failed call is forgotten so the next one tries again.\nconst loaded = new Map<string, Promise<unknown[]>>()\nconst versions = reactive<Record<string, number>>({})\nconst everyVersion = shallowRef(0)\nwatch(\n () => props.sources,\n () => loaded.clear(),\n)\nfunction load(name: string, arg: unknown) {\n const key = `${name}\\u0000${JSON.stringify(arg)}`\n let result = loaded.get(key)\n if (!result) {\n const source = props.sources?.[name]\n result = source\n ? Promise.resolve()\n .then(() => source(arg))\n .then((list) => (Array.isArray(list) ? list : []))\n : Promise.reject(\n new Error(`[@dou-ui/form-builder] there is no source \"${name}\" in \\`sources\\``),\n )\n result.catch((error: unknown) => {\n if (loaded.get(key) === result) loaded.delete(key)\n console.warn(`[@dou-ui/form-builder] form option source \"${name}\" failed:`, error)\n })\n loaded.set(key, result)\n }\n return result\n}\nfunction reloadSource(name?: string) {\n if (name === undefined) {\n loaded.clear()\n everyVersion.value++\n return\n }\n for (const key of loaded.keys()) if (key.startsWith(`${name}\\u0000`)) loaded.delete(key)\n versions[name] = (versions[name] ?? 0) + 1\n}\n\nconst messages = useFormRendererMessages(() => props.messages)\nconst formRef = useTemplateRef<FormInstance>('formRef')\n\n// Tabs and steps holding a field bring it into view before it is scrolled to.\nconst revealers = new Set<(path: string) => boolean>()\nfunction scrollToField(name: string) {\n for (const reveal of revealers) reveal(name)\n void nextTick(() => formRef.value?.scrollToField(name))\n}\nasync function validate(names?: string | string[]) {\n return (await formRef.value?.validate(names)) ?? { valid: true, errors: {} }\n}\nfunction getValues(): FormModel {\n return props.keepHidden\n ? cloneValue(model.value)\n : visibleValues(props.form, model.value, { functions: props.functions })\n}\nfunction onInvalid(errors: Record<string, string>) {\n const first = Object.keys(errors)[0]\n if (first) scrollToField(first)\n emit('invalid', errors)\n}\n\n// The form items showing an error, for the tabs to mark their pages.\ntype Tracked = { path: () => string | undefined; error: () => string | undefined }\nconst tracked = shallowReactive(new Set<Tracked>())\nconst errorPaths = computed(() => {\n const paths: string[] = []\n for (const entry of tracked) {\n const path = entry.path()\n if (path && entry.error()) paths.push(path)\n }\n return paths\n})\n\n/** A form that is a series of steps shows the default slot on its last step. */\nconst footerOwner = computed(() =>\n props.readonly ? undefined : props.form.nodes.find(isSteps)?.id,\n)\n\nprovide(rendererKey, {\n model: () => model.value,\n fields: () => fields.value,\n labelWidth: () => labelWidth.value,\n changed: (name, value) => emit('change', name, value),\n functions: () => props.functions,\n validators: () => props.validators,\n readonly: () => props.readonly,\n blockedFormulas: () => analysis.value.blocked,\n settled: () => settled.value,\n controlsReadonly,\n load,\n sourceVersion: (name) => everyVersion.value + (versions[name] ?? 0),\n messages: () => messages.value,\n validate,\n scrollToField,\n addRevealer: (reveal) => {\n revealers.add(reveal)\n return () => revealers.delete(reveal)\n },\n trackError: (path, error) => {\n const entry = { path, error }\n tracked.add(entry)\n return () => tracked.delete(entry)\n },\n errorPaths: () => errorPaths.value,\n footer: () => slots.default?.(),\n footerOwner: () => footerOwner.value,\n resets: () => resets.value,\n})\n\n// Resetting the whole form, by `resetFields()` or a reset button, also\n// takes steps back to the first one.\nconst resets = shallowRef(0)\nfunction resetFields(names?: string | string[]) {\n formRef.value?.resetFields(names)\n if (names === undefined) resets.value++\n}\nfunction onReset() {\n resets.value++\n emit('reset')\n}\n\n/** Shows errors through the form, warning in the renderer's own name about the ones it cannot show. */\nfunction setErrors(errors: Partial<FormErrors>) {\n const form = formRef.value\n const missed = form\n ? showFormErrors(form, errors)\n : Object.keys(errors).filter((path) => errors[path])\n if (missed.length && process.env.NODE_ENV !== 'production')\n console.warn(\n `[@dou-ui/form-builder] DouFormRenderer.setErrors: no field shows ${missed.map((path) => `\"${path}\"`).join(', ')}. Show these errors elsewhere, e.g. above the form.`,\n )\n return missed\n}\n\ndefineExpose<FormRendererExpose>({\n get model() {\n return model.value\n },\n validate,\n resetFields,\n clearValidate: (names) => formRef.value?.clearValidate(names),\n setErrors,\n scrollToField,\n reloadSource,\n getValues,\n})\n</script>\n\n<template>\n <DouForm\n ref=\"formRef\"\n :class=\"[\n 'dou-form-renderer',\n `dou-form-renderer--${layout}`,\n (form.requiredMark ?? true) === true && !readonly && 'dou-form-renderer--marked',\n readonly && 'dou-form-renderer--readonly',\n ]\"\n :model=\"model\"\n :rules=\"rules\"\n :layout=\"layout\"\n :label-align=\"form.labelAlign\"\n :size=\"form.size\"\n :required-mark=\"readonly ? false : (form.requiredMark ?? true)\"\n :disabled=\"disabled\"\n :readonly=\"controlsReadonly()\"\n :scroll-to-error=\"false\"\n @submit=\"emit('submit', getValues())\"\n @invalid=\"onInvalid\"\n @reset=\"onReset\"\n >\n <div\n :key=\"readonly ? 'readonly' : 'edit'\"\n class=\"dou-form-renderer__grid\"\n :style=\"{ '--dou-form-columns': columns }\"\n >\n <FormNodeView\n v-for=\"node in form.nodes\"\n :key=\"nodeKey(node)\"\n :node=\"node\"\n :columns=\"columns\"\n />\n </div>\n <slot v-if=\"!footerOwner\" />\n </DouForm>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { checkVersion, cloneValue, getPath, isSteps, setPath } from "../core/document.js";
|
|
2
|
+
import { equalValues } from "../core/expression.js";
|
|
3
|
+
import { fillValues, visibleValues } from "../core/values.js";
|
|
4
|
+
import { resolveFields } from "../fields/builtin.js";
|
|
5
|
+
import { useFormRendererMessages } from "../locale.js";
|
|
6
|
+
import { controlsReadonlyKey, rendererKey } from "./context.js";
|
|
7
|
+
import { analyseFormulas, settleFormulas } from "../core/formulas.js";
|
|
8
|
+
import { FormNodeView, nodeKey } from "./node-view.js";
|
|
9
|
+
import { DouForm, showFormErrors } from "dou-ui";
|
|
10
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, defineComponent, effectScope, inject, nextTick, normalizeClass, normalizeStyle, openBlock, provide, reactive, renderList, renderSlot, shallowReactive, shallowRef, unref, useSlots, useTemplateRef, watch, withCtx } from "vue";
|
|
11
|
+
// src/renderer/form-renderer.vue?vue&type=script&setup=true&lang.ts
|
|
12
|
+
var form_renderer_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
13
|
+
name: "DouFormRenderer",
|
|
14
|
+
__name: "form-renderer",
|
|
15
|
+
props: {
|
|
16
|
+
form: {},
|
|
17
|
+
model: {},
|
|
18
|
+
fields: {},
|
|
19
|
+
rules: {},
|
|
20
|
+
disabled: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
default: false
|
|
23
|
+
},
|
|
24
|
+
readonly: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: false
|
|
27
|
+
},
|
|
28
|
+
functions: {},
|
|
29
|
+
sources: {},
|
|
30
|
+
validators: {},
|
|
31
|
+
keepHidden: { type: Boolean },
|
|
32
|
+
messages: {}
|
|
33
|
+
},
|
|
34
|
+
emits: [
|
|
35
|
+
"change",
|
|
36
|
+
"submit",
|
|
37
|
+
"invalid",
|
|
38
|
+
"reset"
|
|
39
|
+
],
|
|
40
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
41
|
+
const props = __props;
|
|
42
|
+
const emit = __emit;
|
|
43
|
+
const slots = useSlots();
|
|
44
|
+
const controlsReadonly = inject(controlsReadonlyKey, () => false);
|
|
45
|
+
const own = reactive({});
|
|
46
|
+
const model = computed(() => reactive(props.model ?? own));
|
|
47
|
+
const fields = computed(() => resolveFields(props.fields));
|
|
48
|
+
const layout = computed(() => props.form.layout ?? "vertical");
|
|
49
|
+
const columns = computed(() => Math.max(1, Math.round(props.form.columns ?? 12)));
|
|
50
|
+
const labelWidth = computed(() => layout.value === "horizontal" ? props.form.labelWidth ?? 96 : void 0);
|
|
51
|
+
const warnedCycles = /* @__PURE__ */ new Set();
|
|
52
|
+
function warnCycle(cycle) {
|
|
53
|
+
const key = cycle.join(" -> ");
|
|
54
|
+
if (warnedCycles.has(key)) return;
|
|
55
|
+
warnedCycles.add(key);
|
|
56
|
+
console.warn(`[@dou-ui/form-builder] form formulas read each other in a circle (${key}); they are not worked out`);
|
|
57
|
+
}
|
|
58
|
+
watch([
|
|
59
|
+
() => props.form,
|
|
60
|
+
model,
|
|
61
|
+
fields,
|
|
62
|
+
() => props.readonly
|
|
63
|
+
], ([document, target, types, readonly]) => {
|
|
64
|
+
checkVersion(document);
|
|
65
|
+
if (!readonly) fillValues(document.nodes, target, types);
|
|
66
|
+
}, { immediate: true });
|
|
67
|
+
const analysis = computed(() => {
|
|
68
|
+
const result = analyseFormulas(props.form, props.functions);
|
|
69
|
+
for (const cycle of result.cycles) warnCycle(cycle.map(({ label }) => label));
|
|
70
|
+
return result;
|
|
71
|
+
});
|
|
72
|
+
watch([
|
|
73
|
+
analysis,
|
|
74
|
+
model,
|
|
75
|
+
() => props.functions,
|
|
76
|
+
() => props.readonly
|
|
77
|
+
], ([formulas, target, functions, readonly], _, onCleanup) => {
|
|
78
|
+
if (readonly) return;
|
|
79
|
+
settleFormulas(props.form, target, functions, formulas);
|
|
80
|
+
const scope = effectScope();
|
|
81
|
+
scope.run(() => {
|
|
82
|
+
for (const { name, expression } of formulas.formulas) watch(() => expression.evaluate({
|
|
83
|
+
values: target,
|
|
84
|
+
value: null,
|
|
85
|
+
functions
|
|
86
|
+
}), (result) => {
|
|
87
|
+
if (!equalValues(result, getPath(target, name))) setPath(target, name, cloneValue(result));
|
|
88
|
+
}, {
|
|
89
|
+
immediate: true,
|
|
90
|
+
flush: "sync"
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
onCleanup(() => scope.stop());
|
|
94
|
+
}, { immediate: true });
|
|
95
|
+
const settled = computed(() => {
|
|
96
|
+
if (!props.readonly) return model.value;
|
|
97
|
+
const copy = cloneValue(model.value);
|
|
98
|
+
settleFormulas(props.form, copy, props.functions, analysis.value);
|
|
99
|
+
return copy;
|
|
100
|
+
});
|
|
101
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
102
|
+
const versions = reactive({});
|
|
103
|
+
const everyVersion = shallowRef(0);
|
|
104
|
+
watch(() => props.sources, () => loaded.clear());
|
|
105
|
+
function load(name, arg) {
|
|
106
|
+
const key = `${name}\u0000${JSON.stringify(arg)}`;
|
|
107
|
+
let result = loaded.get(key);
|
|
108
|
+
if (!result) {
|
|
109
|
+
const source = props.sources?.[name];
|
|
110
|
+
result = source ? Promise.resolve().then(() => source(arg)).then((list) => Array.isArray(list) ? list : []) : Promise.reject(/* @__PURE__ */ new Error(`[@dou-ui/form-builder] there is no source "${name}" in \`sources\``));
|
|
111
|
+
result.catch((error) => {
|
|
112
|
+
if (loaded.get(key) === result) loaded.delete(key);
|
|
113
|
+
console.warn(`[@dou-ui/form-builder] form option source "${name}" failed:`, error);
|
|
114
|
+
});
|
|
115
|
+
loaded.set(key, result);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function reloadSource(name) {
|
|
120
|
+
if (name === void 0) {
|
|
121
|
+
loaded.clear();
|
|
122
|
+
everyVersion.value++;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const key of loaded.keys()) if (key.startsWith(`${name}\u0000`)) loaded.delete(key);
|
|
126
|
+
versions[name] = (versions[name] ?? 0) + 1;
|
|
127
|
+
}
|
|
128
|
+
const messages = useFormRendererMessages(() => props.messages);
|
|
129
|
+
const formRef = useTemplateRef("formRef");
|
|
130
|
+
const revealers = /* @__PURE__ */ new Set();
|
|
131
|
+
function scrollToField(name) {
|
|
132
|
+
for (const reveal of revealers) reveal(name);
|
|
133
|
+
nextTick(() => formRef.value?.scrollToField(name));
|
|
134
|
+
}
|
|
135
|
+
async function validate(names) {
|
|
136
|
+
return await formRef.value?.validate(names) ?? {
|
|
137
|
+
valid: true,
|
|
138
|
+
errors: {}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function getValues() {
|
|
142
|
+
return props.keepHidden ? cloneValue(model.value) : visibleValues(props.form, model.value, { functions: props.functions });
|
|
143
|
+
}
|
|
144
|
+
function onInvalid(errors) {
|
|
145
|
+
const first = Object.keys(errors)[0];
|
|
146
|
+
if (first) scrollToField(first);
|
|
147
|
+
emit("invalid", errors);
|
|
148
|
+
}
|
|
149
|
+
const tracked = shallowReactive(/* @__PURE__ */ new Set());
|
|
150
|
+
const errorPaths = computed(() => {
|
|
151
|
+
const paths = [];
|
|
152
|
+
for (const entry of tracked) {
|
|
153
|
+
const path = entry.path();
|
|
154
|
+
if (path && entry.error()) paths.push(path);
|
|
155
|
+
}
|
|
156
|
+
return paths;
|
|
157
|
+
});
|
|
158
|
+
/** A form that is a series of steps shows the default slot on its last step. */
|
|
159
|
+
const footerOwner = computed(() => props.readonly ? void 0 : props.form.nodes.find(isSteps)?.id);
|
|
160
|
+
provide(rendererKey, {
|
|
161
|
+
model: () => model.value,
|
|
162
|
+
fields: () => fields.value,
|
|
163
|
+
labelWidth: () => labelWidth.value,
|
|
164
|
+
changed: (name, value) => emit("change", name, value),
|
|
165
|
+
functions: () => props.functions,
|
|
166
|
+
validators: () => props.validators,
|
|
167
|
+
readonly: () => props.readonly,
|
|
168
|
+
blockedFormulas: () => analysis.value.blocked,
|
|
169
|
+
settled: () => settled.value,
|
|
170
|
+
controlsReadonly,
|
|
171
|
+
load,
|
|
172
|
+
sourceVersion: (name) => everyVersion.value + (versions[name] ?? 0),
|
|
173
|
+
messages: () => messages.value,
|
|
174
|
+
validate,
|
|
175
|
+
scrollToField,
|
|
176
|
+
addRevealer: (reveal) => {
|
|
177
|
+
revealers.add(reveal);
|
|
178
|
+
return () => revealers.delete(reveal);
|
|
179
|
+
},
|
|
180
|
+
trackError: (path, error) => {
|
|
181
|
+
const entry = {
|
|
182
|
+
path,
|
|
183
|
+
error
|
|
184
|
+
};
|
|
185
|
+
tracked.add(entry);
|
|
186
|
+
return () => tracked.delete(entry);
|
|
187
|
+
},
|
|
188
|
+
errorPaths: () => errorPaths.value,
|
|
189
|
+
footer: () => slots.default?.(),
|
|
190
|
+
footerOwner: () => footerOwner.value,
|
|
191
|
+
resets: () => resets.value
|
|
192
|
+
});
|
|
193
|
+
const resets = shallowRef(0);
|
|
194
|
+
function resetFields(names) {
|
|
195
|
+
formRef.value?.resetFields(names);
|
|
196
|
+
if (names === void 0) resets.value++;
|
|
197
|
+
}
|
|
198
|
+
function onReset() {
|
|
199
|
+
resets.value++;
|
|
200
|
+
emit("reset");
|
|
201
|
+
}
|
|
202
|
+
/** Shows errors through the form, warning in the renderer's own name about the ones it cannot show. */
|
|
203
|
+
function setErrors(errors) {
|
|
204
|
+
const form = formRef.value;
|
|
205
|
+
const missed = form ? showFormErrors(form, errors) : Object.keys(errors).filter((path) => errors[path]);
|
|
206
|
+
if (missed.length && process.env.NODE_ENV !== "production") console.warn(`[@dou-ui/form-builder] DouFormRenderer.setErrors: no field shows ${missed.map((path) => `"${path}"`).join(", ")}. Show these errors elsewhere, e.g. above the form.`);
|
|
207
|
+
return missed;
|
|
208
|
+
}
|
|
209
|
+
__expose({
|
|
210
|
+
get model() {
|
|
211
|
+
return model.value;
|
|
212
|
+
},
|
|
213
|
+
validate,
|
|
214
|
+
resetFields,
|
|
215
|
+
clearValidate: (names) => formRef.value?.clearValidate(names),
|
|
216
|
+
setErrors,
|
|
217
|
+
scrollToField,
|
|
218
|
+
reloadSource,
|
|
219
|
+
getValues
|
|
220
|
+
});
|
|
221
|
+
return (_ctx, _cache) => {
|
|
222
|
+
return openBlock(), createBlock(unref(DouForm), {
|
|
223
|
+
ref_key: "formRef",
|
|
224
|
+
ref: formRef,
|
|
225
|
+
class: normalizeClass([
|
|
226
|
+
"dou-form-renderer",
|
|
227
|
+
`dou-form-renderer--${layout.value}`,
|
|
228
|
+
(__props.form.requiredMark ?? true) === true && !__props.readonly && "dou-form-renderer--marked",
|
|
229
|
+
__props.readonly && "dou-form-renderer--readonly"
|
|
230
|
+
]),
|
|
231
|
+
model: model.value,
|
|
232
|
+
rules: __props.rules,
|
|
233
|
+
layout: layout.value,
|
|
234
|
+
"label-align": __props.form.labelAlign,
|
|
235
|
+
size: __props.form.size,
|
|
236
|
+
"required-mark": __props.readonly ? false : __props.form.requiredMark ?? true,
|
|
237
|
+
disabled: __props.disabled,
|
|
238
|
+
readonly: unref(controlsReadonly)(),
|
|
239
|
+
"scroll-to-error": false,
|
|
240
|
+
onSubmit: _cache[0] || (_cache[0] = ($event) => emit("submit", getValues())),
|
|
241
|
+
onInvalid,
|
|
242
|
+
onReset
|
|
243
|
+
}, {
|
|
244
|
+
default: withCtx(() => [(openBlock(), createElementBlock("div", {
|
|
245
|
+
key: __props.readonly ? "readonly" : "edit",
|
|
246
|
+
class: "dou-form-renderer__grid",
|
|
247
|
+
style: normalizeStyle({ "--dou-form-columns": columns.value })
|
|
248
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.form.nodes, (node) => {
|
|
249
|
+
return openBlock(), createBlock(unref(FormNodeView), {
|
|
250
|
+
key: unref(nodeKey)(node),
|
|
251
|
+
node,
|
|
252
|
+
columns: columns.value
|
|
253
|
+
}, null, 8, ["node", "columns"]);
|
|
254
|
+
}), 128))], 4)), !footerOwner.value ? renderSlot(_ctx.$slots, "default", {}, void 0, void 0, 0) : createCommentVNode("", true)]),
|
|
255
|
+
_: 3
|
|
256
|
+
}, 8, [
|
|
257
|
+
"class",
|
|
258
|
+
"model",
|
|
259
|
+
"rules",
|
|
260
|
+
"layout",
|
|
261
|
+
"label-align",
|
|
262
|
+
"size",
|
|
263
|
+
"required-mark",
|
|
264
|
+
"disabled",
|
|
265
|
+
"readonly"
|
|
266
|
+
]);
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
export { form_renderer_vue_vue_type_script_setup_true_lang_default as default };
|
|
271
|
+
|
|
272
|
+
//# sourceMappingURL=form-renderer.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-renderer.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementBlock","_normalizeStyle","_Fragment","_renderList","_renderSlot","_createCommentVNode"],"sources":["../../src/renderer/form-renderer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n computed,\n effectScope,\n inject,\n nextTick,\n provide,\n reactive,\n shallowReactive,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\nimport { DouForm, showFormErrors } from 'dou-ui'\nimport type { FormErrors, FormInstance, FormModel } from 'dou-ui'\nimport { checkVersion, cloneValue, getPath, isSteps, setPath } from '../core/document'\nimport { equalValues } from '../core/expression'\nimport { fillValues, visibleValues } from '../core/values'\nimport { resolveFields } from '../fields/builtin'\nimport { useFormRendererMessages } from '../locale'\nimport { controlsReadonlyKey, rendererKey } from './context'\nimport type {\n FormRendererEmits,\n FormRendererExpose,\n FormRendererProps,\n FormRendererSlots,\n} from './context'\nimport { analyseFormulas, settleFormulas } from '../core/formulas'\nimport { FormNodeView, nodeKey } from './node-view'\n\ndefineOptions({ name: 'DouFormRenderer' })\n\nconst props = withDefaults(defineProps<FormRendererProps>(), { disabled: false, readonly: false })\nconst emit = defineEmits<FormRendererEmits>()\nconst slots = defineSlots<FormRendererSlots>()\n\nconst controlsReadonly = inject(controlsReadonlyKey, () => false)\n\nconst own = reactive<FormModel>({})\n// `reactive` hands back the same proxy for an object it has seen, and\n// writes through to the object itself.\nconst model = computed(() => reactive(props.model ?? own) as FormModel)\nconst fields = computed(() => resolveFields(props.fields))\nconst layout = computed(() => props.form.layout ?? 'vertical')\nconst columns = computed(() => Math.max(1, Math.round(props.form.columns ?? 12)))\nconst labelWidth = computed(() =>\n layout.value === 'horizontal' ? (props.form.labelWidth ?? 96) : undefined,\n)\n\nconst warnedCycles = new Set<string>()\nfunction warnCycle(cycle: string[]) {\n const key = cycle.join(' -> ')\n if (warnedCycles.has(key)) return\n warnedCycles.add(key)\n console.warn(\n `[@dou-ui/form-builder] form formulas read each other in a circle (${key}); they are not worked out`,\n )\n}\n\n// Fill in the fields the model has no value for, before the form items\n// mount and take the values as the ones to reset to. A read-only form\n// shows the values as they are, so a field left empty does not show a default.\nwatch(\n [() => props.form, model, fields, () => props.readonly],\n ([document, target, types, readonly]) => {\n checkVersion(document)\n if (!readonly) fillValues(document.nodes, target, types)\n },\n {\n immediate: true,\n },\n)\n\n// Formulas: each field with one holds its result, worked out again as\n// soon as a value it reads changes (synchronously, so a formula reading\n// another formula sees its new result). Formulas on a circle are skipped.\n// The document's formulas and the items' make one set, so a circle through\n// both is found too; each one on it is skipped, here and in the lists.\nconst analysis = computed(() => {\n const result = analyseFormulas(props.form, props.functions)\n for (const cycle of result.cycles) warnCycle(cycle.map(({ label }) => label))\n return result\n})\nwatch(\n [analysis, model, () => props.functions, () => props.readonly],\n ([formulas, target, functions, readonly], _, onCleanup) => {\n // A read-only form works formulas out as it shows them, and writes nothing.\n if (readonly) return\n // Everything at once first, so the server renders the results the page\n // will have: watchers there run only once, before the lists fill in.\n settleFormulas(props.form, target, functions, formulas)\n const scope = effectScope()\n scope.run(() => {\n for (const { name, expression } of formulas.formulas)\n watch(\n () => expression.evaluate({ values: target, value: null, functions }),\n (result) => {\n if (!equalValues(result, getPath(target, name)))\n setPath(target, name, cloneValue(result))\n },\n { immediate: true, flush: 'sync' },\n )\n })\n onCleanup(() => scope.stop())\n },\n { immediate: true },\n)\n\n// A read-only form shows formulas worked out on a copy of the values, so a\n// total reads the items' results even when the model has none of them.\nconst settled = computed(() => {\n if (!props.readonly) return model.value\n const copy = cloneValue(model.value)\n settleFormulas(props.form, copy, props.functions, analysis.value)\n return copy\n})\n\n// Options from sources, kept per source and `arg` for the renderer's\n// life; a failed call is forgotten so the next one tries again.\nconst loaded = new Map<string, Promise<unknown[]>>()\nconst versions = reactive<Record<string, number>>({})\nconst everyVersion = shallowRef(0)\nwatch(\n () => props.sources,\n () => loaded.clear(),\n)\nfunction load(name: string, arg: unknown) {\n const key = `${name}\\u0000${JSON.stringify(arg)}`\n let result = loaded.get(key)\n if (!result) {\n const source = props.sources?.[name]\n result = source\n ? Promise.resolve()\n .then(() => source(arg))\n .then((list) => (Array.isArray(list) ? list : []))\n : Promise.reject(\n new Error(`[@dou-ui/form-builder] there is no source \"${name}\" in \\`sources\\``),\n )\n result.catch((error: unknown) => {\n if (loaded.get(key) === result) loaded.delete(key)\n console.warn(`[@dou-ui/form-builder] form option source \"${name}\" failed:`, error)\n })\n loaded.set(key, result)\n }\n return result\n}\nfunction reloadSource(name?: string) {\n if (name === undefined) {\n loaded.clear()\n everyVersion.value++\n return\n }\n for (const key of loaded.keys()) if (key.startsWith(`${name}\\u0000`)) loaded.delete(key)\n versions[name] = (versions[name] ?? 0) + 1\n}\n\nconst messages = useFormRendererMessages(() => props.messages)\nconst formRef = useTemplateRef<FormInstance>('formRef')\n\n// Tabs and steps holding a field bring it into view before it is scrolled to.\nconst revealers = new Set<(path: string) => boolean>()\nfunction scrollToField(name: string) {\n for (const reveal of revealers) reveal(name)\n void nextTick(() => formRef.value?.scrollToField(name))\n}\nasync function validate(names?: string | string[]) {\n return (await formRef.value?.validate(names)) ?? { valid: true, errors: {} }\n}\nfunction getValues(): FormModel {\n return props.keepHidden\n ? cloneValue(model.value)\n : visibleValues(props.form, model.value, { functions: props.functions })\n}\nfunction onInvalid(errors: Record<string, string>) {\n const first = Object.keys(errors)[0]\n if (first) scrollToField(first)\n emit('invalid', errors)\n}\n\n// The form items showing an error, for the tabs to mark their pages.\ntype Tracked = { path: () => string | undefined; error: () => string | undefined }\nconst tracked = shallowReactive(new Set<Tracked>())\nconst errorPaths = computed(() => {\n const paths: string[] = []\n for (const entry of tracked) {\n const path = entry.path()\n if (path && entry.error()) paths.push(path)\n }\n return paths\n})\n\n/** A form that is a series of steps shows the default slot on its last step. */\nconst footerOwner = computed(() =>\n props.readonly ? undefined : props.form.nodes.find(isSteps)?.id,\n)\n\nprovide(rendererKey, {\n model: () => model.value,\n fields: () => fields.value,\n labelWidth: () => labelWidth.value,\n changed: (name, value) => emit('change', name, value),\n functions: () => props.functions,\n validators: () => props.validators,\n readonly: () => props.readonly,\n blockedFormulas: () => analysis.value.blocked,\n settled: () => settled.value,\n controlsReadonly,\n load,\n sourceVersion: (name) => everyVersion.value + (versions[name] ?? 0),\n messages: () => messages.value,\n validate,\n scrollToField,\n addRevealer: (reveal) => {\n revealers.add(reveal)\n return () => revealers.delete(reveal)\n },\n trackError: (path, error) => {\n const entry = { path, error }\n tracked.add(entry)\n return () => tracked.delete(entry)\n },\n errorPaths: () => errorPaths.value,\n footer: () => slots.default?.(),\n footerOwner: () => footerOwner.value,\n resets: () => resets.value,\n})\n\n// Resetting the whole form, by `resetFields()` or a reset button, also\n// takes steps back to the first one.\nconst resets = shallowRef(0)\nfunction resetFields(names?: string | string[]) {\n formRef.value?.resetFields(names)\n if (names === undefined) resets.value++\n}\nfunction onReset() {\n resets.value++\n emit('reset')\n}\n\n/** Shows errors through the form, warning in the renderer's own name about the ones it cannot show. */\nfunction setErrors(errors: Partial<FormErrors>) {\n const form = formRef.value\n const missed = form\n ? showFormErrors(form, errors)\n : Object.keys(errors).filter((path) => errors[path])\n if (missed.length && process.env.NODE_ENV !== 'production')\n console.warn(\n `[@dou-ui/form-builder] DouFormRenderer.setErrors: no field shows ${missed.map((path) => `\"${path}\"`).join(', ')}. Show these errors elsewhere, e.g. above the form.`,\n )\n return missed\n}\n\ndefineExpose<FormRendererExpose>({\n get model() {\n return model.value\n },\n validate,\n resetFields,\n clearValidate: (names) => formRef.value?.clearValidate(names),\n setErrors,\n scrollToField,\n reloadSource,\n getValues,\n})\n</script>\n\n<template>\n <DouForm\n ref=\"formRef\"\n :class=\"[\n 'dou-form-renderer',\n `dou-form-renderer--${layout}`,\n (form.requiredMark ?? true) === true && !readonly && 'dou-form-renderer--marked',\n readonly && 'dou-form-renderer--readonly',\n ]\"\n :model=\"model\"\n :rules=\"rules\"\n :layout=\"layout\"\n :label-align=\"form.labelAlign\"\n :size=\"form.size\"\n :required-mark=\"readonly ? false : (form.requiredMark ?? true)\"\n :disabled=\"disabled\"\n :readonly=\"controlsReadonly()\"\n :scroll-to-error=\"false\"\n @submit=\"emit('submit', getValues())\"\n @invalid=\"onInvalid\"\n @reset=\"onReset\"\n >\n <div\n :key=\"readonly ? 'readonly' : 'edit'\"\n class=\"dou-form-renderer__grid\"\n :style=\"{ '--dou-form-columns': columns }\"\n >\n <FormNodeView\n v-for=\"node in form.nodes\"\n :key=\"nodeKey(node)\"\n :node=\"node\"\n :columns=\"columns\"\n />\n </div>\n <slot v-if=\"!footerOwner\" />\n </DouForm>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cloneValue, createId, emptyDocument, fieldNodes, findNode, getPath, isContainer, isField, isList, isPage, isSection, isSteps, isTabs, setPath, unsetPath, valueNodes, walkNodes } from "../core/document.js";
|
|
2
|
+
import { ExpressionError, builtinFunctions, checkExpression, compileExpression, equalValues, evaluateExpression, isEmptyValue, parseExpression, truthy } from "../core/expression.js";
|
|
3
|
+
import { defineFormField, fieldProps, useFormField } from "../fields/define.js";
|
|
4
|
+
import { emptyItem, fillValues, startValue, visibleValues } from "../core/values.js";
|
|
5
|
+
import { fitPathToOptions, fitToOptions } from "../fields/options.js";
|
|
6
|
+
import { normalizeOptions } from "../fields/components.js";
|
|
7
|
+
import { formatValue } from "../fields/format.js";
|
|
8
|
+
import { builtinFields, resolveFields } from "../fields/builtin.js";
|
|
9
|
+
import { formRendererLocales, formatMessage, useFormRendererMessages } from "../locale.js";
|
|
10
|
+
import { findFormulaCycles } from "../core/formulas.js";
|
|
11
|
+
import { rulesOf, toFormRule } from "../core/rules.js";
|
|
12
|
+
import form_renderer_default from "./form-renderer.js";
|
|
13
|
+
import { checkDocument, checkDocumentShape, migrateDocument } from "../core/check.js";
|
|
14
|
+
import { withInstall } from "dou-ui";
|
|
15
|
+
// src/renderer/index.ts
|
|
16
|
+
var DouFormRenderer = withInstall(form_renderer_default);
|
|
17
|
+
export { DouFormRenderer, DouFormRenderer as default, ExpressionError, builtinFields, builtinFunctions, checkDocument, checkDocumentShape, checkExpression, cloneValue, compileExpression, createId, defineFormField, emptyDocument, emptyItem, equalValues, evaluateExpression, fieldNodes, fieldProps, fillValues, findFormulaCycles, findNode, fitPathToOptions, fitToOptions, formRendererLocales, formatMessage, formatValue, getPath, isContainer, isEmptyValue, isField, isList, isPage, isSection, isSteps, isTabs, migrateDocument, normalizeOptions, parseExpression, resolveFields, rulesOf, setPath, startValue, toFormRule, truthy, unsetPath, useFormField, useFormRendererMessages, valueNodes, visibleValues, walkNodes };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["FormRenderer"],"sources":["../../src/renderer/index.ts"],"sourcesContent":["import type { FormHTMLAttributes } from 'vue'\nimport { withInstall } from 'dou-ui'\nimport FormRenderer from './form-renderer.vue'\n\nexport const DouFormRenderer = withInstall<typeof FormRenderer, FormHTMLAttributes>(FormRenderer)\nexport default DouFormRenderer\n\nexport type {\n FormRendererEmits,\n FormRendererExpose,\n FormRendererProps,\n FormRendererSlots,\n FormOptionsLoader,\n} from './context'\nexport { findFormulaCycles } from '../core/formulas'\nexport { checkDocument, checkDocumentShape, migrateDocument } from '../core/check'\nexport type { CheckDocumentOptions, DocumentProblem, DocumentShapeProblem } from '../core/check'\nexport { emptyItem, fillValues, startValue, visibleValues } from '../core/values'\nexport type { VisibleValuesOptions } from '../core/values'\nexport { formatMessage, formRendererLocales, useFormRendererMessages } from '../locale'\nexport type { FormRendererMessages, FormRendererMessagesOverride } from '../locale'\n// The form's rule and result types, so props and methods are typed from one import.\nexport type { FormRules, FormValidateResult } from 'dou-ui'\nexport { builtinFields, resolveFields } from '../fields/builtin'\nexport { defineFormField, fieldProps, useFormField } from '../fields/define'\nexport type { FormFieldContext, FormFieldProps, FormFieldType } from '../fields/define'\nexport { normalizeOptions } from '../fields/components'\nexport { formatValue } from '../fields/format'\nexport type { FormFormatContext } from '../fields/format'\nexport { fitPathToOptions, fitToOptions } from '../fields/options'\nexport type { FormOption } from '../fields/components'\nexport {\n cloneValue,\n createId,\n emptyDocument,\n fieldNodes,\n findNode,\n getPath,\n isContainer,\n isField,\n isList,\n isPage,\n isSection,\n isSteps,\n isTabs,\n setPath,\n unsetPath,\n valueNodes,\n walkNodes,\n} from '../core/document'\nexport { rulesOf, toFormRule } from '../core/rules'\nexport type { RulesOptions } from '../core/rules'\nexport {\n builtinFunctions,\n checkExpression,\n compileExpression,\n equalValues,\n evaluateExpression,\n ExpressionError,\n isEmptyValue,\n parseExpression,\n truthy,\n} from '../core/expression'\nexport type {\n BinaryOperator,\n CompiledExpression,\n ExpressionErrorCode,\n ExpressionNode,\n ExpressionProblem,\n ExpressionScope,\n FormFunction,\n} from '../core/expression'\nexport type {\n FormBreakpoint,\n FormContainerNode,\n FormDocument,\n FormFieldNode,\n FormListNode,\n FormNode,\n FormOptionSource,\n FormPageNode,\n FormResponsive,\n FormRuleSpec,\n FormSectionNode,\n FormStepsNode,\n FormTabsNode,\n FormValidator,\n FormValidatorContext,\n} from '../core/types'\n\nexport type FormRendererInstance = InstanceType<typeof FormRenderer>\n\ndeclare module 'vue' {\n export interface GlobalComponents {\n DouFormRenderer: typeof DouFormRenderer\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAIA,IAAa,kBAAkB,YAAqDA,qBAAY"}
|