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)