webcoreui 1.2.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 (82) hide show
  1. package/README.md +14 -6
  2. package/astro.d.ts +9 -0
  3. package/astro.js +6 -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/Badge/Badge.astro +2 -0
  12. package/components/Badge/Badge.svelte +2 -0
  13. package/components/Badge/Badge.tsx +2 -0
  14. package/components/Badge/badge.module.scss +26 -0
  15. package/components/Badge/badge.ts +1 -0
  16. package/components/Checkbox/Checkbox.svelte +2 -0
  17. package/components/Checkbox/Checkbox.tsx +0 -2
  18. package/components/Checkbox/checkbox.ts +3 -1
  19. package/components/Collapsible/collapsible.ts +1 -1
  20. package/components/Counter/Counter.astro +164 -0
  21. package/components/Counter/Counter.svelte +141 -0
  22. package/components/Counter/Counter.tsx +161 -0
  23. package/components/Counter/counter.module.scss +155 -0
  24. package/components/Counter/counter.ts +21 -0
  25. package/components/DataTable/DataTable.astro +4 -4
  26. package/components/DataTable/DataTable.svelte +1 -1
  27. package/components/DataTable/DataTable.tsx +2 -2
  28. package/components/Icon/map.ts +2 -0
  29. package/components/Image/Image.astro +45 -0
  30. package/components/Image/Image.svelte +51 -0
  31. package/components/Image/Image.tsx +52 -0
  32. package/components/Image/image.module.scss +47 -0
  33. package/components/Image/image.ts +13 -0
  34. package/components/ImageLoader/ImageLoader.astro +82 -0
  35. package/components/ImageLoader/ImageLoader.svelte +72 -0
  36. package/components/ImageLoader/ImageLoader.tsx +82 -0
  37. package/components/ImageLoader/imageloader.module.scss +13 -0
  38. package/components/ImageLoader/imageloader.ts +6 -0
  39. package/components/Input/input.ts +2 -2
  40. package/components/List/List.astro +3 -0
  41. package/components/List/List.svelte +12 -9
  42. package/components/List/List.tsx +3 -0
  43. package/components/List/list.module.scss +5 -0
  44. package/components/List/list.ts +40 -39
  45. package/components/Menu/Menu.tsx +1 -1
  46. package/components/Pagination/Pagination.tsx +1 -1
  47. package/components/Pagination/pagination.module.scss +1 -0
  48. package/components/Popover/Popover.astro +28 -26
  49. package/components/Popover/Popover.svelte +2 -0
  50. package/components/Popover/Popover.tsx +2 -0
  51. package/components/Popover/popover.module.scss +10 -2
  52. package/components/Popover/popover.ts +17 -16
  53. package/components/Progress/Progress.astro +6 -2
  54. package/components/Progress/Progress.svelte +6 -2
  55. package/components/Progress/Progress.tsx +6 -2
  56. package/components/Progress/progress.module.scss +15 -0
  57. package/components/Progress/progress.ts +1 -0
  58. package/components/RangeSlider/RangeSlider.astro +5 -0
  59. package/components/RangeSlider/RangeSlider.svelte +3 -3
  60. package/components/RangeSlider/RangeSlider.tsx +1 -1
  61. package/components/RangeSlider/rangeslider.ts +1 -0
  62. package/components/Switch/Switch.svelte +2 -0
  63. package/components/Switch/Switch.tsx +0 -2
  64. package/components/Switch/switch.module.scss +1 -0
  65. package/components/Switch/switch.ts +3 -1
  66. package/components/Textarea/Textarea.svelte +2 -0
  67. package/components/Textarea/textarea.ts +7 -6
  68. package/components/ThemeSwitcher/themeswitcher.module.scss +1 -0
  69. package/components/ThemeSwitcher/themeswitcher.ts +1 -0
  70. package/icons/minus.svg +3 -0
  71. package/icons.d.ts +1 -0
  72. package/icons.js +1 -0
  73. package/index.d.ts +12 -5
  74. package/package.json +111 -109
  75. package/react.d.ts +9 -0
  76. package/react.js +6 -0
  77. package/scss/resets.scss +2 -0
  78. package/svelte.d.ts +9 -0
  79. package/svelte.js +6 -0
  80. package/utils/DOMUtils.ts +3 -3
  81. package/utils/modal.ts +2 -2
  82. 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,18 +65,18 @@ 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.9`
68
- - [TypeScript](https://www.npmjs.com/package/typescript) - `v5.8`
68
+ - [Sass](https://www.npmjs.com/package/sass) - `v1.97`
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.13`
74
+ - [Astro](https://www.npmjs.com/package/astro) - `v5.17`
74
75
  - **For Svelte projects**
75
- - [Svelte](https://www.npmjs.com/package/svelte) - `v5.38`
76
+ - [Svelte](https://www.npmjs.com/package/svelte) - `v5.53`
76
77
  - **For React projects**
77
- - [React](https://www.npmjs.com/package/react) - `v19.0`
78
- - [React DOM](https://www.npmjs.com/package/react-dom) -`v19.0`
78
+ - [React](https://www.npmjs.com/package/react) - `v19.2`
79
+ - [React DOM](https://www.npmjs.com/package/react-dom) -`v19.2`
79
80
 
80
81
  ### Installation with CLI
81
82
 
@@ -167,6 +168,9 @@ html body {
167
168
  --w-collapsible-initial-height: 0;
168
169
  --w-collapsible-max-height: 100%;
169
170
 
171
+ // Counter component
172
+ --w-counter-width: 10ch;
173
+
170
174
  // Masonry component
171
175
  --w-masonry-gap: 5px;
172
176
 
@@ -287,12 +291,15 @@ import { Accordion } from 'webcoreui/react'
287
291
  - [ConditionalWrapper](https://github.com/Frontendland/webcoreui/tree/main/src/components/ConditionalWrapper)
288
292
  - [ContextMenu](https://github.com/Frontendland/webcoreui/tree/main/src/components/ContextMenu)
289
293
  - [Copy](https://github.com/Frontendland/webcoreui/tree/main/src/components/Copy)
294
+ - [Counter](https://github.com/Frontendland/webcoreui/tree/main/src/components/Counter)
290
295
  - [DataTable](https://github.com/Frontendland/webcoreui/tree/main/src/components/DataTable)
291
296
  - [Flex](https://github.com/Frontendland/webcoreui/tree/main/src/components/Flex)
292
297
  - [Footer](https://github.com/Frontendland/webcoreui/tree/main/src/components/Footer)
293
298
  - [Grid](https://github.com/Frontendland/webcoreui/tree/main/src/components/Grid)
294
299
  - [Group](https://github.com/Frontendland/webcoreui/tree/main/src/components/Group)
295
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)
296
303
  - [Input](https://github.com/Frontendland/webcoreui/tree/main/src/components/Input)
297
304
  - [Kbd](https://github.com/Frontendland/webcoreui/tree/main/src/components/Kbd)
298
305
  - [List](https://github.com/Frontendland/webcoreui/tree/main/src/components/List)
@@ -332,6 +339,7 @@ import { Accordion } from 'webcoreui/react'
332
339
  - [BlogCard](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/BlogCard)
333
340
  - [ComponentMap](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/ComponentMap)
334
341
  - [DeviceMockup](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/DeviceMockup)
342
+ - [Empty](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/Empty)
335
343
  - [ErrorPage](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/ErrorPage)
336
344
  - [ExpandableTable](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/ExpandableTable)
337
345
  - [FAQ](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/FAQ)
package/astro.d.ts CHANGED
@@ -14,12 +14,15 @@ import type { CollapsibleProps as WCollapsibleProps } from './components/Collaps
14
14
  import type { ConditionalWrapperProps as WConditionalWrapperProps } from './components/ConditionalWrapper/conditionalwrapper'
15
15
  import type { ContextMenuProps as WContextMenuProps } from './components/ContextMenu/contextmenu'
16
16
  import type { CopyProps as WCopyProps } from './components/Copy/copy'
17
+ import type { CounterProps as WCounterProps } from './components/Counter/counter'
17
18
  import type { DataTableProps as WDataTableProps } from './components/DataTable/datatable'
18
19
  import type { FlexProps as WFlexProps } from './components/Flex/flex'
19
20
  import type { FooterProps as WFooterProps } from './components/Footer/footer'
20
21
  import type { GridProps as WGridProps } from './components/Grid/grid'
21
22
  import type { GroupProps as WGroupProps } from './components/Group/group'
22
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'
23
26
  import type { InputProps as WInputProps } from './components/Input/input'
24
27
  import type { KbdProps as WKbdProps } from './components/Kbd/kbd'
25
28
  import type { ListProps as WListProps } from './components/List/list'
@@ -75,12 +78,15 @@ declare module 'webcoreui/astro' {
75
78
  export function ConditionalWrapper(_props: WConditionalWrapperProps): any
76
79
  export function ContextMenu(_props: WContextMenuProps): any
77
80
  export function Copy(_props: WCopyProps): any
81
+ export function Counter(_props: WCounterProps): any
78
82
  export function DataTable(_props: WDataTableProps): any
79
83
  export function Flex(_props: WFlexProps): any
80
84
  export function Footer(_props: WFooterProps): any
81
85
  export function Grid(_props: WGridProps): any
82
86
  export function Group(_props: WGroupProps): any
83
87
  export function Icon(_props: WIconProps): any
88
+ export function Image(_props: WImageProps): any
89
+ export function ImageLoader(_props: WImageLoaderProps): any
84
90
  export function Input(_props: WInputProps): any
85
91
  export function Kbd(_props: WKbdProps): any
86
92
  export function List(_props: WListProps): any
@@ -129,12 +135,15 @@ declare module 'webcoreui/astro' {
129
135
  export type ConditionalWrapperProps = WConditionalWrapperProps
130
136
  export type ContextMenuProps = WContextMenuProps
131
137
  export type CopyProps = WCopyProps
138
+ export type CounterProps = WCounterProps
132
139
  export type DataTableProps = WDataTableProps
133
140
  export type FlexProps = WFlexProps
134
141
  export type FooterProps = WFooterProps
135
142
  export type GridProps = WGridProps
136
143
  export type GroupProps = WGroupProps
137
144
  export type IconProps = WIconProps
145
+ export type ImageProps = WImageProps
146
+ export type ImageLoaderProps = WImageLoaderProps
138
147
  export type InputProps = WInputProps
139
148
  export type KbdProps = WKbdProps
140
149
  export type ListProps = WListProps
package/astro.js CHANGED
@@ -14,12 +14,15 @@ import CollapsibleComponent from './components/Collapsible/Collapsible.astro'
14
14
  import ConditionalWrapperComponent from './components/ConditionalWrapper/ConditionalWrapper.astro'
15
15
  import ContextMenuComponent from './components/ContextMenu/ContextMenu.astro'
16
16
  import CopyComponent from './components/Copy/Copy.astro'
17
+ import CounterComponent from './components/Counter/Counter.astro'
17
18
  import DataTableComponent from './components/DataTable/DataTable.astro'
18
19
  import FlexComponent from './components/Flex/Flex.astro'
19
20
  import FooterComponent from './components/Footer/Footer.astro'
20
21
  import GridComponent from './components/Grid/Grid.astro'
21
22
  import GroupComponent from './components/Group/Group.astro'
22
23
  import IconComponent from './components/Icon/Icon.astro'
24
+ import ImageComponent from './components/Image/Image.astro'
25
+ import ImageLoaderComponent from './components/ImageLoader/ImageLoader.astro'
23
26
  import InputComponent from './components/Input/Input.astro'
24
27
  import KbdComponent from './components/Kbd/Kbd.astro'
25
28
  import ListComponent from './components/List/List.astro'
@@ -68,12 +71,15 @@ export const Collapsible = CollapsibleComponent
68
71
  export const ConditionalWrapper = ConditionalWrapperComponent
69
72
  export const ContextMenu = ContextMenuComponent
70
73
  export const Copy = CopyComponent
74
+ export const Counter = CounterComponent
71
75
  export const DataTable = DataTableComponent
72
76
  export const Flex = FlexComponent
73
77
  export const Footer = FooterComponent
74
78
  export const Grid = GridComponent
75
79
  export const Group = GroupComponent
76
80
  export const Icon = IconComponent
81
+ export const Image = ImageComponent
82
+ export const ImageLoader = ImageLoaderComponent
77
83
  export const Input = InputComponent
78
84
  export const Kbd = KbdComponent
79
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}
@@ -10,6 +10,7 @@ const {
10
10
  hover,
11
11
  small,
12
12
  rounded,
13
+ transparent,
13
14
  className,
14
15
  ...rest
15
16
  } = Astro.props
@@ -20,6 +21,7 @@ const classes = [
20
21
  hover && styles.hover,
21
22
  small && styles.small,
22
23
  rounded && styles.round,
24
+ transparent && styles.transparent,
23
25
  className
24
26
  ]
25
27
  ---
@@ -10,6 +10,7 @@
10
10
  hover,
11
11
  small,
12
12
  rounded,
13
+ transparent,
13
14
  className,
14
15
  onClick,
15
16
  children,
@@ -22,6 +23,7 @@
22
23
  (onClick || hover) && styles.hover,
23
24
  small && styles.small,
24
25
  rounded && styles.round,
26
+ transparent && styles.transparent,
25
27
  className
26
28
  ])
27
29
  </script>
@@ -11,6 +11,7 @@ const Badge = ({
11
11
  hover,
12
12
  small,
13
13
  rounded,
14
+ transparent,
14
15
  className,
15
16
  children,
16
17
  ...rest
@@ -21,6 +22,7 @@ const Badge = ({
21
22
  (onClick || hover) && styles.hover,
22
23
  small && styles.small,
23
24
  rounded && styles.round,
25
+ transparent && styles.transparent,
24
26
  className
25
27
  ])
26
28
 
@@ -91,4 +91,30 @@
91
91
  &.round {
92
92
  @include border-radius(lg);
93
93
  }
94
+
95
+ &.transparent {
96
+ @include background(transparent);
97
+ @include typography(primary);
98
+ @include spacing(p0);
99
+
100
+ &.secondary {
101
+ @include typography(primary-20);
102
+ }
103
+
104
+ &.info {
105
+ @include typography(info);
106
+ }
107
+
108
+ &.success {
109
+ @include typography(success);
110
+ }
111
+
112
+ &.warning {
113
+ @include typography(warning);
114
+ }
115
+
116
+ &.alert {
117
+ @include typography(alert);
118
+ }
119
+ }
94
120
  }
@@ -12,6 +12,7 @@ export type BadgeProps = {
12
12
  hover?: boolean
13
13
  small?: boolean
14
14
  rounded?: boolean
15
+ transparent?: boolean
15
16
  className?: string
16
17
  [key: string]: any
17
18
  }
@@ -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
 
@@ -0,0 +1,164 @@
1
+ ---
2
+ import type { CounterProps } from './counter'
3
+
4
+ import Icon from '../Icon/Icon.astro'
5
+
6
+ import minusIcon from '../../icons/minus.svg?raw'
7
+ import plusIcon from '../../icons/plus.svg?raw'
8
+
9
+ import styles from './counter.module.scss'
10
+
11
+ interface Props extends CounterProps {}
12
+
13
+ const {
14
+ type = 'compact',
15
+ theme,
16
+ rounded,
17
+ minIcon,
18
+ maxIcon,
19
+ className,
20
+ width,
21
+ value = 0,
22
+ disabled,
23
+ ...rest
24
+ } = Astro.props
25
+
26
+ const classes = [
27
+ styles.counter,
28
+ styles[type],
29
+ theme && styles[theme],
30
+ rounded && styles.rounded,
31
+ className
32
+ ]
33
+
34
+ const subtractIcon = minIcon || minusIcon
35
+ const addIcon = maxIcon || plusIcon
36
+
37
+ const styleVariable = width
38
+ ? `--w-counter-width: ${width};`
39
+ : null
40
+ ---
41
+
42
+ <div class:list={classes} data-id="w-counter" style={styleVariable}>
43
+ <button data-id="w-counter-min" disabled={disabled}>
44
+ <Fragment>
45
+ {subtractIcon.startsWith('<svg')
46
+ ? <Fragment set:html={subtractIcon} />
47
+ : <Icon type={subtractIcon} />
48
+ }
49
+ </Fragment>
50
+ </button>
51
+ <input
52
+ type="number"
53
+ value={value}
54
+ disabled={disabled}
55
+ {...rest}
56
+ />
57
+ <button data-id="w-counter-max" disabled={disabled}>
58
+ <Fragment>
59
+ {addIcon.startsWith('<svg')
60
+ ? <Fragment set:html={addIcon} />
61
+ : <Icon type={addIcon} />
62
+ }
63
+ </Fragment>
64
+ </button>
65
+ </div>
66
+
67
+ <script>
68
+ import { off, on } from '../../utils/DOMUtils'
69
+ import { dispatch } from '../../utils/event'
70
+
71
+ const addEventListeners = () => {
72
+ const buttonSelector = '[data-id="w-counter"] button'
73
+ const inputSelector = '[data-id="w-counter"] input'
74
+ const eventName = 'counterOnChange'
75
+
76
+ let intervalId: ReturnType<typeof setTimeout>
77
+ let timeoutId: ReturnType<typeof setTimeout>
78
+ let longPressDelay = 500
79
+ let isKeyDown = false
80
+
81
+ const updateValue = (input: HTMLInputElement, min?: boolean) => {
82
+ const step = input.step ? Number(input.step) : 1
83
+ const direction = min ? -1 : 1
84
+ const newValue = Number(input.value) + (direction * step)
85
+
86
+ if ((input.min && newValue < Number(input.min)) || (input.max && newValue > Number(input.max))) {
87
+ return
88
+ }
89
+
90
+ input.value = String(newValue)
91
+
92
+ dispatch(eventName, {
93
+ name: input.name,
94
+ value: newValue
95
+ })
96
+ }
97
+
98
+ const startHold = (event: Event) => {
99
+ const target = event.currentTarget
100
+
101
+ if (target instanceof HTMLButtonElement && target.parentElement) {
102
+ const input = target.parentElement.querySelector('input') as HTMLInputElement
103
+ const min = target.dataset.id === 'w-counter-min'
104
+
105
+ updateValue(input, min)
106
+
107
+ timeoutId = setTimeout(function repeat() {
108
+ updateValue(input, min)
109
+
110
+ longPressDelay = Math.max(50, longPressDelay * 0.8)
111
+
112
+ intervalId = setTimeout(repeat, longPressDelay)
113
+ }, 500)
114
+ }
115
+ }
116
+
117
+ const stopHold = () => {
118
+ clearTimeout(timeoutId)
119
+ clearTimeout(intervalId)
120
+
121
+ isKeyDown = false
122
+ longPressDelay = 500
123
+ }
124
+
125
+ on(buttonSelector, 'mousedown', startHold, true)
126
+ on(buttonSelector, 'touchstart', startHold, true)
127
+
128
+ on(buttonSelector, 'mouseup', stopHold, true)
129
+ on(buttonSelector, 'mouseleave', stopHold, true)
130
+ on(buttonSelector, 'touchend', stopHold, true)
131
+ on(buttonSelector, 'touchcancel', stopHold, true)
132
+
133
+ on(buttonSelector, 'keydown', (event: KeyboardEvent) => {
134
+ if (event.key === 'Enter' && !isKeyDown) {
135
+ event.preventDefault()
136
+ startHold(event)
137
+
138
+ isKeyDown = true
139
+ }
140
+ }, true)
141
+
142
+ on(buttonSelector, 'keyup', (event: KeyboardEvent) => {
143
+ if (event.key === 'Enter') {
144
+ stopHold()
145
+ }
146
+ }, true)
147
+
148
+ on(inputSelector, 'input', (event: Event) => {
149
+ const target = event.target
150
+
151
+ if (target instanceof HTMLInputElement) {
152
+ dispatch(eventName, {
153
+ name: target.name,
154
+ value: Number(target.value)
155
+ })
156
+ }
157
+ }, true)
158
+ }
159
+
160
+ off(document, 'astro:after-swap', addEventListeners)
161
+ on(document, 'astro:after-swap', addEventListeners)
162
+
163
+ addEventListeners()
164
+ </script>