@marianmeres/stuic 2.56.0 → 2.58.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.
- package/dist/components/Avatar/Avatar.svelte +5 -4
- package/dist/components/Avatar/Avatar.svelte.d.ts +1 -1
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.svelte +65 -13
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +1 -1
- package/dist/components/DropdownMenu/index.css +35 -1
- package/dist/components/KbdShortcut/KbdShortcut.svelte +1 -1
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/utils/breakpoint.svelte.js +7 -2
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
/** Optional string for color hash calculation (e.g., email, user ID). Falls back to `initials` */
|
|
23
23
|
hashSource?: string;
|
|
24
24
|
/** Size preset or custom Tailwind size class */
|
|
25
|
-
size?: "sm" | "md" | "lg" | "xl" | string;
|
|
25
|
+
size?: "sm" | "md" | "lg" | "xl" | "2xl" | string;
|
|
26
26
|
/** Click handler - when provided, renders as a button */
|
|
27
27
|
onclick?: (event: MouseEvent) => void;
|
|
28
28
|
/** Background color (Tailwind class). Ignored if autoColor=true */
|
|
@@ -62,9 +62,10 @@
|
|
|
62
62
|
|
|
63
63
|
const SIZE_PRESETS: Record<string, { container: string; icon: number }> = {
|
|
64
64
|
sm: { container: "size-8 text-xs", icon: 16 },
|
|
65
|
-
md: { container: "size-10 text-
|
|
66
|
-
lg: { container: "size-
|
|
67
|
-
xl: { container: "size-
|
|
65
|
+
md: { container: "size-10 text-base", icon: 20 },
|
|
66
|
+
lg: { container: "size-12 text-lg", icon: 28 },
|
|
67
|
+
xl: { container: "size-14 text-xl", icon: 32 },
|
|
68
|
+
"2xl": { container: "size-16 text-2xl", icon: 36 },
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
// Extract initials from input string (email, name, or raw initials)
|
|
@@ -23,7 +23,7 @@ export interface Props {
|
|
|
23
23
|
/** Optional string for color hash calculation (e.g., email, user ID). Falls back to `initials` */
|
|
24
24
|
hashSource?: string;
|
|
25
25
|
/** Size preset or custom Tailwind size class */
|
|
26
|
-
size?: "sm" | "md" | "lg" | "xl" | string;
|
|
26
|
+
size?: "sm" | "md" | "lg" | "xl" | "2xl" | string;
|
|
27
27
|
/** Click handler - when provided, renders as a button */
|
|
28
28
|
onclick?: (event: MouseEvent) => void;
|
|
29
29
|
/** Background color (Tailwind class). Ignored if autoColor=true */
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
border-button-group-border dark:border-button-group-border-dark
|
|
102
102
|
flex justify-between
|
|
103
103
|
|
|
104
|
-
focus-within:border-button-group-accent
|
|
104
|
+
focus-within:border-button-group-accent focus-within:dark:border-button-group-accent-dark
|
|
105
105
|
focus-within:ring-button-group-accent/20 focus-within:dark:ring-button-group-accent-dark/20
|
|
106
106
|
focus-within:ring-4
|
|
107
107
|
`;
|
|
@@ -210,9 +210,9 @@
|
|
|
210
210
|
text-neutral-900 dark:text-neutral-100
|
|
211
211
|
border-neutral-200 dark:border-neutral-700
|
|
212
212
|
hover:brightness-95 dark:hover:brightness-110
|
|
213
|
-
focus-visible:outline-2 focus-visible:outline-offset-2
|
|
214
213
|
cursor-pointer
|
|
215
|
-
|
|
214
|
+
`;
|
|
215
|
+
// focus-visible:outline-2 focus-visible:outline-offset-2
|
|
216
216
|
|
|
217
217
|
export const DROPDOWN_MENU_DROPDOWN_CLASSES = `
|
|
218
218
|
stuic-dropdown-menu-dropdown
|
|
@@ -271,11 +271,12 @@
|
|
|
271
271
|
import Thc from "../Thc/Thc.svelte";
|
|
272
272
|
import "./index.css";
|
|
273
273
|
import { BodyScroll } from "../../utils/body-scroll-locker.js";
|
|
274
|
+
import { waitForTwoRepaints } from "../../utils/paint.js";
|
|
274
275
|
|
|
275
276
|
let {
|
|
276
277
|
items,
|
|
277
278
|
isOpen = $bindable(false),
|
|
278
|
-
position = "bottom-span-
|
|
279
|
+
position = "bottom-span-right",
|
|
279
280
|
offset = "0.25rem",
|
|
280
281
|
maxHeight = "300px",
|
|
281
282
|
closeOnSelect = true,
|
|
@@ -314,7 +315,14 @@
|
|
|
314
315
|
let wrapperEl: HTMLDivElement = $state()!;
|
|
315
316
|
let activeItemEl: HTMLButtonElement | undefined = $state();
|
|
316
317
|
const reducedMotion = prefersReducedMotion();
|
|
317
|
-
|
|
318
|
+
|
|
319
|
+
// Runtime overflow detection state
|
|
320
|
+
let runtimeFallback = $state(false);
|
|
321
|
+
let switchingToFallback = false; // Non-reactive flag to prevent recursion
|
|
322
|
+
|
|
323
|
+
const isSupported = $derived(
|
|
324
|
+
!forceFallback && !runtimeFallback && isAnchorPositioningSupported()
|
|
325
|
+
);
|
|
318
326
|
|
|
319
327
|
// Track expanded sections (independent toggle - multiple can be open)
|
|
320
328
|
let expandedSections = $state<Set<string | number>>(new Set());
|
|
@@ -380,6 +388,50 @@
|
|
|
380
388
|
}
|
|
381
389
|
});
|
|
382
390
|
|
|
391
|
+
// Reset runtime fallback when menu closes
|
|
392
|
+
$effect(() => {
|
|
393
|
+
if (!isOpen) {
|
|
394
|
+
// Unlock body scroll if we were in runtime fallback mode
|
|
395
|
+
// (must do this before resetting runtimeFallback, otherwise isSupported
|
|
396
|
+
// becomes true and the main body scroll effect skips the unlock)
|
|
397
|
+
if (runtimeFallback && !noScrollLock) {
|
|
398
|
+
BodyScroll.unlock();
|
|
399
|
+
}
|
|
400
|
+
runtimeFallback = false;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Runtime viewport overflow detection
|
|
405
|
+
$effect(() => {
|
|
406
|
+
if (!isOpen || !dropdownEl || forceFallback || runtimeFallback) return;
|
|
407
|
+
if (!isAnchorPositioningSupported()) return;
|
|
408
|
+
if (switchingToFallback) return;
|
|
409
|
+
|
|
410
|
+
const checkOverflow = async () => {
|
|
411
|
+
await waitForTwoRepaints();
|
|
412
|
+
if (!dropdownEl || !isOpen) return;
|
|
413
|
+
|
|
414
|
+
const rect = dropdownEl.getBoundingClientRect();
|
|
415
|
+
const viewportWidth = window.innerWidth;
|
|
416
|
+
const viewportHeight = window.innerHeight;
|
|
417
|
+
|
|
418
|
+
if (
|
|
419
|
+
rect.left < 0 ||
|
|
420
|
+
rect.right > viewportWidth ||
|
|
421
|
+
rect.top < 0 ||
|
|
422
|
+
rect.bottom > viewportHeight
|
|
423
|
+
) {
|
|
424
|
+
switchingToFallback = true;
|
|
425
|
+
runtimeFallback = true;
|
|
426
|
+
requestAnimationFrame(() => {
|
|
427
|
+
switchingToFallback = false;
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
checkOverflow();
|
|
433
|
+
});
|
|
434
|
+
|
|
383
435
|
// Scroll active item into view
|
|
384
436
|
$effect(() => {
|
|
385
437
|
if (isOpen && _navItems.active?.id) {
|
|
@@ -651,12 +703,12 @@
|
|
|
651
703
|
role="menuitem"
|
|
652
704
|
class={twMerge(
|
|
653
705
|
DROPDOWN_MENU_ITEM_CLASSES,
|
|
706
|
+
classItem,
|
|
707
|
+
item.class,
|
|
654
708
|
isActive && "bg-neutral-200 dark:bg-neutral-600",
|
|
655
709
|
isActive && classItemActive,
|
|
656
710
|
item.disabled && "opacity-50 cursor-not-allowed pointer-events-none",
|
|
657
|
-
item.disabled && classItemDisabled
|
|
658
|
-
classItem,
|
|
659
|
-
item.class
|
|
711
|
+
item.disabled && classItemDisabled
|
|
660
712
|
)}
|
|
661
713
|
onclick={() => selectItem(item)}
|
|
662
714
|
onmouseenter={() => navItems.setActive(item)}
|
|
@@ -709,12 +761,12 @@
|
|
|
709
761
|
class={twMerge(
|
|
710
762
|
DROPDOWN_MENU_ITEM_CLASSES,
|
|
711
763
|
"font-medium",
|
|
764
|
+
classExpandable,
|
|
765
|
+
item.class,
|
|
712
766
|
isExpandableActive && "bg-neutral-200 dark:bg-neutral-600",
|
|
713
767
|
isExpandableActive && classItemActive,
|
|
714
768
|
item.disabled && "opacity-50 cursor-not-allowed pointer-events-none",
|
|
715
|
-
item.disabled && classItemDisabled
|
|
716
|
-
classExpandable,
|
|
717
|
-
item.class
|
|
769
|
+
item.disabled && classItemDisabled
|
|
718
770
|
)}
|
|
719
771
|
onclick={() => toggleExpanded(item.id)}
|
|
720
772
|
onmouseenter={() =>
|
|
@@ -763,13 +815,13 @@
|
|
|
763
815
|
role="menuitem"
|
|
764
816
|
class={twMerge(
|
|
765
817
|
DROPDOWN_MENU_ITEM_CLASSES,
|
|
818
|
+
classItem,
|
|
819
|
+
childItem.class,
|
|
766
820
|
isChildActive && "bg-neutral-200 dark:bg-neutral-600",
|
|
767
821
|
isChildActive && classItemActive,
|
|
768
822
|
childItem.disabled &&
|
|
769
823
|
"opacity-50 cursor-not-allowed pointer-events-none",
|
|
770
|
-
childItem.disabled && classItemDisabled
|
|
771
|
-
classItem,
|
|
772
|
-
childItem.class
|
|
824
|
+
childItem.disabled && classItemDisabled
|
|
773
825
|
)}
|
|
774
826
|
onclick={() => selectItem(childItem)}
|
|
775
827
|
onmouseenter={() => navItems.setActive(childItem)}
|
|
@@ -143,7 +143,7 @@ export interface Props extends Omit<HTMLButtonAttributes, "children"> {
|
|
|
143
143
|
noScrollLock?: boolean;
|
|
144
144
|
}
|
|
145
145
|
export declare const DROPDOWN_MENU_BASE_CLASSES = "stuic-dropdown-menu relative inline-block";
|
|
146
|
-
export declare const DROPDOWN_MENU_TRIGGER_CLASSES = "\n\t\tinline-flex items-center justify-center gap-2\n\t\tpx-3 py-2\n\t\trounded-md border\n\t\tbg-white dark:bg-neutral-800\n\t\ttext-neutral-900 dark:text-neutral-100\n\t\tborder-neutral-200 dark:border-neutral-700\n\t\thover:brightness-95 dark:hover:brightness-110\n\t\
|
|
146
|
+
export declare const DROPDOWN_MENU_TRIGGER_CLASSES = "\n\t\tinline-flex items-center justify-center gap-2\n\t\tpx-3 py-2\n\t\trounded-md border\n\t\tbg-white dark:bg-neutral-800\n\t\ttext-neutral-900 dark:text-neutral-100\n\t\tborder-neutral-200 dark:border-neutral-700\n\t\thover:brightness-95 dark:hover:brightness-110\n\t\tcursor-pointer\n\t\t";
|
|
147
147
|
export declare const DROPDOWN_MENU_DROPDOWN_CLASSES = "\n\t\tstuic-dropdown-menu-dropdown\n\t\tbg-white dark:bg-neutral-800\n\t\ttext-neutral-900 dark:text-neutral-100\n\t\tborder border-neutral-200 dark:border-neutral-700\n\t\trounded-md shadow-sm\n\t\tp-1\n\t\toverflow-y-auto\n\t\tz-50\n\t\tmin-w-48\n\t";
|
|
148
148
|
export declare const DROPDOWN_MENU_ITEM_CLASSES = "\n\t\tw-full\n\t\tflex items-center gap-2\n\t\tpx-3 py-1.5\n\t\tmin-h-[44px]\n\t\ttext-left \n\t\trounded-md\n\t\tcursor-pointer\n\t\ttouch-action-manipulation\n\t\thover:bg-neutral-100 dark:hover:bg-neutral-700\n\t\tfocus:outline-none\n\t\tfocus-visible:bg-neutral-200 dark:focus-visible:bg-neutral-600\n\t";
|
|
149
149
|
export declare const DROPDOWN_MENU_DIVIDER_CLASSES = "\n\t\th-px my-1\n\t\tbg-neutral-200 dark:bg-neutral-700\n\t";
|
|
@@ -3,10 +3,44 @@
|
|
|
3
3
|
scrollbar-width: thin;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
@position-try --pop-top {
|
|
7
|
+
position-area: top; /* above, centered */
|
|
8
|
+
}
|
|
9
|
+
@position-try --pop-top-span-right {
|
|
10
|
+
position-area: top span-right; /* above, aligned to anchor's left edge */
|
|
11
|
+
}
|
|
12
|
+
@position-try --pop-top-span-left {
|
|
13
|
+
position-area: top span-left; /* above, aligned to anchor's right edge */
|
|
14
|
+
}
|
|
15
|
+
@position-try --pop-bottom {
|
|
16
|
+
position-area: bottom; /* below, centered */
|
|
17
|
+
}
|
|
18
|
+
@position-try --pop-bottom-span-right {
|
|
19
|
+
position-area: bottom span-right;
|
|
20
|
+
}
|
|
21
|
+
@position-try --pop-bottom-span-left {
|
|
22
|
+
position-area: bottom span-left;
|
|
23
|
+
}
|
|
24
|
+
@position-try --pop-left {
|
|
25
|
+
position-area: left;
|
|
26
|
+
}
|
|
27
|
+
@position-try --pop-right {
|
|
28
|
+
position-area: right;
|
|
29
|
+
}
|
|
30
|
+
|
|
6
31
|
/* CSS Anchor Positioning supported mode - flip only when viewport overflow */
|
|
7
32
|
@supports (anchor-name: --anchor) {
|
|
8
33
|
.stuic-dropdown-menu-dropdown {
|
|
9
|
-
position-try-fallbacks: flip-block, flip-inline;
|
|
34
|
+
/* position-try-fallbacks: flip-block, flip-inline; */
|
|
35
|
+
|
|
36
|
+
/* position-area is set via inline style based on position param */
|
|
37
|
+
|
|
38
|
+
/* fallbacks ensure popover stays within viewport */
|
|
39
|
+
/* order: try other bottom positions first, then top, then left/right */
|
|
40
|
+
position-try-fallbacks:
|
|
41
|
+
flip-inline, --pop-bottom-span-right, --pop-bottom-span-left, --pop-bottom,
|
|
42
|
+
flip-block, --pop-top-span-right, --pop-top-span-left, --pop-top,
|
|
43
|
+
--pop-left, --pop-right;
|
|
10
44
|
}
|
|
11
45
|
}
|
|
12
46
|
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -35,3 +35,4 @@ export { iconLucideCircle as iconCircle } from "@marianmeres/icons-fns/lucide/ic
|
|
|
35
35
|
export { iconLucideSquare as iconSquare } from "@marianmeres/icons-fns/lucide/iconLucideSquare.js";
|
|
36
36
|
export { iconLucideMenu as iconMenu } from "@marianmeres/icons-fns/lucide/iconLucideMenu.js";
|
|
37
37
|
export { iconLucideUser as iconUser } from "@marianmeres/icons-fns/lucide/iconLucideUser.js";
|
|
38
|
+
export { iconLucideEllipsisVertical as iconEllipsisVertical } from "@marianmeres/icons-fns/lucide/iconLucideEllipsisVertical.js";
|
package/dist/icons/index.js
CHANGED
|
@@ -39,3 +39,4 @@ export { iconLucideCircle as iconCircle } from "@marianmeres/icons-fns/lucide/ic
|
|
|
39
39
|
export { iconLucideSquare as iconSquare } from "@marianmeres/icons-fns/lucide/iconLucideSquare.js";
|
|
40
40
|
export { iconLucideMenu as iconMenu } from "@marianmeres/icons-fns/lucide/iconLucideMenu.js";
|
|
41
41
|
export { iconLucideUser as iconUser } from "@marianmeres/icons-fns/lucide/iconLucideUser.js";
|
|
42
|
+
export { iconLucideEllipsisVertical as iconEllipsisVertical } from "@marianmeres/icons-fns/lucide/iconLucideEllipsisVertical.js";
|
|
@@ -29,7 +29,8 @@ const _breakpoints = [
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
export class Breakpoint {
|
|
32
|
-
|
|
32
|
+
/** Singleton */
|
|
33
|
+
static #instance;
|
|
33
34
|
#bp = $derived.by(() => {
|
|
34
35
|
const w = innerWidth.current || 0;
|
|
35
36
|
return _breakpoints.reduce((m, [k, v]) => {
|
|
@@ -39,7 +40,11 @@ export class Breakpoint {
|
|
|
39
40
|
}, { current: null, sm: false, md: false, lg: false, xl: false, "2xl": false });
|
|
40
41
|
});
|
|
41
42
|
static get instance() {
|
|
42
|
-
return (Breakpoint.#
|
|
43
|
+
// return (Breakpoint.#instance ??= new Breakpoint();) // does not work with Svelte correctly...
|
|
44
|
+
if (!Breakpoint.#instance) {
|
|
45
|
+
Breakpoint.#instance = new Breakpoint();
|
|
46
|
+
}
|
|
47
|
+
return Breakpoint.#instance;
|
|
43
48
|
}
|
|
44
49
|
get current() {
|
|
45
50
|
return this.#bp.current;
|