@dryui/ui 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.
- package/dist/accordion/accordion-content.svelte +1 -1
- package/dist/alert/alert.svelte +1 -1
- package/dist/app-frame/app-frame.svelte +125 -0
- package/dist/app-frame/app-frame.svelte.d.ts +10 -0
- package/dist/app-frame/index.d.ts +8 -0
- package/dist/app-frame/index.js +1 -0
- package/dist/aurora/aurora.svelte +22 -59
- package/dist/beam/beam.svelte +28 -9
- package/dist/carousel/carousel-button-dots.svelte +25 -8
- package/dist/carousel/carousel-button-thumbnails.svelte +25 -8
- package/dist/carousel/carousel-root.svelte +115 -4
- package/dist/carousel/carousel-slide.svelte +5 -1
- package/dist/carousel/carousel-viewport.svelte +2 -0
- package/dist/carousel/context.svelte.d.ts +5 -0
- package/dist/chart/chart-bars.svelte +25 -16
- package/dist/chart/chart-donut.svelte +25 -16
- package/dist/chart/chart-root.svelte +134 -30
- package/dist/chart/chart-root.svelte.d.ts +1 -0
- package/dist/chart/context.svelte.d.ts +3 -1
- package/dist/chart/context.svelte.js +1 -0
- package/dist/chart/index.d.ts +1 -0
- package/dist/chromatic-shift/chromatic-shift.svelte +36 -9
- package/dist/collapsible/collapsible-content.svelte +2 -1
- package/dist/combobox/combobox-content.svelte +26 -44
- package/dist/combobox/combobox-content.svelte.d.ts +1 -1
- package/dist/combobox/combobox-input-root.svelte +7 -1
- package/dist/combobox/combobox-input.svelte +21 -8
- package/dist/country-select/country-select-button-input.svelte +124 -260
- package/dist/date-picker/datepicker-content.svelte +18 -26
- package/dist/date-picker/datepicker-content.svelte.d.ts +2 -1
- package/dist/date-picker/datepicker-input-root.svelte +7 -1
- package/dist/date-range-picker/date-range-picker-content.svelte +18 -14
- package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +2 -1
- package/dist/date-range-picker/date-range-picker-root.svelte +7 -1
- package/dist/displacement/displacement.svelte +16 -22
- package/dist/drag-and-drop/context.svelte.d.ts +2 -0
- package/dist/drag-and-drop/drag-and-drop-handle.svelte +34 -5
- package/dist/drag-and-drop/drag-and-drop-item.svelte +23 -14
- package/dist/drag-and-drop/drag-and-drop-root.svelte +60 -16
- package/dist/god-rays/god-rays.svelte +11 -0
- package/dist/gradient-mesh/gradient-mesh.svelte +27 -5
- package/dist/hover-card/context.svelte.d.ts +1 -10
- package/dist/hover-card/context.svelte.js +1 -2
- package/dist/hover-card/hover-card-content.svelte +41 -3
- package/dist/hover-card/hover-card-root.svelte +7 -55
- package/dist/hover-card/hover-card-trigger.svelte +79 -40
- package/dist/hover-card/hover-card-trigger.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal/motion.d.ts +1 -1
- package/dist/internal/motion.js +1 -1
- package/dist/marquee/marquee.svelte +56 -8
- package/dist/mega-menu/context.svelte.d.ts +2 -1
- package/dist/mega-menu/mega-menu-button-trigger.svelte +2 -14
- package/dist/mega-menu/mega-menu-item.svelte +3 -1
- package/dist/mega-menu/mega-menu-panel.svelte +35 -3
- package/dist/mega-menu/mega-menu-root.svelte +28 -13
- package/dist/menubar/context.svelte.d.ts +2 -2
- package/dist/menubar/menubar-button-trigger.svelte +5 -3
- package/dist/menubar/menubar-content.svelte +20 -12
- package/dist/menubar/menubar-root.svelte +4 -4
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +18 -55
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte.d.ts +1 -1
- package/dist/noise/noise.svelte +38 -6
- package/dist/notification-center/context.svelte.d.ts +0 -1
- package/dist/notification-center/notification-center-panel.svelte +54 -35
- package/dist/notification-center/notification-center-root.svelte +0 -1
- package/dist/notification-center/notification-center-trigger-button.svelte +1 -8
- package/dist/option-picker/option-picker-description.svelte +2 -2
- package/dist/option-picker/option-picker-item.svelte +10 -3
- package/dist/option-picker/option-picker-label.svelte +2 -2
- package/dist/option-picker/option-picker-preview.svelte +18 -13
- package/dist/phone-input/phone-input-select.svelte +2 -152
- package/dist/phone-input/phone-input-select.svelte.d.ts +1 -7
- package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +84 -29
- package/dist/scroll-area/scroll-area.svelte +16 -4
- package/dist/select/select-content.svelte +21 -31
- package/dist/select/select-content.svelte.d.ts +1 -1
- package/dist/select/select-root-input.svelte +7 -1
- package/dist/shimmer/shimmer.svelte +22 -12
- package/dist/tabs/tabs-list.svelte +12 -0
- package/dist/transfer/transfer-item.svelte +0 -3
- package/dist/transfer/transfer-list-input.svelte +1 -6
- package/dist/tree/context.svelte.d.ts +7 -1
- package/dist/tree/tree-item-children.svelte +12 -10
- package/dist/tree/tree-item-label.svelte +6 -17
- package/dist/tree/tree-item-label.svelte.d.ts +2 -2
- package/dist/tree/tree-item.svelte +28 -1
- package/dist/tree/tree-root.svelte +135 -59
- package/dist/typography/heading.svelte +1 -0
- package/package.json +8 -2
- package/skills/dryui/SKILL.md +1 -0
- package/dist/hover-card/hover-card-root.svelte.d.ts +0 -9
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { Button } from '../button/index.js';
|
|
4
5
|
import { getHoverCardCtx } from './context.svelte.js';
|
|
5
6
|
|
|
6
|
-
interface Props extends HTMLAttributes<
|
|
7
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
7
8
|
href?: string;
|
|
8
9
|
children: Snippet;
|
|
9
10
|
}
|
|
@@ -12,57 +13,95 @@
|
|
|
12
13
|
|
|
13
14
|
const ctx = getHoverCardCtx();
|
|
14
15
|
|
|
15
|
-
let
|
|
16
|
+
let triggerEl = $state<HTMLAnchorElement | HTMLButtonElement>();
|
|
16
17
|
|
|
17
18
|
$effect(() => {
|
|
18
|
-
if (
|
|
19
|
-
ctx.triggerEl =
|
|
19
|
+
if (triggerEl) {
|
|
20
|
+
ctx.triggerEl = triggerEl;
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
if (ctx.triggerEl === triggerEl) {
|
|
24
|
+
ctx.triggerEl = null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
20
27
|
}
|
|
21
28
|
});
|
|
29
|
+
|
|
30
|
+
function handlePointerEnter() {
|
|
31
|
+
ctx.triggerHovered = true;
|
|
32
|
+
ctx.show();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function handlePointerLeave() {
|
|
36
|
+
ctx.triggerHovered = false;
|
|
37
|
+
ctx.close();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleFocusIn() {
|
|
41
|
+
ctx.triggerFocused = true;
|
|
42
|
+
|
|
43
|
+
if (ctx.ignoreNextTriggerFocusOpen) {
|
|
44
|
+
ctx.ignoreNextTriggerFocusOpen = false;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ctx.showImmediate();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleFocusOut() {
|
|
52
|
+
ctx.triggerFocused = false;
|
|
53
|
+
ctx.close();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
57
|
+
if (event.key === 'Escape' && ctx.open) {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
ctx.forceClose();
|
|
60
|
+
triggerEl?.focus();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleRef(el: HTMLButtonElement | HTMLAnchorElement | null) {
|
|
65
|
+
triggerEl = el ?? undefined;
|
|
66
|
+
}
|
|
22
67
|
</script>
|
|
23
68
|
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
69
|
+
<span class={className} data-hover-card-trigger-shell>
|
|
70
|
+
<Button
|
|
71
|
+
ref={handleRef}
|
|
72
|
+
variant="link"
|
|
73
|
+
size="sm"
|
|
74
|
+
color="primary"
|
|
75
|
+
id={ctx.triggerId}
|
|
76
|
+
{href}
|
|
77
|
+
data-hover-card-trigger
|
|
78
|
+
aria-haspopup="dialog"
|
|
79
|
+
aria-controls={ctx.contentId}
|
|
80
|
+
aria-expanded={ctx.open}
|
|
81
|
+
data-state={ctx.open ? 'open' : 'closed'}
|
|
82
|
+
onpointerenter={handlePointerEnter}
|
|
83
|
+
onpointerleave={handlePointerLeave}
|
|
84
|
+
onfocusin={handleFocusIn}
|
|
85
|
+
onfocusout={handleFocusOut}
|
|
86
|
+
onkeydown={handleKeydown}
|
|
87
|
+
{...rest}
|
|
88
|
+
>
|
|
89
|
+
{@render children()}
|
|
90
|
+
</Button>
|
|
91
|
+
</span>
|
|
41
92
|
|
|
42
93
|
<style>
|
|
43
|
-
[data-hover-card-trigger] {
|
|
94
|
+
[data-hover-card-trigger-shell] {
|
|
95
|
+
--dry-btn-min-height: auto;
|
|
96
|
+
--dry-btn-padding-x: 0;
|
|
97
|
+
--dry-btn-padding-y: 0;
|
|
98
|
+
--dry-btn-font-size: inherit;
|
|
99
|
+
--dry-btn-radius: var(--dry-radius-sm);
|
|
44
100
|
display: inline-grid;
|
|
45
|
-
grid-auto-flow: column;
|
|
46
|
-
grid-auto-columns: max-content;
|
|
47
|
-
align-items: center;
|
|
48
|
-
gap: var(--dry-space-1);
|
|
49
|
-
color: var(--dry-color-text-brand);
|
|
50
|
-
text-decoration: underline;
|
|
51
|
-
text-decoration-color: var(--dry-color-stroke-brand);
|
|
52
|
-
text-decoration-thickness: 1px;
|
|
53
|
-
text-underline-offset: 0.18em;
|
|
54
101
|
cursor: help;
|
|
55
|
-
transition:
|
|
56
|
-
color var(--dry-duration-fast) var(--dry-ease-default),
|
|
57
|
-
text-decoration-color var(--dry-duration-fast) var(--dry-ease-default);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
[data-hover-card-trigger]:hover {
|
|
61
|
-
color: var(--dry-color-fill-brand-hover);
|
|
62
|
-
text-decoration-color: currentColor;
|
|
63
102
|
}
|
|
64
103
|
|
|
65
|
-
[data-hover-card-trigger]:focus-
|
|
104
|
+
[data-hover-card-trigger-shell]:focus-within {
|
|
66
105
|
outline: 2px solid var(--dry-color-stroke-focus);
|
|
67
106
|
outline-offset: 3px;
|
|
68
107
|
border-radius: var(--dry-radius-sm);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
interface Props extends HTMLAttributes<
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
4
4
|
href?: string;
|
|
5
5
|
children: Snippet;
|
|
6
6
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,8 @@ export type { BadgeProps, BadgeColor } from './badge/index.js';
|
|
|
98
98
|
export { Chart } from './chart/index.js';
|
|
99
99
|
export type { ChartDataPoint, ChartStackedDataPoint } from './chart/index.js';
|
|
100
100
|
export type { ChartRootProps, ChartBarsProps, ChartLineProps, ChartAreaProps, ChartDonutProps, ChartStackedBarProps, ChartHorizontalBarProps, ChartXAxisProps, ChartYAxisProps } from './chart/index.js';
|
|
101
|
+
export { AppFrame } from './app-frame/index.js';
|
|
102
|
+
export type { AppFrameProps } from './app-frame/index.js';
|
|
101
103
|
export { ChatThread } from './chat-thread/index.js';
|
|
102
104
|
export type { ChatThreadProps } from './chat-thread/index.js';
|
|
103
105
|
export { TypingIndicator } from './typing-indicator/index.js';
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,7 @@ export { Table } from './table/index.js';
|
|
|
50
50
|
export { Avatar } from './avatar/index.js';
|
|
51
51
|
export { Badge } from './badge/index.js';
|
|
52
52
|
export { Chart } from './chart/index.js';
|
|
53
|
+
export { AppFrame } from './app-frame/index.js';
|
|
53
54
|
export { ChatThread } from './chat-thread/index.js';
|
|
54
55
|
export { TypingIndicator } from './typing-indicator/index.js';
|
|
55
56
|
export { Icon } from './icon/index.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2 } from '@dryui/primitives/internal/motion';
|
|
1
|
+
export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2, observeInViewport, observePageVisibility, observeOffscreenState } from '@dryui/primitives/internal/motion';
|
|
2
2
|
export declare function supportsViewTransitions(): boolean;
|
|
3
3
|
export declare function supportsScrollTimelines(): boolean;
|
|
4
4
|
export declare function extractThemeColor(property: string, element?: HTMLElement): [number, number, number];
|
package/dist/internal/motion.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Re-export shared motion utilities from primitives (single source of truth)
|
|
2
|
-
export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2 } from '@dryui/primitives/internal/motion';
|
|
2
|
+
export { getReducedMotionPreference, observeReducedMotionPreference, supportsIntersectionObservers, supportsPointerTracking, supportsPropertyRegistration, registerPropertyOnce, supportsWebGL2, observeInViewport, observePageVisibility, observeOffscreenState } from '@dryui/primitives/internal/motion';
|
|
3
3
|
// UI-only motion utilities
|
|
4
4
|
export function supportsViewTransitions() {
|
|
5
5
|
return typeof document !== 'undefined' && 'startViewTransition' in document;
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
observeInViewport,
|
|
7
|
+
observePageVisibility,
|
|
8
|
+
observeReducedMotionPreference
|
|
9
|
+
} from '@dryui/primitives/internal/motion';
|
|
6
10
|
|
|
7
11
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
8
12
|
speed?: number;
|
|
@@ -24,18 +28,40 @@
|
|
|
24
28
|
...rest
|
|
25
29
|
}: Props = $props();
|
|
26
30
|
|
|
31
|
+
let rootEl: HTMLDivElement | undefined = $state();
|
|
27
32
|
let contentEl: HTMLDivElement | undefined = $state();
|
|
28
33
|
let contentSize = $state(0);
|
|
29
34
|
let prefersReducedMotion = $state(false);
|
|
35
|
+
let onScreen = $state(true);
|
|
36
|
+
let tabVisible = $state(true);
|
|
30
37
|
|
|
31
38
|
const isVertical = $derived(direction === 'up' || direction === 'down');
|
|
32
39
|
const duration = $derived(contentSize > 0 && speed > 0 ? contentSize / speed : 0);
|
|
40
|
+
const paused = $derived(!onScreen || !tabVisible);
|
|
33
41
|
|
|
34
|
-
onMount(() =>
|
|
35
|
-
observeReducedMotionPreference((matches) => {
|
|
42
|
+
onMount(() => {
|
|
43
|
+
const unsubscribeMotion = observeReducedMotionPreference((matches) => {
|
|
36
44
|
prefersReducedMotion = matches;
|
|
37
|
-
})
|
|
38
|
-
|
|
45
|
+
});
|
|
46
|
+
const unsubscribeVisibility = observePageVisibility((visible) => {
|
|
47
|
+
tabVisible = visible;
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
unsubscribeMotion();
|
|
51
|
+
unsubscribeVisibility();
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (!rootEl) return;
|
|
57
|
+
return observeInViewport(
|
|
58
|
+
rootEl,
|
|
59
|
+
(inView) => {
|
|
60
|
+
onScreen = inView;
|
|
61
|
+
},
|
|
62
|
+
{ rootMargin: '200px' }
|
|
63
|
+
);
|
|
64
|
+
});
|
|
39
65
|
|
|
40
66
|
$effect(() => {
|
|
41
67
|
if (!contentEl) return;
|
|
@@ -57,6 +83,7 @@
|
|
|
57
83
|
$effect(() => {
|
|
58
84
|
node.style.setProperty('--marquee-duration', `${duration}s`);
|
|
59
85
|
node.style.setProperty('--marquee-gap', gap);
|
|
86
|
+
node.style.setProperty('--marquee-shift', `${contentSize}px`);
|
|
60
87
|
});
|
|
61
88
|
}
|
|
62
89
|
|
|
@@ -68,12 +95,14 @@
|
|
|
68
95
|
</script>
|
|
69
96
|
|
|
70
97
|
<div
|
|
98
|
+
bind:this={rootEl}
|
|
71
99
|
class={className}
|
|
72
100
|
data-marquee
|
|
73
101
|
data-direction={direction}
|
|
74
102
|
data-pause-on-hover={pauseOnHover || undefined}
|
|
75
103
|
data-fade={fade || undefined}
|
|
76
104
|
data-reduced-motion={prefersReducedMotion || undefined}
|
|
105
|
+
data-paused={paused || undefined}
|
|
77
106
|
use:applyRootStyles
|
|
78
107
|
{...rest}
|
|
79
108
|
>
|
|
@@ -95,15 +124,19 @@
|
|
|
95
124
|
|
|
96
125
|
overflow: hidden;
|
|
97
126
|
position: relative;
|
|
127
|
+
contain: content;
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
[data-marquee-track] {
|
|
101
131
|
display: grid;
|
|
102
132
|
grid-auto-flow: var(--_flow, column);
|
|
103
|
-
gap: var(--marquee-gap, 1rem);
|
|
104
133
|
animation-duration: var(--dry-marquee-speed);
|
|
105
134
|
animation-timing-function: linear;
|
|
106
135
|
animation-iteration-count: infinite;
|
|
136
|
+
backface-visibility: hidden;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
[data-marquee]:not([data-paused]) [data-marquee-track] {
|
|
107
140
|
will-change: transform;
|
|
108
141
|
}
|
|
109
142
|
|
|
@@ -113,6 +146,17 @@
|
|
|
113
146
|
gap: var(--marquee-gap, 1rem);
|
|
114
147
|
}
|
|
115
148
|
|
|
149
|
+
/* Trailing padding on content (not gap on track) keeps the keyframe loop seamless. */
|
|
150
|
+
[data-marquee][data-direction='left'] [data-marquee-content],
|
|
151
|
+
[data-marquee][data-direction='right'] [data-marquee-content] {
|
|
152
|
+
padding-inline-end: var(--marquee-gap, 1rem);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
[data-marquee][data-direction='up'] [data-marquee-content],
|
|
156
|
+
[data-marquee][data-direction='down'] [data-marquee-content] {
|
|
157
|
+
padding-block-end: var(--marquee-gap, 1rem);
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
[data-marquee][data-direction='left'] [data-marquee-track],
|
|
117
161
|
[data-marquee][data-direction='right'] [data-marquee-track] {
|
|
118
162
|
animation-name: marquee-horizontal;
|
|
@@ -132,12 +176,16 @@
|
|
|
132
176
|
animation-play-state: paused;
|
|
133
177
|
}
|
|
134
178
|
|
|
179
|
+
[data-marquee][data-paused] [data-marquee-track] {
|
|
180
|
+
animation-play-state: paused;
|
|
181
|
+
}
|
|
182
|
+
|
|
135
183
|
@keyframes marquee-horizontal {
|
|
136
184
|
from {
|
|
137
185
|
transform: translateX(0);
|
|
138
186
|
}
|
|
139
187
|
to {
|
|
140
|
-
transform: translateX(-
|
|
188
|
+
transform: translateX(calc(-1 * var(--marquee-shift, 0px)));
|
|
141
189
|
}
|
|
142
190
|
}
|
|
143
191
|
|
|
@@ -146,7 +194,7 @@
|
|
|
146
194
|
transform: translateY(0);
|
|
147
195
|
}
|
|
148
196
|
to {
|
|
149
|
-
transform: translateY(-
|
|
197
|
+
transform: translateY(calc(-1 * var(--marquee-shift, 0px)));
|
|
150
198
|
}
|
|
151
199
|
}
|
|
152
200
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export interface MegaMenuContext {
|
|
2
2
|
readonly activeItem: string | null;
|
|
3
|
-
openItem: (id: string) => void;
|
|
3
|
+
openItem: (id: string, triggerId: string) => void;
|
|
4
4
|
closeItem: () => void;
|
|
5
5
|
}
|
|
6
6
|
export declare const setMegaMenuCtx: (ctx: MegaMenuContext) => MegaMenuContext, getMegaMenuCtx: () => MegaMenuContext;
|
|
7
7
|
export interface MegaMenuItemContext {
|
|
8
8
|
readonly itemId: string;
|
|
9
9
|
readonly triggerId: string;
|
|
10
|
+
readonly panelId: string;
|
|
10
11
|
readonly open: boolean;
|
|
11
12
|
}
|
|
12
13
|
export declare const setMegaMenuItemCtx: (ctx: MegaMenuItemContext) => MegaMenuItemContext, getMegaMenuItemCtx: () => MegaMenuItemContext;
|
|
@@ -17,18 +17,7 @@
|
|
|
17
17
|
if (itemCtx.open) {
|
|
18
18
|
ctx.closeItem();
|
|
19
19
|
} else {
|
|
20
|
-
ctx.openItem(itemCtx.itemId);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
25
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
26
|
-
e.preventDefault();
|
|
27
|
-
if (itemCtx.open) {
|
|
28
|
-
ctx.closeItem();
|
|
29
|
-
} else {
|
|
30
|
-
ctx.openItem(itemCtx.itemId);
|
|
31
|
-
}
|
|
20
|
+
ctx.openItem(itemCtx.itemId, itemCtx.triggerId);
|
|
32
21
|
}
|
|
33
22
|
}
|
|
34
23
|
</script>
|
|
@@ -37,13 +26,12 @@
|
|
|
37
26
|
variant="trigger"
|
|
38
27
|
type="button"
|
|
39
28
|
id={itemCtx.triggerId}
|
|
29
|
+
aria-controls={itemCtx.open ? itemCtx.panelId : undefined}
|
|
40
30
|
aria-expanded={itemCtx.open}
|
|
41
|
-
aria-haspopup="true"
|
|
42
31
|
data-mega-menu-trigger
|
|
43
32
|
data-state={itemCtx.open ? 'open' : 'closed'}
|
|
44
33
|
{...rest}
|
|
45
34
|
onclick={handleClick}
|
|
46
|
-
onkeydown={handleKeydown}
|
|
47
35
|
>
|
|
48
36
|
{@render children()}
|
|
49
37
|
</Button>
|
|
@@ -13,17 +13,19 @@
|
|
|
13
13
|
const ctx = getMegaMenuCtx();
|
|
14
14
|
const itemId = generateFormId('mm-item');
|
|
15
15
|
const triggerId = generateFormId('mm-trigger');
|
|
16
|
+
const panelId = generateFormId('mm-panel');
|
|
16
17
|
|
|
17
18
|
setMegaMenuItemCtx({
|
|
18
19
|
itemId,
|
|
19
20
|
triggerId,
|
|
21
|
+
panelId,
|
|
20
22
|
get open() {
|
|
21
23
|
return ctx.activeItem === itemId;
|
|
22
24
|
}
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
function handlePointerEnter() {
|
|
26
|
-
ctx.openItem(itemId);
|
|
28
|
+
ctx.openItem(itemId, triggerId);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function handlePointerLeave() {
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
const itemCtx = getMegaMenuItemCtx();
|
|
25
25
|
|
|
26
26
|
let panelEl = $state<HTMLDivElement | null>(null);
|
|
27
|
+
let layout = $state<'columns' | 'stacked'>('columns');
|
|
28
|
+
const STACK_THRESHOLD = 32 * 16;
|
|
27
29
|
|
|
28
30
|
const popover = createPositionedPopover({
|
|
29
31
|
triggerEl: () => document.getElementById(itemCtx.triggerId),
|
|
@@ -36,8 +38,18 @@
|
|
|
36
38
|
offset: () => 4
|
|
37
39
|
});
|
|
38
40
|
|
|
41
|
+
function watchLayout(node: HTMLDivElement) {
|
|
42
|
+
const update = () => {
|
|
43
|
+
const next = window.innerWidth < STACK_THRESHOLD ? 'stacked' : 'columns';
|
|
44
|
+
if (next !== layout) layout = next;
|
|
45
|
+
};
|
|
46
|
+
update();
|
|
47
|
+
window.addEventListener('resize', update);
|
|
48
|
+
return () => window.removeEventListener('resize', update);
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
function handlePointerEnter() {
|
|
40
|
-
ctx.openItem(itemCtx.itemId);
|
|
52
|
+
ctx.openItem(itemCtx.itemId, itemCtx.triggerId);
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
function handlePointerLeave() {
|
|
@@ -70,12 +82,15 @@
|
|
|
70
82
|
{@attach attachPanel}
|
|
71
83
|
{@attach syncPopover(itemCtx.open)}
|
|
72
84
|
{@attach fromAction(popover.applyPosition, () => style)}
|
|
73
|
-
|
|
85
|
+
{@attach watchLayout}
|
|
86
|
+
id={itemCtx.panelId}
|
|
87
|
+
role="group"
|
|
74
88
|
popover="manual"
|
|
75
89
|
data-mega-menu-panel
|
|
76
90
|
aria-labelledby={itemCtx.triggerId}
|
|
77
91
|
data-state={itemCtx.open ? 'open' : 'closed'}
|
|
78
92
|
data-full-width={fullWidth || undefined}
|
|
93
|
+
data-layout={layout}
|
|
79
94
|
class={className}
|
|
80
95
|
onpointerenter={handlePointerEnter}
|
|
81
96
|
onpointerleave={handlePointerLeave}
|
|
@@ -86,6 +101,13 @@
|
|
|
86
101
|
{/if}
|
|
87
102
|
|
|
88
103
|
<style>
|
|
104
|
+
@position-try --dry-mega-menu-panel-viewport-fit {
|
|
105
|
+
position-area: none;
|
|
106
|
+
inset-inline: 4vw;
|
|
107
|
+
inset-block-start: anchor(bottom);
|
|
108
|
+
margin-top: 0.5rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
89
111
|
[data-mega-menu-panel] {
|
|
90
112
|
inset: unset;
|
|
91
113
|
margin: 0;
|
|
@@ -95,11 +117,14 @@
|
|
|
95
117
|
border-radius: var(--dry-radius-lg, 0.5rem);
|
|
96
118
|
box-shadow: var(--dry-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1));
|
|
97
119
|
padding: var(--dry-space-4, 1rem);
|
|
120
|
+
/* dryui-allow width */
|
|
121
|
+
max-inline-size: var(--dry-mega-menu-panel-max-width, min(92vw, 60rem));
|
|
98
122
|
display: grid;
|
|
99
123
|
grid-auto-flow: column;
|
|
100
|
-
grid-auto-columns: max-content;
|
|
124
|
+
grid-auto-columns: minmax(var(--dry-mega-menu-panel-column-min, 12rem), max-content);
|
|
101
125
|
align-items: start;
|
|
102
126
|
gap: var(--dry-space-6, 1.5rem);
|
|
127
|
+
--dry-anchor-try-fallbacks: flip-block, flip-inline, --dry-mega-menu-panel-viewport-fit;
|
|
103
128
|
}
|
|
104
129
|
|
|
105
130
|
[data-mega-menu-panel]:not(:popover-open) {
|
|
@@ -110,6 +135,13 @@
|
|
|
110
135
|
display: grid;
|
|
111
136
|
}
|
|
112
137
|
|
|
138
|
+
[data-mega-menu-panel][data-layout='stacked'] {
|
|
139
|
+
grid-auto-flow: row;
|
|
140
|
+
grid-auto-columns: auto;
|
|
141
|
+
grid-auto-rows: auto;
|
|
142
|
+
gap: var(--dry-space-4, 1rem);
|
|
143
|
+
}
|
|
144
|
+
|
|
113
145
|
[data-mega-menu-panel][data-full-width] {
|
|
114
146
|
justify-self: stretch;
|
|
115
147
|
}
|
|
@@ -10,25 +10,45 @@
|
|
|
10
10
|
let { class: className, children, ...rest }: Props = $props();
|
|
11
11
|
|
|
12
12
|
let activeItem = $state<string | null>(null);
|
|
13
|
+
let activeTriggerId = $state<string | null>(null);
|
|
13
14
|
let openTimer: ReturnType<typeof setTimeout> | undefined;
|
|
14
15
|
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
15
16
|
|
|
17
|
+
function clearPendingTimers() {
|
|
18
|
+
clearTimeout(openTimer);
|
|
19
|
+
clearTimeout(closeTimer);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function closeImmediately(options?: { restoreFocus?: boolean }) {
|
|
23
|
+
const triggerId = options?.restoreFocus ? activeTriggerId : null;
|
|
24
|
+
clearPendingTimers();
|
|
25
|
+
activeItem = null;
|
|
26
|
+
activeTriggerId = null;
|
|
27
|
+
|
|
28
|
+
if (!triggerId) return;
|
|
29
|
+
|
|
30
|
+
const trigger = document.getElementById(triggerId);
|
|
31
|
+
if (trigger instanceof HTMLElement) {
|
|
32
|
+
trigger.focus();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
setMegaMenuCtx({
|
|
17
37
|
get activeItem() {
|
|
18
38
|
return activeItem;
|
|
19
39
|
},
|
|
20
|
-
openItem(id) {
|
|
21
|
-
|
|
22
|
-
clearTimeout(openTimer);
|
|
40
|
+
openItem(id, triggerId) {
|
|
41
|
+
clearPendingTimers();
|
|
23
42
|
openTimer = setTimeout(() => {
|
|
24
43
|
activeItem = id;
|
|
44
|
+
activeTriggerId = triggerId;
|
|
25
45
|
}, 150);
|
|
26
46
|
},
|
|
27
47
|
closeItem() {
|
|
28
|
-
|
|
29
|
-
clearTimeout(closeTimer);
|
|
48
|
+
clearPendingTimers();
|
|
30
49
|
closeTimer = setTimeout(() => {
|
|
31
50
|
activeItem = null;
|
|
51
|
+
activeTriggerId = null;
|
|
32
52
|
}, 300);
|
|
33
53
|
}
|
|
34
54
|
});
|
|
@@ -36,9 +56,7 @@
|
|
|
36
56
|
function handleKeydown(e: KeyboardEvent) {
|
|
37
57
|
if (e.key === 'Escape' && activeItem) {
|
|
38
58
|
e.preventDefault();
|
|
39
|
-
|
|
40
|
-
clearTimeout(closeTimer);
|
|
41
|
-
activeItem = null;
|
|
59
|
+
closeImmediately({ restoreFocus: true });
|
|
42
60
|
}
|
|
43
61
|
}
|
|
44
62
|
|
|
@@ -46,16 +64,13 @@
|
|
|
46
64
|
const nav = e.currentTarget as HTMLElement;
|
|
47
65
|
const related = e.relatedTarget as Node | null;
|
|
48
66
|
if (related && !nav.contains(related)) {
|
|
49
|
-
|
|
50
|
-
clearTimeout(closeTimer);
|
|
51
|
-
activeItem = null;
|
|
67
|
+
closeImmediately();
|
|
52
68
|
}
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
$effect(() => {
|
|
56
72
|
return () => {
|
|
57
|
-
|
|
58
|
-
clearTimeout(closeTimer);
|
|
73
|
+
clearPendingTimers();
|
|
59
74
|
};
|
|
60
75
|
});
|
|
61
76
|
</script>
|
|
@@ -7,8 +7,8 @@ export interface MenubarContext {
|
|
|
7
7
|
registerMenu: (id: string) => void;
|
|
8
8
|
unregisterMenu: (id: string) => void;
|
|
9
9
|
getMenuIds: () => string[];
|
|
10
|
-
focusNextMenu: (currentId: string) => void;
|
|
11
|
-
focusPrevMenu: (currentId: string) => void;
|
|
10
|
+
focusNextMenu: (currentId: string, open?: boolean) => void;
|
|
11
|
+
focusPrevMenu: (currentId: string, open?: boolean) => void;
|
|
12
12
|
}
|
|
13
13
|
export declare const setMenubarCtx: (ctx: MenubarContext) => MenubarContext, getMenubarCtx: () => MenubarContext;
|
|
14
14
|
export interface MenubarMenuContext {
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
children: Snippet;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
let { children, onclick, onkeydown, ...rest }: Props = $props();
|
|
11
|
+
let { children, onclick, onkeydown, id, ...rest }: Props = $props();
|
|
12
12
|
|
|
13
13
|
const ctx = getMenubarCtx();
|
|
14
14
|
const menuCtx = getMenubarMenuCtx();
|
|
15
|
+
const triggerId = $derived(id ?? `menubar-trigger-${menuCtx.menuId}`);
|
|
15
16
|
|
|
16
17
|
function handleClick(e: MouseEvent & { currentTarget: HTMLButtonElement }) {
|
|
17
18
|
if (menuCtx.open) {
|
|
@@ -32,12 +33,12 @@
|
|
|
32
33
|
switch (e.key) {
|
|
33
34
|
case 'ArrowRight': {
|
|
34
35
|
e.preventDefault();
|
|
35
|
-
ctx.focusNextMenu(menuCtx.menuId);
|
|
36
|
+
ctx.focusNextMenu(menuCtx.menuId, ctx.hasOpenMenu);
|
|
36
37
|
break;
|
|
37
38
|
}
|
|
38
39
|
case 'ArrowLeft': {
|
|
39
40
|
e.preventDefault();
|
|
40
|
-
ctx.focusPrevMenu(menuCtx.menuId);
|
|
41
|
+
ctx.focusPrevMenu(menuCtx.menuId, ctx.hasOpenMenu);
|
|
41
42
|
break;
|
|
42
43
|
}
|
|
43
44
|
case 'ArrowDown': {
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
<Button
|
|
60
61
|
variant="trigger"
|
|
61
62
|
type="button"
|
|
63
|
+
id={triggerId}
|
|
62
64
|
role="menuitem"
|
|
63
65
|
aria-haspopup="menu"
|
|
64
66
|
aria-expanded={menuCtx.open}
|