fluid-ui-svelte 0.3.2 → 0.3.3

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.
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import { type Snippet } from 'svelte';
5
5
 
6
6
  const {
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import type { Snippet } from 'svelte';
5
5
  import type { TransitionConfig } from 'svelte/transition';
6
6
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLImgAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
 
5
5
  const {
6
6
  class: className = '',
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLInputAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
4
- import { applyCharacterFilter } from '../utilities/applyCharacterFilter.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
+ import { applyCharacterFilter } from '../utilities/inputField.js';
5
5
 
6
6
  let {
7
7
  type = 'text',
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAnchorAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import type { Snippet } from 'svelte';
5
5
 
6
6
  let {
@@ -15,9 +15,6 @@
15
15
  } & HTMLAnchorAttributes = $props();
16
16
  </script>
17
17
 
18
- <a
19
- {...rest}
20
- class={mergeClasses(className, overrideDefaultStyling ? '' : 'fluid-link')}
21
- >
18
+ <a {...rest} class={mergeClasses(className, overrideDefaultStyling ? '' : 'fluid-link')}>
22
19
  {@render children()}</a
23
- >
20
+ >
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" generics="T">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import type { Snippet } from 'svelte';
5
5
 
6
6
  const {
@@ -36,4 +36,4 @@
36
36
  {@render itemTemplate(item)}
37
37
  </li>
38
38
  {/each}
39
- </svelte:element>
39
+ </svelte:element>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" generics="T,U,V">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import type { Snippet } from 'svelte';
5
5
 
6
6
  const {
@@ -39,12 +39,11 @@
39
39
  } & HTMLAttributes<HTMLTableElement> = $props();
40
40
  </script>
41
41
 
42
- <table
43
- class={mergeClasses(className, overrideDefaultStyling ? '' : 'fluid-table')}
44
- {...rest}
45
- >
42
+ <table class={mergeClasses(className, overrideDefaultStyling ? '' : 'fluid-table')} {...rest}>
46
43
  {#if caption}
47
- <caption class={mergeClasses(captionClass, overrideDefaultStyling ? '' : 'fluid-table-caption')}>
44
+ <caption
45
+ class={mergeClasses(captionClass, overrideDefaultStyling ? '' : 'fluid-table-caption')}
46
+ >
48
47
  {caption}
49
48
  </caption>
50
49
  {/if}
@@ -77,4 +76,4 @@
77
76
  {/each}
78
77
  </tr>
79
78
  </tfoot>
80
- </table>
79
+ </table>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import type { Snippet } from 'svelte';
5
5
 
6
6
  const {
@@ -1,18 +1,18 @@
1
1
  <script lang="ts">
2
2
  import { Container, Button } from '../base/index.js';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import { slide, type TransitionConfig } from 'svelte/transition';
5
5
  import type { Snippet } from 'svelte';
6
6
 
7
7
  const {
8
- variation = '',
8
+ variant = '',
9
9
  componentId,
10
10
  header,
11
11
  body,
12
12
  transitionFunction = slide,
13
13
  transitionDuration = 250
14
14
  }: {
15
- variation?: string;
15
+ variant?: string;
16
16
  componentId?: string;
17
17
  header: Snippet<[options: { isExpanded: boolean }]>;
18
18
  body: Snippet;
@@ -22,12 +22,12 @@
22
22
  const componentState = $state({ isExpanded: false });
23
23
  </script>
24
24
 
25
- <Container id={componentId} class={mergeClasses(variation, 'fluid-accordion-wrapper')}>
25
+ <Container id={componentId} class={mergeClasses(variant, 'fluid-accordion-wrapper')}>
26
26
  <Button
27
27
  onclick={async () => {
28
28
  componentState.isExpanded = !componentState.isExpanded;
29
29
  }}
30
- class={mergeClasses(variation, 'fluid-accordion-header')}
30
+ class={mergeClasses(variant, 'fluid-accordion-header')}
31
31
  overrideDefaultStyling
32
32
  >
33
33
  {@render header(componentState)}
@@ -36,7 +36,7 @@
36
36
  <Container
37
37
  transitionFn={transitionFunction}
38
38
  transitionParams={{ duration: transitionDuration }}
39
- class={mergeClasses(variation, 'fluid-accordion-body')}
39
+ class={mergeClasses(variant, 'fluid-accordion-body')}
40
40
  >
41
41
  {@render body()}
42
42
  </Container>
@@ -1,7 +1,7 @@
1
1
  import { type TransitionConfig } from 'svelte/transition';
2
2
  import type { Snippet } from 'svelte';
3
3
  type $$ComponentProps = {
4
- variation?: string;
4
+ variant?: string;
5
5
  componentId?: string;
6
6
  header: Snippet<[options: {
7
7
  isExpanded: boolean;
@@ -5,7 +5,7 @@
5
5
  generateCalendarCellStyles,
6
6
  generateDaysOfTheMonthFromDate
7
7
  } from '../utilities/calendar.js';
8
- import { mergeClasses } from '../utilities/mergeClasses.js';
8
+ import { mergeClasses } from '../utilities/common.js';
9
9
 
10
10
  let {
11
11
  variant = '',
@@ -1,13 +1,21 @@
1
1
  <script lang="ts" generics="T">
2
2
  import { Container } from '../base/index.js';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
4
- import { scrollToIndex, getSwipeDirection } from '../utilities/carousel.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
+ import {
5
+ scrollToIndex,
6
+ handleTouchStart,
7
+ handleTouchMove,
8
+ handleTouchEnd,
9
+ type CarouselInternalState
10
+ } from '../utilities/carousel.js';
5
11
  import type { Snippet } from 'svelte';
6
12
 
7
13
  let {
8
14
  componentId = crypto.randomUUID(),
9
15
  variant = '',
10
16
  orientation = 'horizontal',
17
+ snapItems = true,
18
+ swipeable = true,
11
19
  activeIndex = $bindable(0),
12
20
  autoplay = false,
13
21
  autoplayDuration = 1000,
@@ -17,68 +25,20 @@
17
25
  componentId?: string;
18
26
  variant?: string;
19
27
  orientation?: 'horizontal' | 'vertical';
28
+ snapItems?: boolean;
29
+ swipeable?: boolean;
20
30
  activeIndex?: number;
21
31
  autoplay?: boolean;
22
32
  autoplayDuration?: number;
23
33
  items: Array<T>;
24
- itemTemplate: Snippet<[{ item: T; index: number }]>;
34
+ itemTemplate: Snippet<[{ item: T; index: number; internalState: CarouselInternalState }]>;
25
35
  } = $props();
26
36
 
27
- let touchStartX = 0;
28
- let touchStartY = 0;
29
-
30
- function handleTouchStart(e: TouchEvent) {
31
- e.stopPropagation();
32
- e.preventDefault();
33
- touchStartX = e.changedTouches[0].screenX;
34
- touchStartY = e.changedTouches[0].screenY;
35
- }
36
-
37
- function handleTouchMove(e: TouchEvent) {
38
- if (!touchStartX || !touchStartY) return;
39
-
40
- const touchCurrentX = e.changedTouches[0].screenX;
41
- const touchCurrentY = e.changedTouches[0].screenY;
42
-
43
- const diffX = Math.abs(touchCurrentX - touchStartX);
44
- const diffY = Math.abs(touchCurrentY - touchStartY);
45
-
46
- // If horizontal carousel and horizontal swipe is dominant
47
- if (orientation === 'horizontal') {
48
- if (diffX > diffY && diffX > 5) {
49
- e.preventDefault();
50
- e.stopPropagation();
51
- }
52
- } else {
53
- // Vertical carousel
54
- if (diffY > diffX && diffY > 5) {
55
- e.preventDefault();
56
- e.stopPropagation();
57
- }
58
- }
59
- }
60
-
61
- function handleTouchEnd(e: TouchEvent) {
62
- e.stopPropagation();
63
- e.preventDefault();
64
- const touchEndX = e.changedTouches[0].screenX;
65
- const touchEndY = e.changedTouches[0].screenY;
66
-
67
- const direction = getSwipeDirection(
68
- touchStartX,
69
- touchStartY,
70
- touchEndX,
71
- touchEndY,
72
- 50,
73
- orientation
74
- );
75
-
76
- if (direction === 'next') {
77
- activeIndex = Math.min(activeIndex + 1, items.length - 1);
78
- } else if (direction === 'prev') {
79
- activeIndex = Math.max(activeIndex - 1, 0);
80
- }
81
- }
37
+ const internalState = $state({
38
+ touchStart: 0,
39
+ initialScroll: 0,
40
+ movementDelta: 0
41
+ });
82
42
 
83
43
  $effect(() => {
84
44
  if (autoplay && items.length > 0) {
@@ -98,17 +58,21 @@
98
58
 
99
59
  <Container
100
60
  id={componentId}
101
- ontouchstart={handleTouchStart}
102
- ontouchmove={handleTouchMove}
103
- ontouchend={handleTouchEnd}
61
+ ontouchstart={swipeable ? (e) => handleTouchStart(e, orientation, internalState) : undefined}
62
+ ontouchmove={swipeable ? (e) => handleTouchMove(e, orientation, internalState) : undefined}
63
+ ontouchend={swipeable
64
+ ? () => {
65
+ activeIndex = handleTouchEnd(snapItems, activeIndex, items.length, internalState);
66
+ }
67
+ : undefined}
104
68
  class={mergeClasses(
105
- `fluid-carousel-container ${orientation === 'vertical' ? 'h-full snap-y flex-col overflow-y-auto' : 'flex snap-x overflow-x-auto'}`,
69
+ `fluid-carousel-container ${swipeable ? 'touch-none' : ''} ${orientation === 'vertical' ? 'h-full flex-col overflow-y-auto' : 'flex overflow-x-auto'}`,
106
70
  variant
107
71
  )}
108
72
  >
109
73
  {#each items as item, index}
110
74
  <Container class={mergeClasses('fluid-carousel-item', variant)} overrideDefaultStyling>
111
- {@render itemTemplate({ item, index })}
75
+ {@render itemTemplate({ item, index, internalState })}
112
76
  </Container>
113
77
  {/each}
114
78
  </Container>
@@ -1,9 +1,12 @@
1
+ import { type CarouselInternalState } from '../utilities/carousel.js';
1
2
  import type { Snippet } from 'svelte';
2
3
  declare function $$render<T>(): {
3
4
  props: {
4
5
  componentId?: string;
5
6
  variant?: string;
6
7
  orientation?: "horizontal" | "vertical";
8
+ snapItems?: boolean;
9
+ swipeable?: boolean;
7
10
  activeIndex?: number;
8
11
  autoplay?: boolean;
9
12
  autoplayDuration?: number;
@@ -11,6 +14,7 @@ declare function $$render<T>(): {
11
14
  itemTemplate: Snippet<[{
12
15
  item: T;
13
16
  index: number;
17
+ internalState: CarouselInternalState;
14
18
  }]>;
15
19
  };
16
20
  exports: {};
@@ -1,29 +1,32 @@
1
1
  <script lang="ts">
2
2
  import Container from '../base/Container.svelte';
3
3
  import Text from '../base/Text.svelte';
4
+ import { mergeClasses } from '../utilities/common.js';
4
5
 
5
6
  const {
6
- variation,
7
+ variant = '',
8
+ componentId,
7
9
  code = '',
8
10
  language = '',
9
11
  showLineNumbers = true
10
12
  }: {
11
- variation?: string;
13
+ variant?: string;
14
+ componentId?: string;
12
15
  code?: string;
13
16
  language?: string;
14
17
  showLineNumbers?: boolean;
15
18
  } = $props();
16
19
  </script>
17
20
 
18
- <Container class="fluid-code-block-container">
21
+ <Container id={componentId} class={mergeClasses(variant, 'fluid-code-block-container')}>
19
22
  {#each code.split('\n') as line, index}
20
- <Container class="fluid-code-block-row">
23
+ <Container class={mergeClasses(variant, 'fluid-code-block-row')}>
21
24
  {#if showLineNumbers}
22
- <Container class="fluid-code-block-index"
23
- ><Text class="select-none">{index}</Text></Container
24
- >
25
+ <Container class={mergeClasses(variant, 'fluid-code-block-index')}>
26
+ <Text class="select-none">{index}</Text>
27
+ </Container>
25
28
  {/if}
26
- <Container class="fluid-code-block-content">
29
+ <Container class={mergeClasses(variant, 'fluid-code-block-content')}>
27
30
  <Text type="pre">
28
31
  <Text type="code" class={'language-' + language}>{line}</Text>
29
32
  </Text>
@@ -1,5 +1,6 @@
1
1
  type $$ComponentProps = {
2
- variation?: string;
2
+ variant?: string;
3
+ componentId?: string;
3
4
  code?: string;
4
5
  language?: string;
5
6
  showLineNumbers?: boolean;
@@ -1,11 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { Container } from '../base/index.js';
3
- import { mergeClasses } from '../utilities/mergeClasses.js';
3
+ import { mergeClasses } from '../utilities/common.js';
4
4
  import { fade, fly, type TransitionConfig } from 'svelte/transition';
5
5
  import type { Snippet } from 'svelte';
6
6
 
7
7
  let {
8
- variation = '',
8
+ variant = '',
9
9
  componentId,
10
10
  isOpen = $bindable(false),
11
11
  position = 'left',
@@ -25,7 +25,7 @@
25
25
  position?: 'left' | 'right' | 'top' | 'bottom';
26
26
  closeOnBackdropClick?: boolean;
27
27
  scrollLock?: boolean;
28
- variation?: string;
28
+ variant?: string;
29
29
  componentId?: string;
30
30
  transitionFn?: (node: Element, params?: any) => TransitionConfig;
31
31
  transitionParams?: TransitionConfig & { x?: number; y?: number };
@@ -65,7 +65,7 @@
65
65
  {#if isOpen}
66
66
  <Container
67
67
  id={componentId}
68
- class={mergeClasses(variation, 'fluid-drawer-container')}
68
+ class={mergeClasses(variant, 'fluid-drawer-container')}
69
69
  transitionFn={backdropTransitionFn}
70
70
  transitionParams={backdropTransitionParams}
71
71
  onclick={async () => {
@@ -77,7 +77,7 @@
77
77
  role="dialog"
78
78
  aria-modal="true"
79
79
  onclick={(event) => event.stopPropagation()}
80
- class={mergeClasses(variation, `fluid-drawer-panel ${positionClasses[position]}`)}
80
+ class={mergeClasses(variant, `fluid-drawer-panel ${positionClasses[position]}`)}
81
81
  {transitionFn}
82
82
  {transitionParams}
83
83
  overrideDefaultStyling
@@ -5,7 +5,7 @@ type $$ComponentProps = {
5
5
  position?: 'left' | 'right' | 'top' | 'bottom';
6
6
  closeOnBackdropClick?: boolean;
7
7
  scrollLock?: boolean;
8
- variation?: string;
8
+ variant?: string;
9
9
  componentId?: string;
10
10
  transitionFn?: (node: Element, params?: any) => TransitionConfig;
11
11
  transitionParams?: TransitionConfig & {
@@ -21,6 +21,6 @@
21
21
  <meta name="description" content={description} />
22
22
  </svelte:head>
23
23
 
24
- <Container class="fluid-page" type="section">
24
+ <Container class="fluid-page" type="main" {...rest}>
25
25
  {@render children?.()}
26
26
  </Container>
@@ -1,2 +1,9 @@
1
+ export type CarouselInternalState = {
2
+ touchStart: number;
3
+ initialScroll: number;
4
+ movementDelta: number;
5
+ };
1
6
  export declare function scrollToIndex(componentId: string, activeIndex: number, totalItems: number, orientation?: 'horizontal' | 'vertical'): void;
2
- export declare function getSwipeDirection(touchStartX: number, touchStartY: number, touchEndX: number, touchEndY: number, threshold?: number, orientation?: 'horizontal' | 'vertical'): 'next' | 'prev' | null;
7
+ export declare function handleTouchStart(e: TouchEvent, orientation: 'horizontal' | 'vertical', internalState: CarouselInternalState): void;
8
+ export declare function handleTouchMove(e: TouchEvent, orientation: 'horizontal' | 'vertical', internalState: CarouselInternalState): void;
9
+ export declare function handleTouchEnd(snapItems: boolean, activeIndex: number, totalItems: number, internalState: CarouselInternalState): number;
@@ -10,18 +10,35 @@ export function scrollToIndex(componentId, activeIndex, totalItems, orientation
10
10
  top: orientation === 'vertical' ? targetPosition : 0
11
11
  });
12
12
  }
13
- export function getSwipeDirection(touchStartX, touchStartY, touchEndX, touchEndY, threshold = 50, orientation = 'horizontal') {
14
- const diffX = touchStartX - touchEndX;
15
- const diffY = touchStartY - touchEndY;
16
- if (orientation === 'horizontal') {
17
- if (Math.abs(diffX) > threshold) {
18
- return diffX > 0 ? 'next' : 'prev';
19
- }
13
+ export function handleTouchStart(e, orientation, internalState) {
14
+ e.stopPropagation();
15
+ const element = e.currentTarget;
16
+ internalState.touchStart =
17
+ orientation === 'vertical' ? e.touches[0].clientY : e.touches[0].clientX;
18
+ internalState.initialScroll = orientation === 'vertical' ? element.scrollTop : element.scrollLeft;
19
+ internalState.movementDelta = 0;
20
+ }
21
+ export function handleTouchMove(e, orientation, internalState) {
22
+ const element = e.currentTarget;
23
+ const currentTouch = orientation === 'vertical' ? e.touches[0].clientY : e.touches[0].clientX;
24
+ internalState.movementDelta = internalState.touchStart - currentTouch;
25
+ element.scrollTo({
26
+ left: orientation === 'vertical' ? 0 : internalState.initialScroll + internalState.movementDelta,
27
+ top: orientation === 'vertical' ? internalState.initialScroll + internalState.movementDelta : 0,
28
+ behavior: 'instant'
29
+ });
30
+ }
31
+ export function handleTouchEnd(snapItems, activeIndex, totalItems, internalState) {
32
+ if (!snapItems)
33
+ return activeIndex;
34
+ const threshold = 50;
35
+ let newIndex = activeIndex;
36
+ if (internalState.movementDelta > threshold && activeIndex < totalItems - 1) {
37
+ newIndex = activeIndex + 1;
20
38
  }
21
- else {
22
- if (Math.abs(diffY) > threshold) {
23
- return diffY > 0 ? 'next' : 'prev';
24
- }
39
+ else if (internalState.movementDelta < -threshold && activeIndex > 0) {
40
+ newIndex = activeIndex - 1;
25
41
  }
26
- return null;
42
+ internalState.movementDelta = 0;
43
+ return newIndex;
27
44
  }
@@ -10,6 +10,6 @@ export declare const codeBlockContents: {
10
10
  calendarSixMonth: string;
11
11
  calendarRange: string;
12
12
  calendarMulti: string;
13
- carouselSlide: string;
14
- carouselFade: string;
13
+ carouselInteractive: string;
14
+ carouselUsage: string;
15
15
  };
@@ -246,38 +246,30 @@ const calendarMulti = `<script>
246
246
  />
247
247
  </Container>
248
248
  </Container>`;
249
- const carouselSlide = `<script>
249
+ const carouselInteractive = `<script>
250
250
  import { Carousel } from 'fluid-ui-svelte/components';
251
- import { Image } from 'fluid-ui-svelte/base';
252
-
253
- const items = [
254
- { src: 'https://via.placeholder.com/800x400?text=Slide+1', alt: 'Slide 1' },
255
- { src: 'https://via.placeholder.com/800x400?text=Slide+2', alt: 'Slide 2' },
256
- { src: 'https://via.placeholder.com/800x400?text=Slide+3', alt: 'Slide 3' }
257
- ];
251
+ const items = [...];
258
252
  </script>
259
253
 
260
- <Carousel {items} type="slide" loop>
261
- {#snippet children(item)}
262
- <Image src={item.src} alt={item.alt} class="w-full h-64 object-cover" />
254
+ <Carousel {items} componentId="demo-id">
255
+ {#snippet itemTemplate({ item })}
256
+ <div class="h-64 flex items-center justify-center">
257
+ {item.text}
258
+ </div>
263
259
  {/snippet}
264
260
  </Carousel>`;
265
- const carouselFade = `<script>
261
+ const carouselUsage = `<script>
266
262
  import { Carousel } from 'fluid-ui-svelte/components';
267
- import { Text } from 'fluid-ui-svelte/base';
268
-
269
- const quotes = [
270
- { text: "Innovation is key.", author: "Tech CEO" },
271
- { text: "Design is intelligence made visible.", author: "Designer" },
272
- { text: "Simplicity is the ultimate sophistication.", author: "Da Vinci" }
273
- ];
263
+ const items = [...];
264
+ let activeIndex = $state(0);
274
265
  </script>
275
266
 
276
- <Carousel {items: quotes} type="fade" autoplay autoplayInterval={4000}>
277
- {#snippet children(quote)}
278
- <div class="h-64 flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-800 p-8 text-center">
279
- <Text type="h3" class="text-2xl font-bold mb-4">"{quote.text}"</Text>
280
- <Text class="text-neutral-500">- {quote.author}</Text>
267
+ <Carousel {items} bind:activeIndex>
268
+ {#snippet itemTemplate({ item, index, internalState })}
269
+ <div class="h-64">
270
+ {item.name}
271
+ <!-- Delta available for custom animations -->
272
+ <span>Delta: {internalState.movementDelta}</span>
281
273
  </div>
282
274
  {/snippet}
283
275
  </Carousel>`;
@@ -293,6 +285,6 @@ export const codeBlockContents = {
293
285
  calendarSixMonth,
294
286
  calendarRange,
295
287
  calendarMulti,
296
- carouselSlide,
297
- carouselFade
288
+ carouselInteractive,
289
+ carouselUsage
298
290
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluid-ui-svelte",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "author": {
5
5
  "name": "Emre Ayaz",
6
6
  "email": "ayazthemre@gmail.com"
File without changes