stalefish 8.0.6 → 8.0.8

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.
@@ -117,8 +117,28 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
117
117
 
118
118
  // Create a stable id for the outer wrapper so we can query a real DOM node
119
119
  // after the template is rendered. Prefer the provided uniqueKey from callers
120
- // (report-pal already supplies it), otherwise fall back to property/name.
121
- const wrapperId = `sf-flatpickr-${uniqueKey || property || Math.random().toString(36).slice(2)}`
120
+ // but DO NOT require it. For robustness, memoize a per-(holdingPen, property)
121
+ // id so re-renders don't generate new ids and callers don't have to pass
122
+ // uniqueKey.
123
+ const ID_MAP_SYMBOL = Symbol.for('stalefish.flatpickr.idMap')
124
+ if (holdingPen && !holdingPen[ID_MAP_SYMBOL]) {
125
+ Object.defineProperty(holdingPen, ID_MAP_SYMBOL, {
126
+ value: {},
127
+ enumerable: false,
128
+ configurable: false,
129
+ writable: false
130
+ })
131
+ }
132
+ const memoId = (() => {
133
+ if (!holdingPen || !property) return null
134
+ const map = holdingPen[ID_MAP_SYMBOL]
135
+ if (!map) return null
136
+ if (!map[property]) {
137
+ map[property] = `sf-flatpickr-${property}-${Math.random().toString(36).slice(2)}`
138
+ }
139
+ return map[property]
140
+ })()
141
+ const wrapperId = `sf-flatpickr-${uniqueKey || memoId || property || Math.random().toString(36).slice(2)}`
122
142
 
123
143
  const inputType = resolvedDisableMobile ? 'text' : (detectTouchscreen() ? (timeOnly ? 'time' : 'date') : 'text')
124
144
 
@@ -134,7 +154,21 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
134
154
  e.target.parentNode.parentNode._flatpickr.close()
135
155
  return false
136
156
  }}>clear</div>` : ''}
137
- <input data-gramm="false" ?disabled=${disabled} style="${disabled ? 'cursor: not-allowed; opacity: 0.3;' : ''}" class="${styles.textfield} ${fieldIsTouched(holdingPen, property) === true ? styles.touched : ''}" ${required ? { required: 'required' } : ''} onchange=${e => { change({ e, holdingPen, property, label: styles.label }); onchange && onchange(e) }} oninput=${e => { e.target.defaultValue = ''; oninput && oninput(e) }} placeholder="${placeholder || ''}${required ? ' *' : ''}" type="${inputType}" ${pattern ? { pattern } : ''} .value=${holdingPen[property] || ''} data-input />
157
+ <input data-gramm="false" ?disabled=${disabled} style="${disabled ? 'cursor: not-allowed; opacity: 0.3;' : ''}" class="${styles.textfield} ${fieldIsTouched(holdingPen, property) === true ? styles.touched : ''}" ${required ? { required: 'required' } : ''} onchange=${e => {
158
+ // Guard against re-render while Flatpickr popup is open: native input onchange
159
+ // can fire on spinner clicks, which would propagate to the parent onchange
160
+ // and trigger a re-render tearing down the popup. If the picker is open,
161
+ // ignore this native onchange and let flatpickr's onClose handler sync state.
162
+ const wrapperEl = e.target && e.target.parentNode && e.target.parentNode.parentNode
163
+ const fp = wrapperEl && wrapperEl._flatpickr
164
+ if (fp && fp.isOpen) {
165
+ e.stopPropagation()
166
+ e.preventDefault()
167
+ return false
168
+ }
169
+ change({ e, holdingPen, property, label: styles.label })
170
+ onchange && onchange(e)
171
+ }} oninput=${e => { e.target.defaultValue = ''; oninput && oninput(e) }} placeholder="${placeholder || ''}${required ? ' *' : ''}" type="${inputType}" ${pattern ? { pattern } : ''} .value=${holdingPen[property] || ''} data-input />
138
172
  </div>
139
173
  </div>
140
174
  `
@@ -144,7 +178,7 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
144
178
  // adapter expects a real Element. We defer with requestAnimationFrame and
145
179
  // query by id.
146
180
  if (typeof window !== 'undefined') {
147
- const doAttach = () => {
181
+ const attachNow = () => {
148
182
  const wrapperEl = document.getElementById(wrapperId)
149
183
  if (!wrapperEl) return
150
184
  attachFlatpickr(
@@ -166,14 +200,13 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
166
200
  flatpickrConfig,
167
201
  {
168
202
  wrap: true,
169
- onValueUpdate: (fpDate, dateString) => {
203
+ // Avoid triggering rerenders on every spinner click which would
204
+ // destroy and recreate the popup, making it seem to "disappear".
205
+ // We update holdingPen once on close instead.
206
+ onValueUpdate: () => {},
207
+ onClose: (selectedDates, dateString) => {
170
208
  const fauxE = {
171
- currentTarget: {
172
- validity: {
173
- valid: true
174
- },
175
- value: dateString
176
- }
209
+ currentTarget: { validity: { valid: true }, value: dateString }
177
210
  }
178
211
  formField(holdingPen, property)(fauxE)
179
212
  onchange && onchange(fauxE)
@@ -183,8 +216,15 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
183
216
  )
184
217
  )
185
218
  }
186
- // Defer to next frame to ensure the element is connected
187
- window.requestAnimationFrame(doAttach)
219
+
220
+ // Try immediate attach if the element is already in the DOM and connected;
221
+ // otherwise, defer to the next frame as a fallback.
222
+ const immediateEl = typeof document !== 'undefined' ? document.getElementById(wrapperId) : null
223
+ if (immediateEl && immediateEl.isConnected) {
224
+ attachNow()
225
+ } else {
226
+ window.requestAnimationFrame(attachNow)
227
+ }
188
228
  }
189
229
 
190
230
  return el
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stalefish",
3
- "version": "8.0.6",
3
+ "version": "8.0.8",
4
4
  "description": "Simple function based component library for halfcab tagged template literals",
5
5
  "main": "index.mjs",
6
6
  "module": "index.mjs",