js-kompo-utils 1.0.23 → 1.0.25

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.
@@ -27,9 +27,11 @@
27
27
  class="vlNativeSelect"
28
28
  :value="nativeValue"
29
29
  @change="onNativeChange"
30
+ @mousedown="onNativeOpen"
31
+ @focus="onNativeOpen"
30
32
  :style="nativeSelectStyle">
31
33
  <option value="">{{ nativePlaceholder }}</option>
32
- <option v-for="opt in options" :key="opt.value" :value="opt.value">{{ nativeLabel(opt) }}</option>
34
+ <option v-for="opt in nativeOptionsList" :key="opt.value" :value="opt.value">{{ nativeLabel(opt) }}</option>
33
35
  </select>
34
36
  </div>
35
37
  <div class="vlOptions vlOptionsTeleported"
@@ -85,8 +87,11 @@ export default {
85
87
  return this.panelStyle
86
88
  },
87
89
  useNativeMobile() {
88
- if (this.$_config('disableNativeMobile')) return false
89
- return this.isTouchDevice && !this.$_multiple && !this.ajaxOptions
90
+ if (!this.$_config('native_on_mobile')) return false
91
+ return this.isTouchDevice && !this.$_multiple
92
+ },
93
+ nativeOptionsList() {
94
+ return this.ajaxOptions ? this.filteredOptions : this.options
90
95
  },
91
96
  nativeValue() {
92
97
  return this.$_value && this.$_value.length ? this.$_value[0].value : ''
@@ -121,12 +126,21 @@ export default {
121
126
  filteredOptions() {
122
127
  if (this.isFocused) this.$nextTick(() => this.refresh())
123
128
  },
129
+ optionsKey() {
130
+ this.$nextTick(() => {
131
+ this.teleportOptions()
132
+ if (this.isFocused) this.refresh()
133
+ })
134
+ },
124
135
  },
125
136
  mounted() {
126
137
  this.teleportOptions()
127
138
  if (typeof window !== 'undefined' && window.matchMedia) {
128
139
  this.isTouchDevice = window.matchMedia('(pointer: coarse)').matches
129
140
  }
141
+ if (this.useNativeMobile && this.ajaxOptions) {
142
+ this.$nextTick(() => this.onNativeOpen())
143
+ }
130
144
  },
131
145
  beforeDestroy() {
132
146
  this.cleanupOptions()
@@ -136,14 +150,33 @@ export default {
136
150
  onNativeChange(e) {
137
151
  const v = e.target.value
138
152
  if (v === '') return
139
- const opt = this.options.find(o => String(o.value) === String(v))
153
+ const list = this.nativeOptionsList || []
154
+ const opt = list.find(o => String(o.value) === String(v))
140
155
  if (opt) this.select(opt)
141
156
  },
157
+ onNativeOpen() {
158
+ if (!this.ajaxOptions) return
159
+ if (this.ajaxOptionsFromField) {
160
+ this.$kompo.vlRequestFieldValue(this.kompoid, this.ajaxOptionsFromField, this.$_elKompoId)
161
+ this.performAjax(this.$_getFromStore())
162
+ } else {
163
+ this.performAjax('')
164
+ }
165
+ },
142
166
  nativeLabel(opt) {
143
167
  const l = opt.label
144
- if (typeof l === 'string') return l
145
- if (l && typeof l === 'object') return l.label || l.text || l.search || ''
146
- return String(opt.value ?? '')
168
+ let raw = ''
169
+ if (typeof l === 'string') raw = l
170
+ else if (l && typeof l === 'object') raw = l.label || l.text || l.search || ''
171
+ else raw = String(opt.value ?? '')
172
+ return this.stripHtml(raw)
173
+ },
174
+ stripHtml(str) {
175
+ if (!str || typeof str !== 'string') return ''
176
+ if (str.indexOf('<') === -1) return str
177
+ const tmp = document.createElement('div')
178
+ tmp.innerHTML = str
179
+ return (tmp.textContent || tmp.innerText || '').trim()
147
180
  },
148
181
  teleportOptions() {
149
182
  const opts = this.$refs.selectOptions
@@ -162,22 +195,82 @@ export default {
162
195
  },
163
196
  attachListeners() {
164
197
  if (this._listenersAttached) return
198
+ this._rafId = null
165
199
  this._onScroll = (e) => {
166
200
  const opts = this.$refs.selectOptions
167
201
  if (opts && e.target instanceof Node && opts.contains(e.target)) return
168
- this.blur()
202
+ if (this._rafId) return
203
+ this._rafId = requestAnimationFrame(() => {
204
+ this._rafId = null
205
+ this.trackPosition()
206
+ })
169
207
  }
170
208
  this._onResize = () => this.refresh()
171
209
  window.addEventListener('scroll', this._onScroll, true)
172
210
  window.addEventListener('resize', this._onResize, { passive: true })
211
+
212
+ const opts = this.$refs.selectOptions
213
+ const anchor = this.anchorEl()
214
+ if (typeof ResizeObserver !== 'undefined') {
215
+ this._panelObserver = new ResizeObserver(() => {
216
+ if (this.isFocused) this.trackPosition()
217
+ })
218
+ if (opts) this._panelObserver.observe(opts)
219
+ this._anchorObserver = new ResizeObserver((entries) => {
220
+ const entry = entries[0]
221
+ if (!entry) return
222
+ const r = entry.contentRect
223
+ const el = entry.target
224
+ if (r.width === 0 || r.height === 0 || !el.isConnected) {
225
+ this.blur()
226
+ }
227
+ })
228
+ if (anchor) this._anchorObserver.observe(anchor)
229
+ }
230
+ if (typeof IntersectionObserver !== 'undefined' && anchor) {
231
+ this._anchorIntersection = new IntersectionObserver((entries) => {
232
+ const entry = entries[entries.length - 1]
233
+ if (entry && entry.intersectionRatio === 0) this.blur()
234
+ }, { threshold: 0 })
235
+ this._anchorIntersection.observe(anchor)
236
+ }
237
+
173
238
  this._listenersAttached = true
174
239
  },
175
240
  detachListeners() {
176
241
  if (!this._listenersAttached) return
242
+ if (this._rafId) {
243
+ cancelAnimationFrame(this._rafId)
244
+ this._rafId = null
245
+ }
177
246
  window.removeEventListener('scroll', this._onScroll, true)
178
247
  window.removeEventListener('resize', this._onResize)
248
+ if (this._panelObserver) {
249
+ this._panelObserver.disconnect()
250
+ this._panelObserver = null
251
+ }
252
+ if (this._anchorObserver) {
253
+ this._anchorObserver.disconnect()
254
+ this._anchorObserver = null
255
+ }
256
+ if (this._anchorIntersection) {
257
+ this._anchorIntersection.disconnect()
258
+ this._anchorIntersection = null
259
+ }
179
260
  this._listenersAttached = false
180
261
  },
262
+ trackPosition() {
263
+ const opts = this.$refs.selectOptions
264
+ const anchor = this.anchorEl()
265
+ if (!opts || !anchor) return
266
+ const rect = anchor.getBoundingClientRect()
267
+ const x = Math.round(rect.left)
268
+ const y = this.flipUp
269
+ ? Math.round(rect.top) - opts.offsetHeight + 1
270
+ : Math.round(rect.bottom) - 1
271
+ opts.style.transform = `translate3d(${x}px, ${y}px, 0)`
272
+ opts.style.width = Math.round(rect.width) + 'px'
273
+ },
181
274
  refresh() {
182
275
  const opts = this.$refs.selectOptions
183
276
  const anchor = this.anchorEl()
@@ -185,13 +278,17 @@ export default {
185
278
 
186
279
  const rect = anchor.getBoundingClientRect()
187
280
  const inOverlay = !!anchor.closest('.kompoModal, .kompoDrawer, .vl-sidebar-l')
281
+ const x = Math.round(rect.left)
282
+ const yBelow = Math.round(rect.bottom) - 1
188
283
  const baseStyle = {
189
284
  position: 'fixed',
190
- left: rect.left + 'px',
191
- width: rect.width + 'px',
192
- zIndex: inOverlay ? 5001 : 1000,
193
- top: rect.bottom + 'px',
285
+ top: '0px',
286
+ left: '0px',
194
287
  bottom: 'auto',
288
+ width: Math.round(rect.width) + 'px',
289
+ zIndex: inOverlay ? 5001 : 1000,
290
+ transform: `translate3d(${x}px, ${yBelow}px, 0)`,
291
+ willChange: 'transform',
195
292
  }
196
293
  this.panelStyle = baseStyle
197
294
 
@@ -202,10 +299,10 @@ export default {
202
299
  const shouldFlip = this.forceUp || (spaceBelow < optsHeight && spaceAbove > spaceBelow)
203
300
  this.flipUp = shouldFlip
204
301
  if (shouldFlip) {
302
+ const yAbove = Math.round(rect.top) - optsHeight + 1
205
303
  this.panelStyle = {
206
304
  ...baseStyle,
207
- top: 'auto',
208
- bottom: (window.innerHeight - rect.top) + 'px',
305
+ transform: `translate3d(${x}px, ${yAbove}px, 0)`,
209
306
  borderRadius: '5px 5px 0 0',
210
307
  }
211
308
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-kompo-utils",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "",
5
5
  "main": "app.js",
6
6
  "scripts": {