{"_id":"@lit-labs/forms","name":"@lit-labs/forms","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@lit-labs/forms","version":"0.1.0","description":"Form helpers for Lit","license":"BSD-3-Clause","homepage":"https://lit.dev/","repository":{"type":"git","url":"git+https://github.com/lit/lit.git","directory":"packages/labs/forms"},"type":"module","main":"index.js","module":"index.js","typings":"index.d.ts","directories":{"test":"test"},"exports":{".":{"types":"./development/index.d.ts","development":"./development/index.js","default":"./index.js"}},"scripts":{"build":"wireit","build:ts":"wireit","build:ts:experimental-decorators-tests":"wireit","build:ts:types":"wireit","build:rollup":"wireit","test":"wireit","test:dev":"wireit","test:prod":"wireit"},"wireit":{"build":{"dependencies":["build:ts","build:ts:experimental-decorators-tests","build:ts:types","build:rollup","../../lit:build"]},"build:ts":{"command":"tsc --build --pretty","dependencies":["../../lit:build:ts:types","../../reactive-element:build:ts:types"],"clean":"if-file-deleted","files":["src/**/*.ts","!src/test/experimental-decorators","tsconfig.json"],"output":["development","!development/test/experimental-decorators","tsconfig.tsbuildinfo"]},"build:ts:experimental-decorators-tests":{"#comment":"This is a separate script from build:ts because it needs a tsconfig with experimentalDecorators.","command":"tsc --pretty --project tsconfig.experimental-decorators-tests.json","clean":"if-file-deleted","dependencies":["build:ts"],"files":["src/test/experimental-decorators/**/*.ts","tsconfig.experimental-decorators-tests.json"],"output":["development/test/experimental-decorators","tsconfig.experimental-decorators-tests.tsbuildinfo"]},"build:ts:types":{"command":"treemirror development . \"**/*.d.ts{,.map}\"","dependencies":["../../internal-scripts:build","build:ts"],"files":[],"output":["*.d.ts{,.map}","lib/**/*.d.ts{,.map}","test/**/*.d.ts{,.map}"]},"build:rollup":{"command":"rollup -c","dependencies":["build:ts"],"files":["rollup.config.js","../../../rollup-common.js"],"output":["index.js{,.map}","lib/**/*.d.ts{,.map}","test/**/*.js{,.map}"]},"test":{"dependencies":["test:dev","test:prod"]},"test:dev":{"command":"MODE=dev node ../../tests/run-web-tests.js \"development/**/*_test.js\" --config ../../tests/web-test-runner.config.js","dependencies":["build","../../tests:build"],"env":{"BROWSERS":{"external":true}},"files":[],"output":[]},"test:prod":{"command":"MODE=prod node ../../tests/run-web-tests.js \"development/**/*_test.js\" --config ../../tests/web-test-runner.config.js","dependencies":["build","../../tests:build"],"env":{"BROWSERS":{"external":true}},"files":[],"output":[]}},"author":{"name":"Google LLC"},"devDependencies":{"@lit-internal/scripts":"^1.0.1","lit":"^3.0.0"},"dependencies":{"@lit/reactive-element":"^2.0.0"},"publishConfig":{"access":"public"},"gitHead":"0fc671bc390cac3b9900fc3616347eeeecd91d5a","_id":"@lit-labs/forms@0.1.0","bugs":{"url":"https://github.com/lit/lit/issues"},"_nodeVersion":"24.7.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-o2SGYJ+bFdoba4xmsWXh+mgDVdSIxc5sZm7c6nWM2al1wczXMn/B1SxwKBRr7B4m1UgcDEzCOOPrcMRXV3jhPw==","shasum":"4f8e0afe7d46be3a5e5fd4a152134239b30358e2","tarball":"https://registry.npmjs.org/@lit-labs/forms/-/forms-0.1.0.tgz","fileCount":19,"unpackedSize":92858,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDe17eGVRyoxMSEGm9cnfXvR92ZOJuD5nEGl6H5dqTB0wIhAM35pNk9/V20C0RT3LTd+IYnWYhnhLDDoy+jUOhlfFz7"}]},"_npmUser":{"name":"justinfagnani","email":"justin@fagnani.com"},"maintainers":[{"name":"sorvell","email":"sorvell@google.com"},{"name":"aomarks","email":"aomarks@gmail.com"},{"name":"justinfagnani","email":"justin@fagnani.com"},{"name":"kevinpschaaf","email":"kevinpschaaf@gmail.com"},{"name":"jbingham","email":"jb@jbingham.com"},{"name":"graynorton","email":"gray@stanfordalumni.org"},{"name":"rictic","email":"rictic@gmail.com"},{"name":"graysnorton","email":"graynorton@google.com"},{"name":"lit-robot","email":"lit-robot@google.com"},{"name":"usergenic","email":"brendan@usergenic.com"},{"name":"augustjk","email":"ajk830@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/forms_0.1.0_1766528787333_0.2952220611770817"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-23T22:26:27.186Z","0.1.0":"2025-12-23T22:26:27.486Z","modified":"2025-12-23T22:26:28.137Z"},"maintainers":[{"name":"sorvell","email":"sorvell@google.com"},{"name":"aomarks","email":"aomarks@gmail.com"},{"name":"justinfagnani","email":"justin@fagnani.com"},{"name":"kevinpschaaf","email":"kevinpschaaf@gmail.com"},{"name":"jbingham","email":"jb@jbingham.com"},{"name":"graynorton","email":"gray@stanfordalumni.org"},{"name":"rictic","email":"rictic@gmail.com"},{"name":"graysnorton","email":"graynorton@google.com"},{"name":"lit-robot","email":"lit-robot@google.com"},{"name":"usergenic","email":"brendan@usergenic.com"},{"name":"augustjk","email":"ajk830@gmail.com"}],"description":"Form helpers for Lit","homepage":"https://lit.dev/","repository":{"type":"git","url":"git+https://github.com/lit/lit.git","directory":"packages/labs/forms"},"author":{"name":"Google LLC"},"bugs":{"url":"https://github.com/lit/lit/issues"},"license":"BSD-3-Clause","readme":"# @lit-labs/forms\n\nA library for building [form-associated custom\nelements](https://web.dev/articles/more-capable-form-controls) with Lit.\n\n[![Build Status](https://github.com/lit/lit/workflows/Tests/badge.svg)](https://github.com/lit/lit/actions?query=workflow%3ATests)\n[![Published on npm](https://img.shields.io/npm/v/@lit-labs/forms.svg?logo=npm)](https://www.npmjs.com/package/@lit-labs/forms)\n[![Join our Discord](https://img.shields.io/badge/discord-join%20chat-5865F2.svg?logo=discord&logoColor=fff)](https://lit.dev/discord/)\n\n> [!WARNING]\n>\n> This package is part of [Lit Labs](https://lit.dev/docs/libraries/labs/). It\n> is published in order to get feedback on the design and may receive breaking\n> changes or stop being supported.\n>\n> Please read our [Lit Labs documentation](https://lit.dev/docs/libraries/labs/)\n> before using this library in production.\n\n## Installation\n\nFrom inside your project folder, run:\n\n```bash\n$ npm install @lit-labs/forms\n```\n\nForm-associated custom elements are web components that participate in form\nsubmission, validation, and state restoration, just like built-in form elements.\n`@lit-labs/forms` helps builds correct form-associated custom elements by\nsetting the proper ARIA roles, form value, disabled state, and more.\n\nThis package provides:\n\n- [`FormControl`](#formcontrol-mixin): A class mixin that makes it easy to build\n  form associated custom elements that implement the standard form control APIs.\n- [`FormAssociated`](#formassociated-mixin): A lower-level class mixin that only\n  helps with form association, but doesn't add any public APIs.\n- [Decorators](#formvalue): Decorators for binding properties to form values and\n  state.\n\n## `FormControl` mixin\n\nThe `FormControl` mixin adds standard form-related public API properties and\nmethods to a custom element. It also enables the use of decorators that let\ndevelopers easily bind class fields to the form value and manage form state for\nrestoration.\n\n> [!NOTE]\n>\n> `FormControl` extends the `FormAssociated` mixin, which handles the\n> ElementInternals integration and enables the decorators, but doesn't add any\n> public API to the class.\n>\n> It's recommended that most developers use `FormControl` instead of\n> `FormAssociated` in order to add the idiomatic API that users expect from form\n> controls (like `.disabled`, `.form`, `.checkValidity()`, etc.). Use\n> `FormAssociated` only if you need to implement a completely custom API\n> surface.\n\n### Example\n\n```ts\nimport {LitElement, html} from 'lit';\nimport {customElement} from 'lit/decorators.js';\nimport {FormControl, formValue} from '@lit-labs/forms';\n\n@customElement('simple-input')\nexport class SimpleInput extends FormControl(LitElement) {\n  @formValue()\n  accessor value = '';\n\n  override render() {\n    return html`\n      <input\n        .value=${this.value}\n        @input=${(e: Event) =>\n          (this.value = (e.target as HTMLInputElement).value)}\n      />\n    `;\n  }\n}\n```\n\nThe added fields and methods mimic native form controls, providing a familiar\nAPI:\n\n- `form`: a readonly property that returns the associated form element.\n- `labels`: a readonly property that returns the labels associated with the\n  element.\n- `name`: a read/write property that reflects the `name` attribute.\n- `disabled`: a read/write property that reflects the `disabled` attribute.\n- `validity`: a readonly property that returns the `ValidityState` of the\n  element.\n- `validationMessage`: a readonly property that returns the error message that\n  would be shown to the user if the element was to be checked for validity.\n- `willValidate`: a readonly property that returns `true` if internals' target\n  element will be validated when the form is submitted. For example, disabled or\n  hidden elements are not validated.\n- `checkValidity()`: If the element is invalid, returns `false` and fires an\n  `invalid` event.\n- `reportValidity()`: Like `checkValidity()` but also shows the validation\n  message to the user.\n\n## `FormAssociated` mixin\n\nThe `FormAssociated` mixin helps define a form-associated custom element.\n\n`FormAssociated` creates a new form-associated base class and implements a\nnumber of best-practice behaviors for form-associated elements:\n\n- Sets the element's ARIA role using `internals.role`.\n- Updates the element's form value and state using `internals.setFormState()`.\n- Saves the initial form value and state for use with form reset and restore.\n- Implements form reset via `formResetCallback()`.\n- Implements form state restoration via `formStateRestoreCallback()`.\n- Syncs the disabled state to `internals.ariaDisabled` via\n  `formDisabledCallback()`.\n- Calls the element's custom validator method, `_getValidity()`, when the form\n  value changes and updates `internals.setValidity()` with the result.\n\n`FormAssociated` doesn't add any public API to the element. Its behavior can be\ncontrolled with the decorators described below.\n\n## Decorators\n\n### `@formValue()`\n\nThe form value can be stored in any class field decorated with `@formValue`.\n\n```ts\n  @formValue()\n  accessor value = '';\n```\n\nBy default, the field must be of type `string | File | FormData | null`.\nIf your element has a value of a different type, you can use a custom form value\nconverter:\n\n```ts\n  @formValue({\n    converter: {\n      toFormValue(value: number) {\n        return String(value);\n      },\n      fromFormValue(value: string) {\n        return Number(value);\n      },\n    },\n  })\n  accessor value = 23;\n```\n\n### `@formStateGetter()`, `@formStateSetter()`, and `@formState()`\n\nForm state is additional data stored by the browser alongside the form value.\nWhile the form _value_ is what gets submitted to the server, the form _state_ is\nused to restore the element's user interface when the user navigates back or\nforward, or when the browser restores a session.\n\nThe underlying browser API is `ElementInternals.setFormValue(value, state)`. The\n`FormAssociated` mixin automatically calls this method whenever the `@formValue`\nproperty changes. If you provide form state, it is passed as the second\nargument.\n\nWhen the browser restores the element, `FormAssociated` receives the stored\nstate and uses it to reset the element's properties.\n\nTo provide this state, you can use the `@formState` decorator on a property, or\n`@formStateGetter` and `@formStateSetter` on methods.\n\nThe `@formState()` decorator marks a field as being part of the form state. Its\nvalue will be included in the state passed to `internals.setFormValue()`.\n\n`@formStateGetter()` and `@formStateSetter()` allow for more complex state\nhandling. They are applied to methods called to get or set the form state. The\n`@formStateGetter()` decorated method is called to retrieve the state object to\npass to `internals.setFormValue()`. The `@formStateSetter()` decorated method is\ncalled with the restored state object when the browser restores the element.\n\n```ts\nclass CustomStateElement extends FormAssociated(LitElement) {\n  @formValue()\n  accessor value = 'bar';\n\n  @formState()\n  @property()\n  accessor count = 0;\n\n  @formStateGetter()\n  // @ts-expect-error #getFormState is called dynamically\n  #getFormState() {\n    return this.value + '#' + this.count;\n  }\n\n  @formStateSetter()\n  // @ts-expect-error #setFormState is called dynamically\n  #setFormState(state: string) {\n    const [value, count] = state.split('#');\n    this.value = value;\n    this.count = Number(count);\n  }\n}\n```\n\n> [!WARNING]\n>\n> TypeScript will complain that a private getter isn't called even if it's\n> decorated and called by the decorator. The FormAssociated API might change so\n> that it doesn't require a `@ts-expect-error` comment to suppress this warning.\n\n## Utilities\n\n- `isDisabled()`: An element can be disabled because its ancestor fieldset is\n  disabled, so checking the `disabled` attribute is insufficient. `isDisabled()`\n  checks if the element matches `:disabled`, correctly handling inherited\n  disabled state.\n\n  ```ts\n  import {isDisabled} from '@lit-labs/forms';\n  ```\n\n## Contributing\n\nPlease see [CONTRIBUTING.md](../../../CONTRIBUTING.md).\n","readmeFilename":"README.md","_rev":"1-da233756776677ad35c20ccaeb935b91"}