Skip to content

Commit 33e39b0

Browse files
feat(v-b-toggle): check for target ID via href if a link (bootstrap-vue#5398)
* feat(v-b-toggle): check for target ID via `href` if a link * Update toggle.js * Update toggle.js * Update toggle.js * Update toggle.js * Update toggle.js * Update toggle.spec.js * Update toggle.js * Update README.md Co-authored-by: Jacob Müller <jacob.mueller.elz@gmail.com>
1 parent 9d5150c commit 33e39b0

File tree

3 files changed

+215
-114
lines changed

3 files changed

+215
-114
lines changed

src/directives/toggle/README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ details and caveats.
1616

1717
## Directive syntax and usage
1818

19-
The directive is applied to the element or component that triggers the visibility of hte target. The
19+
The directive is applied to the element or component that triggers the visibility of the target. The
2020
target component can be specified (via its ID) as either a directive modifier(s), the directive
2121
argument, or as a string/array passed to as the directive value:
2222

@@ -34,7 +34,7 @@ argument, or as a string/array passed to as the directive value:
3434

3535
Modifiers, argument, and the value can be used at the same time when targeting multiple components.
3636

37-
### Example usage
37+
**Example usage:**
3838

3939
```html
4040
<template>
@@ -62,6 +62,44 @@ Modifiers, argument, and the value can be used at the same time when targeting m
6262
<!-- v-b-toggle-directive.vue -->
6363
```
6464

65+
## Usage on links
66+
67+
<span class="badge badge-info small">2.15.0+</span>
68+
69+
If placing the directive on a link (or a component that renders a link), the target ID can
70+
alternatively be specified via the `href` attribute.
71+
72+
Note that the browser URL will change and the page may scroll the target into view. To prevent the
73+
URL from changing and the page from scrolling, add `@click.prevent` to the link.
74+
75+
**Example usage:**
76+
77+
```html
78+
<template>
79+
<div>
80+
<div class="mb-3">
81+
<a v-b-toggle href="#example-collapse" @click.prevent>Toggle Collapse</a>
82+
<b-button v-b-toggle href="#example-sidebar" @click.prevent>Toggle Sidebar</b-button>
83+
</div>
84+
85+
<b-collapse id="example-collapse">
86+
<b-card title="Collapsible card">
87+
Hello world!
88+
</b-card>
89+
</b-collapse>
90+
91+
<b-sidebar id="example-sidebar" title="Sidebar" shadow>
92+
<div class="px-3 py-2">
93+
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis
94+
in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
95+
</div>
96+
</b-sidebar>
97+
</div>
98+
</template>
99+
100+
<!-- v-b-toggle-links.vue -->
101+
```
102+
65103
## Hiding and showing content in the toggle trigger element
66104

67105
When using the `v-b-toggle` directive, the class `collapsed` will automatically be placed on the

src/directives/toggle/toggle.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import KeyCodes from '../../utils/key-codes'
22
import looseEqual from '../../utils/loose-equal'
33
import { arrayIncludes, concat } from '../../utils/array'
4-
import { addClass, hasAttr, isDisabled, removeAttr, removeClass, setAttr } from '../../utils/dom'
4+
import {
5+
addClass,
6+
getAttr,
7+
hasAttr,
8+
isDisabled,
9+
isTag,
10+
removeAttr,
11+
removeClass,
12+
setAttr
13+
} from '../../utils/dom'
514
import { isBrowser } from '../../utils/env'
615
import { eventOn, eventOff } from '../../utils/events'
716
import { isString } from '../../utils/inspect'
@@ -51,19 +60,29 @@ export const EVENT_STATE_REQUEST = 'bv::request::collapse::state'
5160

5261
const KEYDOWN_KEY_CODES = [ENTER, SPACE]
5362

63+
const RX_HASH = /^#/
64+
const RX_HASH_ID = /^#[A-Za-z]+[\w\-:.]*$/
5465
const RX_SPLIT_SEPARATOR = /\s+/
5566

5667
// --- Helper methods ---
5768

58-
const isNonStandardTag = el => !arrayIncludes(['BUTTON', 'A'], el.tagName)
69+
const isNonStandardTag = el => !arrayIncludes(['button', 'a'], el.tagName.toLowerCase())
5970

60-
const getTargets = ({ modifiers, arg, value }) => {
71+
const getTargets = ({ modifiers, arg, value }, el) => {
6172
// Any modifiers are considered target IDs
6273
const targets = keys(modifiers || {})
6374

6475
// If value is a string, split out individual targets (if space delimited)
6576
value = isString(value) ? value.split(RX_SPLIT_SEPARATOR) : value
6677

78+
// Support target ID as link href (`href="#id"`)
79+
if (isTag(el.tagName, 'a')) {
80+
const href = getAttr(el, 'href') || ''
81+
if (RX_HASH_ID.test(href)) {
82+
targets.push(href.replace(RX_HASH, ''))
83+
}
84+
}
85+
6786
// Add ID from `arg` (if provided), and support value
6887
// as a single string ID or an array of string IDs
6988
// If `value` is not an array or string, then it gets filtered out
@@ -172,7 +191,7 @@ const handleUpdate = (el, binding, vnode) => {
172191
setToggleState(el, el[BV_TOGGLE_STATE])
173192

174193
// Parse list of target IDs
175-
const targets = getTargets(binding)
194+
const targets = getTargets(binding, el)
176195

177196
/* istanbul ignore else */
178197
// Ensure the `aria-controls` hasn't been overwritten

0 commit comments

Comments
 (0)