@pzerelles/headlessui-svelte 2.1.2-next.67 → 2.1.2-next.69

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.
@@ -0,0 +1,169 @@
1
+ <script lang="ts" module>
2
+ import type { Props } from "../utils/types.js"
3
+
4
+ const DEFAULT_HOVER_AREA_TAG = "span" as const
5
+ export type PopoverHoverAreaSlot = {
6
+ open: boolean
7
+ hover: boolean
8
+ disabled: boolean
9
+ }
10
+ export type PopoverHoverAreaPropsWeControl = "aria-controls" | "aria-expanded"
11
+
12
+ export type PopoverHoverAreaOwnProps = {
13
+ element?: HTMLElement
14
+ id?: string
15
+ disabled?: boolean
16
+ delay?: number
17
+ }
18
+
19
+ export type PopoverHoverAreaProps = Props<
20
+ typeof DEFAULT_HOVER_AREA_TAG,
21
+ PopoverHoverAreaSlot,
22
+ PopoverHoverAreaOwnProps
23
+ >
24
+ </script>
25
+
26
+ <script lang="ts">
27
+ import { useId } from "../hooks/use-id.js"
28
+ import { PopoverStates, usePopoverContext, usePopoverPanelContext } from "./context.svelte.js"
29
+ import { useFloatingReference } from "../internal/floating.svelte.js"
30
+ import { untrack } from "svelte"
31
+ import { useHover } from "../hooks/use-hover.svelte.js"
32
+ import { mergeProps } from "../utils/render.js"
33
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte"
34
+
35
+ const internalId = useId()
36
+ let {
37
+ element = $bindable(),
38
+ id = `headlessui-popover-hover-area-${internalId}`,
39
+ disabled = false,
40
+ delay = 0,
41
+ ...theirProps
42
+ }: PopoverHoverAreaProps = $props()
43
+
44
+ const context = usePopoverContext("PopoverHoverArea")
45
+ const panelContext = usePopoverPanelContext()
46
+
47
+ // A button inside a panel will just have "close" functionality, no "open" functionality. However,
48
+ // if a `PopoverButton` is rendered inside a `Popover` which in turn is rendered inside a
49
+ // `PopoverPanel` (aka nested popovers), then we need to make sure that the button is able to
50
+ // open the nested popover.
51
+ //
52
+ // The `Popover` itself will also render a `PopoverPanelContext` but with a value of `null`. That
53
+ // way we don't need to keep track of _which_ `PopoverPanel` (if at all) we are in, we can just
54
+ // check if we are in a `PopoverPanel` or not since this will always point to the nearest one and
55
+ // won't pierce through `Popover` components themselves.
56
+ const isWithinPanel = panelContext !== undefined
57
+
58
+ $effect(() => {
59
+ // [isWithinPanel, id, dispatch]
60
+ if (isWithinPanel) return
61
+ id
62
+ return untrack(() => {
63
+ context.setButtonId(id)
64
+ return () => {
65
+ context.setButtonId(undefined)
66
+ }
67
+ })
68
+ })
69
+
70
+ // This is a little bit different compared to the `id` we already have. The goal is to have a very
71
+ // unique identifier for this specific component. This can be achieved with the `id` from above.
72
+ //
73
+ // However, the difference is for React 17 and lower where the `useId` hook doesn't exist yet.
74
+ // There we will generate a unique ID based on a simple counter, but for SSR this will result in
75
+ // `undefined` first, later it is patched to be a unique ID. The problem is that this patching
76
+ // happens after the component is rendered and therefore there is a moment in time where multiple
77
+ // buttons have the exact same ID and the `state.buttons` would result in something like:
78
+ //
79
+ // ```js
80
+ // ['headlessui-popover-button-undefined', 'headlessui-popover-button-1']
81
+ // ```
82
+ //
83
+ // With this approach we guarantee that there is a unique value for each button.
84
+ const uniqueIdentifier = Symbol()
85
+
86
+ const floatingReference = useFloatingReference()
87
+ const { setReference } = $derived(floatingReference)
88
+ $effect(() => {
89
+ setReference(element)
90
+ })
91
+ $effect(() => {
92
+ if (isWithinPanel) return
93
+ element
94
+ untrack(() => {
95
+ if (element) {
96
+ context.buttons.push(uniqueIdentifier)
97
+ } else {
98
+ let idx = context.buttons.indexOf(uniqueIdentifier)
99
+ if (idx !== -1) context.buttons.splice(idx, 1)
100
+ }
101
+
102
+ if (context.buttons.length > 1) {
103
+ console.warn("You are already using a <PopoverButton /> but only 1 <PopoverButton /> is supported.")
104
+ }
105
+
106
+ if (element) context.setButton(element)
107
+ })
108
+ })
109
+
110
+ const { isHovered: hover, hoverProps } = $derived(
111
+ useHover({
112
+ get disabled() {
113
+ return disabled
114
+ },
115
+ })
116
+ )
117
+
118
+ const visible = $derived(context.popoverState === PopoverStates.Open)
119
+ const slot = $derived({
120
+ open: visible,
121
+ disabled,
122
+ hover,
123
+ } satisfies PopoverHoverAreaSlot)
124
+
125
+ const ourProps = $derived(
126
+ isWithinPanel
127
+ ? mergeProps(
128
+ {
129
+ disabled: disabled || undefined,
130
+ },
131
+ hoverProps
132
+ )
133
+ : mergeProps(
134
+ {
135
+ id: context.buttonId,
136
+ "aria-expanded": context.popoverState === PopoverStates.Open,
137
+ "aria-controls": context.panel ? context.panelId : undefined,
138
+ disabled: disabled || undefined,
139
+ },
140
+ hoverProps
141
+ )
142
+ )
143
+
144
+ let timeout: NodeJS.Timeout | undefined
145
+
146
+ $effect(() => {
147
+ if (hover && !visible) {
148
+ timeout = setTimeout(() => {
149
+ timeout = undefined
150
+ context.togglePopover()
151
+ }, delay)
152
+ } else if (!hover) {
153
+ if (timeout) {
154
+ clearTimeout(timeout)
155
+ timeout = undefined
156
+ }
157
+ if (visible) context.closePopover()
158
+ }
159
+ })
160
+ </script>
161
+
162
+ <ElementOrComponent
163
+ {ourProps}
164
+ {theirProps}
165
+ slots={slot}
166
+ defaultTag={DEFAULT_HOVER_AREA_TAG}
167
+ name="PopoverHoverArea"
168
+ bind:element
169
+ />
@@ -0,0 +1,18 @@
1
+ import type { Props } from "../utils/types.js";
2
+ declare const DEFAULT_HOVER_AREA_TAG: "span";
3
+ export type PopoverHoverAreaSlot = {
4
+ open: boolean;
5
+ hover: boolean;
6
+ disabled: boolean;
7
+ };
8
+ export type PopoverHoverAreaPropsWeControl = "aria-controls" | "aria-expanded";
9
+ export type PopoverHoverAreaOwnProps = {
10
+ element?: HTMLElement;
11
+ id?: string;
12
+ disabled?: boolean;
13
+ delay?: number;
14
+ };
15
+ export type PopoverHoverAreaProps = Props<typeof DEFAULT_HOVER_AREA_TAG, PopoverHoverAreaSlot, PopoverHoverAreaOwnProps>;
16
+ declare const PopoverHoverArea: import("svelte").Component<PopoverHoverAreaProps, {}, "element">;
17
+ type PopoverHoverArea = ReturnType<typeof PopoverHoverArea>;
18
+ export default PopoverHoverArea;
@@ -3,4 +3,5 @@ export { default as PopoverBackdrop, type PopoverBackdropProps, type BackdropRen
3
3
  export { default as PopoverButton, type PopoverButtonProps, type PopoverButtonSlot, type PopoverButtonOwnProps, } from "./PopoverButton.svelte";
4
4
  export { default as PopoverGroup, type PopoverGroupProps, type PopoverGroupOwnProps } from "./PopoverGroup.svelte";
5
5
  export { default as PopoverPanel, type PopoverPanelProps, type PanelRenderPropArg as PopoverPanelSlot, type PopoverPanelOwnProps, } from "./PopoverPanel.svelte";
6
+ export { default as PopoverHoverArea, type PopoverHoverAreaProps, type PopoverHoverAreaSlot, type PopoverHoverAreaOwnProps, } from "./PopoverHoverArea.svelte";
6
7
  export { usePopoverContext } from "./context.svelte.js";
@@ -3,4 +3,5 @@ export { default as PopoverBackdrop, } from "./PopoverBackdrop.svelte";
3
3
  export { default as PopoverButton, } from "./PopoverButton.svelte";
4
4
  export { default as PopoverGroup } from "./PopoverGroup.svelte";
5
5
  export { default as PopoverPanel, } from "./PopoverPanel.svelte";
6
+ export { default as PopoverHoverArea, } from "./PopoverHoverArea.svelte";
6
7
  export { usePopoverContext } from "./context.svelte.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pzerelles/headlessui-svelte",
3
- "version": "2.1.2-next.67",
3
+ "version": "2.1.2-next.69",
4
4
  "repository": {
5
5
  "url": "https://github.com/pzerelles/headlessui-svelte"
6
6
  },