PageSourceSearch

https://cdn2.futurefund.com/assets/honey/forms/controllers/flatpickr_controller-cfc523a5.js

js futurefund.com collected 2026-09-24 11:52:22 UTC 4,372 bytes, 139 lines download raw bytes

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.