webcoreui 1.3.0 → 1.4.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 +6 -3
  2. package/astro.d.ts +6 -0
  3. package/astro.js +4 -0
  4. package/components/Accordion/Accordion.astro +1 -0
  5. package/components/Accordion/Accordion.svelte +1 -1
  6. package/components/Accordion/Accordion.tsx +1 -1
  7. package/components/Accordion/accordion.ts +1 -0
  8. package/components/Avatar/Avatar.astro +4 -2
  9. package/components/Avatar/Avatar.svelte +6 -4
  10. package/components/Avatar/Avatar.tsx +4 -2
  11. package/components/Checkbox/Checkbox.svelte +2 -0
  12. package/components/Checkbox/Checkbox.tsx +0 -2
  13. package/components/Checkbox/checkbox.ts +3 -1
  14. package/components/Collapsible/collapsible.ts +1 -1
  15. package/components/DataTable/DataTable.astro +4 -4
  16. package/components/DataTable/DataTable.svelte +1 -1
  17. package/components/DataTable/DataTable.tsx +1 -1
  18. package/components/Image/Image.astro +45 -0
  19. package/components/Image/Image.svelte +51 -0
  20. package/components/Image/Image.tsx +52 -0
  21. package/components/Image/image.module.scss +47 -0
  22. package/components/Image/image.ts +13 -0
  23. package/components/ImageLoader/ImageLoader.astro +82 -0
  24. package/components/ImageLoader/ImageLoader.svelte +72 -0
  25. package/components/ImageLoader/ImageLoader.tsx +82 -0
  26. package/components/ImageLoader/imageloader.module.scss +13 -0
  27. package/components/ImageLoader/imageloader.ts +6 -0
  28. package/components/Input/input.ts +2 -2
  29. package/components/Popover/popover.module.scss +4 -2
  30. package/components/RangeSlider/RangeSlider.svelte +3 -3
  31. package/components/RangeSlider/RangeSlider.tsx +1 -1
  32. package/components/Switch/Switch.svelte +2 -0
  33. package/components/Switch/Switch.tsx +0 -2
  34. package/components/Switch/switch.module.scss +1 -0
  35. package/components/Switch/switch.ts +3 -1
  36. package/components/Textarea/Textarea.svelte +2 -0
  37. package/components/Textarea/textarea.ts +7 -6
  38. package/components/ThemeSwitcher/themeswitcher.module.scss +1 -0
  39. package/components/ThemeSwitcher/themeswitcher.ts +1 -0
  40. package/index.d.ts +12 -5
  41. package/package.json +22 -22
  42. package/react.d.ts +6 -0
  43. package/react.js +4 -0
  44. package/scss/resets.scss +2 -0
  45. package/svelte.d.ts +6 -0
  46. package/svelte.js +4 -0
  47. package/utils/DOMUtils.ts +3 -3
  48. package/utils/modal.ts +2 -2
  49. package/utils/popover.ts +87 -46
package/README.md CHANGED
@@ -52,6 +52,7 @@
52
52
 
53
53
  - Full documentation available on [webcoreui.dev](https://webcoreui.dev).
54
54
  - For installation steps, visit our [setup docs](https://webcoreui.dev/docs/setup).
55
+ - To build and test components visually, visit our [builder](https://webcoreui.dev/build).
55
56
 
56
57
  ## Getting Started
57
58
 
@@ -64,15 +65,15 @@ Webcore can be used as a standalone project, or it can be integrated into your e
64
65
 
65
66
  Webcore components use Sass for styling. To use the component library, you must have the following packages installed:
66
67
 
67
- - [Sass](https://www.npmjs.com/package/sass) - `v1.94`
68
+ - [Sass](https://www.npmjs.com/package/sass) - `v1.97`
68
69
  - [TypeScript](https://www.npmjs.com/package/typescript) - `v5.9`
69
70
 
70
71
  Depending on your project setup, you'll also need the following packages:
71
72
 
72
73
  - **For Astro projects**
73
- - [Astro](https://www.npmjs.com/package/astro) - `v5.16`
74
+ - [Astro](https://www.npmjs.com/package/astro) - `v5.17`
74
75
  - **For Svelte projects**
75
- - [Svelte](https://www.npmjs.com/package/svelte) - `v5.45`
76
+ - [Svelte](https://www.npmjs.com/package/svelte) - `v5.53`
76
77
  - **For React projects**
77
78
  - [React](https://www.npmjs.com/package/react) - `v19.2`
78
79
  - [React DOM](https://www.npmjs.com/package/react-dom) -`v19.2`
@@ -297,6 +298,8 @@ import { Accordion } from 'webcoreui/react'
297
298
  - [Grid](https://github.com/Frontendland/webcoreui/tree/main/src/components/Grid)
298
299
  - [Group](https://github.com/Frontendland/webcoreui/tree/main/src/components/Group)
299
300
  - [Icon](https://github.com/Frontendland/webcoreui/tree/main/src/components/Icon)
301
+ - [Image](https://github.com/Frontendland/webcoreui/tree/main/src/components/Image)
302
+ - [ImageLoader](https://github.com/Frontendland/webcoreui/tree/main/src/components/ImageLoader)
300
303
  - [Input](https://github.com/Frontendland/webcoreui/tree/main/src/components/Input)
301
304
  - [Kbd](https://github.com/Frontendland/webcoreui/tree/main/src/components/Kbd)
302
305
  - [List](https://github.com/Frontendland/webcoreui/tree/main/src/components/List)
package/astro.d.ts CHANGED
@@ -21,6 +21,8 @@ import type { FooterProps as WFooterProps } from './components/Footer/footer'
21
21
  import type { GridProps as WGridProps } from './components/Grid/grid'
22
22
  import type { GroupProps as WGroupProps } from './components/Group/group'
23
23
  import type { IconProps as WIconProps } from './components/Icon/icon'
24
+ import type { ImageProps as WImageProps } from './components/Image/image'
25
+ import type { ImageLoaderProps as WImageLoaderProps } from './components/ImageLoader/imageloader'
24
26
  import type { InputProps as WInputProps } from './components/Input/input'
25
27
  import type { KbdProps as WKbdProps } from './components/Kbd/kbd'
26
28
  import type { ListProps as WListProps } from './components/List/list'
@@ -83,6 +85,8 @@ declare module 'webcoreui/astro' {
83
85
  export function Grid(_props: WGridProps): any
84
86
  export function Group(_props: WGroupProps): any
85
87
  export function Icon(_props: WIconProps): any
88
+ export function Image(_props: WImageProps): any
89
+ export function ImageLoader(_props: WImageLoaderProps): any
86
90
  export function Input(_props: WInputProps): any
87
91
  export function Kbd(_props: WKbdProps): any
88
92
  export function List(_props: WListProps): any
@@ -138,6 +142,8 @@ declare module 'webcoreui/astro' {
138
142
  export type GridProps = WGridProps
139
143
  export type GroupProps = WGroupProps
140
144
  export type IconProps = WIconProps
145
+ export type ImageProps = WImageProps
146
+ export type ImageLoaderProps = WImageLoaderProps
141
147
  export type InputProps = WInputProps
142
148
  export type KbdProps = WKbdProps
143
149
  export type ListProps = WListProps
package/astro.js CHANGED
@@ -21,6 +21,8 @@ import FooterComponent from './components/Footer/Footer.astro'
21
21
  import GridComponent from './components/Grid/Grid.astro'
22
22
  import GroupComponent from './components/Group/Group.astro'
23
23
  import IconComponent from './components/Icon/Icon.astro'
24
+ import ImageComponent from './components/Image/Image.astro'
25
+ import ImageLoaderComponent from './components/ImageLoader/ImageLoader.astro'
24
26
  import InputComponent from './components/Input/Input.astro'
25
27
  import KbdComponent from './components/Kbd/Kbd.astro'
26
28
  import ListComponent from './components/List/List.astro'
@@ -76,6 +78,8 @@ export const Footer = FooterComponent
76
78
  export const Grid = GridComponent
77
79
  export const Group = GroupComponent
78
80
  export const Icon = IconComponent
81
+ export const Image = ImageComponent
82
+ export const ImageLoader = ImageLoaderComponent
79
83
  export const Input = InputComponent
80
84
  export const Kbd = KbdComponent
81
85
  export const List = ListComponent
@@ -29,6 +29,7 @@ const classes = [
29
29
  <button
30
30
  class:list={[styles.title, item.reverse && styles.reverse]}
31
31
  data-toggle="true"
32
+ data-open={item.expanded}
32
33
  >
33
34
  {item.title}
34
35
  {icon !== 'none' && (
@@ -15,7 +15,7 @@
15
15
  className
16
16
  }: AccordionProps = $props()
17
17
 
18
- let toggleState = $state(Array(items.length).fill(false))
18
+ let toggleState = $state(items.map(item => item.expanded ?? false))
19
19
 
20
20
  const toggle = (index: number) => {
21
21
  toggleState = toggleState.map((_, i) => index === i
@@ -14,7 +14,7 @@ const Accordion = ({
14
14
  reverse,
15
15
  className
16
16
  }: AccordionProps) => {
17
- const [state, setState] = useState(Array(items.length).fill(false))
17
+ const [state, setState] = useState(items.map(item => item.expanded ?? false))
18
18
 
19
19
  const toggle = (index: number) => {
20
20
  setState(state.map((_, i) => index === i
@@ -3,6 +3,7 @@ export type AccordionProps = {
3
3
  title: string
4
4
  content: string
5
5
  reverse?: boolean
6
+ expanded?: boolean
6
7
  }[]
7
8
  icon?: 'plus' | 'none' | undefined | null
8
9
  reverse?: boolean
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  import type { AvatarProps } from './avatar'
3
3
 
4
+ import Image from '../Image/Image.astro'
5
+
4
6
  import styles from './avatar.module.scss'
5
7
 
6
8
  interface Props extends AvatarProps {}
@@ -35,7 +37,7 @@ const groupClasses = [
35
37
  style={borderColor ? `--w-avatar-border: ${borderColor};` : null}
36
38
  >
37
39
  {img.map((img, index) => (
38
- <img
40
+ <Image
39
41
  src={img}
40
42
  alt={Array.isArray(alt) ? alt[index] : alt}
41
43
  width={Array.isArray(size) ? size[index] : size}
@@ -47,7 +49,7 @@ const groupClasses = [
47
49
  ))}
48
50
  </div>
49
51
  ) : (
50
- <img
52
+ <Image
51
53
  src={img}
52
54
  alt={Array.isArray(alt) ? alt[0] : alt}
53
55
  width={Array.isArray(size) ? size[0] : size}
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { AvatarProps } from './avatar'
3
3
 
4
+ import Image from '../Image/Image.svelte'
5
+
4
6
  import { classNames } from '../../utils/classNames'
5
7
 
6
8
  import styles from './avatar.module.scss'
@@ -35,24 +37,24 @@
35
37
  style={borderColor ? `--w-avatar-border: ${borderColor};` : null}
36
38
  >
37
39
  {#each img as img, index}
38
- <img
40
+ <Image
39
41
  src={img}
40
42
  alt={Array.isArray(alt) ? alt[index] : alt}
41
43
  width={Array.isArray(size) ? size[index] : size}
42
44
  height={Array.isArray(size) ? size[index] : size}
43
45
  loading={lazy ? 'lazy' : null}
44
- class={classes}
46
+ className={classes}
45
47
  style={Array.isArray(size) ? `--w-avatar-index: ${size[index]}` : null}
46
48
  />
47
49
  {/each}
48
50
  </div>
49
51
  {:else}
50
- <img
52
+ <Image
51
53
  src={img}
52
54
  alt={Array.isArray(alt) ? alt[0] : alt}
53
55
  width={Array.isArray(size) ? size[0] : size}
54
56
  height={Array.isArray(size) ? size[0] : size}
55
- class={classes}
57
+ className={classes}
56
58
  loading={lazy ? 'lazy' : null}
57
59
  style={borderColor ? `--w-avatar-border: ${borderColor};` : null}
58
60
  />
@@ -1,6 +1,8 @@
1
1
  import React from 'react'
2
2
  import type { AvatarProps } from './avatar'
3
3
 
4
+ import Image from '../Image/Image.tsx'
5
+
4
6
  import { classNames } from '../../utils/classNames'
5
7
 
6
8
  import styles from './avatar.module.scss'
@@ -37,7 +39,7 @@ const Avatar = ({
37
39
  style={borderColorStyle}
38
40
  >
39
41
  {img.map((img, index) => (
40
- <img
42
+ <Image
41
43
  key={index}
42
44
  src={img}
43
45
  alt={Array.isArray(alt) ? alt[index] : alt}
@@ -53,7 +55,7 @@ const Avatar = ({
53
55
  ))}
54
56
  </div>
55
57
  ) : (
56
- <img
58
+ <Image
57
59
  src={img}
58
60
  alt={Array.isArray(alt) ? alt[0] : alt}
59
61
  width={Array.isArray(size) ? size[0] : size}
@@ -17,6 +17,7 @@
17
17
  color,
18
18
  className,
19
19
  onClick,
20
+ onChange,
20
21
  ...rest
21
22
  }: SvelteCheckboxProps = $props()
22
23
 
@@ -42,6 +43,7 @@
42
43
  checked={checked}
43
44
  disabled={disabled}
44
45
  onclick={onClick}
46
+ onchange={onChange}
45
47
  {...rest}
46
48
  />
47
49
  <span class={styles.check}>
@@ -16,7 +16,6 @@ const Checkbox = ({
16
16
  disabled,
17
17
  color,
18
18
  className,
19
- onClick,
20
19
  ...rest
21
20
  }: ReactCheckboxProps) => {
22
21
  const classes = classNames([
@@ -43,7 +42,6 @@ const Checkbox = ({
43
42
  type="checkbox"
44
43
  defaultChecked={checked}
45
44
  disabled={disabled}
46
- onClick={onClick}
47
45
  {...rest}
48
46
  />
49
47
  <span
@@ -1,4 +1,4 @@
1
- import type { MouseEventHandler } from 'svelte/elements'
1
+ import type { ChangeEventHandler, MouseEventHandler } from 'svelte/elements'
2
2
 
3
3
  export type CheckboxProps = {
4
4
  checked?: boolean
@@ -11,9 +11,11 @@ export type CheckboxProps = {
11
11
  }
12
12
 
13
13
  export type SvelteCheckboxProps = {
14
+ onChange?: ChangeEventHandler<HTMLInputElement>
14
15
  onClick?: MouseEventHandler<HTMLInputElement>
15
16
  } & CheckboxProps
16
17
 
17
18
  export type ReactCheckboxProps = {
19
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
18
20
  onClick?: React.MouseEventHandler<HTMLInputElement>
19
21
  } & CheckboxProps
@@ -17,6 +17,6 @@ export type SvelteCollapsibleProps = {
17
17
  export type ReactCollapsibleProps = {
18
18
  on: React.ReactNode
19
19
  off: React.ReactNode
20
- children?: React.ReactNode
20
+ children: React.ReactNode
21
21
  } & CollapsibleProps
22
22
 
@@ -149,7 +149,7 @@ const hasPagination = data?.length && itemsPerPage
149
149
  {!!columnFilterItems?.length && (
150
150
  <tfoot data-hidden="true">
151
151
  <tr>
152
- <td colspan={data?.[0].length} class={styles['no-results']}>{noResultsLabel}</td>
152
+ <td colspan={headings?.length} class={styles['no-results']}>{noResultsLabel}</td>
153
153
  </tr>
154
154
  </tfoot>
155
155
  )}
@@ -251,9 +251,9 @@ const hasPagination = data?.length && itemsPerPage
251
251
  const sortedTableRows = Array.from(
252
252
  table?.querySelectorAll('tbody tr') as NodeListOf<HTMLTableRowElement>
253
253
  ).sort((a, b) => {
254
- let aValue: string | number = (a.querySelector(`[data-name=${sortBy}]`) as HTMLElement)
254
+ let aValue: string | number = (a.querySelector(`[data-name="${sortBy}"]`) as HTMLElement)
255
255
  ?.innerText.replace(/\s/g, '')
256
- let bValue: string | number = (b.querySelector(`[data-name=${sortBy}]`) as HTMLElement)
256
+ let bValue: string | number = (b.querySelector(`[data-name="${sortBy}"]`) as HTMLElement)
257
257
  ?.innerText.replace(/\s/g, '')
258
258
 
259
259
  if (!isNaN(aValue as any)) {
@@ -298,7 +298,7 @@ const hasPagination = data?.length && itemsPerPage
298
298
  return
299
299
  }
300
300
 
301
- const affectedTableCells = Array.from(table.querySelectorAll(`[data-name=${eventName}]`)) as HTMLElement[]
301
+ const affectedTableCells = Array.from(table.querySelectorAll(`[data-name="${eventName}"]`)) as HTMLElement[]
302
302
 
303
303
  const columnToggleListElement = Array.from(event.list.children)
304
304
  .find(child => (child as HTMLLIElement).dataset.name === event.name) as HTMLLIElement
@@ -250,7 +250,7 @@
250
250
  {#if columnFilterIndexes?.length && !filteredData.length}
251
251
  <tfoot>
252
252
  <tr>
253
- <td colspan={data?.[0].length} class={styles['no-results']}>
253
+ <td colspan={headings?.length} class={styles['no-results']}>
254
254
  {noResultsLabel}
255
255
  </td>
256
256
  </tr>
@@ -255,7 +255,7 @@ const DataTable = ({
255
255
  <tfoot>
256
256
  <tr>
257
257
  <td
258
- colSpan={data?.[0].length}
258
+ colSpan={headings?.length}
259
259
  className={styles['no-results']}
260
260
  >
261
261
  {noResultsLabel}
@@ -0,0 +1,45 @@
1
+ ---
2
+ import type { ImageProps } from './image'
3
+
4
+ import AspectRatio from '../AspectRatio/AspectRatio.astro'
5
+ import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.astro'
6
+
7
+ import styles from './image.module.scss'
8
+
9
+ interface Props extends ImageProps {}
10
+
11
+ const {
12
+ src,
13
+ alt,
14
+ width,
15
+ height,
16
+ lazy,
17
+ ratio,
18
+ center,
19
+ full,
20
+ rounded,
21
+ className,
22
+ ...rest
23
+ } = Astro.props
24
+
25
+ const classes = [
26
+ styles.img,
27
+ center && styles.center,
28
+ full && styles[full],
29
+ rounded && styles[rounded],
30
+ className
31
+ ]
32
+ ---
33
+
34
+ <ConditionalWrapper condition={!!ratio}>
35
+ <AspectRatio slot="wrapper" ratio={ratio || ''}>children</AspectRatio>
36
+ <img
37
+ {...rest}
38
+ src={src}
39
+ alt={alt || ''}
40
+ width={width}
41
+ height={height}
42
+ loading={lazy ? 'lazy' : null}
43
+ class:list={classes}
44
+ />
45
+ </ConditionalWrapper>
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import type { ImageProps } from './image'
3
+
4
+ import AspectRatio from '../AspectRatio/AspectRatio.svelte'
5
+
6
+ import { classNames } from '../../utils/classNames'
7
+
8
+ import styles from './image.module.scss'
9
+
10
+ const {
11
+ src,
12
+ alt,
13
+ width,
14
+ height,
15
+ lazy,
16
+ ratio,
17
+ center,
18
+ full,
19
+ rounded,
20
+ className,
21
+ ...rest
22
+ }: ImageProps = $props()
23
+
24
+ const classes = classNames([
25
+ styles.img,
26
+ center && styles.center,
27
+ full && styles[full],
28
+ rounded && styles[rounded],
29
+ className
30
+ ])
31
+ </script>
32
+
33
+ {#snippet img()}
34
+ <img
35
+ {...rest}
36
+ src={src}
37
+ alt={alt || ''}
38
+ width={width}
39
+ height={height}
40
+ loading={lazy ? 'lazy' : null}
41
+ class={classes}
42
+ />
43
+ {/snippet}
44
+
45
+ {#if ratio}
46
+ <AspectRatio ratio={ratio}>
47
+ {@render img()}
48
+ </AspectRatio>
49
+ {:else}
50
+ {@render img()}
51
+ {/if}
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import type { ImageProps } from './image'
3
+
4
+ import AspectRatio from '../AspectRatio/AspectRatio.tsx'
5
+ import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
6
+
7
+ import { classNames } from '../../utils/classNames'
8
+
9
+ import styles from './image.module.scss'
10
+
11
+ const Image = ({
12
+ src,
13
+ alt,
14
+ width,
15
+ height,
16
+ lazy,
17
+ ratio,
18
+ center,
19
+ full,
20
+ rounded,
21
+ className,
22
+ ...rest
23
+ }: ImageProps) => {
24
+ const classes = classNames([
25
+ styles.img,
26
+ center && styles.center,
27
+ full && styles[full],
28
+ rounded && styles[rounded],
29
+ className
30
+ ])
31
+
32
+ return (
33
+ <ConditionalWrapper
34
+ condition={!!ratio}
35
+ wrapper={children => (
36
+ <AspectRatio ratio={ratio || ''}>{children}</AspectRatio>
37
+ )}
38
+ >
39
+ <img
40
+ {...rest}
41
+ src={src}
42
+ alt={alt || ''}
43
+ width={width}
44
+ height={height}
45
+ loading={lazy ? 'lazy' : undefined}
46
+ className={classes}
47
+ />
48
+ </ConditionalWrapper>
49
+ )
50
+ }
51
+
52
+ export default Image
@@ -0,0 +1,47 @@
1
+ @use '../../scss/config.scss' as *;
2
+
3
+ .img {
4
+ &.center {
5
+ @include spacing(auto-none);
6
+ }
7
+
8
+ &.width {
9
+ @include size('w100%');
10
+ }
11
+
12
+ &.height {
13
+ @include size('h100%');
14
+ }
15
+
16
+ &.both {
17
+ @include size(100%);
18
+ }
19
+
20
+ &.top {
21
+ @include border-radius(top);
22
+ }
23
+
24
+ &.bottom {
25
+ @include border-radius(bottom);
26
+ }
27
+
28
+ &.left {
29
+ @include border-radius(left);
30
+ }
31
+
32
+ &.right {
33
+ @include border-radius(right);
34
+ }
35
+
36
+ &.diagonal {
37
+ @include border-radius(diagonal);
38
+ }
39
+
40
+ &.reverse-diagonal {
41
+ @include border-radius(reverse-diagonal);
42
+ }
43
+
44
+ &.none {
45
+ @include border-radius(none);
46
+ }
47
+ }
@@ -0,0 +1,13 @@
1
+ export type ImageProps = {
2
+ src: string
3
+ alt: string
4
+ width: number | string
5
+ height: number | string
6
+ lazy?: boolean
7
+ ratio?: string
8
+ center?: boolean
9
+ full?: 'width' | 'height' | 'both'
10
+ rounded?: 'top' | 'bottom' | 'left' | 'right' | 'diagonal' | 'reverse-diagonal' | 'none'
11
+ className?: string
12
+ [key: string]: any
13
+ }
@@ -0,0 +1,82 @@
1
+ ---
2
+ import type { ImageLoaderProps } from './imageloader'
3
+
4
+ import Image from '../Image/Image.astro'
5
+ import Skeleton from '../Skeleton/Skeleton.astro'
6
+
7
+ import { classNames } from '../../utils/classNames'
8
+
9
+ import styles from './imageloader.module.scss'
10
+
11
+ interface Props extends ImageLoaderProps {}
12
+
13
+ const {
14
+ fallback,
15
+ animate,
16
+ type,
17
+ width,
18
+ height,
19
+ color,
20
+ waveColor,
21
+ className,
22
+ ...rest
23
+ } = Astro.props
24
+
25
+ const styleVariables = classNames([
26
+ `width: ${width}px;`,
27
+ `height: ${height}px;`
28
+ ])
29
+ ---
30
+
31
+ <div data-id="w-image-loader" class={styles.loader} style={styleVariables}>
32
+ <Skeleton
33
+ animate={animate}
34
+ type={type}
35
+ width={Number(width)}
36
+ height={Number(height)}
37
+ color={color}
38
+ waveColor={waveColor}
39
+ className={className}
40
+ />
41
+ <Image
42
+ width={width}
43
+ height={height}
44
+ className={className}
45
+ data-fallback={fallback}
46
+ {...rest}
47
+ />
48
+ </div>
49
+
50
+ <script>
51
+ import { get, on } from '../../utils/DOMUtils'
52
+
53
+ const addEventListeners = () => {
54
+ const images = get<HTMLImageElement>('[data-id="w-image-loader"] img', true)
55
+
56
+ if (!images || !(images instanceof NodeList)) {
57
+ return
58
+ }
59
+
60
+ images.forEach(img => {
61
+ const container = img.parentElement as HTMLDivElement
62
+ const skeleton = container.firstElementChild as HTMLDivElement
63
+
64
+ const handleError = () => {
65
+ img.src = img.dataset.fallback || img.src
66
+ skeleton?.remove()
67
+ }
68
+
69
+ if (img.complete) {
70
+ img.naturalWidth === 0 ? handleError() : skeleton?.remove()
71
+
72
+ return
73
+ }
74
+
75
+ img.addEventListener('load', () => skeleton?.remove(), { once: true })
76
+ img.addEventListener('error', () => handleError(), { once: true })
77
+ })
78
+ }
79
+
80
+ on(document, 'astro:after-swap', addEventListeners)
81
+ addEventListeners()
82
+ </script>