{"_id":"@dreamonkey/vue-lx-forms","_rev":"4-ec794157218fc026befab26b4e611dcb","name":"@dreamonkey/vue-lx-forms","dist-tags":{"latest":"0.0.3"},"versions":{"0.0.1":{"name":"@dreamonkey/vue-lx-forms","version":"0.0.1","keywords":["vue","typescript","form","forms","reactive","components","helix","lx","dynamic"],"author":{"url":"https://github.com/IlCallo","name":"Paolo Caleffi","email":"p.caleffi@dreamonkey.com"},"license":"MIT","_id":"@dreamonkey/vue-lx-forms@0.0.1","maintainers":[{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},{"name":"mrkappa","email":"k.borghi@dreamonkey.com"}],"homepage":"https://github.com/dreamonkey/vue-lx-forms#readme","bugs":{"url":"https://github.com/dreamonkey/vue-lx-forms/issues"},"dist":{"shasum":"4e7d621d01846da4d2af64befdabd6ad46b7a2ca","tarball":"https://registry.npmjs.org/@dreamonkey/vue-lx-forms/-/vue-lx-forms-0.0.1.tgz","fileCount":34,"integrity":"sha512-tjeD2eS8Gq1d2q4BZ0mHQuezQpZA8EDfsBhEkWmrV3SVnx/E1Ko3XF6VLjrPZemcsb70t++wNyueEQZf0gJq7A==","signatures":[{"sig":"MEYCIQDqDfnahreQ5TV8h1Vg+IjZ8Hih149KlQAXg53hsAAkigIhAIxE2XhmKznwZHet1S+SV2j9rrSMpILYkqCPZOPDI4De","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":44940,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/QtRCRA9TVsSAnZWagAAHv4P/1ZSQKQzOP7RWlqLNLnw\nb9CAGKFr5RJ6eNhgpU43xjBkQxzj0BfjbThYHrDb9qHlmoWuNNzI4jPrrm04\nh2iJe8+YdamHeZlJOVV7N3M1LI2CK2dooLx5EdrKIGZBECOC6EHfv7iQ1B/t\ndWOLlymKYIj2Y1apR1BMo+DuaLcnOUJD2gVDsiXezEIQyfGS3j/GmgY+PDEj\ne0L/KayiURyGcE/HShHfVBTaBCyo/Z4Ufg+bmXzMMWEnWng+euqNyTkxKgFq\ndFdzhh8q/AODlFo6zPz9Pkwh+PqQRsYgaGnti+7c0v9VPYjk68PDvZXA6LBA\n9EaVksKVZpxvZRcHUvReY4uznj8D7oCDw35x9ccosze9PAEDQTo3YyC42bft\ngzoCGvGa79DX1vItlyJHMvX1wy8wNSUtY4rFiwUkp4PnzVo9KEjpofJdq1Zz\nBdSflCL3T5F9mqHGOohg9AX7jj4b0lG0dWakKDYVXSK+UU5MTMwK1PbZHmwh\nmgE9gtptb0BUtXJQU7U0Flx0NMLFZz6RQ9pFeStulMyNBGHqISu0/q5TzkmX\nX88hoJHuxDSDKQGw7WM/lE9UhULzM4nUjqZ1XERqMNJ1C4w9GmkAdEpJHjKv\nSBtD6yHoky4qP82f7NZGTIsuWzvfG6aPpBs0k0qtmdwi7JEkW6mnTsaIXGDo\nBwTS\r\n=GfFX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","_from":"file:dreamonkey-vue-lx-forms-0.0.1.tgz","engines":{"npm":">= 6.14.12","node":">= 12.22.1","yarn":">= 1.17.3"},"scripts":{"lint":"eslint --ext .js,.ts,.vue ./ --fix --report-unused-disable-directives","build":"rimraf dist && tsc --declaration && copyfiles -f src/resolver.vue dist","deploy":"pnpm build && pnpm publish --tag latest","format":"prettier --write \"**/*.{json,md,graphql,vue,js,ts}\" --ignore-path .gitignore"},"_npmUser":{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},"_resolved":"","_integrity":"","repository":{"url":"git+https://github.com/dreamonkey/vue-lx-forms.git","type":"git"},"_npmVersion":"6.14.15","description":"Builder for highly reactive forms following a bring-your-components approach. Based on Vue reactivity, full TypeScript support","directories":{},"_nodeVersion":"12.22.9","dependencies":{"install":"^0.13.0","lodash-es":"^4.17.21"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.2.29","eslint":"^8.8.0","rimraf":"^3.0.2","prettier":"^2.5.1","copyfiles":"^2.4.1","typescript":"^4.5.5","@babel/types":"^7.17.0","@types/lodash-es":"^4.17.5","eslint-plugin-vue":"^8.4.0","eslint-config-prettier":"^8.3.0","@typescript-eslint/parser":"^5.10.2","@typescript-eslint/eslint-plugin":"^5.10.2"},"peerDependencies":{"vue":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vue-lx-forms_0.0.1_1643973457203_0.3114815217386946","host":"s3://npm-registry-packages"}},"0.0.2":{"name":"@dreamonkey/vue-lx-forms","version":"0.0.2","keywords":["vue","typescript","form","forms","reactive","components","helix","lx","dynamic"],"author":{"url":"https://github.com/IlCallo","name":"Paolo Caleffi","email":"p.caleffi@dreamonkey.com"},"license":"MIT","_id":"@dreamonkey/vue-lx-forms@0.0.2","maintainers":[{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},{"name":"mrkappa","email":"k.borghi@dreamonkey.com"}],"homepage":"https://github.com/dreamonkey/vue-lx-forms#readme","bugs":{"url":"https://github.com/dreamonkey/vue-lx-forms/issues"},"dist":{"shasum":"35226992760a6bbbbd8e05fe65b9e03e506318e9","tarball":"https://registry.npmjs.org/@dreamonkey/vue-lx-forms/-/vue-lx-forms-0.0.2.tgz","fileCount":34,"integrity":"sha512-gqwgi//ElZP+f62G6pq1vUw43yImpXLppKJA9To+QsfrEe1pTc011X3JXaYeoasgjdTPfPIkPENF/D9+zY44SA==","signatures":[{"sig":"MEQCIHIE97VdZomDiLo5kbs4u5iQqsvKkYKQSls54EaWhGG3AiBUi/fQZTRXyM2xNG6b/2zDkyoPryQnfgOfsT5BKTqxNw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":64237,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiA+s0CRA9TVsSAnZWagAAR6EP/jeJcYc3zIVaov8VidQv\n63N7BZQzyi4r+gvQn/RGI0oH+x6Em76EVe6XBG7ctIq9EmG9iD+diCIkbh8T\nJw9yRsBC6jA5x0jLXyVcvawcuF4P4Bb6qEfYokn/rPd1nh9nSs6EDE+/2+ha\n64eeIetHxvvQ3r+sfJ6nQF/sn0OzbSy1wNag9iXzWCF2a1giAaR73V5Xbz2S\nbFI4+5mGIrde9KD3W/QRRY8wVcWqp6hIp4Eoe6cD/FTkAw7W67YX/JFw+KrH\nFwnt68MGbFhP+5O4uJaThqroaRNqnaTdQ1JHjpLcMM5yUKvVoxwC6Eyn4PlU\ni2rnOmksrBaXBzRj6VDd15xPAZWZCaqGLGLkc2NV6X6Vp6HUP0eS1gVR/Zb9\n8NvigXFt3b/ZAkQWmeQC4w/iB1+6c9DF3qYCS1Iry5uR61OllnvhcmB7qNRm\niFUkeTItfcfNW8c5iSB0iHNa6zfvEyqf/xoLzMo29AeDADAwhg/o8DbaMszh\nc13Gp6YSJw/6T2HCAo5fxfmjx8rjicpUkAyrxPjUq88HWDI6V1AtZpO5nvXQ\nwtVUolzQhlYMY+igm9fFqYI1JKUEk7B1CFTDDrL5xiu044TnUDMo+mx1y5Tu\nGvs/sQxU3e33nznnTUj62zaIeG0y6SwRT+ioiakIV7E4jzyvQB9ul2RYzaHQ\nmyhn\r\n=ne6+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","_from":"file:dreamonkey-vue-lx-forms-0.0.2.tgz","engines":{"npm":">= 6.14.12","node":">= 12.22.1","yarn":">= 1.17.3"},"scripts":{"lint":"eslint --ext .js,.ts,.vue ./ --fix --report-unused-disable-directives","build":"rimraf dist && tsc --declaration && copyfiles -f src/resolver.vue dist","deploy":"pnpm build && pnpm publish --tag latest","format":"prettier --write \"**/*.{json,md,graphql,vue,js,ts}\" --ignore-path .gitignore"},"_npmUser":{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},"_resolved":"","_integrity":"","repository":{"url":"git+https://github.com/dreamonkey/vue-lx-forms.git","type":"git"},"_npmVersion":"6.14.15","description":"Builder for highly reactive forms following a bring-your-components approach. Based on Vue reactivity, full TypeScript support","directories":{},"_nodeVersion":"12.22.9","dependencies":{"install":"^0.13.0","lodash-es":"^4.17.21"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.2.29","eslint":"^8.8.0","rimraf":"^3.0.2","prettier":"^2.5.1","copyfiles":"^2.4.1","typescript":"^4.5.5","@babel/types":"^7.17.0","@types/lodash-es":"^4.17.5","eslint-plugin-vue":"^8.4.0","eslint-config-prettier":"^8.3.0","@typescript-eslint/parser":"^5.10.2","@typescript-eslint/eslint-plugin":"^5.10.2"},"peerDependencies":{"vue":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vue-lx-forms_0.0.2_1644423988009_0.7471160702611577","host":"s3://npm-registry-packages"}},"0.0.3":{"name":"@dreamonkey/vue-lx-forms","version":"0.0.3","type":"module","description":"Builder for highly reactive forms following a bring-your-components approach. Based on Vue reactivity, full TypeScript support","main":"dist/index.js","exports":"./dist/index.js","types":"dist/index.d.ts","repository":{"type":"git","url":"git+https://github.com/dreamonkey/vue-lx-forms.git"},"keywords":["vue","typescript","form","forms","reactive","components","helix","lx","dynamic"],"author":{"name":"Paolo Caleffi","email":"p.caleffi@dreamonkey.com","url":"https://github.com/IlCallo"},"license":"MIT","bugs":{"url":"https://github.com/dreamonkey/vue-lx-forms/issues"},"homepage":"https://github.com/dreamonkey/vue-lx-forms#readme","publishConfig":{"access":"public"},"dependencies":{"es-toolkit":"^1.43.0"},"devDependencies":{"@babel/types":"^7.28.5","copyfiles":"^2.4.1","eslint":"^9.39.1","eslint-config-coralloy":"^0.7.1","prettier":"^3.7.4","rimraf":"^6.1.2","typescript":"^5.9.3","vue":"^3.5.25"},"peerDependencies":{"vue":"^3.0.0"},"engines":{"node":"^22.18"},"scripts":{"lint":"eslint --cache --fix","format":"prettier --write \"**/*.{json,md,graphql,vue,js,ts}\" --ignore-path .gitignore","build":"rimraf dist && tsc --declaration && copyfiles -f src/resolver.vue dist","deploy":"pnpm build && pnpm publish --tag latest"},"_id":"@dreamonkey/vue-lx-forms@0.0.3","_integrity":"sha512-P5oWoeKnPDbJTsHpdceD+a3xgb2WeSstwxEeySqFRdVnUEIBxaMJVTGAbJHNOa8LN9x+qfMA2N1ra2Cjs5Qn1w==","_resolved":"/tmp/0c86cd9dea55bed55942e9091c6318d5/dreamonkey-vue-lx-forms-0.0.3.tgz","_from":"file:dreamonkey-vue-lx-forms-0.0.3.tgz","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-P5oWoeKnPDbJTsHpdceD+a3xgb2WeSstwxEeySqFRdVnUEIBxaMJVTGAbJHNOa8LN9x+qfMA2N1ra2Cjs5Qn1w==","shasum":"cb62ac708bd3480619a2eb2d6ee5d76d050c374f","tarball":"https://registry.npmjs.org/@dreamonkey/vue-lx-forms/-/vue-lx-forms-0.0.3.tgz","fileCount":34,"unpackedSize":43359,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICgf04Rqsn+MfEfY7oXASG3SWnEP96871niyo5qACl1SAiEAqAokYf0FoFh6GwyVcV/zzT7vfE9GX4VuEPuKSrnBIkw="}]},"_npmUser":{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},"directories":{},"maintainers":[{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},{"name":"mrkappa","email":"k.borghi@dreamonkey.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vue-lx-forms_0.0.3_1765545581595_0.12466409652038646"},"_hasShrinkwrap":false}},"time":{"created":"2022-02-04T11:17:37.141Z","modified":"2025-12-12T13:19:41.947Z","0.0.1":"2022-02-04T11:17:37.356Z","0.0.2":"2022-02-09T16:26:28.138Z","0.0.3":"2025-12-12T13:19:41.735Z"},"bugs":{"url":"https://github.com/dreamonkey/vue-lx-forms/issues"},"author":{"name":"Paolo Caleffi","email":"p.caleffi@dreamonkey.com","url":"https://github.com/IlCallo"},"license":"MIT","homepage":"https://github.com/dreamonkey/vue-lx-forms#readme","keywords":["vue","typescript","form","forms","reactive","components","helix","lx","dynamic"],"repository":{"type":"git","url":"git+https://github.com/dreamonkey/vue-lx-forms.git"},"description":"Builder for highly reactive forms following a bring-your-components approach. Based on Vue reactivity, full TypeScript support","maintainers":[{"name":"ilcallo","email":"p.caleffi@dreamonkey.com"},{"name":"mrkappa","email":"k.borghi@dreamonkey.com"}],"readme":"# Vue LX Forms\n\nIt reads as \"Vue Helix Forms\", named after the DNA helix from which we borrow some concepts.\n\nWe start from a declarative configuration (the DNA helix, mapping genetic instructions to biological \"features\") of generic \"descriptors\" (the nucleobases), then collect user defined bindings with components (the complementary bases) meant to render them.  \n`lx-resolver` component (acting as the RNA primer) accepts the configuration as input and, sewing descriptors and components together, render the form (which represent the \"biological result\" encoded into the DNA).  \nThe user can mutate the internal state via form fields (environment-induced DNA mutations), the configuration will then adapt to these changes and show/hide form components accordingly.\n\nTechnically speaking, this is a form builder following a bring-your-components approach, but is flexible and extensible enough to render any kind of state driven component tree.\nIt shines when used for complex fields configurations with many business rules interconnecting fields visibility with the underlying state, while it may be overkill for simpler scenarios.\n\nThe whole system is strongly typed.\n\n## Installation\n\n```sh\n$ yarn add @dreamonkey/vue-lx-forms\n```\n\n```ts\nimport { LxForms, type Binding } from '@dreamonkey/vue-lx-forms';\n\nconst bindings: Binding[] = [\n  // ... bindings!\n];\n\n// Vue CLI/Vite project\nimport { createApp } from 'vue';\n\nconst app = createApp({});\n\napp.use(LxForms, bindings);\n\n// Quasar CLI project (using boot files)\nimport { boot } from 'quasar/wrappers';\n\nexport default boot(({ app }) => {\n  app.use(LxForms, bindings);\n});\n```\n\n## Usage\n\nHere's a guide showing how you can use the whole system.\n\n### Define a descriptor type\n\nEven if you can use strings too, we encurage you to use enums when possible as it helps to better manage namespaces in case you need to use the system for multiple fields sets, especially if they share components or descriptors.\n\n```ts\n// models.ts\nexport enum OrdersDescriptorType {\n  Text = 'Text',\n}\n```\n\n### Define and register the descriptor interface (TS-only)\n\n```ts\n// descriptors.ts\nimport { SimpleDescriptor } from '@dreamonkey/vue-lx-forms';\nimport { OrdersDescriptorType } from './models';\n\nexport type TextDescriptor = SimpleDescriptor<\n  OrdersDescriptorType.Text,\n  string\n>;\n\ndeclare module '@dreamonkey/vue-lx-forms' {\n  interface DescriptorMap {\n    [OrdersDescriptorType.Text]: TextDescriptor;\n  }\n}\n```\n\n### Create the component\n\nEach descriptor type must have exactly one component registered to render it, except when using `descriptor.component` override option.\nViceversa, a component may be used to render multiple descriptors types.\nNote that a single descriptor can be shared by multiple descriptor types too.\n\n```vue\n<!-- text.vue -->\n<script lang=\"ts\">\nimport {\n  extractDescriptorModel,\n  getDescriptorProps,\n} from '@dreamonkey/vue-lx-forms';\nimport { defineComponent } from 'vue';\nimport { TextDescriptor } from './descriptors';\n\nexport default defineComponent({\n  name: 'TextField',\n  inheritAttrs: false,\n  props: getDescriptorProps<TextDescriptor>(),\n  setup(props) {\n    // Never use `descriptor.model` property directly, extract it using `extractDescriptorModel` helper\n    const model = extractDescriptorModel(props.descriptor);\n    return { model };\n  },\n});\n</script>\n\n<template>\n  <label>\n    {{ descriptor.label }}\n    <input v-model=\"model\" type=\"text\" v-bind=\"$attrs\" />\n  </label>\n</template>\n```\n\n### Bind a descriptor type to a component component\n\n```ts\n// bindings.ts\nimport { registerDescriptor, Binding } from '@dreamonkey/vue-lx-forms';\nimport { OrdersDescriptorType } from './models';\nimport TextField from './text.vue';\n\nexport const binding: Binding = {\n  type: OrdersDescriptorType.Text,\n  component: TextField,\n};\n\n// You can skip this if you provide all bindings\n// as the second parameter of `app.use(LxForms, bindings)`\nregisterDescriptor(binding);\n```\n\n### Define the configuration\n\nProvide the initial state and the descriptor list, you'll obtain the configuration, its related result object, as well as the inner reactive state in case you need to tamper with it from outside the system.\n\n```ts\n// configuration.ts\nimport { createDescriptor, useLxForms } from '@dreamonkey/vue-lx-forms';\nimport { FormFieldType } from './models';\n\n// You must define all properties which will be used, even if set to undefined,\n// to let the system know it needs to generate a matching ref for them\nconst orderInitialData = {\n  id: 1,\n  username: 'XXXX-000',\n  food: undefined,\n  details: undefined,\n};\n\nconst { configuration, result, state } = useLxForms(\n  orderInitialData,\n  // Every property of \"stateRefs\" contains a ref initialized with the matching property of the initial state\n  (stateRefs) => [\n    createDescriptor({\n      type: FormFieldType.Text,\n      model: stateRefs.username,\n      label: 'Insert your username',\n    }),\n    createDescriptor({\n      type: FormFieldType.Text,\n      model: stateRefs.food,\n      label: 'What do you want to eat?',\n    }),\n    // Only show the \"details\" when the \"food\" is initialized\n    createConditional(\n      () => stateRefs.food.value !== undefined,\n      createDescriptor({\n        type: FormFieldType.Text,\n        model: stateRefs.details,\n        label: 'Any details for the cook?',\n      }),\n    ),\n  ],\n);\n\n// Note that the result computed ref will only contain matching properties for used descriptors,\n// while state is a reactive object containing all properties regardless of the current configuration\n// >> result.value => { username: 'XXXX-000', food: undefined }\n// >> state => { id: 1, username: 'XXXX-000', food: undefined, details: undefined }\n\n// You can use \"state\" to manually tamper with the underlying data from outside the system\nstate.food = 'Lasagna';\n// \"details\" is now available, since \"food\" is defined\n// >> result.value => { username: 'XXXX-000', food: 'Lasagna', details: undefined }\nstate.details = 'No cheese please';\n// >> result.value => { username: 'XXXX-000', food: 'Lasagna', details: 'No cheese please' }\nstate.food = undefined;\n// \"details\" is now not available, since \"food\" is undefined, even tho its previously set value is retained\n// >> result.value => { username: 'XXXX-000', food: undefined }\nstate.food = 'Pasta alla carbonara';\n// \"details\" is now available again, since \"food\" is defined, and it retained its previously set value\n// >> result.value => { username: 'XXXX-000', food: 'Pasta alla carbonara', details: 'No cheese please' }\n\nexport const { ordersFields: configuration, order: result };\n```\n\n### Render fields and use the result\n\n```vue\n<!-- form.vue -->\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { ordersFields, order } from './configuration';\n\nexport default defineComponent({\n  name: 'OrderForm',\n  setup(props) {\n    function logOrder() {\n      console.log(order.value);\n    }\n\n    return { ordersFields, logOrder };\n  },\n});\n</script>\n\n<template>\n  <form @submit=\"logOrder\">\n    <lx-resolver\n      v-for=\"descriptor in ordersFields\"\n      :key=\"descriptor.id\"\n      :descriptor=\"descriptor\"\n    />\n\n    <input type=\"submit\" value=\"Send order\" />\n  </form>\n</template>\n```\n\n## Core concepts\n\n### Descriptors\n\nDescriptors are the building blocks of the whole system.\nIdeally, each descriptor should hold all information bits and GUI-independent code which will later be needed by a component when rendering it as part of the whole form.\n\nIdeally, a descriptor should not care about the GUI-related code and stick to higher level abstractions, as updating bindings to use different sets of components should result in different GUIs without needing changes to the descriptors.\n\nAt its bare minimul, each descriptor must have:\n\n- an `id`, used by Vue to distinguish between `LxResolver` instances, which is automatically filled in when using `createDescriptor`;\n- a `type`, used by `LxResolver` to decide which component to render, which can be a simple string, an enum or even a symbol;\n- a `label`, since almost all fields of a form always have a title or label of some kind;\n- a `model`, which must be a reactive ref, even if initialized to `undefined`.\n\nYou can also provide a custom `component` option to override/manually specify which component should be used to render the descriptor.\n\nYou should bind descriptor types to a component using `registerDescriptor`, `registerDescriptors` or the second argument of the plugin installation function.\n\n```ts\nconst binding: Binding = {\n  type: 'text',\n  component: TextField,\n};\n\nconst bindings: Binding[] = [\n  {\n    type: 'select',\n    component: SelectField,\n  },\n  {\n    type: 'checkbox',\n    component: CheckboxField,\n  },\n];\n\n// Register a single descriptor\nregisterDescriptor(binding);\n\n// Register multiple descriptors\nregisterDescriptors(bindings);\n\n// Register multiple descriptors when installing the plugin\napp.use(LxForms, bindings);\n```\n\nIt's fine to have multiple descriptors types bound to a single component, provided that it's able to manage all of them correctly.\nEg. text, textarea and password descriptor types can usually be managed by the same component.\n\n```ts\nconst binding: Binding = {\n  type: ['text', 'textarea', 'password'],\n  component: TextLikeField,\n};\n\nregisterDescriptor(binding);\n```\n\nIf you find yourself in need to create descriptors dynamically, share the same descriptor options between multiple instances, or define them at a time where the underlying reactive state doesn't exist yet, you can use **descriptor factories** patter.\nThis pattern consist into wrapping the descriptor creation code into a wrapper function (the factory) which will then accept an object containing state refs later on, to create the actual instance of the descriptor.\n\n```ts\nimport {\n  createDescriptor,\n  DescriptorFactoryFn,\n} from '@dreamonkey/vue-lx-forms';\n\nconst initialState = {\n  username: undefined,\n  food: undefined,\n};\n\nconst coldDescriptorList: DescriptorFactoryFn[] = [\n  (stateRefs) => {\n    return createDescriptor({\n      type: 'text',\n      label: 'Insert username',\n      model: stateRefs.username,\n    });\n  },\n  (stateRefs) => {\n    return createDescriptor({\n      type: 'text',\n      label: 'Insert favourite food',\n      model: stateRefs.food,\n    });\n  },\n];\n\nconst { configuration, result, state } = useLxForms(initialState, (stateRefs) =>\n  coldDescriptorList.map((descriptorFactory) => descriptorFactory(modelRefs)),\n);\n```\n\n#### TypeScript support\n\nYou define a new descriptor interface by extending `BaseDescriptor`, providing an unique `type` value and the type of the model used by the descriptor.\nAll additional properties are considered type-related options.\n\n```ts\ninterface SelectDescriptor\n  // Fields rendered by this descriptor know the model must be read ad written as a string or undefined (the latter is implicit, all models can be undefined)\n  extends BaseDescriptor<string> {\n  type: 'select';\n  lazyOptionsFn: () => Promise<string[]>; // Type-related option, will be used by the component to retrieve the select options\n}\n```\n\nIf your descriptor don't have any type-related option, you can use `SimpleDescriptor` instead.\n\n```ts\ntype TextDescriptor = SimpleDescriptor<'text', string>;\n```\n\nIt's perfectly fine to have more than a descriptor type for a single descriptor, as long as all types share the same type-related options.\n\n```ts\ntype TextLikeDescriptor = SimpleDescriptor<\n  'text' | 'textarea' | 'password',\n  string\n>;\n```\n\nIf all your descriptors share common options, you can add them augmenting `CustomBaseDescriptorProperties` interface.\n\n```ts\nimport '@dreamonkey/vue-lx-forms';\n\ndeclare module '@dreamonkey/vue-lx-forms' {\n  interface CustomBaseDescriptorProperties {\n    required: boolean; // Every descriptor MUST have this property\n    placeholder?: string; // Every descriptor MAY have this property\n  }\n}\n```\n\nOnce you defined all your descriptors interfaces, you'll need to augment `DescriptorMap` interface to map each descriptor type to its descriptor interface. Once you did this, TypeScript will use `type` value to provide autocompletion when creating descriptors using `createDescriptor` and when registering bindings.\nWe hope to be able to automate this step in the future.\n\n```ts\nimport '@dreamonkey/vue-lx-forms';\n\ndeclare module '@dreamonkey/vue-lx-forms' {\n  interface DescriptorMap {\n    select: SelectDescriptor;\n    text: TextDescriptor;\n  }\n}\n```\n\nDescriptors interfaces are also useful to provide autocompletion into components, providing them as type parameter to `getDescriptorProps`, as you can see in next section example.\n\n### Components\n\nSince most of a field logic is stored into the descriptor, you can easily switch between different component sets just by changing bindings, but a descriptor is useless without a paired component able to render it.\n\nAll components you hook to the system must accept a `descriptor` prop and, if you use it in any way, extract `model` property from the descriptor. This last bit should happen outside Vue reactivity system, to avoid uncorrect unwrapping.\n\nUse `getDescriptorProps` to accomplish the first task.\nTo get proper autocompletion, provide via the type parameter the interfaces of all descriptors that the component is able to manage.\n\nFor the latter task, use `extractDescriptorModel` instead.\nIt accepts a descriptor and returns its `model` property, correctly extracted outside of Vue reactivity system.\nThis happens since we're accessing a property on a prop (which is a reactive object), and that property is a ref itself.\n**Never use `model` property directly from `descriptor` prop (eg. via `descriptor.model` inside templates), as it simply won't work as you expect, breaking the app.**\n\n```vue\n<!-- text.vue -->\n<script lang=\"ts\">\nimport {\n  extractDescriptorModel,\n  getDescriptorProps,\n} from '@dreamonkey/vue-lx-forms';\nimport { defineComponent } from 'vue';\nimport { TextDescriptor, PasswordDescriptor } from './descriptors';\n\nexport default defineComponent({\n  name: 'TextField',\n  props: getDescriptorProps<TextDescriptor | PasswordDescriptor>(),\n  setup(props) {\n    const model = extractDescriptorModel(props.descriptor);\n\n    // Thanks to the specified descriptors interfaces, `props.descriptor` have autocomplete for\n    // all type-related options if you use `type` as discriminant for the union\n    if (props.descriptor.type === 'text') {\n      // ... text-specific actions\n    } else {\n      // ... password-specific actions\n    }\n\n    return { model };\n  },\n});\n</script>\n\n<template>\n  <label>\n    {{ descriptor.label }}\n    <input v-model=\"model\" type=\"text\" />\n  </label>\n</template>\n```\n\nTo allow props pass-through to nested elements, add `inheritAttrs: false` to the component and `v-bind` its `$attrs` on the input element.\n\n```vue\n<script lang=\"ts\">\nexport default defineComponent({\n  inheritAttrs: false,\n  // ... other options\n});\n</script>\n\n<template>\n  <label>\n    {{ descriptor.label }}\n    <input v-model=\"model\" type=\"text\" v-bind=\"$attrs\" />\n  </label>\n</template>\n```\n\n### Internal state\n\nEach `Descriptor` uses a reactive variable to store the data provided by the user, which is actually an hook to property of a reactive shared state object.\nThe shared state is generated from the initial state you provide to `useLxForms`, thus which properties you define there is important: always initialize optional properties to `undefined` if you need the system to react to changes on them.\n\nThe reactive state is returned by `useLxForms` as `state` so you can tamper with it programmatically.\nNote that ideally `state` should only be mutated indirectly via models provided to each descriptor, and we only provide it as an escape hatch for complex scenarios.\nTake care if you find yourself tampering the `state` directly often, are you're probably using the system in the wrong way.\n\nSince `state` is a very generic name, we suggest you to always rename it to make it clear of which entity that state is holding data, keeping `State` suffix to let devs know it's the low level reactive object.\n\n```ts\nconst { state: orderState } = useLxForms(/* ... */);\n\norderState.food = 'Pizza';\n```\n\n### Configuration\n\n`useLxForms` expects a function as its second parameter, which gets in input an object of refs bound to the internal state and should return an array where each item can _recursively_ be:\n\n- a descriptor;\n- an array of descriptors;\n- a ref resolving to a descriptor or an array of descriptors.\n\nThe `configuration` returned from `useLxForms` is a computed which is based on that function, but where all reactive refs along the way are recursively unwrapped and all arrays flattened, to get a flat array of descriptors. This avoids many problems connected with the usage of recursive components and makes it really easy to render the configuration.\nSince we unwrap all refs and it's executed inside a computed body, the configuration will react to changes in any ref accessed into it.\n\nThis allows you to create highly reactive forms, showing or hiding fields or groups of fields depending on the value of either an outside ref or one of the provided state-related refs.\n\n```ts\nimport { createDescriptor, useLxForms } from '@dreamonkey/vue-lx-forms';\nimport { computed } from 'vue';\n\nconst orderInitialData = {\n  username: 'XXXX-000',\n  food: undefined,\n  drink: undefined,\n  details: undefined,\n};\n\nconst { configuration, state } = useLxForms(\n  orderInitialData,\n  // Every property of \"stateRefs\" contains a ref initialized with the matching property of the initial state\n  (stateRefs) => [\n    // Single descriptor\n    createDescriptor({\n      type: FormFieldType.Text,\n      model: stateRefs.username,\n      label: 'Insert your username',\n    }),\n    // Array of descriptors\n    [\n      createDescriptor({\n        type: FormFieldType.Text,\n        model: stateRefs.food,\n        label: 'What do you want to eat?',\n      }),\n\n      // Reactive ref of some kind\n      // Equal to \"createConditional\"\n      computed(() =>\n        stateRefs.food.value !== undefined\n          ? createDescriptor({\n              type: FormFieldType.Text,\n              model: stateRefs.details,\n              label: 'Any details for the cook?',\n            })\n          : [],\n      ),\n    ],\n    // Nested array of descriptors\n    [\n      [\n        createDescriptor({\n          type: FormFieldType.Text,\n          model: stateRefs.drink,\n          label: 'What do you want to drink?',\n        }),\n      ],\n    ],\n  ],\n);\n\n// >> configuration.value => [\n//   { /* username descriptor */ },\n//   { /* food descriptor */ },\n//   { /* drink descriptor */ },\n// ]\n\nstate.food = 'Lasagna';\n\n// >> configuration.value => [\n//   { /* username descriptor */ },\n//   { /* food descriptor */ },\n//   { /* details descriptor */ },\n//   { /* drink descriptor */ },\n// ]\n```\n\n### Transformers\n\nWe already covered how you can use conditional logic into a configuration to display or hide descriptors, and how you can extract it to helper functions as `createConditional`.\n\nHowever, sometimes the conditional logic is strictly related to a descriptor and it would be bothersome or not possible to apply the descriptor and an helper function together all the times.\nTo cover this use case you can use transformers.\n\nA transformer is a function that accepts a descriptor as input and returns either a descriptor, an array of descriptors or a computed ref containing either.\n\nThe build-in [binary descriptor](https://github.com/dreamonkey/vue-lx-forms/blob/main/src/transformers/binary.ts) is a good example of how you can use this feature.\n\nYou can register a transformer when registering bindings for a particular descriptor type.\n`createDescriptor` will automatically execute it whenever a match is found.\n\n```ts\nconst binding: Binding = {\n  type: 'binary',\n  component: BinaryField,\n  transformer: binaryTransformer,\n};\n\nregisterDescriptor(binding);\n```\n\n### LxResolver\n\nOnce you got the whole system set up, and you generated a configuration, you need to render that configuration.\n`LxResolver` component does just that: when provided with a descriptor, it resolves the components based on the descriptor type and your bindings, then render it providing the descriptor as prop.\n\nSince the configuration is flat, a simple `v-for` is what you need to show all fields of the configuration.\n\n```vue\n<template>\n  <lx-resolver\n    v-for=\"descriptor in configuration\"\n    :key=\"descriptor.id\"\n    :descriptor=\"descriptor\"\n  />\n</template>\n```\n\n### Result\n\nWhenever you need to extract the current configuration data, you should use the `result` computed property provided by `useLxForms`.\nYou can think of `result` as a cleaned up version of `state`, where the data for all unused descriptors is left aside.\n\nHere're the main differences between `result` and `state`:\n\n- `result` is a computed ref, thus it's readonly and its value should be accessed via `result.value`, while `state` is a writable reactive object;\n- `result` will only contain properties bound to displayed fields, while `state` contains all properties present in the initial object;\n\nSince `result` is a very generic name, we suggest you to always rename it to make it clear of which entity you're representing an instance.\n\n```ts\nconst { result: order } = useLxForms(/* ... */);\n\nconsole.log(order.value); // { food: 'Pizza', ... }\n```\n\n## Caveats and pitfalls\n\n**Never use a method generating a new descriptor object INSIDE a computed function body**\nIt will result in a new descriptor being created every time the computed property re-evaluate\nand could cause an infinite recursion loop\nUse `createConditional` helper instead\n\n<!--\nTODO:\n\n## Component helpers\n\n### `getDescriptorProps`\n\nMust be a function as we need to type it\n\n### `extractDescriptorModel`\n\n### `registerDescriptors`\n\nRegister many descriptor bindings\nCan register multiple descriptor types for the same binding\nYou can call this from where you want and as many time you want, even at runtime\nThe binding registry is a singleton\nThe last registered binding wins in case of conflict\n\n### `registerDescriptor`\n\nShorthand to call `registerDescriptors` with a single descriptor binding\n\n### `getBindingByDescriptorType`\n\nGet the binding registered for a given descriptor type\nThis can be useful when doing advanced meta programming\n\n## Configuration helpers\n\n### `useLxForms`\n\nGets initial model and the descriptors list, returns configuration, readonly result object and reactive state (for manual tampering)\n\n### `createDescriptor`\n\n### `createConditional`\n\n## Built-in transformers\n\n### Binary\n\n### Select\n\n## Built-in behaviours\n\n### Disabled\n\n### Hidden\n\n### Required\n\n## Common use cases\n\n### Static configuration\n\n### API generated configuration\n\n### Apply a behaviour to all descriptors\n\n### Descriptors and components creation\n\nMake it work > make it fast > make it beautiful\n\nWhen having trouble abstracting it into the descriptor from the start, write everything into the component, then split concerns into composables and use custom descriptor descriptors properties or global properties to abstract it\n\nAt the end of the process, components should contain only the logic needed to display and interact with descriptor, not the logic\n-->\n","readmeFilename":"README.md"}