diff --git a/Plugin.php b/Plugin.php
new file mode 100644
index 0000000..af4bae0
--- /dev/null
+++ b/Plugin.php
@@ -0,0 +1,76 @@
+ 'YearPicker',
+ 'description' => 'Add a Year Picker form widget',
+ 'author' => 'LucasPalomba',
+ 'icon' => 'icon-leaf'
+ ];
+ }
+
+ /**
+ * Register method, called when the plugin is first registered.
+ *
+ * @return void
+ */
+ public function register()
+ {
+
+ }
+
+ /**
+ * Boot method, called right before the request route.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+
+ $properties = [
+ 'minYear' => [
+ 'title' => 'Min. Year',
+ 'type' => 'string'
+ ],
+ 'maxYear' => [
+ 'title' => 'Max. Year',
+ 'type' => 'string'
+
+ ],
+ ];
+
+ if(PluginManager::instance()->exists('RainLab.Builder')){
+ Event::listen('pages.builder.registerControls', function($controlLibrary) use($properties) {
+ $controlLibrary->registerControl(
+ 'yearpicker',
+ 'Year Picker',
+ 'Sélecteur d\'année',
+ \RainLab\Builder\Classes\ControlLibrary::GROUP_WIDGETS,
+ 'icon-file-image-o',
+ $controlLibrary->getStandardProperties([], $properties),
+ 'LucasPalomba\YearPicker\Classes\ControlDesignTimeProvider'
+ );
+ });
+ }
+ }
+ public function registerFormWidgets()
+ {
+ return [
+ \LucasPalomba\YearPicker\FormWidgets\YearPicker::class => 'yearpicker'
+ ];
+ }
+}
diff --git a/classes/ControlDesignTimeProvider.php b/classes/ControlDesignTimeProvider.php
new file mode 100644
index 0000000..317e6ce
--- /dev/null
+++ b/classes/ControlDesignTimeProvider.php
@@ -0,0 +1,9 @@
+defaultControlsTypes[] = 'yearpicker';
+ }
+}
\ No newline at end of file
diff --git a/classes/controldesigntimeprovider/_control-yearpicker.htm b/classes/controldesigntimeprovider/_control-yearpicker.htm
new file mode 100644
index 0000000..4b42708
--- /dev/null
+++ b/classes/controldesigntimeprovider/_control-yearpicker.htm
@@ -0,0 +1,3 @@
+
+ = e(trans('lucaspalomba.yearpicker::lang.yearpicker.placeholder')) ?>
+
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..ec881aa
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,8 @@
+{
+ "name": "lucaspalomba/yearpicker-plugin",
+ "type": "october-plugin",
+ "description": "No description provided yet...",
+ "require": {
+ "composer/installers": "~1.0"
+ }
+}
diff --git a/formwidgets/YearPicker.php b/formwidgets/YearPicker.php
new file mode 100644
index 0000000..cacfad2
--- /dev/null
+++ b/formwidgets/YearPicker.php
@@ -0,0 +1,58 @@
+fillFromConfig([
+ 'minYear',
+ 'maxYear',
+ ]);
+ }
+
+ public function render(){
+ $this->prepareVars();
+ $this->addCss('css/yearpicker.css');
+ $this->addJs('js/yearpicker.js');
+ return $this->makePartial('yearpicker');
+ }
+
+ public function prepareVars(){
+ $this->vars['name'] = $this->getFieldName();
+ $this->vars['value'] = $this->getLoadValue();
+ $this->vars['field'] = $this->formField;
+ $this->vars['minYear'] = $this->minYear;
+ $this->vars['maxYear'] = $this->maxYear;
+ }
+
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveValue($value)
+ {
+ if (!strlen($value)) {
+ return null;
+ }
+
+ return $value;
+ }
+}
\ No newline at end of file
diff --git a/formwidgets/yearpicker/assets/css/yearpicker.css b/formwidgets/yearpicker/assets/css/yearpicker.css
new file mode 100644
index 0000000..4d1576a
--- /dev/null
+++ b/formwidgets/yearpicker/assets/css/yearpicker.css
@@ -0,0 +1,105 @@
+:root {
+ --background-color: #fff;
+ --border-color: #95a6a733;
+ --text-color: #555;
+ --selected-text-color: #6bc48d;
+ --hover-background-color: #eee;
+}
+
+.yearpicker-container {
+ position: absolute;
+ color: var(--text-color);
+ border: 1px solid var(--border-color);
+ background-color: var(--background-color);
+ box-shadow: 0 0 32px rgb(67 86 100 / 20%);;
+ border-radius: 8px;
+ font-family: sans-serif;
+ font-size: 12px;
+ margin-top: 5px;
+ width: 310px;
+ z-index: 10100;
+}
+
+.yearpicker-header {
+ display: flex;
+ width: 100%;
+ height: 2.5rem;
+ border-bottom: 1px solid var(--border-color);
+ align-items: center;
+ justify-content: space-between;
+ padding:0.25rem 1rem;
+}
+
+.yearpicker-prev,
+.yearpicker-next {
+ background-color: transparent;
+ background-position: 50%;
+ background-repeat: no-repeat;
+ background-size: 75% 75%;
+ border: 0;
+ cursor: pointer;
+ display: block;
+ height: 30px;
+ opacity: .5;
+ outline: none;
+ overflow: hidden;
+ padding: 0;
+ position: relative;
+ *position: absolute;
+ text-indent: 100%;
+ *top: 0;
+ white-space: nowrap;
+ width: 20px;
+}
+
+.yearpicker-prev{
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==);
+}
+.yearpicker-next{
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=);
+}
+
+.yearpicker-prev:hover,
+.yearpicker-next:hover {
+ opacity: 1;
+}
+
+.yearpicker-current{
+ line-height: 1;
+}
+
+.yearpicker-year {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ padding: 0.5rem;
+}
+
+.yearpicker-items {
+ list-style: none;
+ padding: 1rem 0.5rem;
+ flex: 0 0 33.3%;
+ width: 100%;
+}
+
+.yearpicker-items:hover {
+ background-color: var(--hover-background-color);
+ color: var(--selected-text-color);
+ cursor: pointer;
+}
+
+.yearpicker-items.selected {
+ background: var(--selected-text-color);
+ color:#fff;
+}
+
+.hide {
+ display: none;
+}
+
+.yearpicker-items.disabled {
+ pointer-events: none;
+ color: #bbb;
+}
\ No newline at end of file
diff --git a/formwidgets/yearpicker/assets/js/yearpicker.js b/formwidgets/yearpicker/assets/js/yearpicker.js
new file mode 100644
index 0000000..01bddb3
--- /dev/null
+++ b/formwidgets/yearpicker/assets/js/yearpicker.js
@@ -0,0 +1,512 @@
++function ($) { "use strict";
+const version = '1.0.0';
+const appName = 'YearPicker';
+
+
+var defaults = {
+ // Auto Hide
+ autoHide: true,
+ // The Initial Date
+ year: null,
+ // Start Date
+ startYear: null,
+ // End Date
+ endYear: null,
+ // A element tag items
+ itemTag: 'li',
+ //css class selected date item
+ selectedClass: 'selected',
+ // css class disabled
+ disabledClass: 'disabled',
+ hideClass: 'hide',
+ highlightedClass: 'highlighted',
+ template: `
+`,
+
+ // Event shortcuts
+ show: null,
+ hide: null,
+ pick: null
+};
+
+var window = typeof window !== 'undefained' ? window : {};
+
+var event_click = 'click.';
+var event_focus = 'focus.';
+var event_keyup = 'keyup.';
+var event_selected = 'selected.';
+var event_show = 'show.';
+var event_hide = 'hide.';
+
+var methods = {
+ // Show datepicker
+ showView: function showView() {
+ this.unbuild();
+ if (!this.build) {
+ this.init();
+ }
+
+ if (this.show) {
+ return;
+ }
+
+ if (this.trigger(event_show).isDefaultPrevented()) {
+ return;
+ }
+ this.show = true;
+ var $template = this.$template,
+ options = this.options;
+
+ $template.removeClass(options.hideClass).on(event_click, $.proxy(this.click, this));
+ $(document).on(event_click, this.onGlobalClick = proxy(this.globalClick, this));
+ this.place();
+ },
+
+ // Hide the datepicker
+ hideView: function hideView() {
+ if (!this.show) {
+ return;
+ }
+
+ if (this.trigger(event_hide).isDefaultPrevented()) {
+ return;
+ }
+
+ var $template = this.$template,
+ options = this.options;
+
+ $template.addClass(options.hideClass).off(event_click,this.click);
+ $(document).off(event_click,this.onGlobalClick);
+ this.show = false;
+ },
+ // toggle show and hide
+ toggle: function toggle() {
+ if (this.show) {
+ this.hideView();
+ } else {
+ this.show();
+ }
+ },
+ setStartYear: function setStartYear(year) {
+ this.startYear = year;
+
+ if (this.build) {
+ this.render();
+ }
+ },
+ setEndYear: function setEndYear(year) {
+ this.endYear = year;
+ if (this.build) {
+ this.render();
+ }
+ }
+};
+
+var handlers = {
+ click: function click(e) {
+ var $target = $(e.target);
+ var options = this.options;
+ var viewYear = this.viewYear;
+ if ($target.hasClass('disabled')) {
+ return;
+ }
+ var view = $target.data('view')
+ switch (view) {
+ case 'yearpicker-prev':
+ var year = viewYear - 12;
+ this.viewYear = year;
+ this.renderYear();
+ break;
+ case 'yearpicker-next':
+ var year = viewYear + 12;
+ this.viewYear = year;
+ this.renderYear();
+ break;
+ case 'yearpicker-items':
+ this.year = parseInt($target.html());
+ this.renderYear();
+ this.hideView();
+ break;
+ default:
+ break;
+ }
+ },
+ globalClick: function globalClick(_ref) {
+ var target = _ref.target;
+ var element = this.element;
+ var hidden = true;
+
+ if (target !== document) {
+ while (target === element || $(target).closest('.yearpicker-header').length === 1) {
+ hidden = false;
+ break;
+ }
+
+ target = target.parentNode;
+ }
+
+ if (hidden) {
+ this.hideView();
+ }
+ }
+}
+
+var render = {
+ renderYear: function renderYear() {
+ var options = this.options,
+ startYear = this.startYear,
+ endYear = this.endYear;
+ var disabledClass = options.disabledClass;
+
+ // viewed year in the calenter
+ var viewYear = this.viewYear;
+ // selected year
+ var selectedYear = this.year;
+ var now = new Date();
+ // current year
+ var thisYear = now.getFullYear();
+
+ var start = -5;
+ var end = 6;
+ var items = [];
+ var prevDisabled = false;
+ var nextDisabled = false;
+ var i = void 0;
+
+ for (i = start; i <= end; i++) {
+ var year = viewYear + i;
+ var disabled = false;
+
+ if (startYear) {
+ disabled = year < startYear;
+ if (i === start) {
+ prevDisabled = disabled;
+ }
+ }
+
+ if (!disabled && endYear) {
+ disabled = year > endYear;
+ if (i === end) {
+ nextDisabled = disabled;
+ }
+ }
+
+ // check for this is a selected year
+ var isSelectedYear = year === selectedYear;
+ var view = isSelectedYear ? 'yearpicker-items' : 'yearpicker-items';
+ items.push(this.createItem({
+ selected: isSelectedYear,
+ disabled: disabled,
+ text: viewYear + i,
+ view: disabled ? 'yearpicker-items disabled' : view,
+ highlighted: year === thisYear
+ }));
+ }
+
+ this.yearsPrev.toggleClass(disabledClass, prevDisabled);
+ this.yearsNext.toggleClass(disabledClass, nextDisabled);
+ this.yearsCurrent.html(selectedYear);
+ this.yearsBody.html(items.join(' '));
+ this.setValue();
+ }
+}
+
+function isString(value) {
+ return typeof value === 'string';
+}
+
+function isNumber(value) {
+ return typeof value === 'number' && value !== 'NaN';
+}
+
+function isUndefained(value) {
+ return typeof value === 'undefined';
+}
+
+function proxy(fn, context) {
+
+ for (var len = arguments.length, args = Array(len > 2 ? len - 2 : 0), key = 2; key < len; key++) {
+ args[key - 2] = arguments[key];
+ }
+
+ return function () {
+ for (var len2 = arguments.length, args2 = Array(len2), key2 = 0; key2 < len2; key2++) {
+ args2[key2] = arguments[key2];
+ }
+
+ return fn.apply(context, args.concat(args2));
+ }
+}
+
+'use strict';
+
+var _setupError = 'YearPicker Error';
+if (isUndefained(jQuery)) {
+ alert(`${appName} ${version} requires jQuery`);
+}
+
+var classCheck = function (instance, constractor) {
+ if (!(instance instanceof constractor)) {
+ alert('cannot call a class as instance of function!!!');
+ }
+}
+
+var class_top_left = appName + '-top-left';
+var class_top_right = appName + '-top-right';
+var class_bottom_left = appName + '-bottom-left';
+var class_bottom_right = appName + '-bottom-right';
+var class_placements = [class_top_left, class_top_right, class_bottom_left, class_bottom_right].join(' ');
+
+var Yearpicker = function () {
+ function Yearpicker(element) {
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ classCheck(this, Yearpicker);
+
+ this.$element = $(element);
+ this.element = element;
+ this.options = $.extend({}, defaults, options);
+ this.build = false;
+ this.show = false;
+ this.startYear = this.$element.data('min-year') || null;
+ this.endYear = this.$element.data('max-year') || null;
+
+ this.create();
+ }
+
+ // yearpicker
+ Yearpicker.prototype = {
+ create: function () {
+ var $this = this.$element,
+ options = this.options;
+ var startYear = this.startYear,
+ endYear = this.endYear,
+ year = this.year;
+
+ //this.trigger = $(options.trigger);
+ this.isInput = $this.is('input') || $this.is('textarea');
+ this.initialValue = this.getValue();
+ this.oldValue = this.initialValue;
+ year = year || this.initialValue || new Date().getFullYear();
+
+ if (startYear) {
+ if (year < startYear) {
+ year = startYear;
+ }
+ this.startYear = startYear;
+ }
+
+ if (endYear) {
+ if (year > endYear) {
+ year = endYear;
+ }
+ this.endYear = endYear;
+ }
+
+ this.year = year;
+ this.viewYear = year;
+ this.initialYear = year;
+ this.bind();
+ this.init();
+ },
+ init: function () {
+ if (this.build) {
+ return;
+ }
+ this.build = true;
+
+ var $this = this.$element,
+ options = this.options;
+ var $template = $(options.template);
+ this.$template = $template;
+
+ this.yearsPrev = $template.find('.yearpicker-prev');
+ this.yearsCurrent = $template.find('.yearpicker-current');
+ this.yearsNext = $template.find('.yearpicker-next');
+ this.yearsBody = $template.find('.yearpicker-year');
+
+ $template.addClass(options.hideClass);
+ $(document.body).append($template.addClass(appName + '-dropdown'));
+ this.renderYear();
+
+ },
+ unbuild: function () {
+ if (!this.build) {
+ return;
+ }
+ this.build = false;
+ this.$template.remove();
+ },
+ // assign a events
+ bind: function () {
+ var $this = this.$element,
+ options = this.options;
+
+ if ($.isFunction(options.show)) {
+ $this.on(event_show, options.show);
+ }
+ if ($.isFunction(options.hide)) {
+ $this.on(event_hide, options.hide);
+ }
+ if ($.isFunction(options.click)) {
+ $this.on(event_click, options.click);
+ }
+ if (this.isInput) {
+ $this.on(event_focus, $.proxy(this.showView, this));
+ } else {
+ $this.on(event_click, $.proxy(this.showView, this));
+ }
+
+ },
+ getValue: function () {
+ var $this = this.$element;
+ var value = this.isInput ? $this.val() : $this.text();
+ value = parseInt(value);
+ return this.isInput ? parseInt($this.val()) : $this.text();
+ },
+ setValue: function () {
+ var $this = this.$element;
+ var value = this.year;
+ if (this.isInput) {
+ $this.val(value);
+ } else {
+ $this.html(value);
+ }
+ },
+ trigger: function (type, data) {
+ var e = $.Event(type, data);
+ this.$element.trigger(e);
+ return e;
+ },
+ place: function () {
+
+ var $this = this.$element,
+ options = this.options,
+ $template = this.$template;
+
+ var containerWidth = $(document).outerWidth(),
+ containerHeight = $(document).outerHeight(),
+ elementWidth = $this.outerWidth(),
+ elementHeight = $this.outerHeight(),
+ width = $template.width(),
+ height = $template.height();
+
+ var elementOffset = $this.offset(),
+ top = elementOffset.top,
+ left = elementOffset.left;
+
+ var offset = parseFloat(options.offset);
+ var placements = class_top_left;
+
+ offset = isNaN(offset) ? 10 : offset;
+
+ // positioning the y axis
+ if (top > height && top + elementHeight + height > containerHeight) {
+ top -= height + offset;
+ placements = class_bottom_left;
+ } else {
+ top += elementHeight + offset;
+ }
+
+ // positioning the x axis
+ if (left + width > containerWidth) {
+ left += elementWidth - width;
+ placements = placements.replace('left', 'right');
+ }
+
+ $template.removeClass(class_placements).addClass(placements).css({
+ top: top,
+ left: left,
+ zIndex: parseInt(this.zIndex, 10)
+ })
+
+ },
+ createItem: function (data) {
+ var options = this.options;
+ var itemTag = options.itemTag;
+
+ var items = {
+ text: '',
+ view: '',
+ selected: false,
+ disabled: false,
+ highlighted: false
+ };
+
+ var classes = [];
+ $.extend(items, data);
+ if (items.selected) {
+ classes.push(options.selectedClass);
+ }
+
+ if (items.disabled) {
+ classes.push(options.disabledClass);
+ }
+
+ if (items.highlighted) {
+ classes.push(options.highlightedClass)
+ }
+
+ return `<${itemTag} class="${items.view} ${classes.join(' ')}" data-view="${items.view}">${items.text}${itemTag}>`
+ }
+ }
+
+ return Yearpicker;
+}();
+
+if ($.extend) {
+ $.extend(Yearpicker.prototype, methods, render, handlers);
+}
+
+if ($.fn) {
+ $.fn.yearpicker = function jQueryYearpicker(option) {
+ for (var len = arguments.length, args = Array(len > 1 ? len - 1 : 0), key = 1; key < len; key++) {
+ args[key - 1] = arguments[key];
+ }
+ var result = void 0;
+
+ this.each(function (i, element) {
+ var $element = $(element);
+ var isDestory = option === 'destroy';
+ var yearpicker = $element.data(appName);
+
+ if (!yearpicker) {
+ if (isDestory) {
+ return;
+ }
+ var options = $.extend({}, $element.data(), $.isPlainObject(option) && option);
+ yearpicker = new Yearpicker(element, options);
+ $element.data(appName, yearpicker);
+ }
+ if (isString(option)) {
+ var fn = yearpicker[option];
+
+ if ($.isFunction(fn)) {
+ result = fn.apply(yearpicker, args);
+
+ if (isDestory) {
+ $element.removeData(appName);
+ }
+ }
+ }
+ });
+
+ return !isUndefained(result) ? result : this;
+ };
+ $.fn.yearpicker.constractor = Yearpicker;
+};
+$(document).render(function(){
+ $('[data-yearpicker]').yearpicker();
+})
+
+}(window.jQuery);
diff --git a/formwidgets/yearpicker/partials/_yearpicker.php b/formwidgets/yearpicker/partials/_yearpicker.php
new file mode 100644
index 0000000..ed0a539
--- /dev/null
+++ b/formwidgets/yearpicker/partials/_yearpicker.php
@@ -0,0 +1,31 @@
+previewMode): ?>
+ = $value ?>
+
+
+
+
+
+
+
+ getAttributes() ?>
+ data-min-year="= $minYear ?>"
+ data-max-year="= $maxYear ?>"
+ data-yearpicker />
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lang/en/lang.php b/lang/en/lang.php
new file mode 100644
index 0000000..4fcf2ca
--- /dev/null
+++ b/lang/en/lang.php
@@ -0,0 +1,7 @@
+ [
+ 'placeholder' => 'Year picker'
+ ]
+];
diff --git a/lang/fr/lang.php b/lang/fr/lang.php
new file mode 100644
index 0000000..4fcf2ca
--- /dev/null
+++ b/lang/fr/lang.php
@@ -0,0 +1,7 @@
+ [
+ 'placeholder' => 'Year picker'
+ ]
+];
diff --git a/updates/version.yaml b/updates/version.yaml
new file mode 100644
index 0000000..f9f4306
--- /dev/null
+++ b/updates/version.yaml
@@ -0,0 +1 @@
+1.0.1: First version of YearPicker