pickadate-rails 1.3.2 → 1.4.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.
@@ -1,21 +1,11 @@
1
1
 
2
2
  /*!
3
- * pickadate.js v3.3.2, 2013/12/28
3
+ * pickadate.js v3.4.0, 2014/02/15
4
4
  * By Amsul, http://amsul.ca
5
5
  * Hosted on http://amsul.github.io/pickadate.js
6
6
  * Licensed under MIT
7
7
  */
8
8
 
9
- /*jshint
10
- debug: true,
11
- devel: true,
12
- browser: true,
13
- asi: true,
14
- unused: true,
15
- boss: true,
16
- eqnull: true
17
- */
18
-
19
9
  (function ( factory ) {
20
10
 
21
11
  // Register as an anonymous module.
@@ -42,7 +32,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
42
32
  var
43
33
  // The state of the picker.
44
34
  STATE = {
45
- id: Math.abs( ~~( Math.random() * 1e9 ) )
35
+ id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) )
46
36
  },
47
37
 
48
38
 
@@ -93,162 +83,26 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
93
83
  ELEMENT.autofocus = ELEMENT == document.activeElement
94
84
  ELEMENT.type = 'text'
95
85
  ELEMENT.readOnly = !SETTINGS.editable
86
+ ELEMENT.id = ELEMENT.id || STATE.id
96
87
 
97
88
 
98
89
  // Create a new picker component with the settings.
99
- P.component = new COMPONENT( P, SETTINGS )
100
-
101
-
102
- // Create the picker root with a new wrapped holder and bind the events.
103
- P.$root = $( PickerConstructor._.node( 'div', createWrappedComponent(), CLASSES.picker ) ).
104
- on({
105
-
106
- // When something within the root is focused, stop from bubbling
107
- // to the doc and remove the “focused” state from the root.
108
- focusin: function( event ) {
109
- P.$root.removeClass( CLASSES.focused )
110
- event.stopPropagation()
111
- },
112
-
113
- // When something within the root holder is clicked, stop it
114
- // from bubbling to the doc.
115
- 'mousedown click': function( event ) {
116
-
117
- var target = event.target
118
-
119
- // Make sure the target isn’t the root holder so it can bubble up.
120
- if ( target != P.$root.children()[ 0 ] ) {
121
-
122
- event.stopPropagation()
90
+ P.component = new COMPONENT(P, SETTINGS)
123
91
 
124
- // * For mousedown events, cancel the default action in order to
125
- // prevent cases where focus is shifted onto external elements
126
- // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
127
- // Also, for Firefox, don’t prevent action on the `option` element.
128
- if ( event.type == 'mousedown' && !$( target ).is( ':input' ) && target.nodeName != 'OPTION' ) {
129
92
 
130
- event.preventDefault()
131
-
132
- // Re-focus onto the element so that users can click away
133
- // from elements focused within the picker.
134
- ELEMENT.focus()
135
- }
136
- }
137
- }
138
- }).
139
-
140
- // If there’s a click on an actionable element, carry out the actions.
141
- on( 'click', '[data-pick], [data-nav], [data-clear]', function() {
142
-
143
- var $target = $( this ),
144
- targetData = $target.data(),
145
- targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),
146
-
147
- // * For IE, non-focusable elements can be active elements as well
148
- // (http://stackoverflow.com/a/2684561).
149
- activeElement = document.activeElement
150
- activeElement = activeElement && ( activeElement.type || activeElement.href )
151
-
152
- // If it’s disabled or nothing inside is actively focused, re-focus the element.
153
- if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
154
- ELEMENT.focus()
155
- }
156
-
157
- // If something is superficially changed, update the `highlight` based on the `nav`.
158
- if ( targetData.nav && !targetDisabled ) {
159
- P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
160
- }
161
-
162
- // If something is picked, set `select` then close with focus.
163
- else if ( PickerConstructor._.isInteger( targetData.pick ) && !targetDisabled ) {
164
- P.set( 'select', targetData.pick ).close( true )
165
- }
166
-
167
- // If a “clear” button is pressed, empty the values and close with focus.
168
- else if ( targetData.clear ) {
169
- P.clear().close( true )
170
- }
171
- }) //P.$root
93
+ // Create the picker root with a holder and then prepare it.
94
+ P.$root = $( PickerConstructor._.node('div', createWrappedComponent(), CLASSES.picker, 'id="' + ELEMENT.id + '_root"') )
95
+ prepareElementRoot()
172
96
 
173
97
 
174
98
  // If there’s a format for the hidden input element, create the element.
175
99
  if ( SETTINGS.formatSubmit ) {
176
-
177
- P._hidden = $(
178
- '<input ' +
179
- 'type=hidden ' +
180
-
181
- // Create the name by using the original input plus a prefix and suffix.
182
- 'name="' + ( typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '' ) +
183
- ELEMENT.name +
184
- ( typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' ) +
185
- '"' +
186
-
187
- // If the element has a value, set the hidden value as well.
188
- ( $ELEMENT.data( 'value' ) || ELEMENT.value ?
189
- ' value="' + PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.formatSubmit, P.component.item.select ] ) + '"' :
190
- ''
191
- ) +
192
- '>'
193
- )[ 0 ]
100
+ prepareElementHidden()
194
101
  }
195
102
 
196
103
 
197
104
  // Prepare the input element.
198
- $ELEMENT.
199
-
200
- // Store the picker data by component name.
201
- data( NAME, P ).
202
-
203
- // Add the “input” class name.
204
- addClass( CLASSES.input ).
205
-
206
- // If there’s a `data-value`, update the value of the element.
207
- val( $ELEMENT.data( 'value' ) ? PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.format, P.component.item.select ] ) : ELEMENT.value ).
208
-
209
- // Insert the hidden input after the element.
210
- after( P._hidden ).
211
-
212
- // On focus/click, open the picker and adjust the root “focused” state.
213
- on( 'focus.P' + STATE.id + ' click.P' + STATE.id, focusToOpen ).
214
-
215
- // If the value changes, update the hidden input with the correct format.
216
- on( 'change.P' + STATE.id, function() {
217
- if ( P._hidden ) {
218
- P._hidden.value = ELEMENT.value ? PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.formatSubmit, P.component.item.select ] ) : ''
219
- }
220
- })
221
-
222
- // Only bind keydown events if the element isn’t editable.
223
- if ( !SETTINGS.editable ) $ELEMENT.
224
-
225
- // Handle keyboard event based on the picker being opened or not.
226
- on( 'keydown.P' + STATE.id, function( event ) {
227
-
228
- var keycode = event.keyCode,
229
-
230
- // Check if one of the delete keys was pressed.
231
- isKeycodeDelete = /^(8|46)$/.test( keycode )
232
-
233
- // For some reason IE clears the input value on “escape”.
234
- if ( keycode == 27 ) {
235
- P.close()
236
- return false
237
- }
238
-
239
- // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
240
- if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[ keycode ] ) {
241
-
242
- // Prevent it from moving the page and bubbling to doc.
243
- event.preventDefault()
244
- event.stopPropagation()
245
-
246
- // If `delete` was pressed, clear the values and close the picker.
247
- // Otherwise open the picker.
248
- if ( isKeycodeDelete ) { P.clear().close() }
249
- else { P.open() }
250
- }
251
- })
105
+ prepareElement()
252
106
 
253
107
 
254
108
  // Insert the root as specified in the settings.
@@ -322,7 +176,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
322
176
  // the events (after a tick for IE - see `P.close`).
323
177
  $ELEMENT.removeClass( CLASSES.input ).removeData( NAME )
324
178
  setTimeout( function() {
325
- $ELEMENT.off( '.P' + STATE.id )
179
+ $ELEMENT.off( '.' + STATE.id )
326
180
  }, 0)
327
181
 
328
182
  // Restore the element state
@@ -350,9 +204,11 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
350
204
 
351
205
  // Add the “active” class.
352
206
  $ELEMENT.addClass( CLASSES.active )
207
+ aria( ELEMENT, 'expanded', true )
353
208
 
354
209
  // Add the “opened” class to the picker root.
355
210
  P.$root.addClass( CLASSES.opened )
211
+ aria( P.$root[0], 'hidden', false )
356
212
 
357
213
  // If we have to give focus, bind the element and doc events.
358
214
  if ( dontGiveFocus !== false ) {
@@ -364,7 +220,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
364
220
  $ELEMENT.trigger( 'focus' )
365
221
 
366
222
  // Bind the document events.
367
- $document.on( 'click.P' + STATE.id + ' focusin.P' + STATE.id, function( event ) {
223
+ $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) {
368
224
 
369
225
  var target = event.target
370
226
 
@@ -372,14 +228,17 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
372
228
  // * Don’t worry about clicks or focusins on the root because those don’t bubble up.
373
229
  // Also, for Firefox, a click on an `option` element bubbles up directly
374
230
  // to the doc. So make sure the target wasn't the doc.
375
- if ( target != ELEMENT && target != document ) {
231
+ // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling,
232
+ // which causes the picker to unexpectedly close when right-clicking it. So make
233
+ // sure the event wasn’t a right-click.
234
+ if ( target != ELEMENT && target != document && event.which != 3 ) {
376
235
 
377
236
  // If the target was the holder that covers the screen,
378
237
  // keep the element focused to maintain tabindex.
379
238
  P.close( target === P.$root.children()[0] )
380
239
  }
381
240
 
382
- }).on( 'keydown.P' + STATE.id, function( event ) {
241
+ }).on( 'keydown.' + STATE.id, function( event ) {
383
242
 
384
243
  var
385
244
  // Get the keycode.
@@ -440,17 +299,20 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
440
299
  // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :|
441
300
  // The focus is triggered *after* the close has completed - causing it
442
301
  // to open again. So unbind and rebind the event at the next tick.
443
- $ELEMENT.off( 'focus.P' + STATE.id ).trigger( 'focus' )
302
+ $ELEMENT.off( 'focus.' + STATE.id ).trigger( 'focus' )
444
303
  setTimeout( function() {
445
- $ELEMENT.on( 'focus.P' + STATE.id, focusToOpen )
304
+ $ELEMENT.on( 'focus.' + STATE.id, focusToOpen )
446
305
  }, 0 )
447
306
  }
448
307
 
449
308
  // Remove the “active” class.
450
309
  $ELEMENT.removeClass( CLASSES.active )
310
+ aria( ELEMENT, 'expanded', false )
451
311
 
452
312
  // Remove the “opened” and “focused” class from the picker root.
453
313
  P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused )
314
+ aria( P.$root[0], 'hidden', true )
315
+ aria( P.$root[0], 'selected', false )
454
316
 
455
317
  // If it’s already closed, do nothing more.
456
318
  if ( !STATE.open ) return P
@@ -459,7 +321,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
459
321
  STATE.open = false
460
322
 
461
323
  // Unbind the document events.
462
- $document.off( '.P' + STATE.id )
324
+ $document.off( '.' + STATE.id )
463
325
 
464
326
  // Trigger the queued “close” events.
465
327
  return P.trigger( 'close' )
@@ -500,14 +362,14 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
500
362
  thingValue = thingObject[ thingItem ]
501
363
 
502
364
  // First, if the item exists and there’s a value, set it.
503
- if ( P.component.item[ thingItem ] ) {
365
+ if ( thingItem in P.component.item ) {
504
366
  P.component.set( thingItem, thingValue, options )
505
367
  }
506
368
 
507
369
  // Then, check to update the element value and broadcast a change.
508
370
  if ( thingItem == 'select' || thingItem == 'clear' ) {
509
- $ELEMENT.val( thingItem == 'clear' ? '' :
510
- PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.format, P.component.get( thingItem ) ] )
371
+ $ELEMENT.val( thingItem == 'clear' ?
372
+ '' : P.get( thingItem, SETTINGS.format )
511
373
  ).trigger( 'change' )
512
374
  }
513
375
  }
@@ -540,9 +402,13 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
540
402
  }
541
403
 
542
404
  // Check if a component item exists, return that.
543
- if ( P.component.item[ thing ] ) {
405
+ if ( thing in P.component.item ) {
544
406
  if ( typeof format == 'string' ) {
545
- return PickerConstructor._.trigger( P.component.formats.toString, P.component, [ format, P.component.get( thing ) ] )
407
+ return PickerConstructor._.trigger(
408
+ P.component.formats.toString,
409
+ P.component,
410
+ [ format, P.component.get( thing ) ]
411
+ )
546
412
  }
547
413
  return P.component.get( thing )
548
414
  }
@@ -584,6 +450,23 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
584
450
  }, //on
585
451
 
586
452
 
453
+
454
+ /**
455
+ * Unbind events on the things.
456
+ */
457
+ off: function() {
458
+ var i, thingName,
459
+ names = arguments;
460
+ for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) {
461
+ thingName = names[i]
462
+ if ( thingName in STATE.methods ) {
463
+ delete STATE.methods[thingName]
464
+ }
465
+ }
466
+ return P
467
+ },
468
+
469
+
587
470
  /**
588
471
  * Fire off method events.
589
472
  */
@@ -637,6 +520,196 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
637
520
  } //createWrappedComponent
638
521
 
639
522
 
523
+
524
+ /**
525
+ * Prepare the input element with all bindings.
526
+ */
527
+ function prepareElement() {
528
+
529
+ $ELEMENT.
530
+
531
+ // Store the picker data by component name.
532
+ data(NAME, P).
533
+
534
+ // Add the “input” class name.
535
+ addClass(CLASSES.input).
536
+
537
+ // If there’s a `data-value`, update the value of the element.
538
+ val( $ELEMENT.data('value') ?
539
+ P.get('select', SETTINGS.format) :
540
+ ELEMENT.value
541
+ ).
542
+
543
+ // On focus/click, open the picker and adjust the root “focused” state.
544
+ on('focus.' + STATE.id + ' click.' + STATE.id, focusToOpen)
545
+
546
+
547
+ // Only bind keydown events if the element isn’t editable.
548
+ if ( !SETTINGS.editable ) {
549
+
550
+ // Handle keyboard event based on the picker being opened or not.
551
+ $ELEMENT.on('keydown.' + STATE.id, function(event) {
552
+
553
+ var keycode = event.keyCode,
554
+
555
+ // Check if one of the delete keys was pressed.
556
+ isKeycodeDelete = /^(8|46)$/.test(keycode)
557
+
558
+ // For some reason IE clears the input value on “escape”.
559
+ if ( keycode == 27 ) {
560
+ P.close()
561
+ return false
562
+ }
563
+
564
+ // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
565
+ if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) {
566
+
567
+ // Prevent it from moving the page and bubbling to doc.
568
+ event.preventDefault()
569
+ event.stopPropagation()
570
+
571
+ // If `delete` was pressed, clear the values and close the picker.
572
+ // Otherwise open the picker.
573
+ if ( isKeycodeDelete ) { P.clear().close() }
574
+ else { P.open() }
575
+ }
576
+ })
577
+ }
578
+
579
+
580
+ // Update the aria attributes.
581
+ aria(ELEMENT, {
582
+ haspopup: true,
583
+ expanded: false,
584
+ readonly: false,
585
+ owns: ELEMENT.id + '_root' + (P._hidden ? ' ' + P._hidden.id : '')
586
+ })
587
+ }
588
+
589
+
590
+ /**
591
+ * Prepare the root picker element with all bindings.
592
+ */
593
+ function prepareElementRoot() {
594
+
595
+ P.$root.
596
+
597
+ on({
598
+
599
+ // When something within the root is focused, stop from bubbling
600
+ // to the doc and remove the “focused” state from the root.
601
+ focusin: function( event ) {
602
+ P.$root.removeClass( CLASSES.focused )
603
+ aria( P.$root[0], 'selected', false )
604
+ event.stopPropagation()
605
+ },
606
+
607
+ // When something within the root holder is clicked, stop it
608
+ // from bubbling to the doc.
609
+ 'mousedown click': function( event ) {
610
+
611
+ var target = event.target
612
+
613
+ // Make sure the target isn’t the root holder so it can bubble up.
614
+ if ( target != P.$root.children()[ 0 ] ) {
615
+
616
+ event.stopPropagation()
617
+
618
+ // * For mousedown events, cancel the default action in order to
619
+ // prevent cases where focus is shifted onto external elements
620
+ // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
621
+ // Also, for Firefox, don’t prevent action on the `option` element.
622
+ if ( event.type == 'mousedown' && !$( target ).is( ':input' ) && target.nodeName != 'OPTION' ) {
623
+
624
+ event.preventDefault()
625
+
626
+ // Re-focus onto the element so that users can click away
627
+ // from elements focused within the picker.
628
+ ELEMENT.focus()
629
+ }
630
+ }
631
+ }
632
+ }).
633
+
634
+ // If there’s a click on an actionable element, carry out the actions.
635
+ on( 'click', '[data-pick], [data-nav], [data-clear]', function() {
636
+
637
+ var $target = $( this ),
638
+ targetData = $target.data(),
639
+ targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),
640
+
641
+ // * For IE, non-focusable elements can be active elements as well
642
+ // (http://stackoverflow.com/a/2684561).
643
+ activeElement = document.activeElement
644
+ activeElement = activeElement && ( activeElement.type || activeElement.href ) && activeElement
645
+
646
+ // If it’s disabled or nothing inside is actively focused, re-focus the element.
647
+ if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
648
+ ELEMENT.focus()
649
+ }
650
+
651
+ // If something is superficially changed, update the `highlight` based on the `nav`.
652
+ if ( targetData.nav && !targetDisabled ) {
653
+ P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
654
+ }
655
+
656
+ // If something is picked, set `select` then close with focus.
657
+ else if ( PickerConstructor._.isInteger( targetData.pick ) && !targetDisabled ) {
658
+ P.set( 'select', targetData.pick ).close( true )
659
+ }
660
+
661
+ // If a “clear” button is pressed, empty the values and close with focus.
662
+ else if ( targetData.clear ) {
663
+ P.clear().close( true )
664
+ }
665
+ }) //P.$root
666
+
667
+ aria( P.$root[0], 'hidden', true )
668
+ }
669
+
670
+
671
+ /**
672
+ * Prepare the hidden input element along with all bindings.
673
+ */
674
+ function prepareElementHidden() {
675
+
676
+ var id = [
677
+ typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '',
678
+ typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit'
679
+ ]
680
+
681
+ P._hidden = $(
682
+ '<input ' +
683
+ 'type=hidden ' +
684
+
685
+ // Create the name and ID by using the original
686
+ // input’s with a prefix and suffix.
687
+ 'name="' + id[0] + ELEMENT.name + id[1] + '"' +
688
+ 'id="' + id[0] + ELEMENT.id + id[1] + '"' +
689
+
690
+ // If the element has a value, set the hidden value as well.
691
+ (
692
+ $ELEMENT.data('value') || ELEMENT.value ?
693
+ ' value="' + P.get('select', SETTINGS.formatSubmit) + '"' :
694
+ ''
695
+ ) +
696
+ '>'
697
+ )[0]
698
+
699
+ $ELEMENT.
700
+
701
+ // If the value changes, update the hidden input with the correct format.
702
+ on('change.' + STATE.id, function() {
703
+ P._hidden.value = ELEMENT.value ?
704
+ P.get('select', SETTINGS.formatSubmit) :
705
+ ''
706
+ }).
707
+
708
+ // Insert the hidden input after the element.
709
+ after(P._hidden)
710
+ }
711
+
712
+
640
713
  // Separated for IE
641
714
  function focusToOpen( event ) {
642
715
 
@@ -644,7 +717,10 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
644
717
  event.stopPropagation()
645
718
 
646
719
  // If it’s a focus event, add the “focused” class to the root.
647
- if ( event.type == 'focus' ) P.$root.addClass( CLASSES.focused )
720
+ if ( event.type == 'focus' ) {
721
+ P.$root.addClass( CLASSES.focused )
722
+ aria( P.$root[0], 'selected', true )
723
+ }
648
724
 
649
725
  // And then finally open the picker.
650
726
  P.open()
@@ -791,7 +867,13 @@ PickerConstructor._ = {
791
867
  */
792
868
  isInteger: function( value ) {
793
869
  return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0
794
- }
870
+ },
871
+
872
+
873
+ /**
874
+ * Create ARIA attribute strings.
875
+ */
876
+ ariaAttr: ariaAttr
795
877
  } //PickerConstructor._
796
878
 
797
879
 
@@ -835,6 +917,37 @@ PickerConstructor.extend = function( name, Component ) {
835
917
 
836
918
 
837
919
 
920
+ function aria(element, attribute, value) {
921
+ if ( $.isPlainObject(attribute) ) {
922
+ for ( var key in attribute ) {
923
+ ariaSet(element, key, attribute[key])
924
+ }
925
+ }
926
+ else {
927
+ ariaSet(element, attribute, value)
928
+ }
929
+ }
930
+ function ariaSet(element, attribute, value) {
931
+ element.setAttribute(
932
+ (attribute == 'role' ? '' : 'aria-') + attribute,
933
+ value
934
+ )
935
+ }
936
+ function ariaAttr(attribute, data) {
937
+ if ( !$.isPlainObject(attribute) ) {
938
+ attribute = { attribute: data }
939
+ }
940
+ data = ''
941
+ for ( var key in attribute ) {
942
+ var attr = (key == 'role' ? '' : 'aria-') + key,
943
+ attrVal = attribute[key]
944
+ data += attrVal == null ? '' : attr + '="' + attribute[key] + '"'
945
+ }
946
+ return data
947
+ }
948
+
949
+
950
+
838
951
  // Expose the picker constructor.
839
952
  return PickerConstructor
840
953