srcdev-nuxt-components 9.2.10 → 9.3.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/.claude/settings.json +14 -0
- package/.claude/skills/components/content-docs.md +16 -1
- package/.claude/skills/components/expanding-panel.md +9 -1
- package/app/app.config.ts +1 -1
- package/app/assets/styles/setup/01.config/index.css +2 -2
- package/app/assets/styles/setup/02.colours/index.css +8 -8
- package/app/assets/styles/setup/03.theming/index.css +6 -6
- package/app/assets/styles/setup/04.elements/forms/index.css +2 -2
- package/app/assets/styles/setup/04.elements/index.css +1 -1
- package/app/assets/styles/setup/05.typography/01.tokens/index.css +3 -3
- package/app/assets/styles/setup/05.typography/02.utility-classes/index.css +6 -6
- package/app/assets/styles/setup/05.typography/index.css +2 -2
- package/app/assets/styles/setup/06.utility-classes/animations/index.css +4 -4
- package/app/assets/styles/setup/06.utility-classes/index.css +3 -3
- package/app/assets/styles/setup/06.utility-classes/spacing/index.css +3 -3
- package/app/assets/styles/setup/a11y/index.css +1 -1
- package/app/assets/styles/setup/index.css +7 -7
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +16 -21
- package/app/components/01.atoms/content-wrappers/docs-pages/tests/ContentDocs.spec.ts +35 -1
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +9 -5
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +6 -0
- package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +9 -5
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +9 -5
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +5 -1
- package/app/components/alert-mask/tests/AlertMaskCore.spec.ts +9 -5
- package/app/composables/tests/useContainerBreakpoints.spec.ts +27 -7
- package/app/composables/tests/useWhatsApp.spec.ts +10 -4
- package/nuxt.config.ts +9 -0
- package/package.json +26 -21
package/.claude/settings.json
CHANGED
|
@@ -106,5 +106,19 @@
|
|
|
106
106
|
"/Users/simoncornforth/websites/nuxt-components/.claude/skills/components",
|
|
107
107
|
"/Users/simoncornforth/websites/nuxt-components/.claude/skills"
|
|
108
108
|
]
|
|
109
|
+
},
|
|
110
|
+
"hooks": {
|
|
111
|
+
"PostToolUse": [
|
|
112
|
+
{
|
|
113
|
+
"matcher": "Write|Edit",
|
|
114
|
+
"hooks": [
|
|
115
|
+
{
|
|
116
|
+
"type": "command",
|
|
117
|
+
"command": "f=$(jq -r '.tool_input.file_path // empty'); if [[ \"$f\" == */app/components/*.vue && \"$f\" != */tests/* && \"$f\" != */stories/* ]]; then name=$(basename \"$f\" .vue); skill=$(printf '%s' \"$name\" | sed -E 's/([a-z0-9])([A-Z])/\\1-\\2/g; s/([A-Z]+)([A-Z][a-z])/\\1-\\2/g' | tr '[:upper:]' '[:lower:]'); jq -n --arg msg \"Component file $f was edited/written. Per Claude.md Development Workflow step 7, check whether .claude/skills/components/$skill.md needs updating to reflect this change (props/slots/models/defaults/behaviour).\" '{hookSpecificOutput:{hookEventName:\"PostToolUse\",additionalContext:$msg}}'; fi",
|
|
118
|
+
"timeout": 15
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
]
|
|
109
123
|
}
|
|
110
124
|
}
|
|
@@ -36,7 +36,7 @@ interface DocsNavItem {
|
|
|
36
36
|
| `v-model:activeNavItem` | `string \| undefined` | `undefined` | The `to` of the currently-active `docsNav` item. Updates automatically on click; bind externally (e.g. to route matching) to control it. |
|
|
37
37
|
| `v-model:activePageNavItem` | `string \| undefined` | `undefined` | Same, for `docsPageNav`. |
|
|
38
38
|
|
|
39
|
-
Open/expanded state of the two panels is **not** exposed as a model — it's driven entirely by the container-width breakpoint logic (see below), not user-controllable.
|
|
39
|
+
Open/expanded state of the two panels is **not** exposed as a model to `ContentDocs`' own consumers — externally it's driven entirely by the container-width breakpoint logic (see below), not user-controllable. Internally, each panel is still a controlled `ExpandingPanel` (`v-model`, not left uncontrolled) so it can be closed programmatically — see "Mobile overlay dismissal" below.
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -93,6 +93,21 @@ Widths are measured on the component's own root element (`useContainerBreakpoint
|
|
|
93
93
|
| 768–1023px (tablet) | collapsible, closed by default | **forced open**, no toggle icon |
|
|
94
94
|
| ≥ 1024px (desktop) | **forced open**, no toggle icon | **forced open**, no toggle icon |
|
|
95
95
|
|
|
96
|
+
Below 1024px (i.e. whenever a panel isn't `forceOpened`), it's also `contentIsOnTop` — an overlay, not in-flow — since a collapsible nav panel pushing page content down/up as it opens reads as janky on mobile/tablet. See "Mobile overlay dismissal" below for how these are closed.
|
|
97
|
+
|
|
98
|
+
### Mobile overlay dismissal
|
|
99
|
+
|
|
100
|
+
Below 1024px, `docsNav`/`docsPageNav` are `contentIsOnTop` `ExpandingPanel`s (see the breakpoint table above), so they need an explicit way to close again beyond just toggling the summary:
|
|
101
|
+
|
|
102
|
+
- **Click outside** — inherited for free from `ExpandingPanel`'s own `contentIsOnTop` behaviour (see `expanding-panel.md`), no extra wiring in `ContentDocs` itself.
|
|
103
|
+
- **Clicking a nav link inside the open panel** — `ContentDocs` does not leave either `ExpandingPanel` uncontrolled: it holds its own `docsNavPanelOpen`/`docsPageNavPanelOpen` refs, bound via `v-model`, and each nav link's click handler sets its panel's ref to `false` in addition to updating `activeNavItem`/`activePageNavItem`. Without this, selecting a link would leave the overlay open on top of the page it just navigated to.
|
|
104
|
+
|
|
105
|
+
Both are no-ops at desktop/tablet where a panel is `forceOpened` — `open` there is `forceOpened || isPanelOpen`, so `forceOpened` wins regardless of what these refs are set to.
|
|
106
|
+
|
|
107
|
+
### DOM order vs visual order (accessibility)
|
|
108
|
+
|
|
109
|
+
In the template, `docsContent` is placed **before** `docsNav`/`docsPageNav` — the reverse of their left-to-right visual position at desktop. This is deliberate: `docsNav`/`docsPageNav` each render an `<h3>` heading (`docsNavLabel`/`docsPageNavLabel`), and if they came first in the DOM, a screen-reader user navigating by heading would hit one of those h3s *before* the consuming page's own `h1` inside `docsContent` — a broken heading outline. Visual position is unaffected because layout comes from `grid-template-areas` (see the `<style>` block), not source order. The trade-off: default Tab order now reaches main content before the side nav — an accepted consequence, not a regression to "fix" back by reverting the order.
|
|
110
|
+
|
|
96
111
|
### Why the two panels share a `name` only on mobile
|
|
97
112
|
|
|
98
113
|
Both panels are `ExpandingPanel`s using the native `<details name="...">` grouping feature, which makes same-named panels mutually exclusive (browser force-closes one when the other opens). That's the wanted behaviour on mobile (accordion — only one open at a time), but at tablet/desktop both panels must be open **simultaneously** — a shared name there would make the browser silently force-close one of them the moment both try to be open. So the component computes distinct names (`"docsNav"` / `"docsPageNav"`) once past mobile, and a shared name (`"docsPanelGroup"`) only while mobile.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| `name` | `string` | `useId()` | Identifies the panel. Used in ARIA attributes (`id-{name}-trigger`, `id-{name}-content`). If omitted, a unique id is generated automatically. |
|
|
14
14
|
| `animationDuration` | `number` | `400` | Expand/collapse transition duration in milliseconds. Pass `0` to disable animation. |
|
|
15
15
|
| `forceOpened` | `boolean` | `false` | When `true`, the panel is always open. The toggle icon is hidden and clicks do not close the panel. |
|
|
16
|
-
| `contentIsOnTop` | `boolean` | `false` | When `true`, the content region is taken out of flow and absolutely positioned directly below the summary, raised above surrounding page content via `z-index` — instead of pushing layout down when it opens. Applies a `content-is-on-top` class to the root `.expanding-panel` element (not the content div). |
|
|
16
|
+
| `contentIsOnTop` | `boolean` | `false` | When `true`, the content region is taken out of flow and absolutely positioned directly below the summary, raised above surrounding page content via `z-index` — instead of pushing layout down when it opens. Applies a `content-is-on-top` class to the root `.expanding-panel` element (not the content div). Also enables click-outside-to-close (see below) — not applied when `forceOpened` is `true`. |
|
|
17
17
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.expanding-panel` element. |
|
|
18
18
|
|
|
19
19
|
## Model
|
|
@@ -157,6 +157,14 @@ Why: `.expanding-panel-content` collapses via `grid-template-rows: 0fr → 1fr`,
|
|
|
157
157
|
|
|
158
158
|
---
|
|
159
159
|
|
|
160
|
+
## Click-outside-to-close (contentIsOnTop only)
|
|
161
|
+
|
|
162
|
+
When `contentIsOnTop` is `true` and the panel is open, clicking anywhere outside the panel's root element closes it (via `@vueuse/core`'s `onClickOutside`, sets `v-model` to `false`) — matching the dismissal behaviour of a native `<select>` or dropdown menu, since that's what an overlay panel functionally is. This does **not** apply to ordinary in-flow panels (`contentIsOnTop: false`, the default) — an inline accordion staying open when you click elsewhere on the page is expected, not a bug. It also never applies when `forceOpened` is `true`, regardless of `contentIsOnTop` — a forced-open panel isn't dismissible by any interaction.
|
|
163
|
+
|
|
164
|
+
If you also want the panel to close when something *inside* it is activated (e.g. a nav link) — clicking outside doesn't cover that case — set `v-model` to `false` from that element's own click handler, same as any other controlled usage. See `ContentDocs`' `docsNav`/`docsPageNav` panels for a real example: both are `contentIsOnTop` on mobile, and each nav link closes its panel on click in addition to relying on click-outside.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
160
168
|
## CSS Token Customization
|
|
161
169
|
|
|
162
170
|
All `--expanding-panel-*` tokens can be overridden at global, page, or instance scope. See `CONSUMER-STYLING.md` in the component directory for full token documentation and examples.
|
package/app/app.config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SemanticTheme, DisplayPromptTheme, DisplayToastTheme, DisplayToastPosition, DisplayToastAlignment } from "
|
|
1
|
+
import type { SemanticTheme, DisplayPromptTheme, DisplayToastTheme, DisplayToastPosition, DisplayToastAlignment } from "./types/components";
|
|
2
2
|
|
|
3
3
|
export default defineAppConfig({
|
|
4
4
|
srcdev: {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
@import "./_normalise";
|
|
2
|
-
@import "./_head";
|
|
1
|
+
@import "./_normalise.css";
|
|
2
|
+
@import "./_head.css";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
@import "./_theme-params";
|
|
2
|
-
@import "./_amber";
|
|
3
|
-
@import "./_blue";
|
|
4
|
-
@import "./_green";
|
|
5
|
-
@import "./_orange";
|
|
6
|
-
@import "./_red";
|
|
7
|
-
@import "./_slate";
|
|
8
|
-
@import "./_sunset";
|
|
1
|
+
@import "./_theme-params.css";
|
|
2
|
+
@import "./_amber.css";
|
|
3
|
+
@import "./_blue.css";
|
|
4
|
+
@import "./_green.css";
|
|
5
|
+
@import "./_orange.css";
|
|
6
|
+
@import "./_red.css";
|
|
7
|
+
@import "./_slate.css";
|
|
8
|
+
@import "./_sunset.css";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
@import "./theme-ramp";
|
|
2
|
-
@import "./_theme-slots";
|
|
3
|
-
@import "./_default";
|
|
4
|
-
@import "./_error";
|
|
5
|
-
@import "./_success";
|
|
6
|
-
@import "./_warning";
|
|
1
|
+
@import "./theme-ramp.css";
|
|
2
|
+
@import "./_theme-slots.css";
|
|
3
|
+
@import "./_default.css";
|
|
4
|
+
@import "./_error.css";
|
|
5
|
+
@import "./_success.css";
|
|
6
|
+
@import "./_warning.css";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
@import "./00.element-defaults";
|
|
1
|
+
@import "./00.element-defaults.css";
|
|
2
2
|
@import "./01.field-layout-container-level.css";
|
|
3
3
|
@import "./02.typography.css";
|
|
4
|
-
@import "./03.generic-input-geometry";
|
|
4
|
+
@import "./03.generic-input-geometry.css";
|
|
5
5
|
@import "./04.slot-icon-system.css";
|
|
6
6
|
@import "./05.checkbox-radio-geometry.css";
|
|
7
7
|
@import "./06.button-geometry.css";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@import "./forms/";
|
|
1
|
+
@import "./forms/index.css";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
@import "./_font-family";
|
|
2
|
-
@import "./_reponsive-font-sizes";
|
|
3
|
-
@import "./_timing-functions";
|
|
1
|
+
@import "./_font-family.css";
|
|
2
|
+
@import "./_reponsive-font-sizes.css";
|
|
3
|
+
@import "./_timing-functions.css";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
@import "./_font-classes-page-heading";
|
|
2
|
-
@import "./_font-classes-page-body";
|
|
3
|
-
@import "./_font-classes-page-link";
|
|
4
|
-
@import "./_font-classes-section";
|
|
5
|
-
@import "./_font-classes-article";
|
|
6
|
-
@import "./_font-classes-card";
|
|
1
|
+
@import "./_font-classes-page-heading.css";
|
|
2
|
+
@import "./_font-classes-page-body.css";
|
|
3
|
+
@import "./_font-classes-page-link.css";
|
|
4
|
+
@import "./_font-classes-section.css";
|
|
5
|
+
@import "./_font-classes-article.css";
|
|
6
|
+
@import "./_font-classes-card.css";
|
|
7
7
|
@import "./_generic-font-classes.css";
|
|
8
8
|
@import "./_generic-font-variation-settings.css";
|
|
9
9
|
@import "./_generic-font-weights.css";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
@import "./01.tokens";
|
|
2
|
-
@import "./02.utility-classes";
|
|
1
|
+
@import "./01.tokens/index.css";
|
|
2
|
+
@import "./02.utility-classes/index.css";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
@import "./_entry-zoom-reveal";
|
|
2
|
-
@import "./_entry-slide-in";
|
|
3
|
-
@import "./_entry-exit-blur";
|
|
4
|
-
@import "./_auto-rotate";
|
|
1
|
+
@import "./_entry-zoom-reveal.css";
|
|
2
|
+
@import "./_entry-slide-in.css";
|
|
3
|
+
@import "./_entry-exit-blur.css";
|
|
4
|
+
@import "./_auto-rotate.css";
|
|
5
5
|
@import "./_animation-scroller-x.css";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import "./animations/";
|
|
2
|
-
@import "./spacing/";
|
|
1
|
+
@import "./animations/index.css";
|
|
2
|
+
@import "./spacing/index.css";
|
|
3
3
|
/* @import "./layout/"; */
|
|
4
|
-
@import "./_a11y";
|
|
4
|
+
@import "./_a11y.css";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
@import "./_fluid-spacing";
|
|
2
|
-
@import "./_margin";
|
|
3
|
-
@import "./_padding";
|
|
1
|
+
@import "./_fluid-spacing.css";
|
|
2
|
+
@import "./_margin.css";
|
|
3
|
+
@import "./_padding.css";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@import "./_variables";
|
|
1
|
+
@import "./_variables.css";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
@layer reset, colours, theming, form-tokens, typography, a11y, components, utilities, consumer;
|
|
2
2
|
|
|
3
|
-
@import "./01.config/" layer(reset);
|
|
4
|
-
@import "./02.colours/" layer(colours);
|
|
5
|
-
@import "./03.theming/" layer(theming);
|
|
6
|
-
@import "./04.elements/" layer(form-tokens);
|
|
7
|
-
@import "./05.typography/" layer(typography);
|
|
8
|
-
@import "./a11y/" layer(a11y);
|
|
9
|
-
@import "./06.utility-classes/" layer(utilities);
|
|
3
|
+
@import "./01.config/index.css" layer(reset);
|
|
4
|
+
@import "./02.colours/index.css" layer(colours);
|
|
5
|
+
@import "./03.theming/index.css" layer(theming);
|
|
6
|
+
@import "./04.elements/index.css" layer(form-tokens);
|
|
7
|
+
@import "./05.typography/index.css" layer(typography);
|
|
8
|
+
@import "./a11y/index.css" layer(a11y);
|
|
9
|
+
@import "./06.utility-classes/index.css" layer(utilities);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<component :is="tag" ref="rootEl" class="content-docs" :class="[elementClasses]">
|
|
3
3
|
<div class="content-docs-inner">
|
|
4
|
+
<div v-if="hasDocsContent" class="docs-content">
|
|
5
|
+
<slot name="docsContent"></slot>
|
|
6
|
+
</div>
|
|
4
7
|
<div v-if="hasDocsNav" class="docs-nav">
|
|
5
8
|
<ExpandingPanel
|
|
9
|
+
v-model="docsNavPanelOpen"
|
|
6
10
|
:name="docsNavPanelName"
|
|
7
11
|
:animation-duration="200"
|
|
8
12
|
:force-opened="docsNavForceOpen"
|
|
@@ -21,7 +25,10 @@
|
|
|
21
25
|
class="docs-nav-link"
|
|
22
26
|
:class="{ 'is-active': item.to === activeNavItem }"
|
|
23
27
|
:aria-current="item.to === activeNavItem ? 'page' : undefined"
|
|
24
|
-
@click="
|
|
28
|
+
@click="
|
|
29
|
+
activeNavItem = item.to;
|
|
30
|
+
docsNavPanelOpen = false;
|
|
31
|
+
"
|
|
25
32
|
>
|
|
26
33
|
<Icon v-if="item.icon" :name="item.icon" class="docs-nav-link-icon" aria-hidden="true" />
|
|
27
34
|
<span class="docs-nav-link-label">{{ item.label }}</span>
|
|
@@ -32,11 +39,9 @@
|
|
|
32
39
|
</template>
|
|
33
40
|
</ExpandingPanel>
|
|
34
41
|
</div>
|
|
35
|
-
<div v-if="hasDocsContent" class="docs-content">
|
|
36
|
-
<slot name="docsContent"></slot>
|
|
37
|
-
</div>
|
|
38
42
|
<div v-if="hasDocsPageNav" class="docs-page-nav">
|
|
39
43
|
<ExpandingPanel
|
|
44
|
+
v-model="docsPageNavPanelOpen"
|
|
40
45
|
:name="docsPageNavPanelName"
|
|
41
46
|
:animation-duration="200"
|
|
42
47
|
:force-opened="docsPageNavForceOpen"
|
|
@@ -55,7 +60,10 @@
|
|
|
55
60
|
class="docs-page-nav-link"
|
|
56
61
|
:class="{ 'is-active': item.to === activePageNavItem }"
|
|
57
62
|
:aria-current="item.to === activePageNavItem ? 'page' : undefined"
|
|
58
|
-
@click="
|
|
63
|
+
@click="
|
|
64
|
+
activePageNavItem = item.to;
|
|
65
|
+
docsPageNavPanelOpen = false;
|
|
66
|
+
"
|
|
59
67
|
>
|
|
60
68
|
<Icon v-if="item.icon" :name="item.icon" class="docs-page-nav-link-icon" aria-hidden="true" />
|
|
61
69
|
<span class="docs-page-nav-link-label">{{ item.label }}</span>
|
|
@@ -121,9 +129,9 @@ const docsNavOnTop = computed(() => !isDesktop.value);
|
|
|
121
129
|
const docsPageNavForceOpen = computed(() => !isMobile.value);
|
|
122
130
|
const docsPageNavOnTop = computed(() => isMobile.value);
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
const docsNavPanelOpen = ref(false);
|
|
133
|
+
const docsPageNavPanelOpen = ref(false);
|
|
134
|
+
|
|
127
135
|
const panelGroupId = useId();
|
|
128
136
|
const docsNavPanelName = computed(() => (isMobile.value ? panelGroupId : `${panelGroupId}-docsNav`));
|
|
129
137
|
const docsPageNavPanelName = computed(() => (isMobile.value ? panelGroupId : `${panelGroupId}-docsPageNav`));
|
|
@@ -132,7 +140,6 @@ const docsPageNavPanelName = computed(() => (isMobile.value ? panelGroupId : `${
|
|
|
132
140
|
<style lang="css">
|
|
133
141
|
@layer components {
|
|
134
142
|
.content-docs {
|
|
135
|
-
/* Shared defaults — theme both docsNav and docsPageNav at once */
|
|
136
143
|
--_heading-font-size: var(--content-docs-heading-font-size, 1.4rem);
|
|
137
144
|
--_heading-font-weight: var(--content-docs-heading-font-weight, 700);
|
|
138
145
|
--_heading-color: var(--content-docs-heading-color, light-dark(var(--slate-10), var(--slate-01)));
|
|
@@ -146,8 +153,6 @@ const docsPageNavPanelName = computed(() => (isMobile.value ? panelGroupId : `${
|
|
|
146
153
|
--_panel-padding-inline: var(--content-docs-panel-padding-inline, 0.8rem);
|
|
147
154
|
--_panel-border-radius: var(--content-docs-panel-border-radius, 0.5rem);
|
|
148
155
|
|
|
149
|
-
/* Fixed-width grid tracks — the docsNav/docsPageNav columns at tablet/desktop.
|
|
150
|
-
docsPageNav gets a narrower track at tablet since docsNav is full-width there. */
|
|
151
156
|
--_nav-column-width: var(--content-docs-nav-column-width, 23rem);
|
|
152
157
|
--_page-nav-column-width: var(--content-docs-page-nav-column-width, 22rem);
|
|
153
158
|
--_page-nav-column-width-tablet: var(--content-docs-page-nav-column-width-tablet, 20rem);
|
|
@@ -164,7 +169,6 @@ const docsPageNavPanelName = computed(() => (isMobile.value ? panelGroupId : `${
|
|
|
164
169
|
--_link-active-bg: var(--content-docs-link-active-bg, light-dark(var(--green-01), var(--green-09)));
|
|
165
170
|
--_link-active-color: var(--content-docs-link-active-color, light-dark(var(--green-10), var(--green-04)));
|
|
166
171
|
|
|
167
|
-
/* Per-side overrides — each falls back to the shared token above */
|
|
168
172
|
--_nav-heading-font-size: var(--content-docs-nav-heading-font-size, var(--_heading-font-size));
|
|
169
173
|
--_nav-heading-font-weight: var(--content-docs-nav-heading-font-weight, var(--_heading-font-weight));
|
|
170
174
|
--_nav-heading-color: var(--content-docs-nav-heading-color, var(--_heading-color));
|
|
@@ -313,15 +317,6 @@ const docsPageNavPanelName = computed(() => (isMobile.value ? panelGroupId : `${
|
|
|
313
317
|
}
|
|
314
318
|
}
|
|
315
319
|
|
|
316
|
-
/* Grid (not flex) so the label column stays aligned at a fixed offset whether or
|
|
317
|
-
not an item has an icon — an icon-less item's label still starts in column 2,
|
|
318
|
-
instead of collapsing back to column 1 like it would with flex.
|
|
319
|
-
|
|
320
|
-
Icon at the end: set --docs-nav-link-icon-order to rtl. This mirrors which
|
|
321
|
-
physical side each grid column renders on without touching column sizing —
|
|
322
|
-
swapping grid-column values directly would leave the label squeezed into the
|
|
323
|
-
icon-sized track instead. The label/icon direction resets further down undo
|
|
324
|
-
the mirroring for their own content so text and icon glyphs don't visually flip. */
|
|
325
320
|
.docs-nav-link {
|
|
326
321
|
display: grid;
|
|
327
322
|
grid-template-columns: var(--docs-nav-link-icon-size, 1.6rem) 1fr;
|
|
@@ -9,7 +9,9 @@ beforeEach(() => {
|
|
|
9
9
|
resizeCallback = null;
|
|
10
10
|
vi.stubGlobal(
|
|
11
11
|
"ResizeObserver",
|
|
12
|
-
|
|
12
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
13
|
+
// arrow function) to remain constructible via `new`.
|
|
14
|
+
vi.fn(function (callback: ResizeObserverCallback) {
|
|
13
15
|
resizeCallback = callback;
|
|
14
16
|
return {
|
|
15
17
|
observe: vi.fn(),
|
|
@@ -74,6 +76,16 @@ describe("ContentDocs", () => {
|
|
|
74
76
|
expect(wrapper.find('[data-testid="body"]').text()).toBe("Body");
|
|
75
77
|
});
|
|
76
78
|
|
|
79
|
+
it("places docsContent before docsNav and docsPageNav in DOM order, so a consumer's h1 is reachable before either nav panel's h3 heading (visual position is unaffected — grid-template-areas places all three, not source order)", async () => {
|
|
80
|
+
const wrapper = await mountSuspended(ContentDocs, {
|
|
81
|
+
props: { docsNavItems: navItems, docsPageNavItems: pageNavItems },
|
|
82
|
+
slots: { docsContent: "<h1>Title</h1>" },
|
|
83
|
+
});
|
|
84
|
+
const inner = wrapper.find(".content-docs-inner").element;
|
|
85
|
+
const children = [...inner.children].map((el) => el.className);
|
|
86
|
+
expect(children).toEqual(["docs-content", "docs-nav", "docs-page-nav"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
77
89
|
// ─── labels ─────────────────────────────────────────────────────────────
|
|
78
90
|
|
|
79
91
|
it("defaults docsNavLabel to 'Navigation' and docsPageNavLabel to 'On this page'", async () => {
|
|
@@ -142,6 +154,28 @@ describe("ContentDocs", () => {
|
|
|
142
154
|
expect(wrapper.emitted("update:activePageNavItem")?.[0]).toEqual(["/one#overview"]);
|
|
143
155
|
});
|
|
144
156
|
|
|
157
|
+
// ─── closing on link click (mobile overlay behaviour) ─────────────────────
|
|
158
|
+
|
|
159
|
+
it("closes the docsNav panel when a link inside it is clicked", async () => {
|
|
160
|
+
const wrapper = await mountSuspended(ContentDocs, { props: { docsNavItems: navItems } });
|
|
161
|
+
|
|
162
|
+
await wrapper.find(".docs-nav summary").trigger("click");
|
|
163
|
+
expect(wrapper.find(".docs-nav details").attributes("open")).toBeDefined();
|
|
164
|
+
|
|
165
|
+
await wrapper.find(".docs-nav").findAll("a")[0]?.trigger("click");
|
|
166
|
+
expect(wrapper.find(".docs-nav details").attributes("open")).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("closes the docsPageNav panel when a link inside it is clicked", async () => {
|
|
170
|
+
const wrapper = await mountSuspended(ContentDocs, { props: { docsPageNavItems: pageNavItems } });
|
|
171
|
+
|
|
172
|
+
await wrapper.find(".docs-page-nav summary").trigger("click");
|
|
173
|
+
expect(wrapper.find(".docs-page-nav details").attributes("open")).toBeDefined();
|
|
174
|
+
|
|
175
|
+
await wrapper.find(".docs-page-nav").find("a").trigger("click");
|
|
176
|
+
expect(wrapper.find(".docs-page-nav details").attributes("open")).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
145
179
|
// ─── container-width-driven forceOpened ────────────────────────────────────
|
|
146
180
|
|
|
147
181
|
describe("breakpoint-driven forceOpened", () => {
|
|
@@ -2,11 +2,15 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import AlertMaskedContent from "../AlertMaskedContent.vue";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
6
|
+
// arrow function) to remain constructible via `new`.
|
|
7
|
+
const mockResizeObserver = vi.fn(function () {
|
|
8
|
+
return {
|
|
9
|
+
observe: vi.fn(),
|
|
10
|
+
unobserve: vi.fn(),
|
|
11
|
+
disconnect: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
10
14
|
|
|
11
15
|
vi.stubGlobal("ResizeObserver", mockResizeObserver);
|
|
12
16
|
|
|
@@ -437,6 +437,12 @@ describe("ExpandingPanel", () => {
|
|
|
437
437
|
await wrapper.find("summary").trigger("click");
|
|
438
438
|
expect(vm.isPanelOpen).toBe(true);
|
|
439
439
|
|
|
440
|
+
// vueuse's onClickOutside debounces re-entrant clicks via a real setTimeout(0) that
|
|
441
|
+
// flips an internal "isProcessingClick" flag back off. With fake timers active
|
|
442
|
+
// (test/vitest.setup.ts) that flag never resets on its own, so the summary click
|
|
443
|
+
// above would otherwise permanently suppress the very next click. Advance past it.
|
|
444
|
+
vi.advanceTimersByTime(0);
|
|
445
|
+
|
|
440
446
|
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
441
447
|
await nextTick();
|
|
442
448
|
|
|
@@ -5,11 +5,15 @@ import SiteNavigation from "../SiteNavigation.vue";
|
|
|
5
5
|
import type { NavItemData } from "~/types/components/navigation-horizontal.d";
|
|
6
6
|
|
|
7
7
|
// useResizeObserver (from @vueuse/core) requires ResizeObserver
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
9
|
+
// arrow function) to remain constructible via `new`.
|
|
10
|
+
const mockResizeObserver = vi.fn(function () {
|
|
11
|
+
return {
|
|
12
|
+
observe: vi.fn(),
|
|
13
|
+
unobserve: vi.fn(),
|
|
14
|
+
disconnect: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
13
17
|
|
|
14
18
|
beforeEach(() => {
|
|
15
19
|
vi.stubGlobal("ResizeObserver", mockResizeObserver);
|
|
@@ -5,11 +5,15 @@ import TabNavigation from "../TabNavigation.vue";
|
|
|
5
5
|
import type { NavItemData } from "~/types/components/navigation-horizontal.d";
|
|
6
6
|
|
|
7
7
|
// useResizeObserver (from @vueuse/core) requires ResizeObserver
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
9
|
+
// arrow function) to remain constructible via `new`.
|
|
10
|
+
const mockResizeObserver = vi.fn(function () {
|
|
11
|
+
return {
|
|
12
|
+
observe: vi.fn(),
|
|
13
|
+
unobserve: vi.fn(),
|
|
14
|
+
disconnect: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
13
17
|
|
|
14
18
|
beforeEach(() => {
|
|
15
19
|
vi.stubGlobal("ResizeObserver", mockResizeObserver);
|
package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts
CHANGED
|
@@ -63,7 +63,11 @@ describe("SliderGallery", () => {
|
|
|
63
63
|
mockImage = { src: "", onload: null, onerror: null };
|
|
64
64
|
vi.stubGlobal(
|
|
65
65
|
"Image",
|
|
66
|
-
|
|
66
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
67
|
+
// arrow function) to remain constructible via `new`.
|
|
68
|
+
vi.fn(function () {
|
|
69
|
+
return mockImage;
|
|
70
|
+
})
|
|
67
71
|
);
|
|
68
72
|
});
|
|
69
73
|
|
|
@@ -23,11 +23,15 @@ interface AlertMaskCoreInstance {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Mock ResizeObserver
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
27
|
+
// arrow function) to remain constructible via `new`.
|
|
28
|
+
const mockResizeObserver = vi.fn(function () {
|
|
29
|
+
return {
|
|
30
|
+
observe: vi.fn(),
|
|
31
|
+
unobserve: vi.fn(),
|
|
32
|
+
disconnect: vi.fn(),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
31
35
|
|
|
32
36
|
vi.stubGlobal("ResizeObserver", mockResizeObserver);
|
|
33
37
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { nextTick, ref } from "vue";
|
|
3
3
|
import { useContainerBreakpoints } from "../useContainerBreakpoints";
|
|
4
4
|
|
|
@@ -8,7 +8,9 @@ beforeEach(() => {
|
|
|
8
8
|
resizeCallback = null;
|
|
9
9
|
vi.stubGlobal(
|
|
10
10
|
"ResizeObserver",
|
|
11
|
-
|
|
11
|
+
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
12
|
+
// arrow function) to remain constructible via `new`.
|
|
13
|
+
vi.fn(function (callback: ResizeObserverCallback) {
|
|
12
14
|
resizeCallback = callback;
|
|
13
15
|
return {
|
|
14
16
|
observe: vi.fn(),
|
|
@@ -19,6 +21,10 @@ beforeEach(() => {
|
|
|
19
21
|
);
|
|
20
22
|
});
|
|
21
23
|
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
document.body.innerHTML = "";
|
|
26
|
+
});
|
|
27
|
+
|
|
22
28
|
function triggerResize(el: HTMLElement, width: number) {
|
|
23
29
|
Object.defineProperty(el, "offsetWidth", { value: width, configurable: true });
|
|
24
30
|
resizeCallback?.(
|
|
@@ -27,15 +33,29 @@ function triggerResize(el: HTMLElement, width: number) {
|
|
|
27
33
|
);
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
// @vueuse/core's useElementSize reads the element's initial width in onMounted via
|
|
37
|
+
// `offsetWidth - computed padding/border`, ahead of any ResizeObserver callback. happy-dom's
|
|
38
|
+
// getComputedStyle() only resolves real "0px" values (rather than "") for padding/border on an
|
|
39
|
+
// element that's actually attached to the document — a detached `createElement` div keeps
|
|
40
|
+
// returning "", so parseFloat() turns the subtraction into NaN. Zero the box model AND attach
|
|
41
|
+
// the element so that initial-width read is a real, non-NaN value.
|
|
42
|
+
function createEl(): HTMLElement {
|
|
43
|
+
const el = document.createElement("div");
|
|
44
|
+
el.style.padding = "0px";
|
|
45
|
+
el.style.border = "0px";
|
|
46
|
+
document.body.appendChild(el);
|
|
47
|
+
return el;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
describe("useContainerBreakpoints", () => {
|
|
31
51
|
it("defaults active to base when narrower than the smallest breakpoint", () => {
|
|
32
|
-
const el = ref<HTMLElement | null>(
|
|
52
|
+
const el = ref<HTMLElement | null>(createEl());
|
|
33
53
|
const { active } = useContainerBreakpoints(undefined, el);
|
|
34
54
|
expect(active.value).toBe("base");
|
|
35
55
|
});
|
|
36
56
|
|
|
37
57
|
it("updates active as the observed element's width crosses breakpoints", async () => {
|
|
38
|
-
const el = ref<HTMLElement | null>(
|
|
58
|
+
const el = ref<HTMLElement | null>(createEl());
|
|
39
59
|
const { active } = useContainerBreakpoints(undefined, el);
|
|
40
60
|
|
|
41
61
|
triggerResize(el.value as HTMLElement, 700);
|
|
@@ -48,7 +68,7 @@ describe("useContainerBreakpoints", () => {
|
|
|
48
68
|
});
|
|
49
69
|
|
|
50
70
|
it("greaterOrEqual/smaller reflect the current width against a named breakpoint", async () => {
|
|
51
|
-
const el = ref<HTMLElement | null>(
|
|
71
|
+
const el = ref<HTMLElement | null>(createEl());
|
|
52
72
|
const { greaterOrEqual, smaller } = useContainerBreakpoints(undefined, el);
|
|
53
73
|
const isLg = greaterOrEqual("lg");
|
|
54
74
|
const isSmallerThanMd = smaller("md");
|
|
@@ -63,7 +83,7 @@ describe("useContainerBreakpoints", () => {
|
|
|
63
83
|
});
|
|
64
84
|
|
|
65
85
|
it("between returns true only within the [min, max) range", async () => {
|
|
66
|
-
const el = ref<HTMLElement | null>(
|
|
86
|
+
const el = ref<HTMLElement | null>(createEl());
|
|
67
87
|
const { between } = useContainerBreakpoints(undefined, el);
|
|
68
88
|
const isTablet = between("sm", "lg");
|
|
69
89
|
|
|
@@ -77,7 +97,7 @@ describe("useContainerBreakpoints", () => {
|
|
|
77
97
|
});
|
|
78
98
|
|
|
79
99
|
it("supports a custom breakpoints map", async () => {
|
|
80
|
-
const el = ref<HTMLElement | null>(
|
|
100
|
+
const el = ref<HTMLElement | null>(createEl());
|
|
81
101
|
const { active } = useContainerBreakpoints({ narrow: 400, wide: 900 }, el);
|
|
82
102
|
|
|
83
103
|
triggerResize(el.value as HTMLElement, 500);
|
|
@@ -6,8 +6,14 @@ const PHONE_NUMBER = "447700900000";
|
|
|
6
6
|
|
|
7
7
|
// mockNuxtImport intercepts Nuxt's auto-import resolution — vi.stubGlobal cannot.
|
|
8
8
|
// Use vi.hoisted so the mock function is available before module evaluation.
|
|
9
|
+
// Nuxt's own router plugin also calls useRuntimeConfig() during app init and
|
|
10
|
+
// reads `.app.baseURL` — the mock must include that shape too, or app
|
|
11
|
+
// initialization throws before the test body runs.
|
|
9
12
|
const { useRuntimeConfigMock } = vi.hoisted(() => ({
|
|
10
|
-
useRuntimeConfigMock: vi.fn(() => ({
|
|
13
|
+
useRuntimeConfigMock: vi.fn(() => ({
|
|
14
|
+
public: { whatsappNumber: PHONE_NUMBER },
|
|
15
|
+
app: { baseURL: "/" },
|
|
16
|
+
})),
|
|
11
17
|
}));
|
|
12
18
|
|
|
13
19
|
mockNuxtImport("useRuntimeConfig", () => useRuntimeConfigMock);
|
|
@@ -17,7 +23,7 @@ describe("useWhatsApp", () => {
|
|
|
17
23
|
|
|
18
24
|
beforeEach(() => {
|
|
19
25
|
windowOpenSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
|
20
|
-
useRuntimeConfigMock.mockReturnValue({ public: { whatsappNumber: PHONE_NUMBER } });
|
|
26
|
+
useRuntimeConfigMock.mockReturnValue({ public: { whatsappNumber: PHONE_NUMBER }, app: { baseURL: "/" } });
|
|
21
27
|
});
|
|
22
28
|
|
|
23
29
|
afterEach(() => {
|
|
@@ -85,14 +91,14 @@ describe("useWhatsApp", () => {
|
|
|
85
91
|
});
|
|
86
92
|
|
|
87
93
|
it("does not call window.open when number is not configured", () => {
|
|
88
|
-
useRuntimeConfigMock.mockReturnValue({ public: { whatsappNumber: "" } });
|
|
94
|
+
useRuntimeConfigMock.mockReturnValue({ public: { whatsappNumber: "" }, app: { baseURL: "/" } });
|
|
89
95
|
const { openWhatsApp } = useWhatsApp();
|
|
90
96
|
openWhatsApp([{ label: "Name", value: "Jane" }]);
|
|
91
97
|
expect(windowOpenSpy).not.toHaveBeenCalled();
|
|
92
98
|
});
|
|
93
99
|
|
|
94
100
|
it("logs a warning when number is not configured", () => {
|
|
95
|
-
useRuntimeConfigMock.mockReturnValue({ public: { whatsappNumber: "" } });
|
|
101
|
+
useRuntimeConfigMock.mockReturnValue({ public: { whatsappNumber: "" }, app: { baseURL: "/" } });
|
|
96
102
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
97
103
|
const { openWhatsApp } = useWhatsApp();
|
|
98
104
|
openWhatsApp([{ label: "Name", value: "Jane" }]);
|
package/nuxt.config.ts
CHANGED
|
@@ -130,6 +130,15 @@ export default defineNuxtConfig({
|
|
|
130
130
|
...(isStandalone ? ["vue-qrcode-reader"] : []),
|
|
131
131
|
],
|
|
132
132
|
},
|
|
133
|
+
// Vite 8 (pulled in by Nuxt 4.5) defaults build.cssMinify to "lightningcss" instead of
|
|
134
|
+
// "esbuild". lightningcss has an open bug where it drops the unprefixed `backdrop-filter`
|
|
135
|
+
// once autoprefixer adds `-webkit-backdrop-filter` alongside it, breaking glass/blur effects
|
|
136
|
+
// (see https://github.com/vitejs/vite/issues/22649) — this repo relies on backdrop-filter in
|
|
137
|
+
// several components (GlassPanel, SiteNavigation, TabNavigation, DisplayDialog, ColourFinder,
|
|
138
|
+
// MagneticNavigation). Pin back to esbuild until upstream fixes it.
|
|
139
|
+
build: {
|
|
140
|
+
cssMinify: "esbuild",
|
|
141
|
+
},
|
|
133
142
|
},
|
|
134
143
|
vue: {
|
|
135
144
|
runtimeCompiler: true,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.3.0",
|
|
5
5
|
"main": "nuxt.config.ts",
|
|
6
6
|
"types": "types.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -90,33 +90,35 @@
|
|
|
90
90
|
"@iconify-json/akar-icons": "1.2.7",
|
|
91
91
|
"@iconify-json/bi": "1.2.7",
|
|
92
92
|
"@iconify-json/bitcoin-icons": "1.2.5",
|
|
93
|
-
"@iconify-json/gravity-ui": "1.2.
|
|
93
|
+
"@iconify-json/gravity-ui": "1.2.14",
|
|
94
94
|
"@iconify-json/ic": "*",
|
|
95
|
-
"@iconify-json/logos": "1.2.
|
|
96
|
-
"@iconify-json/lucide": "
|
|
97
|
-
"@iconify-json/material-symbols": "1.2.
|
|
95
|
+
"@iconify-json/logos": "1.2.13",
|
|
96
|
+
"@iconify-json/lucide": "1.2.124",
|
|
97
|
+
"@iconify-json/material-symbols": "1.2.89",
|
|
98
98
|
"@iconify-json/mdi": "1.2.3",
|
|
99
99
|
"@iconify-json/radix-icons": "*",
|
|
100
|
-
"@nuxt/eslint": "1.
|
|
100
|
+
"@nuxt/eslint": "1.17.0",
|
|
101
101
|
"@nuxt/schema": "4.4.6",
|
|
102
|
-
"@nuxt/test-utils": "
|
|
102
|
+
"@nuxt/test-utils": "4.1.0",
|
|
103
103
|
"@nuxtjs/storybook": "9.1.0-29374011.dab79ae",
|
|
104
|
-
"@playwright/test": "1.
|
|
104
|
+
"@playwright/test": "1.62.1",
|
|
105
105
|
"@storybook/addon-a11y": "10.0.7",
|
|
106
106
|
"@storybook/addon-docs": "10.0.7",
|
|
107
107
|
"@storybook/builder-vite": "10.0.7",
|
|
108
|
-
"@vue/test-utils": "2.4.
|
|
109
|
-
"@vueuse/core": "14.
|
|
110
|
-
"eslint": "10.
|
|
108
|
+
"@vue/test-utils": "2.4.11",
|
|
109
|
+
"@vueuse/core": "14.4.0",
|
|
110
|
+
"eslint": "10.8.1",
|
|
111
111
|
"eslint-plugin-storybook": "10.0.7",
|
|
112
|
-
"happy-dom": "20.
|
|
112
|
+
"happy-dom": "20.11.2",
|
|
113
113
|
"nuxt-qrcode": "0.4.10",
|
|
114
|
+
"oxc-parser": "0.131.0",
|
|
114
115
|
"patch-package": "8.0.1",
|
|
115
|
-
"release-it": "
|
|
116
|
+
"release-it": "21.0.2",
|
|
116
117
|
"storybook": "10.0.7",
|
|
117
|
-
"typescript": "
|
|
118
|
-
"vitest": "
|
|
119
|
-
"vue-
|
|
118
|
+
"typescript": "5.9.3",
|
|
119
|
+
"vitest": "4.1.11",
|
|
120
|
+
"vue-router": "5.2.0",
|
|
121
|
+
"vue-tsc": "3.3.10"
|
|
120
122
|
},
|
|
121
123
|
"release-it": {
|
|
122
124
|
"$schema": "https://unpkg.com/release-it/schema/release-it.json",
|
|
@@ -130,15 +132,18 @@
|
|
|
130
132
|
},
|
|
131
133
|
"dependencies": {
|
|
132
134
|
"@nuxt/fonts": "0.14.0",
|
|
133
|
-
"@nuxt/icon": "2.
|
|
134
|
-
"@nuxt/image": "2.
|
|
135
|
-
"@oddbird/css-anchor-positioning": "0.
|
|
136
|
-
"@pinia/nuxt": "0.
|
|
135
|
+
"@nuxt/icon": "2.5.0",
|
|
136
|
+
"@nuxt/image": "2.1.0",
|
|
137
|
+
"@oddbird/css-anchor-positioning": "0.10.2",
|
|
138
|
+
"@pinia/nuxt": "1.0.2",
|
|
137
139
|
"@vueuse/motion": "3.0.3",
|
|
138
140
|
"focus-trap-vue": "4.1.0",
|
|
139
141
|
"nuxt": "4.4.6",
|
|
140
|
-
"pinia": "
|
|
142
|
+
"pinia": "4.0.3",
|
|
141
143
|
"pinia-plugin-persistedstate": "4.7.1",
|
|
142
144
|
"zod": "4.4.3"
|
|
145
|
+
},
|
|
146
|
+
"overrides": {
|
|
147
|
+
"vue-router": "5.2.0"
|
|
143
148
|
}
|
|
144
149
|
}
|