@marianmeres/stuic 2.22.0 → 2.25.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.
@@ -65,6 +65,8 @@ export interface PopoverOptions {
65
65
  onHide?: () => void;
66
66
  /** Debug mode */
67
67
  debug?: boolean;
68
+ /** Programmatically control open state (reactive) */
69
+ open?: boolean;
68
70
  }
69
71
  /**
70
72
  * A Svelte action that displays a popover anchored to an element using CSS Anchor Positioning.
@@ -155,6 +155,7 @@ export function popover(anchorEl, fn) {
155
155
  let hideTimer = null;
156
156
  let isVisible = false;
157
157
  let do_debug = false;
158
+ let prevOpen = undefined;
158
159
  // Unique identifiers
159
160
  const rnd = Math.random().toString(36).slice(2);
160
161
  const id = `popover-${rnd}`;
@@ -421,6 +422,17 @@ export function popover(anchorEl, fn) {
421
422
  }
422
423
  // Note: trigger mode change while visible is not fully handled
423
424
  // User should close and reopen for trigger mode change to take effect
425
+ // Handle programmatic open/close
426
+ const openValue = opts.open;
427
+ if (openValue !== undefined && openValue !== prevOpen) {
428
+ if (openValue && !isVisible) {
429
+ show();
430
+ }
431
+ else if (!openValue && isVisible) {
432
+ hide();
433
+ }
434
+ }
435
+ prevOpen = openValue;
424
436
  });
425
437
  // Event listeners effect
426
438
  $effect(() => {
@@ -0,0 +1,127 @@
1
+ <script lang="ts" module>
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+
4
+ export interface Props extends Omit<
5
+ HTMLAttributes<HTMLDivElement>,
6
+ "children" | "class"
7
+ > {
8
+ /** Shape variant */
9
+ variant?: "text" | "circle" | "rectangle";
10
+ /** Width (e.g., "100%", "200px") */
11
+ width?: string;
12
+ /** Height (e.g., "1rem", "40px") */
13
+ height?: string;
14
+ /** Shorthand for circle size (sets both width & height) */
15
+ size?: string;
16
+ /** Number of text lines (for text variant) */
17
+ lines?: number;
18
+ /** Gap between lines (for text variant) */
19
+ gap?: string;
20
+ /** Last line width (for text variant) */
21
+ lastLineWidth?: string;
22
+ /** Animation style */
23
+ animation?: "shimmer" | "pulse" | "none";
24
+ /** Animation duration */
25
+ duration?: string;
26
+ /** Border radius (boolean for default, string for custom) */
27
+ rounded?: boolean | string;
28
+ /** Accessibility label */
29
+ ariaLabel?: string;
30
+ /** Bindable element reference */
31
+ el?: HTMLDivElement;
32
+ /** CSS class */
33
+ class?: string | string[];
34
+ }
35
+ </script>
36
+
37
+ <script lang="ts">
38
+ import "./index.css";
39
+ import { twMerge } from "../../utils/tw-merge.js";
40
+ import { prefersReducedMotion } from "../../utils/prefers-reduced-motion.svelte.js";
41
+
42
+ let {
43
+ variant = "rectangle",
44
+ width,
45
+ height,
46
+ size,
47
+ lines = 1,
48
+ gap = "0.5rem",
49
+ lastLineWidth = "75%",
50
+ animation = "shimmer",
51
+ duration = "1.5s",
52
+ rounded = true,
53
+ ariaLabel,
54
+ el = $bindable(),
55
+ class: classProp = "",
56
+ ...restProps
57
+ }: Props = $props();
58
+
59
+ const reduceMotion = prefersReducedMotion();
60
+
61
+ const effectiveAnimation = $derived(reduceMotion.current ? "none" : animation);
62
+
63
+ const baseClass = $derived(
64
+ twMerge(
65
+ "block bg-neutral-200 dark:bg-neutral-700",
66
+ effectiveAnimation === "shimmer" && "stuic-skeleton-shimmer",
67
+ effectiveAnimation === "pulse" && "stuic-skeleton-pulse",
68
+ variant === "circle" && "stuic-skeleton-circle",
69
+ rounded === true && variant !== "circle" && "rounded",
70
+ classProp
71
+ )
72
+ );
73
+
74
+ const baseStyle = $derived.by(() => {
75
+ const styles: string[] = [];
76
+ if (duration) styles.push(`--skeleton-duration: ${duration}`);
77
+ if (variant === "circle" && size) {
78
+ styles.push(`width: ${size}`, `height: ${size}`);
79
+ } else {
80
+ if (width) styles.push(`width: ${width}`);
81
+ if (height) styles.push(`height: ${height}`);
82
+ }
83
+ if (typeof rounded === "string") {
84
+ styles.push(`border-radius: ${rounded}`);
85
+ }
86
+ return styles.join("; ");
87
+ });
88
+ </script>
89
+
90
+ {#if variant === "text" && lines > 1}
91
+ <div
92
+ bind:this={el}
93
+ role="status"
94
+ aria-busy="true"
95
+ aria-label={ariaLabel}
96
+ class="stuic-skeleton-text-container"
97
+ style:gap
98
+ {...restProps}
99
+ >
100
+ {#each Array(lines) as _, i}
101
+ {@const isLast = i === lines - 1}
102
+ <div
103
+ class={baseClass}
104
+ style="{baseStyle}; width: {isLast
105
+ ? lastLineWidth
106
+ : width || '100%'}; height: {height || '1rem'}"
107
+ ></div>
108
+ {/each}
109
+ {#if ariaLabel}
110
+ <span class="sr-only">{ariaLabel}</span>
111
+ {/if}
112
+ </div>
113
+ {:else}
114
+ <div
115
+ bind:this={el}
116
+ role="status"
117
+ aria-busy="true"
118
+ aria-label={ariaLabel}
119
+ class={baseClass}
120
+ style={baseStyle}
121
+ {...restProps}
122
+ >
123
+ {#if ariaLabel}
124
+ <span class="sr-only">{ariaLabel}</span>
125
+ {/if}
126
+ </div>
127
+ {/if}
@@ -0,0 +1,33 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "class"> {
3
+ /** Shape variant */
4
+ variant?: "text" | "circle" | "rectangle";
5
+ /** Width (e.g., "100%", "200px") */
6
+ width?: string;
7
+ /** Height (e.g., "1rem", "40px") */
8
+ height?: string;
9
+ /** Shorthand for circle size (sets both width & height) */
10
+ size?: string;
11
+ /** Number of text lines (for text variant) */
12
+ lines?: number;
13
+ /** Gap between lines (for text variant) */
14
+ gap?: string;
15
+ /** Last line width (for text variant) */
16
+ lastLineWidth?: string;
17
+ /** Animation style */
18
+ animation?: "shimmer" | "pulse" | "none";
19
+ /** Animation duration */
20
+ duration?: string;
21
+ /** Border radius (boolean for default, string for custom) */
22
+ rounded?: boolean | string;
23
+ /** Accessibility label */
24
+ ariaLabel?: string;
25
+ /** Bindable element reference */
26
+ el?: HTMLDivElement;
27
+ /** CSS class */
28
+ class?: string | string[];
29
+ }
30
+ import "./index.css";
31
+ declare const Skeleton: import("svelte").Component<Props, {}, "el">;
32
+ type Skeleton = ReturnType<typeof Skeleton>;
33
+ export default Skeleton;
@@ -0,0 +1,62 @@
1
+ /* Define CSS custom properties for use in gradients */
2
+ :root {
3
+ --skeleton-base: #e5e5e5;
4
+ --skeleton-highlight: #f5f5f5;
5
+ }
6
+
7
+ .dark {
8
+ --skeleton-base: #404040;
9
+ --skeleton-highlight: #525252;
10
+ }
11
+
12
+ .stuic-skeleton-circle {
13
+ border-radius: 50%;
14
+ }
15
+
16
+ .stuic-skeleton-text-container {
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ /* Shimmer animation */
22
+ @keyframes skeleton-shimmer {
23
+ 0% {
24
+ background-position: -200% 0;
25
+ }
26
+ 100% {
27
+ background-position: 200% 0;
28
+ }
29
+ }
30
+
31
+ .stuic-skeleton-shimmer {
32
+ background: linear-gradient(
33
+ 90deg,
34
+ var(--skeleton-base) 25%,
35
+ var(--skeleton-highlight) 50%,
36
+ var(--skeleton-base) 75%
37
+ );
38
+ background-size: 200% 100%;
39
+ animation: skeleton-shimmer var(--skeleton-duration, 1.5s) ease-in-out infinite;
40
+ }
41
+
42
+ /* Pulse animation */
43
+ @keyframes skeleton-pulse {
44
+ 0%, 100% {
45
+ opacity: 1;
46
+ }
47
+ 50% {
48
+ opacity: 0.4;
49
+ }
50
+ }
51
+
52
+ .stuic-skeleton-pulse {
53
+ animation: skeleton-pulse var(--skeleton-duration, 1.5s) ease-in-out infinite;
54
+ }
55
+
56
+ /* Reduced motion */
57
+ @media (prefers-reduced-motion: reduce) {
58
+ .stuic-skeleton-shimmer,
59
+ .stuic-skeleton-pulse {
60
+ animation: none;
61
+ }
62
+ }
@@ -0,0 +1 @@
1
+ export { default as Skeleton, type Props as SkeletonProps } from "./Skeleton.svelte";
@@ -0,0 +1 @@
1
+ export { default as Skeleton } from "./Skeleton.svelte";
@@ -97,7 +97,7 @@
97
97
  class={twMerge(
98
98
  "stuic-switch",
99
99
  `m-2
100
- relative inline-flex flex-shrink-0 items-center
100
+ relative inline-flex shrink-0 items-center
101
101
  rounded-full cursor-pointer
102
102
 
103
103
  transition-colors duration-100
package/dist/index.d.ts CHANGED
@@ -40,6 +40,7 @@ export * from "./components/Modal/index.js";
40
40
  export * from "./components/ModalDialog/index.js";
41
41
  export * from "./components/Notifications/index.js";
42
42
  export * from "./components/Progress/index.js";
43
+ export * from "./components/Skeleton/index.js";
43
44
  export * from "./components/SlidingPanels/index.js";
44
45
  export * from "./components/Spinner/index.js";
45
46
  export * from "./components/Switch/index.js";
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ export * from "./components/Modal/index.js";
41
41
  export * from "./components/ModalDialog/index.js";
42
42
  export * from "./components/Notifications/index.js";
43
43
  export * from "./components/Progress/index.js";
44
+ export * from "./components/Skeleton/index.js";
44
45
  export * from "./components/SlidingPanels/index.js";
45
46
  export * from "./components/Spinner/index.js";
46
47
  export * from "./components/Switch/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "2.22.0",
3
+ "version": "2.25.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",