mi-element 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/element.js CHANGED
@@ -52,10 +52,13 @@ class MiElement extends HTMLElement {
52
52
  }
53
53
  }
54
54
  connectedCallback() {
55
- this.#controllers.forEach(controller => controller.hostConnected?.());
56
- const {shadowRootInit: shadowRootInit, useGlobalStyles: useGlobalStyles, template: template} = this.constructor;
57
- this.renderRoot = shadowRootInit ? this.shadowRoot ?? this.attachShadow(shadowRootInit) : this,
55
+ const {shadowRootInit: shadowRootInit, useGlobalStyles: useGlobalStyles, template: template, formAssociated: formAssociated} = this.constructor;
56
+ if (this.#controllers.forEach(controller => controller.hostConnected?.()), this.renderRoot = shadowRootInit ? this.shadowRoot ?? this.attachShadow(shadowRootInit) : this,
58
57
  this.addTemplate(template), useGlobalStyles && addGlobalStyles(this.renderRoot),
58
+ formAssociated && this.handleFormdata) {
59
+ const internals = this.attachInternals();
60
+ internals.form && this.on('formdata', ev => this.handleFormdata(ev), internals.form);
61
+ }
59
62
  this.render(), this.requestUpdate();
60
63
  }
61
64
  disconnectedCallback() {
package/docs/element.md CHANGED
@@ -7,6 +7,7 @@
7
7
  * [disconnectedCallback()](#disconnectedcallback)
8
8
  * [attributeChangedCallback(name, oldValue, newValue)](#attributechangedcallbackname-oldvalue-newvalue)
9
9
  * [Update Cycle](#update-cycle)
10
+ * [Form-Associated Elements](#form-associated-elements)
10
11
  * [render()](#render)
11
12
  * [update(changedAttributes)](#updatechangedattributes)
12
13
  * [shouldUpdate(changedAttributes)](#shouldupdatechangedattributes)
@@ -206,6 +207,85 @@ class Counter extends MiElement {
206
207
  }
207
208
  ```
208
209
 
210
+ ## Form-Associated Elements
211
+
212
+ MiElement supports [form-associated custom elements][form-associated], allowing
213
+ your components to participate in HTML forms just like native form controls.
214
+
215
+ [form-associated]: https://web.dev/articles/form-associated-custom-elements
216
+
217
+ ### Declaring a Form-Associated Element
218
+
219
+ Set `static formAssociated = true` on your component to enable form association:
220
+
221
+ ```js
222
+ import { define, MiElement, html } from 'mi-element'
223
+
224
+ class CustomInput extends MiElement {
225
+ static formAssociated = true
226
+
227
+ static get properties() {
228
+ return {
229
+ name: { type: String },
230
+ value: { type: String, initial: '' }
231
+ }
232
+ }
233
+
234
+ static template = html`<input type="text" />`
235
+
236
+ render() {
237
+ this.refs = this.refsBySelector({ input: 'input' })
238
+ this.refs.input.addEventListener('input', (ev) => {
239
+ this.value = ev.target.value
240
+ })
241
+ }
242
+ }
243
+
244
+ define('custom-input', CustomInput)
245
+ ```
246
+
247
+ ### Handling Form Data
248
+
249
+ Implement the `handleFormdata(ev)` method to submit your component's data with
250
+ the form:
251
+
252
+ ```js
253
+ class CustomInput extends MiElement {
254
+ static formAssociated = true
255
+
256
+ // ...other code...
257
+
258
+ handleFormdata(ev) {
259
+ // Only include data if the component has a name attribute
260
+ if (this.name) {
261
+ ev.formData.append(this.name, this.refs.input.value)
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ The `handleFormdata` method is automatically called when the form is submitted or
268
+ when `FormData` is created from the form.
269
+
270
+ ### Usage Example
271
+
272
+ ```html
273
+ <form id="my-form">
274
+ <custom-input name="username" value="john"></custom-input>
275
+ <custom-input name="email" value="john@example.com"></custom-input>
276
+ <button type="submit">Submit</button>
277
+ </form>
278
+
279
+ <script>
280
+ const form = document.getElementById('my-form')
281
+ const formData = new FormData(form)
282
+
283
+ console.log(formData.get('username')) // 'john'
284
+ console.log(formData.get('email')) // 'john@example.com'
285
+ </script>
286
+ ```
287
+
288
+
209
289
  ## render()
210
290
 
211
291
  Initial rendering of the component. Try to render the component only once!
@@ -275,7 +355,7 @@ class Counter extends MiElement {
275
355
  static template = `
276
356
  <button id>Count</button>
277
357
  <p>Counter value: <span>0</span></p>
278
- `
358
+ ``
279
359
 
280
360
  render() {
281
361
  // template is already rendered on `this.renderRoot`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mi-element",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Build lightweight reactive micro web-components",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/commenthol/mi-element/tree/main/packages/mi-element#readme",
package/src/element.js CHANGED
@@ -159,9 +159,11 @@ export class MiElement extends HTMLElement {
159
159
  * @category lifecycle
160
160
  */
161
161
  connectedCallback() {
162
- this.#controllers.forEach((controller) => controller.hostConnected?.())
163
162
  // @ts-expect-error
164
- const { shadowRootInit, useGlobalStyles, template } = this.constructor
163
+ const { shadowRootInit, useGlobalStyles, template, formAssociated } =
164
+ this.constructor
165
+ // connect all controllers
166
+ this.#controllers.forEach((controller) => controller.hostConnected?.())
165
167
  this.renderRoot = shadowRootInit
166
168
  ? (this.shadowRoot ?? this.attachShadow(shadowRootInit))
167
169
  : this
@@ -169,6 +171,31 @@ export class MiElement extends HTMLElement {
169
171
  if (useGlobalStyles) {
170
172
  addGlobalStyles(this.renderRoot)
171
173
  }
174
+ /**
175
+ * handle formdata event if `handleFormdata` method is defined on the component.
176
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/formdata_event
177
+ * ```js
178
+ * class MyElement extends MiElement {
179
+ * static formAssociated = true // required to receive formdata event
180
+ * handleFormdata(ev) {
181
+ * const { name, value } = this.refs.input
182
+ * ev.formData.append(name, value)
183
+ * }
184
+ * render() {
185
+ * this.renderRoot.innerHTML = html`<input name="${this.name}" value="${this.value}">`
186
+ * this.refs = { input: this.renderRoot.querySelector('input') }
187
+ * }
188
+ * }
189
+ * ```
190
+ */
191
+ // @ts-expect-error
192
+ if (formAssociated && this.handleFormdata) {
193
+ const internals = this.attachInternals()
194
+ if (internals.form) {
195
+ // @ts-expect-error
196
+ this.on('formdata', (ev) => this.handleFormdata(ev), internals.form)
197
+ }
198
+ }
172
199
  this.render() // initial render
173
200
  this.requestUpdate() // request initial update
174
201
  }