vlite3 1.4.15 → 1.4.17
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/components/CategoryManager/CategoryManager.vue2.js +1 -1
- package/components/ColorPicker/ColorIro.vue3.js +2 -2
- package/components/ColorPicker/ColorPicker.vue.js +2 -2
- package/components/CommandPalette/CommandPaletteContent.vue2.js +1 -1
- package/components/CommandPalette/{CommandPaletteItem.vue2.js → CommandPaletteItem.vue.js} +1 -1
- package/components/NavbarCommandPalette.vue.js +1 -1
- package/components/Screen/ScreenFilter.vue.js +1 -1
- package/components/SeoProvider/SeoProvider.vue.d.ts +70 -0
- package/components/SeoProvider/SeoProvider.vue.js +52 -0
- package/components/SeoProvider/SeoProvider.vue2.js +4 -0
- package/components/SeoProvider/domAdapter.d.ts +6 -0
- package/components/SeoProvider/domAdapter.js +68 -0
- package/components/SeoProvider/index.d.ts +4 -0
- package/components/SeoProvider/normalizeSeo.d.ts +61 -0
- package/components/SeoProvider/normalizeSeo.js +195 -0
- package/components/SeoProvider/types.d.ts +146 -0
- package/components/ThemeProvider/ThemeProvider.vue.js +22 -24
- package/components/ThemeProvider/themeVars.d.ts +9 -13
- package/components/ThemeProvider/themeVars.js +2 -2
- package/composables/useSeo.d.ts +34 -0
- package/composables/useSeo.js +28 -0
- package/index.d.ts +2 -0
- package/index.js +230 -219
- package/package.json +1 -1
- /package/components/ColorPicker/{ColorIro.vue.js → ColorIro.vue2.js} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic, framework-agnostic SEO contract.
|
|
3
|
+
*
|
|
4
|
+
* The goal of this layer is to keep SEO **normalization** (turning
|
|
5
|
+
* "store config + route context + page-specific overrides" into one
|
|
6
|
+
* canonical object) reusable, while letting the **application**
|
|
7
|
+
* (Nuxt, plain Vue + DOM, server-rendered HTML, …) decide **how** the
|
|
8
|
+
* data is actually emitted to the document head.
|
|
9
|
+
*
|
|
10
|
+
* The split is:
|
|
11
|
+
*
|
|
12
|
+
* 1. `SeoData` — the final shape every consumer (Nuxt
|
|
13
|
+
* `useSeoMeta`, Open Graph crawler, plain
|
|
14
|
+
* `<meta>` injection) understands.
|
|
15
|
+
* 2. `SeoAdapter` — a tiny interface the host app provides
|
|
16
|
+
* to apply `SeoData` to the document. In
|
|
17
|
+
* Nuxt this wraps `useSeoMeta` / `useHead`;
|
|
18
|
+
* in a Vue admin preview it mutates
|
|
19
|
+
* `document.head` directly. The provider
|
|
20
|
+
* itself never imports Nuxt or DOM APIs.
|
|
21
|
+
* 3. `useSeo` — a Vue composable for components that
|
|
22
|
+
* want to declare per-page SEO context
|
|
23
|
+
* (page type, dynamic replacements, image).
|
|
24
|
+
* 4. `SeoProvider` — the Vue component that owns the
|
|
25
|
+
* lifecycle: it merges the current page
|
|
26
|
+
* context, runs the normalizer, and calls
|
|
27
|
+
* the adapter on every change.
|
|
28
|
+
*
|
|
29
|
+
* Keeping the layer UI-agnostic means `vlite3` can ship SEO support
|
|
30
|
+
* without forcing every consumer onto Nuxt — and tests can run with
|
|
31
|
+
* a stub adapter that just records the last call.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Final, fully-resolved SEO payload.
|
|
35
|
+
*
|
|
36
|
+
* Every field is **already normalized** — null vs empty string,
|
|
37
|
+
* fallbacks applied, dynamic tokens replaced. Consumers (Nuxt
|
|
38
|
+
* `useSeoMeta`, DOM mutation, SSR string templating) should be
|
|
39
|
+
* able to read this object and emit `<meta>` tags without further
|
|
40
|
+
* computation.
|
|
41
|
+
*/
|
|
42
|
+
export interface SeoData {
|
|
43
|
+
/** Page title, ready to feed `useSeoMeta({ title })` / `<title>`. */
|
|
44
|
+
title: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional title template, e.g. `"%s · Acme"`. Falsy → leave the
|
|
47
|
+
* host app's default behaviour in place.
|
|
48
|
+
*/
|
|
49
|
+
titleTemplate?: string | null;
|
|
50
|
+
/** Meta description, 1–2 sentences, crawler-friendly. */
|
|
51
|
+
description: string;
|
|
52
|
+
/** Comma- or space-separated keywords. Empty string if unset. */
|
|
53
|
+
keywords: string;
|
|
54
|
+
/** Absolute canonical URL. Falsy → host decides (use `useRequestURL` etc.). */
|
|
55
|
+
canonicalUrl?: string | null;
|
|
56
|
+
/** Robots directive, e.g. `"index, follow"` or `"noindex, nofollow"`. */
|
|
57
|
+
robots?: string | null;
|
|
58
|
+
/** Absolute favicon URL (`.ico` / `.png`). */
|
|
59
|
+
favicon?: string | null;
|
|
60
|
+
/** Absolute share-image URL (used for both OG and Twitter by default). */
|
|
61
|
+
image?: string | null;
|
|
62
|
+
ogTitle?: string | null;
|
|
63
|
+
ogDescription?: string | null;
|
|
64
|
+
ogImage?: string | null;
|
|
65
|
+
ogImageAlt?: string | null;
|
|
66
|
+
ogUrl?: string | null;
|
|
67
|
+
ogType?: string | null;
|
|
68
|
+
ogSiteName?: string | null;
|
|
69
|
+
ogLocale?: string | null;
|
|
70
|
+
twitterCard?: string | null;
|
|
71
|
+
twitterTitle?: string | null;
|
|
72
|
+
twitterDescription?: string | null;
|
|
73
|
+
twitterImage?: string | null;
|
|
74
|
+
twitterSite?: string | null;
|
|
75
|
+
twitterCreator?: string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Site name used in OG `site_name`. Today the `ogSiteName` field
|
|
78
|
+
* carries the same value — `siteName` is exposed separately for
|
|
79
|
+
* adapters that prefer to render it as JSON-LD instead of a meta
|
|
80
|
+
* tag.
|
|
81
|
+
*/
|
|
82
|
+
siteName?: string | null;
|
|
83
|
+
/** Free-form JSON-LD block. Adapter decides whether to render. */
|
|
84
|
+
jsonLd?: Record<string, unknown> | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The full, normalized context the `SeoProvider` hands to its
|
|
88
|
+
* adapter. Same as `SeoData` plus the canonical `pageType` key
|
|
89
|
+
* (used for analytics / structured data) and the unresolved raw
|
|
90
|
+
* inputs in case the adapter needs them.
|
|
91
|
+
*/
|
|
92
|
+
export interface SeoPayload extends SeoData {
|
|
93
|
+
/**
|
|
94
|
+
* Logical page key, e.g. `"home"`, `"product"`, `"collection"`.
|
|
95
|
+
* Adapter can use it for breadcrumb JSON-LD, custom `<meta>`,
|
|
96
|
+
* or just logging.
|
|
97
|
+
*/
|
|
98
|
+
pageType: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Partial SEO data a page (or any component) can push into the
|
|
102
|
+
* provider via `useSeo({ … })`. Anything not supplied is filled by
|
|
103
|
+
* the provider's defaults / store-level fallback. Keys with `null`
|
|
104
|
+
* explicitly clear the inherited value.
|
|
105
|
+
*/
|
|
106
|
+
export interface SeoContextInput {
|
|
107
|
+
pageType?: string;
|
|
108
|
+
title?: string | null;
|
|
109
|
+
description?: string | null;
|
|
110
|
+
keywords?: string | null;
|
|
111
|
+
image?: string | null;
|
|
112
|
+
canonicalUrl?: string | null;
|
|
113
|
+
robots?: string | null;
|
|
114
|
+
/**
|
|
115
|
+
* Token replacements applied to every string field, e.g.
|
|
116
|
+
* `{ productName: 'Blue Tee' }` for `{{productName}}`.
|
|
117
|
+
*/
|
|
118
|
+
replacements?: Record<string, string | number | null | undefined>;
|
|
119
|
+
/**
|
|
120
|
+
* Free-form extras merged on top of the resolved payload. Use
|
|
121
|
+
* sparingly — the canonical fields above are preferred.
|
|
122
|
+
*/
|
|
123
|
+
overrides?: Partial<SeoData>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The contract a host application (Nuxt app, Vue admin shell,
|
|
127
|
+
* SSR template) provides so the generic `SeoProvider` can emit
|
|
128
|
+
* the resolved SEO without depending on any specific framework.
|
|
129
|
+
*
|
|
130
|
+
* The adapter receives the final, normalized payload. The
|
|
131
|
+
* provider calls it whenever the merged SEO context changes.
|
|
132
|
+
*/
|
|
133
|
+
export interface SeoAdapter {
|
|
134
|
+
/**
|
|
135
|
+
* Apply the SEO payload to the document. Called reactively on
|
|
136
|
+
* every change; may be called multiple times during a session
|
|
137
|
+
* (e.g. on route change, then on data load).
|
|
138
|
+
*/
|
|
139
|
+
apply(payload: SeoPayload): void;
|
|
140
|
+
/**
|
|
141
|
+
* Optional cleanup hook, e.g. clearing injected meta tags when
|
|
142
|
+
* the provider unmounts. The default implementation in
|
|
143
|
+
* `normalizeSeo` no-ops.
|
|
144
|
+
*/
|
|
145
|
+
reset?(): void;
|
|
146
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { defineComponent as m,
|
|
2
|
-
import { buildThemeVariables as
|
|
3
|
-
import { THEME_STYLES_KEY as
|
|
4
|
-
import { isDarkColor as
|
|
5
|
-
const
|
|
1
|
+
import { defineComponent as m, computed as o, provide as u, openBlock as a, createElementBlock as v, normalizeClass as c, createBlock as f, resolveDynamicComponent as p, withCtx as h, createTextVNode as C, toDisplayString as b, renderSlot as g } from "vue";
|
|
2
|
+
import { buildThemeVariables as S, buildThemeStyleBlock as y, buildCardOverrideStyles as T } from "./themeVars.js";
|
|
3
|
+
import { THEME_STYLES_KEY as k } from "../../composables/useThemeStyles.js";
|
|
4
|
+
import { isDarkColor as B } from "../../utils/colorUtils.js";
|
|
5
|
+
const P = /* @__PURE__ */ m({
|
|
6
6
|
__name: "ThemeProvider",
|
|
7
7
|
props: {
|
|
8
8
|
bgColor: { default: void 0 },
|
|
@@ -14,35 +14,33 @@ const V = /* @__PURE__ */ m({
|
|
|
14
14
|
maxWidth: { default: void 0 },
|
|
15
15
|
rootClass: { default: void 0 }
|
|
16
16
|
},
|
|
17
|
-
setup(
|
|
18
|
-
const e =
|
|
19
|
-
() =>
|
|
17
|
+
setup(t) {
|
|
18
|
+
const e = t, r = o(() => e.bgColor ? B(e.bgColor) : !1), l = o(() => S(e)), i = o(
|
|
19
|
+
() => y(l.value, ".vlite-theme-provider")
|
|
20
|
+
), d = o(
|
|
21
|
+
() => e.bgColor ? T(e.bgColor, r.value, ".vlite-theme-provider") : ""
|
|
20
22
|
), s = o(
|
|
21
|
-
() =>
|
|
22
|
-
), n = o(
|
|
23
|
-
() => [d.value, s.value].filter(Boolean).join(`
|
|
23
|
+
() => [i.value, d.value].filter(Boolean).join(`
|
|
24
24
|
`)
|
|
25
25
|
);
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
class: C([
|
|
26
|
+
return u(k, l), (n, _) => (a(), v("div", {
|
|
27
|
+
class: c([
|
|
29
28
|
"vlite-theme-provider",
|
|
30
|
-
|
|
29
|
+
t.rootClass,
|
|
31
30
|
"transition-colors duration-300",
|
|
32
|
-
{ dark:
|
|
33
|
-
])
|
|
34
|
-
style: p(l.value)
|
|
31
|
+
{ dark: r.value }
|
|
32
|
+
])
|
|
35
33
|
}, [
|
|
36
|
-
(
|
|
37
|
-
default:
|
|
38
|
-
b(
|
|
34
|
+
(a(), f(p("style"), null, {
|
|
35
|
+
default: h(() => [
|
|
36
|
+
C(b(s.value), 1)
|
|
39
37
|
]),
|
|
40
38
|
_: 1
|
|
41
39
|
})),
|
|
42
|
-
|
|
43
|
-
],
|
|
40
|
+
g(n.$slots, "default")
|
|
41
|
+
], 2));
|
|
44
42
|
}
|
|
45
43
|
});
|
|
46
44
|
export {
|
|
47
|
-
|
|
45
|
+
P as default
|
|
48
46
|
};
|
|
@@ -11,7 +11,7 @@ export declare const DEFAULT_MAX_WIDTH = 1440;
|
|
|
11
11
|
export declare function buildCardOverrideStyles(bg: string, isDark: boolean, scope?: string): string;
|
|
12
12
|
/**
|
|
13
13
|
* Build a `<style>` text block that re-applies every generated CSS custom
|
|
14
|
-
* property with `!important`, scoped to a
|
|
14
|
+
* property with `!important`, scoped to a provider root selector.
|
|
15
15
|
*
|
|
16
16
|
* Why this exists:
|
|
17
17
|
*
|
|
@@ -25,11 +25,9 @@ export declare function buildCardOverrideStyles(bg: string, isDark: boolean, sco
|
|
|
25
25
|
* `[data-theme="dark"]` block. That silently nukes the provider's
|
|
26
26
|
* inline values and the brand color disappears.
|
|
27
27
|
*
|
|
28
|
-
* The fix is a redundant layer that has the
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* can override the theme — not Tailwind defaults, not ERP global
|
|
32
|
-
* styles, not third-party stylesheets.
|
|
28
|
+
* The fix is a redundant layer that has the same behavior as
|
|
29
|
+
* `ShopThemeProvider`: a generated style rule that writes every
|
|
30
|
+
* theme token with `!important` on the provider root.
|
|
33
31
|
*
|
|
34
32
|
* Pure: no DOM, no Vue. Returns a CSS string ready to be dropped
|
|
35
33
|
* inside a `<component :is="'style'">{{ block }}</component>` block.
|
|
@@ -38,20 +36,18 @@ export declare function buildCardOverrideStyles(bg: string, isDark: boolean, sco
|
|
|
38
36
|
* provider root `font-size` that drives the same inherited base-size behavior
|
|
39
37
|
* as `ShopThemeProvider`.
|
|
40
38
|
*/
|
|
41
|
-
export declare function buildThemeStyleBlock(vars: ThemeStyles,
|
|
39
|
+
export declare function buildThemeStyleBlock(vars: ThemeStyles, selector?: string): string;
|
|
42
40
|
/**
|
|
43
|
-
* Note: We
|
|
44
|
-
* in the theme variables object, so that Vue inline bindings (`:style="themeStyles"`)
|
|
45
|
-
* would carry the `!important` flag.
|
|
41
|
+
* Note: We keep `!important` out of the theme variables object itself.
|
|
46
42
|
*
|
|
47
43
|
* However, Vue's DOM patching (`style.setProperty(key, value)`) strictly rejects
|
|
48
44
|
* the `value` argument if it contains `!important`. Thus, any inline style
|
|
49
45
|
* object with `!important` silently fails and drops the styles entirely in SPA
|
|
50
46
|
* environments.
|
|
51
47
|
*
|
|
52
|
-
* Therefore, we provide
|
|
53
|
-
* consumers, and rely
|
|
54
|
-
*
|
|
48
|
+
* Therefore, we provide raw variables (without `!important`) to `useThemeStyles()`
|
|
49
|
+
* consumers, and rely on the `<style>` block generated by `buildThemeStyleBlock`
|
|
50
|
+
* to enforce `!important` overrides across the application.
|
|
55
51
|
*/
|
|
56
52
|
/**
|
|
57
53
|
* Compute the full theme-styles record from a {@link ThemeProviderProps}.
|
|
@@ -345,7 +345,7 @@ ${l} .bg-card-light .bg-card-light .bg-card-light {
|
|
|
345
345
|
`;
|
|
346
346
|
}
|
|
347
347
|
const O = (r) => r.replace(/[{};]/g, "");
|
|
348
|
-
function U(r, o) {
|
|
348
|
+
function U(r, o = ".vlite-theme-provider") {
|
|
349
349
|
if (!o) return "";
|
|
350
350
|
const l = [];
|
|
351
351
|
for (const [e, f] of Object.entries(r)) {
|
|
@@ -355,7 +355,7 @@ function U(r, o) {
|
|
|
355
355
|
c.includes("!important") ? ` ${e}: ${c};` : ` ${e}: ${c} !important;`
|
|
356
356
|
);
|
|
357
357
|
}
|
|
358
|
-
return l.length === 0 ? "" :
|
|
358
|
+
return l.length === 0 ? "" : `${o} {
|
|
359
359
|
${l.join(`
|
|
360
360
|
`)}
|
|
361
361
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { InjectionKey, Ref } from 'vue';
|
|
2
|
+
import { SeoContextInput } from '../components/SeoProvider/types';
|
|
3
|
+
/**
|
|
4
|
+
* Mutable, reactive bag of SEO contexts. The provider owns the
|
|
5
|
+
* ref; pages push into it. We keep it as a `Map` so multiple
|
|
6
|
+
* pages on the same route (e.g. a checkout step embedded in a
|
|
7
|
+
* parent) can each contribute, and so `clear()` removes only
|
|
8
|
+
* the calling page's contribution.
|
|
9
|
+
*/
|
|
10
|
+
export type SeoContextBag = Ref<Map<symbol, SeoContextInput>>;
|
|
11
|
+
export declare const SEO_CONTEXT_KEY: InjectionKey<SeoContextBag>;
|
|
12
|
+
/**
|
|
13
|
+
* Minimal interface the page-level composable needs from the
|
|
14
|
+
* provider. Exposed as a separate type so the provider component
|
|
15
|
+
* doesn't have to import Vue's `provide` / `inject` machinery
|
|
16
|
+
* from the composable side.
|
|
17
|
+
*/
|
|
18
|
+
export interface SeoContextRegistrar {
|
|
19
|
+
register(input: SeoContextInput): SeoContextHandle;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Per-page handle. `update()` replaces the registered context
|
|
23
|
+
* in place; `clear()` removes it.
|
|
24
|
+
*/
|
|
25
|
+
export interface SeoContextHandle {
|
|
26
|
+
update(next: SeoContextInput): void;
|
|
27
|
+
clear(): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Public composable. Safe to call outside a provider — falls
|
|
31
|
+
* back to a no-op registrar so admin shells and one-off tests
|
|
32
|
+
* don't have to wire the provider up just to render.
|
|
33
|
+
*/
|
|
34
|
+
export declare const useSeo: (input?: SeoContextInput) => SeoContextHandle;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { inject as r, onBeforeUnmount as u } from "vue";
|
|
2
|
+
const a = /* @__PURE__ */ Symbol("vlite3:seo-context"), s = (o = {}) => {
|
|
3
|
+
const e = r(a, null);
|
|
4
|
+
if (!e)
|
|
5
|
+
return {
|
|
6
|
+
update: () => {
|
|
7
|
+
},
|
|
8
|
+
clear: () => {
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const t = /* @__PURE__ */ Symbol("vlite3:seo-context-entry");
|
|
12
|
+
e.value.set(t, o);
|
|
13
|
+
const n = {
|
|
14
|
+
update(l) {
|
|
15
|
+
e.value.set(t, l);
|
|
16
|
+
},
|
|
17
|
+
clear() {
|
|
18
|
+
e.value.delete(t);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return u(() => {
|
|
22
|
+
e.value.delete(t);
|
|
23
|
+
}), n;
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
a as SEO_CONTEXT_KEY,
|
|
27
|
+
s as useSeo
|
|
28
|
+
};
|
package/index.d.ts
CHANGED
|
@@ -63,8 +63,10 @@ export * from './components/AsyncSelect';
|
|
|
63
63
|
export * from './components/ImageComparison';
|
|
64
64
|
export { default as ImageMagnifier } from './components/ImageMagnifier.vue';
|
|
65
65
|
export * from './components/ThemeProvider';
|
|
66
|
+
export * from './components/SeoProvider';
|
|
66
67
|
export * from './components/ScaleGenerator';
|
|
67
68
|
export { THEME_STYLES_KEY, useThemeStyles } from './composables/useThemeStyles';
|
|
69
|
+
export { useSeo, SEO_CONTEXT_KEY, type SeoContextBag, type SeoContextHandle } from './composables/useSeo';
|
|
68
70
|
export { default as Icon } from './components/Icon.vue';
|
|
69
71
|
export { default as Logo } from './components/Logo.vue';
|
|
70
72
|
export { default as Alert } from './components/Alert.vue';
|