webcoreui 0.6.0 → 0.6.1

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.
@@ -15,7 +15,7 @@ export type ButtonProps = {
15
15
  | '_parent'
16
16
  | '_top'
17
17
  | '_unfencedTop'
18
- | null
18
+ | ''
19
19
  | undefined
20
20
  href?: string
21
21
  className?: string
@@ -13,7 +13,7 @@ interface Props extends CarouselProps {}
13
13
 
14
14
  const {
15
15
  items,
16
- visibleItems = 1,
16
+ itemsPerSlide = 1,
17
17
  subText,
18
18
  scrollSnap = true,
19
19
  progress,
@@ -38,6 +38,7 @@ const containerClasses = [
38
38
  const wrapperClasses = [
39
39
  styles.wrapper,
40
40
  effect && styles[effect],
41
+ itemsPerSlide > 1 && styles['no-snap'],
41
42
  wrapperClassName
42
43
  ]
43
44
 
@@ -51,10 +52,10 @@ const paginationClasses = classNames([
51
52
  !subText && paginationClassName
52
53
  ])
53
54
 
54
- const totalPages = Math.ceil(items / visibleItems)
55
+ const totalPages = Math.ceil(items / itemsPerSlide)
55
56
  const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
56
- const style = visibleItems > 1
57
- ? `--w-slide-width: ${100 / visibleItems}%;`
57
+ const style = itemsPerSlide > 1
58
+ ? `--w-slide-width: calc(${100 / itemsPerSlide}% - 5px);`
58
59
  : null
59
60
  ---
60
61
 
@@ -67,7 +68,7 @@ const style = visibleItems > 1
67
68
  <ul
68
69
  class:list={wrapperClasses}
69
70
  style={style}
70
- data-visible-items={visibleItems > 1 ? visibleItems : null}
71
+ data-visible-items={itemsPerSlide > 1 ? itemsPerSlide : null}
71
72
  >
72
73
  <slot />
73
74
  </ul>
@@ -159,8 +160,16 @@ const style = visibleItems > 1
159
160
 
160
161
  const progress = target.closest('section').querySelector('.w-carousel-progress')
161
162
  const progressValue = (100 / (Number(target.dataset.totalPages) - 1))
162
- const visibleItems = Number(carousel.dataset.visibleItems) || 0
163
- const pageIndex = (event.page + visibleItems) - 1
163
+ const itemsPerSlide = Number(carousel.dataset.visibleItems) || 1
164
+ const totalItems = carousel.children.length
165
+ const indexes = Array.from({ length: Math.ceil(totalItems / itemsPerSlide) }, (_, i) => {
166
+ return Array.from({ length: itemsPerSlide }, (_, j) => (i * itemsPerSlide) + j)
167
+ .filter(index => index < totalItems)
168
+ })
169
+
170
+ const pageIndex = event.direction === 'prev'
171
+ ? indexes[event.page - 1][0]
172
+ : indexes[event.page - 1][indexes[event.page - 1].length - 1]
164
173
 
165
174
  const liElement = carousel.children[pageIndex]
166
175
  const subText = event.target.nextElementSibling
@@ -14,7 +14,7 @@
14
14
  import type { PaginationEventType } from '../Pagination/pagination'
15
15
 
16
16
  export let items: SvelteCarouselProps['items'] = 0
17
- export let visibleItems: SvelteCarouselProps['visibleItems'] = 1
17
+ export let itemsPerSlide: SvelteCarouselProps['itemsPerSlide'] = 1
18
18
  export let subText: SvelteCarouselProps['subText'] = ''
19
19
  export let scrollSnap: SvelteCarouselProps['scrollSnap'] = true
20
20
  export let progress: SvelteCarouselProps['progress'] = false
@@ -46,6 +46,7 @@
46
46
  const wrapperClasses = classNames([
47
47
  styles.wrapper,
48
48
  effect && styles[effect],
49
+ itemsPerSlide! > 1 && styles['no-snap'],
49
50
  wrapperClassName
50
51
  ])
51
52
 
@@ -59,10 +60,10 @@
59
60
  !subText && paginationClassName
60
61
  ])
61
62
 
62
- const totalPages = Math.ceil(items / visibleItems!)
63
+ const totalPages = Math.ceil(items / itemsPerSlide!)
63
64
  const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
64
- const style = visibleItems! > 1
65
- ? `--w-slide-width: ${100 / visibleItems!}%;`
65
+ const style = itemsPerSlide! > 1
66
+ ? `--w-slide-width: calc(${100 / itemsPerSlide!}% - 5px);`
66
67
  : null
67
68
 
68
69
  const updateValues = () => {
@@ -100,7 +101,16 @@
100
101
  }, debounce)
101
102
 
102
103
  const paginate = (event: PaginationEventType) => {
103
- const liElement = carouselItems[event.page - 1] as HTMLLIElement
104
+ const indexes = Array.from({ length: Math.ceil(items / itemsPerSlide!) }, (_, i) => {
105
+ return Array.from({ length: itemsPerSlide! }, (_, j) => (i * itemsPerSlide!) + j)
106
+ .filter(index => index < items)
107
+ })
108
+
109
+ const pageIndex = event.direction === 'prev'
110
+ ? indexes[event.page - 1][0]
111
+ : indexes[event.page - 1][indexes[event.page - 1].length - 1]
112
+
113
+ const liElement = carouselItems[pageIndex] as HTMLLIElement
104
114
 
105
115
  liElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
106
116
 
@@ -14,7 +14,7 @@ import type { PaginationEventType } from '../Pagination/pagination'
14
14
 
15
15
  const Carousel = ({
16
16
  items,
17
- visibleItems = 1,
17
+ itemsPerSlide = 1,
18
18
  subText,
19
19
  scrollSnap = true,
20
20
  progress,
@@ -49,6 +49,7 @@ const Carousel = ({
49
49
  const wrapperClasses = classNames([
50
50
  styles.wrapper,
51
51
  effect && styles[effect],
52
+ itemsPerSlide! > 1 && styles['no-snap'],
52
53
  wrapperClassName
53
54
  ])
54
55
 
@@ -62,10 +63,10 @@ const Carousel = ({
62
63
  !subText && paginationClassName
63
64
  ])
64
65
 
65
- const totalPages = Math.ceil(items / visibleItems!)
66
+ const totalPages = Math.ceil(items / itemsPerSlide!)
66
67
  const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
67
- const style = visibleItems > 1
68
- ? { '--w-slide-width': `${100 / visibleItems}%;` } as React.CSSProperties
68
+ const style = itemsPerSlide > 1
69
+ ? { '--w-slide-width': `calc(${100 / itemsPerSlide!}% - 5px);` } as React.CSSProperties
69
70
  : undefined
70
71
 
71
72
  const updateValues = (page: number) => {
@@ -105,7 +106,16 @@ const Carousel = ({
105
106
  }, debounce)
106
107
 
107
108
  const paginate = (event: PaginationEventType) => {
108
- const liElement = carouselItems.current[event.page - 1]
109
+ const indexes = Array.from({ length: Math.ceil(items / itemsPerSlide!) }, (_, i) => {
110
+ return Array.from({ length: itemsPerSlide! }, (_, j) => (i * itemsPerSlide!) + j)
111
+ .filter(index => index < items)
112
+ })
113
+
114
+ const pageIndex = event.direction === 'prev'
115
+ ? indexes[event.page - 1][0]
116
+ : indexes[event.page - 1][indexes[event.page - 1].length - 1]
117
+
118
+ const liElement = carouselItems.current[pageIndex]
109
119
 
110
120
  liElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
111
121
 
@@ -34,12 +34,15 @@ body {
34
34
  filter: saturate(0);
35
35
  }
36
36
 
37
+ &:not(.no-snap) li {
38
+ scroll-snap-align: center;
39
+ }
40
+
37
41
  li {
38
42
  @include transition();
39
43
  @include spacing(m0);
40
44
  @include layout(flex, h-center);
41
45
 
42
- scroll-snap-align: center;
43
46
  min-width: var(--w-slide-width);
44
47
  }
45
48
  }
@@ -2,7 +2,7 @@ import type { PaginationProps } from '../Pagination/pagination'
2
2
 
3
3
  export type CarouselProps = {
4
4
  items: number
5
- visibleItems?: number
5
+ itemsPerSlide?: number
6
6
  subText?: string
7
7
  autoplay?: boolean
8
8
  vertical?: boolean
@@ -72,8 +72,8 @@ const columnToggleItems = [{
72
72
  }]
73
73
 
74
74
  const columnFilterItems = headings?.filter(heading => typeof heading !== 'string')
75
- .filter(heading => heading.filterable)
76
- .map(heading => heading.name)
75
+ .filter(heading => (heading as HeadingObject).filterable)
76
+ .map(heading => (heading as HeadingObject).name)
77
77
  .map(heading => getColumnName(heading))
78
78
 
79
79
  const hasPagination = data?.length && itemsPerPage
@@ -1,287 +1,287 @@
1
- import React, { useState } from 'react'
2
- import type { HeadingObject, ReactDataTableProps } from './datatable'
3
-
4
- import Button from '../Button/Button.tsx'
5
- import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
6
- import Input from '../Input/Input.tsx'
7
- import Pagination from '../Pagination/Pagination.tsx'
8
- import Select from '../Select/Select.tsx'
9
-
10
- import { classNames } from '../../utils/classNames'
11
- import { debounce } from '../../utils/debounce'
12
-
13
- import checkIcon from '../../icons/check.svg?raw'
14
- import orderIcon from '../../icons/order.svg?raw'
15
- import searchIcon from '../../icons/search.svg?raw'
16
-
17
- import styles from './datatable.module.scss'
18
-
19
- import type { ListEventType } from '../List/list'
20
-
21
- // eslint-disable-next-line complexity
22
- const DataTable = ({
23
- headings,
24
- filterPlaceholder = 'Filter entries',
25
- showFilterIcon,
26
- noResultsLabel = 'No results.',
27
- itemsPerPage,
28
- subText,
29
- columnToggleLabel = 'Columns',
30
- pagination,
31
- data,
32
- hover,
33
- striped,
34
- offsetStripe,
35
- compact,
36
- maxHeight,
37
- className,
38
- id,
39
- onFilter,
40
- children
41
- }: ReactDataTableProps) => {
42
- const [filteredData, setFilteredData] = useState<any>(data)
43
- const [toggledData, setToggledData] = useState(filteredData)
44
- const [filteredHeadings, setFilteredHeadings] = useState<any>(headings)
45
- const [page, setPage] = useState(1)
46
- const [hasActiveFilter, setHasActiveFilter] = useState(false)
47
- const [sortOrder, setSortOrder] = useState(1)
48
-
49
- const classes = classNames([
50
- styles.table,
51
- hover && styles.hover,
52
- striped && styles[`striped-${striped}s`],
53
- offsetStripe && styles.offset,
54
- compact && styles.compact,
55
- maxHeight && styles.scroll
56
- ])
57
-
58
- const footerClasses = classNames([
59
- styles.footer,
60
- subText && styles.between
61
- ])
62
-
63
- const styleVariables = {
64
- ...(maxHeight && { 'max-height': maxHeight })
65
- } as React.CSSProperties
66
-
67
- const showColumnToggle = headings?.some(heading => {
68
- return typeof heading === 'string' ? false : heading.toggleable
69
- })
70
-
71
- const columnToggleItems = [{
72
- items: headings?.length ? headings
73
- .filter(heading => typeof heading !== 'string' && heading.toggleable)
74
- .map(heading => ({
75
- icon: checkIcon,
76
- name: (heading as HeadingObject).name,
77
- value: String(headings.findIndex(h => {
78
- return (h as HeadingObject).name === (heading as HeadingObject).name
79
- }))
80
- })) : []
81
- }]
82
-
83
- const columnFilterIndexes = headings?.map(heading => (heading as HeadingObject).filterable)
84
- .map((heading, index) => heading ? index : null)
85
- .filter(heading => heading !== null) || []
86
-
87
- const hasPagination = data?.length && itemsPerPage
88
- ? data.length > itemsPerPage
89
- : false
90
-
91
- const filter = debounce((event: Event) => {
92
- const target = event.target as HTMLInputElement
93
-
94
- setHasActiveFilter(!!target.value)
95
-
96
- setFilteredData(toggledData?.filter((row: string[]) => {
97
- const rowValue = row.filter((_, index) => columnFilterIndexes.includes(index))
98
- .join('')
99
- .toLowerCase()
100
-
101
- return rowValue.includes(target.value.toLowerCase())
102
- }))
103
-
104
- onFilter?.({
105
- results: filteredData,
106
- numberOfResults: filteredData.length
107
- })
108
- }, 400)
109
-
110
- const toggleColumns = (event: ListEventType) => {
111
- const columnToggleListElement = Array.from(event.list.children)
112
- .find(child => (child as HTMLLIElement).dataset.name === event.name) as HTMLLIElement
113
- const svgIcon = columnToggleListElement.children[0] as HTMLElement
114
- let mappedData
115
-
116
- svgIcon.style.opacity = svgIcon.style.opacity === '0'
117
- ? '1'
118
- : '0'
119
-
120
- if (svgIcon.style.opacity === '0') {
121
- mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[]) => {
122
- return row.map((column, index) => index === Number(event.value) ? null : column)
123
- })
124
-
125
- setFilteredData(mappedData)
126
-
127
- setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string) => {
128
- return ((heading as HeadingObject)?.name || heading) === event.name ? null : heading
129
- }))
130
- } else {
131
- mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[], x: number) => {
132
- return row.map((column, y) => y === Number(event.value) ? data?.[x][y] : column)
133
- })
134
-
135
- setFilteredData(mappedData)
136
-
137
- setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string, index: number) => {
138
- return ((headings?.[index] as HeadingObject)?.name || headings?.[index]) === event.name
139
- ? headings?.[index]
140
- : heading
141
- }))
142
- }
143
-
144
- setToggledData(mappedData)
145
- }
146
-
147
- const sort = (index: number) => {
148
- const sortedData = filteredData.sort((a: string[], b: string[]) => {
149
- let aValue: string | number = a[index]
150
- let bValue: string | number = b[index]
151
-
152
- if (!isNaN(aValue as any)) {
153
- aValue = Number(aValue)
154
- }
155
-
156
- if (!isNaN(bValue as any)) {
157
- bValue = Number(bValue)
158
- }
159
-
160
- return aValue > bValue
161
- ? sortOrder * -1
162
- : sortOrder
163
- })
164
-
165
- setFilteredData(sortedData)
166
- setSortOrder(sortOrder === 1 ? -1 : 1)
167
- }
168
-
169
- const isNextPage = (index: number) => {
170
- if (hasPagination && itemsPerPage && !hasActiveFilter) {
171
- const currentPage = Math.ceil((index + 1) / itemsPerPage)
172
-
173
- return currentPage !== page ? 'true' : undefined
174
- }
175
-
176
- if (hasActiveFilter && itemsPerPage) {
177
- return index >= itemsPerPage ? 'true' : undefined
178
- }
179
-
180
- return undefined
181
- }
182
-
183
- return (
184
- <section className={className} id={id}>
185
- {(!!columnFilterIndexes?.length || showColumnToggle) && (
186
- <div className={styles.filters}>
187
- {!!columnFilterIndexes?.length && (
188
- <Input
189
- type="search"
190
- placeholder={filterPlaceholder}
191
- onInput={filter}
192
- >
193
- {showFilterIcon && (
194
- <span dangerouslySetInnerHTML={{ __html: searchIcon }} />
195
- )}
196
- </Input>
197
- )}
198
- {showColumnToggle && (
199
- <Select
200
- name={`data-table-${id || crypto.randomUUID()}`}
201
- itemGroups={columnToggleItems}
202
- position="bottom-end"
203
- value={columnToggleLabel}
204
- onChange={toggleColumns}
205
- updateValue={false}
206
- />
207
- )}
208
- </div>
209
- )}
210
-
211
- <div className={classes} style={styleVariables}>
212
- <table>
213
- {!!filteredHeadings?.length && (
214
- <thead>
215
- <tr>
216
- {filteredHeadings?.map((heading: HeadingObject | string, index: number) => {
217
- if (!heading) {
218
- return null
219
- }
220
-
221
- return (
222
- <th key={index}>
223
- <ConditionalWrapper
224
- condition={!!(heading as HeadingObject).sortable}
225
- wrapper={children => (
226
- <Button theme="flat" slot="wrapper" onClick={() => sort(index)}>
227
- {children}
228
- <span dangerouslySetInnerHTML={{ __html: orderIcon }} />
229
- </Button>
230
- )}
231
- >
232
- {(heading as HeadingObject).name || heading as string}
233
- </ConditionalWrapper>
234
- </th>
235
- )
236
- })}
237
- </tr>
238
- </thead>
239
- )}
240
-
241
- <tbody>
242
- {filteredData?.map((row: string[], rowIndex: number) => (
243
- <tr key={rowIndex} data-hidden={isNextPage(rowIndex)}>
244
- {row.filter(Boolean).map((column, columnIndex) => (
245
- <td
246
- key={columnIndex}
247
- dangerouslySetInnerHTML={{ __html: column }}
248
- />
249
- ))}
250
- </tr>
251
- ))}
252
- {children}
253
- </tbody>
254
- {!filteredData?.length && (
255
- <tfoot>
256
- <tr>
257
- <td
258
- colSpan={data?.[0].length}
259
- className={styles['no-results']}
260
- >
261
- {noResultsLabel}
262
- </td>
263
- </tr>
264
- </tfoot>
265
- )}
266
- </table>
267
- </div>
268
- {(subText || hasPagination) && (
269
- <div className={footerClasses}>
270
- {subText && (
271
- <span className={styles.subtext}>{subText}</span>
272
- )}
273
- {(hasPagination && itemsPerPage && !hasActiveFilter) && (
274
- <Pagination
275
- {...pagination}
276
- totalPages={Math.ceil((data?.length || 0) / itemsPerPage)}
277
- currentPage={page}
278
- onChange={event => setPage(event.page)}
279
- />
280
- )}
281
- </div>
282
- )}
283
- </section>
284
- )
285
- }
286
-
287
- export default DataTable
1
+ import React, { useState } from 'react'
2
+ import type { HeadingObject, ReactDataTableProps } from './datatable'
3
+
4
+ import Button from '../Button/Button.tsx'
5
+ import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
6
+ import Input from '../Input/Input.tsx'
7
+ import Pagination from '../Pagination/Pagination.tsx'
8
+ import Select from '../Select/Select.tsx'
9
+
10
+ import { classNames } from '../../utils/classNames'
11
+ import { debounce } from '../../utils/debounce'
12
+
13
+ import checkIcon from '../../icons/check.svg?raw'
14
+ import orderIcon from '../../icons/order.svg?raw'
15
+ import searchIcon from '../../icons/search.svg?raw'
16
+
17
+ import styles from './datatable.module.scss'
18
+
19
+ import type { ListEventType } from '../List/list'
20
+
21
+ // eslint-disable-next-line complexity
22
+ const DataTable = ({
23
+ headings,
24
+ filterPlaceholder = 'Filter entries',
25
+ showFilterIcon,
26
+ noResultsLabel = 'No results.',
27
+ itemsPerPage,
28
+ subText,
29
+ columnToggleLabel = 'Columns',
30
+ pagination,
31
+ data,
32
+ hover,
33
+ striped,
34
+ offsetStripe,
35
+ compact,
36
+ maxHeight,
37
+ className,
38
+ id,
39
+ onFilter,
40
+ children
41
+ }: ReactDataTableProps) => {
42
+ const [filteredData, setFilteredData] = useState<any>(data)
43
+ const [toggledData, setToggledData] = useState(filteredData)
44
+ const [filteredHeadings, setFilteredHeadings] = useState<any>(headings)
45
+ const [page, setPage] = useState(1)
46
+ const [hasActiveFilter, setHasActiveFilter] = useState(false)
47
+ const [sortOrder, setSortOrder] = useState(1)
48
+
49
+ const classes = classNames([
50
+ styles.table,
51
+ hover && styles.hover,
52
+ striped && styles[`striped-${striped}s`],
53
+ offsetStripe && styles.offset,
54
+ compact && styles.compact,
55
+ maxHeight && styles.scroll
56
+ ])
57
+
58
+ const footerClasses = classNames([
59
+ styles.footer,
60
+ subText && styles.between
61
+ ])
62
+
63
+ const styleVariables = {
64
+ ...(maxHeight && { maxHeight })
65
+ } as React.CSSProperties
66
+
67
+ const showColumnToggle = headings?.some(heading => {
68
+ return typeof heading === 'string' ? false : heading.toggleable
69
+ })
70
+
71
+ const columnToggleItems = [{
72
+ items: headings?.length ? headings
73
+ .filter(heading => typeof heading !== 'string' && heading.toggleable)
74
+ .map(heading => ({
75
+ icon: checkIcon,
76
+ name: (heading as HeadingObject).name,
77
+ value: String(headings.findIndex(h => {
78
+ return (h as HeadingObject).name === (heading as HeadingObject).name
79
+ }))
80
+ })) : []
81
+ }]
82
+
83
+ const columnFilterIndexes = headings?.map(heading => (heading as HeadingObject).filterable)
84
+ .map((heading, index) => heading ? index : null)
85
+ .filter(heading => heading !== null) || []
86
+
87
+ const hasPagination = data?.length && itemsPerPage
88
+ ? data.length > itemsPerPage
89
+ : false
90
+
91
+ const filter = debounce((event: Event) => {
92
+ const target = event.target as HTMLInputElement
93
+
94
+ setHasActiveFilter(!!target.value)
95
+
96
+ setFilteredData(toggledData?.filter((row: string[]) => {
97
+ const rowValue = row.filter((_, index) => columnFilterIndexes.includes(index))
98
+ .join('')
99
+ .toLowerCase()
100
+
101
+ return rowValue.includes(target.value.toLowerCase())
102
+ }))
103
+
104
+ onFilter?.({
105
+ results: filteredData,
106
+ numberOfResults: filteredData.length
107
+ })
108
+ }, 400)
109
+
110
+ const toggleColumns = (event: ListEventType) => {
111
+ const columnToggleListElement = Array.from(event.list.children)
112
+ .find(child => (child as HTMLLIElement).dataset.name === event.name) as HTMLLIElement
113
+ const svgIcon = columnToggleListElement.children[0] as HTMLElement
114
+ let mappedData
115
+
116
+ svgIcon.style.opacity = svgIcon.style.opacity === '0'
117
+ ? '1'
118
+ : '0'
119
+
120
+ if (svgIcon.style.opacity === '0') {
121
+ mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[]) => {
122
+ return row.map((column, index) => index === Number(event.value) ? null : column)
123
+ })
124
+
125
+ setFilteredData(mappedData)
126
+
127
+ setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string) => {
128
+ return ((heading as HeadingObject)?.name || heading) === event.name ? null : heading
129
+ }))
130
+ } else {
131
+ mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[], x: number) => {
132
+ return row.map((column, y) => y === Number(event.value) ? data?.[x][y] : column)
133
+ })
134
+
135
+ setFilteredData(mappedData)
136
+
137
+ setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string, index: number) => {
138
+ return ((headings?.[index] as HeadingObject)?.name || headings?.[index]) === event.name
139
+ ? headings?.[index]
140
+ : heading
141
+ }))
142
+ }
143
+
144
+ setToggledData(mappedData)
145
+ }
146
+
147
+ const sort = (index: number) => {
148
+ const sortedData = filteredData.sort((a: string[], b: string[]) => {
149
+ let aValue: string | number = a[index]
150
+ let bValue: string | number = b[index]
151
+
152
+ if (!isNaN(aValue as any)) {
153
+ aValue = Number(aValue)
154
+ }
155
+
156
+ if (!isNaN(bValue as any)) {
157
+ bValue = Number(bValue)
158
+ }
159
+
160
+ return aValue > bValue
161
+ ? sortOrder * -1
162
+ : sortOrder
163
+ })
164
+
165
+ setFilteredData(sortedData)
166
+ setSortOrder(sortOrder === 1 ? -1 : 1)
167
+ }
168
+
169
+ const isNextPage = (index: number) => {
170
+ if (hasPagination && itemsPerPage && !hasActiveFilter) {
171
+ const currentPage = Math.ceil((index + 1) / itemsPerPage)
172
+
173
+ return currentPage !== page ? 'true' : undefined
174
+ }
175
+
176
+ if (hasActiveFilter && itemsPerPage) {
177
+ return index >= itemsPerPage ? 'true' : undefined
178
+ }
179
+
180
+ return undefined
181
+ }
182
+
183
+ return (
184
+ <section className={className} id={id}>
185
+ {(!!columnFilterIndexes?.length || showColumnToggle) && (
186
+ <div className={styles.filters}>
187
+ {!!columnFilterIndexes?.length && (
188
+ <Input
189
+ type="search"
190
+ placeholder={filterPlaceholder}
191
+ onInput={filter}
192
+ >
193
+ {showFilterIcon && (
194
+ <span dangerouslySetInnerHTML={{ __html: searchIcon }} />
195
+ )}
196
+ </Input>
197
+ )}
198
+ {showColumnToggle && (
199
+ <Select
200
+ name={`data-table-${id || crypto.randomUUID()}`}
201
+ itemGroups={columnToggleItems}
202
+ position="bottom-end"
203
+ value={columnToggleLabel}
204
+ onChange={toggleColumns}
205
+ updateValue={false}
206
+ />
207
+ )}
208
+ </div>
209
+ )}
210
+
211
+ <div className={classes} style={styleVariables}>
212
+ <table>
213
+ {!!filteredHeadings?.length && (
214
+ <thead>
215
+ <tr>
216
+ {filteredHeadings?.map((heading: HeadingObject | string, index: number) => {
217
+ if (!heading) {
218
+ return null
219
+ }
220
+
221
+ return (
222
+ <th key={index}>
223
+ <ConditionalWrapper
224
+ condition={!!(heading as HeadingObject).sortable}
225
+ wrapper={children => (
226
+ <Button theme="flat" slot="wrapper" onClick={() => sort(index)}>
227
+ {children}
228
+ <span dangerouslySetInnerHTML={{ __html: orderIcon }} />
229
+ </Button>
230
+ )}
231
+ >
232
+ {(heading as HeadingObject).name || heading as string}
233
+ </ConditionalWrapper>
234
+ </th>
235
+ )
236
+ })}
237
+ </tr>
238
+ </thead>
239
+ )}
240
+
241
+ <tbody>
242
+ {filteredData?.map((row: string[], rowIndex: number) => (
243
+ <tr key={rowIndex} data-hidden={isNextPage(rowIndex)}>
244
+ {row.filter(Boolean).map((column, columnIndex) => (
245
+ <td
246
+ key={columnIndex}
247
+ dangerouslySetInnerHTML={{ __html: column }}
248
+ />
249
+ ))}
250
+ </tr>
251
+ ))}
252
+ {children}
253
+ </tbody>
254
+ {!filteredData?.length && (
255
+ <tfoot>
256
+ <tr>
257
+ <td
258
+ colSpan={data?.[0].length}
259
+ className={styles['no-results']}
260
+ >
261
+ {noResultsLabel}
262
+ </td>
263
+ </tr>
264
+ </tfoot>
265
+ )}
266
+ </table>
267
+ </div>
268
+ {(subText || hasPagination) && (
269
+ <div className={footerClasses}>
270
+ {subText && (
271
+ <span className={styles.subtext}>{subText}</span>
272
+ )}
273
+ {(hasPagination && itemsPerPage && !hasActiveFilter) && (
274
+ <Pagination
275
+ {...pagination}
276
+ totalPages={Math.ceil((data?.length || 0) / itemsPerPage)}
277
+ currentPage={page}
278
+ onChange={event => setPage(event.page)}
279
+ />
280
+ )}
281
+ </div>
282
+ )}
283
+ </section>
284
+ )
285
+ }
286
+
287
+ export default DataTable
@@ -150,6 +150,7 @@ const generatedPages = pages?.length
150
150
  const prevPageButton = element.querySelector('[data-page="prev"]') as HTMLButtonElement
151
151
  const nextPageButton = element.querySelector('[data-page="next"]') as HTMLButtonElement
152
152
  const currentPageButton = element.querySelector('[data-active]') as HTMLButtonElement
153
+ const previousPage = currentPage
153
154
 
154
155
  const pageElements = Array
155
156
  .from(pagination.children)
@@ -179,6 +180,7 @@ const generatedPages = pages?.length
179
180
 
180
181
  dispatch('paginate', {
181
182
  page: currentPage,
183
+ direction: previousPage > currentPage ? 'prev' : 'next',
182
184
  ...(activeButton?.dataset.page && { label: activeButton?.dataset.page }),
183
185
  target: element,
184
186
  trusted: event.isTrusted
@@ -45,6 +45,8 @@
45
45
  }))
46
46
 
47
47
  const paginate = (to: string | number) => {
48
+ const previousPage = calculatedCurrentPage
49
+
48
50
  if (to === 'prev') {
49
51
  calculatedCurrentPage = calculatedCurrentPage - 1
50
52
  } else if (to === 'next') {
@@ -57,6 +59,7 @@
57
59
 
58
60
  onChange?.({
59
61
  page: calculatedCurrentPage,
62
+ direction: previousPage > calculatedCurrentPage ? 'prev' : 'next',
60
63
  ...(label && { label })
61
64
  })
62
65
  }
@@ -87,7 +90,7 @@
87
90
  theme={theme}
88
91
  onClick={!(disablePrevious || (calculatedCurrentPage === 1 && !previousLink))
89
92
  ? () => paginate('prev')
90
- : null
93
+ : undefined
91
94
  }
92
95
  >
93
96
  {#if showChevrons || type === 'arrows'}
@@ -107,7 +110,7 @@
107
110
  theme={theme}
108
111
  onClick={calculatedCurrentPage !== index + 1
109
112
  ? () => paginate(index + 1)
110
- : null
113
+ : undefined
111
114
  }
112
115
  >
113
116
  {page.label}
@@ -129,7 +132,7 @@
129
132
  theme={theme}
130
133
  onClick={!disableNext
131
134
  ? () => paginate('next')
132
- : null
135
+ : undefined
133
136
  }
134
137
  >
135
138
  {#if type !== 'arrows'}
@@ -53,6 +53,7 @@ const Pagination = ({
53
53
  }))
54
54
 
55
55
  const paginate = (to: string | number) => {
56
+ const previousPage = calculatedCurrentPage
56
57
  let currentPage = calculatedCurrentPage
57
58
 
58
59
  if (to === 'prev') {
@@ -69,6 +70,7 @@ const Pagination = ({
69
70
 
70
71
  onChange?.({
71
72
  page: currentPage,
73
+ direction: previousPage > currentPage ? 'prev' : 'next',
72
74
  ...(label && { label })
73
75
  })
74
76
  }
@@ -104,7 +106,7 @@ const Pagination = ({
104
106
  theme={theme}
105
107
  onClick={!(disablePrevious || (calculatedCurrentPage === 1 && !previousLink))
106
108
  ? () => paginate('prev')
107
- : null
109
+ : undefined
108
110
  }
109
111
  >
110
112
  {(showChevrons || type === 'arrows') && (
@@ -122,7 +124,7 @@ const Pagination = ({
122
124
  theme={theme}
123
125
  onClick={calculatedCurrentPage !== index + 1
124
126
  ? () => paginate(index + 1)
125
- : null
127
+ : undefined
126
128
  }
127
129
  >
128
130
  {page.label}
@@ -144,7 +146,7 @@ const Pagination = ({
144
146
  theme={theme}
145
147
  onClick={!disableNext
146
148
  ? () => paginate('next')
147
- : null
149
+ : undefined
148
150
  }
149
151
  >
150
152
  {type !== 'arrows' && nextPageLabel}
@@ -2,6 +2,7 @@ import type { ButtonProps } from '../Button/button'
2
2
 
3
3
  export type PaginationEventType = {
4
4
  page: number
5
+ direction: 'prev' | 'next'
5
6
  label?: string | number | undefined
6
7
  }
7
8
 
package/index.d.ts CHANGED
@@ -68,6 +68,14 @@ declare module 'webcoreui' {
68
68
  cancel: () => void
69
69
  }
70
70
 
71
+ export const get: (selector: string, all: boolean) => Element | NodeListOf<Element> | null
72
+ export const on: (
73
+ selector: string | HTMLElement,
74
+ event: string,
75
+ callback: any,
76
+ all?: boolean
77
+ ) => void
78
+
71
79
  export const dispatch: (event: string, detail: unknown) => void
72
80
  export const listen: (event: string, callback: (e: any) => unknown) => {
73
81
  remove: () => void
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './utils/classNames.ts'
2
2
  export * from './utils/cookies.ts'
3
3
  export * from './utils/debounce.ts'
4
+ export * from './utils/DOMUtils.ts'
4
5
  export * from './utils/event.ts'
5
6
  export * from './utils/interpolate.ts'
6
7
  export * from './utils/modal.ts'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "webcoreui",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "scripts": {
6
6
  "prepare": "husky",
7
7
  "pre-commit": "lint-staged",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@astrojs/check": "0.7.0",
17
+ "@astrojs/node": "8.3.4",
17
18
  "@astrojs/react": "3.5.0",
18
19
  "@astrojs/svelte": "5.5.0",
19
20
  "@eslint/js": "9.9.1",
@@ -11,7 +11,9 @@ $fontSizes: (
11
11
  'lg': 18px,
12
12
  'xl': 21px,
13
13
  '2xl': 24px,
14
- '3xl': 28px
14
+ '3xl': 28px,
15
+ '4xl': 32px,
16
+ '5xl': 36px
15
17
  ) !default;
16
18
 
17
19
  $lineHeights: (
@@ -5,7 +5,7 @@
5
5
  @use '../config' as *;
6
6
 
7
7
  $breakpointMap: (
8
- 'xs': 3,
8
+ 'xs': 4,
9
9
  'sm': 4,
10
10
  'md': 6,
11
11
  'lg': 8
@@ -0,0 +1,28 @@
1
+ export const get = (selector: string, all: boolean = false) => all
2
+ ? document?.querySelectorAll(selector)
3
+ : document?.querySelector(selector)
4
+
5
+ export const on = (
6
+ selector: string | HTMLElement,
7
+ event: string,
8
+ callback: any,
9
+ all?: boolean
10
+ ) => {
11
+ if (all) {
12
+ const elements = document.querySelectorAll(selector as string)
13
+
14
+ elements?.forEach(element => {
15
+ element.addEventListener(event, callback)
16
+ })
17
+
18
+ return
19
+ }
20
+
21
+ if (typeof selector === 'string') {
22
+ (get(selector) as HTMLElement)?.addEventListener(event, callback)
23
+
24
+ return
25
+ }
26
+
27
+ selector?.addEventListener(event, callback)
28
+ }
@@ -1,31 +0,0 @@
1
- @import '../config';
2
-
3
- @mixin elements() {
4
- code {
5
- display: inline-block;
6
- padding: 2px 10px;
7
- border-radius: 5px;
8
- border: 1px solid var(--w-color-primary-50);
9
- font-size: 14px;
10
- }
11
-
12
- figure {
13
- margin: 0;
14
- }
15
-
16
- p {
17
- font-size: 16px;
18
- line-height: 1.7;
19
- margin: 20px 0;
20
- }
21
-
22
- ul, ol {
23
- line-height: 1.7;
24
- font-size: 16px;
25
- margin: 20px 0;
26
-
27
- li {
28
- margin-bottom: 10px;
29
- }
30
- }
31
- }