Skip to content

Commit 07b224e

Browse files
author
Valgeir Bjornsson
committed
Manual mode
- Add `show` prop to manually control the Popper - Update docs - Update README - Refactor code
1 parent 8517395 commit 07b224e

File tree

7 files changed

+217
-88
lines changed

7 files changed

+217
-88
lines changed

README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,21 @@ yarn add vue3-popper
6363

6464
## Props
6565

66-
| Name | Default | Description |
67-
| ------------------ | -------- | -------------------------------------------------------------------------------- |
68-
| `placement` | `bottom` | Preferred placement of the Popper |
69-
| `disableClickAway` | `false` | Disables automatically closing the Popper when the user clicks away from it |
70-
| `offsetSkid` | `0` | Offset in pixels along the trigger element |
71-
| `offsetDistance` | `12` | Offset in pixels away from the trigger element |
72-
| `hover` | `false` | Trigger the Popper on hover |
73-
| `arrow` | `false` | Display an arrow on the Popper |
74-
| `arrowPadding` | `0` | Stop arrow from reaching the edge of the Popper (in pixels) |
75-
| `disabled` | `false` | Disables the Popper. If it was already open, it will be closed. |
76-
| `openDelay` | `0` | Open the Popper after a delay (ms) |
77-
| `closeDelay` | `0` | Close the Popper after a delay (ms) |
78-
| `interactive` | `true` | If the Popper should be interactive, it will close when clicked/hovered if false |
79-
| `content` | `null` | If your content is just a simple string, you can pass it as a prop |
66+
| Name | Default | Description |
67+
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------- |
68+
| `placement` | `bottom` | Preferred placement of the Popper |
69+
| `disableClickAway` | `false` | Disables automatically closing the Popper when the user clicks away from it |
70+
| `offsetSkid` | `0` | Offset in pixels along the trigger element |
71+
| `offsetDistance` | `12` | Offset in pixels away from the trigger element |
72+
| `hover` | `false` | Trigger the Popper on hover |
73+
| `arrow` | `false` | Display an arrow on the Popper |
74+
| `arrowPadding` | `0` | Stop arrow from reaching the edge of the Popper (in pixels) |
75+
| `disabled` | `false` | Disables the Popper. If it was already open, it will be closed. |
76+
| `openDelay` | `0` | Open the Popper after a delay (ms) |
77+
| `closeDelay` | `0` | Close the Popper after a delay (ms) |
78+
| `interactive` | `true` | If the Popper should be interactive, it will close when clicked/hovered if false |
79+
| `content` | `null` | If your content is just a simple string, you can pass it as a prop |
80+
| `show` | `null` | Control the Popper **manually**, other events (click, hover) are ignored if this is set to `true/false` |
8081

8182
## Events
8283

docs/.vitepress/theme/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import PopperDynamicTheme from "../../components/PopperDynamicTheme.vue";
55
import PopperDeep from "../../components/PopperDeep.vue";
66
import PopperScopedSlots from "../../components/PopperScopedSlots.vue";
77
import PopperDemo from "../../components/PopperDemo.vue";
8+
import PopperManual from "../../components/PopperManual.vue";
89
import CodeBlock from "../../components/CodeBlock.vue";
910
import CodeGroup from "../../components/CodeGroup.ts";
1011
import Button from "../../components/Button.vue";
@@ -18,6 +19,7 @@ export default {
1819
app.component("PopperDeep", PopperDeep);
1920
app.component("PopperScopedSlots", PopperScopedSlots);
2021
app.component("PopperDemo", PopperDemo);
22+
app.component("PopperManual", PopperManual);
2123
app.component("CodeBlock", CodeBlock);
2224
app.component("CodeGroup", CodeGroup);
2325
app.component("Button", Button);

docs/components/PopperManual.vue

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<template>
2+
<div class="popper-container">
3+
<Popper
4+
class="popper-demo"
5+
arrow
6+
content="This is the Popper content 🍿"
7+
:show="showPopper"
8+
>
9+
<Button>Demo</Button>
10+
</Popper>
11+
<div>
12+
<input id="toggle" type="checkbox" v-model="showPopper" />
13+
<label for="toggle">Toggle Popper</label>
14+
</div>
15+
</div>
16+
</template>
17+
18+
<script>
19+
import Popper from "../../dist/popper.esm";
20+
import Button from "./Button.vue";
21+
export default {
22+
name: "PopperManual",
23+
components: {
24+
Popper,
25+
Button,
26+
},
27+
data() {
28+
return {
29+
showPopper: true,
30+
};
31+
},
32+
};
33+
</script>
34+
35+
<style scoped>
36+
.popper-container {
37+
padding: 50px;
38+
border: 2px dashed #dadada;
39+
border-radius: 10px;
40+
display: flex;
41+
flex-direction: column;
42+
gap: 150px;
43+
align-items: center;
44+
justify-content: center;
45+
}
46+
47+
.popper-demo {
48+
--popper-theme-background-color: #ffffff;
49+
--popper-theme-background-color-hover: #ffffff;
50+
--popper-theme-text-color: #333333;
51+
--popper-theme-border-width: 1px;
52+
--popper-theme-border-style: solid;
53+
--popper-theme-border-color: #dadada;
54+
--popper-theme-border-radius: 6px;
55+
--popper-theme-padding: 32px;
56+
--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, 0.25);
57+
}
58+
</style>

docs/guide/api.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@
22

33
## Props
44

5-
| Name | Default | Description |
6-
| ------------------ | -------- | -------------------------------------------------------------------------------- |
7-
| `placement` | `bottom` | Preferred placement of the Popper |
8-
| `disableClickAway` | `false` | Disables automatically closing the Popper when the user clicks away from it |
9-
| `offsetSkid` | `0` | Offset in pixels along the trigger element |
10-
| `offsetDistance` | `12` | Offset in pixels away from the trigger element |
11-
| `hover` | `false` | Trigger the Popper on hover |
12-
| `arrow` | `false` | Display an arrow on the Popper |
13-
| `arrowPadding` | `0` | Stop arrow from reaching the edge of the Popper (in pixels) |
14-
| `disabled` | `false` | Disables the Popper. If it was already open, it will be closed. |
15-
| `openDelay` | `0` | Open the Popper after a delay (ms) |
16-
| `closeDelay` | `0` | Close the Popper after a delay (ms) |
17-
| `interactive` | `true` | If the Popper should be interactive, it will close when clicked/hovered if false |
18-
| `content` | `null` | If your content is just a simple string, you can pass it as a prop |
5+
| Name | Default | Description |
6+
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------- |
7+
| `placement` | `bottom` | Preferred placement of the Popper |
8+
| `disableClickAway` | `false` | Disables automatically closing the Popper when the user clicks away from it |
9+
| `offsetSkid` | `0` | Offset in pixels along the trigger element |
10+
| `offsetDistance` | `12` | Offset in pixels away from the trigger element |
11+
| `hover` | `false` | Trigger the Popper on hover |
12+
| `arrow` | `false` | Display an arrow on the Popper |
13+
| `arrowPadding` | `0` | Stop arrow from reaching the edge of the Popper (in pixels) |
14+
| `disabled` | `false` | Disables the Popper. If it was already open, it will be closed. |
15+
| `openDelay` | `0` | Open the Popper after a delay (ms) |
16+
| `closeDelay` | `0` | Close the Popper after a delay (ms) |
17+
| `interactive` | `true` | If the Popper should be interactive, it will close when clicked/hovered if false |
18+
| `content` | `null` | If your content is just a simple string, you can pass it as a prop |
19+
| `show` | `null` | Control the Popper **manually**, other events (click, hover) are ignored if this is set to `true/false` |
1920

2021
## Events
2122

2223
| Name | Description |
2324
| -------------- | ------------------------- |
24-
| `open:popper` | When the Popper is open |
25+
| `open:popper` | When the Popper is opened |
2526
| `close:popper` | When the Popper is hidden |
2627

2728
## Slots

docs/guide/getting-started.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,42 @@ You can gain access to the `close` function for those edge cases. In this exampl
341341

342342
<popper-scoped-slots />
343343

344+
## Manually controlling the Popper
345+
346+
You can use the `show` prop to manually control the Popper. Other events (click, hover) are ignored when in manual mode.
347+
348+
```vue
349+
<template>
350+
<div>
351+
<Popper
352+
class="popper-demo"
353+
arrow
354+
content="This is the Popper content 🍿"
355+
:show="showPopper"
356+
>
357+
<Button>Demo</Button>
358+
</Popper>
359+
<div>
360+
<input id="toggle" type="checkbox" v-model="showPopper" />
361+
<label for="toggle">Toggle Popper</label>
362+
</div>
363+
</div>
364+
</template>
365+
366+
<script>
367+
export default {
368+
name: "PopperManual",
369+
data() {
370+
return {
371+
showPopper: true,
372+
};
373+
},
374+
};
375+
</script>
376+
```
377+
378+
<popper-manual />
379+
344380
::: tip
345381
`Vue 3 Popper` has multiple useful props as well, check out the API docs for more info.
346382
:::

src/component/Popper.vue

Lines changed: 82 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
<template>
22
<div
33
:style="interactiveStyle"
4-
@mouseleave="hover && handleClose()"
5-
v-click-away="{ handler: handleClose, enabled: enableClickAway }"
4+
@mouseleave="hover && closePopper()"
5+
v-click-away="{ handler: closePopper, enabled: enableClickAway }"
66
>
77
<div
88
ref="triggerNode"
9-
@mouseover="hover && handleOpen()"
10-
@click="handleToggle"
11-
@focus="handleOpen"
12-
@blur="handleClose"
13-
@keyup.esc="handleClose"
9+
@mouseover="hover && openPopper()"
10+
@click="togglePopper"
11+
@focus="openPopper"
12+
@keyup.esc="closePopper"
1413
class="inline-block"
1514
>
1615
<!-- The default slot to trigger the popper -->
1716
<slot />
1817
</div>
1918
<Transition name="fade">
2019
<div
21-
@click="!interactive && handleToggle()"
20+
@click="!interactive && closePopper()"
2221
v-show="shouldShowPopper"
2322
:class="['popper', shouldShowPopper ? 'inline-block' : null]"
2423
ref="popperNode"
@@ -35,7 +34,14 @@
3534

3635
<script>
3736
import { debounce } from "debounce";
38-
import { ref, computed, defineComponent, toRefs, watch } from "vue";
37+
import {
38+
ref,
39+
computed,
40+
defineComponent,
41+
toRefs,
42+
watch,
43+
watchEffect,
44+
} from "vue";
3945
import { usePopper, useContent } from "@/composables";
4046
import clickAway from "@/directives";
4147
@@ -106,6 +112,13 @@
106112
type: Boolean,
107113
default: false,
108114
},
115+
/**
116+
* Manually open/close the Popper, other events are ignored if this prop is set
117+
*/
118+
show: {
119+
type: Boolean,
120+
default: null,
121+
},
109122
/**
110123
* Disables the Popper. If it was already open, it will be closed.
111124
*/
@@ -170,30 +183,66 @@
170183
const modifiedIsOpen = ref(false);
171184
172185
const {
173-
offsetSkid,
174-
offsetDistance,
175186
arrowPadding,
176-
placement,
177-
disabled,
178-
disableClickAway,
179-
openDelay,
180187
closeDelay,
181-
interactive,
182188
content,
189+
disableClickAway,
190+
disabled,
191+
interactive,
192+
show,
193+
offsetDistance,
194+
offsetSkid,
195+
openDelay,
196+
placement,
183197
} = toRefs(props);
184198
185199
const { isOpen, open, close } = usePopper({
186-
popperNode,
187-
triggerNode,
188-
offsetSkid,
189-
offsetDistance,
190200
arrowPadding,
191-
placement,
192201
emit,
202+
offsetDistance,
203+
offsetSkid,
204+
placement,
205+
popperNode,
206+
triggerNode,
193207
});
194208
195209
const { hasContent } = useContent(slots, popperNode, content);
196210
211+
const manualMode = computed(() => show.value !== null);
212+
const invalid = computed(() => disabled.value || !hasContent.value);
213+
const shouldShowPopper = computed(() => isOpen.value && !invalid.value);
214+
const enableClickAway = computed(
215+
() => !disableClickAway.value && !manualMode.value,
216+
);
217+
// Add an invisible border to keep the Popper open when hovering from the trigger into it
218+
const interactiveStyle = computed(() =>
219+
interactive.value
220+
? `border: ${offsetDistance.value}px solid transparent; margin: -${offsetDistance.value}px;`
221+
: null,
222+
);
223+
224+
const openPopper = async () => {
225+
if (invalid.value || manualMode.value) {
226+
return;
227+
}
228+
229+
await delay(openDelay.value);
230+
open();
231+
};
232+
233+
const closePopper = async () => {
234+
if (manualMode.value) {
235+
return;
236+
}
237+
238+
await delay(closeDelay.value);
239+
close();
240+
};
241+
242+
const togglePopper = () => {
243+
isOpen.value ? closePopper() : openPopper();
244+
};
245+
197246
/**
198247
* If Popper is open, we automatically close it if it becomes
199248
* disabled or without content.
@@ -219,48 +268,30 @@
219268
}
220269
});
221270
222-
const handleOpen = async () => {
223-
if (invalid.value) {
224-
return;
271+
/**
272+
* Watch for manual mode
273+
*/
274+
watchEffect(() => {
275+
if (manualMode.value) {
276+
show.value
277+
? delay(openDelay.value).then(open)
278+
: delay(closeDelay.value).then(close);
225279
}
226-
227-
await delay(openDelay.value);
228-
open();
229-
};
230-
231-
const handleClose = async () => {
232-
await delay(closeDelay.value);
233-
close();
234-
};
235-
236-
const handleToggle = () => {
237-
isOpen.value ? handleClose() : handleOpen();
238-
};
239-
240-
const invalid = computed(() => disabled.value || !hasContent.value);
241-
const shouldShowPopper = computed(() => isOpen.value && !invalid.value);
242-
const enableClickAway = computed(() => !disableClickAway.value);
243-
// Add an invisible border to keep the Popper open when hovering from the trigger into it
244-
const interactiveStyle = computed(() =>
245-
interactive.value
246-
? `border: ${offsetDistance.value}px solid transparent; margin: -${offsetDistance.value}px;`
247-
: null,
248-
);
280+
});
249281
250282
return {
251283
popperNode,
252284
triggerNode,
253-
isOpen,
254285
close,
255-
handleToggle,
256-
handleOpen,
257-
handleClose,
258286
shouldShowPopper,
259287
enableClickAway,
260288
modifiedIsOpen,
261289
interactive,
262290
interactiveStyle,
263291
content,
292+
togglePopper,
293+
closePopper,
294+
openPopper,
264295
};
265296
},
266297
});

0 commit comments

Comments
 (0)