11/* eslint-disable class-methods-use-this */
22
33import { html , css , nothing } from '@lion/core' ;
4- import { ObserverMixin } from '@lion/core/src/ObserverMixin.js' ;
54import { FormatMixin } from '@lion/field' ;
65
76export const ChoiceInputMixin = superclass =>
87 // eslint-disable-next-line
9- class ChoiceInputMixin extends FormatMixin ( ObserverMixin ( superclass ) ) {
10- get delegations ( ) {
8+ class ChoiceInputMixin extends FormatMixin ( superclass ) {
9+ static get properties ( ) {
1110 return {
12- ...super . delegations ,
13- target : ( ) => this . inputElement ,
14- properties : [ ...super . delegations . properties , 'checked' ] ,
15- attributes : [ ...super . delegations . attributes , 'checked' ] ,
11+ ...super . properties ,
12+ /**
13+ * Boolean indicating whether or not this element is checked by the end user.
14+ */
15+ checked : {
16+ type : Boolean ,
17+ reflect : true ,
18+ } ,
19+ /**
20+ * Whereas 'normal' `.modelValue`s usually store a complex/typed version
21+ * of a view value, choice inputs have a slightly different approach.
22+ * In order to remain their Single Source of Truth characteristic, choice inputs
23+ * store both the value and 'checkedness', in the format { value: 'x', checked: true }
24+ * Different from the platform, this also allows to serialize the 'non checkedness',
25+ * allowing to restore form state easily and inform the server about unchecked options.
26+ */
27+ modelValue : {
28+ type : Object ,
29+ hasChanged : ( nw , old = { } ) => nw . value !== old . value || nw . checked !== old . checked ,
30+ } ,
31+ /**
32+ * The value property of the modelValue. It provides an easy inteface for storing
33+ * (complex) values in the modelValue
34+ */
35+ choiceValue : {
36+ type : Object ,
37+ } ,
1638 } ;
1739 }
1840
19- static get syncObservers ( ) {
20- return {
21- ...super . syncObservers ,
22- _syncModelValueToChecked : [ 'modelValue' ] ,
23- } ;
41+ get choiceValue ( ) {
42+ return this . modelValue . value ;
2443 }
2544
26- static get asyncObservers ( ) {
27- return {
28- ... super . asyncObservers ,
29- _reflectCheckedToCssClass : [ ' modelValue' ] ,
30- } ;
45+ set choiceValue ( value ) {
46+ this . requestUpdate ( 'choiceValue' , this . choiceValue ) ;
47+ if ( this . modelValue . value !== value ) {
48+ this . modelValue = { value , checked : this . modelValue . checked } ;
49+ }
3150 }
3251
33- get choiceChecked ( ) {
34- return this . modelValue . checked ;
35- }
52+ _requestUpdate ( name , oldValue ) {
53+ super . _requestUpdate ( name , oldValue ) ;
3654
37- set choiceChecked ( checked ) {
38- if ( this . modelValue . checked !== checked ) {
39- this . modelValue = { value : this . modelValue . value , checked } ;
55+ if ( name === 'modelValue' ) {
56+ if ( this . modelValue . checked !== this . checked ) {
57+ this . __syncModelCheckedToChecked ( this . modelValue . checked ) ;
58+ }
59+ } else if ( name === 'checked' ) {
60+ if ( this . modelValue . checked !== this . checked ) {
61+ this . __syncCheckedToModel ( this . checked ) ;
62+ }
4063 }
4164 }
4265
43- get choiceValue ( ) {
44- return this . modelValue . value ;
66+ firstUpdated ( c ) {
67+ super . firstUpdated ( c ) ;
68+ if ( c . has ( 'checked' ) ) {
69+ // Here we set the initial value for our [slot=input] content,
70+ // which has been set by our SlotMixin
71+ this . __syncCheckedToInputElement ( ) ;
72+ }
4573 }
4674
47- set choiceValue ( value ) {
48- if ( this . modelValue . value !== value ) {
49- this . modelValue = { value, checked : this . modelValue . checked } ;
75+ updated ( c ) {
76+ super . updated ( c ) ;
77+ if ( c . has ( 'modelValue' ) ) {
78+ this . _reflectCheckedToCssClass ( { modelValue : this . modelValue } ) ;
79+ this . __syncCheckedToInputElement ( ) ;
5080 }
5181 }
5282
@@ -56,15 +86,9 @@ export const ChoiceInputMixin = superclass =>
5686 }
5787
5888 /**
59- * @override
60- * Override InteractionStateMixin
61- * 'prefilled' should be false when modelValue is { checked: false }, which would return
62- * true in original method (since non-empty objects are considered prefilled by default).
89+ * Styles for [input=radio] and [input=checkbox] wrappers.
90+ * For [role=option] extensions, please override completely
6391 */
64- static _isPrefilled ( modelValue ) {
65- return modelValue . checked ;
66- }
67-
6892 static get styles ( ) {
6993 return [
7094 css `
@@ -79,6 +103,10 @@ export const ChoiceInputMixin = superclass =>
79103 ] ;
80104 }
81105
106+ /**
107+ * Template for [input=radio] and [input=checkbox] wrappers.
108+ * For [role=option] extensions, please override completely
109+ */
82110 render ( ) {
83111 return html `
84112 < slot name ="input "> </ slot >
@@ -96,22 +124,44 @@ export const ChoiceInputMixin = superclass =>
96124 }
97125
98126 connectedCallback ( ) {
99- if ( super . connectedCallback ) super . connectedCallback ( ) ;
100- this . addEventListener ( 'user-input-changed' , this . _toggleChecked ) ;
127+ super . connectedCallback ( ) ;
128+ this . addEventListener ( 'user-input-changed' , this . __toggleChecked ) ;
101129 this . _reflectCheckedToCssClass ( ) ;
102130 }
103131
104132 disconnectedCallback ( ) {
105- if ( super . disconnectedCallback ) super . disconnectedCallback ( ) ;
106- this . removeEventListener ( 'user-input-changed' , this . _toggleChecked ) ;
133+ super . disconnectedCallback ( ) ;
134+ this . removeEventListener ( 'user-input-changed' , this . __toggleChecked ) ;
135+ }
136+
137+ __toggleChecked ( ) {
138+ this . checked = ! this . checked ;
107139 }
108140
109- _toggleChecked ( ) {
110- this . choiceChecked = ! this . choiceChecked ;
141+ __syncModelCheckedToChecked ( checked ) {
142+ this . checked = checked ;
111143 }
112144
113- _syncModelValueToChecked ( { modelValue } ) {
114- this . checked = ! ! modelValue . checked ;
145+ __syncCheckedToModel ( checked ) {
146+ this . modelValue = { value : this . choiceValue , checked } ;
147+ }
148+
149+ __syncCheckedToInputElement ( ) {
150+ // .inputElement might not be available yet(slot content)
151+ // or at all (no reliance on platform construct, in case of [role=option])
152+ if ( this . inputElement ) {
153+ this . inputElement . checked = this . checked ;
154+ }
155+ }
156+
157+ /**
158+ * @override
159+ * Override InteractionStateMixin
160+ * 'prefilled' should be false when modelValue is { checked: false }, which would return
161+ * true in original method (since non-empty objects are considered prefilled by default).
162+ */
163+ static _isPrefilled ( modelValue ) {
164+ return modelValue . checked ;
115165 }
116166
117167 /**
@@ -126,32 +176,15 @@ export const ChoiceInputMixin = superclass =>
126176
127177 /**
128178 * @override
129- * Override FormatMixin default dispatching of model-value-changed as it only does a simple
130- * comparision which is not enough in js because
131- * { value: 'foo', checked: true } !== { value: 'foo', checked: true }
132- * We do our own "deep" comparision.
133- *
134- * @param {object } modelValue
135- * @param {object } modelValue the old one
179+ * hasChanged is designed for async (updated) callback, also check for sync
180+ * (_requestUpdate) callback
136181 */
137- // TODO: consider making a generic option inside FormatMixin for deep object comparisons when
138- // modelValue is an object
139- _dispatchModelValueChangedEvent ( { modelValue } , { modelValue : old } ) {
140- let changed = true ;
141- if ( old ) {
142- changed = modelValue . value !== old . value || modelValue . checked !== old . checked ;
143- }
144- if ( changed ) {
145- this . dispatchEvent (
146- new CustomEvent ( 'model-value-changed' , { bubbles : true , composed : true } ) ,
147- ) ;
182+ _onModelValueChanged ( { modelValue } , { modelValue : old } ) {
183+ if ( this . constructor . _classProperties . get ( 'modelValue' ) . hasChanged ( modelValue , old ) ) {
184+ super . _onModelValueChanged ( { modelValue } ) ;
148185 }
149186 }
150187
151- _reflectCheckedToCssClass ( ) {
152- this . classList [ this . choiceChecked ? 'add' : 'remove' ] ( 'state-checked' ) ;
153- }
154-
155188 /**
156189 * @override
157190 * Overridden from FormatMixin, since a different modelValue is used for choice inputs.
@@ -171,11 +204,30 @@ export const ChoiceInputMixin = superclass =>
171204
172205 /**
173206 * @override
174- * Overridden from ValidateMixin , since a different modelValue is used for choice inputs.
207+ * Overridden from Field , since a different modelValue is used for choice inputs.
175208 */
176209 __isRequired ( modelValue ) {
177210 return {
178211 required : ! ! modelValue . checked ,
179212 } ;
180213 }
214+
215+ /**
216+ * @deprecated use .checked
217+ */
218+ get choiceChecked ( ) {
219+ return this . checked ;
220+ }
221+
222+ /**
223+ * @deprecated use .checked
224+ */
225+ set choiceChecked ( c ) {
226+ this . checked = c ;
227+ }
228+
229+ /** @deprecated for styling purposes, use [checked] attribute */
230+ _reflectCheckedToCssClass ( ) {
231+ this . classList [ this . checked ? 'add' : 'remove' ] ( 'state-checked' ) ;
232+ }
181233 } ;
0 commit comments