webcoreui 1.1.0-beta.3 → 1.2.0-0

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.
Files changed (49) hide show
  1. package/README.md +47 -4
  2. package/astro.d.ts +5 -0
  3. package/astro.js +2 -0
  4. package/components/BottomNavigation/BottomNavigation.astro +1 -3
  5. package/components/Breadcrumb/Breadcrumb.astro +1 -3
  6. package/components/Carousel/Carousel.astro +79 -9
  7. package/components/Carousel/Carousel.svelte +40 -9
  8. package/components/Carousel/Carousel.tsx +46 -11
  9. package/components/Carousel/carousel.ts +3 -1
  10. package/components/ContextMenu/ContextMenu.astro +8 -3
  11. package/components/ContextMenu/ContextMenu.svelte +7 -2
  12. package/components/ContextMenu/ContextMenu.tsx +7 -2
  13. package/components/Copy/Copy.astro +3 -5
  14. package/components/Copy/Copy.svelte +1 -1
  15. package/components/Copy/Copy.tsx +1 -1
  16. package/components/Input/input.ts +62 -62
  17. package/components/Modal/Modal.astro +75 -75
  18. package/components/Modal/modal.ts +25 -25
  19. package/components/OTPInput/OTPInput.astro +194 -96
  20. package/components/OTPInput/OTPInput.svelte +141 -26
  21. package/components/OTPInput/OTPInput.tsx +140 -36
  22. package/components/OTPInput/otpinput.module.scss +59 -85
  23. package/components/Pagination/Pagination.astro +3 -3
  24. package/components/Pagination/Pagination.svelte +4 -4
  25. package/components/Pagination/pagination.module.scss +3 -3
  26. package/components/RangeSlider/RangeSlider.astro +270 -0
  27. package/components/RangeSlider/RangeSlider.svelte +188 -0
  28. package/components/RangeSlider/RangeSlider.tsx +205 -0
  29. package/components/RangeSlider/rangeslider.module.scss +143 -0
  30. package/components/RangeSlider/rangeslider.ts +37 -0
  31. package/components/Sidebar/Sidebar.astro +1 -3
  32. package/components/Stepper/Stepper.astro +1 -3
  33. package/index.d.ts +23 -4
  34. package/index.js +2 -0
  35. package/package.json +109 -103
  36. package/react.d.ts +5 -0
  37. package/react.js +2 -0
  38. package/scss/global/breakpoints.scss +15 -0
  39. package/scss/setup.scss +7 -1
  40. package/svelte.d.ts +5 -0
  41. package/svelte.js +2 -0
  42. package/utils/DOMUtils.ts +27 -2
  43. package/utils/bodyFreeze.ts +1 -1
  44. package/utils/context.ts +2 -2
  45. package/utils/getBreakpoint.ts +17 -0
  46. package/utils/isOneOf.ts +5 -0
  47. package/utils/modal.ts +54 -55
  48. package/utils/toast.ts +1 -1
  49. package/utils/webcore.ts +0 -28
package/README.md CHANGED
@@ -40,14 +40,18 @@
40
40
  - [Documentation](#documentation)
41
41
  - [Getting started](#getting-started)
42
42
  - [Prerequisites](#prerequisites)
43
- - [Installation](#installation)
43
+ - [Installation with CLI](#installation-with-cli)
44
+ - [Manual Installation](#manual-installation)
44
45
  - [Setup](#setup)
45
46
  - [Using Components](#using-components)
46
47
  - [Components](#components)
48
+ - [Blocks](#blocks)
49
+ - [Templates](#templates)
47
50
 
48
51
  ## Documentation
49
52
 
50
- Full documentation available on [webcoreui.dev](https://webcoreui.dev).
53
+ - Full documentation available on [webcoreui.dev](https://webcoreui.dev).
54
+ - For installation steps, visit our [setup docs](https://webcoreui.dev/docs/setup).
51
55
 
52
56
  ## Getting Started
53
57
 
@@ -73,7 +77,28 @@ Depending on your project setup, you'll also need the following packages:
73
77
  - [React](https://www.npmjs.com/package/react) - `v19.0`
74
78
  - [React DOM](https://www.npmjs.com/package/react-dom) -`v19.0`
75
79
 
76
- ### Installation
80
+ ### Installation with CLI
81
+
82
+ You can use our CLI tool to create a new Webcore project, or integrate it into an existing project more easily:
83
+
84
+ ```bash
85
+ # Create a new Webcore project
86
+ npm create webcore@latest
87
+
88
+ # Update configuration files for an existing Astro project
89
+ npm create webcore@latest config
90
+
91
+ # Create a new Webcore project with a specific template
92
+ npm create webcore@latest template [TemplateName] [destination]
93
+
94
+ # Use the "Portfolio" template on the current directory
95
+ npm create webcore@latest template Portfolio
96
+
97
+ # Create the "Portfolio" template in the "portfolio" directory
98
+ npm create webcore@latest template Portfolio ./portfolio
99
+ ```
100
+
101
+ ### Manual Installation
77
102
 
78
103
  Install Webcore as a dependency by running one of the following command:
79
104
 
@@ -87,6 +112,16 @@ yarn add webcoreui
87
112
 
88
113
  ### Setup
89
114
 
115
+ Add the following integration to your Astro configuration file (`astro.config.mjs`) at the root of your project directory:
116
+
117
+ ```js
118
+ import { webcore } from 'webcoreui/integration'
119
+
120
+ export default defineConfig({
121
+ integrations: [webcore()]
122
+ })
123
+ ```
124
+
90
125
  Create an empty [`webcore.config.scss`](https://webcoreui.dev/docs/css-configuration#webcoreconfigscss) file at the root of your project to setup CSS configurations. Setup default styles and fonts by calling the following in your global SCSS file:
91
126
 
92
127
  ```scss
@@ -101,7 +136,7 @@ Create an empty [`webcore.config.scss`](https://webcoreui.dev/docs/css-configura
101
136
  > [!TIP]
102
137
  > You can download the fonts Webcore uses from the [`public/fonts`](https://github.com/Frontendland/webcoreui/tree/main/public/fonts) directory.
103
138
 
104
- The `Setup` mixin can also accept the following options:
139
+ The `setup` mixin can also accept the following options:
105
140
 
106
141
 
107
142
  | Property | Default value | Purpose |
@@ -110,6 +145,7 @@ The `Setup` mixin can also accept the following options:
110
145
  | `includeUtilities` | `true` | Adds utility classes for CSS. Read more about the available utility classes [here](https://webcoreui.dev/docs/layout). |
111
146
  | `includeTooltip` | `true` | Adds styles for using tooltips.
112
147
  | `includeScrollbarStyles` | `true` | Adds styles for scrollbars.
148
+ | `includeBreakpoints` | `true` | Exposes breakpoint variables in CSS for JS. Used by components for responsiveness.
113
149
 
114
150
  Default component styles can be changed by overriding the following CSS variables:
115
151
 
@@ -143,6 +179,11 @@ html body {
143
179
  // Radio component
144
180
  --w-radio-color: var(--w-color-primary);
145
181
 
182
+ // RangeSlider component
183
+ --w-range-slider-background: var(--w-color-primary-50);
184
+ --w-range-slider-color: var(--w-color-primary);
185
+ --w-range-slider-thumb: var(--w-color-primary-50);
186
+
146
187
  // Rating component
147
188
  --w-rating-color: var(--w-color-primary);
148
189
  --w-rating-empty-color: var(--w-color-primary);
@@ -263,6 +304,7 @@ import { Accordion } from 'webcoreui/react'
263
304
  - [Popover](https://github.com/Frontendland/webcoreui/tree/main/src/components/Popover)
264
305
  - [Progress](https://github.com/Frontendland/webcoreui/tree/main/src/components/Progress)
265
306
  - [Radio](https://github.com/Frontendland/webcoreui/tree/main/src/components/Radio)
307
+ - [RangeSlider](https://github.com/Frontendland/webcoreui/tree/main/src/components/RangeSlider)
266
308
  - [Rating](https://github.com/Frontendland/webcoreui/tree/main/src/components/Rating)
267
309
  - [Ribbon](https://github.com/Frontendland/webcoreui/tree/main/src/components/Ribbon)
268
310
  - [Select](https://github.com/Frontendland/webcoreui/tree/main/src/components/Select)
@@ -286,6 +328,7 @@ import { Accordion } from 'webcoreui/react'
286
328
  ## Blocks
287
329
 
288
330
  - [Author](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/Author)
331
+ - [AvatarWithRating](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/AvatarWithRating)
289
332
  - [BlogCard](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/BlogCard)
290
333
  - [ComponentMap](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/ComponentMap)
291
334
  - [DeviceMockup](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/DeviceMockup)
package/astro.d.ts CHANGED
@@ -31,6 +31,7 @@ import type { PaginationProps as WPaginationProps } from './components/Paginatio
31
31
  import type { PopoverProps as WPopoverProps } from './components/Popover/popover'
32
32
  import type { ProgressProps as WProgressProps } from './components/Progress/progress'
33
33
  import type { RadioProps as WRadioProps } from './components/Radio/radio'
34
+ import type { RangeSliderProps as WRangeSliderProps } from './components/RangeSlider/rangeslider'
34
35
  import type { RatingProps as WRatingProps } from './components/Rating/rating'
35
36
  import type { RibbonProps as WRibbonProps } from './components/Ribbon/ribbon'
36
37
  import type { SelectProps as WSelectProps } from './components/Select/select'
@@ -54,6 +55,7 @@ import type { ToastProps as WToastProps } from './components/Toast/toast'
54
55
  import type { DataTableEventType as WDataTableEventType, HeadingObject as WHeadingObject } from './components/DataTable/datatable.ts'
55
56
  import type { ListEventType as WListEventType } from './components/List/list.ts'
56
57
  import type { PaginationEventType as WPaginationEventType } from './components/Pagination/pagination.ts'
58
+ import type { RangeSliderEventType as WRangeSliderEventType } from './components/RangeSlider/rangeslider.ts'
57
59
  import type { SelectEventType as WSelectEventType } from './components/Select/select.ts'
58
60
 
59
61
  declare module 'webcoreui/astro' {
@@ -90,6 +92,7 @@ declare module 'webcoreui/astro' {
90
92
  export function Popover(_props: WPopoverProps): any
91
93
  export function Progress(_props: WProgressProps): any
92
94
  export function Radio(_props: WRadioProps): any
95
+ export function RangeSlider(_props: WRangeSliderProps): any
93
96
  export function Rating(_props: WRatingProps): any
94
97
  export function Ribbon(_props: WRibbonProps): any
95
98
  export function Select(_props: WSelectProps): any
@@ -143,6 +146,7 @@ declare module 'webcoreui/astro' {
143
146
  export type PopoverProps = WPopoverProps
144
147
  export type ProgressProps = WProgressProps
145
148
  export type RadioProps = WRadioProps
149
+ export type RangeSliderProps = WRangeSliderProps
146
150
  export type RatingProps = WRatingProps
147
151
  export type RibbonProps = WRibbonProps
148
152
  export type SelectProps = WSelectProps
@@ -167,5 +171,6 @@ declare module 'webcoreui/astro' {
167
171
  export type HeadingObject = WHeadingObject
168
172
  export type ListEventType = WListEventType
169
173
  export type PaginationEventType = WPaginationEventType
174
+ export type RangeSliderEventType = WRangeSliderEventType
170
175
  export type SelectEventType = WSelectEventType
171
176
  }
package/astro.js CHANGED
@@ -31,6 +31,7 @@ import PaginationComponent from './components/Pagination/Pagination.astro'
31
31
  import PopoverComponent from './components/Popover/Popover.astro'
32
32
  import ProgressComponent from './components/Progress/Progress.astro'
33
33
  import RadioComponent from './components/Radio/Radio.astro'
34
+ import RangeSliderComponent from './components/RangeSlider/RangeSlider.astro'
34
35
  import RatingComponent from './components/Rating/Rating.astro'
35
36
  import RibbonComponent from './components/Ribbon/Ribbon.astro'
36
37
  import SelectComponent from './components/Select/Select.astro'
@@ -84,6 +85,7 @@ export const Pagination = PaginationComponent
84
85
  export const Popover = PopoverComponent
85
86
  export const Progress = ProgressComponent
86
87
  export const Radio = RadioComponent
88
+ export const RangeSlider = RangeSliderComponent
87
89
  export const Rating = RatingComponent
88
90
  export const Ribbon = RibbonComponent
89
91
  export const Select = SelectComponent
@@ -5,8 +5,6 @@ import Icon from '../Icon/Icon.astro'
5
5
 
6
6
  import styles from './bottomnavigation.module.scss'
7
7
 
8
- import type { IconProps } from '../Icon/icon'
9
-
10
8
  interface Props extends BottomNavigationProps {}
11
9
 
12
10
  const {
@@ -39,7 +37,7 @@ const styleVariable = maxWidth
39
37
  <Fragment>
40
38
  {item.icon?.startsWith('<svg')
41
39
  ? <Fragment set:html={item.icon} />
42
- : <Icon type={item.icon as IconProps['type']} />
40
+ : <Icon type={item.icon} />
43
41
  }
44
42
  </Fragment>
45
43
  )}
@@ -6,8 +6,6 @@ import Icon from '../Icon/Icon.astro'
6
6
 
7
7
  import styles from './breadcrumb.module.scss'
8
8
 
9
- import type { IconProps } from '../Icon/icon'
10
-
11
9
  interface Props extends BreadcrumbProps {}
12
10
 
13
11
  const {
@@ -33,7 +31,7 @@ const classes = [
33
31
  <Fragment>
34
32
  {item.icon.startsWith('<svg')
35
33
  ? <Fragment set:html={item.icon} />
36
- : <Icon type={item.icon as IconProps['type']} />
34
+ : <Icon type={item.icon} />
37
35
  }
38
36
  </Fragment>
39
37
  )}
@@ -35,10 +35,14 @@ const containerClasses = [
35
35
  scrollSnap && styles.snap
36
36
  ]
37
37
 
38
+ const getItemsPerSlide = () => typeof itemsPerSlide === 'number'
39
+ ? itemsPerSlide
40
+ : itemsPerSlide.default || 1
41
+
38
42
  const wrapperClasses = [
39
43
  styles.wrapper,
40
44
  effect && styles[effect],
41
- itemsPerSlide > 1 && styles['no-snap'],
45
+ getItemsPerSlide() > 1 && styles['no-snap'],
42
46
  wrapperClassName
43
47
  ]
44
48
 
@@ -52,10 +56,10 @@ const paginationClasses = classNames([
52
56
  !subText && paginationClassName
53
57
  ])
54
58
 
55
- const totalPages = Math.ceil(items / itemsPerSlide)
59
+ const totalPages = Math.ceil(items / getItemsPerSlide())
56
60
  const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
57
- const style = itemsPerSlide > 1
58
- ? `--w-slide-width: calc(${100 / itemsPerSlide}% - 5px);`
61
+ const style = getItemsPerSlide() > 1
62
+ ? `--w-slide-width: calc(${100 / getItemsPerSlide()}% - 5px);`
59
63
  : null
60
64
  ---
61
65
 
@@ -68,7 +72,8 @@ const style = itemsPerSlide > 1
68
72
  <ul
69
73
  class:list={wrapperClasses}
70
74
  style={style}
71
- data-visible-items={itemsPerSlide > 1 ? itemsPerSlide : null}
75
+ data-visible-items={getItemsPerSlide() > 1 ? getItemsPerSlide() : null}
76
+ data-breakpoint-items={typeof itemsPerSlide !== 'number' ? JSON.stringify(itemsPerSlide) : null}
72
77
  >
73
78
  <slot />
74
79
  </ul>
@@ -101,6 +106,7 @@ const style = itemsPerSlide > 1
101
106
  <script>
102
107
  import { debounce } from '../../utils/debounce'
103
108
  import { listen } from '../../utils/event'
109
+ import { getBreakpoint } from '../../utils/getBreakpoint'
104
110
 
105
111
  const addEventListeners = () => {
106
112
  const carousels = Array.from(document.querySelectorAll('[data-id="w-carousel"]'))
@@ -127,7 +133,7 @@ const style = itemsPerSlide > 1
127
133
  }
128
134
 
129
135
  for (let i = 0; i < diff; i++) {
130
- triggerButton.click()
136
+ triggerButton?.click()
131
137
  }
132
138
 
133
139
  Array.from(carouselElement.children).forEach(li => {
@@ -140,12 +146,72 @@ const style = itemsPerSlide > 1
140
146
  }
141
147
  }
142
148
 
149
+ const updateOnResize = (entries: ResizeObserverEntry[]) => {
150
+ const target = entries[0].target
151
+ const carousel = target.querySelector<HTMLUListElement>('ul')
152
+ const pagination = target.parentElement?.querySelector<HTMLUListElement>('[data-id="w-pagination"]')
153
+ const breakpoint = getBreakpoint()
154
+
155
+ if (!target || !carousel || !pagination) {
156
+ return
157
+ }
158
+
159
+ if (carousel.dataset.currentBreakpoint === breakpoint) {
160
+ return
161
+ }
162
+
163
+ const progress = target.parentElement?.querySelector<HTMLDivElement>('.w-carousel-progress')
164
+ const prevPageButton = pagination.querySelector<HTMLButtonElement>('[data-page="prev"]')
165
+ const nextPageButton = pagination.querySelector<HTMLButtonElement>('[data-page="next"]')
166
+ const subText = pagination.nextElementSibling
167
+ const breakpoints = JSON.parse(carousel.dataset.breakpointItems || '')
168
+ const itemsPerSlide = breakpoints[breakpoint] || breakpoints.default
169
+ const totalItems = carousel.children.length
170
+ const totalPages = Math.ceil(totalItems / itemsPerSlide)
171
+
172
+ carousel.dataset.currentBreakpoint = breakpoint
173
+ carousel.dataset.visibleItems = itemsPerSlide
174
+ carousel.children[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' })
175
+ carousel.style.setProperty('--w-slide-width', `calc(${100 / itemsPerSlide}% - 5px)`)
176
+
177
+ pagination.dataset.totalPages = String(totalPages)
178
+
179
+ if (!(prevPageButton && nextPageButton)) {
180
+ pagination.innerHTML = Array.from({ length: totalPages }, (_, i) => i + 1)
181
+ .map(i => `
182
+ <li>
183
+ <button
184
+ data-active="${i - 1 === 0}"
185
+ data-page="${i}"
186
+ aria-label="${`page ${i}`}"
187
+ ></button>
188
+ </li>
189
+ `).join('')
190
+ }
191
+
192
+ if (progress && progress.children[0] instanceof HTMLDivElement) {
193
+ progress.children[0].style.setProperty('--w-progress-width', '0%')
194
+ }
195
+
196
+ if (subText instanceof HTMLSpanElement && subText?.dataset.text) {
197
+ subText.innerText = subText.dataset.text
198
+ .replace('{0}', '1')
199
+ .replace('{1}', String(totalPages))
200
+ }
201
+ }
202
+
143
203
  carousels.forEach(carousel => {
144
204
  const carouselElement = carousel as HTMLDivElement
205
+ const carouselList = carousel.querySelector<HTMLUListElement>('ul')
206
+ const observer = new ResizeObserver(updateOnResize)
145
207
  const debounceAmount = carouselElement.dataset.debounce
146
208
  ? Number(carouselElement.dataset.debounce)
147
209
  : 20
148
210
 
211
+ if (carouselList?.dataset.breakpointItems) {
212
+ observer.observe(carouselElement)
213
+ }
214
+
149
215
  carousel.addEventListener('scroll', debounce((event: Event) => {
150
216
  scroll(event)
151
217
  }, debounceAmount))
@@ -169,6 +235,10 @@ const style = itemsPerSlide > 1
169
235
  .filter(index => index < totalItems)
170
236
  })
171
237
 
238
+ if (indexes.length < event.page) {
239
+ return
240
+ }
241
+
172
242
  const pageIndex = event.direction === 'prev'
173
243
  ? indexes[event.page - 1][0]
174
244
  : indexes[event.page - 1][indexes[event.page - 1].length - 1]
@@ -194,15 +264,15 @@ const style = itemsPerSlide > 1
194
264
  }
195
265
 
196
266
  if (event.trusted) {
197
- const carouselContaienr = target.closest('section').querySelector('[data-id="w-carousel"]')
267
+ const carouselContainer = target.closest('section').querySelector('[data-id="w-carousel"]')
198
268
 
199
269
  liElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
200
270
  liElement.dataset.active = 'true'
201
271
 
202
- carouselContaienr.dataset.paginated = 'true'
272
+ carouselContainer.dataset.paginated = 'true'
203
273
 
204
274
  setTimeout(() => {
205
- carouselContaienr.removeAttribute('data-paginated')
275
+ carouselContainer.removeAttribute('data-paginated')
206
276
  }, 300)
207
277
  }
208
278
  })
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { classNames } from '../../utils/classNames'
10
10
  import { debounce as debounceScroll } from '../../utils/debounce'
11
+ import { getBreakpoint } from '../../utils/getBreakpoint'
11
12
 
12
13
  import styles from './carousel.module.scss'
13
14
 
@@ -29,12 +30,28 @@
29
30
  onScroll
30
31
  }: SvelteCarouselProps = $props()
31
32
 
33
+ const getItemsPerSlide = () => {
34
+ if (carousel) {
35
+ return typeof itemsPerSlide === 'number'
36
+ ? itemsPerSlide
37
+ : itemsPerSlide[getBreakpoint()] || itemsPerSlide.default || 1
38
+ }
39
+
40
+ return typeof itemsPerSlide === 'number'
41
+ ? itemsPerSlide
42
+ : itemsPerSlide.default || 1
43
+ }
44
+
32
45
  let carouselContainer: HTMLDivElement
33
46
  let carousel: HTMLUListElement
34
47
  let carouselItems: HTMLCollection | NodeListOf<HTMLLIElement>
35
48
  let progressValue = $state(0)
36
49
  let paginated = false
37
50
  let currentPage = $state(1)
51
+ let totalPages = $state(Math.ceil(items / getItemsPerSlide()))
52
+ let style = $state(getItemsPerSlide() > 1
53
+ ? `--w-slide-width: calc(${100 / getItemsPerSlide()}% - 5px);`
54
+ : null)
38
55
 
39
56
  const classes = classNames([
40
57
  styles.carousel,
@@ -49,7 +66,7 @@
49
66
  const wrapperClasses = classNames([
50
67
  styles.wrapper,
51
68
  effect && styles[effect],
52
- itemsPerSlide! > 1 && styles['no-snap'],
69
+ getItemsPerSlide() > 1 && styles['no-snap'],
53
70
  wrapperClassName
54
71
  ])
55
72
 
@@ -63,11 +80,7 @@
63
80
  !subText && paginationClassName
64
81
  ])
65
82
 
66
- const totalPages = Math.ceil(items / itemsPerSlide!)
67
83
  const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
68
- const style = itemsPerSlide! > 1
69
- ? `--w-slide-width: calc(${100 / itemsPerSlide!}% - 5px);`
70
- : null
71
84
 
72
85
  const updateValues = () => {
73
86
  const activeElement = carouselItems[currentPage - 1] as HTMLLIElement
@@ -104,8 +117,8 @@
104
117
  }, debounce)
105
118
 
106
119
  const paginate = (event: PaginationEventType) => {
107
- const indexes = Array.from({ length: Math.ceil(items / itemsPerSlide!) }, (_, i) => {
108
- return Array.from({ length: itemsPerSlide! }, (_, j) => (i * itemsPerSlide!) + j)
120
+ const indexes = Array.from({ length: Math.ceil(items / getItemsPerSlide()) }, (_, i) => {
121
+ return Array.from({ length: getItemsPerSlide() }, (_, j) => (i * getItemsPerSlide()) + j)
109
122
  .filter(index => index < items)
110
123
  })
111
124
 
@@ -126,15 +139,33 @@
126
139
  }, 300)
127
140
  }
128
141
 
142
+ const updateOnResize = () => {
143
+ currentPage = 1
144
+ progressValue = 0
145
+ totalPages = Math.ceil(items / getItemsPerSlide())
146
+ style = `--w-slide-width: calc(${100 / getItemsPerSlide()}% - 5px);`
147
+
148
+ if (subTextValue) {
149
+ subText = subTextValue
150
+ .replace('{0}', '1')
151
+ .replace('{1}', String(totalPages))
152
+ }
153
+ }
154
+
129
155
  onMount(() => {
130
156
  const usedInAstro = carousel.children[0].nodeName === 'ASTRO-SLOT'
131
-
132
- carouselContainer.addEventListener('scroll', scroll)
157
+ const observer = new ResizeObserver(updateOnResize)
133
158
 
134
159
  carouselItems = usedInAstro
135
160
  ? carousel.querySelectorAll('li')
136
161
  : carousel.children
137
162
 
163
+ carouselContainer.addEventListener('scroll', scroll)
164
+
165
+ if (typeof itemsPerSlide !== 'number') {
166
+ observer.observe(carouselContainer)
167
+ }
168
+
138
169
  return () => {
139
170
  carouselContainer.removeEventListener('scroll', scroll)
140
171
  }
@@ -7,6 +7,7 @@ import Progress from '../Progress/Progress.tsx'
7
7
 
8
8
  import { classNames } from '../../utils/classNames'
9
9
  import { debounce as debounceScroll } from '../../utils/debounce'
10
+ import { getBreakpoint } from '../../utils/getBreakpoint'
10
11
 
11
12
  import styles from './carousel.module.scss'
12
13
 
@@ -27,14 +28,30 @@ const Carousel = ({
27
28
  onScroll,
28
29
  children
29
30
  }: ReactCarouselProps) => {
31
+ const getItemsPerSlide = () => {
32
+ if (carousel.current) {
33
+ return typeof itemsPerSlide === 'number'
34
+ ? itemsPerSlide
35
+ : itemsPerSlide[getBreakpoint()] || itemsPerSlide.default || 1
36
+ }
37
+
38
+ return typeof itemsPerSlide === 'number'
39
+ ? itemsPerSlide
40
+ : itemsPerSlide.default || 1
41
+ }
42
+
30
43
  const carouselContainer = useRef<HTMLDivElement>(null)
31
44
  const carousel = useRef<HTMLUListElement>(null)
32
45
  const carouselItems = useRef<any>(null)
33
46
  const paginated = useRef(false)
34
47
  const currentPage = useRef(1)
48
+ const totalPages = useRef(Math.ceil(items / getItemsPerSlide()))
35
49
 
36
50
  const [progressValue, setProgressValue] = useState(0)
37
51
  const [updatedSubText, setUpdatedSubText] = useState(subText)
52
+ const [style, setStyle] = useState(getItemsPerSlide() > 1
53
+ ? { '--w-slide-width': `calc(${100 / getItemsPerSlide()}% - 5px)` } as React.CSSProperties
54
+ : undefined)
38
55
 
39
56
  const classes = classNames([
40
57
  styles.carousel,
@@ -49,7 +66,7 @@ const Carousel = ({
49
66
  const wrapperClasses = classNames([
50
67
  styles.wrapper,
51
68
  effect && styles[effect],
52
- itemsPerSlide! > 1 && styles['no-snap'],
69
+ getItemsPerSlide() > 1 && styles['no-snap'],
53
70
  wrapperClassName
54
71
  ])
55
72
 
@@ -63,11 +80,7 @@ const Carousel = ({
63
80
  !subText && paginationClassName
64
81
  ])
65
82
 
66
- const totalPages = Math.ceil(items / itemsPerSlide!)
67
83
  const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
68
- const style = itemsPerSlide > 1
69
- ? { '--w-slide-width': `calc(${100 / itemsPerSlide!}% - 5px);` } as React.CSSProperties
70
- : undefined
71
84
 
72
85
  const updateValues = (page: number) => {
73
86
  const activeElement = carouselItems.current[page - 1]
@@ -79,12 +92,12 @@ const Carousel = ({
79
92
  setUpdatedSubText(
80
93
  subTextValue
81
94
  .replace('{0}', String(page))
82
- .replace('{1}', String(totalPages))
95
+ .replace('{1}', String(totalPages.current))
83
96
  )
84
97
  }
85
98
 
86
99
  if (progress) {
87
- const percentage = (100 / (totalPages - 1))
100
+ const percentage = (100 / (totalPages.current - 1))
88
101
 
89
102
  setProgressValue(percentage * (page - 1))
90
103
  }
@@ -106,8 +119,8 @@ const Carousel = ({
106
119
  }, debounce)
107
120
 
108
121
  const paginate = (event: PaginationEventType) => {
109
- const indexes = Array.from({ length: Math.ceil(items / itemsPerSlide!) }, (_, i) => {
110
- return Array.from({ length: itemsPerSlide! }, (_, j) => (i * itemsPerSlide!) + j)
122
+ const indexes = Array.from({ length: Math.ceil(items / getItemsPerSlide()) }, (_, i) => {
123
+ return Array.from({ length: getItemsPerSlide() }, (_, j) => (i * getItemsPerSlide()) + j)
111
124
  .filter(index => index < items)
112
125
  })
113
126
 
@@ -128,8 +141,26 @@ const Carousel = ({
128
141
  }, 300)
129
142
  }
130
143
 
144
+ const updateOnResize = () => {
145
+ currentPage.current = 1
146
+ setProgressValue(0)
147
+ totalPages.current = Math.ceil(items / getItemsPerSlide())
148
+ setStyle(prevStyle => ({
149
+ ...prevStyle,
150
+ '--w-slide-width': `calc(${100 / getItemsPerSlide()}% - 5px)`
151
+ }) as React.CSSProperties)
152
+
153
+ if (subTextValue) {
154
+ setUpdatedSubText(subTextValue
155
+ .replace('{0}', '1')
156
+ .replace('{1}', String(totalPages.current))
157
+ )
158
+ }
159
+ }
160
+
131
161
  useEffect(() => {
132
162
  const usedInAstro = carousel.current?.children[0].nodeName === 'ASTRO-SLOT'
163
+ const observer = new ResizeObserver(updateOnResize)
133
164
 
134
165
  carouselItems.current = usedInAstro
135
166
  ? carousel.current?.querySelectorAll('li')
@@ -137,6 +168,10 @@ const Carousel = ({
137
168
 
138
169
  carouselContainer.current?.addEventListener('scroll', scroll)
139
170
 
171
+ if (typeof itemsPerSlide !== 'number') {
172
+ observer.observe(carouselContainer.current!)
173
+ }
174
+
140
175
  return () => {
141
176
  carouselContainer.current?.removeEventListener('scroll', scroll)
142
177
  }
@@ -162,7 +197,7 @@ const Carousel = ({
162
197
  type="arrows"
163
198
  {...pagination}
164
199
  currentPage={currentPage.current}
165
- totalPages={totalPages}
200
+ totalPages={totalPages.current}
166
201
  className={paginationClasses}
167
202
  onChange={paginate}
168
203
  />
@@ -170,7 +205,7 @@ const Carousel = ({
170
205
  <span className={styles.subtext}>
171
206
  {updatedSubText
172
207
  .replace('{0}', '1')
173
- .replace('{1}', String(totalPages))
208
+ .replace('{1}', String(totalPages.current))
174
209
  }
175
210
  </span>
176
211
  )}
@@ -1,10 +1,12 @@
1
1
  import type { Snippet } from 'svelte'
2
2
 
3
+ import type { Responsive } from '../../utils/getLayoutClasses'
4
+
3
5
  import type { PaginationProps } from '../Pagination/pagination'
4
6
 
5
7
  export type CarouselProps = {
6
8
  items: number
7
- itemsPerSlide?: number
9
+ itemsPerSlide?: number | Responsive<number>
8
10
  subText?: string
9
11
  autoplay?: boolean
10
12
  vertical?: boolean
@@ -61,8 +61,9 @@ if (!Astro.slots.has('context')) {
61
61
  on(ctx, 'contextmenu', (event: MouseEvent) => {
62
62
  event.preventDefault()
63
63
 
64
+ const target = event.currentTarget as HTMLElement
65
+
64
66
  if (!contents[i]) {
65
- const target = event.currentTarget as HTMLElement
66
67
  contents[i] = target.lastElementChild as HTMLDivElement
67
68
  }
68
69
 
@@ -74,8 +75,12 @@ if (!Astro.slots.has('context')) {
74
75
  })
75
76
  }
76
77
 
77
- contents[i].style.top = `${event.offsetY}px`
78
- contents[i].style.left = `${event.offsetX}px`
78
+ const rect = target.getBoundingClientRect()
79
+ const x = event.clientX - rect.left
80
+ const y = event.clientY - rect.top
81
+
82
+ contents[i].style.top = `${y}px`
83
+ contents[i].style.left = `${x}px`
79
84
  contents[i].dataset.show = 'true'
80
85
  })
81
86
  })
@@ -24,8 +24,13 @@
24
24
  event.preventDefault()
25
25
 
26
26
  if (content) {
27
- content.style.top = `${event.offsetY}px`
28
- content.style.left = `${event.offsetX}px`
27
+ const target = event.currentTarget as HTMLElement
28
+ const rect = target.getBoundingClientRect()
29
+ const x = event.clientX - rect.left
30
+ const y = event.clientY - rect.top
31
+
32
+ content.style.top = `${y}px`
33
+ content.style.left = `${x}px`
29
34
  content.dataset.show = 'true'
30
35
  }
31
36
  }