flyql-vue 0.0.37

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,783 @@
1
+ <template>
2
+ <div class="flyql-columns" :class="{ 'flyql-columns--focused': focused, 'flyql-dark': dark }" ref="editorRoot">
3
+ <span class="flyql-columns__icon">
4
+ <slot name="icon">
5
+ <svg
6
+ width="13"
7
+ height="13"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ stroke-width="2"
12
+ stroke-linecap="round"
13
+ stroke-linejoin="round"
14
+ >
15
+ <rect x="3" y="3" width="7" height="7" />
16
+ <rect x="14" y="3" width="7" height="7" />
17
+ <rect x="3" y="14" width="7" height="7" />
18
+ <rect x="14" y="14" width="7" height="7" />
19
+ </svg>
20
+ </slot>
21
+ </span>
22
+ <div class="flyql-columns__container" ref="containerRef">
23
+ <pre class="flyql-columns__highlight" ref="highlightRef" v-html="highlightedHtml" aria-hidden="true"></pre>
24
+ <textarea
25
+ class="flyql-columns__input"
26
+ ref="textareaRef"
27
+ rows="1"
28
+ :value="modelValue"
29
+ :placeholder="placeholder"
30
+ @input="onInput"
31
+ @keydown="onKeydown"
32
+ @focus="onFocus"
33
+ @blur="onBlur"
34
+ @scroll="onScroll"
35
+ @click="onCursorMove"
36
+ @paste="onPaste"
37
+ @compositionstart="engine.state.composing = true"
38
+ @compositionend="onCompositionEnd"
39
+ spellcheck="false"
40
+ autocomplete="off"
41
+ autocorrect="off"
42
+ autocapitalize="off"
43
+ aria-label="FlyQL columns expression input"
44
+ role="combobox"
45
+ :aria-expanded="focused && activated && suggestions.length > 0"
46
+ :aria-activedescendant="
47
+ focused && activated && suggestions.length > 0
48
+ ? instanceId + '-suggestion-' + selectedIndex
49
+ : undefined
50
+ "
51
+ ></textarea>
52
+ </div>
53
+ <!-- Suggestion panel -->
54
+ <Teleport to="body">
55
+ <div
56
+ v-if="focused && activated"
57
+ ref="panelRef"
58
+ class="flyql-panel"
59
+ :class="{ 'flyql-dark': dark }"
60
+ @mousedown.prevent="onPanelMousedown"
61
+ :style="panelStyle"
62
+ >
63
+ <div v-if="debug" class="flyql-panel__header flyql-panel__debug">
64
+ <span v-if="context"
65
+ >state={{ context.state }} expecting={{ context.expecting }} col={{ context.column }} mod={{
66
+ context.transformer
67
+ }}</span
68
+ >
69
+ <span v-else>no context</span>
70
+ </div>
71
+ <div class="flyql-panel__header">
72
+ Suggestions: <span class="flyql-panel__state">{{ stateLabel }}</span>
73
+ <span
74
+ v-if="isLoading && suggestions.length > 0"
75
+ class="flyql-panel__spinner flyql-panel__spinner--inline"
76
+ ></span>
77
+ </div>
78
+ <div class="flyql-panel__body" aria-live="polite">
79
+ <div v-if="isLoading && suggestions.length === 0" class="flyql-panel__loading">
80
+ <slot name="loading">
81
+ <span class="flyql-panel__spinner"></span>
82
+ </slot>
83
+ </div>
84
+ <ul v-if="suggestions.length > 0" ref="listRef" class="flyql-panel__list" role="listbox">
85
+ <li
86
+ v-for="(item, index) in suggestions"
87
+ :key="index"
88
+ :id="instanceId + '-suggestion-' + index"
89
+ :ref="(el) => setItemRef(el, index)"
90
+ class="flyql-panel__item"
91
+ :class="{ 'flyql-panel__item--active': index === selectedIndex }"
92
+ :aria-selected="index === selectedIndex"
93
+ role="option"
94
+ @click="onSuggestionSelect(index)"
95
+ >
96
+ <span class="flyql-panel__badge" :class="'flyql-panel__badge--' + item.type">
97
+ {{ badgeText(item.type) }}
98
+ </span>
99
+ <span class="flyql-panel__label" v-html="highlightMatch(item.label)"></span>
100
+ <span v-if="item.detail" class="flyql-panel__detail">{{ item.detail }}</span>
101
+ </li>
102
+ </ul>
103
+ <div v-if="!isLoading && suggestions.length === 0 && message" class="flyql-panel__message">
104
+ {{ message }}
105
+ </div>
106
+ <div v-if="!isLoading && suggestions.length === 0 && !message" class="flyql-panel__empty">
107
+ No suggestions
108
+ </div>
109
+ </div>
110
+ <div
111
+ v-if="diagnostics.length > 0"
112
+ class="flyql-panel__diagnostics"
113
+ @mousedown.stop="panelInteracting = true"
114
+ @mouseup="panelInteracting = false"
115
+ >
116
+ <div class="flyql-panel__header">Diagnostics</div>
117
+ <div
118
+ v-for="(diag, idx) in diagnostics"
119
+ :key="idx"
120
+ :class="['flyql-panel__diagnostic-item', 'flyql-panel__diagnostic-item--' + diag.severity]"
121
+ @mouseenter="hoveredDiagIndex = idx"
122
+ @mouseleave="hoveredDiagIndex = -1"
123
+ >
124
+ <span
125
+ class="flyql-panel__diagnostic-bullet"
126
+ :class="'flyql-panel__diagnostic-bullet--' + diag.severity"
127
+ ></span>
128
+ <span class="flyql-panel__diagnostic-msg">{{ diag.message }}</span>
129
+ </div>
130
+ </div>
131
+ <div class="flyql-panel__footer">
132
+ <span v-if="footerInfo && footerInfo.column" class="flyql-panel__footer-col">{{
133
+ footerInfo.column
134
+ }}</span>
135
+ <span v-if="footerInfo && footerInfo.type" class="flyql-panel__footer-label">{{
136
+ footerInfo.type
137
+ }}</span>
138
+ <span v-if="!footerInfo" class="flyql-panel__footer-label">{{ stateLabel }}</span>
139
+ </div>
140
+ </div>
141
+ </Teleport>
142
+ </div>
143
+ </template>
144
+
145
+ <script setup>
146
+ import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
147
+ import { ColumnsEngine } from './columns-engine.js'
148
+ import './flyql.css'
149
+
150
+ const props = defineProps({
151
+ modelValue: { type: String, default: '' },
152
+ columns: { type: Object, default: null },
153
+ capabilities: { type: Object, default: null },
154
+ onKeyDiscovery: { type: Function, default: null },
155
+ placeholder: { type: String, default: '' },
156
+ autofocus: { type: Boolean, default: false },
157
+ debug: { type: Boolean, default: false },
158
+ dark: { type: Boolean, default: false },
159
+ registry: { type: Object, default: null },
160
+ })
161
+
162
+ const emit = defineEmits([
163
+ 'update:modelValue',
164
+ 'update:parsed',
165
+ 'submit',
166
+ 'parse-error',
167
+ 'focus',
168
+ 'blur',
169
+ 'diagnostics',
170
+ ])
171
+
172
+ const isLoading = ref(false)
173
+
174
+ const engineOpts = {
175
+ onKeyDiscovery: props.onKeyDiscovery,
176
+ onLoadingChange: (loading) => {
177
+ isLoading.value = loading
178
+ },
179
+ }
180
+ if (props.capabilities) {
181
+ engineOpts.capabilities = props.capabilities
182
+ }
183
+ if (props.registry) {
184
+ engineOpts.registry = props.registry
185
+ }
186
+ const engine = new ColumnsEngine(props.columns, engineOpts)
187
+
188
+ const instanceId = 'flyql-cols-' + Math.random().toString(36).substring(2, 8)
189
+
190
+ const textareaRef = ref(null)
191
+ const highlightRef = ref(null)
192
+ const containerRef = ref(null)
193
+ const editorRoot = ref(null)
194
+ const listRef = ref(null)
195
+ const focused = ref(false)
196
+ const activated = ref(false)
197
+ const panelRef = ref(null)
198
+ const panelLeft = ref(0)
199
+ const panelTop = ref(0)
200
+
201
+ const suggestions = ref([])
202
+ const selectedIndex = ref(0)
203
+ const message = ref('')
204
+ const stateLabel = ref('')
205
+ const context = ref(null)
206
+ const footerInfo = ref(null)
207
+ const lastParseError = ref(null)
208
+ const diagnostics = ref([])
209
+ const hoveredDiagIndex = ref(-1)
210
+ let panelInteracting = false
211
+
212
+ const panelStyle = computed(() => ({
213
+ left: panelLeft.value + 'px',
214
+ top: panelTop.value + 'px',
215
+ }))
216
+
217
+ function highlightMatch(label) {
218
+ return engine.highlightMatch(label)
219
+ }
220
+
221
+ function syncFromEngine() {
222
+ suggestions.value = engine.suggestions
223
+ selectedIndex.value = engine.state.selectedIndex
224
+ message.value = engine.message
225
+ stateLabel.value = engine.getStateLabel()
226
+ context.value = engine.context
227
+ footerInfo.value = engine.getFooterInfo()
228
+ diagnostics.value = engine.diagnostics
229
+
230
+ const currentError = engine.getParseError()
231
+ if (currentError !== lastParseError.value) {
232
+ lastParseError.value = currentError
233
+ emit('parse-error', currentError)
234
+ }
235
+ }
236
+
237
+ function emitParsed() {
238
+ const parsed = engine.getParsedColumns()
239
+ emit('update:parsed', parsed)
240
+ }
241
+
242
+ const highlightedHtml = computed(() => {
243
+ return engine.getHighlightTokens(props.modelValue, diagnostics.value, hoveredDiagIndex.value)
244
+ })
245
+
246
+ function updatePanelPosition(ctx) {
247
+ const ta = textareaRef.value
248
+ if (!ta || !ctx) return
249
+
250
+ const range = engine.getInsertRange(ctx, ta.value)
251
+ const textBeforeToken = ta.value.substring(0, range.start)
252
+
253
+ const mirror = document.createElement('div')
254
+ const style = getComputedStyle(ta)
255
+
256
+ mirror.style.position = 'absolute'
257
+ mirror.style.visibility = 'hidden'
258
+ mirror.style.whiteSpace = 'pre-wrap'
259
+ mirror.style.wordWrap = 'break-word'
260
+ mirror.style.overflowWrap = 'break-word'
261
+ mirror.style.width = style.width
262
+ mirror.style.fontFamily = style.fontFamily
263
+ mirror.style.fontSize = style.fontSize
264
+ mirror.style.lineHeight = style.lineHeight
265
+ mirror.style.padding = style.padding
266
+ mirror.style.border = style.border
267
+ mirror.style.boxSizing = style.boxSizing
268
+ mirror.style.letterSpacing = style.letterSpacing
269
+ mirror.style.tabSize = style.tabSize
270
+
271
+ const textNode = document.createTextNode(textBeforeToken)
272
+ const span = document.createElement('span')
273
+ span.textContent = '|'
274
+
275
+ mirror.appendChild(textNode)
276
+ mirror.appendChild(span)
277
+ document.body.appendChild(mirror)
278
+
279
+ try {
280
+ const spanRect = span.getBoundingClientRect()
281
+ const mirrorRect = mirror.getBoundingClientRect()
282
+ const taRect = ta.getBoundingClientRect()
283
+ const cursorLeft = taRect.left + (spanRect.left - mirrorRect.left) - ta.scrollLeft
284
+ const panelWidth = panelRef.value?.offsetWidth || 600
285
+ const viewportWidth = document.documentElement.clientWidth
286
+ if (cursorLeft + panelWidth > viewportWidth) {
287
+ panelLeft.value = Math.max(0, cursorLeft - panelWidth)
288
+ } else {
289
+ panelLeft.value = cursorLeft
290
+ }
291
+ const panelHeight = panelRef.value?.offsetHeight || 280
292
+ const spaceBelow = window.innerHeight - taRect.bottom - 4
293
+ if (spaceBelow < panelHeight && taRect.top > panelHeight) {
294
+ panelTop.value = taRect.top - panelHeight - 4
295
+ } else {
296
+ panelTop.value = taRect.bottom + 4
297
+ }
298
+ } finally {
299
+ document.body.removeChild(mirror)
300
+ }
301
+ }
302
+
303
+ async function triggerSuggestions() {
304
+ const ta = textareaRef.value
305
+ if (!ta) return
306
+ engine.setQuery(ta.value)
307
+ engine.setCursorPosition(ta.selectionStart)
308
+ try {
309
+ const { ctx, seq } = await engine.updateSuggestions()
310
+ if (engine.isStale(seq)) return
311
+ engine.getDiagnostics()
312
+ syncFromEngine()
313
+ emit('diagnostics', engine.diagnostics)
314
+ nextTick(() => {
315
+ updatePanelPosition(ctx)
316
+ })
317
+ } catch {
318
+ engine.getDiagnostics()
319
+ syncFromEngine()
320
+ emit('diagnostics', engine.diagnostics)
321
+ }
322
+ }
323
+
324
+ function onCursorMove() {
325
+ activated.value = true
326
+ setTimeout(() => {
327
+ triggerSuggestions()
328
+ }, 0)
329
+ }
330
+
331
+ function onInput(e) {
332
+ activated.value = true
333
+ const value = e.target.value
334
+ emit('update:modelValue', value)
335
+ if (engine.state.composing) return
336
+ nextTick(() => {
337
+ autoResize()
338
+ triggerSuggestions()
339
+ emitParsed()
340
+ })
341
+ }
342
+
343
+ function onCompositionEnd(e) {
344
+ engine.state.composing = false
345
+ const value = e.target.value
346
+ emit('update:modelValue', value)
347
+ nextTick(() => {
348
+ triggerSuggestions()
349
+ emitParsed()
350
+ })
351
+ }
352
+
353
+ function onPaste() {
354
+ activated.value = true
355
+ nextTick(() => {
356
+ autoResize()
357
+ triggerSuggestions()
358
+ emitParsed()
359
+ })
360
+ }
361
+
362
+ function onKeydown(e) {
363
+ if (e.key === 'PageUp' || e.key === 'PageDown') {
364
+ e.preventDefault()
365
+ if (suggestions.value.length > 0) {
366
+ const len = suggestions.value.length
367
+ let idx = engine.state.selectedIndex
368
+ idx = e.key === 'PageUp' ? Math.max(0, idx - 10) : Math.min(len - 1, idx + 10)
369
+ engine.state.selectedIndex = idx
370
+ selectedIndex.value = idx
371
+ }
372
+ return
373
+ }
374
+ if (suggestions.value.length > 0) {
375
+ if (e.key === 'ArrowUp') {
376
+ e.preventDefault()
377
+ engine.navigateUp()
378
+ selectedIndex.value = engine.state.selectedIndex
379
+ return
380
+ }
381
+ if (e.key === 'ArrowDown') {
382
+ e.preventDefault()
383
+ engine.navigateDown()
384
+ selectedIndex.value = engine.state.selectedIndex
385
+ return
386
+ }
387
+ if (e.key === 'Enter' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
388
+ e.preventDefault()
389
+ acceptSuggestion(selectedIndex.value)
390
+ return
391
+ }
392
+ }
393
+
394
+ if (e.key === 'Enter' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
395
+ e.preventDefault()
396
+ return
397
+ }
398
+
399
+ if (e.key === 'Escape') {
400
+ e.preventDefault()
401
+ activated.value = false
402
+ return
403
+ }
404
+
405
+ if (e.key === 'Tab') {
406
+ if (activated.value && suggestions.value.length > 0) {
407
+ e.preventDefault()
408
+ acceptSuggestion(selectedIndex.value)
409
+ } else if (!activated.value) {
410
+ e.preventDefault()
411
+ activated.value = true
412
+ triggerSuggestions()
413
+ }
414
+ return
415
+ }
416
+
417
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
418
+ e.preventDefault()
419
+ emit('submit')
420
+ return
421
+ }
422
+
423
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
424
+ const ta = textareaRef.value
425
+ if (ta) {
426
+ const pos = ta.selectionStart
427
+ const len = ta.value.length
428
+ const newPos = e.key === 'ArrowRight' ? Math.min(pos + 1, len) : Math.max(pos - 1, 0)
429
+ engine.setQuery(ta.value)
430
+ engine.setCursorPosition(newPos)
431
+ engine
432
+ .updateSuggestions()
433
+ .then(({ ctx, seq }) => {
434
+ if (engine.isStale(seq)) return
435
+ syncFromEngine()
436
+ nextTick(() => {
437
+ updatePanelPosition(ctx)
438
+ })
439
+ })
440
+ .catch(() => {
441
+ syncFromEngine()
442
+ })
443
+ }
444
+ return
445
+ }
446
+ }
447
+
448
+ function acceptSuggestion(index) {
449
+ const suggestion = engine.selectSuggestion(index)
450
+ if (!suggestion) return
451
+
452
+ const ta = textareaRef.value
453
+ if (!ta) return
454
+
455
+ const currentValue = ta.value
456
+ // Use engine's tracked cursor — ta.selectionStart can be stale after Vue re-renders
457
+ const cursorPos = engine.state.cursorPosition
458
+ const ctx = engine.buildContext(currentValue.substring(0, cursorPos), currentValue)
459
+ const range = engine.getInsertRange(ctx, currentValue, suggestion)
460
+ const insertText = suggestion.insertText
461
+
462
+ const before = currentValue.substring(0, range.start)
463
+ const after = currentValue.substring(range.end)
464
+ const newValue = before + insertText + after
465
+ let newCursorPos = range.start + insertText.length
466
+ if (suggestion.cursorOffset) {
467
+ newCursorPos += suggestion.cursorOffset
468
+ }
469
+
470
+ // Update engine state first
471
+ engine.setQuery(newValue)
472
+ engine.setCursorPosition(newCursorPos)
473
+
474
+ // Update DOM and emit
475
+ ta.value = newValue
476
+ ta.selectionStart = newCursorPos
477
+ ta.selectionEnd = newCursorPos
478
+ ta.focus()
479
+ emit('update:modelValue', newValue)
480
+ emitParsed()
481
+
482
+ // Restore cursor after Vue re-render, then update suggestions
483
+ nextTick(() => {
484
+ const t = textareaRef.value
485
+ if (t) {
486
+ t.selectionStart = newCursorPos
487
+ t.selectionEnd = newCursorPos
488
+ }
489
+ autoResize()
490
+ engine
491
+ .updateSuggestions()
492
+ .then(({ ctx: nextCtx, seq }) => {
493
+ if (engine.isStale(seq)) return
494
+ engine.getDiagnostics()
495
+ syncFromEngine()
496
+ emit('diagnostics', engine.diagnostics)
497
+ nextTick(() => {
498
+ updatePanelPosition(nextCtx)
499
+ })
500
+ })
501
+ .catch(() => {
502
+ engine.getDiagnostics()
503
+ syncFromEngine()
504
+ emit('diagnostics', engine.diagnostics)
505
+ })
506
+ })
507
+ }
508
+
509
+ function onSuggestionSelect(index) {
510
+ acceptSuggestion(index)
511
+ textareaRef.value?.focus()
512
+ }
513
+
514
+ function onScroll() {
515
+ const ta = textareaRef.value
516
+ const hl = highlightRef.value
517
+ if (ta && hl) {
518
+ hl.scrollTop = ta.scrollTop
519
+ hl.scrollLeft = ta.scrollLeft
520
+ }
521
+ }
522
+
523
+ function onFocus() {
524
+ focused.value = true
525
+ activated.value = true
526
+ triggerSuggestions()
527
+ emit('focus')
528
+ }
529
+
530
+ function onPanelMousedown(e) {
531
+ // Allow text selection in diagnostics (mousedown.stop handles that),
532
+ // prevent blur for everything else
533
+ e.preventDefault()
534
+ }
535
+
536
+ function onBlur() {
537
+ if (panelInteracting) return
538
+ focused.value = false
539
+ activated.value = false
540
+ emit('blur')
541
+ }
542
+
543
+ function autoResize() {
544
+ const ta = textareaRef.value
545
+ const hl = highlightRef.value
546
+ if (!ta) return
547
+ ta.style.height = 'auto'
548
+ ta.style.height = ta.scrollHeight + 'px'
549
+ if (hl) {
550
+ hl.style.height = ta.scrollHeight + 'px'
551
+ }
552
+ }
553
+
554
+ const itemRefs = ref({})
555
+
556
+ function setItemRef(el, index) {
557
+ if (el) {
558
+ itemRefs.value[index] = el
559
+ } else {
560
+ delete itemRefs.value[index]
561
+ }
562
+ }
563
+
564
+ function badgeText(type) {
565
+ switch (type) {
566
+ case 'column':
567
+ return 'C'
568
+ case 'transformer':
569
+ return 'T'
570
+ case 'delimiter':
571
+ return 'S'
572
+ default:
573
+ return '?'
574
+ }
575
+ }
576
+
577
+ watch(activated, (val) => {
578
+ if (!val) {
579
+ engine.state.setActivated(false)
580
+ engine.clearKeyCache()
581
+ }
582
+ })
583
+
584
+ watch(
585
+ () => props.onKeyDiscovery,
586
+ (newFn) => {
587
+ engine.onKeyDiscovery = newFn || null
588
+ },
589
+ )
590
+
591
+ watch(selectedIndex, async (idx) => {
592
+ await nextTick()
593
+ const el = itemRefs.value[idx]
594
+ if (el) {
595
+ el.scrollIntoView({ block: 'nearest' })
596
+ }
597
+ })
598
+
599
+ watch(
600
+ () => props.modelValue,
601
+ () => {
602
+ nextTick(() => {
603
+ autoResize()
604
+ emitParsed()
605
+ })
606
+ },
607
+ )
608
+
609
+ watch(
610
+ () => props.columns,
611
+ (newColumns) => {
612
+ engine.setColumns(newColumns)
613
+ engine.getDiagnostics()
614
+ diagnostics.value = engine.diagnostics
615
+ emit('diagnostics', engine.diagnostics)
616
+ },
617
+ )
618
+
619
+ watch(
620
+ () => props.registry,
621
+ (newReg) => {
622
+ engine.setRegistry(newReg)
623
+ engine.getDiagnostics()
624
+ diagnostics.value = engine.diagnostics
625
+ emit('diagnostics', engine.diagnostics)
626
+ },
627
+ )
628
+
629
+ function onWindowScroll() {
630
+ if (focused.value && activated.value && context.value) {
631
+ updatePanelPosition(context.value)
632
+ }
633
+ }
634
+
635
+ onMounted(() => {
636
+ autoResize()
637
+ window.addEventListener('scroll', onWindowScroll, true)
638
+ if (props.autofocus) {
639
+ nextTick(() => {
640
+ textareaRef.value?.focus()
641
+ })
642
+ }
643
+ })
644
+
645
+ onBeforeUnmount(() => {
646
+ activated.value = false
647
+ window.removeEventListener('scroll', onWindowScroll, true)
648
+ })
649
+
650
+ function focus() {
651
+ textareaRef.value?.focus()
652
+ }
653
+
654
+ function blur() {
655
+ textareaRef.value?.blur()
656
+ }
657
+
658
+ function getQueryStatus() {
659
+ engine.setQuery(props.modelValue)
660
+ return engine.getQueryStatus()
661
+ }
662
+
663
+ function getParsedColumns() {
664
+ engine.setQuery(props.modelValue)
665
+ return engine.getParsedColumns()
666
+ }
667
+
668
+ defineExpose({ focus, blur, getQueryStatus, getParsedColumns })
669
+ </script>
670
+
671
+ <style scoped>
672
+ .flyql-columns {
673
+ position: relative;
674
+ background: var(--flyql-bg);
675
+ border: 1px solid var(--flyql-border);
676
+ border-radius: 8px;
677
+ transition: border-color 0.15s;
678
+ }
679
+
680
+ .flyql-columns--focused {
681
+ border-color: var(--flyql-border-focus);
682
+ }
683
+
684
+ .flyql-columns__icon {
685
+ position: absolute;
686
+ left: 10px;
687
+ top: 9px;
688
+ font-size: 13px;
689
+ color: var(--flyql-placeholder-color);
690
+ pointer-events: none;
691
+ z-index: 1;
692
+ }
693
+
694
+ .flyql-columns__container {
695
+ position: relative;
696
+ }
697
+
698
+ .flyql-columns__highlight,
699
+ .flyql-columns__input {
700
+ font-family: var(--flyql-code-font-family);
701
+ font-size: var(--flyql-font-size);
702
+ line-height: 18px;
703
+ padding: 6px 8px 6px 32px;
704
+ margin: 0;
705
+ white-space: pre-wrap;
706
+ word-wrap: break-word;
707
+ overflow-wrap: break-word;
708
+ border: none;
709
+ outline: none;
710
+ box-sizing: border-box;
711
+ width: 100%;
712
+ }
713
+
714
+ .flyql-columns__highlight {
715
+ position: absolute;
716
+ top: 0;
717
+ left: 0;
718
+ right: 0;
719
+ bottom: 0;
720
+ pointer-events: none;
721
+ overflow: hidden;
722
+ color: var(--flyql-text);
723
+ background: transparent;
724
+ }
725
+
726
+ .flyql-columns__input {
727
+ position: relative;
728
+ display: block;
729
+ resize: none;
730
+ overflow: hidden;
731
+ background: transparent;
732
+ color: transparent;
733
+ caret-color: var(--flyql-text);
734
+ }
735
+
736
+ .flyql-columns__input::placeholder {
737
+ color: var(--flyql-placeholder-color);
738
+ }
739
+ </style>
740
+
741
+ <style>
742
+ /* Columns highlighting classes (unscoped so they apply inside v-html) */
743
+ .flyql-col-column {
744
+ color: var(--flyql-key-color);
745
+ }
746
+
747
+ .flyql-col-operator {
748
+ color: var(--flyql-operator-color);
749
+ }
750
+
751
+ .flyql-col-transformer {
752
+ color: var(--flyql-transformer-color);
753
+ }
754
+
755
+ .flyql-dark .flyql-col-transformer {
756
+ color: var(--flyql-transformer-color);
757
+ }
758
+
759
+ .flyql-col-argument {
760
+ color: var(--flyql-value-color);
761
+ }
762
+
763
+ .flyql-col-alias {
764
+ color: var(--flyql-operator-color);
765
+ font-style: italic;
766
+ }
767
+
768
+ .flyql-col-error {
769
+ color: var(--flyql-error-color);
770
+ text-decoration: wavy underline;
771
+ }
772
+
773
+ /* Columns panel badge styles */
774
+ .flyql-panel__badge--transformer {
775
+ background: var(--flyql-transformer-color);
776
+ color: #fff;
777
+ }
778
+
779
+ .flyql-panel__badge--delimiter {
780
+ background: var(--flyql-placeholder-color);
781
+ color: #fff;
782
+ }
783
+ </style>