stalefish 8.0.2 → 8.0.4

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.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Flatpickr adapter for Stalefish (halfcab/lit-based)
3
+ *
4
+ * Goals:
5
+ * - Always initialize flatpickr on a real Element (not a DocumentFragment/TemplateResult)
6
+ * - Defer initialization until the node is connected, to be SSR/DOM-safe
7
+ * - Store the instance on the wrapper element (`wrapper._flatpickr`) consistently
8
+ * - Provide simple attach/detach helpers for reuse across components
9
+ *
10
+ * Usage:
11
+ * import flatpickr from 'flatpickr'
12
+ * import { attachFlatpickr, detachFlatpickr } from '../adapters/flatpickr.mjs'
13
+ *
14
+ * const wrapper = el.firstElementChild // your component's outer <div>
15
+ * const instance = attachFlatpickr(wrapper, flatpickr, { wrap: true, ...options })
16
+ * // later on removal:
17
+ * detachFlatpickr(wrapper)
18
+ */
19
+
20
+ /**
21
+ * Resolves the actual wrapper Element to pass to flatpickr.
22
+ * Accepts either an Element, or a container whose firstElementChild is the wrapper.
23
+ * Falls back to querySelector for common markers if necessary.
24
+ * @param {any} node
25
+ * @returns {Element|null}
26
+ */
27
+ function resolveWrapperElement (node) {
28
+ if (!node) return null
29
+ // If it's already an Element (HTMLElement/SVGElement), use it directly
30
+ if (typeof Element !== 'undefined' && node instanceof Element) return node
31
+ // If it's a container/fragment with a firstElementChild, use that
32
+ if (node.firstElementChild) return node.firstElementChild
33
+ // Last resort: try to find a likely wrapper inside
34
+ if (node.querySelector) {
35
+ return node.querySelector('[data-flatpickr-wrapper], [data-input]')
36
+ }
37
+ return null
38
+ }
39
+
40
+ /**
41
+ * Attaches or updates a flatpickr instance on the given wrapper/container.
42
+ * The instance is stored on `wrapperEl._flatpickr`.
43
+ *
44
+ * @param {Element|any} wrapperOrContainer - An Element or a container whose firstElementChild is the wrapper
45
+ * @param {Function} flatpickrLib - The flatpickr factory function (import flatpickr from 'flatpickr')
46
+ * @param {Object} config - Flatpickr configuration options
47
+ * @returns {Object|null} flatpickr instance, or null if deferred/not created
48
+ */
49
+ export function attachFlatpickr (wrapperOrContainer, flatpickrLib, config = {}) {
50
+ if (typeof window === 'undefined') return null // SSR guard
51
+ if (!flatpickrLib) return null
52
+
53
+ const wrapperEl = resolveWrapperElement(wrapperOrContainer)
54
+ if (!wrapperEl) return null
55
+
56
+ // If instance already exists, try to update options where supported
57
+ if (wrapperEl._flatpickr) {
58
+ try {
59
+ // Not all options are dynamically settable, but this is a safe best-effort
60
+ wrapperEl._flatpickr.set(config)
61
+ } catch (e) {
62
+ try {
63
+ wrapperEl._flatpickr.destroy()
64
+ } catch (_) {}
65
+ wrapperEl._flatpickr = null
66
+ }
67
+ }
68
+
69
+ const init = () => {
70
+ // Ensure wrap:true is preserved if provided; caller decides
71
+ const instance = flatpickrLib(wrapperEl, config)
72
+ wrapperEl._flatpickr = instance
73
+ return instance
74
+ }
75
+
76
+ // If not connected yet, defer until the next frame when likely connected
77
+ if (!wrapperEl.isConnected) {
78
+ requestAnimationFrame(() => {
79
+ if (!wrapperEl._flatpickr && wrapperEl.isConnected) {
80
+ try { init() } catch (e) { /* swallow init errors in deferred path */ }
81
+ }
82
+ })
83
+ return null
84
+ }
85
+
86
+ return init()
87
+ }
88
+
89
+ /**
90
+ * Destroys and detaches a flatpickr instance from the wrapper element, if present.
91
+ * @param {Element|any} wrapperOrContainer
92
+ */
93
+ export function detachFlatpickr (wrapperOrContainer) {
94
+ const wrapperEl = resolveWrapperElement(wrapperOrContainer)
95
+ if (wrapperEl && wrapperEl._flatpickr) {
96
+ try { wrapperEl._flatpickr.destroy() } catch (e) {}
97
+ try { delete wrapperEl._flatpickr } catch (e) { wrapperEl._flatpickr = null }
98
+ }
99
+ }
100
+
101
+ export default {
102
+ attachFlatpickr,
103
+ detachFlatpickr
104
+ }
@@ -1,5 +1,6 @@
1
1
  import { html, css, formField, fieldIsTouched } from 'halfcab'
2
2
  import flatpickr from 'flatpickr'
3
+ import { attachFlatpickr } from '../adapters/flatpickr.mjs'
3
4
  import calendarIcon from './icons/calendarIcon.mjs'
4
5
  import timeIcon from './icons/timeIcon.mjs'
5
6
 
@@ -105,6 +106,15 @@ function detectTouchscreen () {
105
106
  }
106
107
 
107
108
  export default ({ wrapperStyle = null, holdingPen, label, placeholder, property, required, pattern, permanentTopPlaceholder = true, permanentTopLabel = false, flatpickrConfig = {}, timeOnly = false, disabled, disableClear = false, onchange, oninput }) => {
109
+ // Determine whether we should force Flatpickr on mobile/touch devices.
110
+ // By default we enable Flatpickr everywhere (disableMobile: true) so that
111
+ // environments that would otherwise show the native date/time inputs will
112
+ // still initialize the Flatpickr widget. Consumers can override by passing
113
+ // { disableMobile: false } inside flatpickrConfig.
114
+ const resolvedDisableMobile = flatpickrConfig && typeof flatpickrConfig.disableMobile !== 'undefined'
115
+ ? flatpickrConfig.disableMobile
116
+ : true
117
+
108
118
  let el = html`
109
119
  <div ${wrapperStyle ? { 'class': wrapperStyle } : ''} style="min-height: 55px; display: inline-block; width: calc(100% - 10px); margin: 40px 5px 5px 5px;">
110
120
  <div style="display: inline-block; width: 100%; text-align: left; position: relative; padding: 0;">
@@ -130,12 +140,16 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
130
140
  formField(holdingPen, property)(fauxE)
131
141
  onchange && onchange(fauxE)
132
142
  })
133
- }} onchange=${e => { change({ e, holdingPen, property, label: styles.label }); onchange && onchange(e) }} oninput=${e => { e.target.defaultValue = ''; oninput && oninput(e) }} placeholder="${placeholder || ''}${required ? ' *' : ''}" type="${detectTouchscreen() ? timeOnly ? 'time' : 'date' : 'text'}" ${pattern ? { pattern } : ''} value="${holdingPen[property] || ''}" data-input />
143
+ }} onchange=${e => { change({ e, holdingPen, property, label: styles.label }); onchange && onchange(e) }} oninput=${e => { e.target.defaultValue = ''; oninput && oninput(e) }} placeholder="${placeholder || ''}${required ? ' *' : ''}" type="${resolvedDisableMobile ? 'text' : (detectTouchscreen() ? (timeOnly ? 'time' : 'date') : 'text')}" ${pattern ? { pattern } : ''} value="${holdingPen[property] || ''}" data-input />
134
144
  </div>
135
145
  </div>
136
146
  `
137
147
 
138
- this.flatpickr = flatpickr(el, Object.assign(flatpickrConfig, {
148
+ // Attach flatpickr instance to the OUTER WRAPPER element (not the fragment) so
149
+ // internal clear/close handlers can access it consistently via parent wrappers.
150
+ // Using `this` or the fragment caused errors after migrating to halfcab/lit.
151
+ const wrapper = el.firstElementChild || el
152
+ attachFlatpickr(wrapper, flatpickr, Object.assign({}, { disableMobile: resolvedDisableMobile }, flatpickrConfig, {
139
153
  wrap: true,
140
154
  onValueUpdate: (fpDate, dateString) => {
141
155
  let fauxE = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stalefish",
3
- "version": "8.0.2",
3
+ "version": "8.0.4",
4
4
  "description": "Simple function based component library for halfcab tagged template literals",
5
5
  "main": "index.mjs",
6
6
  "module": "index.mjs",