1/** 2 * @class FlatpickrController 3 * @extends Controller 4 * @abstract 5 * @description 6 * Shared base controller for the Honey Forms date pickers. Not registered on 7 * its own â {@link FlatpickrDateController} and {@link FlatpickrTimeController} 8 * extend it and supply their picker-specific options via {@link pickerOptions}. 9 * 10 * Mounted on the field's wrapper element so the input and the trailing icons are 11 * all within scope: 12 * - Initializes {@link https://flatpickr.js.org/|Flatpickr} on the `input` target 13 * - Repositions the calendar inside a `<dialog>` so it escapes modal clipping 14 * - Swaps the calendar icon for a clear (Ã) button once a value is set, and 15 * clears the value (leaving the field blank) when that button is pressed 16 * 17 * @requires flatpickr 18 */ 19import { Controller } from "@hotwired/stimulus" 20import "flatpickr" 21 22export default class FlatpickrController extends Controller { 23 /** 24 * @static 25 * @property {string[]} targets 26 * @property {HTMLInputElement} inputTarget - The field Flatpickr enhances 27 * @property {HTMLElement} calendarTarget - Calendar icon, shown when empty 28 * @property {HTMLButtonElement} clearTarget - Clear button, shown when a value is set 29 */ 30 static targets = ["input", "calendar", "clear"] 31 32 /** 33 * The Flatpickr instance. 34 * @type {Object|null} 35 */ 36 flatpickr = null 37 38 /** 39 * Flatpickr options specific to the picker type. Overridden by subclasses. 40 * 41 * @returns {Object} Flatpickr configuration 42 */ 43 get pickerOptions() { 44 return {} 45 } 46 47 /** 48 * Initializes Flatpickr on the input target, gives its generated input a 49 * pointer cursor, and sets the initial clear/calendar icon state. Called 50 * automatically on connect. 51 * 52 * @method connect 53 * @returns {void} 54 */ 55 connect() { 56 const options = { 57 ...this.pickerOptions, 58 onChange: [() => this.updateClearVisibility()] 59 } 60 61 this.appendToDialog(options) 62 63 this.flatpickr = flatpickr(this.inputTarget, options) 64 65 // The field is read-only and opens the calendar on click, so give the 66 // visible input a pointer cursor. Flatpickr swaps in its own `altInput`, so 67 // style that element directly; set it inline to win over any inherited rule. 68 const field = this.flatpickr.altInput || this.inputTarget 69 field.style.cursor = "pointer" 70 71 this.updateClearVisibility() 72 } 73 74 /** 75 * Tears down Flatpickr so its alternate input is not duplicated when the 76 * controller reconnects (e.g. on Turbo navigation). 77 * 78 * @method disconnect 79 * @returns {void} 80 */ 81 disconnect() { 82 if (this.flatpickr) { 83 this.flatpickr.destroy() 84 this.flatpickr = null 85 } 86 } 87 88 /** 89 * Clears the selected date and leaves the field blank. Bound to the clear 90 * button via `data-action`. 91 * 92 * @method clear 93 * @returns {void} 94 */ 95 clear() { 96 this.flatpickr.clear() 97 this.updateClearVisibility() 98 } 99 100 /** 101 * Shows the clear button when the field has a value and is enabled; otherwise 102 * shows the calendar icon. 103 * 104 * @method updateClearVisibility 105 * @returns {void} 106 */ 107 updateClearVisibility() { 108 const showClear = this.flatpickr.selectedDates.length > 0 && !this.inputTarget.disabled 109 110 if (this.hasClearTarget) this.clearTarget.classList.toggle("hidden", !showClear) 111 if (this.hasCalendarTarget) this.calendarTarget.classList.toggle("hidden", showClear) 112 } 113 114 /** 115 * When the field is inside a `<dialog>` (modal), append the calendar inside 116 * the dialog so it stays in the browser's top layer, then reposition it after 117 * Flatpickr's own positioning runs. 118 * 119 * @method appendToDialog 120 * @param {Object} options - Flatpickr options to mutate in place 121 * @returns {void} 122 */ 123 appendToDialog(options) { 124 const dialog = this.element.closest("dialog") 125 if (!dialog) return 126 127 options.appendTo = dialog 128 options.onOpen = [(_dates, _str, instance) => { 129 requestAnimationFrame(() => { 130 const input = instance.altInput || instance.element 131 const inputRect = input.getBoundingClientRect() 132 const dialogRect = dialog.getBoundingClientRect() 133 const cal = instance.calendarContainer 134 cal.style.top = (inputRect.bottom - dialogRect.top + dialog.scrollTop) + "px" 135 cal.style.left = (inputRect.left - dialogRect.left + dialog.scrollLeft) + "px" 136 }) 137 }] 138 } 139}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.