@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.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/builder/canvas.js +695 -0
  4. package/dist/builder/canvas.js.map +1 -0
  5. package/dist/builder/completion.js +49 -0
  6. package/dist/builder/completion.js.map +1 -0
  7. package/dist/builder/condition-editor.js +6 -0
  8. package/dist/builder/condition-editor.js.map +1 -0
  9. package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js +410 -0
  10. package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/builder/condition.js +159 -0
  12. package/dist/builder/condition.js.map +1 -0
  13. package/dist/builder/context.js +477 -0
  14. package/dist/builder/context.js.map +1 -0
  15. package/dist/builder/core/commands.js +373 -0
  16. package/dist/builder/core/commands.js.map +1 -0
  17. package/dist/builder/core/history.js +76 -0
  18. package/dist/builder/core/history.js.map +1 -0
  19. package/dist/builder/core/transaction.js +298 -0
  20. package/dist/builder/core/transaction.js.map +1 -0
  21. package/dist/builder/drag.js +466 -0
  22. package/dist/builder/drag.js.map +1 -0
  23. package/dist/builder/expression-box.js +19 -0
  24. package/dist/builder/expression-box.js.map +1 -0
  25. package/dist/builder/expression-input.js +6 -0
  26. package/dist/builder/expression-input.js.map +1 -0
  27. package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js +117 -0
  28. package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/builder/extension.js +47 -0
  30. package/dist/builder/extension.js.map +1 -0
  31. package/dist/builder/form-builder.js +6 -0
  32. package/dist/builder/form-builder.js.map +1 -0
  33. package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js +450 -0
  34. package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js.map +1 -0
  35. package/dist/builder/index.js +22 -0
  36. package/dist/builder/index.js.map +1 -0
  37. package/dist/builder/inspector.js +6 -0
  38. package/dist/builder/inspector.js.map +1 -0
  39. package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js +442 -0
  40. package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/builder/json-dialog.js +6 -0
  42. package/dist/builder/json-dialog.js.map +1 -0
  43. package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js +281 -0
  44. package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/builder/locale.js +1271 -0
  46. package/dist/builder/locale.js.map +1 -0
  47. package/dist/builder/logic-fields.js +18 -0
  48. package/dist/builder/logic-fields.js.map +1 -0
  49. package/dist/builder/logic.js +332 -0
  50. package/dist/builder/logic.js.map +1 -0
  51. package/dist/builder/menu.js +92 -0
  52. package/dist/builder/menu.js.map +1 -0
  53. package/dist/builder/outline-row.js +6 -0
  54. package/dist/builder/outline-row.js.map +1 -0
  55. package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js +88 -0
  56. package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js.map +1 -0
  57. package/dist/builder/outline.js +6 -0
  58. package/dist/builder/outline.js.map +1 -0
  59. package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js +111 -0
  60. package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  61. package/dist/builder/palette.js +6 -0
  62. package/dist/builder/palette.js.map +1 -0
  63. package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js +96 -0
  64. package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
  65. package/dist/builder/preview.js +6 -0
  66. package/dist/builder/preview.js.map +1 -0
  67. package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js +111 -0
  68. package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js.map +1 -0
  69. package/dist/builder/settings-binding.js +57 -0
  70. package/dist/builder/settings-binding.js.map +1 -0
  71. package/dist/builder/settings.js +487 -0
  72. package/dist/builder/settings.js.map +1 -0
  73. package/dist/builder/span-field.js +104 -0
  74. package/dist/builder/span-field.js.map +1 -0
  75. package/dist/code/expression-input.js +6 -0
  76. package/dist/code/expression-input.js.map +1 -0
  77. package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js +88 -0
  78. package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/code/extension.js +202 -0
  80. package/dist/code/extension.js.map +1 -0
  81. package/dist/code/index.js +19 -0
  82. package/dist/code/index.js.map +1 -0
  83. package/dist/code/language.js +43 -0
  84. package/dist/code/language.js.map +1 -0
  85. package/dist/collaboration/index.js +382 -0
  86. package/dist/collaboration/index.js.map +1 -0
  87. package/dist/collaboration/order.js +88 -0
  88. package/dist/collaboration/order.js.map +1 -0
  89. package/dist/core/check.js +314 -0
  90. package/dist/core/check.js.map +1 -0
  91. package/dist/core/document.js +112 -0
  92. package/dist/core/document.js.map +1 -0
  93. package/dist/core/expression.js +854 -0
  94. package/dist/core/expression.js.map +1 -0
  95. package/dist/core/formulas.js +242 -0
  96. package/dist/core/formulas.js.map +1 -0
  97. package/dist/core/rules.js +83 -0
  98. package/dist/core/rules.js.map +1 -0
  99. package/dist/core/values.js +80 -0
  100. package/dist/core/values.js.map +1 -0
  101. package/dist/fields/builtin.js +167 -0
  102. package/dist/fields/builtin.js.map +1 -0
  103. package/dist/fields/components.js +155 -0
  104. package/dist/fields/components.js.map +1 -0
  105. package/dist/fields/define.js +44 -0
  106. package/dist/fields/define.js.map +1 -0
  107. package/dist/fields/format.js +125 -0
  108. package/dist/fields/format.js.map +1 -0
  109. package/dist/fields/options.js +54 -0
  110. package/dist/fields/options.js.map +1 -0
  111. package/dist/index.js +20 -0
  112. package/dist/locale.js +161 -0
  113. package/dist/locale.js.map +1 -0
  114. package/dist/renderer/containers.js +430 -0
  115. package/dist/renderer/containers.js.map +1 -0
  116. package/dist/renderer/context.js +21 -0
  117. package/dist/renderer/context.js.map +1 -0
  118. package/dist/renderer/form-renderer.js +6 -0
  119. package/dist/renderer/form-renderer.js.map +1 -0
  120. package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js +272 -0
  121. package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  122. package/dist/renderer/index.js +19 -0
  123. package/dist/renderer/index.js.map +1 -0
  124. package/dist/renderer/node-view.js +385 -0
  125. package/dist/renderer/node-view.js.map +1 -0
  126. package/dist/style.css +1 -0
  127. package/dist/style.d.ts +1 -0
  128. package/dist/style.js +27 -0
  129. package/dist/types/builder/canvas.d.ts +5 -0
  130. package/dist/types/builder/completion.d.ts +35 -0
  131. package/dist/types/builder/condition-editor.vue.d.ts +17 -0
  132. package/dist/types/builder/condition.d.ts +37 -0
  133. package/dist/types/builder/context.d.ts +149 -0
  134. package/dist/types/builder/core/commands.d.ts +74 -0
  135. package/dist/types/builder/core/history.d.ts +23 -0
  136. package/dist/types/builder/core/transaction.d.ts +108 -0
  137. package/dist/types/builder/drag.d.ts +52 -0
  138. package/dist/types/builder/expression-box.d.ts +8 -0
  139. package/dist/types/builder/expression-input.vue.d.ts +29 -0
  140. package/dist/types/builder/extension.d.ts +149 -0
  141. package/dist/types/builder/form-builder.vue.d.ts +24 -0
  142. package/dist/types/builder/index.d.ts +76 -0
  143. package/dist/types/builder/inspector.vue.d.ts +3 -0
  144. package/dist/types/builder/json-dialog.vue.d.ts +10 -0
  145. package/dist/types/builder/locale.d.ts +195 -0
  146. package/dist/types/builder/logic-fields.d.ts +2 -0
  147. package/dist/types/builder/logic.d.ts +57 -0
  148. package/dist/types/builder/menu.d.ts +23 -0
  149. package/dist/types/builder/outline-row.vue.d.ts +19 -0
  150. package/dist/types/builder/outline.vue.d.ts +3 -0
  151. package/dist/types/builder/palette.vue.d.ts +3 -0
  152. package/dist/types/builder/preview.vue.d.ts +3 -0
  153. package/dist/types/builder/settings-binding.d.ts +23 -0
  154. package/dist/types/builder/settings.d.ts +49 -0
  155. package/dist/types/builder/span-field.d.ts +4 -0
  156. package/dist/types/builder/types.d.ts +59 -0
  157. package/dist/types/code/expression-input.vue.d.ts +23 -0
  158. package/dist/types/code/extension.d.ts +45 -0
  159. package/dist/types/code/index.d.ts +12 -0
  160. package/dist/types/code/language.d.ts +6 -0
  161. package/dist/types/collaboration/index.d.ts +64 -0
  162. package/dist/types/collaboration/order.d.ts +20 -0
  163. package/dist/types/core/check.d.ts +68 -0
  164. package/dist/types/core/document.d.ts +36 -0
  165. package/dist/types/core/expression.d.ts +175 -0
  166. package/dist/types/core/formulas.d.ts +44 -0
  167. package/dist/types/core/rules.d.ts +23 -0
  168. package/dist/types/core/types.d.ts +211 -0
  169. package/dist/types/core/values.d.ts +22 -0
  170. package/dist/types/fields/builtin.d.ts +9 -0
  171. package/dist/types/fields/components.d.ts +177 -0
  172. package/dist/types/fields/define.d.ts +93 -0
  173. package/dist/types/fields/format.d.ts +76 -0
  174. package/dist/types/fields/options.d.ts +11 -0
  175. package/dist/types/index.d.ts +3 -0
  176. package/dist/types/locale.d.ts +45 -0
  177. package/dist/types/renderer/containers.d.ts +20 -0
  178. package/dist/types/renderer/context.d.ts +186 -0
  179. package/dist/types/renderer/form-renderer.vue.d.ts +25 -0
  180. package/dist/types/renderer/index.d.ts +81 -0
  181. package/dist/types/renderer/node-view.d.ts +56 -0
  182. package/package.json +88 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.js","names":[],"sources":["../../src/builder/canvas.ts"],"sourcesContent":["import {\n computed,\n defineComponent,\n h,\n inject,\n onBeforeUnmount,\n onMounted,\n provide,\n reactive,\n shallowRef,\n watch,\n} from 'vue'\nimport type { InjectionKey, PropType, Ref, VNode } from 'vue'\nimport { DouButton, DouContextMenu, DouDropdown, DouForm, DouTooltip, holdPointer } from 'dou-ui'\nimport type { ContextMenuExpose, DropdownExpose } from 'dou-ui'\nimport {\n ArrowDownIcon,\n ArrowUpIcon,\n CopyIcon,\n CornerLeftUpIcon,\n MoreHorizontalIcon,\n PlusIcon,\n SigmaIcon,\n TrashIcon,\n WorkflowIcon,\n} from '@dou-ui/icons'\nimport { getPath, isContainer, isList, isPage, setPath } from '../core/document'\nimport type {\n FormContainerNode,\n FormFieldNode,\n FormListNode,\n FormNode,\n FormPageNode,\n FormResponsive,\n FormSectionNode,\n} from '../core/types'\nimport { startValue } from '../core/values'\nimport type { FormFieldType } from '../fields/define'\nimport { formatMessage, useFormRendererMessages } from '../locale'\nimport type { FormRendererMessages } from '../locale'\nimport { rendererKey, rootScope, scopeKey } from '../renderer/context'\nimport { menuItems, nodeMenu } from './menu'\nimport type { FormBuilderNodeMark } from './extension'\nimport type { NodeScope, RendererContext } from '../renderer/context'\nimport { FormNodeView, nodeKey, spanStyle } from '../renderer/node-view'\nimport { canvasOrder, useBuilderState } from './context'\nimport type { BuilderState, BuilderView } from './context'\nimport { formNodePlaces } from './core/transaction'\nimport { nodeTitle } from './settings'\nimport { createDragController } from './drag'\nimport type { DragLayer } from './drag'\n\n/**\n * The key a node's value has on the canvas: made from its id, not its\n * name, so a name being typed, left empty or used twice does not bind\n * the field to another field's value.\n */\nconst previewKey = (node: FormNode) => `n_${node.id.replace(/\\W/g, '_')}`\n\n/**\n * A field as the canvas draws it: always shown, enabled and editable in\n * look, with no options to load, and its value under `previewKey`. Kept\n * per node, so an untouched field is not drawn again.\n */\nconst designed = new WeakMap<FormFieldNode, FormFieldNode>()\nfunction designNode(node: FormFieldNode): FormFieldNode {\n let result = designed.get(node)\n if (!result) {\n const rest: FormFieldNode = { ...node }\n for (const key of ['visibleWhen', 'disabledWhen', 'formula', 'source', 'requiredWhen'] as const)\n delete rest[key]\n if ('name' in node) rest.name = previewKey(node)\n result = node.requiredWhen ? { ...rest, required: true } : rest\n designed.set(node, result)\n }\n return result\n}\n\n/**\n * The values the canvas shows: each field's default, and one item in\n * every list. Only fields whose node changed are written again.\n */\nfunction useScratch(state: BuilderState) {\n const model = reactive<Record<string, unknown>>({})\n const written = new WeakMap<FormNode, string>()\n function sync(\n nodes: readonly FormNode[],\n prefix: string,\n types: ReadonlyMap<string, FormFieldType>,\n ) {\n for (const node of nodes) {\n if (isList(node)) {\n const path = prefix + previewKey(node)\n const items = getPath(model, path)\n if (!Array.isArray(items) || !items.length || typeof items[0] !== 'object')\n setPath(model, path, [{}])\n sync(node.children, `${path}.0.`, types)\n } else if (isContainer(node)) sync(node.children, prefix, types)\n else if ('name' in node) {\n const path = prefix + previewKey(node)\n if (written.get(node) === path) continue\n const type = types.get(node.type)\n if (!type || type.kind === 'display') continue\n written.set(node, path)\n setPath(model, path, startValue(type, node))\n }\n }\n }\n watch(\n () => [state.document, state.fields] as const,\n ([document, types]) => sync(document.nodes, '', types),\n { immediate: true },\n )\n return model\n}\n\nconst noop = () => {}\n\nconst canvasView = (state: BuilderState): BuilderView => ({\n name: 'canvas',\n order: () => canvasOrder(state),\n})\n\n/** How many fields the canvas draws right away when it opens. */\nconst EAGER_FIELDS = 16\n\ninterface CanvasLazy {\n /** Whether a field being set up is drawn right away. */\n eager: () => boolean\n /** Call `show` once the element comes near the visible part of the canvas; returns a stop. */\n observe: (el: Element, show: () => void) => (() => void) | void\n /** The last selection came from a press, so the node is in view already. */\n pressed: boolean\n}\nconst lazyKey: InjectionKey<CanvasLazy> = Symbol('DouFormBuilderCanvasLazy')\n\n/** The canvas: the form as the renderer draws it, each node selectable. */\nexport const FormBuilderCanvas = defineComponent({\n name: 'DouFormBuilderCanvas',\n setup() {\n const state = useBuilderState()\n const scratch = useScratch(state)\n const rendererMessages = useFormRendererMessages()\n const document = computed(() => state.document)\n const layout = computed(() => document.value.layout ?? 'vertical')\n const columns = computed(() => Math.max(1, Math.round(document.value.columns ?? 12)))\n\n const renderer: RendererContext = {\n model: () => scratch,\n fields: () => state.fields as Map<string, FormFieldType>,\n labelWidth: () =>\n layout.value === 'horizontal' ? (document.value.labelWidth ?? 96) : undefined,\n changed: noop,\n functions: () => undefined,\n validators: () => undefined,\n readonly: () => false,\n blockedFormulas: () => new Set(),\n settled: () => scratch,\n controlsReadonly: () => false,\n load: () => Promise.resolve([]),\n sourceVersion: () => 0,\n messages: () => rendererMessages.value,\n validate: async () => ({ valid: true, errors: {} }),\n scrollToField: noop,\n addRevealer: () => noop,\n trackError: () => noop,\n errorPaths: () => [],\n footer: () => null,\n footerOwner: () => undefined,\n resets: () => 0,\n }\n provide(rendererKey, renderer)\n provide(scopeKey, rootScope)\n\n const view = canvasView(state)\n\n // Fields off screen are drawn once they come near it; the first ones\n // right away, so the canvas does not open on placeholders.\n const scroller = shallowRef<HTMLElement | null>(null)\n let budget = EAGER_FIELDS\n const waiting = new Map<Element, () => void>()\n let observer: IntersectionObserver | undefined\n const lazy: CanvasLazy = {\n eager: () => budget-- > 0,\n pressed: false,\n observe(el, show) {\n if (typeof IntersectionObserver === 'undefined') return show()\n observer ??= new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue\n observer!.unobserve(entry.target)\n waiting.get(entry.target)?.()\n waiting.delete(entry.target)\n }\n },\n { root: scroller.value, rootMargin: '800px 0px' },\n )\n waiting.set(el, show)\n observer.observe(el)\n return () => {\n observer?.unobserve(el)\n waiting.delete(el)\n }\n },\n }\n provide(lazyKey, lazy)\n onMounted(() => (budget = Infinity))\n onBeforeUnmount(() => observer?.disconnect())\n\n // Keep the selected node in view after the arrow keys; a press already sees it.\n watch(\n () => state.selection[state.selection.length - 1],\n (id) => {\n if (lazy.pressed) return void (lazy.pressed = false)\n if (!id) return\n requestAnimationFrame(() => {\n const el = globalThis.document?.getElementById(state.domId(id, 'canvas'))\n el?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n })\n },\n )\n\n // Drags: the line, the tinted container and the card under the pointer are\n // moved by the controller directly, without rendering the canvas.\n const layer: DragLayer = { indicator: null, holder: null, ghost: null }\n const dragger = createDragController(state, () => scroller.value, layer)\n state.dragger.value = dragger\n onBeforeUnmount(() => {\n dragger.stop()\n if (state.dragger.value === dragger) state.dragger.value = null\n })\n\n const active = computed(() => {\n const last = state.selection[state.selection.length - 1]\n return last ? state.domId(last, 'canvas') : undefined\n })\n\n // The menu of the selected nodes, opened by a right click or the menu key.\n const contextMenu = shallowRef<ContextMenuExpose | null>(null)\n const openMenu = (x: number, y: number) => {\n if (nodeMenu(state, state.selection).length) contextMenu.value?.open(x, y)\n }\n state.openMenu.value = openMenu\n onBeforeUnmount(() => {\n if (state.openMenu.value === openMenu) state.openMenu.value = null\n })\n function onTreeKeydown(event: KeyboardEvent) {\n if (event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10')) {\n event.preventDefault()\n const id = state.selection[state.selection.length - 1]\n const rect = id\n ? globalThis.document?.getElementById(state.domId(id, 'canvas'))?.getBoundingClientRect()\n : undefined\n if (rect) openMenu(rect.left + 16, rect.top + 16)\n return\n }\n state.keydown(event, view)\n }\n\n return () => {\n const doc = document.value\n return h(\n 'div',\n {\n ref: scroller,\n class: 'dou-form-builder__canvas',\n // A press beside the form selects the form itself.\n onClick: (event: MouseEvent) => {\n if (dragger.swallowClick()) return\n if (!(event.target as HTMLElement).closest('.dou-form-builder__node'))\n state.select(null)\n },\n },\n [\n h('div', { class: 'dou-form-builder__drop-layer', 'aria-hidden': true }, [\n h('div', {\n ref: (el) => (layer.holder = el as HTMLElement | null),\n class: 'dou-form-builder__drop-holder',\n hidden: true,\n }),\n h('div', {\n ref: (el) => (layer.indicator = el as HTMLElement | null),\n class: 'dou-form-builder__drop-line',\n hidden: true,\n }),\n h('div', {\n ref: (el) => (layer.ghost = el as HTMLElement | null),\n class: 'dou-form-builder__drag-ghost',\n hidden: true,\n }),\n ]),\n h(\n DouContextMenu,\n {\n ref: (instance: unknown) =>\n (contextMenu.value = instance as ContextMenuExpose | null),\n },\n {\n default: () => h('span', { class: 'dou-form-builder__menu-anchor' }),\n menu: () => menuItems(nodeMenu(state, state.selection)),\n },\n ),\n h('div', { class: 'dou-form-builder__paper' }, [\n h(\n DouForm,\n {\n class: [\n 'dou-form-renderer',\n `dou-form-renderer--${layout.value}`,\n (doc.requiredMark ?? true) === true && 'dou-form-renderer--marked',\n ],\n model: scratch,\n layout: layout.value,\n labelAlign: doc.labelAlign,\n size: doc.size,\n requiredMark: doc.requiredMark ?? true,\n scrollToError: false,\n onSubmit: noop,\n },\n () =>\n h(\n 'div',\n {\n ref: (el) => (state.canvas.value = el as HTMLElement | null),\n class: ['dou-form-renderer__grid', 'dou-form-builder__tree'],\n 'data-holder': '',\n style: { '--dou-form-columns': columns.value },\n role: 'tree',\n tabindex: 0,\n 'aria-label': state.messages.canvas.label,\n 'aria-multiselectable': true,\n 'aria-activedescendant': active.value,\n onKeydown: onTreeKeydown,\n },\n doc.nodes.length\n ? designNodes(doc.nodes, columns.value, 0, 1)\n : [emptyHint(state)],\n ),\n ),\n ]),\n ],\n )\n }\n },\n})\n\nfunction emptyHint(state: BuilderState) {\n const messages = state.messages.canvas\n return h(\n 'div',\n { class: 'dou-form-builder__empty' },\n state.readonly ? messages.readonlyEmpty : messages.empty,\n )\n}\n\nfunction designNodes(nodes: readonly FormNode[], columns: number, inset: number, level: number) {\n return nodes.map((node, index) =>\n h(DesignNode, {\n key: nodeKey(node),\n node,\n columns,\n inset,\n level,\n position: index + 1,\n size: nodes.length,\n }),\n )\n}\n\n/** A grid of child nodes, or a hint where there are none. */\nfunction childGrid(\n state: BuilderState,\n holder: string,\n nodes: readonly FormNode[],\n columns: number,\n inset: number,\n level: number,\n) {\n return h(\n 'div',\n {\n class: 'dou-form-renderer__grid',\n style: { '--dou-form-columns': columns },\n role: 'group',\n 'data-holder': holder,\n },\n nodes.length ? designNodes(nodes, columns, inset, level) : [emptyHint(state)],\n )\n}\n\nconst DesignNode = defineComponent({\n name: 'DouFormBuilderNode',\n props: {\n node: { type: Object as PropType<FormNode>, required: true },\n columns: { type: Number, required: true },\n inset: { type: Number, default: 0 },\n level: { type: Number, required: true },\n position: { type: Number, required: true },\n size: { type: Number, required: true },\n },\n setup(props) {\n const state = useBuilderState()\n const renderer = inject(rendererKey)!\n const lazy = inject(lazyKey)!\n const selected = computed(() => state.selected.value.has(props.node.id))\n const primary = computed(() => state.focused.value?.id === props.node.id)\n\n // Fields inside a list read and write its one item on the canvas.\n if (isList(props.node)) {\n const outer = inject(scopeKey, rootScope)\n const prefix = () => `${outer.prefix()}${previewKey(props.node)}.0.`\n const scope: NodeScope = {\n prefix,\n item: () => null,\n }\n provide(scopeKey, scope)\n }\n\n // A field off screen waits with a placeholder of about its height.\n const shown = shallowRef(isContainer(props.node) || lazy.eager())\n const el = shallowRef<HTMLElement | null>(null)\n let stop: (() => void) | void\n onMounted(() => {\n if (!shown.value && el.value) stop = lazy.observe(el.value, () => (shown.value = true))\n })\n onBeforeUnmount(() => stop?.())\n\n const moreOpen = shallowRef(false)\n\n function press(event: MouseEvent, id = props.node.id) {\n event.stopPropagation()\n if (state.dragger.value?.swallowClick()) return\n lazy.pressed = true\n state.pick(id, event, canvasView(state))\n state.canvas.value?.focus({ preventScroll: true })\n }\n\n return () => {\n const node = props.node\n const title = nodeTitle(node, state.fields as Map<string, FormFieldType>, state.messages)\n const logic = logicOf(state, node)\n const marks = state.extensions().marks.flatMap((marksOf) => marksOf(state, node))\n let body: VNode\n if (isList(node)) body = listBody(state, node, props, renderer.messages())\n else if (node.type === 'tabs' || node.type === 'steps')\n body = pagesBody(state, node as FormContainerNode, props, press)\n else if (isContainer(node)) body = sectionBody(state, node as FormSectionNode, props)\n else if (!shown.value)\n body = h('div', { class: 'dou-form-builder__placeholder', 'data-type': node.type }, [\n h('span', { class: 'dou-form-builder__placeholder-label' }),\n h('span', { class: 'dou-form-builder__placeholder-box' }),\n ])\n else\n body = h(\n 'div',\n { class: 'dou-form-builder__content', inert: true },\n h(FormNodeView, {\n node: designNode(node as FormFieldNode),\n columns: props.columns,\n inset: props.inset,\n }),\n )\n return h(\n 'div',\n {\n class: [\n 'dou-form-renderer__cell',\n 'dou-form-builder__node',\n isContainer(node) && 'is-container',\n selected.value && 'is-selected',\n marks.length > 0 && 'is-marked',\n ],\n ref: el,\n style: [\n spanStyle(node.span, props.columns),\n marks.length ? { '--dou-form-builder-mark': marks[0]!.color ?? 'currentColor' } : null,\n ],\n id: state.domId(node.id, 'canvas'),\n role: 'treeitem',\n 'aria-level': props.level,\n 'aria-posinset': props.position,\n 'aria-setsize': props.size,\n 'aria-selected': selected.value,\n 'aria-expanded': isContainer(node) ? true : undefined,\n 'aria-label': title,\n 'aria-description': logic?.text,\n 'data-node': node.id,\n onClick: press,\n onContextmenu: (event: MouseEvent) => {\n event.stopPropagation()\n event.preventDefault()\n // On touch screens a long press drags; the action bar has the menu.\n if ((event as PointerEvent).pointerType === 'touch') return\n if (!selected.value) state.select(node.id)\n state.openMenu.value?.(event.clientX, event.clientY)\n },\n onPointerdown: (event: PointerEvent) => {\n // The innermost node takes the press; the buttons and the width handle keep theirs.\n event.stopPropagation()\n const target = event.target as HTMLElement\n if (\n target.closest(\n '.dou-form-builder__actions, .dou-form-builder__resize, .dou-form-builder__strip',\n )\n )\n return\n state.dragger.value?.pressNode(event, node.id)\n },\n },\n [\n body,\n logic && !primary.value ? logicMark(logic) : null,\n marks.length ? markTags(marks) : null,\n primary.value && !state.readonly ? actionBar(state, node, title, moreOpen) : null,\n primary.value && !state.readonly && !isPage(node) ? resizeHandle(state, node, el) : null,\n ],\n )\n }\n },\n})\n\n/** The labels of a node's marks, over its top edge, each in its color. */\nfunction markTags(marks: FormBuilderNodeMark[]) {\n return h(\n 'div',\n { class: 'dou-form-builder__marks', 'aria-hidden': 'true' },\n marks.map((mark) =>\n h(\n 'span',\n {\n key: mark.key,\n class: 'dou-form-builder__mark',\n style: mark.color ? { '--dou-form-builder-mark': mark.color } : undefined,\n },\n mark.label,\n ),\n ),\n )\n}\n\nconst LOGIC_KEYS = ['visibleWhen', 'disabledWhen', 'requiredWhen', 'formula'] as const\n\n/** The conditions and the formula a node has, as lines for a tooltip; none without them. */\nfunction logicOf(state: BuilderState, node: FormNode) {\n const settings = state.messages.settings as Record<string, string>\n const lines: string[] = []\n let conditions = false\n let formula = false\n for (const key of LOGIC_KEYS) {\n const text = (node as Partial<Record<(typeof LOGIC_KEYS)[number], string>>)[key]\n if (!text?.trim()) continue\n if (key === 'formula') formula = true\n else conditions = true\n lines.push(\n formatMessage(state.messages.condition.summary, { label: settings[key] ?? key, text }),\n )\n }\n return lines.length ? { lines, text: lines.join('; '), conditions, formula } : undefined\n}\n\n/** A mark in the node's corner when it has conditions or a formula, telling them on hover. */\nfunction logicMark(logic: NonNullable<ReturnType<typeof logicOf>>) {\n return h(\n DouTooltip,\n { placement: 'top-end' },\n {\n default: () =>\n h('span', { class: 'dou-form-builder__logic-mark', 'aria-hidden': 'true' }, [\n logic.conditions ? h(WorkflowIcon) : null,\n logic.formula ? h(SigmaIcon) : null,\n ]),\n content: () =>\n h(\n 'span',\n { class: 'dou-form-builder__logic-lines' },\n logic.lines.map((line) => h('span', { key: line }, line)),\n ),\n },\n )\n}\n\ntype BodyProps = { columns: number; inset: number; level: number }\n\nfunction sectionBody(state: BuilderState, node: FormSectionNode | FormPageNode, props: BodyProps) {\n const columns = node.columns ?? props.columns\n const bordered = !isPage(node) && node.bordered\n return h('div', { class: ['dou-form-renderer__section', bordered && 'is-bordered'] }, [\n node.title || node.description\n ? h('div', { class: 'dou-form-renderer__section-header', inert: true }, [\n node.title && h('div', { class: 'dou-form-renderer__section-title' }, node.title),\n node.description &&\n h('div', { class: 'dou-form-renderer__section-description' }, node.description),\n ])\n : null,\n childGrid(\n state,\n node.id,\n node.children,\n columns,\n props.inset + (bordered ? 1 : 0),\n props.level + 1,\n ),\n ])\n}\n\nfunction listBody(\n state: BuilderState,\n node: FormListNode,\n props: BodyProps,\n messages: FormRendererMessages,\n) {\n const columns = node.columns ?? props.columns\n return h('div', { class: 'dou-form-renderer__list' }, [\n node.label || node.help\n ? h('div', { class: 'dou-form-renderer__section-header', inert: true }, [\n node.label &&\n h(\n 'div',\n { class: ['dou-form-renderer__section-title', node.required && 'is-required'] },\n node.label,\n ),\n node.help && h('div', { class: 'dou-form-renderer__section-description' }, node.help),\n ])\n : null,\n h('div', { class: 'dou-form-renderer__list-item dou-form-builder__item' }, [\n h(\n 'div',\n { class: 'dou-form-renderer__list-item-header', inert: true },\n h(\n 'span',\n { class: 'dou-form-renderer__list-item-title' },\n formatMessage(messages.item, { index: 1 }),\n ),\n ),\n childGrid(state, node.id, node.children, columns, props.inset + 1, props.level + 1),\n ]),\n h(\n 'div',\n { class: 'dou-form-builder__list-add', inert: true },\n h(\n DouButton,\n { variant: 'outline', tabindex: -1 },\n { leading: () => h(PlusIcon), default: () => node.addText ?? messages.add },\n ),\n ),\n ])\n}\n\nfunction pagesBody(\n state: BuilderState,\n node: FormContainerNode,\n props: BodyProps,\n press: (event: MouseEvent, id?: string) => void,\n) {\n const pages = node.children as FormPageNode[]\n const shown = state.shownPage(node)\n const steps = node.type === 'steps'\n const level = props.level + 1\n return h('div', { class: ['dou-form-builder__pages', steps && 'is-steps'] }, [\n h('div', { class: 'dou-form-builder__strip', role: 'group' }, [\n ...pages.map((page, index) =>\n h(\n 'div',\n {\n key: page.id,\n class: [\n 'dou-form-builder__page-tab',\n page === shown && 'is-shown',\n state.selected.value.has(page.id) && 'is-selected',\n ],\n id: state.domId(page.id, 'canvas'),\n role: 'treeitem',\n 'aria-level': level,\n 'aria-posinset': index + 1,\n 'aria-setsize': pages.length,\n 'aria-selected': state.selected.value.has(page.id),\n 'aria-expanded': page === shown,\n 'data-node': page.id,\n onClick: (event: MouseEvent) => press(event, page.id),\n },\n [\n steps ? h('span', { class: 'dou-form-builder__step-number' }, String(index + 1)) : null,\n h('span', { class: 'dou-form-builder__page-title' }, page.title || ' '),\n ],\n ),\n ),\n state.readonly\n ? null\n : h(\n 'button',\n {\n type: 'button',\n class: 'dou-form-builder__page-add',\n tabindex: -1,\n 'aria-label': state.messages.actions.addPage,\n title: state.messages.actions.addPage,\n onClick: (event: MouseEvent) => {\n event.stopPropagation()\n state.addPage(node.id)\n },\n },\n h(PlusIcon),\n ),\n ]),\n shown\n ? h(\n 'div',\n {\n class: ['dou-form-builder__page', state.selected.value.has(shown.id) && 'is-selected'],\n onClick: (event: MouseEvent) => press(event, shown.id),\n },\n [\n shown.description\n ? h(\n 'div',\n { class: 'dou-form-renderer__page-description', inert: true },\n shown.description,\n )\n : null,\n childGrid(\n state,\n shown.id,\n shown.children,\n shown.columns ?? props.columns,\n props.inset,\n level + 1,\n ),\n ],\n )\n : null,\n ])\n}\n\n/** One button of the selected node's action bar, its name and key in a tooltip. */\nconst actionButton = (\n label: string,\n shortcut: string | undefined,\n icon: VNode,\n onClick: (event: MouseEvent) => void,\n disabled = false,\n) =>\n h(\n DouTooltip,\n { content: label, shortcut },\n {\n default: () =>\n h(\n 'button',\n {\n type: 'button',\n class: 'dou-form-builder__action',\n tabindex: -1,\n disabled,\n 'aria-label': label,\n onClick,\n },\n icon,\n ),\n },\n )\n\n/** The buttons of the selected node; its type and name show in the inspector's header. */\nfunction actionBar(state: BuilderState, node: FormNode, title: string, moreOpen: Ref<boolean>) {\n const messages = state.messages\n const place = formNodePlaces(state.document).get(node.id)\n const siblings = place?.parent ? place.parent.children.length : state.document.nodes.length\n const after = () => state.canvas.value?.focus({ preventScroll: true })\n const ids = state.selected.value.has(node.id) ? state.selection : [node.id]\n const actions = [\n place?.parent\n ? {\n label: messages.actions.parent,\n shortcut: 'escape',\n icon: CornerLeftUpIcon,\n run: () => state.selectParent(),\n }\n : null,\n {\n label: messages.actions.up,\n shortcut: 'mod+arrowup',\n icon: ArrowUpIcon,\n run: () => state.moveBy(-1, [node.id]),\n disabled: !place?.index,\n },\n {\n label: messages.actions.down,\n shortcut: 'mod+arrowdown',\n icon: ArrowDownIcon,\n run: () => state.moveBy(1, [node.id]),\n disabled: !place || place.index >= siblings - 1,\n },\n {\n label: messages.actions.duplicate,\n shortcut: 'mod+d',\n icon: CopyIcon,\n run: () => state.duplicate([node.id]),\n },\n {\n label: messages.actions.remove,\n shortcut: 'delete',\n icon: TrashIcon,\n run: () => state.remove([node.id]),\n danger: true,\n },\n ].filter((action) => action !== null)\n // A node too narrow for the row of buttons gets one button with a menu instead (see style.css).\n let menu: DropdownExpose | null = null\n const more = h(\n DouDropdown,\n {\n ref: (instance: unknown) => (menu = instance as DropdownExpose | null),\n placement: 'bottom-end',\n // Closed by a choice, Escape or a press outside: the keys go on working on the canvas.\n 'onUpdate:open': (open: boolean) => {\n moreOpen.value = open\n if (!open) requestAnimationFrame(after)\n },\n },\n {\n default: () =>\n h(\n 'button',\n {\n type: 'button',\n class: 'dou-form-builder__action',\n tabindex: -1,\n 'aria-label': messages.actions.more,\n onClick: (event: MouseEvent) => event.stopPropagation(),\n // Escape closes the open menu only, rather than selecting the parent on the canvas.\n onKeydown: (event: KeyboardEvent) => {\n const target = event.currentTarget as HTMLElement\n if (event.key !== 'Escape' || target.getAttribute('aria-expanded') !== 'true') return\n event.stopPropagation()\n menu?.close()\n },\n },\n h(MoreHorizontalIcon),\n ),\n menu: () => menuItems(nodeMenu(state, ids)),\n },\n )\n // Items of extensions are only in the menu, so the menu button shows at any width.\n const extra = state.extensions().menus.length > 0\n return h(\n 'div',\n {\n class: 'dou-form-builder__actions',\n role: 'toolbar',\n 'aria-label': title,\n 'data-extra': extra || undefined,\n },\n [\n ...actions.map((action) =>\n actionButton(\n action.label,\n action.shortcut,\n h(action.icon),\n (event) => {\n event.stopPropagation()\n action.run()\n after()\n },\n action.disabled,\n ),\n ),\n // The tip goes on a wrapper, since the menu takes the button itself; it stands aside while the menu is open.\n h(\n DouTooltip,\n { content: messages.actions.more, disabled: moreOpen.value },\n { default: () => h('span', { class: 'dou-form-builder__actions-more' }, more) },\n ),\n ],\n )\n}\n\n/** The form widths the breakpoints of a responsive width start at, as in the renderer's styles. */\nconst breakpointWidths = [\n ['xl', 960],\n ['lg', 800],\n ['md', 640],\n ['sm', 480],\n] as const\n\n/** The key of a responsive width that applies at the form's width now. */\nfunction activeSpanKey(span: Exclude<FormResponsive<number>, number>, cell: HTMLElement) {\n const width = cell.closest('.dou-form-renderer')?.getBoundingClientRect().width ?? 0\n for (const [key, min] of breakpointWidths) if (width >= min && span[key] !== undefined) return key\n return 'base' as const\n}\n\n/** The handle on the selected node's end edge: drag it to change the width, snapping to columns. */\nfunction resizeHandle(state: BuilderState, node: FormNode, cell: Ref<HTMLElement | null>) {\n return h('div', {\n class: 'dou-form-builder__resize',\n title: state.messages.drag.resize,\n 'aria-hidden': true,\n onClick: (event: MouseEvent) => event.stopPropagation(),\n onPointerdown: (event: PointerEvent) => startResize(event, state, node.id, cell.value),\n })\n}\n\nfunction startResize(\n event: PointerEvent,\n state: BuilderState,\n id: string,\n cell: HTMLElement | null,\n) {\n if (event.button !== 0 || !cell?.parentElement) return\n event.preventDefault()\n event.stopPropagation()\n const handle = event.currentTarget as HTMLElement\n // Read the grid once; moving the pointer then only does math.\n const grid = cell.parentElement\n const style = getComputedStyle(grid)\n const columns = Math.max(\n 1,\n Math.round(Number(style.getPropertyValue('--dou-form-columns')) || 12),\n )\n const gap = Number.parseFloat(style.columnGap) || 0\n const unit = (grid.getBoundingClientRect().width + gap) / columns\n const left = cell.getBoundingClientRect().left\n const hold = holdPointer(event, handle)\n hold.grab()\n const node = formNodePlaces(state.document).get(id)?.node\n const span = node?.span\n const key = span !== undefined && typeof span === 'object' ? activeSpanKey(span, cell) : null\n const current = () => {\n const now = formNodePlaces(state.document).get(id)?.node.span\n if (now === undefined) return columns\n if (typeof now === 'number') return now\n return now[key ?? 'base'] ?? now.base ?? columns\n }\n let last = current()\n handle.dataset.value = `${last} / ${columns}`\n cell.classList.add('is-resizing')\n\n const onMove = (e: PointerEvent) => {\n if (e.pointerId !== event.pointerId) return\n const next = Math.min(columns, Math.max(1, Math.round((e.clientX - left + gap) / unit)))\n if (next === last) return\n last = next\n handle.dataset.value = `${next} / ${columns}`\n const now = formNodePlaces(state.document).get(id)?.node.span\n const value =\n key && now !== undefined && typeof now === 'object'\n ? { ...now, [key]: next }\n : next === columns\n ? undefined\n : next\n const tr = state.transaction().update(id, { span: value })\n tr.setMeta('group', `resize:${id}`)\n tr.setMeta('origin', 'resize')\n state.dispatch(tr)\n }\n const onUp = (e: PointerEvent) => {\n if (e.pointerId !== event.pointerId) return\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n hold.release()\n delete handle.dataset.value\n cell.classList.remove('is-resizing')\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n}\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { referenceSource } from "./condition.js";
2
+ // src/builder/completion.ts
3
+ var WORD = /[A-Za-z_$À-￿][\w$.À-￿]*$/;
4
+ var LIMIT = 50;
5
+ /** Whether the caret is inside quoted text, where nothing is completed. */
6
+ function inString(text) {
7
+ let quote = "";
8
+ for (let i = 0; i < text.length; i++) {
9
+ const char = text[i];
10
+ if (quote) {
11
+ if (char === "\\") i++;
12
+ else if (char === quote) quote = "";
13
+ } else if (char === "'" || char === "\"") quote = char;
14
+ }
15
+ return !!quote;
16
+ }
17
+ /**
18
+ * What can be typed at the caret: field paths after `values.` or `item.`,
19
+ * else the names an expression starts with and the functions.
20
+ */
21
+ function completeExpression(text, caret, scope, labels) {
22
+ const before = text.slice(0, caret);
23
+ const match = WORD.exec(before);
24
+ if (!match || inString(before)) return [];
25
+ const word = match[0];
26
+ const start = caret - word.length;
27
+ const make = (label, insert, description) => ({
28
+ label,
29
+ description,
30
+ text: text.slice(0, start) + insert + text.slice(caret),
31
+ caret: start + insert.length
32
+ });
33
+ const dot = word.indexOf(".");
34
+ if (dot >= 0) {
35
+ const root = word.slice(0, dot);
36
+ const rest = word.slice(dot + 1);
37
+ const list = root === "values" ? scope.names : root === "item" ? scope.itemNames : void 0;
38
+ if (!list) return [];
39
+ return list.filter(({ path }) => path.startsWith(rest) && path !== rest).slice(0, LIMIT).map(({ path, label }) => make(path, referenceSource(root, path), label));
40
+ }
41
+ const roots = [make("values.", "values.", labels.values)];
42
+ if (scope.itemNames) roots.push(make("item.", "item.", labels.item));
43
+ if (scope.value) roots.push(make("value", "value", labels.value));
44
+ const functions = scope.functions.map((name) => make(`${name}()`, `${name}(`));
45
+ return [...roots, ...functions].filter((completion) => completion.label.startsWith(word) && completion.label !== word);
46
+ }
47
+ export { completeExpression };
48
+
49
+ //# sourceMappingURL=completion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion.js","names":[],"sources":["../../src/builder/completion.ts"],"sourcesContent":["import { referenceSource } from './condition'\n\n/** What an expression can read, for completions. */\nexport interface CompletionScope {\n /** Paths under `values`, with what to show beside each. */\n names: readonly { path: string; label?: string }[]\n /** Paths under `item`; `undefined` where there is no list item. */\n itemNames?: readonly { path: string; label?: string }[]\n /** Whether `value` (the field's own value) can be read. */\n value?: boolean\n functions: readonly string[]\n}\n\nexport interface Completion {\n /** What the list shows. */\n label: string\n /** A quieter text after it, e.g. the field's label. */\n description?: string\n /** The whole text once chosen. */\n text: string\n /** Where the caret goes in `text`. */\n caret: number\n}\n\nconst WORD = /[A-Za-z_$À-￿][\\w$.À-￿]*$/\nconst LIMIT = 50\n\n/** Whether the caret is inside quoted text, where nothing is completed. */\nfunction inString(text: string) {\n let quote = ''\n for (let i = 0; i < text.length; i++) {\n const char = text[i]!\n if (quote) {\n if (char === '\\\\') i++\n else if (char === quote) quote = ''\n } else if (char === \"'\" || char === '\"') quote = char\n }\n return !!quote\n}\n\n/**\n * What can be typed at the caret: field paths after `values.` or `item.`,\n * else the names an expression starts with and the functions.\n */\nexport function completeExpression(\n text: string,\n caret: number,\n scope: CompletionScope,\n labels: { values: string; item: string; value: string },\n): Completion[] {\n const before = text.slice(0, caret)\n const match = WORD.exec(before)\n if (!match || inString(before)) return []\n const word = match[0]\n const start = caret - word.length\n const make = (label: string, insert: string, description?: string): Completion => ({\n label,\n description,\n text: text.slice(0, start) + insert + text.slice(caret),\n caret: start + insert.length,\n })\n\n const dot = word.indexOf('.')\n if (dot >= 0) {\n const root = word.slice(0, dot)\n const rest = word.slice(dot + 1)\n const list = root === 'values' ? scope.names : root === 'item' ? scope.itemNames : undefined\n if (!list) return []\n return list\n .filter(({ path }) => path.startsWith(rest) && path !== rest)\n .slice(0, LIMIT)\n .map(({ path, label }) => make(path, referenceSource(root as 'values' | 'item', path), label))\n }\n\n const roots: Completion[] = [make('values.', 'values.', labels.values)]\n if (scope.itemNames) roots.push(make('item.', 'item.', labels.item))\n if (scope.value) roots.push(make('value', 'value', labels.value))\n const functions = scope.functions.map((name) => make(`${name}()`, `${name}(`))\n return [...roots, ...functions].filter(\n (completion) => completion.label.startsWith(word) && completion.label !== word,\n )\n}\n"],"mappings":";;AAwBA,IAAM,OAAO;AACb,IAAM,QAAQ;;AAGd,SAAS,SAAS,MAAc;CAC9B,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK;EAClB,IAAI,OAAO;GACT,IAAI,SAAS,MAAM;QACd,IAAI,SAAS,OAAO,QAAQ;EACnC,OAAO,IAAI,SAAS,OAAO,SAAS,MAAK,QAAQ;CACnD;CACA,OAAO,CAAC,CAAC;AACX;;;;;AAMA,SAAgB,mBACd,MACA,OACA,OACA,QACc;CACd,MAAM,SAAS,KAAK,MAAM,GAAG,KAAK;CAClC,MAAM,QAAQ,KAAK,KAAK,MAAM;CAC9B,IAAI,CAAC,SAAS,SAAS,MAAM,GAAG,OAAO,CAAC;CACxC,MAAM,OAAO,MAAM;CACnB,MAAM,QAAQ,QAAQ,KAAK;CAC3B,MAAM,QAAQ,OAAe,QAAgB,iBAAsC;EACjF;EACA;EACA,MAAM,KAAK,MAAM,GAAG,KAAK,IAAI,SAAS,KAAK,MAAM,KAAK;EACtD,OAAO,QAAQ,OAAO;CACxB;CAEA,MAAM,MAAM,KAAK,QAAQ,GAAG;CAC5B,IAAI,OAAO,GAAG;EACZ,MAAM,OAAO,KAAK,MAAM,GAAG,GAAG;EAC9B,MAAM,OAAO,KAAK,MAAM,MAAM,CAAC;EAC/B,MAAM,OAAO,SAAS,WAAW,MAAM,QAAQ,SAAS,SAAS,MAAM,YAAY,KAAA;EACnF,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,OAAO,KACJ,QAAQ,EAAE,WAAW,KAAK,WAAW,IAAI,KAAK,SAAS,IAAI,CAAC,CAC5D,MAAM,GAAG,KAAK,CAAC,CACf,KAAK,EAAE,MAAM,YAAY,KAAK,MAAM,gBAAgB,MAA2B,IAAI,GAAG,KAAK,CAAC;CACjG;CAEA,MAAM,QAAsB,CAAC,KAAK,WAAW,WAAW,OAAO,MAAM,CAAC;CACtE,IAAI,MAAM,WAAW,MAAM,KAAK,KAAK,SAAS,SAAS,OAAO,IAAI,CAAC;CACnE,IAAI,MAAM,OAAO,MAAM,KAAK,KAAK,SAAS,SAAS,OAAO,KAAK,CAAC;CAChE,MAAM,YAAY,MAAM,UAAU,KAAK,SAAS,KAAK,GAAG,KAAK,KAAK,GAAG,KAAK,EAAE,CAAC;CAC7E,OAAO,CAAC,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC,QAC7B,eAAe,WAAW,MAAM,WAAW,IAAI,KAAK,WAAW,UAAU,IAC5E;AACF"}
@@ -0,0 +1,6 @@
1
+ import condition_editor_vue_vue_type_script_setup_true_lang_default from "./condition-editor.vue_vue_type_script_setup_true_lang.js";
2
+ // src/builder/condition-editor.vue
3
+ var condition_editor_default = condition_editor_vue_vue_type_script_setup_true_lang_default;
4
+ export { condition_editor_default as default };
5
+
6
+ //# sourceMappingURL=condition-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition-editor.js","names":["_sfc_main"],"sources":["../../src/builder/condition-editor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, shallowRef, watch } from 'vue'\nimport {\n DouButton,\n DouInput,\n DouInputNumber,\n DouSegmentedControl,\n DouSelect,\n DouTooltip,\n useFormReadonly,\n} from 'dou-ui'\nimport type { SelectValue } from 'dou-ui'\nimport { BracesIcon, CloseIcon, FilterIcon, PlusIcon } from '@dou-ui/icons'\nimport { parseCondition, printCondition, unaryOperators } from './condition'\nimport type { Condition, ConditionLiteral, ConditionOperator, ConditionRow } from './condition'\nimport { useBuilderState } from './context'\nimport ExpressionInput from './expression-box'\nimport { operatorsOf } from './logic'\nimport type { LogicTarget } from './logic'\n\n/**\n * A condition as rows of \"field, how, value\", written to the node as\n * expression text. An expression the rows cannot say is edited as text.\n */\ndefineOptions({ name: 'DouFormBuilderCondition' })\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string | null\n /** The fields the rows can read. */\n targets?: LogicTarget[]\n disabled?: boolean\n }>(),\n { modelValue: '', targets: () => [] },\n)\nconst emit = defineEmits<{ 'update:modelValue': [value: string] }>()\n\nconst state = useBuilderState()\nconst words = computed(() => state.messages.condition)\n// Read-only, the rows show as they are, without buttons to change them.\nconst readonly = useFormReadonly(() => state.readonly)\n\nconst condition = shallowRef<Condition>({ join: 'and', rows: [] })\nconst asText = shallowRef(false)\n/** The text this editor wrote last, which needs no reading back. */\nlet written: string | undefined\n\nwatch(\n () => props.modelValue ?? '',\n (source) => {\n if (source === written) return\n written = undefined\n const parsed = parseCondition(source)\n asText.value = !parsed\n if (parsed) condition.value = parsed\n },\n { immediate: true },\n)\n\nfunction commit(next: Condition) {\n condition.value = next\n const text = printCondition(next)\n written = text\n if (text !== (props.modelValue ?? '')) emit('update:modelValue', text)\n}\n\nconst keyOf = (scope: string, path: string) => `${scope}:${path}`\nconst option = (target: LogicTarget) => ({\n value: keyOf(target.scope, target.path),\n label: target.label === target.path ? target.path : `${target.label} · ${target.path}`,\n})\nconst targetsByKey = computed(\n () => new Map(props.targets.map((target) => [keyOf(target.scope, target.path), target])),\n)\nconst fieldOptions = computed(() => {\n const own = props.targets.filter((target) => target.scope === 'item')\n const all = props.targets.filter((target) => target.scope === 'values')\n if (!own.length) return all.map(option)\n return [\n { label: words.value.itemGroup, options: own.map(option) },\n { label: words.value.formGroup, options: all.map(option) },\n ]\n})\n\nconst targetOf = (row: ConditionRow) => targetsByKey.value.get(keyOf(row.scope, row.path))\nconst operators = (row: ConditionRow): ConditionOperator[] => {\n const target = targetOf(row)\n return target ? operatorsOf[target.kind] : operatorsOf.text\n}\nfunction operatorOptions(row: ConditionRow) {\n const date = targetOf(row)?.kind === 'date'\n const labels = words.value.operators\n const dates = words.value.dateOperators\n return operators(row).map((op) => ({\n value: op,\n label: (date ? dates[op] : undefined) ?? labels[op] ?? op,\n }))\n}\n\nfunction setRow(index: number, patch: Partial<ConditionRow>) {\n const rows = condition.value.rows.slice()\n rows[index] = { ...rows[index]!, ...patch }\n commit({ ...condition.value, rows })\n}\n\nfunction pickField(index: number, key: SelectValue | SelectValue[] | null | undefined) {\n const target = targetsByKey.value.get(String(key))\n if (!target) return\n const row = condition.value.rows[index]!\n const ops = operatorsOf[target.kind]\n const op = ops.includes(row.op) ? row.op : ops[0]!\n setRow(index, { scope: target.scope, path: target.path, op, value: initial(target, op) })\n}\n\nfunction initial(target: LogicTarget, op: ConditionOperator) {\n if (unaryOperators.has(op)) return undefined\n if (target.kind === 'boolean') return true\n return op === 'in' ? [] : undefined\n}\n\nfunction pickOperator(index: number, op: ConditionOperator) {\n const row = condition.value.rows[index]!\n const target = targetOf(row)\n const keep = !unaryOperators.has(op) && (op === 'in') === Array.isArray(row.value)\n setRow(index, { op, value: keep ? row.value : target ? initial(target, op) : undefined })\n}\n\nfunction addRow() {\n const first = props.targets[0]\n const row: ConditionRow = first\n ? {\n scope: first.scope,\n path: first.path,\n op: operatorsOf[first.kind][0]!,\n value: initial(first, operatorsOf[first.kind][0]!),\n }\n : { scope: 'values', path: '', op: 'eq' }\n commit({ ...condition.value, rows: [...condition.value.rows, row] })\n}\n\nfunction removeRow(index: number) {\n const rows = condition.value.rows.filter((_, at) => at !== index)\n commit({ ...condition.value, rows })\n}\n\nconst joinOptions = computed(() => [\n { value: 'and', label: words.value.all },\n { value: 'or', label: words.value.any },\n])\n\n/** What a row's value input is. */\nfunction valueInput(row: ConditionRow) {\n if (unaryOperators.has(row.op)) return 'none'\n const target = targetOf(row)\n if (!target) return 'text'\n if (target.kind === 'boolean') return 'boolean'\n if (row.op === 'in' || target.options?.length) return 'select'\n if (target.kind === 'number') return 'number'\n return 'text'\n}\n\nconst booleanOptions = computed(() => [\n { value: true, label: words.value.yes },\n { value: false, label: words.value.no },\n])\n\nfunction selectOptions(row: ConditionRow) {\n return (targetOf(row)?.options ?? []) as { label: string; value: SelectValue }[]\n}\n\nconst canUseRows = computed(() => !!parseCondition(props.modelValue ?? ''))\nfunction toRows() {\n const parsed = parseCondition(props.modelValue ?? '')\n if (!parsed) return\n condition.value = parsed\n asText.value = false\n}\n\nconst names = computed(() =>\n props.targets\n .filter((target) => target.scope === 'values')\n .map((target) => ({ path: target.path, label: target.label })),\n)\nconst itemNames = computed(() => {\n const own = props.targets.filter((target) => target.scope === 'item')\n return own.length ? own.map((target) => ({ path: target.path, label: target.label })) : undefined\n})\nconst textValue = computed({\n get: () => props.modelValue ?? '',\n // Text typed here stays text, even once it could be shown as rows.\n set: (value: string) => {\n written = value\n emit('update:modelValue', value)\n },\n})\n</script>\n\n<template>\n <div class=\"dou-form-builder__condition\">\n <!-- Read-only, a condition not set shows a dash. -->\n <div\n v-if=\"readonly && !asText && !condition.rows.length\"\n class=\"dou-form-builder__condition-empty\"\n >\n —\n </div>\n <template v-if=\"!asText\">\n <DouSegmentedControl\n v-if=\"condition.rows.length > 1\"\n :model-value=\"condition.join\"\n :options=\"joinOptions\"\n :disabled=\"disabled\"\n size=\"sm\"\n block\n @update:model-value=\"commit({ ...condition, join: $event as 'and' | 'or' })\"\n />\n <div\n v-for=\"(row, index) in condition.rows\"\n :key=\"index\"\n class=\"dou-form-builder__condition-row\"\n role=\"group\"\n :aria-label=\"`${words.row} ${index + 1}`\"\n >\n <div class=\"dou-form-builder__condition-line\">\n <DouSelect\n :model-value=\"row.path ? keyOf(row.scope, row.path) : null\"\n :options=\"fieldOptions\"\n :placeholder=\"words.field\"\n :aria-label=\"words.field\"\n :disabled=\"disabled\"\n :filterable=\"targets.length > 8\"\n size=\"sm\"\n class=\"dou-form-builder__condition-field\"\n @update:model-value=\"pickField(index, $event)\"\n />\n <DouTooltip v-if=\"!readonly\" :content=\"words.remove\">\n <DouButton\n :aria-label=\"words.remove\"\n :disabled=\"disabled\"\n size=\"sm\"\n variant=\"ghost\"\n @click=\"removeRow(index)\"\n >\n <CloseIcon />\n </DouButton>\n </DouTooltip>\n </div>\n <div class=\"dou-form-builder__condition-line\">\n <DouSelect\n :model-value=\"row.op\"\n :options=\"operatorOptions(row)\"\n :aria-label=\"words.operator\"\n :disabled=\"disabled\"\n size=\"sm\"\n class=\"dou-form-builder__condition-op\"\n @update:model-value=\"pickOperator(index, $event as ConditionOperator)\"\n />\n <template v-if=\"valueInput(row) !== 'none'\">\n <DouSelect\n v-if=\"valueInput(row) === 'boolean'\"\n :model-value=\"row.value as SelectValue\"\n :options=\"booleanOptions\"\n :aria-label=\"words.value\"\n :disabled=\"disabled\"\n size=\"sm\"\n class=\"dou-form-builder__condition-value\"\n @update:model-value=\"setRow(index, { value: $event as boolean })\"\n />\n <DouSelect\n v-else-if=\"valueInput(row) === 'select'\"\n :model-value=\"row.value as SelectValue | SelectValue[]\"\n :options=\"selectOptions(row)\"\n :multiple=\"row.op === 'in'\"\n :creatable=\"!selectOptions(row).length\"\n :filterable=\"!selectOptions(row).length\"\n :placeholder=\"words.value\"\n :aria-label=\"words.value\"\n :disabled=\"disabled\"\n size=\"sm\"\n class=\"dou-form-builder__condition-value\"\n @update:model-value=\"\n setRow(index, { value: $event as ConditionLiteral | ConditionLiteral[] })\n \"\n />\n <DouInputNumber\n v-else-if=\"valueInput(row) === 'number'\"\n :model-value=\"typeof row.value === 'number' ? row.value : null\"\n :controls=\"false\"\n :placeholder=\"words.value\"\n :aria-label=\"words.value\"\n :disabled=\"disabled\"\n size=\"sm\"\n class=\"dou-form-builder__condition-value\"\n @update:model-value=\"setRow(index, { value: $event ?? undefined })\"\n />\n <DouInput\n v-else\n :model-value=\"typeof row.value === 'string' ? row.value : ''\"\n :placeholder=\"targetOf(row)?.kind === 'date' ? 'YYYY-MM-DD' : words.value\"\n :aria-label=\"words.value\"\n :disabled=\"disabled\"\n size=\"sm\"\n class=\"dou-form-builder__condition-value\"\n @update:model-value=\"setRow(index, { value: $event })\"\n />\n </template>\n </div>\n </div>\n </template>\n <ExpressionInput\n v-else\n v-model=\"textValue\"\n :names=\"names\"\n :item-names=\"itemNames\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n />\n <div v-if=\"!readonly\" class=\"dou-form-builder__condition-footer\">\n <DouButton\n v-if=\"!asText\"\n size=\"sm\"\n variant=\"ghost\"\n :disabled=\"disabled || !targets.length\"\n @click=\"addRow\"\n >\n <template #leading><PlusIcon /></template>\n {{ words.add }}\n </DouButton>\n <DouButton\n v-if=\"!asText\"\n size=\"sm\"\n variant=\"ghost\"\n class=\"dou-form-builder__condition-mode\"\n :disabled=\"disabled\"\n @click=\"asText = true\"\n >\n <template #leading><BracesIcon /></template>\n {{ words.toText }}\n </DouButton>\n <!-- Says why rows are out of reach. -->\n <DouTooltip v-else :content=\"words.notRows\" :disabled=\"canUseRows\">\n <DouButton\n size=\"sm\"\n variant=\"ghost\"\n class=\"dou-form-builder__condition-mode\"\n :disabled=\"disabled || !canUseRows\"\n @click=\"toRows\"\n >\n <template #leading><FilterIcon /></template>\n {{ words.toRows }}\n </DouButton>\n </DouTooltip>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,410 @@
1
+ import { useBuilderState } from "./context.js";
2
+ import { operatorsOf } from "./logic.js";
3
+ import { parseCondition, printCondition, unaryOperators } from "./condition.js";
4
+ import ExpressionBox from "./expression-box.js";
5
+ import { DouButton, DouInput, DouInputNumber, DouSegmentedControl, DouSelect, DouTooltip, useFormReadonly } from "dou-ui";
6
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, renderList, shallowRef, toDisplayString, unref, watch, withCtx } from "vue";
7
+ import { BracesIcon, CloseIcon, FilterIcon, PlusIcon } from "@dou-ui/icons";
8
+ // src/builder/condition-editor.vue?vue&type=script&setup=true&lang.ts
9
+ var _hoisted_1 = { class: "dou-form-builder__condition" };
10
+ var _hoisted_2 = {
11
+ key: 0,
12
+ class: "dou-form-builder__condition-empty"
13
+ };
14
+ var _hoisted_3 = ["aria-label"];
15
+ var _hoisted_4 = { class: "dou-form-builder__condition-line" };
16
+ var _hoisted_5 = { class: "dou-form-builder__condition-line" };
17
+ var _hoisted_6 = {
18
+ key: 3,
19
+ class: "dou-form-builder__condition-footer"
20
+ };
21
+ /**
22
+ * A condition as rows of "field, how, value", written to the node as
23
+ * expression text. An expression the rows cannot say is edited as text.
24
+ */
25
+ var condition_editor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
26
+ name: "DouFormBuilderCondition",
27
+ __name: "condition-editor",
28
+ props: {
29
+ modelValue: { default: "" },
30
+ targets: { default: () => [] },
31
+ disabled: { type: Boolean }
32
+ },
33
+ emits: ["update:modelValue"],
34
+ setup(__props, { emit: __emit }) {
35
+ const props = __props;
36
+ const emit = __emit;
37
+ const state = useBuilderState();
38
+ const words = computed(() => state.messages.condition);
39
+ const readonly = useFormReadonly(() => state.readonly);
40
+ const condition = shallowRef({
41
+ join: "and",
42
+ rows: []
43
+ });
44
+ const asText = shallowRef(false);
45
+ /** The text this editor wrote last, which needs no reading back. */
46
+ let written;
47
+ watch(() => props.modelValue ?? "", (source) => {
48
+ if (source === written) return;
49
+ written = void 0;
50
+ const parsed = parseCondition(source);
51
+ asText.value = !parsed;
52
+ if (parsed) condition.value = parsed;
53
+ }, { immediate: true });
54
+ function commit(next) {
55
+ condition.value = next;
56
+ const text = printCondition(next);
57
+ written = text;
58
+ if (text !== (props.modelValue ?? "")) emit("update:modelValue", text);
59
+ }
60
+ const keyOf = (scope, path) => `${scope}:${path}`;
61
+ const option = (target) => ({
62
+ value: keyOf(target.scope, target.path),
63
+ label: target.label === target.path ? target.path : `${target.label} · ${target.path}`
64
+ });
65
+ const targetsByKey = computed(() => new Map(props.targets.map((target) => [keyOf(target.scope, target.path), target])));
66
+ const fieldOptions = computed(() => {
67
+ const own = props.targets.filter((target) => target.scope === "item");
68
+ const all = props.targets.filter((target) => target.scope === "values");
69
+ if (!own.length) return all.map(option);
70
+ return [{
71
+ label: words.value.itemGroup,
72
+ options: own.map(option)
73
+ }, {
74
+ label: words.value.formGroup,
75
+ options: all.map(option)
76
+ }];
77
+ });
78
+ const targetOf = (row) => targetsByKey.value.get(keyOf(row.scope, row.path));
79
+ const operators = (row) => {
80
+ const target = targetOf(row);
81
+ return target ? operatorsOf[target.kind] : operatorsOf.text;
82
+ };
83
+ function operatorOptions(row) {
84
+ const date = targetOf(row)?.kind === "date";
85
+ const labels = words.value.operators;
86
+ const dates = words.value.dateOperators;
87
+ return operators(row).map((op) => ({
88
+ value: op,
89
+ label: (date ? dates[op] : void 0) ?? labels[op] ?? op
90
+ }));
91
+ }
92
+ function setRow(index, patch) {
93
+ const rows = condition.value.rows.slice();
94
+ rows[index] = {
95
+ ...rows[index],
96
+ ...patch
97
+ };
98
+ commit({
99
+ ...condition.value,
100
+ rows
101
+ });
102
+ }
103
+ function pickField(index, key) {
104
+ const target = targetsByKey.value.get(String(key));
105
+ if (!target) return;
106
+ const row = condition.value.rows[index];
107
+ const ops = operatorsOf[target.kind];
108
+ const op = ops.includes(row.op) ? row.op : ops[0];
109
+ setRow(index, {
110
+ scope: target.scope,
111
+ path: target.path,
112
+ op,
113
+ value: initial(target, op)
114
+ });
115
+ }
116
+ function initial(target, op) {
117
+ if (unaryOperators.has(op)) return void 0;
118
+ if (target.kind === "boolean") return true;
119
+ return op === "in" ? [] : void 0;
120
+ }
121
+ function pickOperator(index, op) {
122
+ const row = condition.value.rows[index];
123
+ const target = targetOf(row);
124
+ setRow(index, {
125
+ op,
126
+ value: !unaryOperators.has(op) && op === "in" === Array.isArray(row.value) ? row.value : target ? initial(target, op) : void 0
127
+ });
128
+ }
129
+ function addRow() {
130
+ const first = props.targets[0];
131
+ const row = first ? {
132
+ scope: first.scope,
133
+ path: first.path,
134
+ op: operatorsOf[first.kind][0],
135
+ value: initial(first, operatorsOf[first.kind][0])
136
+ } : {
137
+ scope: "values",
138
+ path: "",
139
+ op: "eq"
140
+ };
141
+ commit({
142
+ ...condition.value,
143
+ rows: [...condition.value.rows, row]
144
+ });
145
+ }
146
+ function removeRow(index) {
147
+ const rows = condition.value.rows.filter((_, at) => at !== index);
148
+ commit({
149
+ ...condition.value,
150
+ rows
151
+ });
152
+ }
153
+ const joinOptions = computed(() => [{
154
+ value: "and",
155
+ label: words.value.all
156
+ }, {
157
+ value: "or",
158
+ label: words.value.any
159
+ }]);
160
+ /** What a row's value input is. */
161
+ function valueInput(row) {
162
+ if (unaryOperators.has(row.op)) return "none";
163
+ const target = targetOf(row);
164
+ if (!target) return "text";
165
+ if (target.kind === "boolean") return "boolean";
166
+ if (row.op === "in" || target.options?.length) return "select";
167
+ if (target.kind === "number") return "number";
168
+ return "text";
169
+ }
170
+ const booleanOptions = computed(() => [{
171
+ value: true,
172
+ label: words.value.yes
173
+ }, {
174
+ value: false,
175
+ label: words.value.no
176
+ }]);
177
+ function selectOptions(row) {
178
+ return targetOf(row)?.options ?? [];
179
+ }
180
+ const canUseRows = computed(() => !!parseCondition(props.modelValue ?? ""));
181
+ function toRows() {
182
+ const parsed = parseCondition(props.modelValue ?? "");
183
+ if (!parsed) return;
184
+ condition.value = parsed;
185
+ asText.value = false;
186
+ }
187
+ const names = computed(() => props.targets.filter((target) => target.scope === "values").map((target) => ({
188
+ path: target.path,
189
+ label: target.label
190
+ })));
191
+ const itemNames = computed(() => {
192
+ const own = props.targets.filter((target) => target.scope === "item");
193
+ return own.length ? own.map((target) => ({
194
+ path: target.path,
195
+ label: target.label
196
+ })) : void 0;
197
+ });
198
+ const textValue = computed({
199
+ get: () => props.modelValue ?? "",
200
+ set: (value) => {
201
+ written = value;
202
+ emit("update:modelValue", value);
203
+ }
204
+ });
205
+ return (_ctx, _cache) => {
206
+ return openBlock(), createElementBlock("div", _hoisted_1, [
207
+ unref(readonly) && !asText.value && !condition.value.rows.length ? (openBlock(), createElementBlock("div", _hoisted_2, " — ")) : createCommentVNode("", true),
208
+ !asText.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [condition.value.rows.length > 1 ? (openBlock(), createBlock(unref(DouSegmentedControl), {
209
+ key: 0,
210
+ "model-value": condition.value.join,
211
+ options: joinOptions.value,
212
+ disabled: __props.disabled,
213
+ size: "sm",
214
+ block: "",
215
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => commit({
216
+ ...condition.value,
217
+ join: $event
218
+ }))
219
+ }, null, 8, [
220
+ "model-value",
221
+ "options",
222
+ "disabled"
223
+ ])) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(condition.value.rows, (row, index) => {
224
+ return openBlock(), createElementBlock("div", {
225
+ key: index,
226
+ class: "dou-form-builder__condition-row",
227
+ role: "group",
228
+ "aria-label": `${words.value.row} ${index + 1}`
229
+ }, [createElementVNode("div", _hoisted_4, [createVNode(unref(DouSelect), {
230
+ "model-value": row.path ? keyOf(row.scope, row.path) : null,
231
+ options: fieldOptions.value,
232
+ placeholder: words.value.field,
233
+ "aria-label": words.value.field,
234
+ disabled: __props.disabled,
235
+ filterable: __props.targets.length > 8,
236
+ size: "sm",
237
+ class: "dou-form-builder__condition-field",
238
+ "onUpdate:modelValue": ($event) => pickField(index, $event)
239
+ }, null, 8, [
240
+ "model-value",
241
+ "options",
242
+ "placeholder",
243
+ "aria-label",
244
+ "disabled",
245
+ "filterable",
246
+ "onUpdate:modelValue"
247
+ ]), !unref(readonly) ? (openBlock(), createBlock(unref(DouTooltip), {
248
+ key: 0,
249
+ content: words.value.remove
250
+ }, {
251
+ default: withCtx(() => [createVNode(unref(DouButton), {
252
+ "aria-label": words.value.remove,
253
+ disabled: __props.disabled,
254
+ size: "sm",
255
+ variant: "ghost",
256
+ onClick: ($event) => removeRow(index)
257
+ }, {
258
+ default: withCtx(() => [createVNode(unref(CloseIcon))]),
259
+ _: 1
260
+ }, 8, [
261
+ "aria-label",
262
+ "disabled",
263
+ "onClick"
264
+ ])]),
265
+ _: 2
266
+ }, 1032, ["content"])) : createCommentVNode("", true)]), createElementVNode("div", _hoisted_5, [createVNode(unref(DouSelect), {
267
+ "model-value": row.op,
268
+ options: operatorOptions(row),
269
+ "aria-label": words.value.operator,
270
+ disabled: __props.disabled,
271
+ size: "sm",
272
+ class: "dou-form-builder__condition-op",
273
+ "onUpdate:modelValue": ($event) => pickOperator(index, $event)
274
+ }, null, 8, [
275
+ "model-value",
276
+ "options",
277
+ "aria-label",
278
+ "disabled",
279
+ "onUpdate:modelValue"
280
+ ]), valueInput(row) !== "none" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [valueInput(row) === "boolean" ? (openBlock(), createBlock(unref(DouSelect), {
281
+ key: 0,
282
+ "model-value": row.value,
283
+ options: booleanOptions.value,
284
+ "aria-label": words.value.value,
285
+ disabled: __props.disabled,
286
+ size: "sm",
287
+ class: "dou-form-builder__condition-value",
288
+ "onUpdate:modelValue": ($event) => setRow(index, { value: $event })
289
+ }, null, 8, [
290
+ "model-value",
291
+ "options",
292
+ "aria-label",
293
+ "disabled",
294
+ "onUpdate:modelValue"
295
+ ])) : valueInput(row) === "select" ? (openBlock(), createBlock(unref(DouSelect), {
296
+ key: 1,
297
+ "model-value": row.value,
298
+ options: selectOptions(row),
299
+ multiple: row.op === "in",
300
+ creatable: !selectOptions(row).length,
301
+ filterable: !selectOptions(row).length,
302
+ placeholder: words.value.value,
303
+ "aria-label": words.value.value,
304
+ disabled: __props.disabled,
305
+ size: "sm",
306
+ class: "dou-form-builder__condition-value",
307
+ "onUpdate:modelValue": ($event) => setRow(index, { value: $event })
308
+ }, null, 8, [
309
+ "model-value",
310
+ "options",
311
+ "multiple",
312
+ "creatable",
313
+ "filterable",
314
+ "placeholder",
315
+ "aria-label",
316
+ "disabled",
317
+ "onUpdate:modelValue"
318
+ ])) : valueInput(row) === "number" ? (openBlock(), createBlock(unref(DouInputNumber), {
319
+ key: 2,
320
+ "model-value": typeof row.value === "number" ? row.value : null,
321
+ controls: false,
322
+ placeholder: words.value.value,
323
+ "aria-label": words.value.value,
324
+ disabled: __props.disabled,
325
+ size: "sm",
326
+ class: "dou-form-builder__condition-value",
327
+ "onUpdate:modelValue": ($event) => setRow(index, { value: $event ?? void 0 })
328
+ }, null, 8, [
329
+ "model-value",
330
+ "placeholder",
331
+ "aria-label",
332
+ "disabled",
333
+ "onUpdate:modelValue"
334
+ ])) : (openBlock(), createBlock(unref(DouInput), {
335
+ key: 3,
336
+ "model-value": typeof row.value === "string" ? row.value : "",
337
+ placeholder: targetOf(row)?.kind === "date" ? "YYYY-MM-DD" : words.value.value,
338
+ "aria-label": words.value.value,
339
+ disabled: __props.disabled,
340
+ size: "sm",
341
+ class: "dou-form-builder__condition-value",
342
+ "onUpdate:modelValue": ($event) => setRow(index, { value: $event })
343
+ }, null, 8, [
344
+ "model-value",
345
+ "placeholder",
346
+ "aria-label",
347
+ "disabled",
348
+ "onUpdate:modelValue"
349
+ ]))], 64)) : createCommentVNode("", true)])], 8, _hoisted_3);
350
+ }), 128))], 64)) : (openBlock(), createBlock(unref(ExpressionBox), {
351
+ key: 2,
352
+ modelValue: textValue.value,
353
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => textValue.value = $event),
354
+ names: names.value,
355
+ "item-names": itemNames.value,
356
+ disabled: __props.disabled,
357
+ readonly: unref(readonly)
358
+ }, null, 8, [
359
+ "modelValue",
360
+ "names",
361
+ "item-names",
362
+ "disabled",
363
+ "readonly"
364
+ ])),
365
+ !unref(readonly) ? (openBlock(), createElementBlock("div", _hoisted_6, [!asText.value ? (openBlock(), createBlock(unref(DouButton), {
366
+ key: 0,
367
+ size: "sm",
368
+ variant: "ghost",
369
+ disabled: __props.disabled || !__props.targets.length,
370
+ onClick: addRow
371
+ }, {
372
+ leading: withCtx(() => [createVNode(unref(PlusIcon))]),
373
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(words.value.add), 1)]),
374
+ _: 1
375
+ }, 8, ["disabled"])) : createCommentVNode("", true), !asText.value ? (openBlock(), createBlock(unref(DouButton), {
376
+ key: 1,
377
+ size: "sm",
378
+ variant: "ghost",
379
+ class: "dou-form-builder__condition-mode",
380
+ disabled: __props.disabled,
381
+ onClick: _cache[2] || (_cache[2] = ($event) => asText.value = true)
382
+ }, {
383
+ leading: withCtx(() => [createVNode(unref(BracesIcon))]),
384
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(words.value.toText), 1)]),
385
+ _: 1
386
+ }, 8, ["disabled"])) : (openBlock(), createBlock(unref(DouTooltip), {
387
+ key: 2,
388
+ content: words.value.notRows,
389
+ disabled: canUseRows.value
390
+ }, {
391
+ default: withCtx(() => [createVNode(unref(DouButton), {
392
+ size: "sm",
393
+ variant: "ghost",
394
+ class: "dou-form-builder__condition-mode",
395
+ disabled: __props.disabled || !canUseRows.value,
396
+ onClick: toRows
397
+ }, {
398
+ leading: withCtx(() => [createVNode(unref(FilterIcon))]),
399
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(words.value.toRows), 1)]),
400
+ _: 1
401
+ }, 8, ["disabled"])]),
402
+ _: 1
403
+ }, 8, ["content", "disabled"]))])) : createCommentVNode("", true)
404
+ ]);
405
+ };
406
+ }
407
+ });
408
+ export { condition_editor_vue_vue_type_script_setup_true_lang_default as default };
409
+
410
+ //# sourceMappingURL=condition-editor.vue_vue_type_script_setup_true_lang.js.map