sanity-plugin-seofields 1.9.0 → 1.10.1

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.
Files changed (56) hide show
  1. package/README.md +71 -0
  2. package/dist/{SeoPreview-LMZAWWOE.cjs → SeoPreview-A2XZVMT2.cjs} +4 -4
  3. package/dist/{SeoPreview-LMZAWWOE.cjs.map → SeoPreview-A2XZVMT2.cjs.map} +1 -1
  4. package/dist/{SeoPreview-UNQBKTQO.js → SeoPreview-TATJQFMP.js} +2 -2
  5. package/dist/chunk-CRCXC45D.js +56 -0
  6. package/dist/chunk-CRCXC45D.js.map +1 -0
  7. package/dist/{chunk-HHO2AKAP.js → chunk-DB5MBPRK.js} +13 -13
  8. package/dist/chunk-DB5MBPRK.js.map +1 -0
  9. package/dist/{chunk-3OK3S2F7.cjs → chunk-EUG4MNRC.cjs} +60 -20
  10. package/dist/chunk-EUG4MNRC.cjs.map +1 -0
  11. package/dist/{chunk-Y46ACXM4.cjs → chunk-LHUFL5LS.cjs} +13 -13
  12. package/dist/chunk-LHUFL5LS.cjs.map +1 -0
  13. package/dist/{chunk-Z5AOUU4H.js → chunk-NLEB47UV.js} +60 -20
  14. package/dist/chunk-NLEB47UV.js.map +1 -0
  15. package/dist/{chunk-FUUWBQE2.cjs → chunk-OULUDJPI.cjs} +66 -7
  16. package/dist/chunk-OULUDJPI.cjs.map +1 -0
  17. package/dist/{chunk-KIEO6QG3.js → chunk-XD4HKLEA.js} +66 -7
  18. package/dist/chunk-XD4HKLEA.js.map +1 -0
  19. package/dist/chunk-XYAZJ3WX.cjs +56 -0
  20. package/dist/chunk-XYAZJ3WX.cjs.map +1 -0
  21. package/dist/cli.js +1 -1
  22. package/dist/head.cjs +10 -2
  23. package/dist/head.cjs.map +1 -1
  24. package/dist/head.d.cts +61 -1
  25. package/dist/head.d.ts +61 -1
  26. package/dist/head.js +9 -1
  27. package/dist/hreflang-_VbSuWYD.d.cts +29 -0
  28. package/dist/hreflang-_VbSuWYD.d.ts +29 -0
  29. package/dist/index.cjs +173 -38
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +3 -2
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.js +336 -201
  34. package/dist/index.js.map +1 -1
  35. package/dist/next.cjs +11 -3
  36. package/dist/next.cjs.map +1 -1
  37. package/dist/next.d.cts +2 -1
  38. package/dist/next.d.ts +2 -1
  39. package/dist/next.js +9 -1
  40. package/dist/next.js.map +1 -1
  41. package/dist/{plugin-DoGeZP79.d.cts → plugin-HFuWZsJu.d.cts} +68 -1
  42. package/dist/{plugin-DQdThyxG.d.ts → plugin-oQUCim56.d.ts} +68 -1
  43. package/dist/server.cjs +2 -2
  44. package/dist/server.cjs.map +1 -1
  45. package/dist/server.d.cts +6 -8
  46. package/dist/server.d.ts +6 -8
  47. package/dist/server.js +1 -1
  48. package/dist/server.js.map +1 -1
  49. package/package.json +2 -2
  50. package/dist/chunk-3OK3S2F7.cjs.map +0 -1
  51. package/dist/chunk-FUUWBQE2.cjs.map +0 -1
  52. package/dist/chunk-HHO2AKAP.js.map +0 -1
  53. package/dist/chunk-KIEO6QG3.js.map +0 -1
  54. package/dist/chunk-Y46ACXM4.cjs.map +0 -1
  55. package/dist/chunk-Z5AOUU4H.js.map +0 -1
  56. /package/dist/{SeoPreview-UNQBKTQO.js.map → SeoPreview-TATJQFMP.js.map} +0 -0
package/dist/head.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { a as SanityImage, b as SanityImageWithAlt } from './types-DxlPXihz.js';
2
+ export { B as BuildHreflangsOptions, H as HreflangLinkEntry, a as HreflangTranslation, b as buildHreflangs } from './hreflang-_VbSuWYD.js';
2
3
 
3
4
  /**
4
5
  * Headless CMS integration helpers for sanity-plugin-seofields
@@ -190,6 +191,17 @@ interface BuildSeoMetaOptions {
190
191
  * imageUrlResolver: (img) => urlFor(img).width(1200).url()
191
192
  */
192
193
  imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined;
194
+ /**
195
+ * Override the hreflang alternates. When provided, this supersedes `seo.hreflangs` — pair it with
196
+ * `buildHreflangs()` to derive alternates from `@sanity/document-internationalization` translations.
197
+ *
198
+ * @example
199
+ * hreflangs: buildHreflangs(data._translations, { baseUrl })
200
+ */
201
+ hreflangs?: Array<{
202
+ locale?: string | null;
203
+ url?: string | null;
204
+ }> | null;
193
205
  }
194
206
  declare const VALID_OG_TYPES: readonly ["website", "article", "profile", "book", "music", "video", "product"];
195
207
  type OGType = (typeof VALID_OG_TYPES)[number];
@@ -251,4 +263,52 @@ declare function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata;
251
263
  */
252
264
  declare function buildSeoHead(options: BuildSeoMetaOptions): SeoHead;
253
265
 
254
- export { type BuildSeoMetaOptions, type SeoFieldsInput, type SeoHead, type SeoHeadLinkTag, type SeoHeadMetaTag, type SeoMetaDefaults, type SeoMetadata, buildSeoHead, buildSeoMeta, sanitizeOGType, sanitizeTwitterCard };
266
+ interface LlmsLink {
267
+ title: string;
268
+ url: string;
269
+ description?: string;
270
+ }
271
+ interface LlmsSection {
272
+ title: string;
273
+ links: LlmsLink[];
274
+ }
275
+ interface BuildLlmsTxtOptions {
276
+ /** H1 title, e.g. the site name. */
277
+ title: string;
278
+ /** One-line blockquote summary rendered directly under the title. */
279
+ summary?: string;
280
+ /** Free-form Markdown prose rendered after the summary. */
281
+ details?: string;
282
+ /** Link sections rendered as `## {title}` + a bullet list. Empty sections are omitted. */
283
+ sections?: LlmsSection[];
284
+ /** Optional version, rendered in a leading `> Version: … | Updated: …` line. */
285
+ version?: string;
286
+ /** Optional "updated" label, rendered alongside `version`. */
287
+ updated?: string;
288
+ /** Prepended to any relative link URL. */
289
+ baseUrl?: string;
290
+ }
291
+ declare function buildLlmsTxt(options: BuildLlmsTxtOptions): string;
292
+ interface LlmsDoc {
293
+ title?: string | null;
294
+ slug?: {
295
+ current?: string | null;
296
+ } | string | null;
297
+ description?: string | null;
298
+ [key: string]: unknown;
299
+ }
300
+ interface DocsToLlmsSectionOptions {
301
+ /** Section heading. */
302
+ title: string;
303
+ /** Prepended to each document's path. */
304
+ baseUrl?: string;
305
+ /** Build the path for a document. Defaults to `/${slug}`. */
306
+ resolvePath?: (doc: LlmsDoc) => string | null | undefined;
307
+ /** Override the link title. Defaults to `doc.title`. */
308
+ resolveTitle?: (doc: LlmsDoc) => string | null | undefined;
309
+ /** Override the link description. Defaults to `doc.description`. */
310
+ resolveDescription?: (doc: LlmsDoc) => string | null | undefined;
311
+ }
312
+ declare function docsToLlmsSection(docs: LlmsDoc[] | null | undefined, options: DocsToLlmsSectionOptions): LlmsSection;
313
+
314
+ export { type BuildLlmsTxtOptions, type BuildSeoMetaOptions, type DocsToLlmsSectionOptions, type LlmsDoc, type LlmsLink, type LlmsSection, type SeoFieldsInput, type SeoHead, type SeoHeadLinkTag, type SeoHeadMetaTag, type SeoMetaDefaults, type SeoMetadata, buildLlmsTxt, buildSeoHead, buildSeoMeta, docsToLlmsSection, sanitizeOGType, sanitizeTwitterCard };
package/dist/head.js CHANGED
@@ -1,13 +1,21 @@
1
1
  import {
2
+ buildLlmsTxt,
2
3
  buildSeoHead,
3
4
  buildSeoMeta,
5
+ docsToLlmsSection,
4
6
  sanitizeOGType,
5
7
  sanitizeTwitterCard
6
- } from "./chunk-KIEO6QG3.js";
8
+ } from "./chunk-XD4HKLEA.js";
9
+ import {
10
+ buildHreflangs
11
+ } from "./chunk-CRCXC45D.js";
7
12
  import "./chunk-7N4MLTMR.js";
8
13
  export {
14
+ buildHreflangs,
15
+ buildLlmsTxt,
9
16
  buildSeoHead,
10
17
  buildSeoMeta,
18
+ docsToLlmsSection,
11
19
  sanitizeOGType,
12
20
  sanitizeTwitterCard
13
21
  };
@@ -0,0 +1,29 @@
1
+ interface HreflangTranslation {
2
+ /** BCP 47 language/region tag, e.g. "en", "fr-FR". */
3
+ language?: string | null;
4
+ /** Sanity slug — either the raw string or the `{ current }` object. */
5
+ slug?: {
6
+ current?: string | null;
7
+ } | string | null;
8
+ /** Pre-built path (e.g. "/fr/about"). Wins over `slug`. */
9
+ path?: string | null;
10
+ /** Absolute URL. Wins over `baseUrl` + path. */
11
+ url?: string | null;
12
+ }
13
+ interface BuildHreflangsOptions {
14
+ /** Site origin, e.g. "https://example.com". Prepended to each translation's path. */
15
+ baseUrl?: string;
16
+ /** Build the path for a translation. Defaults to `/${slug}`. Return null/undefined to skip. */
17
+ resolvePath?: (t: HreflangTranslation) => string | null | undefined;
18
+ /** Language tag to also emit as an `x-default` entry (pointing at that language's URL). */
19
+ xDefault?: string;
20
+ /** Include the current document itself in the set. */
21
+ current?: HreflangTranslation;
22
+ }
23
+ interface HreflangLinkEntry {
24
+ locale: string;
25
+ url: string;
26
+ }
27
+ declare function buildHreflangs(translations: HreflangTranslation[] | null | undefined, options?: BuildHreflangsOptions): HreflangLinkEntry[];
28
+
29
+ export { type BuildHreflangsOptions as B, type HreflangLinkEntry as H, type HreflangTranslation as a, buildHreflangs as b };
@@ -0,0 +1,29 @@
1
+ interface HreflangTranslation {
2
+ /** BCP 47 language/region tag, e.g. "en", "fr-FR". */
3
+ language?: string | null;
4
+ /** Sanity slug — either the raw string or the `{ current }` object. */
5
+ slug?: {
6
+ current?: string | null;
7
+ } | string | null;
8
+ /** Pre-built path (e.g. "/fr/about"). Wins over `slug`. */
9
+ path?: string | null;
10
+ /** Absolute URL. Wins over `baseUrl` + path. */
11
+ url?: string | null;
12
+ }
13
+ interface BuildHreflangsOptions {
14
+ /** Site origin, e.g. "https://example.com". Prepended to each translation's path. */
15
+ baseUrl?: string;
16
+ /** Build the path for a translation. Defaults to `/${slug}`. Return null/undefined to skip. */
17
+ resolvePath?: (t: HreflangTranslation) => string | null | undefined;
18
+ /** Language tag to also emit as an `x-default` entry (pointing at that language's URL). */
19
+ xDefault?: string;
20
+ /** Include the current document itself in the set. */
21
+ current?: HreflangTranslation;
22
+ }
23
+ interface HreflangLinkEntry {
24
+ locale: string;
25
+ url: string;
26
+ }
27
+ declare function buildHreflangs(translations: HreflangTranslation[] | null | undefined, options?: BuildHreflangsOptions): HreflangLinkEntry[];
28
+
29
+ export { type BuildHreflangsOptions as B, type HreflangLinkEntry as H, type HreflangTranslation as a, buildHreflangs as b };
package/dist/index.cjs CHANGED
@@ -1,8 +1,11 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }
2
2
 
3
+ var _chunkXYAZJ3WXcjs = require('./chunk-XYAZJ3WX.cjs');
3
4
 
4
5
 
5
- var _chunk3OK3S2F7cjs = require('./chunk-3OK3S2F7.cjs');
6
+
7
+
8
+ var _chunkEUG4MNRCcjs = require('./chunk-EUG4MNRC.cjs');
6
9
 
7
10
 
8
11
 
@@ -22,7 +25,7 @@ var _chunk5XTQRILLcjs = require('./chunk-5XTQRILL.cjs');
22
25
 
23
26
 
24
27
 
25
- var _chunkY46ACXM4cjs = require('./chunk-Y46ACXM4.cjs');
28
+ var _chunkLHUFL5LScjs = require('./chunk-LHUFL5LS.cjs');
26
29
 
27
30
 
28
31
 
@@ -465,11 +468,11 @@ function SeoGenButton({
465
468
  let result;
466
469
  if (ai.testMode) {
467
470
  await new Promise((r) => setTimeout(r, 400));
468
- result = _chunk3OK3S2F7cjs.pickTestOutput.call(void 0, field);
471
+ result = _chunkEUG4MNRCcjs.pickTestOutput.call(void 0, field);
469
472
  } else {
470
473
  const controller = new AbortController();
471
474
  abortRef.current = controller;
472
- result = await _chunk3OK3S2F7cjs.generateSeoText.call(void 0, field, content, focusKeyword, keywords, {
475
+ result = await _chunkEUG4MNRCcjs.generateSeoText.call(void 0, field, content, focusKeyword, keywords, {
473
476
  config: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, ai), { _projectId: workspace.projectId }),
474
477
  signal: controller.signal,
475
478
  meta
@@ -542,7 +545,7 @@ var FocusKeywordInput = (props) => {
542
545
  const title = (parent == null ? void 0 : parent.title) || "";
543
546
  const description = (parent == null ? void 0 : parent.description) || "";
544
547
  const slug = ((_a2 = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a2.current) || "";
545
- const placement = _chunkY46ACXM4cjs.getFocusKeywordPlacement.call(void 0, value, title, description);
548
+ const placement = _chunkLHUFL5LScjs.getFocusKeywordPlacement.call(void 0, value, title, description);
546
549
  const items = [];
547
550
  if (title) {
548
551
  if (placement.atStartOfTitle) {
@@ -611,6 +614,87 @@ var FocusKeywordInput = (props) => {
611
614
  };
612
615
  var FocusKeywordInput_default = FocusKeywordInput;
613
616
 
617
+ // src/components/meta/HreflangInput.tsx
618
+
619
+
620
+
621
+
622
+
623
+ var DEFAULT_LOCALE_FIELD = "language";
624
+ var DEFAULT_API_VERSION = "2024-01-01";
625
+ function safeField(name) {
626
+ return name && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : DEFAULT_LOCALE_FIELD;
627
+ }
628
+ var HreflangInput = (props) => {
629
+ const { schemaType, onChange, renderDefault } = props;
630
+ const { options } = schemaType;
631
+ const { hreflang, baseUrl, apiVersion } = options != null ? options : {};
632
+ const rootDoc = _sanity.useFormValue.call(void 0, []);
633
+ const client = _sanity.useClient.call(void 0, { apiVersion: apiVersion != null ? apiVersion : DEFAULT_API_VERSION });
634
+ const [state, setState] = _react.useState.call(void 0, "idle");
635
+ const [message, setMessage] = _react.useState.call(void 0, null);
636
+ const localeField = safeField(hreflang == null ? void 0 : hreflang.localeField);
637
+ const handleSync = _react.useCallback.call(void 0, async () => {
638
+ var _a;
639
+ const rawId = rootDoc == null ? void 0 : rootDoc._id;
640
+ if (!rawId) {
641
+ setState("error");
642
+ setMessage("Save the document once before syncing translations.");
643
+ return;
644
+ }
645
+ setState("loading");
646
+ setMessage(null);
647
+ try {
648
+ const publishedId = rawId.replace(/^drafts\./, "");
649
+ const query = `*[_type == "translation.metadata" && references($id)][0].translations[].value->{
650
+ "language": ${localeField},
651
+ "slug": slug.current
652
+ }`;
653
+ const translations = (_a = await client.fetch(query, { id: publishedId })) != null ? _a : [];
654
+ const entries = _chunkXYAZJ3WXcjs.buildHreflangs.call(void 0, translations, {
655
+ baseUrl,
656
+ resolvePath: hreflang == null ? void 0 : hreflang.resolvePath
657
+ });
658
+ if (entries.length === 0) {
659
+ setState("error");
660
+ setMessage("No translations with a resolvable URL were found for this document.");
661
+ return;
662
+ }
663
+ const items = entries.map((e) => ({
664
+ _type: "hreflangEntry",
665
+ _key: e.locale,
666
+ locale: e.locale,
667
+ url: e.url
668
+ }));
669
+ onChange(_sanity.PatchEvent.from(_sanity.set.call(void 0, items)));
670
+ setState("done");
671
+ setMessage(
672
+ `Synced ${items.length} alternate${items.length === 1 ? "" : "s"} from translations.`
673
+ );
674
+ } catch (err) {
675
+ setState("error");
676
+ setMessage(err instanceof Error ? err.message : "Failed to sync translations.");
677
+ }
678
+ }, [rootDoc == null ? void 0 : rootDoc._id, client, localeField, baseUrl, hreflang, onChange]);
679
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
680
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ui.Flex, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
681
+ _ui.Button,
682
+ {
683
+ mode: "ghost",
684
+ tone: "primary",
685
+ fontSize: 1,
686
+ icon: _icons.TranslateIcon,
687
+ text: state === "loading" ? "Syncing\u2026" : "Sync from translations",
688
+ onClick: handleSync,
689
+ disabled: state === "loading"
690
+ }
691
+ ) }),
692
+ message && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ui.Card, { padding: 2, radius: 2, tone: state === "error" ? "critical" : "positive", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ui.Text, { size: 1, children: message }) }),
693
+ renderDefault(props)
694
+ ] });
695
+ };
696
+ var HreflangInput_default = HreflangInput;
697
+
614
698
  // src/components/meta/KeywordsInput.tsx
615
699
 
616
700
 
@@ -651,9 +735,9 @@ function KeywordSuggestions({
651
735
  let csv;
652
736
  if (ai.testMode) {
653
737
  await new Promise((r) => setTimeout(r, 400));
654
- csv = _chunk3OK3S2F7cjs.pickTestOutput.call(void 0, "keywords");
738
+ csv = _chunkEUG4MNRCcjs.pickTestOutput.call(void 0, "keywords");
655
739
  } else {
656
- csv = await _chunk3OK3S2F7cjs.generateSeoText.call(void 0, "keywords", content, focusKeyword, existing, {
740
+ csv = await _chunkEUG4MNRCcjs.generateSeoText.call(void 0, "keywords", content, focusKeyword, existing, {
657
741
  config: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, ai), { _projectId: workspace.projectId }),
658
742
  signal: abortRef.current.signal
659
743
  });
@@ -843,16 +927,24 @@ var KeywordsInput_default = KeywordsInput;
843
927
 
844
928
 
845
929
  var MetaDescription = (props) => {
930
+ var _a, _b;
846
931
  const { value, renderDefault, path, schemaType, onChange } = props;
847
932
  const { options } = schemaType;
848
933
  const parent = _sanity.useFormValue.call(void 0, [path[0]]);
849
934
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
850
935
  const keywords = _react.useMemo.call(void 0, () => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
936
+ const rootDoc = _sanity.useFormValue.call(void 0, []);
937
+ const keywordsVisible = (_b = (_a = options == null ? void 0 : options.isKeywordsVisible) == null ? void 0 : _a.call(options, rootDoc == null ? void 0 : rootDoc._type)) != null ? _b : true;
851
938
  const feedbackItems = _react.useMemo.call(void 0,
852
- () => _chunkY46ACXM4cjs.getMetaDescriptionValidationMessages.call(void 0, value || "", keywords, isParentseoField),
853
- [value, keywords, isParentseoField]
939
+ () => _chunkLHUFL5LScjs.getMetaDescriptionValidationMessages.call(void 0,
940
+ value || "",
941
+ keywords,
942
+ isParentseoField,
943
+ keywordsVisible
944
+ ),
945
+ [value, keywords, isParentseoField, keywordsVisible]
854
946
  );
855
- const readability = _react.useMemo.call(void 0, () => _chunk3OK3S2F7cjs.analyzeReadability.call(void 0, value || ""), [value]);
947
+ const readability = _react.useMemo.call(void 0, () => _chunkEUG4MNRCcjs.analyzeReadability.call(void 0, value || ""), [value]);
856
948
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
857
949
  renderDefault(props),
858
950
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -910,7 +1002,7 @@ var MetaImage = (props) => {
910
1002
  const seoParent = _sanity.useFormValue.call(void 0, [path[0]]);
911
1003
  const hasImage = !!(value == null ? void 0 : value.asset);
912
1004
  const feedbackItems = _react.useMemo.call(void 0,
913
- () => _chunkY46ACXM4cjs.getMetaImageValidation.call(void 0, hasImage, seoParent),
1005
+ () => _chunkLHUFL5LScjs.getMetaImageValidation.call(void 0, hasImage, seoParent),
914
1006
  [hasImage, seoParent]
915
1007
  );
916
1008
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
@@ -1141,7 +1233,7 @@ var MetaTagsPreview_default = MetaTagsPreview;
1141
1233
 
1142
1234
 
1143
1235
  var MetaTitle = (props) => {
1144
- var _a, _b, _c;
1236
+ var _a, _b, _c, _d, _e;
1145
1237
  const { value, renderDefault, path, schemaType, onChange } = props;
1146
1238
  const { options } = schemaType;
1147
1239
  const parent = _sanity.useFormValue.call(void 0, [path[0]]);
@@ -1185,9 +1277,16 @@ var MetaTitle = (props) => {
1185
1277
  return titleSuffixOption;
1186
1278
  }, [titleSuffixQuery, groqTitleSuffix, titleSuffixOption, rootDoc]);
1187
1279
  const suffixLength = resolvedSuffix ? resolvedSuffix.length + 3 : 0;
1280
+ const keywordsVisible = (_e = (_d = options == null ? void 0 : options.isKeywordsVisible) == null ? void 0 : _d.call(options, rootDoc == null ? void 0 : rootDoc._type)) != null ? _e : true;
1188
1281
  const feedbackItems = _react.useMemo.call(void 0,
1189
- () => _chunkY46ACXM4cjs.getMetaTitleValidationMessages.call(void 0, value || "", keywords, isParentseoField, suffixLength),
1190
- [value, keywords, isParentseoField, suffixLength]
1282
+ () => _chunkLHUFL5LScjs.getMetaTitleValidationMessages.call(void 0,
1283
+ value || "",
1284
+ keywords,
1285
+ isParentseoField,
1286
+ suffixLength,
1287
+ keywordsVisible
1288
+ ),
1289
+ [value, keywords, isParentseoField, suffixLength, keywordsVisible]
1191
1290
  );
1192
1291
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
1193
1292
  renderDefault(props),
@@ -1371,6 +1470,9 @@ var getFieldHiddenFunction = (fieldName, config) => {
1371
1470
  return isFieldHidden(fieldName, config, documentType);
1372
1471
  };
1373
1472
  };
1473
+ var getKeywordsVisibilityChecker = (config) => {
1474
+ return (documentType) => !isFieldHidden("keywords", config, documentType);
1475
+ };
1374
1476
 
1375
1477
  // src/utils/utils.ts
1376
1478
  var isEmpty = (value) => {
@@ -1386,14 +1488,17 @@ var isEmpty = (value) => {
1386
1488
 
1387
1489
 
1388
1490
  var OgDescription = (props) => {
1491
+ var _a, _b;
1389
1492
  const { value, renderDefault, path, schemaType, onChange } = props;
1390
1493
  const { options } = schemaType;
1391
1494
  const parent = _sanity.useFormValue.call(void 0, [path[0]]);
1392
1495
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1393
1496
  const keywords = _react.useMemo.call(void 0, () => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1497
+ const rootDoc = _sanity.useFormValue.call(void 0, []);
1498
+ const keywordsVisible = (_b = (_a = options == null ? void 0 : options.isKeywordsVisible) == null ? void 0 : _a.call(options, rootDoc == null ? void 0 : rootDoc._type)) != null ? _b : true;
1394
1499
  const feedbackItems = _react.useMemo.call(void 0,
1395
- () => _chunkY46ACXM4cjs.getOgDescriptionValidation.call(void 0, value || "", keywords, isParentseoField),
1396
- [value, keywords, isParentseoField]
1500
+ () => _chunkLHUFL5LScjs.getOgDescriptionValidation.call(void 0, value || "", keywords, isParentseoField, keywordsVisible),
1501
+ [value, keywords, isParentseoField, keywordsVisible]
1397
1502
  );
1398
1503
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
1399
1504
  renderDefault(props),
@@ -1436,7 +1541,7 @@ var OgImage = (props) => {
1436
1541
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
1437
1542
  const altText = imgValue == null ? void 0 : imgValue.alt;
1438
1543
  const feedbackItems = _react.useMemo.call(void 0,
1439
- () => _chunkY46ACXM4cjs.getOgImageValidation.call(void 0, hasImage, altText, seoParent),
1544
+ () => _chunkLHUFL5LScjs.getOgImageValidation.call(void 0, hasImage, altText, seoParent),
1440
1545
  [hasImage, altText, seoParent]
1441
1546
  );
1442
1547
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
@@ -1467,7 +1572,7 @@ var OgImage_default = OgImage;
1467
1572
  var OgImageUrl = (props) => {
1468
1573
  const { value, renderDefault, path } = props;
1469
1574
  const seoParent = _sanity.useFormValue.call(void 0, [path[0]]);
1470
- const feedbackItems = _react.useMemo.call(void 0, () => _chunkY46ACXM4cjs.getOgImageUrlValidation.call(void 0, value, seoParent), [value, seoParent]);
1575
+ const feedbackItems = _react.useMemo.call(void 0, () => _chunkLHUFL5LScjs.getOgImageUrlValidation.call(void 0, value, seoParent), [value, seoParent]);
1471
1576
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
1472
1577
  renderDefault(props),
1473
1578
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ui.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
@@ -1494,14 +1599,17 @@ var OgImageUrl_default = OgImageUrl;
1494
1599
 
1495
1600
 
1496
1601
  var OgTitle = (props) => {
1602
+ var _a, _b;
1497
1603
  const { value, renderDefault, path, schemaType, onChange } = props;
1498
1604
  const { options } = schemaType;
1499
1605
  const parent = _sanity.useFormValue.call(void 0, [path[0]]);
1500
1606
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1501
1607
  const keywords = _react.useMemo.call(void 0, () => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1608
+ const rootDoc = _sanity.useFormValue.call(void 0, []);
1609
+ const keywordsVisible = (_b = (_a = options == null ? void 0 : options.isKeywordsVisible) == null ? void 0 : _a.call(options, rootDoc == null ? void 0 : rootDoc._type)) != null ? _b : true;
1502
1610
  const feedbackItems = _react.useMemo.call(void 0,
1503
- () => _chunkY46ACXM4cjs.getOgTitleValidation.call(void 0, value || "", keywords, isParentseoField),
1504
- [value, keywords, isParentseoField]
1611
+ () => _chunkLHUFL5LScjs.getOgTitleValidation.call(void 0, value || "", keywords, isParentseoField, keywordsVisible),
1612
+ [value, keywords, isParentseoField, keywordsVisible]
1505
1613
  );
1506
1614
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
1507
1615
  renderDefault(props),
@@ -1554,7 +1662,9 @@ function openGraph(config = {}) {
1554
1662
  components: {
1555
1663
  input: OgTitle_default
1556
1664
  },
1557
- options: _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1665
+ options: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {}), {
1666
+ isKeywordsVisible: getKeywordsVisibilityChecker(config)
1667
+ })
1558
1668
  })),
1559
1669
  _sanity.defineField.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
1560
1670
  name: "description"
@@ -1565,7 +1675,9 @@ function openGraph(config = {}) {
1565
1675
  components: {
1566
1676
  input: OgDescription_default
1567
1677
  },
1568
- options: _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1678
+ options: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {}), {
1679
+ isKeywordsVisible: getKeywordsVisibilityChecker(config)
1680
+ })
1569
1681
  })),
1570
1682
  _sanity.defineField.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
1571
1683
  name: "siteName"
@@ -1657,14 +1769,17 @@ function openGraph(config = {}) {
1657
1769
 
1658
1770
 
1659
1771
  var TwitterDescription = (props) => {
1772
+ var _a, _b;
1660
1773
  const { value, renderDefault, path, schemaType, onChange } = props;
1661
1774
  const { options } = schemaType;
1662
1775
  const parent = _sanity.useFormValue.call(void 0, [path[0]]);
1663
1776
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1664
1777
  const keywords = _react.useMemo.call(void 0, () => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1778
+ const rootDoc = _sanity.useFormValue.call(void 0, []);
1779
+ const keywordsVisible = (_b = (_a = options == null ? void 0 : options.isKeywordsVisible) == null ? void 0 : _a.call(options, rootDoc == null ? void 0 : rootDoc._type)) != null ? _b : true;
1665
1780
  const feedbackItems = _react.useMemo.call(void 0,
1666
- () => _chunkY46ACXM4cjs.getTwitterDescriptionValidation.call(void 0, value || "", keywords, isParentseoField),
1667
- [value, keywords, isParentseoField]
1781
+ () => _chunkLHUFL5LScjs.getTwitterDescriptionValidation.call(void 0, value || "", keywords, isParentseoField, keywordsVisible),
1782
+ [value, keywords, isParentseoField, keywordsVisible]
1668
1783
  );
1669
1784
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
1670
1785
  renderDefault(props),
@@ -1707,7 +1822,7 @@ var TwitterImage = (props) => {
1707
1822
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
1708
1823
  const altText = imgValue == null ? void 0 : imgValue.alt;
1709
1824
  const feedbackItems = _react.useMemo.call(void 0,
1710
- () => _chunkY46ACXM4cjs.getTwitterImageValidation.call(void 0, hasImage, altText, seoParent),
1825
+ () => _chunkLHUFL5LScjs.getTwitterImageValidation.call(void 0, hasImage, altText, seoParent),
1711
1826
  [hasImage, altText, seoParent]
1712
1827
  );
1713
1828
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
@@ -1739,7 +1854,7 @@ var TwitterImageUrl = (props) => {
1739
1854
  const { value, renderDefault, path } = props;
1740
1855
  const seoParent = _sanity.useFormValue.call(void 0, [path[0]]);
1741
1856
  const feedbackItems = _react.useMemo.call(void 0,
1742
- () => _chunkY46ACXM4cjs.getTwitterImageUrlValidation.call(void 0, value, seoParent),
1857
+ () => _chunkLHUFL5LScjs.getTwitterImageUrlValidation.call(void 0, value, seoParent),
1743
1858
  [value, seoParent]
1744
1859
  );
1745
1860
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
@@ -1768,14 +1883,17 @@ var TwitterImageUrl_default = TwitterImageUrl;
1768
1883
 
1769
1884
 
1770
1885
  var TwitterTitle = (props) => {
1886
+ var _a, _b;
1771
1887
  const { value, renderDefault, path, schemaType, onChange } = props;
1772
1888
  const { options } = schemaType;
1773
1889
  const parent = _sanity.useFormValue.call(void 0, [path[0]]);
1774
1890
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1775
1891
  const keywords = _react.useMemo.call(void 0, () => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1892
+ const rootDoc = _sanity.useFormValue.call(void 0, []);
1893
+ const keywordsVisible = (_b = (_a = options == null ? void 0 : options.isKeywordsVisible) == null ? void 0 : _a.call(options, rootDoc == null ? void 0 : rootDoc._type)) != null ? _b : true;
1776
1894
  const feedbackItems = _react.useMemo.call(void 0,
1777
- () => _chunkY46ACXM4cjs.getTwitterTitleValidation.call(void 0, value || "", keywords, isParentseoField),
1778
- [value, keywords, isParentseoField]
1895
+ () => _chunkLHUFL5LScjs.getTwitterTitleValidation.call(void 0, value || "", keywords, isParentseoField, keywordsVisible),
1896
+ [value, keywords, isParentseoField, keywordsVisible]
1779
1897
  );
1780
1898
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _ui.Stack, { space: 3, children: [
1781
1899
  renderDefault(props),
@@ -1849,7 +1967,9 @@ function twitter(config = {}) {
1849
1967
  components: {
1850
1968
  input: twitterTitle_default
1851
1969
  },
1852
- options: _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1970
+ options: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {}), {
1971
+ isKeywordsVisible: getKeywordsVisibilityChecker(config)
1972
+ })
1853
1973
  })),
1854
1974
  _sanity.defineField.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
1855
1975
  name: "description",
@@ -1860,7 +1980,9 @@ function twitter(config = {}) {
1860
1980
  components: {
1861
1981
  input: twitterDescription_default
1862
1982
  },
1863
- options: _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1983
+ options: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {}), {
1984
+ isKeywordsVisible: getKeywordsVisibilityChecker(config)
1985
+ })
1864
1986
  })),
1865
1987
  _sanity.defineField.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
1866
1988
  name: "imageType"
@@ -1919,7 +2041,7 @@ function twitter(config = {}) {
1919
2041
  }
1920
2042
 
1921
2043
  // src/schemas/index.ts
1922
- var LazySeoPreview = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./SeoPreview-LMZAWWOE.cjs"))));
2044
+ var LazySeoPreview = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./SeoPreview-A2XZVMT2.cjs"))));
1923
2045
  var SeoPreviewWrapper = (props) => _react.createElement.call(void 0, _react.Suspense, { fallback: null }, _react.createElement.call(void 0, LazySeoPreview, props));
1924
2046
  function buildFieldGroupMap(groups) {
1925
2047
  const map = /* @__PURE__ */ new Map();
@@ -1983,7 +2105,9 @@ function getAiOption(config) {
1983
2105
  return config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {};
1984
2106
  }
1985
2107
  function getTitleFieldOptions(config) {
1986
- return _chunkA57XNQC7cjs.__spreadValues.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(config.seoPreview)), getAiOption(config));
2108
+ return _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(config.seoPreview)), getAiOption(config)), {
2109
+ isKeywordsVisible: getKeywordsVisibilityChecker(config)
2110
+ });
1987
2111
  }
1988
2112
  function buildGeoChecklistField(config, fieldGroupMap) {
1989
2113
  if (config.geo === false) return [];
@@ -2023,6 +2147,7 @@ function buildMetaTagsPreviewField(config, fieldGroupMap) {
2023
2147
  ];
2024
2148
  }
2025
2149
  function seoFieldsSchema(config = {}) {
2150
+ var _a;
2026
2151
  const groupsCfg = config.fieldGroups;
2027
2152
  const fieldGroupMap = (groupsCfg == null ? void 0 : groupsCfg.length) ? buildFieldGroupMap(groupsCfg) : void 0;
2028
2153
  const sanityGroups = (groupsCfg == null ? void 0 : groupsCfg.length) ? toSanityGroups(groupsCfg) : void 0;
@@ -2078,7 +2203,9 @@ function seoFieldsSchema(config = {}) {
2078
2203
  components: {
2079
2204
  input: MetaDescription_default
2080
2205
  },
2081
- options: getAiOption(config),
2206
+ options: _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {}, getAiOption(config)), {
2207
+ isKeywordsVisible: getKeywordsVisibilityChecker(config)
2208
+ }),
2082
2209
  hidden: getFieldHiddenFunction("description", config)
2083
2210
  })),
2084
2211
  fieldGroupMap
@@ -2147,12 +2274,20 @@ function seoFieldsSchema(config = {}) {
2147
2274
  fieldGroupMap
2148
2275
  ),
2149
2276
  withGroup(
2150
- _sanity.defineField.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
2277
+ _sanity.defineField.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, _chunkA57XNQC7cjs.__spreadProps.call(void 0, _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
2151
2278
  name: "hreflangs"
2152
2279
  }, getFieldInfo("hreflangs", config.fieldOverrides)), {
2153
2280
  type: "array",
2154
2281
  of: [{ type: "hreflangEntry" }],
2155
- description: "Add alternate language/region versions. Include x-default for the fallback URL.",
2282
+ description: "Add alternate language/region versions. Include x-default for the fallback URL."
2283
+ }), ((_a = config.hreflang) == null ? void 0 : _a.autoFill) ? {
2284
+ components: { input: HreflangInput_default },
2285
+ options: {
2286
+ hreflang: config.hreflang,
2287
+ baseUrl: config.baseUrl,
2288
+ apiVersion: config.apiVersion
2289
+ }
2290
+ } : {}), {
2156
2291
  hidden: getFieldHiddenFunction("hreflangs", config)
2157
2292
  })),
2158
2293
  fieldGroupMap
@@ -2607,17 +2742,17 @@ var scoreKeywordArray = (keywords, title, description) => {
2607
2742
  }
2608
2743
  const issues = [];
2609
2744
  let score = 2;
2610
- if (_chunkY46ACXM4cjs.hasMatchingKeyword.call(void 0, title || "", keywords) || _chunkY46ACXM4cjs.hasMatchingKeyword.call(void 0, description || "", keywords)) {
2745
+ if (_chunkLHUFL5LScjs.hasMatchingKeyword.call(void 0, title || "", keywords) || _chunkLHUFL5LScjs.hasMatchingKeyword.call(void 0, description || "", keywords)) {
2611
2746
  score += 3;
2612
2747
  } else {
2613
2748
  issues.push("Keywords defined but not used in title or description");
2614
2749
  }
2615
- const stuffed = _chunkY46ACXM4cjs.hasKeywordOveruse.call(void 0, title || "", keywords) || _chunkY46ACXM4cjs.hasKeywordOveruse.call(void 0, description || "", keywords);
2750
+ const stuffed = _chunkLHUFL5LScjs.hasKeywordOveruse.call(void 0, title || "", keywords) || _chunkLHUFL5LScjs.hasKeywordOveruse.call(void 0, description || "", keywords);
2616
2751
  if (stuffed) issues.push("Keyword stuffing detected \u2014 reduce repeated keyword usage");
2617
2752
  return { score, issues, stuffed };
2618
2753
  };
2619
2754
  var scoreFocusKeyword = (focusKeyword, title, description) => {
2620
- const placement = _chunkY46ACXM4cjs.getFocusKeywordPlacement.call(void 0, focusKeyword, title, description);
2755
+ const placement = _chunkLHUFL5LScjs.getFocusKeywordPlacement.call(void 0, focusKeyword, title, description);
2621
2756
  if (!placement.hasFocusKeyword) {
2622
2757
  return { score: 0, issues: ["No focus keyword defined"], stuffed: false };
2623
2758
  }