srcdev-nuxt-components 9.3.5 → 9.3.6
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/.claude/skills/components/expanding-panel.md +7 -5
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +7 -7
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +10 -5
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +47 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +12 -4
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +8 -6
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +8 -6
- package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +5 -5
- package/package.json +1 -1
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
`ExpandingPanel` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close by styling the `::details-content` pseudo-element (the browser's own anonymous box wrapping everything after `<summary>`), supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `AccordianCore`).
|
|
5
|
+
`ExpandingPanel` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close by styling the `::details-content` pseudo-element (the browser's own anonymous box wrapping everything after `<summary>`) where supported, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `AccordianCore`).
|
|
6
6
|
|
|
7
|
-
`::details-content
|
|
7
|
+
**WebKit (Safari/iOS) does not support `::details-content`** as of 2026-08, confirmed via real-device testing — Chrome's device-emulation mode cannot catch this, since it's still the Blink engine underneath regardless of which browser UI it's simulating. WebKit silently drops the whole rule block: no console warning, no error, just no animation and (more seriously) `content-is-on-top`'s `position: absolute` never applying, so an overlay panel renders in normal document flow and can break the surrounding layout. Fixed 2026-08-22: `ExpandingPanel` now carries an `@supports not selector(::details-content)` fallback block that reproduces `ExpandingPanelClassic`'s `grid-template-rows` technique automatically — no consumer action needed, this works transparently on every browser. See pitfall #19 in the repo's `CLAUDE.md` for the full mechanism.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`ExpandingPanelClassic` still exists as a standalone option — same props/model/slots API, always uses the `grid-template-rows` implementation with no pseudo-element dependency at all — but there's no longer a compatibility reason to prefer it over `ExpandingPanel`.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -145,7 +145,9 @@ Always supply a meaningful `name` prop when using multiple panels on the same pa
|
|
|
145
145
|
|
|
146
146
|
## Styling the content when contentIsOnTop
|
|
147
147
|
|
|
148
|
-
`background-color`, `padding`, and a shadow can be set directly on `.expanding-panel-content` (or on a wrapper inside the `#content` slot — either works)
|
|
148
|
+
`background-color`, `padding`, and a shadow can be set directly on `.expanding-panel-content` (or on a wrapper inside the `#content` slot — either works) in browsers that support `::details-content`: the clipping boundary there is `::details-content` itself (`overflow: clip`, animated `height`), and `.expanding-panel-content` is a *child* of that box, so its own box is correctly clipped regardless of where the styling lives.
|
|
149
|
+
|
|
150
|
+
In the WebKit fallback path (no `::details-content` support), `.expanding-panel-content` itself is the animated grid track (`grid-template-rows: 0fr → 1fr`) and remains the element being sized — so styling placed directly on it is still clipped correctly there too. The one structural difference: the fallback wraps the `#content` slot in an internal `.inner` div (needed so a plain child's `min-height: auto` doesn't stop the row collapsing to true `0`) — this is purely an implementation detail for the collapse mechanism and isn't a styling hook; don't target `.inner` directly, style `.expanding-panel-content` or the slot's own markup instead, same as the `::details-content` path.
|
|
149
151
|
|
|
150
152
|
---
|
|
151
153
|
|
|
@@ -211,7 +213,7 @@ See [component-local-style-override.md](../component-local-style-override.md) fo
|
|
|
211
213
|
|
|
212
214
|
## Notes
|
|
213
215
|
|
|
214
|
-
- The open/close animation targets `::details-content` (`height: 0 → auto`, `overflow: clip`) — no JS height measurement needed. Requires `interpolate-size: allow-keywords`
|
|
216
|
+
- The open/close animation targets `::details-content` (`height: 0 → auto`, `overflow: clip`) where supported — no JS height measurement needed. Requires `interpolate-size: allow-keywords` for the height transition itself (Chromium only as of 2026; other browsers with `::details-content` support still toggle correctly, just instantly). WebKit has neither, and falls back to `ExpandingPanelClassic`'s `grid-template-rows` technique automatically (see the Overview section above) — that path animates in every browser regardless of `interpolate-size`.
|
|
215
217
|
- `content-is-on-top` is applied to the root `.expanding-panel` element, not `.expanding-panel-content` — style overrides must scope through it, e.g. `.expanding-panel.my-panel .expanding-panel-content { ... }`.
|
|
216
218
|
- When `forceOpened` is `true`, `open` stays `true` regardless of `v-model`, but `v-model` still updates internally on clicks (useful if you later set `forceOpened` back to `false`).
|
|
217
219
|
- Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `AccordianCore` which handles this automatically.
|
|
@@ -8,7 +8,7 @@ exports[`AccordianCore > renders correct HTML structure with all props set 1`] =
|
|
|
8
8
|
<div class="label-wrapper"></div>
|
|
9
9
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
10
10
|
</summary>
|
|
11
|
-
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"></div>
|
|
11
|
+
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"><div class="inner"></div></div>
|
|
12
12
|
</details>
|
|
13
13
|
</div>
|
|
14
14
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
@@ -17,7 +17,7 @@ exports[`AccordianCore > renders correct HTML structure with all props set 1`] =
|
|
|
17
17
|
<div class="label-wrapper"></div>
|
|
18
18
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
19
19
|
</summary>
|
|
20
|
-
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"></div>
|
|
20
|
+
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"><div class="inner"></div></div>
|
|
21
21
|
</details>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
@@ -26,7 +26,7 @@ exports[`AccordianCore > renders correct HTML structure with all props set 1`] =
|
|
|
26
26
|
<div class="label-wrapper"></div>
|
|
27
27
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
28
28
|
</summary>
|
|
29
|
-
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"></div>
|
|
29
|
+
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"><div class="inner"></div></div>
|
|
30
30
|
</details>
|
|
31
31
|
</div>
|
|
32
32
|
</div>"
|
|
@@ -40,7 +40,7 @@ exports[`AccordianCore > renders correct HTML structure with default props 1`] =
|
|
|
40
40
|
<div class="label-wrapper"></div>
|
|
41
41
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
42
42
|
</summary>
|
|
43
|
-
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"></div>
|
|
43
|
+
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"><div class="inner"></div></div>
|
|
44
44
|
</details>
|
|
45
45
|
</div>
|
|
46
46
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
@@ -49,7 +49,7 @@ exports[`AccordianCore > renders correct HTML structure with default props 1`] =
|
|
|
49
49
|
<div class="label-wrapper"></div>
|
|
50
50
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
51
51
|
</summary>
|
|
52
|
-
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"></div>
|
|
52
|
+
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"><div class="inner"></div></div>
|
|
53
53
|
</details>
|
|
54
54
|
</div>
|
|
55
55
|
</div>"
|
|
@@ -63,7 +63,7 @@ exports[`AccordianCore > renders correct HTML structure with populated slots 1`]
|
|
|
63
63
|
<div class="label-wrapper"><span>Summary 0</span></div>
|
|
64
64
|
<div class="icon-wrapper"><span>Icon 0</span></div>
|
|
65
65
|
</summary>
|
|
66
|
-
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"><span>Content 0</span></div>
|
|
66
|
+
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"><div class="inner"><span>Content 0</span></div></div>
|
|
67
67
|
</details>
|
|
68
68
|
</div>
|
|
69
69
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
@@ -72,7 +72,7 @@ exports[`AccordianCore > renders correct HTML structure with populated slots 1`]
|
|
|
72
72
|
<div class="label-wrapper"><span>Summary 1</span></div>
|
|
73
73
|
<div class="icon-wrapper"><span>Icon 1</span></div>
|
|
74
74
|
</summary>
|
|
75
|
-
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"><span>Content 1</span></div>
|
|
75
|
+
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"><div class="inner"><span>Content 1</span></div></div>
|
|
76
76
|
</details>
|
|
77
77
|
</div>
|
|
78
78
|
</div>"
|
|
@@ -90,11 +90,16 @@ visual style, pass a modifier class:
|
|
|
90
90
|
- `--expanding-panel-content-gap` and `--expanding-panel-content-z-index` only take effect when
|
|
91
91
|
`contentIsOnTop` is `true` — they're no-ops for the default (in-flow) layout.
|
|
92
92
|
- `background-color`, `padding`, `border`, and shadow are all safe to set directly on
|
|
93
|
-
`.expanding-panel-content`.
|
|
94
|
-
|
|
95
|
-
`.expanding-panel-content` is a child of
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
`.expanding-panel-content`. Where `::details-content` is supported (not WebKit — see below), the
|
|
94
|
+
open/close animation clips via `overflow: clip` on that pseudo-element, and
|
|
95
|
+
`.expanding-panel-content` is a child of it, so its own box is correctly clipped to the animated
|
|
96
|
+
height while closed/closing. A wrapper inside the `#content` slot works equally well if you
|
|
97
|
+
prefer to keep the styling scoped to your own markup.
|
|
98
|
+
- WebKit (Safari/iOS) doesn't support `::details-content` — `ExpandingPanel` falls back to
|
|
99
|
+
`ExpandingPanelClassic`'s `grid-template-rows` technique there automatically, no consumer action
|
|
100
|
+
needed. That path wraps the `#content` slot in an internal `.inner` div (an implementation
|
|
101
|
+
detail of the collapse mechanism, not a styling hook) but styling on `.expanding-panel-content`
|
|
102
|
+
itself still works correctly, same as the `::details-content` path.
|
|
98
103
|
- Don't stack multiple `contentIsOnTop` panels as direct siblings (linked via a shared `name` or
|
|
99
104
|
not). The overlay is absolutely positioned so it doesn't push the next element down — which
|
|
100
105
|
means a sibling `ExpandingPanel` placed right after it sits exactly where the overlay renders,
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
:aria-labelledby="`id-${name}-trigger`"
|
|
24
24
|
role="region"
|
|
25
25
|
>
|
|
26
|
-
<
|
|
26
|
+
<div class="inner">
|
|
27
|
+
<slot name="content"></slot>
|
|
28
|
+
</div>
|
|
27
29
|
</div>
|
|
28
30
|
</details>
|
|
29
31
|
</div>
|
|
@@ -187,6 +189,50 @@ const onDetailsToggle = (event: Event) => {
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
}
|
|
192
|
+
|
|
193
|
+
/* ::details-content isn't supported in WebKit (Safari/iOS) as of 2026-08 — the two rule
|
|
194
|
+
blocks above are silently dropped there entirely (no error, no animation, and crucially
|
|
195
|
+
content-is-on-top's position: absolute never applies, so the overlay renders in normal
|
|
196
|
+
document flow instead). This reproduces both using ExpandingPanelClassic's
|
|
197
|
+
grid-template-rows technique (broadly supported, including Safari), targeting the real
|
|
198
|
+
.expanding-panel-content element (and its .inner child, matching Classic's structure —
|
|
199
|
+
.inner's own overflow:hidden is what lets the grid row actually collapse to 0, since a
|
|
200
|
+
plain block child's default min-height:auto would otherwise floor it at content height)
|
|
201
|
+
instead of the pseudo-element. */
|
|
202
|
+
@supports not selector(::details-content) {
|
|
203
|
+
.expanding-panel-details {
|
|
204
|
+
.expanding-panel-content {
|
|
205
|
+
display: grid;
|
|
206
|
+
grid-template-rows: 0fr;
|
|
207
|
+
transition: grid-template-rows v-bind(animationDurationStr) ease-in-out;
|
|
208
|
+
will-change: grid-template-rows;
|
|
209
|
+
|
|
210
|
+
.inner {
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
margin-top: 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&[open] .expanding-panel-content {
|
|
217
|
+
grid-template-rows: 1fr;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
&.content-is-on-top {
|
|
223
|
+
@supports not selector(::details-content) {
|
|
224
|
+
.expanding-panel-details {
|
|
225
|
+
position: relative;
|
|
226
|
+
|
|
227
|
+
.expanding-panel-content {
|
|
228
|
+
position: absolute;
|
|
229
|
+
top: calc(100% + var(--expanding-panel-content-gap, 0px));
|
|
230
|
+
inset-inline: 0;
|
|
231
|
+
z-index: var(--expanding-panel-content-z-index, 10);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
190
236
|
}
|
|
191
237
|
}
|
|
192
238
|
</style>
|
|
@@ -7,7 +7,9 @@ exports[`ExpandingPanel > renders correct HTML structure with all props set 1`]
|
|
|
7
7
|
<div class="label-wrapper"></div>
|
|
8
8
|
<div class="icon-wrapper icon-wrapper--hidden"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
9
9
|
</summary>
|
|
10
|
-
<div id="id-snap-all-content" class="expanding-panel-content" aria-labelledby="id-snap-all-trigger" role="region"
|
|
10
|
+
<div id="id-snap-all-content" class="expanding-panel-content" aria-labelledby="id-snap-all-trigger" role="region">
|
|
11
|
+
<div class="inner"></div>
|
|
12
|
+
</div>
|
|
11
13
|
</details>
|
|
12
14
|
</div>"
|
|
13
15
|
`;
|
|
@@ -19,7 +21,9 @@ exports[`ExpandingPanel > renders correct HTML structure with contentIsOnTop tru
|
|
|
19
21
|
<div class="label-wrapper"></div>
|
|
20
22
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
21
23
|
</summary>
|
|
22
|
-
<div id="id-snap-content-on-top-content" class="expanding-panel-content" aria-labelledby="id-snap-content-on-top-trigger" role="region"
|
|
24
|
+
<div id="id-snap-content-on-top-content" class="expanding-panel-content" aria-labelledby="id-snap-content-on-top-trigger" role="region">
|
|
25
|
+
<div class="inner"></div>
|
|
26
|
+
</div>
|
|
23
27
|
</details>
|
|
24
28
|
</div>"
|
|
25
29
|
`;
|
|
@@ -31,7 +35,9 @@ exports[`ExpandingPanel > renders correct HTML structure with default props 1`]
|
|
|
31
35
|
<div class="label-wrapper"></div>
|
|
32
36
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
33
37
|
</summary>
|
|
34
|
-
<div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region"
|
|
38
|
+
<div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region">
|
|
39
|
+
<div class="inner"></div>
|
|
40
|
+
</div>
|
|
35
41
|
</details>
|
|
36
42
|
</div>"
|
|
37
43
|
`;
|
|
@@ -44,7 +50,9 @@ exports[`ExpandingPanel > renders correct HTML structure with populated slots 1`
|
|
|
44
50
|
<div class="icon-wrapper"><span>▸</span></div>
|
|
45
51
|
</summary>
|
|
46
52
|
<div id="id-snap-slots-content" class="expanding-panel-content" aria-labelledby="id-snap-slots-trigger" role="region">
|
|
47
|
-
<
|
|
53
|
+
<div class="inner">
|
|
54
|
+
<p>Content text</p>
|
|
55
|
+
</div>
|
|
48
56
|
</div>
|
|
49
57
|
</details>
|
|
50
58
|
</div>"
|
|
@@ -13,12 +13,14 @@ exports[`NavigationItems > renders correct HTML structure with default props 1`]
|
|
|
13
13
|
<div class="icon-wrapper"><span class="iconify i-mdi:chevron-down icon" aria-hidden="true"></span></div>
|
|
14
14
|
</summary>
|
|
15
15
|
<div id="id-overflow-navigation-group-content" class="expanding-panel-content" aria-labelledby="id-overflow-navigation-group-trigger" role="region">
|
|
16
|
-
<div class="
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
<div class="inner">
|
|
17
|
+
<div class="overflow-navigation-sub-nav-inner">
|
|
18
|
+
<ul class="overflow-navigation-sub-nav-list">
|
|
19
|
+
<li class="overflow-navigation-sub-nav-item"><a href="/forms/examples/buttons" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Buttons</span></a></li>
|
|
20
|
+
<li class="overflow-navigation-sub-nav-item"><a href="/ui/tabs" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Tabs</span></a></li>
|
|
21
|
+
</ul>
|
|
22
|
+
<div aria-hidden="true" class="overflow-sub-nav-indicator-hovered"></div>
|
|
23
|
+
</div>
|
|
22
24
|
</div>
|
|
23
25
|
</div>
|
|
24
26
|
</details>
|
|
@@ -46,12 +46,14 @@ exports[`ResponsiveHeader > renders correct HTML structure with default props 1`
|
|
|
46
46
|
<div class="icon-wrapper"><span class="iconify i-mdi:chevron-down icon" aria-hidden="true"></span></div>
|
|
47
47
|
</summary>
|
|
48
48
|
<div id="id-overflow-navigation-group-content" class="expanding-panel-content" aria-labelledby="id-overflow-navigation-group-trigger" role="region">
|
|
49
|
-
<div class="
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
<div class="inner">
|
|
50
|
+
<div class="overflow-navigation-sub-nav-inner">
|
|
51
|
+
<ul class="overflow-navigation-sub-nav-list">
|
|
52
|
+
<li class="overflow-navigation-sub-nav-item"><a href="/forms/examples/buttons" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Buttons</span></a></li>
|
|
53
|
+
<li class="overflow-navigation-sub-nav-item"><a href="/ui/tabs" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Tabs</span></a></li>
|
|
54
|
+
</ul>
|
|
55
|
+
<div aria-hidden="true" class="overflow-sub-nav-indicator-hovered"></div>
|
|
56
|
+
</div>
|
|
55
57
|
</div>
|
|
56
58
|
</div>
|
|
57
59
|
</details>
|
|
@@ -67,12 +67,12 @@ exports[`ServicesSection > renders correct HTML structure 1`] = `
|
|
|
67
67
|
<div class="label-wrapper">FAQ question?</div>
|
|
68
68
|
<div class="icon-wrapper"><span class="iconify i-mdi:chevron-down" aria-hidden="true"></span></div>
|
|
69
69
|
</summary>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
<div id="id-faq-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-faq-v-0-0-0-trigger" role="region">
|
|
71
|
+
<div class="inner">
|
|
72
|
+
<p class="services-section__faq-answer">FAQ answer.</p>
|
|
73
|
+
</div>
|
|
74
74
|
</div>
|
|
75
|
-
</
|
|
75
|
+
</details>
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
<div class="glass-panel services-section__glass-panel p-24">
|