Skip to content

Commit ed1b8df

Browse files
committed
chore: update dependencies and devDependencies
@coreui/coreui ^5.2.0 → ^5.4.0 @docsearch/css ^3.8.2 → ^3.9.0 @docsearch/js ^3.8.2 → ^3.9.0 @rollup/plugin-commonjs ^28.0.2 → ^28.0.3 @rollup/plugin-node-resolve ^16.0.0 → ^16.0.1 eslint ^9.17.0 → ^9.28.0 eslint-config-prettier ^9.1.0 → ^10.1.5 eslint-plugin-prettier ^5.2.1 → ^5.4.1 eslint-plugin-unicorn ^56.0.1 → ^59.0.1 eslint-plugin-vue ^9.32.0 → ^10.1.0 globals ^15.14.0 → ^16.2.0 lerna ^8.1.9 → ^8.2.2 prettier ^3.4.2 → ^3.5.3 rollup ^4.30.1 → ^4.41.1 sass ^1.83.1 → ^1.89.1 ts-jest ^29.2.5 → ^29.3.4 typescript ^5.7.2 → ^5.8.3 typescript-eslint ^8.19.1 → ^8.33.1 vue ^3.5.13 → ^3.5.16 vue-types ^5.1.3 → ^6.0.0
1 parent e024754 commit ed1b8df

File tree

8 files changed

+371
-21
lines changed

8 files changed

+371
-21
lines changed

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
},
2424
"devDependencies": {
2525
"@vue/vue3-jest": "29.2.6",
26-
"eslint": "^9.17.0",
27-
"eslint-config-prettier": "^9.1.0",
28-
"eslint-plugin-prettier": "^5.2.1",
29-
"eslint-plugin-unicorn": "^56.0.1",
30-
"eslint-plugin-vue": "^9.32.0",
31-
"globals": "^15.14.0",
32-
"lerna": "^8.1.9",
26+
"eslint": "^9.28.0",
27+
"eslint-config-prettier": "^10.1.5",
28+
"eslint-plugin-prettier": "^5.4.1",
29+
"eslint-plugin-unicorn": "^59.0.1",
30+
"eslint-plugin-vue": "^10.1.0",
31+
"globals": "^16.2.0",
32+
"lerna": "^8.2.2",
3333
"npm-run-all": "^4.1.5",
34-
"prettier": "^3.4.2",
35-
"typescript-eslint": "^8.19.1"
34+
"prettier": "^3.5.3",
35+
"typescript-eslint": "^8.33.1"
3636
}
3737
}

packages/coreui-vue/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,25 @@
4141
"test:update": "jest --coverage --updateSnapshot"
4242
},
4343
"dependencies": {
44-
"@coreui/coreui": "^5.2.0",
44+
"@coreui/coreui": "^5.4.0",
4545
"@popperjs/core": "^2.11.8"
4646
},
4747
"devDependencies": {
48-
"@rollup/plugin-commonjs": "^28.0.2",
49-
"@rollup/plugin-node-resolve": "^16.0.0",
48+
"@rollup/plugin-commonjs": "^28.0.3",
49+
"@rollup/plugin-node-resolve": "^16.0.1",
5050
"@rollup/plugin-typescript": "^12.1.2",
5151
"@types/jest": "^29.5.14",
5252
"@vue/test-utils": "^2.4.6",
5353
"@vue/vue3-jest": "29.2.6",
5454
"cross-env": "^7.0.3",
5555
"jest": "^29.7.0",
5656
"jest-environment-jsdom": "^29.7.0",
57-
"rollup": "^4.30.1",
57+
"rollup": "^4.41.1",
5858
"rollup-plugin-vue": "^6.0.0",
59-
"ts-jest": "^29.2.5",
60-
"typescript": "^5.7.2",
61-
"vue": "^3.5.13",
62-
"vue-types": "^5.1.3"
59+
"ts-jest": "^29.3.4",
60+
"typescript": "^5.8.3",
61+
"vue": "^3.5.16",
62+
"vue-types": "^6.0.0"
6363
},
6464
"peerDependencies": {
6565
"vue": "^3.5.0"
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// CStepper.ts
2+
import {
3+
defineComponent,
4+
h,
5+
ref,
6+
watch,
7+
computed,
8+
nextTick,
9+
onMounted,
10+
toRefs,
11+
shallowRef,
12+
watchEffect,
13+
} from 'vue'
14+
import { CCollapse } from '../collapse'
15+
import type { StepperStepData, StepperStepValidationResult } from './types'
16+
17+
export const CStepper = defineComponent({
18+
name: 'CStepper',
19+
inheritAttrs: false,
20+
props: {
21+
modelValue: Number,
22+
defaultActiveStepIndex: {
23+
type: Number,
24+
default: 0,
25+
},
26+
layout: {
27+
type: String as () => 'horizontal' | 'vertical',
28+
default: 'horizontal',
29+
},
30+
linear: {
31+
type: Boolean,
32+
default: true,
33+
},
34+
steps: {
35+
type: Array as () => StepperStepData[],
36+
required: true,
37+
},
38+
stepButtonLayout: {
39+
type: String as () => 'horizontal' | 'vertical',
40+
default: 'horizontal',
41+
},
42+
validation: {
43+
type: Boolean,
44+
default: true,
45+
},
46+
id: String,
47+
},
48+
emits: ['update:modelValue', 'finish', 'reset', 'stepChange', 'stepValidationComplete'],
49+
setup(props, { emit, slots, attrs, expose }) {
50+
const { modelValue, defaultActiveStepIndex, validation, steps, layout, linear } = toRefs(props)
51+
52+
const activeStepIndex = ref<number>(modelValue.value ?? defaultActiveStepIndex.value ?? 0)
53+
const isControlled = computed(() => modelValue.value !== undefined)
54+
const isFinished = ref(false)
55+
const stepsRef = ref<HTMLOListElement | null>(null)
56+
const stepButtonRefs = shallowRef<(HTMLButtonElement | null)[]>([])
57+
58+
watch(modelValue, (val) => {
59+
if (val !== undefined) activeStepIndex.value = val
60+
})
61+
62+
watch(activeStepIndex, (val) => {
63+
if (isControlled.value) emit('update:modelValue', val)
64+
})
65+
66+
const isStepValid = (index: number): boolean => {
67+
if (!validation.value) return true
68+
69+
const form = steps.value[index]?.formRef?.value
70+
if (!form) return true
71+
72+
const valid = form.checkValidity()
73+
emit('stepValidationComplete', { stepNumber: index + 1, isValid: valid })
74+
if (!valid) form.reportValidity()
75+
return valid
76+
}
77+
78+
const setActiveStep = (index: number, bypassValidation = false) => {
79+
if (index < 0 || index >= steps.value.length || index === activeStepIndex.value) return
80+
if (!bypassValidation && index > activeStepIndex.value && !isStepValid(activeStepIndex.value))
81+
return
82+
83+
activeStepIndex.value = index
84+
emit('stepChange', index + 1)
85+
}
86+
87+
const next = () => {
88+
if (activeStepIndex.value < steps.value.length - 1) {
89+
setActiveStep(activeStepIndex.value + 1)
90+
} else {
91+
finish()
92+
}
93+
}
94+
95+
const prev = () => {
96+
if (activeStepIndex.value > 0) {
97+
setActiveStep(activeStepIndex.value - 1, true)
98+
}
99+
}
100+
101+
const finish = () => {
102+
if (activeStepIndex.value === steps.value.length - 1 && isStepValid(activeStepIndex.value)) {
103+
isFinished.value = true
104+
emit('finish')
105+
}
106+
}
107+
108+
const reset = () => {
109+
if (validation.value) {
110+
steps.value.forEach((s) => s.formRef?.value?.reset?.())
111+
}
112+
activeStepIndex.value = defaultActiveStepIndex.value
113+
isFinished.value = false
114+
emit('reset')
115+
emit('stepChange', defaultActiveStepIndex.value)
116+
nextTick(() => {
117+
stepButtonRefs.value[defaultActiveStepIndex.value]?.focus()
118+
})
119+
}
120+
121+
const handleKeyDown = (event: KeyboardEvent) => {
122+
const buttons = stepButtonRefs.value
123+
const current = event.target as HTMLButtonElement
124+
const index = buttons.findIndex((b) => b === current)
125+
if (index === -1) return
126+
127+
let nextIndex = index
128+
switch (event.key) {
129+
case 'ArrowRight':
130+
case 'ArrowDown':
131+
nextIndex = (index + 1) % buttons.length
132+
break
133+
case 'ArrowLeft':
134+
case 'ArrowUp':
135+
nextIndex = (index - 1 + buttons.length) % buttons.length
136+
break
137+
case 'Home':
138+
nextIndex = 0
139+
break
140+
case 'End':
141+
nextIndex = buttons.length - 1
142+
break
143+
default:
144+
return
145+
}
146+
147+
event.preventDefault()
148+
buttons[nextIndex]?.focus()
149+
}
150+
151+
expose({ next, prev, finish, reset })
152+
153+
return () => {
154+
const isVertical = layout.value === 'vertical'
155+
stepButtonRefs.value = []
156+
157+
return h(
158+
'div',
159+
{
160+
...attrs,
161+
class: ['stepper', { 'stepper-vertical': isVertical }, attrs.class],
162+
},
163+
[
164+
h(
165+
'ol',
166+
{
167+
class: 'stepper-steps',
168+
role: 'tablist',
169+
'aria-orientation': isVertical ? 'vertical' : 'horizontal',
170+
onKeydown: handleKeyDown,
171+
ref: stepsRef,
172+
},
173+
steps.value.map((step, index) => {
174+
const isActive = !isFinished.value && index === activeStepIndex.value
175+
const isComplete = isFinished.value || index < activeStepIndex.value
176+
const isDisabled =
177+
isFinished.value || (linear.value && index > activeStepIndex.value + 1)
178+
const stepId = `step-${props.id || 'stepper'}-${index}`
179+
const panelId = `panel-${props.id || 'stepper'}-${index}`
180+
181+
return h(
182+
'li',
183+
{
184+
key: index,
185+
class: ['stepper-step', props.stepButtonLayout],
186+
role: 'presentation',
187+
},
188+
[
189+
h(
190+
'button',
191+
{
192+
type: 'button',
193+
class: ['stepper-step-button', { active: isActive, complete: isComplete }],
194+
disabled: isDisabled,
195+
id: stepId,
196+
role: 'tab',
197+
'aria-selected': isActive,
198+
tabindex: isActive ? 0 : -1,
199+
'aria-controls': step.content ? panelId : undefined,
200+
onClick: () =>
201+
setActiveStep(index, !linear.value || index <= activeStepIndex.value),
202+
ref: (el) => (stepButtonRefs.value[index] = el as HTMLButtonElement),
203+
},
204+
[
205+
h('span', { class: 'stepper-step-indicator' }, [
206+
isComplete
207+
? h('span', { class: 'stepper-step-indicator-icon' })
208+
: h(
209+
'span',
210+
{ class: 'stepper-step-indicator-text' },
211+
step.indicator ?? index + 1
212+
),
213+
]),
214+
h('span', { class: 'stepper-step-label' }, step.label),
215+
]
216+
),
217+
index < steps.value.length - 1 && h('div', { class: 'stepper-step-connector' }),
218+
step.content &&
219+
isVertical &&
220+
h(
221+
CCollapse,
222+
{
223+
class: 'stepper-step-content',
224+
id: panelId,
225+
role: 'tabpanel',
226+
visible: isActive,
227+
'aria-hidden': !isActive,
228+
'aria-labelledby': stepId,
229+
'aria-live': 'polite',
230+
},
231+
() => step.content
232+
),
233+
]
234+
)
235+
})
236+
),
237+
!isVertical &&
238+
steps.value.some((s) => s.content != null) &&
239+
h(
240+
'div',
241+
{ class: 'stepper-content' },
242+
steps.value.map((step, index) => {
243+
const isActive = !isFinished.value && index === activeStepIndex.value
244+
const stepId = `step-${props.id || 'stepper'}-${index}`
245+
const panelId = `panel-${props.id || 'stepper'}-${index}`
246+
247+
return h(
248+
'div',
249+
{
250+
key: index,
251+
id: panelId,
252+
role: 'tabpanel',
253+
'aria-hidden': !isActive,
254+
'aria-labelledby': stepId,
255+
'aria-live': 'polite',
256+
class: ['stepper-pane', { active: isActive, show: isActive }],
257+
},
258+
step.content
259+
)
260+
})
261+
),
262+
]
263+
)
264+
}
265+
},
266+
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { shallowMount } from '@vue/test-utils'
2+
import { CBadge as Component } from '../../'
3+
4+
const ComponentName = 'CBadge'
5+
const wrapper = shallowMount(Component)
6+
const customWrapper = shallowMount(Component, {
7+
props: {
8+
color: 'success',
9+
},
10+
attrs: {
11+
class: 'bazinga',
12+
},
13+
slots: {
14+
default: 'Hello World!',
15+
},
16+
})
17+
18+
describe(`Loads and display ${ComponentName} component`, () => {
19+
it('has a name', () => {
20+
expect(Component.name).toMatch(ComponentName)
21+
})
22+
it('renders correctly', () => {
23+
expect(wrapper.element).toMatchSnapshot()
24+
})
25+
it('renders correctly with slot', () => {
26+
expect(customWrapper.element).toMatchSnapshot()
27+
})
28+
})
29+
30+
describe(`Customize ${ComponentName} component`, () => {
31+
it('has a prope class names', () => {
32+
expect(customWrapper.classes('bazinga')).toBe(true)
33+
expect(customWrapper.classes('badge')).toBe(true)
34+
expect(customWrapper.classes('bg-success')).toBe(true)
35+
})
36+
it('default slot contains text', () => {
37+
expect(customWrapper.text()).toBe('Hello World!')
38+
})
39+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Loads and display CBadge component renders correctly 1`] = `
4+
<span
5+
class="badge"
6+
/>
7+
`;
8+
9+
exports[`Loads and display CBadge component renders correctly with slot 1`] = `
10+
<span
11+
class="badge bg-success bazinga"
12+
>
13+
Hello World!
14+
</span>
15+
`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { App } from 'vue'
2+
import { CStepper } from './CStepper'
3+
4+
const CStepperPlugin = {
5+
install: (app: App): void => {
6+
app.component(CStepper.name as string, CStepper)
7+
},
8+
}
9+
10+
export { CStepper, CStepperPlugin }

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy