{"_id":"@aarongustafson/form-repeatable","_rev":"6-aa39e528f4633ee3277822fc12db8496","name":"@aarongustafson/form-repeatable","dist-tags":{"latest":"1.2.0"},"versions":{"0.1.0":{"name":"@aarongustafson/form-repeatable","version":"0.1.0","keywords":["html","custom element","web component"],"author":{"url":"https://www.aaron-gustafson.com/","name":"Aaron Gustafson","email":"aaron@easy-designs.net"},"license":"MIT","_id":"@aarongustafson/form-repeatable@0.1.0","maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"homepage":"https://github.com/aarongustafson/form-repeatable#readme","bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"dist":{"shasum":"16338af9297acb470f4d4c8df8b3e22289da8fd9","tarball":"https://registry.npmjs.org/@aarongustafson/form-repeatable/-/form-repeatable-0.1.0.tgz","fileCount":7,"integrity":"sha512-DZj0DciHfF6HF9Rn5PDmv5qmynwK32t7eu6Lb5JOxXHKFpNdn+lA1jAA/p1JI7nm3/StZ4pmvos3O8tkVg9Teg==","signatures":[{"sig":"MEQCIDtn06BdHPiaOhvzPxFvY5/Bv5pbwYup2m2x7KJ/ZkGXAiB+4AnzGUm9wRMwdYFvAHNU1eL+J0gbhuvCMGlnHnThTA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30001},"main":"index.js","type":"module","module":"form-repeatable.js","engines":{"node":">=14.0.0"},"exports":{".":{"import":"./index.js"},"./define.js":{"import":"./define.js"},"./form-repeatable.js":{"import":"./form-repeatable.js"},"./custom-elements.json":"./custom-elements.json"},"gitHead":"b5294f9872030ad70c2952aab0bcc08fae6a23f1","scripts":{"lint":"npm run lint:eslint && npm run lint:prettier","test":"vitest","setup":"node scripts/setup.js","format":"npm run format:eslint && npm run format:prettier","test:ui":"vitest --ui","test:run":"vitest run","lint:eslint":"eslint .","format:eslint":"eslint . --fix","lint:prettier":"prettier \"**/*.js\" --check --ignore-path .gitignore","test:coverage":"vitest run --coverage","format:prettier":"prettier \"**/*.js\" --write --ignore-path .gitignore"},"_npmUser":{"name":"aarongustafson","email":"aaron@easy-designs.net"},"repository":{"url":"git+https://github.com/aarongustafson/form-repeatable.git","type":"git"},"_npmVersion":"10.8.2","description":"A web component that enables you to control the duplication of fields.","directories":{},"_nodeVersion":"20.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"eslint":"^9.39.1","vitest":"^4.0.10","prettier":"^3.6.2","happy-dom":"^20.0.10","@vitest/ui":"^4.0.10","@vitest/coverage-v8":"^4.0.10","@testing-library/dom":"^10.4.1","@open-wc/eslint-config":"^13.0.0","eslint-config-prettier":"^10.1.8","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/form-repeatable_0.1.0_1764634270567_0.3592676931428218","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@aarongustafson/form-repeatable","version":"0.1.1","keywords":["html","custom element","web component"],"author":{"url":"https://www.aaron-gustafson.com/","name":"Aaron Gustafson","email":"aaron@easy-designs.net"},"license":"MIT","_id":"@aarongustafson/form-repeatable@0.1.1","maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"homepage":"https://github.com/aarongustafson/form-repeatable#readme","bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"dist":{"shasum":"da5a30eb5d5e579f13e74a9ed1cd097d2075e204","tarball":"https://registry.npmjs.org/@aarongustafson/form-repeatable/-/form-repeatable-0.1.1.tgz","fileCount":7,"integrity":"sha512-wPJIHHAqMlhLwaN4tFtqzLDaCw9eBXWtFjob+pL2G85DGNEmECRYWm27ueLkE6n5EOWCmrns00cuor2/tb4aBQ==","signatures":[{"sig":"MEUCIQCtB40ybkaEvf4xm/sg/wzv8DJC81lFEivKqiR/VUBFhgIgfh5UcYnGnEIT1Ooptubjf+lCL7EJ+UvVXbV27R3Szf8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aarongustafson%2fform-repeatable@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":30001},"main":"index.js","type":"module","module":"form-repeatable.js","engines":{"node":">=14.0.0"},"exports":{".":{"import":"./index.js"},"./define.js":{"import":"./define.js"},"./form-repeatable.js":{"import":"./form-repeatable.js"},"./custom-elements.json":"./custom-elements.json"},"gitHead":"c0b5cbbfce6918ec93c6448fdf99559ec6b329f2","scripts":{"lint":"npm run lint:eslint && npm run lint:prettier","test":"vitest","setup":"node scripts/setup.js","format":"npm run format:eslint && npm run format:prettier","test:ui":"vitest --ui","test:run":"vitest run","lint:eslint":"eslint .","format:eslint":"eslint . --fix","lint:prettier":"prettier \"**/*.js\" --check --ignore-path .gitignore","test:coverage":"vitest run --coverage","format:prettier":"prettier \"**/*.js\" --write --ignore-path .gitignore"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:09b27648-f236-408c-8570-863bb023f143"}},"repository":{"url":"git+https://github.com/aarongustafson/form-repeatable.git","type":"git"},"_npmVersion":"11.6.4","description":"A web component that enables you to control the duplication of fields.","directories":{},"_nodeVersion":"20.19.5","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"eslint":"^9.39.1","vitest":"^4.0.10","prettier":"^3.6.2","happy-dom":"^20.0.10","@vitest/ui":"^4.0.10","@vitest/coverage-v8":"^4.0.10","@testing-library/dom":"^10.4.1","@open-wc/eslint-config":"^13.0.0","eslint-config-prettier":"^10.1.8","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/form-repeatable_0.1.1_1764634383098_0.3170507845599133","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@aarongustafson/form-repeatable","version":"0.1.2","keywords":["html","custom element","web component"],"author":{"url":"https://www.aaron-gustafson.com/","name":"Aaron Gustafson","email":"aaron@easy-designs.net"},"license":"MIT","_id":"@aarongustafson/form-repeatable@0.1.2","maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"homepage":"https://github.com/aarongustafson/form-repeatable#readme","bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"dist":{"shasum":"3b165dd7bd590a819a5aaf43584f297cac187f13","tarball":"https://registry.npmjs.org/@aarongustafson/form-repeatable/-/form-repeatable-0.1.2.tgz","fileCount":7,"integrity":"sha512-kvUkFtSv4az9SFVwaoZbjQRiaIh7cWh4JU4kroU03YfdkoWQKps6xKUW1EhlLZoewjwTweoHirYJnxdMjQsIgQ==","signatures":[{"sig":"MEUCIQC21k88H6L/fxwaHNt/VXufi0mIu472pOTCNLt+vmS0WwIgIAlk0fOQYzWm5mYPydEDnUPbxOU+PUGhC5ZlLxxnktE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aarongustafson%2fform-repeatable@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":30050},"main":"index.js","type":"module","module":"form-repeatable.js","engines":{"node":">=14.0.0"},"exports":{".":{"import":"./index.js"},"./define.js":{"import":"./define.js"},"./form-repeatable.js":{"import":"./form-repeatable.js"},"./custom-elements.json":"./custom-elements.json"},"gitHead":"24c322e20d6190520376a26039c9749d94622d61","scripts":{"lint":"npm run lint:eslint && npm run lint:prettier","test":"vitest","setup":"node scripts/setup.js","format":"npm run format:eslint && npm run format:prettier","test:ui":"vitest --ui","test:run":"vitest run","lint:eslint":"eslint .","format:eslint":"eslint . --fix","lint:prettier":"prettier \"**/*.js\" --check --ignore-path .gitignore","test:coverage":"vitest run --coverage","format:prettier":"prettier \"**/*.js\" --write --ignore-path .gitignore"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:09b27648-f236-408c-8570-863bb023f143"}},"repository":{"url":"git+https://github.com/aarongustafson/form-repeatable.git","type":"git"},"_npmVersion":"11.6.4","description":"A web component that enables you to control the duplication of fields.","directories":{},"_nodeVersion":"20.19.5","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"eslint":"^9.39.1","vitest":"^4.0.10","prettier":"^3.6.2","happy-dom":"^20.0.10","@vitest/ui":"^4.0.10","@vitest/coverage-v8":"^4.0.10","@testing-library/dom":"^10.4.1","@open-wc/eslint-config":"^13.0.0","eslint-config-prettier":"^10.1.8","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/form-repeatable_0.1.2_1764643220850_0.838527915744919","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@aarongustafson/form-repeatable","version":"1.0.0","keywords":["html","custom element","web component"],"author":{"url":"https://www.aaron-gustafson.com/","name":"Aaron Gustafson","email":"aaron@easy-designs.net"},"license":"MIT","_id":"@aarongustafson/form-repeatable@1.0.0","maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"homepage":"https://github.com/aarongustafson/form-repeatable#readme","bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"dist":{"shasum":"fb7061ddfdc4d11d1a664961c902c1d41bb8d8be","tarball":"https://registry.npmjs.org/@aarongustafson/form-repeatable/-/form-repeatable-1.0.0.tgz","fileCount":7,"integrity":"sha512-IzcEYK121LL3TvUu0pjf513CHx+Z+vanKTrFyr6NIRmjQNX+V65ncp4vxG26l4Ld5LswsFsfvmvasW+dHOwErg==","signatures":[{"sig":"MEYCIQDxGw6o2SzkzTmk+I4ED1jBj0fLTT+X/qLg9PJATsO5dQIhALMV2E+q3FYow/v3+IyAZhbCFqfCjHcWfhH/EABh90ac","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aarongustafson%2fform-repeatable@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":30595},"main":"index.js","type":"module","module":"form-repeatable.js","engines":{"node":">=14.0.0"},"exports":{".":{"import":"./index.js"},"./define.js":{"import":"./define.js"},"./form-repeatable.js":{"import":"./form-repeatable.js"},"./custom-elements.json":"./custom-elements.json"},"gitHead":"ff6ed9302e447ac099dabd8526f2644a31914fe6","scripts":{"lint":"npm run lint:eslint && npm run lint:prettier","test":"vitest","setup":"node scripts/setup.js","format":"npm run format:eslint && npm run format:prettier","test:ui":"vitest --ui","test:run":"vitest run","lint:eslint":"eslint .","format:eslint":"eslint . --fix","lint:prettier":"prettier \"**/*.js\" --check --ignore-path .gitignore","test:coverage":"vitest run --coverage","format:prettier":"prettier \"**/*.js\" --write --ignore-path .gitignore"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:09b27648-f236-408c-8570-863bb023f143"}},"repository":{"url":"git+https://github.com/aarongustafson/form-repeatable.git","type":"git"},"_npmVersion":"11.6.4","description":"A web component that enables you to control the duplication of fields.","directories":{},"_nodeVersion":"20.19.6","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"eslint":"^9.39.1","vitest":"^4.0.10","prettier":"^3.6.2","happy-dom":"^20.0.10","@vitest/ui":"^4.0.10","@vitest/coverage-v8":"^4.0.10","@testing-library/dom":"^10.4.1","@open-wc/eslint-config":"^13.0.0","eslint-config-prettier":"^10.1.8","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/form-repeatable_1.0.0_1765169568334_0.4500152287258552","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@aarongustafson/form-repeatable","version":"1.1.0","keywords":["html","custom element","web component"],"author":{"url":"https://www.aaron-gustafson.com/","name":"Aaron Gustafson","email":"aaron@easy-designs.net"},"license":"MIT","_id":"@aarongustafson/form-repeatable@1.1.0","maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"homepage":"https://github.com/aarongustafson/form-repeatable#readme","bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"dist":{"shasum":"af427e4081821a10b495bb373894641a53b1200c","tarball":"https://registry.npmjs.org/@aarongustafson/form-repeatable/-/form-repeatable-1.1.0.tgz","fileCount":7,"integrity":"sha512-ZSqjYbm2YddK+VtM1fe4g4W6AbdgDO/r3GwgKCyaZzbeGl5iBNHoFszeLoIluOJXoS7/ftgGYxtFhr6tmBb8eQ==","signatures":[{"sig":"MEQCICudoP36yF5bPdaeGfSoddub1nAzYTKznSkPaZVM5cpxAiARshowvT8wllT3i4ZHQzsyE0bQTlyGokeZq0ZvgVMqqQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aarongustafson%2fform-repeatable@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":30917},"main":"index.js","type":"module","module":"form-repeatable.js","engines":{"node":">=14.0.0"},"exports":{".":{"import":"./index.js"},"./define.js":{"import":"./define.js"},"./form-repeatable.js":{"import":"./form-repeatable.js"},"./custom-elements.json":"./custom-elements.json"},"gitHead":"52061628e5373d11d98995868ab38a4929d7a3e1","scripts":{"lint":"npm run lint:eslint && npm run lint:prettier","test":"vitest","setup":"node scripts/setup.js","format":"npm run format:eslint && npm run format:prettier","test:ui":"vitest --ui","version":"node scripts/update-demo-versions.js && git add demo/esm.html","test:run":"vitest run","lint:eslint":"eslint .","format:eslint":"eslint . --fix","lint:prettier":"prettier \"**/*.js\" --check --ignore-path .gitignore","test:coverage":"vitest run --coverage","format:prettier":"prettier \"**/*.js\" --write --ignore-path .gitignore"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:09b27648-f236-408c-8570-863bb023f143"}},"repository":{"url":"git+https://github.com/aarongustafson/form-repeatable.git","type":"git"},"_npmVersion":"11.6.4","description":"A web component that enables you to control the duplication of fields.","directories":{},"_nodeVersion":"20.19.6","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"eslint":"^9.39.1","vitest":"^4.0.10","prettier":"^3.6.2","happy-dom":"^20.0.10","@vitest/ui":"^4.0.10","@vitest/coverage-v8":"^4.0.10","@testing-library/dom":"^10.4.1","@open-wc/eslint-config":"^13.0.0","eslint-config-prettier":"^10.1.8","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/form-repeatable_1.1.0_1765304463570_0.8317283607175918","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@aarongustafson/form-repeatable","version":"1.2.0","description":"A web component that enables you to control the duplication of fields.","keywords":["html","custom element","web component"],"author":{"name":"Aaron Gustafson","email":"aaron@easy-designs.net","url":"https://www.aaron-gustafson.com/"},"license":"MIT","homepage":"https://github.com/aarongustafson/form-repeatable#readme","repository":{"type":"git","url":"git+https://github.com/aarongustafson/form-repeatable.git"},"bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"type":"module","main":"index.js","module":"form-repeatable.js","types":"form-repeatable.d.ts","customElements":"custom-elements.json","exports":{".":{"import":"./index.js","types":"./form-repeatable.d.ts"},"./form-repeatable.js":{"import":"./form-repeatable.js","types":"./form-repeatable.d.ts"},"./define.js":{"import":"./define.js"},"./custom-elements.json":"./custom-elements.json"},"engines":{"node":">=20.0.0"},"scripts":{"setup":"node scripts/setup.js","test":"vitest","test:ui":"vitest --ui","test:run":"vitest run","test:coverage":"vitest run --coverage","lint":"npm run lint:eslint && npm run lint:prettier","format":"npm run format:eslint && npm run format:prettier","lint:eslint":"eslint .","format:eslint":"eslint . --fix","lint:prettier":"prettier \"**/*.js\" --check --ignore-path .gitignore","format:prettier":"prettier \"**/*.js\" --write --ignore-path .gitignore","version":"node scripts/update-demo-versions.js && git add demo/esm.html","postversion":"git push --follow-tags"},"devDependencies":{"@open-wc/eslint-config":"^13.0.0","@testing-library/dom":"^10.4.1","@testing-library/user-event":"^14.6.1","@vitest/coverage-v8":"^4.0.10","@vitest/ui":"^4.0.10","eslint":"^9.39.1","eslint-config-prettier":"^10.1.8","happy-dom":"^20.0.10","prettier":"^3.6.2","vitest":"^4.0.10"},"publishConfig":{"access":"public"},"gitHead":"5f64b234a5d15ade8da1cfca7c03f614396a62bc","_id":"@aarongustafson/form-repeatable@1.2.0","_nodeVersion":"20.19.6","_npmVersion":"11.7.0","dist":{"integrity":"sha512-nrXGrWXXlUKJQJl6RxhlXXj0Qc0ma9kyzEk//GAqn0TEY7jlKlAjLyc9wH0oW5gdE504JKwT5U9wBCmv3uJkMg==","shasum":"3e4fc624fb4212b7309c4cf2f41ac74c9dd971a3","tarball":"https://registry.npmjs.org/@aarongustafson/form-repeatable/-/form-repeatable-1.2.0.tgz","fileCount":8,"unpackedSize":34639,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aarongustafson%2fform-repeatable@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDM2X3wi8IKZRj2G6UdNNP72fofQ9K+5/S3f4pX49ROnQIgYk2iOt3+HDcY7/07HsfHb06/2sEjqV9BPPwCTiGNAgc="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:09b27648-f236-408c-8570-863bb023f143"}},"directories":{},"maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/form-repeatable_1.2.0_1765687364973_0.014283088003777866"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-02T00:11:10.395Z","modified":"2025-12-14T04:42:45.477Z","0.1.0":"2025-12-02T00:11:10.765Z","0.1.1":"2025-12-02T00:13:03.271Z","0.1.2":"2025-12-02T02:40:21.018Z","1.0.0":"2025-12-08T04:52:48.483Z","1.1.0":"2025-12-09T18:21:03.722Z","1.2.0":"2025-12-14T04:42:45.125Z"},"bugs":{"url":"https://github.com/aarongustafson/form-repeatable/issues"},"author":{"name":"Aaron Gustafson","email":"aaron@easy-designs.net","url":"https://www.aaron-gustafson.com/"},"license":"MIT","homepage":"https://github.com/aarongustafson/form-repeatable#readme","keywords":["html","custom element","web component"],"repository":{"type":"git","url":"git+https://github.com/aarongustafson/form-repeatable.git"},"description":"A web component that enables you to control the duplication of fields.","maintainers":[{"name":"aarongustafson","email":"aaron@easy-designs.net"}],"readme":"# form-repeatable Web Component\n\n[![npm version](https://img.shields.io/npm/v/@aarongustafson/form-repeatable.svg)](https://www.npmjs.com/package/@aarongustafson/form-repeatable) [![Build Status](https://img.shields.io/github/actions/workflow/status/aarongustafson/form-repeatable/ci.yml?branch=main)](https://github.com/aarongustafson/form-repeatable/actions)\n\nA single-instance web component that manages repeatable form field groups internally using shadow DOM and native form participation via the ElementInternals API.\n\n## Features\n\n- 🔄 **Manage repeatable field groups** - single component instance manages all groups\n- 🔢 **Auto-increment** numeric values in labels, IDs, and `for` attributes\n- ❌ **Add and remove groups** dynamically with min/max constraints\n- 📋 **Native form participation** - uses ElementInternals API for seamless form integration\n- 🎨 **Flexible templating** - use first child or explicit `<template>` element\n- 🌍 **Global styles** - automatically adopts page stylesheets into shadow DOM\n- 🎯 **Modern CSS Grid layout** - default 2-column layout with subgrid support\n- 📦 **Zero dependencies** - pure web component\n- ♿ **Accessible** - semantic HTML with proper ARIA labels\n- 🎯 **TypeScript-ready** with type definitions\n\n## TypeScript & Framework Support\n\n- Bundled `.d.ts` definitions describe `FormRepeatableElement` and `defineFormRepeatable`, so editors and build pipelines get full typing with zero config.\n- `addLabel`, `removeLabel`, `min`, and `max` now reflect between properties and attributes, keeping declarative templates and reactive frameworks in sync with the DOM.\n- `_upgradeProperty` ensures properties assigned before the browser upgrades the custom element are re-applied once the class is connected (useful for SSR and hydration scenarios).\n- Global `HTMLElementTagNameMap` augmentation lets TypeScript understand `document.querySelector('form-repeatable')` without additional type casts.\n\n## Demo\n\n[Live Demo](https://aarongustafson.github.io/form-repeatable/demo/) ([Source](./demo/index.html))\n\n**Additional Examples:**\n\n- [unpkg CDN](https://aarongustafson.github.io/form-repeatable/demo/unpkg.html) ([Source](./demo/unpkg.html))\n- [esm.sh CDN](https://aarongustafson.github.io/form-repeatable/demo/esm.html) ([Source](./demo/esm.html))\n\n## Installation\n\n```bash\nnpm install @aarongustafson/form-repeatable\n```\n\n## Usage\n\n### Option 1: Import the class and define manually\n\nImport the class and define the custom element with your preferred tag name:\n\n```javascript\nimport { FormRepeatableElement } from '@aarongustafson/form-repeatable';\n\ncustomElements.define('my-custom-name', FormRepeatableElement);\n```\n\n### Option 2: Auto-define the custom element (browser environments only)\n\nUse the guarded definition helper to register the element when `customElements` is available:\n\n```javascript\nimport '@aarongustafson/form-repeatable/define.js';\n```\n\nIf you prefer to control when the element is registered, call the helper directly:\n\n```javascript\nimport { defineFormRepeatable } from '@aarongustafson/form-repeatable/define.js';\n\ndefineFormRepeatable();\n```\n\nYou can also include the guarded script from HTML:\n\n```html\n<script src=\"./node_modules/@aarongustafson/form-repeatable/define.js\" type=\"module\"></script>\n```\n\n### Basic Example\n\n```html\n<form>\n  <form-repeatable>\n    <div>\n      <label for=\"stop-1\">Stop 1</label>\n      <input id=\"stop-1\" type=\"text\" name=\"stops[]\">\n    </div>\n  </form-repeatable>\n</form>\n```\n\nThe component uses the first child as a template. When the user clicks \"Add Another\":\n1. Clones the template\n2. Increments numbers (\"Stop 1\" → \"Stop 2\", `stop-1` → `stop-2`)\n3. Appends the new group to the shadow DOM\n4. Shows remove buttons when more than the minimum groups exist\n5. Updates the form value via ElementInternals\n\n### Progressive Enhancement with Pre-populated Groups\n\nYou can provide multiple initial groups that will be moved into the shadow DOM:\n\n```html\n<form>\n  <form-repeatable min=\"2\">\n    <div>\n      <label for=\"phone-1\">Phone 1</label>\n      <input id=\"phone-1\" type=\"tel\" name=\"phones[]\" value=\"555-0100\">\n    </div>\n    <div>\n      <label for=\"phone-2\">Phone 2</label>\n      <input id=\"phone-2\" type=\"tel\" name=\"phones[]\" value=\"555-0101\">\n    </div>\n    <div>\n      <label for=\"phone-3\">Phone 3</label>\n      <input id=\"phone-3\" type=\"tel\" name=\"phones[]\">\n    </div>\n  </form-repeatable>\n</form>\n```\n\nAll child elements become groups managed by the single component instance.\n\n### Multiple Fields per Group\n\nEach group can contain multiple related fields:\n\n```html\n<form-repeatable>\n  <fieldset>\n    <legend>Guest 1</legend>\n    <label for=\"guest-name-1\">Name</label>\n    <input id=\"guest-name-1\" type=\"text\" name=\"guest-name-1\">\n\n    <label for=\"guest-email-1\">Email</label>\n    <input id=\"guest-email-1\" type=\"email\" name=\"guest-email-1\">\n  </fieldset>\n</form-repeatable>\n```\n\nAll numeric values in labels, IDs, and attributes are incremented when new groups are added.\n\n### Explicit Template with Placeholders\n\nInstead of using the first child as a template, you can provide an explicit `<template>` element with `{n}` placeholders:\n\n```html\n<form-repeatable>\n  <template>\n    <div>\n      <label for=\"email-{n}\">Email {n}</label>\n      <input id=\"email-{n}\" type=\"email\" name=\"emails[]\">\n    </div>\n  </template>\n</form-repeatable>\n```\n\nThe `{n}` placeholders are replaced with sequential numbers (1, 2, 3, etc.).\n\n## Attributes\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `add-label` | `string` | `\"Add Another\"` | Custom label for the \"Add\" button |\n| `remove-label` | `string` | `\"Remove\"` | Custom label for the \"Remove\" button. The accessible name (`aria-label`) is automatically composed by combining this with the first label/legend text (e.g., \"Remove Stop 1\"). |\n| `min` | `number` | `1` | Minimum number of groups (must be > 0). Remove buttons are hidden when at minimum. |\n| `max` | `number` | `null` | Maximum number of groups (optional, must be > min). Add button is hidden when at maximum. |\n\n### Example with Min/Max Constraints\n\n```html\n<form-repeatable min=\"2\" max=\"5\" add-label=\"Add Team Member\" remove-label=\"Remove Member\">\n  <div>\n    <label for=\"member-1\">Team Member 1</label>\n    <input id=\"member-1\" type=\"text\" name=\"members[]\">\n  </div>\n</form-repeatable>\n```\n\nThis creates a component that:\n- Starts with 1 group (will allow adding until min is met)\n- Cannot have fewer than 2 groups\n- Cannot have more than 5 groups\n- Uses custom button labels\n\n## Events\n\nThe component fires custom events that you can listen to:\n\n| Event | Description | Detail |\n|-------|-------------|--------|\n| `form-repeatable:added` | Fired when a new group is added | `{ group: Object, groupCount: number }` |\n| `form-repeatable:removed` | Fired when a group is removed | `{ group: Object, groupCount: number }` |\n\n### Example Event Handling\n\n```javascript\nconst repeatable = document.querySelector('form-repeatable');\n\nrepeatable.addEventListener('form-repeatable:added', (event) => {\n  console.log('Group added. Total groups:', event.detail.groupCount);\n});\n\nrepeatable.addEventListener('form-repeatable:removed', (event) => {\n  console.log('Group removed. Total groups:', event.detail.groupCount);\n});\n```\n\n## CSS Parts\n\nThe component exposes CSS parts that allow you to style internal shadow DOM elements:\n\n| Part | Description |\n|------|-------------|\n| `groups` | Container for all groups (default: CSS grid with 2-column layout) |\n| `group` | Each repeatable group wrapper (default: subgrid spanning both columns) |\n| `content` | Container for the group's fields (default: column 1) |\n| `group-controls` | Container for group-level controls, remove button (default: column 2) |\n| `controls` | Container for field-level controls, add button (default: below groups) |\n| `button` | Both buttons (style all buttons together) |\n| `add-button` | The \"Add Another\" button |\n| `remove-button` | The \"Remove\" buttons |\n\n### Example Styling\n\n```css\n/* Style all buttons */\nform-repeatable::part(button) {\n  padding: 0.5rem 1rem;\n  font-weight: bold;\n  border: none;\n  border-radius: 4px;\n  cursor: pointer;\n}\n\n/* Style the add button specifically */\nform-repeatable::part(add-button) {\n  background: #28a745;\n  color: white;\n}\n\nform-repeatable::part(add-button):hover {\n  background: #218838;\n}\n\n/* Style the remove button specifically */\nform-repeatable::part(remove-button) {\n  background: #dc3545;\n  color: white;\n}\n\n/* Style each group */\nform-repeatable::part(group) {\n  padding: 1rem;\n  background: #f8f9fa;\n  border-radius: 4px;\n  margin-bottom: 0.5rem;\n}\n\n/* Customize the grid layout */\nform-repeatable::part(groups) {\n  grid-template-columns: 1fr auto; /* Adjust column sizes */\n  gap: 1rem;\n}\n```\n\n## Default Styling\n\nThe component uses a **CSS Grid layout** by default:\n\n- **Two columns**: `minmax(min-content, 2fr)` for content, `minmax(min-content, 1fr)` for controls\n- **Subgrid**: Each group uses `subgrid` to align with the parent grid\n- **Column 1**: Group content (fields, labels, etc.)\n- **Column 2**: Remove buttons (aligned to the right)\n- **Below groups**: Add button appears after all groups\n- **Global styles**: Page stylesheets are automatically adopted into the shadow DOM\n\nYou can override the grid layout using `::part(groups)` as shown above.\n\n### Advanced Styling\n\nThe component uses Shadow DOM with adopted stylesheets. You can style it using:\n\n1. **CSS parts** (shown above) - style internal shadow DOM elements\n2. **Global stylesheets** - automatically adopted into shadow DOM, so page styles apply\n3. **Host element** - style the component container itself\n\n```css\n/* Style the host element */\nform-repeatable {\n  display: block;\n  margin-bottom: 2rem;\n}\n\n/* Global styles automatically apply to elements in shadow DOM */\ninput,\nselect,\ntextarea {\n  width: 100%;\n  padding: 0.5rem;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n\nlabel {\n  display: block;\n  font-weight: bold;\n  margin-bottom: 0.25rem;\n}\n```\n\n## Form Participation\n\nThis component uses the **ElementInternals API** for native form participation:\n\n- **Automatic submission**: All inputs within groups are collected and submitted with the form\n- **FormData integration**: Values are added to FormData automatically\n- **Form reset**: Supports native form reset functionality\n- **Disabled state**: Respects form disabled state\n- **Browser support**: 95% (Chrome 77+, Firefox 93+, Safari 16.4+)\n\nThe component is form-associated (`static formAssociated = true`) and manages form values internally using `attachInternals()`.\n\n## How It Works\n\nThe component uses a **single-instance architecture**:\n\n1. **Template extraction**: Parses the first child element (or explicit `<template>`) to create a reusable template\n2. **Number detection**: Identifies numeric patterns using regex `/(.*)(\\d+)(.*)/` and converts to `{n}` placeholders\n3. **Group initialization**: Moves all light DOM children into shadow DOM as initial groups\n4. **Adding groups**: Clones the template, replaces `{n}` with sequential numbers, appends to shadow DOM\n5. **Removing groups**: Removes the group and renumbers remaining groups sequentially (1, 2, 3...)\n6. **Form value sync**: Collects all inputs from groups and updates FormData via ElementInternals\n7. **Global styles**: Automatically adopts page stylesheets into shadow DOM for consistent styling\n\n## Browser Support\n\nThis component uses modern web standards:\n- Custom Elements v1\n- Shadow DOM v1\n- ElementInternals API\n- Adopted Stylesheets\n- CSS Grid & Subgrid\n- ES Modules\n\n**Supported browsers:**\n- Chrome/Edge 117+ (for subgrid support)\n- Firefox 71+ (for subgrid support)\n- Safari 16.4+ (for ElementInternals support)\n\nFor broader browser support without subgrid, you can override the default grid layout using CSS parts. For older browsers, you may need polyfills from [@webcomponents/webcomponentsjs](https://github.com/webcomponents/polyfills/tree/master/packages/webcomponentsjs).\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Run tests\nnpm test\n\n# Run tests with UI\nnpm run test:ui\n\n# Run tests with coverage\nnpm run test:coverage\n\n# Lint code\nnpm run lint\n\n# Format code\nnpm run format\n\n# View demo\nopen demo/index.html\n```\n\n## License\n\nMIT © [Aaron Gustafson](https://www.aaron-gustafson.com/)\n","readmeFilename":"README.md"}