@bagelink/vue 1.2.77 → 1.2.81
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.
- package/bin/experimentalGenTypedRoutes.ts +13 -2
- package/dist/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Carousel2.vue.d.ts +89 -0
- package/dist/components/Carousel2.vue.d.ts.map +1 -0
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts +4 -3
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/CalendarPopover.vue.d.ts +2 -2
- package/dist/components/calendar/views/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/form/BagelForm.vue.d.ts +1 -0
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/BglMultiStepForm.vue.d.ts +7 -4
- package/dist/components/form/BglMultiStepForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +14 -6
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/index.cjs +173 -143
- package/dist/index.mjs +173 -143
- package/dist/style.css +206 -176
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Slider.vue +13 -11
- package/src/components/form/BagelForm.vue +2 -1
- package/src/components/form/BglMultiStepForm.vue +47 -32
- package/src/components/form/inputs/CodeEditor/Index.vue +160 -98
- package/src/components/form/inputs/RichText/index.vue +12 -11
- package/src/utils/index.ts +38 -13
- package/dist/types/timeago.d.ts +0 -23
- package/dist/types/timeago.d.ts.map +0 -1
|
@@ -7,7 +7,7 @@ type AutoplayMode = 'disabled' | 'standard' | 'linear'
|
|
|
7
7
|
type AutoplayValue = boolean | AutoplayMode
|
|
8
8
|
|
|
9
9
|
interface CarouselOptions {
|
|
10
|
-
|
|
10
|
+
speed?: number
|
|
11
11
|
easing?: Easing
|
|
12
12
|
perPage?: number | { [key: number]: number }
|
|
13
13
|
startIndex?: number
|
|
@@ -22,6 +22,7 @@ interface CarouselOptions {
|
|
|
22
22
|
pauseOnHover?: boolean
|
|
23
23
|
dots?: boolean
|
|
24
24
|
slideWidth?: number
|
|
25
|
+
slideGap?: number
|
|
25
26
|
onInit?: () => void
|
|
26
27
|
onChange?: () => void
|
|
27
28
|
}
|
|
@@ -35,7 +36,7 @@ interface DragState {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const props = withDefaults(defineProps<CarouselOptions>(), {
|
|
38
|
-
|
|
39
|
+
speed: 350,
|
|
39
40
|
easing: 'ease-out',
|
|
40
41
|
startIndex: 0,
|
|
41
42
|
draggable: true,
|
|
@@ -44,11 +45,11 @@ const props = withDefaults(defineProps<CarouselOptions>(), {
|
|
|
44
45
|
loop: true,
|
|
45
46
|
rtl: false,
|
|
46
47
|
autoplay: 'disabled',
|
|
47
|
-
autoplayInterval:
|
|
48
|
-
autoplaySpeed: 50,
|
|
48
|
+
autoplayInterval: 5000,
|
|
49
49
|
pauseOnHover: true,
|
|
50
50
|
dots: false,
|
|
51
51
|
slideWidth: 300,
|
|
52
|
+
slideGap: 1
|
|
52
53
|
})
|
|
53
54
|
|
|
54
55
|
const emit = defineEmits<{
|
|
@@ -118,7 +119,7 @@ const config = computed<ComputedConfig>(() => {
|
|
|
118
119
|
const calculatedPerPage = perPage.value
|
|
119
120
|
|
|
120
121
|
return {
|
|
121
|
-
|
|
122
|
+
speed: props.speed,
|
|
122
123
|
easing: props.easing,
|
|
123
124
|
perPage: calculatedPerPage,
|
|
124
125
|
startIndex: props.startIndex,
|
|
@@ -129,10 +130,11 @@ const config = computed<ComputedConfig>(() => {
|
|
|
129
130
|
rtl: props.rtl,
|
|
130
131
|
autoplay: autoplayMode,
|
|
131
132
|
autoplayInterval: props.autoplayInterval,
|
|
132
|
-
autoplaySpeed: props.
|
|
133
|
+
autoplaySpeed: props.speed / 4,
|
|
133
134
|
pauseOnHover: props.pauseOnHover,
|
|
134
135
|
dots: props.dots,
|
|
135
136
|
slideWidth: props.slideWidth,
|
|
137
|
+
slideGap: props.slideGap,
|
|
136
138
|
onInit: () => { emit('init') },
|
|
137
139
|
onChange: () => { emit('change') }
|
|
138
140
|
}
|
|
@@ -343,7 +345,7 @@ function buildSliderFrameItem(elm: Element): HTMLElement {
|
|
|
343
345
|
const elementContainer = document.createElement('div')
|
|
344
346
|
elementContainer.style.cssFloat = config.value.rtl ? 'right' : 'left'
|
|
345
347
|
elementContainer.style.float = config.value.rtl ? 'right' : 'left'
|
|
346
|
-
|
|
348
|
+
elementContainer.style.padding = config.value.slideGap ? `${config.value.slideGap / 2}rem` : '0'
|
|
347
349
|
const percentage = config.value.loop
|
|
348
350
|
? 100 / (innerElements.value.length + (perPage.value * 2))
|
|
349
351
|
: 100 / innerElements.value.length
|
|
@@ -474,8 +476,8 @@ function disableTransition(): void {
|
|
|
474
476
|
|
|
475
477
|
function enableTransition(): void {
|
|
476
478
|
if (sliderFrame.value) {
|
|
477
|
-
sliderFrame.value.style.webkitTransition = `all ${config.value.
|
|
478
|
-
sliderFrame.value.style.transition = `all ${config.value.
|
|
479
|
+
sliderFrame.value.style.webkitTransition = `all ${config.value.speed}ms ${config.value.easing}`
|
|
480
|
+
sliderFrame.value.style.transition = `all ${config.value.speed}ms ${config.value.easing}`
|
|
479
481
|
}
|
|
480
482
|
}
|
|
481
483
|
|
|
@@ -996,7 +998,7 @@ defineExpose({
|
|
|
996
998
|
width: 12px;
|
|
997
999
|
height: 12px;
|
|
998
1000
|
border-radius: 50px;
|
|
999
|
-
background-color:
|
|
1001
|
+
background-color: var(--bgl-gray-light);
|
|
1000
1002
|
border: none;
|
|
1001
1003
|
padding: 0;
|
|
1002
1004
|
cursor: pointer;
|
|
@@ -1004,7 +1006,7 @@ defineExpose({
|
|
|
1004
1006
|
}
|
|
1005
1007
|
|
|
1006
1008
|
.carousel-dot.active {
|
|
1007
|
-
background-color:
|
|
1009
|
+
background-color: var(--bgl-primary);
|
|
1008
1010
|
width: 26px;
|
|
1009
1011
|
}
|
|
1010
1012
|
</style>
|
|
@@ -111,6 +111,7 @@ function updateFormData(fieldId: string, value: any) {
|
|
|
111
111
|
|
|
112
112
|
// Form validation
|
|
113
113
|
const validateForm = () => form.value?.reportValidity() ?? false
|
|
114
|
+
const checkValidity = () => form.value?.checkValidity() ?? false
|
|
114
115
|
const formError = ref<Error>()
|
|
115
116
|
// Form submission
|
|
116
117
|
async function handleSubmit() {
|
|
@@ -156,7 +157,7 @@ function handleSlotInputChange(event: Event) {
|
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
defineExpose({ form, isDirty, validateForm, resolveSchema, refreshSchema })
|
|
160
|
+
defineExpose({ form, isDirty, validateForm, resolveSchema, refreshSchema, checkValidity })
|
|
160
161
|
</script>
|
|
161
162
|
|
|
162
163
|
<template>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import type { BglFormSchemaFnT } from '@bagelink/vue'
|
|
3
3
|
|
|
4
4
|
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers'
|
|
5
|
-
import { BagelForm, Btn, useBglSchema } from '@bagelink/vue'
|
|
6
|
-
import { ref, watch, computed } from 'vue'
|
|
5
|
+
import { BagelForm, Btn, useBglSchema, sleep } from '@bagelink/vue'
|
|
6
|
+
import { ref, watch, computed, nextTick } from 'vue'
|
|
7
7
|
|
|
8
8
|
const props = withDefaults(
|
|
9
9
|
defineProps<{
|
|
@@ -13,7 +13,7 @@ const props = withDefaults(
|
|
|
13
13
|
'schema' | `${string}modelValue` | `ref${string}` | `onVnode${string}` | 'onSubmit'
|
|
14
14
|
)
|
|
15
15
|
>
|
|
16
|
-
schema
|
|
16
|
+
schema?: BglFormSchemaFnT<T>
|
|
17
17
|
showProgress?: boolean
|
|
18
18
|
rtl?: boolean
|
|
19
19
|
stepLabels?: string[]
|
|
@@ -119,9 +119,6 @@ const currentStepSchema = computed(() => {
|
|
|
119
119
|
return [computedSchema.value[actualIndex]]
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
-
const isStepping = ref(false)
|
|
123
|
-
let isSteppingTO: NodeJS.Timeout
|
|
124
|
-
|
|
125
122
|
// Tracks which way we're sliding (left or right)
|
|
126
123
|
const slideDirection = ref(props.rtl ? 'right' : 'left')
|
|
127
124
|
|
|
@@ -138,9 +135,7 @@ watch(
|
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
previousStep.value = oldStep
|
|
141
|
-
|
|
142
|
-
isStepping.value = true
|
|
143
|
-
isSteppingTO = setTimeout(() => (isStepping.value = false), 200)
|
|
138
|
+
// We don't need isStepping flag anymore
|
|
144
139
|
emits('stepChange', {
|
|
145
140
|
newStep,
|
|
146
141
|
oldStep,
|
|
@@ -150,19 +145,32 @@ watch(
|
|
|
150
145
|
}
|
|
151
146
|
)
|
|
152
147
|
|
|
153
|
-
const
|
|
148
|
+
const isLastStep = computed(() => currentStep.value === numberOfSteps.value - 1)
|
|
154
149
|
|
|
155
150
|
const isStepValidated = computed(() => (stepIndex: number) => validatedSteps.value.includes(stepIndex))
|
|
156
151
|
|
|
152
|
+
// Add a computed property to check if current step is valid
|
|
153
|
+
const isStepValid = ref(false)
|
|
154
|
+
|
|
155
|
+
async function checkCurrentStepValidity() {
|
|
156
|
+
await nextTick()
|
|
157
|
+
if (!props.validateOnSteps) isStepValid.value = true
|
|
158
|
+
else isStepValid.value = formRef.value?.checkValidity() ?? false
|
|
159
|
+
}
|
|
160
|
+
|
|
157
161
|
function prevStep() {
|
|
158
162
|
if (currentStep.value > 0) currentStep.value--
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
const formContainer = ref<HTMLElement>()
|
|
162
166
|
|
|
163
|
-
function nextStep() {
|
|
167
|
+
async function nextStep() {
|
|
168
|
+
// Always use reportValidity when attempting to move to next step
|
|
169
|
+
// This will show validation errors to the user
|
|
164
170
|
if (props.validateOnSteps && reportValidity() === false) return
|
|
165
|
-
if (
|
|
171
|
+
if (!isLastStep.value) currentStep.value++
|
|
172
|
+
await sleep(400)
|
|
173
|
+
checkCurrentStepValidity()
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
function goToStep(stepIndex: number) {
|
|
@@ -173,6 +181,7 @@ function goToStep(stepIndex: number) {
|
|
|
173
181
|
}
|
|
174
182
|
|
|
175
183
|
// For forward navigation to non-validated steps, validate current step first if needed
|
|
184
|
+
// This will show validation errors to the user
|
|
176
185
|
if (props.validateOnSteps && reportValidity() === false) {
|
|
177
186
|
return false
|
|
178
187
|
}
|
|
@@ -187,6 +196,7 @@ function goToStep(stepIndex: number) {
|
|
|
187
196
|
}
|
|
188
197
|
|
|
189
198
|
function handleSubmit() {
|
|
199
|
+
// Show validation errors to the user when submitting
|
|
190
200
|
if (reportValidity() === false) return
|
|
191
201
|
emits('submit', formData.value)
|
|
192
202
|
}
|
|
@@ -194,15 +204,11 @@ function handleSubmit() {
|
|
|
194
204
|
function reset() {
|
|
195
205
|
validatedSteps.value = []
|
|
196
206
|
currentStep.value = 0
|
|
197
|
-
// Clear form if BagelForm supports it
|
|
198
|
-
// if (formRef.value && typeof formRef.value.clearForm === 'function') {
|
|
199
|
-
// formRef.value.clearForm()
|
|
200
|
-
// }
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
// Re-evaluate filtered steps when formData changes
|
|
204
210
|
watch(() => formData.value, () => {
|
|
205
|
-
|
|
211
|
+
checkCurrentStepValidity()
|
|
206
212
|
if (currentStep.value >= numberOfSteps.value && numberOfSteps.value > 0) {
|
|
207
213
|
currentStep.value = 0
|
|
208
214
|
}
|
|
@@ -211,11 +217,13 @@ watch(() => formData.value, () => {
|
|
|
211
217
|
defineExpose({
|
|
212
218
|
submit: handleSubmit,
|
|
213
219
|
validateForm: reportValidity,
|
|
220
|
+
checkValidity: formRef.value?.checkValidity,
|
|
214
221
|
isDirty: computed(() => formRef.value?.isDirty),
|
|
215
222
|
reset,
|
|
216
223
|
goToStep,
|
|
217
224
|
currentStep: computed(() => currentStep.value),
|
|
218
225
|
totalSteps: computed(() => numberOfSteps.value),
|
|
226
|
+
isStepValid,
|
|
219
227
|
nextStep,
|
|
220
228
|
prevStep,
|
|
221
229
|
})
|
|
@@ -260,7 +268,7 @@ defineExpose({
|
|
|
260
268
|
:name="slideDirection === 'right' ? 'slide-right' : 'slide-left'"
|
|
261
269
|
mode="out-in"
|
|
262
270
|
>
|
|
263
|
-
<div
|
|
271
|
+
<div :key="currentStep" ref="formContainer" class="bgl-form-container">
|
|
264
272
|
<BagelForm
|
|
265
273
|
ref="formRef"
|
|
266
274
|
v-model="formData"
|
|
@@ -286,11 +294,17 @@ defineExpose({
|
|
|
286
294
|
submit: handleSubmit,
|
|
287
295
|
currentStep,
|
|
288
296
|
totalSteps: numberOfSteps,
|
|
289
|
-
|
|
297
|
+
isLastStep,
|
|
298
|
+
isStepValid,
|
|
290
299
|
}"
|
|
291
300
|
>
|
|
292
|
-
<Btn v-if="currentStep !== 0" color="gray" icon="arrow_back" click="prevStep" />
|
|
293
|
-
<Btn
|
|
301
|
+
<Btn v-if="currentStep !== 0" color="gray" icon="arrow_back" @click="prevStep" />
|
|
302
|
+
<Btn
|
|
303
|
+
v-if="!isLastStep"
|
|
304
|
+
icon="arrow_forward"
|
|
305
|
+
:disabled="props.validateOnSteps && !isStepValid"
|
|
306
|
+
@click="nextStep"
|
|
307
|
+
/>
|
|
294
308
|
<Btn v-else value="Submit" @click="handleSubmit" />
|
|
295
309
|
</slot>
|
|
296
310
|
</div>
|
|
@@ -305,12 +319,10 @@ defineExpose({
|
|
|
305
319
|
gap: 1rem;
|
|
306
320
|
width: 100%;
|
|
307
321
|
/* Default transition duration */
|
|
308
|
-
--transition-duration:
|
|
322
|
+
--transition-duration: 300ms;
|
|
309
323
|
--move-distance: 35%;
|
|
310
|
-
--ease-in:
|
|
311
|
-
--ease-out:
|
|
312
|
-
/* --ease-in: cubic-bezier(0.42, 0, 0.58, 1); */
|
|
313
|
-
/* --ease-out: cubic-bezier(0.5, 0, 0.75, 0); */
|
|
324
|
+
--ease-in: cubic-bezier(0.42, 0, 0.58, 1);
|
|
325
|
+
--ease-out: cubic-bezier(0.42, 0, 0.58, 1);
|
|
314
326
|
}
|
|
315
327
|
|
|
316
328
|
.bgl-steps-indicator {
|
|
@@ -398,16 +410,18 @@ defineExpose({
|
|
|
398
410
|
justify-content: center;
|
|
399
411
|
gap: 1rem;
|
|
400
412
|
grid-area: unset !important;
|
|
401
|
-
|
|
413
|
+
margin-top: 2rem;
|
|
402
414
|
}
|
|
403
415
|
|
|
404
416
|
/* Slide Left Animation (going forward) */
|
|
405
417
|
.slide-left-enter-active {
|
|
406
|
-
transition: opacity
|
|
418
|
+
transition: opacity var(--transition-duration) var(--ease-in),
|
|
419
|
+
transform var(--transition-duration) var(--ease-in);
|
|
407
420
|
}
|
|
408
421
|
|
|
409
422
|
.slide-left-leave-active {
|
|
410
|
-
transition: opacity
|
|
423
|
+
transition: opacity var(--transition-duration) var(--ease-out),
|
|
424
|
+
transform var(--transition-duration) var(--ease-out);
|
|
411
425
|
}
|
|
412
426
|
|
|
413
427
|
.slide-left-enter-from {
|
|
@@ -422,12 +436,13 @@ defineExpose({
|
|
|
422
436
|
|
|
423
437
|
/* Slide Right Animation (going back) */
|
|
424
438
|
.slide-right-enter-active {
|
|
425
|
-
transition:
|
|
439
|
+
transition: opacity var(--transition-duration) var(--ease-in),
|
|
440
|
+
transform var(--transition-duration) var(--ease-in);
|
|
426
441
|
}
|
|
427
442
|
|
|
428
443
|
.slide-right-leave-active {
|
|
429
|
-
transition:
|
|
430
|
-
|
|
444
|
+
transition: opacity var(--transition-duration) var(--ease-out),
|
|
445
|
+
transform var(--transition-duration) var(--ease-out);
|
|
431
446
|
}
|
|
432
447
|
|
|
433
448
|
.slide-right-enter-from {
|
|
@@ -7,10 +7,9 @@ declare global {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
import { appendStyle, appendScript } from '@bagelink/vue'
|
|
10
|
-
import {
|
|
10
|
+
import { onMounted, ref, computed, watch } from 'vue'
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const { language, readonly = false, modelValue = '', autodetect, ignoreIllegals = true, label, height = '300px' } = defineProps<{
|
|
12
|
+
interface CodeEditorProps {
|
|
14
13
|
language?: Language
|
|
15
14
|
readonly?: boolean
|
|
16
15
|
modelValue?: string
|
|
@@ -18,39 +17,50 @@ const { language, readonly = false, modelValue = '', autodetect, ignoreIllegals
|
|
|
18
17
|
ignoreIllegals?: boolean
|
|
19
18
|
label?: string
|
|
20
19
|
height?: string
|
|
21
|
-
}
|
|
20
|
+
}
|
|
21
|
+
// Props with default values
|
|
22
|
+
const props = withDefaults(defineProps<CodeEditorProps>(), {
|
|
23
|
+
language: 'html',
|
|
24
|
+
readonly: false,
|
|
25
|
+
modelValue: '',
|
|
26
|
+
autodetect: true,
|
|
27
|
+
ignoreIllegals: true,
|
|
28
|
+
label: '',
|
|
29
|
+
height: '240px'
|
|
30
|
+
})
|
|
22
31
|
|
|
23
32
|
const emit = defineEmits(['update:modelValue'])
|
|
33
|
+
// State
|
|
34
|
+
const code = ref(props.modelValue || '')
|
|
35
|
+
const editorRef = ref<HTMLDivElement>()
|
|
36
|
+
const loaded = ref(false)
|
|
37
|
+
const hljs = ref<HilightJS | null>(null)
|
|
38
|
+
// Computed
|
|
39
|
+
const maxHeight = computed(() => {
|
|
40
|
+
const h = props.height ?? '240px'
|
|
41
|
+
return h.match(/^\d+$/) ? `${h}px` : h
|
|
42
|
+
})
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
const formattedCode = computed(() => {
|
|
45
|
+
if (!hljs.value) return escapeHtml(code.value)
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let code = $ref('')
|
|
30
|
-
const textarea = $ref<HTMLTextAreaElement>()
|
|
31
|
-
let loaded = $ref(false)
|
|
47
|
+
try {
|
|
48
|
+
const lang = props.language || ''
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})
|
|
50
|
+
if (lang && !props.autodetect && !hljs.value.getLanguage(lang)) {
|
|
51
|
+
console.warn(`The language "${lang}" is not available.`)
|
|
52
|
+
return escapeHtml(code.value)
|
|
53
|
+
}
|
|
38
54
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
55
|
+
const result = props.autodetect
|
|
56
|
+
? hljs.value.highlightAuto(code.value)
|
|
57
|
+
: hljs.value.highlight(code.value, { language: lang, ignoreIllegals: props.ignoreIllegals })
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
console.
|
|
47
|
-
return escapeHtml(code)
|
|
59
|
+
return result.value || escapeHtml(code.value)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Highlighting error:', error)
|
|
62
|
+
return escapeHtml(code.value)
|
|
48
63
|
}
|
|
49
|
-
const lang = language || ''
|
|
50
|
-
const result = autodetect
|
|
51
|
-
? hljs?.highlightAuto(code)
|
|
52
|
-
: hljs?.highlight(code, { language: lang, ignoreIllegals })
|
|
53
|
-
return result?.value || ''
|
|
54
64
|
})
|
|
55
65
|
|
|
56
66
|
// Methods
|
|
@@ -67,113 +77,165 @@ function escapeHtml(unsafe: string) {
|
|
|
67
77
|
})
|
|
68
78
|
}
|
|
69
79
|
|
|
80
|
+
function handleInput(e: Event) {
|
|
81
|
+
const target = e.target as HTMLTextAreaElement
|
|
82
|
+
code.value = target.value
|
|
83
|
+
emit('update:modelValue', code.value)
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
function handleTab(event: KeyboardEvent) {
|
|
87
|
+
if (event.key !== 'Tab') return
|
|
88
|
+
|
|
89
|
+
event.preventDefault()
|
|
71
90
|
const target = event.target as HTMLTextAreaElement
|
|
72
91
|
const start = target.selectionStart
|
|
73
|
-
const
|
|
74
|
-
code = code.slice(0, start) + tab + code.slice(start)
|
|
75
|
-
nextTick(() => {
|
|
76
|
-
target.selectionStart = target.selectionEnd = start + tab.length
|
|
77
|
-
})
|
|
78
|
-
}
|
|
92
|
+
const end = target.selectionEnd
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
// Add tab or indent selected text
|
|
95
|
+
const newValue = `${code.value.substring(0, start)} ${code.value.substring(end)}`
|
|
96
|
+
code.value = newValue
|
|
97
|
+
emit('update:modelValue', code.value)
|
|
98
|
+
|
|
99
|
+
// Move cursor position after the inserted tab
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
target.selectionStart = target.selectionEnd = start + 2
|
|
102
|
+
}, 0)
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
// Lifecycle
|
|
87
106
|
onMounted(async () => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
try {
|
|
108
|
+
// Load highlight.js
|
|
109
|
+
await appendScript('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js', { id: 'hljs-cdn' })
|
|
110
|
+
await appendStyle('https://cdn.jsdelivr.net/npm/highlight.js/styles/atom-one-dark.min.css')
|
|
111
|
+
|
|
112
|
+
if (window.hljs) {
|
|
113
|
+
hljs.value = window.hljs
|
|
114
|
+
loaded.value = true
|
|
115
|
+
} else {
|
|
116
|
+
console.error('Failed to load highlight.js')
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('Error loading highlight.js:', error)
|
|
98
120
|
}
|
|
99
121
|
})
|
|
100
122
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (newVal !== code) {
|
|
104
|
-
code = newVal
|
|
123
|
+
// Watch for external modelValue changes
|
|
124
|
+
watch(() => props.modelValue, (newVal) => {
|
|
125
|
+
if (newVal !== undefined && newVal !== code.value) {
|
|
126
|
+
code.value = newVal
|
|
105
127
|
}
|
|
106
128
|
}, { immediate: true })
|
|
107
129
|
</script>
|
|
108
130
|
|
|
109
131
|
<template>
|
|
110
|
-
<div class="
|
|
111
|
-
<label v-if="label" class="label
|
|
112
|
-
<div
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
<div class="code-editor-container ltr" :style="{ maxHeight }">
|
|
133
|
+
<label v-if="label" class="label">{{ label }}</label>
|
|
134
|
+
<div
|
|
135
|
+
v-if="loaded"
|
|
136
|
+
ref="editorRef"
|
|
137
|
+
class="code-editor-grandpa"
|
|
138
|
+
>
|
|
139
|
+
<div class="editor-content-papa relative">
|
|
140
|
+
<pre class="code-display" wrap><code v-html="formattedCode" /></pre>
|
|
117
141
|
<textarea
|
|
118
142
|
v-if="!readonly"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class="code-editor absolute inset-0 bg-transparent overflow-hidden h-100 p-0 m-0 codeText border-none txt-start"
|
|
143
|
+
:value="code"
|
|
144
|
+
class="code-input"
|
|
122
145
|
spellcheck="false"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@
|
|
127
|
-
@
|
|
146
|
+
autocomplete="off"
|
|
147
|
+
autocorrect="off"
|
|
148
|
+
autocapitalize="off"
|
|
149
|
+
@input="handleInput"
|
|
150
|
+
@keydown="handleTab"
|
|
128
151
|
/>
|
|
129
152
|
</div>
|
|
130
153
|
</div>
|
|
131
154
|
</div>
|
|
132
155
|
</template>
|
|
133
156
|
|
|
134
|
-
<style>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
position: absolute;
|
|
157
|
+
<style scoped>
|
|
158
|
+
.code-editor-container {
|
|
159
|
+
margin-bottom: 0.5rem;
|
|
160
|
+
height: 100%;
|
|
139
161
|
}
|
|
140
|
-
</style>
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
word-wrap: break-word;
|
|
147
|
-
caret-color: var(--bgl-white);
|
|
148
|
-
color: var(--bgl-white);
|
|
163
|
+
.label {
|
|
164
|
+
display: block;
|
|
165
|
+
text-align: left;
|
|
166
|
+
margin-bottom: 0.25rem;
|
|
149
167
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
|
|
169
|
+
.code-editor-grandpa {
|
|
170
|
+
background: #22252A;
|
|
171
|
+
border-radius: 0.25rem;
|
|
172
|
+
width: 100%;
|
|
173
|
+
height: 100%;
|
|
174
|
+
overflow: auto;
|
|
175
|
+
padding: 1ch;
|
|
176
|
+
padding-inline-start: 2ch;
|
|
153
177
|
}
|
|
154
|
-
.code-editor-wrap:focus-within, .code-editor-wrap:focus-visible, .code-editor-wrap:focus {
|
|
155
|
-
box-shadow: inset 0 0 10px #00000021;
|
|
156
|
-
outline: solid 1px var(--border-color);
|
|
157
|
-
/* outline: -webkit-focus-ring-color auto 1px; */
|
|
158
178
|
|
|
179
|
+
.code-editor-grandpa:focus-within {
|
|
180
|
+
outline: solid 1px var(--border-color, #4f575f);
|
|
181
|
+
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.13);
|
|
159
182
|
}
|
|
160
183
|
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
184
|
+
.editor-content-papa {
|
|
185
|
+
position: relative;
|
|
186
|
+
width: 100%;
|
|
187
|
+
padding-bottom: calc(100% - 5lh);
|
|
188
|
+
}
|
|
189
|
+
.code-display,
|
|
190
|
+
.code-input {
|
|
191
|
+
inset: 0;
|
|
192
|
+
margin: 0;
|
|
193
|
+
padding: 0;
|
|
194
|
+
width: 100%;
|
|
195
|
+
height: 100%;
|
|
196
|
+
overflow: auto;
|
|
197
|
+
font-family: monospace;
|
|
198
|
+
font-size: 1em;
|
|
199
|
+
line-height: 1.5;
|
|
200
|
+
tab-size: 2;
|
|
201
|
+
word-break: keep-all;
|
|
202
|
+
text-align: left;
|
|
164
203
|
}
|
|
165
204
|
|
|
166
|
-
.code-
|
|
167
|
-
|
|
168
|
-
|
|
205
|
+
.code-display {
|
|
206
|
+
position: relative;
|
|
207
|
+
color: #fff;
|
|
208
|
+
pointer-events: none;
|
|
209
|
+
z-index: 1;
|
|
169
210
|
}
|
|
170
211
|
|
|
171
|
-
.code-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
.code-display code {
|
|
213
|
+
display: block;
|
|
214
|
+
background: transparent !important;
|
|
215
|
+
padding: 0 !important;
|
|
174
216
|
}
|
|
175
217
|
|
|
176
|
-
.code-
|
|
177
|
-
|
|
218
|
+
.code-input {
|
|
219
|
+
position: absolute;
|
|
220
|
+
background: transparent;
|
|
221
|
+
color: transparent;
|
|
222
|
+
caret-color: #fff;
|
|
223
|
+
border: none;
|
|
224
|
+
resize: none;
|
|
225
|
+
outline: none;
|
|
226
|
+
z-index: 2;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.code-input::selection {
|
|
230
|
+
background-color: rgba(36, 102, 188, 0.3);
|
|
231
|
+
color: transparent;
|
|
232
|
+
}
|
|
233
|
+
</style>
|
|
234
|
+
|
|
235
|
+
<style>
|
|
236
|
+
/* Global styles */
|
|
237
|
+
pre code.hljs {
|
|
238
|
+
padding: 0 !important;
|
|
239
|
+
background: transparent !important;
|
|
178
240
|
}
|
|
179
241
|
</style>
|