stalefish 8.0.1 → 8.0.3

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,5 @@
1
1
  import { html } from 'halfcab'
2
2
 
3
3
  export default ({ width, minWidth, height, text, colour, disabled }) => html`
4
- <button style="${disabled ? 'opacity: 0.3;' : ''}font-family: inherit; line-height: inherit; cursor: ${disabled ? 'not-allowed' : 'pointer'}; background-color: ${colour}; ${minWidth ? `min-width: ${minWidth};` : ''} border-radius: 3px; margin: 30px 10px; width: ${width}; height: ${height}; border: none; box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); font-size: 17px; color: white;" ${disabled ? 'disabled' : ''} onclick=${e => this.action(e)}>${text}</button>
4
+ <button style="${disabled ? 'opacity: 0.3;' : ''}font-family: inherit; line-height: inherit; cursor: ${disabled ? 'not-allowed' : 'pointer'}; background-color: ${colour}; ${minWidth ? `min-width: ${minWidth};` : ''} border-radius: 3px; margin: 30px 10px; width: ${width}; height: ${height}; border: none; box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); font-size: 17px; color: white;" ?disabled=${disabled} onclick=${e => this.action(e)}>${text}</button>
5
5
  `
@@ -30,15 +30,19 @@ let styles = css`
30
30
  `
31
31
 
32
32
  export default ({ wrapperStyle, holdingPen, label, property, required, indeterminate, disabled, darkBackground, onchange }) => {
33
- let checkboxEl = html`<input data-gramm="false" ${disabled ? { disabled } : ''} style="${disabled ? 'cursor: not-allowed;' : ''}" class="${styles.checkbox} ${fieldIsTouched(holdingPen, property) === true ? styles.touched : ''}" value="${holdingPen[property] === true ? 'true' : null}" ${holdingPen[property] === true ? { checked: 'checked' } : ''} onchange=${e => {
33
+ let checkboxEl = html`<input data-gramm="false" ${disabled ? { disabled } : ''} style="${disabled ? 'cursor: not-allowed;' : ''}" class="${styles.checkbox} ${fieldIsTouched(holdingPen, property) === true ? styles.touched : ''}" value="${holdingPen[property] === true ? 'true' : null}" ?checked=${holdingPen[property] === true} onchange=${e => {
34
34
  formField(holdingPen, property)(e)
35
- this.onchange && this.onchange(e)
35
+ onchange && onchange(e)
36
36
  }} type="checkbox" ${required ? { required: 'required' } : ''} />`
37
37
 
38
38
  checkboxEl.indeterminate = indeterminate || false
39
39
 
40
+ const wrapperClassName = wrapperStyle
41
+ ? (typeof wrapperStyle === 'string' ? wrapperStyle : (wrapperStyle.toString ? wrapperStyle.toString() : ''))
42
+ : ''
43
+
40
44
  return html`
41
- <label style="${disabled ? 'cursor: not-allowed; opacity: 0.3;' : ''}text-align: left; width: 100%; display: inline-block; vertical-align: bottom;" ${wrapperStyle ? { 'class': wrapperStyle } : ''}>
45
+ <label style="${disabled ? 'cursor: not-allowed; opacity: 0.3;' : ''}text-align: left; width: 100%; display: inline-block; vertical-align: bottom;" class="${wrapperClassName}">
42
46
  <span class="${styles.label}" style="background-color: rgba(255,255,255,${darkBackground ? 1 : 0.8});">${label}${required ? ' *' : ''}${checkboxEl}</span>
43
47
  </label>
44
48
  `
@@ -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
 
@@ -117,7 +118,7 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
117
118
  e.target.parentNode.parentNode._flatpickr.close()
118
119
  return false
119
120
  }}>clear</div>` : ''}
120
- <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' } : ''} onfocus=${e => {
121
+ <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' } : ''} onfocus=${e => {
121
122
  e.target.parentNode.parentNode._flatpickr && e.target.parentNode.parentNode._flatpickr.set('onValueUpdate', (fpDate, dateString) => {
122
123
  let fauxE = {
123
124
  currentTarget: {
@@ -135,7 +136,11 @@ export default ({ wrapperStyle = null, holdingPen, label, placeholder, property,
135
136
  </div>
136
137
  `
137
138
 
138
- this.flatpickr = flatpickr(el, Object.assign(flatpickrConfig, {
139
+ // Attach flatpickr instance to the OUTER WRAPPER element (not the fragment) so
140
+ // internal clear/close handlers can access it consistently via parent wrappers.
141
+ // Using `this` or the fragment caused errors after migrating to halfcab/lit.
142
+ const wrapper = el.firstElementChild || el
143
+ attachFlatpickr(wrapper, flatpickr, Object.assign({}, flatpickrConfig, {
139
144
  wrap: true,
140
145
  onValueUpdate: (fpDate, dateString) => {
141
146
  let fauxE = {
@@ -76,7 +76,7 @@ export default ({ wrapperStyle = null, holdingPen, label, property, options, req
76
76
  <div class="${styles.down}">${solidDown({ colour: '#ccc' })}</div>
77
77
  <span class="${styles.label}">${label}${required ? ' *' : ''}</span>
78
78
  <select ${disabled ? { disabled } : ''} style="${disabled ? 'cursor: not-allowed; opacity: 0.3;' : ''}background-color: ${typeof currentOption === 'object' && currentOption.colour ? `#${currentOption.colour}` : 'white'}" class="${styles.selectBox} ${fieldIsTouched(holdingPen, property) === true ? styles.touched : ''}" oninput=${e => { formField(holdingPen, property)(e); oninput && oninput(e) }} onchange=${e => { formField(holdingPen, property)(e); onchange && onchange(e) }} onblur=${formField(holdingPen, property)}>
79
- <option value="${required ? 'Select an option' : ''}" ${!holdingPen[property] ? { selected: 'true' } : ''} ${required ? { disabled: 'disabled' } : ''}>${required ? 'Select an option' : ''}</option>
79
+ <option value="${required ? 'Select an option' : ''}" ?selected=${!holdingPen[property]} ?disabled=${required} : ''}>${required ? 'Select an option' : ''}</option>
80
80
  ${options.map(option => {
81
81
  let optionValue
82
82
  let optionName
@@ -86,7 +86,7 @@ export default ({ wrapperStyle = null, holdingPen, label, property, options, req
86
86
  } else {
87
87
  optionValue = option
88
88
  }
89
- return html`<option value="${optionValue}" ${holdingPen[property] == optionValue ? { selected: 'true' } : ''}>${optionName || optionValue}</option>` // eslint-disable-line
89
+ return html`<option value="${optionValue}" ?selected=${holdingPen[property] == optionValue}>${optionName || optionValue}</option>` // eslint-disable-line
90
90
  })}
91
91
  </select>
92
92
  </label>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stalefish",
3
- "version": "8.0.1",
3
+ "version": "8.0.3",
4
4
  "description": "Simple function based component library for halfcab tagged template literals",
5
5
  "main": "index.mjs",
6
6
  "module": "index.mjs",