pseudo-dom 0.15.0 → 0.17.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.
package/README.md CHANGED
@@ -59,6 +59,18 @@ reflected properties (`ariaLabel`, `ariaExpanded`, `ariaValueNow`, ...) are real
59
59
  matching `aria-*` attribute (`ariaLabel` <-> `aria-label`; the mapping isn't camelCase-to-kebab-case, so
60
60
  `ariaColCount` <-> `aria-colcount`, not `aria-col-count`).
61
61
 
62
+ `getAttributeNode` / `setAttributeNode` / `removeAttributeNode` give and take real `Attr` nodes (backed by
63
+ `AttrService`) instead of plain strings - `setAttributeNode` returns whatever `Attr` previously held that name (or
64
+ `null` when it's new), and `removeAttributeNode` throws when the element has no attribute matching the one given, like
65
+ the DOM's. Every `*NS` method (`getAttributeNS`, `hasAttributeNS`, `setAttributeNS`, `removeAttributeNS`,
66
+ `getAttributeNodeNS`, `setAttributeNodeNS`) behaves exactly like its non-NS counterpart, ignoring the namespace
67
+ argument entirely - there's no real namespace parsing here, matching `getElementsByTagNameNS`.
68
+
69
+ **Form controls** have a real `value` (`input`, `textarea`, `select`, `button`, `option`, `output`) and `checked`
70
+ (`input`). Like the DOM's, they start from the `value` / `checked` attributes (a checkbox with no `value` reads
71
+ `'on'`), and once set they stop following the attribute - setting them never changes the attribute. `value` is always
72
+ a string, so `parseInt(input.value)` works on a number attribute. `cloneNode` keeps the current value and checkedness.
73
+
62
74
  **`style`** is a real, live `CSSStyleDeclaration`:
63
75
 
64
76
  - Named property access (`el.style.backgroundColor = 'red'`) alongside `getPropertyValue` / `setProperty` /
@@ -169,9 +181,6 @@ it) - useful for watching pseudo-dom-driven code run, or checking a final result
169
181
  - There are text and comment nodes (`PseudoText`, `PseudoComment`); `textContent` works like the DOM's (the text of
170
182
  everything below, and setting it replaces the children with a text node)
171
183
 
172
- ## Not implemented yet
173
-
174
- - The `Attr`-node / namespaced attribute methods (`getAttributeNode`, `getAttributeNS`, ...)
175
184
  ## Modules
176
185
 
177
186
  <dl>
@@ -2433,9 +2442,14 @@ Simulate the behaviour of the HTMLElement Class when there is no DOM available.
2433
2442
  * [new HTMLElementService([elementOptions])](#new_HTMLElementService_new)
2434
2443
  * [.style](#HTMLElementService+style) ⇒ [<code>CSSStyleDeclarationService</code>](#CSSStyleDeclarationService)
2435
2444
  * [.dataset](#HTMLElementService+dataset) ⇒ <code>Object.&lt;string, string&gt;</code>
2445
+ * [.value](#HTMLElementService+value) ⇒ <code>string</code> \| <code>undefined</code>
2446
+ * [.checked](#HTMLElementService+checked) ⇒ <code>boolean</code> \| <code>undefined</code>
2436
2447
  * [.innerHTML](#HTMLElementService+innerHTML) ⇒ <code>undefined</code>
2437
2448
  * [.outerHTML](#HTMLElementService+outerHTML) ⇒ <code>undefined</code>
2438
2449
  * [.canFocus](#HTMLElementService+canFocus) ⇒ <code>boolean</code>
2450
+ * [.hasValueProperty()](#HTMLElementService+hasValueProperty) ⇒ <code>boolean</code>
2451
+ * [.hasCheckedProperty()](#HTMLElementService+hasCheckedProperty) ⇒ <code>boolean</code>
2452
+ * [.setExpando(name, value)](#HTMLElementService+setExpando) ⇒ <code>undefined</code>
2439
2453
  * [.cloneShallow()](#HTMLElementService+cloneShallow) ⇒ [<code>NodeService</code>](#NodeService)
2440
2454
  * [.equalsShallow(other)](#HTMLElementService+equalsShallow) ⇒ <code>boolean</code>
2441
2455
  * [.parse(html)](#HTMLElementService+parse) ⇒ <code>Array.&lt;\*&gt;</code>
@@ -2470,6 +2484,21 @@ and style.color = 'red' work.
2470
2484
  The element's data-* attributes, live, under their camelCase names (data-foo-bar <-> dataset.fooBar). Backed
2471
2485
  directly by getAttribute / setAttribute, so it is never out of sync with the attributes themselves.
2472
2486
 
2487
+ **Kind**: instance property of [<code>HTMLElementService</code>](#HTMLElementService)
2488
+ <a name="HTMLElementService+value"></a>
2489
+
2490
+ ### htmlElementService.value ⇒ <code>string</code> \| <code>undefined</code>
2491
+ The current value of a form control. Until it is set (or edited), it is the value attribute (the default value),
2492
+ or '' when there is none ('on' for a checkbox or radio); setting it never changes the attribute, like the DOM's.
2493
+ Only form controls (input, textarea, select, button, option, output) have one.
2494
+
2495
+ **Kind**: instance property of [<code>HTMLElementService</code>](#HTMLElementService)
2496
+ <a name="HTMLElementService+checked"></a>
2497
+
2498
+ ### htmlElementService.checked ⇒ <code>boolean</code> \| <code>undefined</code>
2499
+ Whether a checkbox or radio input is checked. Until it is set, it follows the checked attribute (which sets the
2500
+ default); setting it never changes the attribute, like the DOM's. Only input has one.
2501
+
2473
2502
  **Kind**: instance property of [<code>HTMLElementService</code>](#HTMLElementService)
2474
2503
  <a name="HTMLElementService+innerHTML"></a>
2475
2504
 
@@ -2501,10 +2530,35 @@ replaceWith. outerHTML's setter is here rather than on ElementService for the sa
2501
2530
  Whether this element can have the focus: form controls and links which are not disabled, and anything with a tabindex.
2502
2531
 
2503
2532
  **Kind**: instance property of [<code>HTMLElementService</code>](#HTMLElementService)
2533
+ <a name="HTMLElementService+hasValueProperty"></a>
2534
+
2535
+ ### htmlElementService.hasValueProperty() ⇒ <code>boolean</code>
2536
+ Whether this tag has a `value` (the form controls which do).
2537
+
2538
+ **Kind**: instance method of [<code>HTMLElementService</code>](#HTMLElementService)
2539
+ <a name="HTMLElementService+hasCheckedProperty"></a>
2540
+
2541
+ ### htmlElementService.hasCheckedProperty() ⇒ <code>boolean</code>
2542
+ Whether this tag has a `checked` (only input does).
2543
+
2544
+ **Kind**: instance method of [<code>HTMLElementService</code>](#HTMLElementService)
2545
+ <a name="HTMLElementService+setExpando"></a>
2546
+
2547
+ ### htmlElementService.setExpando(name, value) ⇒ <code>undefined</code>
2548
+ Put a plain own property on the element, as assigning a property this element does not have does in the DOM.
2549
+
2550
+ **Kind**: instance method of [<code>HTMLElementService</code>](#HTMLElementService)
2551
+
2552
+ | Param | Type |
2553
+ | --- | --- |
2554
+ | name | <code>string</code> |
2555
+ | value | <code>\*</code> |
2556
+
2504
2557
  <a name="HTMLElementService+cloneShallow"></a>
2505
2558
 
2506
2559
  ### htmlElementService.cloneShallow() ⇒ [<code>NodeService</code>](#NodeService)
2507
- Style is not attribute-backed like most properties (see the constructor), so cloneNode needs its own copy of it.
2560
+ Style is not attribute-backed like most properties (see the constructor), so cloneNode needs its own copy of it,
2561
+ and a form control keeps its current value and checkedness (as the DOM's cloneNode does).
2508
2562
 
2509
2563
  **Kind**: instance method of [<code>HTMLElementService</code>](#HTMLElementService)
2510
2564
  <a name="HTMLElementService+equalsShallow"></a>
@@ -2961,6 +3015,15 @@ Simulate the behaviour of the Element Class when there is no DOM available.
2961
3015
  * [.removeAttribute(attributeName)](#ElementService+removeAttribute) ⇒ <code>undefined</code>
2962
3016
  * [.getAttributeNames()](#ElementService+getAttributeNames) ⇒ <code>Array.&lt;string&gt;</code>
2963
3017
  * [.hasAttributes()](#ElementService+hasAttributes) ⇒ <code>boolean</code>
3018
+ * [.getAttributeNode(attributeName)](#ElementService+getAttributeNode) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3019
+ * [.getAttributeNodeNS(namespace, attributeName)](#ElementService+getAttributeNodeNS) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3020
+ * [.getAttributeNS(namespace, attributeName)](#ElementService+getAttributeNS) ⇒ <code>string</code> \| <code>null</code>
3021
+ * [.hasAttributeNS(namespace, attributeName)](#ElementService+hasAttributeNS) ⇒ <code>boolean</code>
3022
+ * [.removeAttributeNode(attr)](#ElementService+removeAttributeNode) ⇒ [<code>AttrService</code>](#AttrService)
3023
+ * [.removeAttributeNS(namespace, attributeName)](#ElementService+removeAttributeNS) ⇒ <code>undefined</code>
3024
+ * [.setAttributeNode(attr)](#ElementService+setAttributeNode) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3025
+ * [.setAttributeNodeNS(attr)](#ElementService+setAttributeNodeNS) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3026
+ * [.setAttributeNS(namespace, attributeName, attributeValue)](#ElementService+setAttributeNS) ⇒ <code>undefined</code>
2964
3027
  * [.toggleAttribute(attributeName, [force])](#ElementService+toggleAttribute) ⇒ <code>boolean</code>
2965
3028
  * [.getBoundingClientRect()](#ElementService+getBoundingClientRect) ⇒ <code>DOMRect</code>
2966
3029
  * [.getClientRects()](#ElementService+getClientRects) ⇒ <code>Array.&lt;DOMRect&gt;</code>
@@ -3272,6 +3335,128 @@ The name of every attribute on the element, in the order they were set.
3272
3335
  Whether the element has any attributes at all.
3273
3336
 
3274
3337
  **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3338
+ <a name="ElementService+getAttributeNode"></a>
3339
+
3340
+ ### elementService.getAttributeNode(attributeName) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3341
+ Retrieve the node representation of an attribute.
3342
+
3343
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3344
+ **Returns**: [<code>AttrService</code>](#AttrService) \| <code>null</code> - An Attr for the attribute, or null when there is no such attribute
3345
+
3346
+ | Param | Type |
3347
+ | --- | --- |
3348
+ | attributeName | <code>string</code> |
3349
+
3350
+ <a name="ElementService+getAttributeNodeNS"></a>
3351
+
3352
+ ### elementService.getAttributeNodeNS(namespace, attributeName) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3353
+ Retrieve the node representation of an attribute. There is no real namespace parsing here, so this ignores
3354
+ the namespace and behaves exactly like getAttributeNode.
3355
+
3356
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3357
+ **Returns**: [<code>AttrService</code>](#AttrService) \| <code>null</code> - An Attr for the attribute, or null when there is no such attribute
3358
+
3359
+ | Param | Type | Description |
3360
+ | --- | --- | --- |
3361
+ | namespace | <code>string</code> | Ignored |
3362
+ | attributeName | <code>string</code> | |
3363
+
3364
+ <a name="ElementService+getAttributeNS"></a>
3365
+
3366
+ ### elementService.getAttributeNS(namespace, attributeName) ⇒ <code>string</code> \| <code>null</code>
3367
+ Retrieve the value of an attribute. There is no real namespace parsing here, so this ignores the namespace
3368
+ and behaves exactly like getAttribute.
3369
+
3370
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3371
+ **Returns**: <code>string</code> \| <code>null</code> - The value, or null when there is no such attribute
3372
+
3373
+ | Param | Type | Description |
3374
+ | --- | --- | --- |
3375
+ | namespace | <code>string</code> | Ignored |
3376
+ | attributeName | <code>string</code> | |
3377
+
3378
+ <a name="ElementService+hasAttributeNS"></a>
3379
+
3380
+ ### elementService.hasAttributeNS(namespace, attributeName) ⇒ <code>boolean</code>
3381
+ Check whether the element has an attribute by that name. There is no real namespace parsing here, so this
3382
+ ignores the namespace and behaves exactly like hasAttribute.
3383
+
3384
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3385
+
3386
+ | Param | Type | Description |
3387
+ | --- | --- | --- |
3388
+ | namespace | <code>string</code> | Ignored |
3389
+ | attributeName | <code>string</code> | |
3390
+
3391
+ <a name="ElementService+removeAttributeNode"></a>
3392
+
3393
+ ### elementService.removeAttributeNode(attr) ⇒ [<code>AttrService</code>](#AttrService)
3394
+ Remove the node representation of an attribute from the element, and return it.
3395
+
3396
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3397
+ **Returns**: [<code>AttrService</code>](#AttrService) - The removed Attr
3398
+ **Throws**:
3399
+
3400
+ - <code>Error</code> When the element has no attribute matching attr.name
3401
+
3402
+
3403
+ | Param | Type |
3404
+ | --- | --- |
3405
+ | attr | [<code>AttrService</code>](#AttrService) |
3406
+
3407
+ <a name="ElementService+removeAttributeNS"></a>
3408
+
3409
+ ### elementService.removeAttributeNS(namespace, attributeName) ⇒ <code>undefined</code>
3410
+ Remove an attribute from the element. There is no real namespace parsing here, so this ignores the namespace
3411
+ and behaves exactly like removeAttribute.
3412
+
3413
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3414
+
3415
+ | Param | Type | Description |
3416
+ | --- | --- | --- |
3417
+ | namespace | <code>string</code> | Ignored |
3418
+ | attributeName | <code>string</code> | |
3419
+
3420
+ <a name="ElementService+setAttributeNode"></a>
3421
+
3422
+ ### elementService.setAttributeNode(attr) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3423
+ Set the node representation of an attribute, adding the attribute if it did not exist. Returns any previous
3424
+ Attr that had the same name, or null when there was none.
3425
+
3426
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3427
+ **Returns**: [<code>AttrService</code>](#AttrService) \| <code>null</code> - The replaced Attr, or null when the attribute was new
3428
+
3429
+ | Param | Type |
3430
+ | --- | --- |
3431
+ | attr | [<code>AttrService</code>](#AttrService) |
3432
+
3433
+ <a name="ElementService+setAttributeNodeNS"></a>
3434
+
3435
+ ### elementService.setAttributeNodeNS(attr) ⇒ [<code>AttrService</code>](#AttrService) \| <code>null</code>
3436
+ Set the node representation of an attribute. There is no real namespace parsing here, so this behaves
3437
+ exactly like setAttributeNode (Attr.name already carries any prefix).
3438
+
3439
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3440
+ **Returns**: [<code>AttrService</code>](#AttrService) \| <code>null</code> - The replaced Attr, or null when the attribute was new
3441
+
3442
+ | Param | Type |
3443
+ | --- | --- |
3444
+ | attr | [<code>AttrService</code>](#AttrService) |
3445
+
3446
+ <a name="ElementService+setAttributeNS"></a>
3447
+
3448
+ ### elementService.setAttributeNS(namespace, attributeName, attributeValue) ⇒ <code>undefined</code>
3449
+ Set the value of an attribute, adding the attribute if it did not exist. There is no real namespace parsing
3450
+ here, so this ignores the namespace and behaves exactly like setAttribute.
3451
+
3452
+ **Kind**: instance method of [<code>ElementService</code>](#ElementService)
3453
+
3454
+ | Param | Type | Description |
3455
+ | --- | --- | --- |
3456
+ | namespace | <code>string</code> | Ignored |
3457
+ | attributeName | <code>string</code> | |
3458
+ | attributeValue | <code>string</code> | |
3459
+
3275
3460
  <a name="ElementService+toggleAttribute"></a>
3276
3461
 
3277
3462
  ### elementService.toggleAttribute(attributeName, [force]) ⇒ <code>boolean</code>
@@ -2605,6 +2605,108 @@
2605
2605
  return this.attributeList.length > 0
2606
2606
  }
2607
2607
 
2608
+ /**
2609
+ * Retrieve the node representation of an attribute.
2610
+ * @param {string} attributeName
2611
+ * @returns {AttrService|null} An Attr for the attribute, or null when there is no such attribute
2612
+ */
2613
+ getAttributeNode (attributeName) {
2614
+ return this.hasAttribute(attributeName) ? new AttrService_1.AttrService(attributeName, this.getAttribute(attributeName) || '', this) : null
2615
+ }
2616
+
2617
+ /**
2618
+ * Retrieve the node representation of an attribute. There is no real namespace parsing here, so this ignores
2619
+ * the namespace and behaves exactly like getAttributeNode.
2620
+ * @param {string} namespace Ignored
2621
+ * @param {string} attributeName
2622
+ * @returns {AttrService|null} An Attr for the attribute, or null when there is no such attribute
2623
+ */
2624
+ getAttributeNodeNS (namespace, attributeName) {
2625
+ return this.getAttributeNode(attributeName)
2626
+ }
2627
+
2628
+ /**
2629
+ * Retrieve the value of an attribute. There is no real namespace parsing here, so this ignores the namespace
2630
+ * and behaves exactly like getAttribute.
2631
+ * @param {string} namespace Ignored
2632
+ * @param {string} attributeName
2633
+ * @returns {string|null} The value, or null when there is no such attribute
2634
+ */
2635
+ getAttributeNS (namespace, attributeName) {
2636
+ return this.getAttribute(attributeName)
2637
+ }
2638
+
2639
+ /**
2640
+ * Check whether the element has an attribute by that name. There is no real namespace parsing here, so this
2641
+ * ignores the namespace and behaves exactly like hasAttribute.
2642
+ * @param {string} namespace Ignored
2643
+ * @param {string} attributeName
2644
+ * @returns {boolean}
2645
+ */
2646
+ hasAttributeNS (namespace, attributeName) {
2647
+ return this.hasAttribute(attributeName)
2648
+ }
2649
+
2650
+ /**
2651
+ * Remove the node representation of an attribute from the element, and return it.
2652
+ * @param {AttrService} attr
2653
+ * @returns {AttrService} The removed Attr
2654
+ * @throws {Error} When the element has no attribute matching attr.name
2655
+ */
2656
+ removeAttributeNode (attr) {
2657
+ if (!this.hasAttribute(attr.name)) {
2658
+ throw new Error(`Failed to execute 'removeAttributeNode': The node provided is owned by another element (no attribute named '${attr.name}')`)
2659
+ }
2660
+ const removed = this.getAttributeNode(attr.name)
2661
+ this.removeAttribute(attr.name)
2662
+ return removed
2663
+ }
2664
+
2665
+ /**
2666
+ * Remove an attribute from the element. There is no real namespace parsing here, so this ignores the namespace
2667
+ * and behaves exactly like removeAttribute.
2668
+ * @param {string} namespace Ignored
2669
+ * @param {string} attributeName
2670
+ * @returns {undefined}
2671
+ */
2672
+ removeAttributeNS (namespace, attributeName) {
2673
+ this.removeAttribute(attributeName)
2674
+ }
2675
+
2676
+ /**
2677
+ * Set the node representation of an attribute, adding the attribute if it did not exist. Returns any previous
2678
+ * Attr that had the same name, or null when there was none.
2679
+ * @param {AttrService} attr
2680
+ * @returns {AttrService|null} The replaced Attr, or null when the attribute was new
2681
+ */
2682
+ setAttributeNode (attr) {
2683
+ const existing = this.getAttributeNode(attr.name)
2684
+ this.setAttribute(attr.name, attr.value)
2685
+ return existing
2686
+ }
2687
+
2688
+ /**
2689
+ * Set the node representation of an attribute. There is no real namespace parsing here, so this behaves
2690
+ * exactly like setAttributeNode (Attr.name already carries any prefix).
2691
+ * @param {AttrService} attr
2692
+ * @returns {AttrService|null} The replaced Attr, or null when the attribute was new
2693
+ */
2694
+ setAttributeNodeNS (attr) {
2695
+ return this.setAttributeNode(attr)
2696
+ }
2697
+
2698
+ /**
2699
+ * Set the value of an attribute, adding the attribute if it did not exist. There is no real namespace parsing
2700
+ * here, so this ignores the namespace and behaves exactly like setAttribute.
2701
+ * @param {string} namespace Ignored
2702
+ * @param {string} attributeName
2703
+ * @param {string} attributeValue
2704
+ * @returns {undefined}
2705
+ */
2706
+ setAttributeNS (namespace, attributeName, attributeValue) {
2707
+ this.setAttribute(attributeName, attributeValue)
2708
+ }
2709
+
2608
2710
  /**
2609
2711
  * Add the attribute (with an empty value) when it is not present, or remove it when it is - unless force says
2610
2712
  * which of those to do instead. Returns whether the attribute is present after the call.
@@ -3806,6 +3908,8 @@
3806
3908
  parent,
3807
3909
  children
3808
3910
  })
3911
+ this.dirtyValue = null
3912
+ this.dirtyChecked = null
3809
3913
  this.styleDeclaration = (0, createStyleDeclaration_1.default)()
3810
3914
  this.datasetProxy = (0, createDataset_1.default)(this)
3811
3915
  }
@@ -3829,12 +3933,94 @@
3829
3933
  }
3830
3934
 
3831
3935
  /**
3832
- * Style is not attribute-backed like most properties (see the constructor), so cloneNode needs its own copy of it.
3936
+ * Whether this tag has a `value` (the form controls which do).
3937
+ * @returns {boolean}
3938
+ */
3939
+ hasValueProperty () {
3940
+ return ['input', 'textarea', 'select', 'button', 'option', 'output'].indexOf(this.tagName.toLowerCase()) >= 0
3941
+ }
3942
+
3943
+ /**
3944
+ * Whether this tag has a `checked` (only input does).
3945
+ * @returns {boolean}
3946
+ */
3947
+ hasCheckedProperty () {
3948
+ return this.tagName.toLowerCase() === 'input'
3949
+ }
3950
+
3951
+ /**
3952
+ * Put a plain own property on the element, as assigning a property this element does not have does in the DOM.
3953
+ * @param {string} name
3954
+ * @param {*} value
3955
+ * @returns {undefined}
3956
+ */
3957
+ setExpando (name, value) {
3958
+ Object.defineProperty(this, name, {
3959
+ value,
3960
+ writable: true,
3961
+ configurable: true,
3962
+ enumerable: true
3963
+ })
3964
+ }
3965
+
3966
+ /**
3967
+ * The current value of a form control. Until it is set (or edited), it is the value attribute (the default value),
3968
+ * or '' when there is none ('on' for a checkbox or radio); setting it never changes the attribute, like the DOM's.
3969
+ * Only form controls (input, textarea, select, button, option, output) have one.
3970
+ * @returns {string|undefined}
3971
+ */
3972
+ get value () {
3973
+ if (!this.hasValueProperty()) {
3974
+ return undefined
3975
+ }
3976
+ if (this.dirtyValue !== null) {
3977
+ return this.dirtyValue
3978
+ }
3979
+ const attribute = this.getAttribute('value')
3980
+ if (attribute !== null) {
3981
+ return String(attribute)
3982
+ }
3983
+ return this.tagName.toLowerCase() === 'input' && /^(checkbox|radio)$/i.test(String(this.getAttribute('type'))) ? 'on' : ''
3984
+ }
3985
+
3986
+ set value (value) {
3987
+ if (this.hasValueProperty()) {
3988
+ this.dirtyValue = String(value)
3989
+ } else {
3990
+ this.setExpando('value', value)
3991
+ }
3992
+ }
3993
+
3994
+ /**
3995
+ * Whether a checkbox or radio input is checked. Until it is set, it follows the checked attribute (which sets the
3996
+ * default); setting it never changes the attribute, like the DOM's. Only input has one.
3997
+ * @returns {boolean|undefined}
3998
+ */
3999
+ get checked () {
4000
+ if (!this.hasCheckedProperty()) {
4001
+ return undefined
4002
+ }
4003
+ return this.dirtyChecked !== null ? this.dirtyChecked : this.hasAttribute('checked')
4004
+ }
4005
+
4006
+ set checked (checked) {
4007
+ if (this.hasCheckedProperty()) {
4008
+ this.dirtyChecked = Boolean(checked)
4009
+ } else {
4010
+ this.setExpando('checked', checked)
4011
+ }
4012
+ }
4013
+
4014
+ /**
4015
+ * Style is not attribute-backed like most properties (see the constructor), so cloneNode needs its own copy of it,
4016
+ * and a form control keeps its current value and checkedness (as the DOM's cloneNode does).
3833
4017
  * @returns {NodeService}
3834
4018
  */
3835
4019
  cloneShallow () {
3836
4020
  const copy = super.cloneShallow()
3837
4021
  copy.style.cssText = this.style.cssText
4022
+ copy.dirtyValue = this.dirtyValue
4023
+ copy.dirtyChecked = this.dirtyChecked
3838
4024
  return copy
3839
4025
  }
3840
4026