tinacms 3.14.4 → 3.15.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/dist/index.js CHANGED
@@ -7,7 +7,8 @@ import React__default, { useState, useCallback, useRef, useEffect, createContext
7
7
  import { createPortal } from "react-dom";
8
8
  import { clsx } from "clsx";
9
9
  import { twMerge } from "tailwind-merge";
10
- import { CircleAlert, X, Search, ChevronDown, Check, AlertTriangle, BracesIcon, Heading6 as Heading6$1, Heading5 as Heading5$1, Heading4 as Heading4$1, Heading3 as Heading3$1, Heading2 as Heading2$1, Heading1 as Heading1$1, ChevronRight, Twitter, SunMedium, Moon, SeparatorHorizontal, Eye, Link2Off, Underline, Trash, Text, Table as Table$1, Superscript, Subscript, Strikethrough, Highlighter, Settings, RectangleHorizontal, RotateCcw, Pilcrow, Outdent, MoreHorizontal, Minus, WrapText, Keyboard, Indent, ExternalLink, Smile, Edit2, GripVertical, MessageSquarePlus, MessageSquare, Ungroup, Combine, RectangleVertical, Baseline, FileCode, PaintBucket, ChevronsUpDown, Quote, AlignRight as AlignRight$1, AlignLeft as AlignLeft$1, AlignJustify, AlignCenter as AlignCenter$1, Plus, PaintBucketIcon, CombineIcon, SquareSplitHorizontalIcon, Grid2X2Icon, Trash2Icon, ArrowUp, ArrowDown, XIcon, ArrowLeft, ArrowRight, EraserIcon, Info, CircleCheck as CircleCheck$1, CircleIcon, ChevronDownIcon as ChevronDownIcon$1, File as File$1, ChevronUp, Edit, Pencil, Clock, CalendarCheck, Calendar as Calendar$1, CalendarDays, RotateCw, ChevronLeft, WifiOff, TriangleAlert, RefreshCw, GitBranch, CircleHelp, Loader2, Lock, ArrowUpDown, GitFork, Globe, Copy, Folder as Folder$1, Clapperboard, LayoutGrid, List as List$1, CloudUpload, ArrowDownToLine, TextCursorInput, Images, AlertCircle, Image, Database, CheckCircle2, ImageOff, KeyRound, Cloud, LoaderCircle, ScrollText, RefreshCwOff, OctagonAlert, Menu, FilePlus, LogOut, FileStack, FolderOpen, History, Users, PanelLeft, Files, User, FileText, GitBranchIcon, Circle as Circle$1, ListOrdered, Grid3x3Icon, CircleX, Link, Unlink, SquarePen, Trash2, House, Unlock } from "lucide-react";
10
+ import { CircleAlert, X, Search, ChevronDown, Check, AlertTriangle, BracesIcon, Heading6 as Heading6$1, Heading5 as Heading5$1, Heading4 as Heading4$1, Heading3 as Heading3$1, Heading2 as Heading2$1, Heading1 as Heading1$1, ChevronRight, Twitter, SunMedium, Moon, SeparatorHorizontal, Eye, Link2Off, Underline, Trash, Text, Table as Table$1, Superscript, Subscript, Strikethrough, Baseline, Highlighter, Settings, RectangleHorizontal, RotateCcw, Pilcrow, Outdent, MoreHorizontal, Minus, WrapText, Keyboard, Indent, ExternalLink, Smile, Edit2, GripVertical, MessageSquarePlus, MessageSquare, Ungroup, Combine, RectangleVertical, FileCode, PaintBucket, ChevronsUpDown, Quote, AlignRight as AlignRight$1, AlignLeft as AlignLeft$1, AlignJustify, AlignCenter as AlignCenter$1, Plus, PaintBucketIcon, CombineIcon, SquareSplitHorizontalIcon, Grid2X2Icon, Trash2Icon, ArrowUp, ArrowDown, XIcon, ArrowLeft, ArrowRight, EraserIcon, Info, CircleCheck as CircleCheck$1, CircleIcon, ChevronDownIcon as ChevronDownIcon$1, File as File$1, ChevronUp, Edit, Pencil, Clock, CalendarCheck, Calendar as Calendar$1, CalendarDays, RotateCw, ChevronLeft, WifiOff, TriangleAlert, RefreshCw, GitBranch, CircleHelp, Loader2, Lock, ArrowUpDown, GitFork, Globe, Copy, Folder as Folder$1, Clapperboard, LayoutGrid, List as List$1, CloudUpload, ArrowDownToLine, TextCursorInput, Images, AlertCircle, Image, Database, CheckCircle2, ImageOff, KeyRound, Cloud, LoaderCircle, ScrollText, RefreshCwOff, OctagonAlert, Menu, FilePlus, LogOut, FileStack, FolderOpen, History, Users, PanelLeft, Files, User, FileText, GitBranchIcon, Circle as Circle$1, ListOrdered, Grid3x3Icon, CircleX, Link, Unlink, SquarePen, Trash2, House, Unlock } from "lucide-react";
11
+ import { isSafeCssColor } from "@tinacms/mdx/sanitize-css-color";
11
12
  import { withRef, cn as cn$1, withCn, createPrimitiveElement, withVariants, withProps, PortalBody, useComposedRef as useComposedRef$1 } from "@udecode/cn";
12
13
  import { BoldPlugin, ItalicPlugin, StrikethroughPlugin, UnderlinePlugin, CodePlugin, BasicMarksPlugin } from "@udecode/plate-basic-marks/react";
13
14
  import { BlockquotePlugin } from "@udecode/plate-block-quote/react";
@@ -19,7 +20,8 @@ import { useLink, LinkPlugin, useLinkToolbarButtonState, useLinkToolbarButton, u
19
20
  import { BulletedListPlugin, NumberedListPlugin, ListItemPlugin, TodoListPlugin, useListToolbarButtonState, useListToolbarButton, ListPlugin } from "@udecode/plate-list/react";
20
21
  import { SlashInputPlugin, SlashPlugin } from "@udecode/plate-slash-command/react";
21
22
  import { TablePlugin, TableRowPlugin, useTableCellElement, TableProvider, useTableElement, useTableMergeState, useTableBordersDropdownMenuContentState, TableCellPlugin, TableCellHeaderPlugin } from "@udecode/plate-table/react";
22
- import { PlateElement, useReadOnly, useElement, PlateLeaf, useSelected, useFocused, useEditorRef, withRef as withRef$1, useComposedRef, useEditorPlugin, useElementSelector, withHOC, usePluginOption, useRemoveNodeButton, useEditorSelector, ParagraphPlugin, createPlatePlugin, PlateContent, PlateContainer, useEditorState, useMarkToolbarButtonState, useMarkToolbarButton, usePlateEditor, useFormInputProps, useEditorSelection, useEditorReadOnly, useOnClickOutside, useEventEditorValue, Plate } from "@udecode/plate/react";
23
+ import { PlateElement, useReadOnly, useElement, PlateLeaf, useSelected, useFocused, useEditorRef, withRef as withRef$1, useComposedRef, useEditorPlugin, useElementSelector, withHOC, usePluginOption, useRemoveNodeButton, useEditorSelector, createPlatePlugin, ParagraphPlugin, PlateContent, PlateContainer, useEditorState, useMarkToolbarButtonState, useMarkToolbarButton, usePlateEditor, useFormInputProps, useEditorSelection, useEditorReadOnly, useOnClickOutside, useEventEditorValue, Plate } from "@udecode/plate/react";
24
+ import colorString, { get as get$1, to } from "color-string";
23
25
  import { isLangSupported, formatCodeBlock, insertEmptyCodeBlock, isCodeBlockEmpty, unwrapCodeBlock, isSelectionAtCodeBlockStart } from "@udecode/plate-code-block";
24
26
  import { Slot } from "@radix-ui/react-slot";
25
27
  import { cva } from "class-variance-authority";
@@ -32,7 +34,7 @@ import { useHTMLInputCursorState, useComboboxInput } from "@udecode/plate-combob
32
34
  import { useComboboxContext, Combobox as Combobox$1, useComboboxStore, ComboboxProvider, Portal, ComboboxPopover, ComboboxItem } from "@ariakit/react";
33
35
  import { filterWords } from "@udecode/plate-combobox";
34
36
  import { toggleList, unwrapList, getListItemEntry } from "@udecode/plate-list";
35
- import { normalizeHeadingLevels, ALL_HEADING_LEVELS, MEDIA_MIME_TYPES, extensionOf, resolveMediaAccept, ASYNC_POLLER_ERROR, ERR_BRANCH_EXISTS, ERR_BRANCH_CONFLICT, MEDIA_CATEGORIES, canonicalPath, isHeadingLevel, TinaSchema, addNamespaceToSchema, parseURL, ERR_HAS_REFERENCES, ERR_ALREADY_EXISTS, isValidRelativePath, RELATIVE_PATH_ALLOWED_CHARS_MESSAGE, resolveForm, RELATIVE_PATH_REGEX, normalizePath, ERR_NOT_INDEXED, validateSchema } from "@tinacms/schema-tools";
37
+ import { normalizeHeadingLevels, ALL_HEADING_LEVELS, DEFAULT_TEXT_COLORS, MEDIA_MIME_TYPES, extensionOf, resolveMediaAccept, ASYNC_POLLER_ERROR, ERR_BRANCH_EXISTS, ERR_BRANCH_CONFLICT, MEDIA_CATEGORIES, canonicalPath, isHeadingLevel, TinaSchema, addNamespaceToSchema, parseURL, ERR_HAS_REFERENCES, ERR_ALREADY_EXISTS, isValidRelativePath, RELATIVE_PATH_ALLOWED_CHARS_MESSAGE, resolveForm, RELATIVE_PATH_REGEX, normalizePath, ERR_NOT_INDEXED, validateSchema } from "@tinacms/schema-tools";
36
38
  import { NAMER, resolveField } from "@tinacms/schema-tools";
37
39
  import { useBlockSelected, BlockSelectionPlugin } from "@udecode/plate-selection/react";
38
40
  import "@udecode/plate-dnd";
@@ -41,7 +43,6 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
41
43
  import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
42
44
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
43
45
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
44
- import colorString, { get as get$1, to } from "color-string";
45
46
  import { ElementApi, isType, NodeApi, mergeProps, createSlatePlugin } from "@udecode/plate";
46
47
  import { createForm, FORM_ERROR, getIn } from "final-form";
47
48
  import arrayMutators from "final-form-arrays";
@@ -2006,6 +2007,7 @@ const Icons = {
2006
2007
  search: Search,
2007
2008
  settings: Settings,
2008
2009
  highlight: Highlighter,
2010
+ textColor: Baseline,
2009
2011
  strikethrough: Strikethrough,
2010
2012
  subscript: Subscript,
2011
2013
  superscript: Superscript,
@@ -2602,18 +2604,33 @@ const InlineComboboxEmpty = ({
2602
2604
  const ToolbarContext = createContext(
2603
2605
  void 0
2604
2606
  );
2607
+ const resolvePalette = (configured, fallback) => {
2608
+ if (!configured) return fallback;
2609
+ return configured.filter(({ value }) => {
2610
+ if (isSafeCssColor(value)) return true;
2611
+ console.warn(
2612
+ `[tinacms] Ignoring unsupported colour "${value}" in rich-text overrides.textColors. Use hex, a named colour, a colour function such as rgb()/lab()/color-mix(), or var(--name).`
2613
+ );
2614
+ return false;
2615
+ });
2616
+ };
2605
2617
  const ToolbarProvider = ({
2606
2618
  tinaForm,
2607
2619
  templates,
2608
2620
  overrides,
2609
2621
  children
2610
2622
  }) => {
2611
- const configured = !Array.isArray(overrides) ? overrides == null ? void 0 : overrides.headingLevels : void 0;
2623
+ const objectOverrides = !Array.isArray(overrides) ? overrides : void 0;
2624
+ const configured = objectOverrides == null ? void 0 : objectOverrides.headingLevels;
2612
2625
  const headingLevelsConfigured = Array.isArray(configured);
2613
2626
  const headingLevels = useMemo(
2614
2627
  () => configured ? normalizeHeadingLevels(configured) : ALL_HEADING_LEVELS,
2615
2628
  [configured]
2616
2629
  );
2630
+ const textColors = useMemo(
2631
+ () => resolvePalette(objectOverrides == null ? void 0 : objectOverrides.textColors, DEFAULT_TEXT_COLORS),
2632
+ [objectOverrides == null ? void 0 : objectOverrides.textColors]
2633
+ );
2617
2634
  return /* @__PURE__ */ React__default.createElement(
2618
2635
  ToolbarContext.Provider,
2619
2636
  {
@@ -2622,7 +2639,8 @@ const ToolbarProvider = ({
2622
2639
  templates,
2623
2640
  overrides,
2624
2641
  headingLevels,
2625
- headingLevelsConfigured
2642
+ headingLevelsConfigured,
2643
+ textColors
2626
2644
  }
2627
2645
  },
2628
2646
  children
@@ -3315,6 +3333,10 @@ const TableRowElement = withRef(
3315
3333
  );
3316
3334
  }
3317
3335
  );
3336
+ const TextColorPlugin = createPlatePlugin({
3337
+ key: "textColor",
3338
+ node: { isLeaf: true }
3339
+ });
3318
3340
  function classNames$1(...classes) {
3319
3341
  return classes.filter(Boolean).join(" ");
3320
3342
  }
@@ -3333,22 +3355,38 @@ function getContrastColor(color) {
3333
3355
  const luminance = (0.299 * r2 + 0.587 * g + 0.114 * b) / 255;
3334
3356
  return luminance > 0.5 ? "#000000" : "#ffffff";
3335
3357
  }
3358
+ const leafColor = (value) => typeof value === "string" && isSafeCssColor(value) ? value : void 0;
3359
+ const INHERIT_NESTED_COLOR = "[&_strong]:text-inherit [&_em]:text-inherit [&_s]:text-inherit [&_u]:text-inherit";
3336
3360
  const HighlightLeaf = ({
3337
3361
  leaf: leaf2,
3338
3362
  ...props
3339
3363
  }) => {
3340
- const backgroundColor = leaf2.highlightColor || "#FEF08A";
3364
+ const backgroundColor = leafColor(leaf2.highlightColor) ?? "#FEF08A";
3365
+ const color = leafColor(leaf2.textColor) ?? getContrastColor(backgroundColor);
3341
3366
  return /* @__PURE__ */ React__default.createElement(
3342
3367
  PlateLeaf,
3343
3368
  {
3344
3369
  as: "mark",
3345
- className: "rounded-sm",
3346
- style: { backgroundColor, color: getContrastColor(backgroundColor) },
3370
+ className: `rounded-sm ${INHERIT_NESTED_COLOR}`,
3371
+ style: { backgroundColor, color },
3347
3372
  leaf: leaf2,
3348
3373
  ...props
3349
3374
  }
3350
3375
  );
3351
3376
  };
3377
+ const TextColorLeaf = ({
3378
+ leaf: leaf2,
3379
+ ...props
3380
+ }) => /* @__PURE__ */ React__default.createElement(
3381
+ PlateLeaf,
3382
+ {
3383
+ as: "span",
3384
+ className: INHERIT_NESTED_COLOR,
3385
+ style: { color: leafColor(leaf2.textColor) },
3386
+ leaf: leaf2,
3387
+ ...props
3388
+ }
3389
+ );
3352
3390
  const Heading1 = ({
3353
3391
  attributes,
3354
3392
  children,
@@ -3505,6 +3543,7 @@ const Components = () => {
3505
3543
  [LinkPlugin.key]: LinkElement,
3506
3544
  [CodePlugin.key]: CodeLeaf,
3507
3545
  [HighlightPlugin.key]: HighlightLeaf,
3546
+ [TextColorPlugin.key]: TextColorLeaf,
3508
3547
  [UnderlinePlugin.key]: withProps(PlateLeaf, { as: "u" }),
3509
3548
  [StrikethroughPlugin.key]: withProps(PlateLeaf, { as: "s" }),
3510
3549
  [ItalicPlugin.key]: withProps(PlateLeaf, { as: "em" }),
@@ -6306,7 +6345,7 @@ const ListFieldMeta = ({
6306
6345
  style: { zIndex: index != null ? 1e3 - index : void 0 },
6307
6346
  ...props
6308
6347
  },
6309
- /* @__PURE__ */ React.createElement(ListHeader, null, /* @__PURE__ */ React.createElement(ListMeta, null, label !== false && /* @__PURE__ */ React.createElement(ListLabel, null, label || name2), description && /* @__PURE__ */ React.createElement(FieldDescription, { className: "whitespace-nowrap text-ellipsis overflow-hidden" }, description)), actions && actions),
6348
+ /* @__PURE__ */ React.createElement(ListHeader, null, /* @__PURE__ */ React.createElement(ListMeta, null, label !== false && /* @__PURE__ */ React.createElement(ListLabel, null, label || name2), description && /* @__PURE__ */ React.createElement(FieldDescription, null, description)), actions && actions),
6310
6349
  children,
6311
6350
  error && typeof error === "string" && /* @__PURE__ */ React.createElement(FieldError, null, error)
6312
6351
  );
@@ -6315,7 +6354,7 @@ const ListHeader = ({ children }) => {
6315
6354
  return /* @__PURE__ */ React.createElement("span", { className: "relative flex gap-2 w-full justify-between items-center mb-2" }, children);
6316
6355
  };
6317
6356
  const ListMeta = ({ children }) => {
6318
- return /* @__PURE__ */ React.createElement("div", { className: "flex-1 truncate" }, children);
6357
+ return /* @__PURE__ */ React.createElement("div", { className: "flex-1 min-w-0" }, children);
6319
6358
  };
6320
6359
  const ListLabel = ({ children }) => {
6321
6360
  return /* @__PURE__ */ React.createElement(
@@ -7808,6 +7847,10 @@ const TagsFieldPlugin = {
7808
7847
  };
7809
7848
  const DEFAULT_DATE_DISPLAY_FORMAT = "MMM DD, YYYY";
7810
7849
  const DEFAULT_TIME_DISPLAY_FORMAT = "h:mm A";
7850
+ const getHourCycle = (timeFormat) => {
7851
+ const tokens = (timeFormat ?? "").replace(/\[[^\]]*\]/g, "");
7852
+ return /[Hk]/.test(tokens) ? 24 : 12;
7853
+ };
7811
7854
  const format$2 = (val, _name, field) => {
7812
7855
  if (!val) return val;
7813
7856
  return new Date(val).toISOString();
@@ -8723,7 +8766,7 @@ const DateField = wrapFieldsWithMeta(
8723
8766
  value ? value.toISOString() : required !== false ? (/* @__PURE__ */ new Date()).toISOString() : value
8724
8767
  ),
8725
8768
  timeFormat: getTimeFormat(),
8726
- hourCycle: 12,
8769
+ hourCycle: getHourCycle(getTimeFormat()),
8727
8770
  dateFormat: getDateFormat(),
8728
8771
  value: date,
8729
8772
  locale: rest.locale ? { code: rest.locale } : void 0
@@ -14328,7 +14371,7 @@ const FormModal = ({ plugin, close: close2 }) => {
14328
14371
  const name = "tinacms";
14329
14372
  const type = "module";
14330
14373
  const typings = "dist/index.d.ts";
14331
- const version = "3.14.4";
14374
+ const version = "3.15.0";
14332
14375
  const main = "dist/index.js";
14333
14376
  const module$1 = "./dist/index.js";
14334
14377
  const exports$1 = { ".": "./dist/index.js", "./dist/client": { "types": "./dist/client.d.ts", "browser": "./dist/client.browser.js", "default": "./dist/client.js" }, "./react": "./dist/react.js", "./dist/react": "./dist/react.js", "./tina-field": "./dist/tina-field.js", "./dist/tina-field": "./dist/tina-field.js", "./dist/rich-text": "./dist/rich-text/index.js", "./dist/rich-text/static": "./dist/rich-text/static.js", "./dist/rich-text/prism": "./dist/rich-text/prism.js" };
@@ -14337,7 +14380,7 @@ const buildConfig = { "entryPoints": ["src/index.ts", "src/rich-text/index.tsx",
14337
14380
  const license = "Apache-2.0";
14338
14381
  const scripts = { "types": "pnpm tsc && pnpm tsc-alias", "build": "tinacms-scripts build", "test": "vitest run", "test-watch": "vitest" };
14339
14382
  const dependencies = { "@ariakit/react": "catalog:", "@dnd-kit/core": "catalog:", "@dnd-kit/sortable": "catalog:", "@dnd-kit/utilities": "catalog:", "@floating-ui/dom": "catalog:", "@floating-ui/react-dom": "catalog:", "@graphql-inspector/core": "^6.2.1", "@radix-ui/react-dialog": "catalog:", "@radix-ui/react-dropdown-menu": "catalog:", "@radix-ui/react-popover": "catalog:", "@radix-ui/react-select": "catalog:", "@radix-ui/react-separator": "catalog:", "@radix-ui/react-slot": "catalog:", "@radix-ui/react-toolbar": "catalog:", "@radix-ui/react-tooltip": "catalog:", "@react-hook/window-size": "catalog:", "@tanstack/react-table": "^8.21.3", "@tinacms/bridge": "workspace:^", "@tinacms/mdx": "workspace:^", "@tinacms/schema-tools": "workspace:^", "@tinacms/search": "workspace:^", "@udecode/cmdk": "catalog:", "@udecode/cn": "catalog:", "@udecode/plate": "catalog:", "@udecode/plate-autoformat": "catalog:", "@udecode/plate-basic-marks": "catalog:", "@udecode/plate-block-quote": "catalog:", "@udecode/plate-break": "catalog:", "@udecode/plate-code-block": "catalog:", "@udecode/plate-combobox": "catalog:", "@udecode/plate-dnd": "catalog:", "@udecode/plate-floating": "catalog:", "@udecode/plate-heading": "catalog:", "@udecode/plate-highlight": "catalog:", "@udecode/plate-horizontal-rule": "catalog:", "@udecode/plate-indent-list": "catalog:", "@udecode/plate-link": "catalog:", "@udecode/plate-list": "catalog:", "@udecode/plate-node-id": "catalog:", "@udecode/plate-reset-node": "catalog:", "@udecode/plate-resizable": "catalog:", "@udecode/plate-selection": "catalog:", "@udecode/plate-slash-command": "catalog:", "@udecode/plate-table": "catalog:", "@udecode/plate-trailing-block": "catalog:", "class-variance-authority": "catalog:", "clsx": "catalog:", "cmdk": "catalog:", "color-string": "catalog:", "date-fns": "4.1.0", "es-toolkit": "^1.42.0", "final-form": "catalog:", "final-form-arrays": "catalog:", "final-form-set-field-data": "catalog:", "graphql": "15.8.0", "graphql-tag": "catalog:", "is-hotkey": "catalog:", "lucide-react": "catalog:", "mermaid": "catalog:", "posthog-js": "^1.347.1", "prism-react-renderer": "catalog:", "prop-types": "catalog:", "react-colorful": "catalog:", "react-day-picker": "^9.13.0", "react-dnd": "catalog:", "react-dnd-html5-backend": "catalog:", "react-dropzone": "catalog:", "react-final-form": "catalog:", "react-router-dom": "catalog:", "react-use": "catalog:", "tailwind-merge": "^2.6.0", "webfontloader": "catalog:", "yup": "^1.6.1", "zod": "catalog:" };
14340
- const devDependencies = { "@graphql-tools/utils": "catalog:", "@testing-library/dom": "catalog:", "@testing-library/jest-dom": "catalog:", "@testing-library/react": "catalog:", "@testing-library/user-event": "catalog:", "@tinacms/scripts": "workspace:*", "@types/atob": "catalog:", "@types/codemirror": "catalog:", "@types/color-string": "catalog:", "@types/node": "^22.13.1", "@types/prop-types": "catalog:", "@types/react": "^18.3.18", "@types/react-dom": "catalog:", "happy-dom": "catalog:", "identity-obj-proxy": "catalog:", "jest-file-snapshot": "^0.7.0", "lowlight": "catalog:", "next": "15.5.21", "react": "^18.3.1", "react-dom": "^18.3.1", "react-is": "^18.3.1", "tsc-alias": "catalog:", "tslib": "catalog:", "typescript": "^5.7.3", "vite": "^5.4.14", "vitest": "^2.1.9" };
14383
+ const devDependencies = { "@testing-library/dom": "catalog:", "@testing-library/jest-dom": "catalog:", "@testing-library/react": "catalog:", "@testing-library/user-event": "catalog:", "@tinacms/scripts": "workspace:*", "@types/atob": "catalog:", "@types/codemirror": "catalog:", "@types/color-string": "catalog:", "@types/node": "^22.13.1", "@types/prop-types": "catalog:", "@types/react": "^18.3.18", "@types/react-dom": "catalog:", "happy-dom": "catalog:", "identity-obj-proxy": "catalog:", "jest-file-snapshot": "^0.7.0", "lowlight": "catalog:", "next": "15.5.27", "react": "^18.3.1", "react-dom": "^18.3.1", "react-is": "^18.3.1", "tsc-alias": "catalog:", "tslib": "catalog:", "typescript": "^5.7.3", "vite": "^5.4.14", "vitest": "^2.1.9" };
14341
14384
  const peerDependencies = { "react": ">=16.14.0", "react-dom": ">=16.14.0" };
14342
14385
  const publishConfig = { "registry": "https://registry.npmjs.org" };
14343
14386
  const repository = { "url": "https://github.com/tinacms/tinacms.git", "directory": "packages/tinacms" };
@@ -19677,18 +19720,11 @@ const MarkToolbarButton = withRef(({ clear, nodeType, ...rest }, ref) => {
19677
19720
  const { props } = useMarkToolbarButton(state);
19678
19721
  return /* @__PURE__ */ React__default.createElement(ToolbarButton, { ref, ...props, ...rest });
19679
19722
  });
19680
- const highlightColors = [
19681
- { label: "Yellow", value: "#FEF08A" },
19682
- { label: "Green", value: "#BBF7D0" },
19683
- { label: "Blue", value: "#BFDBFE" },
19684
- { label: "Red", value: "#CC4141" }
19685
- ];
19686
19723
  const BoldToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Bold (⌘+B)", nodeType: BoldPlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.bold, null));
19687
19724
  const StrikethroughToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Strikethrough", nodeType: StrikethroughPlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.strikethrough, null));
19688
19725
  const ItalicToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Italic (⌘+I)", nodeType: ItalicPlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.italic, null));
19689
19726
  const CodeToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Code (⌘+E)", nodeType: CodePlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.code, null));
19690
- const HighlightToolbarButton = () => /* @__PURE__ */ React__default.createElement(HighlightColorToolbarButton, null);
19691
- const useHighlightToolbar = () => {
19727
+ const useColorMarkToolbar = (toMarks) => {
19692
19728
  const editor = useEditorRef();
19693
19729
  const openState = useOpenState();
19694
19730
  const savedSelection = React__default.useRef(editor.selection);
@@ -19705,8 +19741,8 @@ const useHighlightToolbar = () => {
19705
19741
  rememberSelection();
19706
19742
  }
19707
19743
  }, [openState.open, rememberSelection]);
19708
- const applyHighlight = React__default.useCallback(
19709
- (highlightColor) => {
19744
+ const applyColor = React__default.useCallback(
19745
+ (color) => {
19710
19746
  if (inlineCodeActive) {
19711
19747
  openState.onOpenChange(false);
19712
19748
  return;
@@ -19714,67 +19750,109 @@ const useHighlightToolbar = () => {
19714
19750
  if (savedSelection.current) {
19715
19751
  editor.tf.select(structuredClone(savedSelection.current));
19716
19752
  }
19717
- if (highlightColor) {
19718
- editor.tf.addMark("highlight", true);
19719
- editor.tf.addMark("highlightColor", highlightColor);
19720
- } else {
19721
- editor.tf.removeMark("highlight");
19722
- editor.tf.removeMark("highlightColor");
19723
- }
19724
- editor.tf.setNodes(
19725
- highlightColor ? {
19726
- highlight: true,
19727
- highlightColor
19728
- } : {
19729
- highlight: void 0,
19730
- highlightColor: void 0
19731
- },
19732
- {
19733
- at: editor.selection ?? void 0,
19734
- match: (node) => editor.api.isText(node),
19735
- split: true
19753
+ const marks = toMarks(color);
19754
+ for (const [key, value] of Object.entries(marks)) {
19755
+ if (value === void 0) {
19756
+ editor.tf.removeMark(key);
19757
+ } else {
19758
+ editor.tf.addMark(key, value);
19736
19759
  }
19737
- );
19760
+ }
19761
+ editor.tf.setNodes(marks, {
19762
+ at: editor.selection ?? void 0,
19763
+ match: (node) => editor.api.isText(node),
19764
+ split: true
19765
+ });
19738
19766
  editor.tf.focus();
19739
19767
  openState.onOpenChange(false);
19740
19768
  },
19741
- [editor, inlineCodeActive, openState]
19769
+ [editor, inlineCodeActive, openState, toMarks]
19742
19770
  );
19743
19771
  return {
19744
- applyHighlight,
19772
+ applyColor,
19745
19773
  inlineCodeActive,
19746
19774
  openState,
19747
19775
  rememberSelection
19748
19776
  };
19749
19777
  };
19750
- const HighlightColorToolbarButton = () => {
19751
- const { applyHighlight, inlineCodeActive, openState, rememberSelection } = useHighlightToolbar();
19778
+ const ColorDropdownToolbarButton = ({
19779
+ tooltip,
19780
+ icon,
19781
+ clearLabel,
19782
+ colors,
19783
+ toMarks,
19784
+ renderSwatch
19785
+ }) => {
19786
+ const { applyColor, inlineCodeActive, openState, rememberSelection } = useColorMarkToolbar(toMarks);
19752
19787
  return /* @__PURE__ */ React__default.createElement(DropdownMenu$1, { modal: false, ...openState }, /* @__PURE__ */ React__default.createElement(DropdownMenuTrigger$1, { asChild: true }, /* @__PURE__ */ React__default.createElement(
19753
19788
  ToolbarButton,
19754
19789
  {
19755
19790
  isDropdown: true,
19756
19791
  showArrow: true,
19757
19792
  pressed: openState.open,
19758
- tooltip: "Highlight color",
19793
+ tooltip,
19759
19794
  disabled: inlineCodeActive,
19760
19795
  onMouseDown: rememberSelection
19761
19796
  },
19762
- /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React__default.createElement(Icons.highlight, null), /* @__PURE__ */ React__default.createElement("span", { className: "sr-only" }, "Highlight color"))
19763
- )), /* @__PURE__ */ React__default.createElement(DropdownMenuContent$1, { align: "start", className: "min-w-[180px]" }, /* @__PURE__ */ React__default.createElement(DropdownMenuItem$1, { onSelect: () => applyHighlight() }, "Clear highlight"), highlightColors.map((color) => /* @__PURE__ */ React__default.createElement(
19797
+ /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-1.5" }, icon, /* @__PURE__ */ React__default.createElement("span", { className: "sr-only" }, tooltip))
19798
+ )), /* @__PURE__ */ React__default.createElement(DropdownMenuContent$1, { align: "start", className: "min-w-[180px]" }, /* @__PURE__ */ React__default.createElement(DropdownMenuItem$1, { onSelect: () => applyColor() }, clearLabel), colors.map((color) => /* @__PURE__ */ React__default.createElement(
19764
19799
  DropdownMenuItem$1,
19765
19800
  {
19766
19801
  key: color.value,
19767
- onSelect: () => applyHighlight(color.value)
19802
+ onSelect: () => applyColor(color.value)
19768
19803
  },
19769
- /* @__PURE__ */ React__default.createElement(
19804
+ renderSwatch(color.value),
19805
+ color.label
19806
+ ))));
19807
+ };
19808
+ const toHighlightMarks = (color) => color ? { highlight: true, highlightColor: color } : { highlight: void 0, highlightColor: void 0 };
19809
+ const toTextColorMarks = (color) => ({
19810
+ textColor: color
19811
+ });
19812
+ const highlightColors = [
19813
+ { label: "Yellow", value: "#FEF08A" },
19814
+ { label: "Green", value: "#BBF7D0" },
19815
+ { label: "Blue", value: "#BFDBFE" },
19816
+ { label: "Red", value: "#CC4141" }
19817
+ ];
19818
+ const HighlightToolbarButton = () => /* @__PURE__ */ React__default.createElement(
19819
+ ColorDropdownToolbarButton,
19820
+ {
19821
+ tooltip: "Highlight color",
19822
+ icon: /* @__PURE__ */ React__default.createElement(Icons.highlight, null),
19823
+ clearLabel: "Clear highlight",
19824
+ colors: highlightColors,
19825
+ toMarks: toHighlightMarks,
19826
+ renderSwatch: (color) => /* @__PURE__ */ React__default.createElement(
19770
19827
  "span",
19771
19828
  {
19772
19829
  className: "mr-2 inline-block size-4 rounded border border-gray-300",
19773
- style: { backgroundColor: color.value }
19830
+ style: { backgroundColor: color }
19774
19831
  }
19775
- ),
19776
- color.label
19777
- ))));
19832
+ )
19833
+ }
19834
+ );
19835
+ const TextColorToolbarButton = () => {
19836
+ const { textColors } = useToolbarContext();
19837
+ return /* @__PURE__ */ React__default.createElement(
19838
+ ColorDropdownToolbarButton,
19839
+ {
19840
+ tooltip: "Text color",
19841
+ icon: /* @__PURE__ */ React__default.createElement(Icons.textColor, null),
19842
+ clearLabel: "Clear text color",
19843
+ colors: textColors,
19844
+ toMarks: toTextColorMarks,
19845
+ renderSwatch: (color) => /* @__PURE__ */ React__default.createElement(
19846
+ "span",
19847
+ {
19848
+ "aria-hidden": true,
19849
+ className: "mr-2 inline-block w-4 text-center font-bold",
19850
+ style: { color }
19851
+ },
19852
+ "A"
19853
+ )
19854
+ }
19855
+ );
19778
19856
  };
19779
19857
  const ListToolbarButton = withRef(({ nodeType = BulletedListPlugin.key, ...rest }, ref) => {
19780
19858
  const state = useListToolbarButtonState({ nodeType });
@@ -20014,6 +20092,11 @@ const toolbarItems = {
20014
20092
  width: () => STANDARD_ICON_WIDTH,
20015
20093
  Component: /* @__PURE__ */ React__default.createElement(HighlightToolbarButton, null)
20016
20094
  },
20095
+ textColor: {
20096
+ label: "Text Color",
20097
+ width: () => STANDARD_ICON_WIDTH,
20098
+ Component: /* @__PURE__ */ React__default.createElement(TextColorToolbarButton, null)
20099
+ },
20017
20100
  italic: {
20018
20101
  label: "Italic",
20019
20102
  width: () => STANDARD_ICON_WIDTH,
@@ -71414,6 +71497,7 @@ const viewPlugins = [
71414
71497
  BasicMarksPlugin,
71415
71498
  UnderlinePlugin,
71416
71499
  HighlightPlugin,
71500
+ TextColorPlugin,
71417
71501
  HeadingPlugin.configure({ options: { levels: 6 } }),
71418
71502
  ParagraphPlugin,
71419
71503
  CodeBlockPlugin.configure({
@@ -71424,24 +71508,24 @@ const viewPlugins = [
71424
71508
  const CorrectNodeBehaviorPlugin = createSlatePlugin({
71425
71509
  key: "WITH_CORRECT_NODE_BEHAVIOR"
71426
71510
  });
71427
- const ClearHighlightOnEnterPlugin = createSlatePlugin({
71428
- key: "CLEAR_HIGHLIGHT_ON_ENTER"
71511
+ const COLOR_MARK_KEYS = ["highlight", "highlightColor", "textColor"];
71512
+ const ClearColorMarksOnEnterPlugin = createSlatePlugin({
71513
+ key: "CLEAR_COLOR_MARKS_ON_ENTER"
71429
71514
  }).overrideEditor(({ editor, tf: { insertBreak } }) => ({
71430
71515
  transforms: {
71431
71516
  insertBreak() {
71432
71517
  const keyboardEvent = editor.currentKeyboardEvent;
71433
71518
  const isPlainEnter = (keyboardEvent == null ? void 0 : keyboardEvent.key) === "Enter" && !keyboardEvent.shiftKey && !keyboardEvent.metaKey && !keyboardEvent.ctrlKey && !keyboardEvent.altKey;
71434
71519
  const activeMarks = editor.api.marks();
71435
- const hasHighlight = Boolean(
71436
- (activeMarks == null ? void 0 : activeMarks.highlight) || (activeMarks == null ? void 0 : activeMarks.highlightColor)
71520
+ const hasColorMark = COLOR_MARK_KEYS.some(
71521
+ (key) => Boolean(activeMarks == null ? void 0 : activeMarks[key])
71437
71522
  );
71438
71523
  insertBreak();
71439
- if (!isPlainEnter || !hasHighlight) {
71524
+ if (!isPlainEnter || !hasColorMark || !editor.api.isEmpty(editor.selection, { block: true })) {
71440
71525
  return;
71441
71526
  }
71442
- editor.tf.removeMark("highlight");
71443
- editor.tf.removeMark("highlightColor");
71444
- editor.tf.unsetNodes(["highlight", "highlightColor"], {
71527
+ editor.tf.removeMarks(COLOR_MARK_KEYS);
71528
+ editor.tf.unsetNodes(COLOR_MARK_KEYS, {
71445
71529
  at: editor.selection ?? void 0,
71446
71530
  match: (node) => editor.api.isText(node)
71447
71531
  });
@@ -71459,7 +71543,6 @@ const createEditorPlugins = ({
71459
71543
  createHardBreakPlugin,
71460
71544
  createInvalidMarkdownPlugin,
71461
71545
  CorrectNodeBehaviorPlugin,
71462
- ClearHighlightOnEnterPlugin,
71463
71546
  LinkPlugin.configure({
71464
71547
  options: {
71465
71548
  // Custom validation function to allow relative links, e.g., /about
@@ -71567,7 +71650,9 @@ const createEditorPlugins = ({
71567
71650
  }
71568
71651
  ]
71569
71652
  }
71570
- })
71653
+ }),
71654
+ // Last, so it wraps list and other block handlers that don't pass Enter on
71655
+ ClearColorMarksOnEnterPlugin
71571
71656
  ];
71572
71657
  const RichEditor = ({ input, tinaForm, field }) => {
71573
71658
  var _a, _b;
@@ -47,6 +47,10 @@ type BaseComponents = {
47
47
  children: JSX.Element;
48
48
  color?: string;
49
49
  };
50
+ textColor?: {
51
+ children: JSX.Element;
52
+ color: string;
53
+ };
50
54
  text?: {
51
55
  children: string;
52
56
  };
@@ -1,3 +1,4 @@
1
+ import { isSafeCssColor } from "@tinacms/mdx/sanitize-css-color";
1
2
  import { sanitizeUrl } from "@tinacms/mdx/sanitize-url";
2
3
  import React from "react";
3
4
  const TinaMarkdown = ({
@@ -15,6 +16,7 @@ const TinaMarkdown = ({
15
16
  return /* @__PURE__ */ React.createElement(MemoNode, { components, key: index, child });
16
17
  }));
17
18
  };
19
+ const safeColor = (value) => value && isSafeCssColor(value) ? value : void 0;
18
20
  const Leaf = (props) => {
19
21
  if (props.bold) {
20
22
  const { bold, ...rest } = props;
@@ -58,17 +60,24 @@ const Leaf = (props) => {
58
60
  }
59
61
  if (props.highlight) {
60
62
  const { highlight, highlightColor, ...rest } = props;
63
+ const color = safeColor(highlightColor);
61
64
  if (props.components.highlight) {
62
65
  const Component = props.components.highlight;
63
- return /* @__PURE__ */ React.createElement(Component, { color: highlightColor }, /* @__PURE__ */ React.createElement(Leaf, { ...rest }));
66
+ return /* @__PURE__ */ React.createElement(Component, { color }, /* @__PURE__ */ React.createElement(Leaf, { ...rest }));
64
67
  }
65
- return /* @__PURE__ */ React.createElement(
66
- "mark",
67
- {
68
- style: highlightColor ? { backgroundColor: highlightColor } : void 0
69
- },
70
- /* @__PURE__ */ React.createElement(Leaf, { ...rest })
71
- );
68
+ return /* @__PURE__ */ React.createElement("mark", { style: color ? { backgroundColor: color } : void 0 }, /* @__PURE__ */ React.createElement(Leaf, { ...rest }));
69
+ }
70
+ if (props.textColor) {
71
+ const { textColor, ...rest } = props;
72
+ const color = safeColor(textColor);
73
+ if (!color) {
74
+ return /* @__PURE__ */ React.createElement(Leaf, { ...rest });
75
+ }
76
+ if (props.components.textColor) {
77
+ const Component = props.components.textColor;
78
+ return /* @__PURE__ */ React.createElement(Component, { color }, /* @__PURE__ */ React.createElement(Leaf, { ...rest }));
79
+ }
80
+ return /* @__PURE__ */ React.createElement("span", { style: { color } }, /* @__PURE__ */ React.createElement(Leaf, { ...rest }));
72
81
  }
73
82
  if (props.components.text) {
74
83
  const Component = props.components.text;
@@ -9,5 +9,6 @@ export interface DateFieldProps {
9
9
  }
10
10
  export declare const DEFAULT_DATE_DISPLAY_FORMAT = "MMM DD, YYYY";
11
11
  export declare const DEFAULT_TIME_DISPLAY_FORMAT = "h:mm A";
12
+ export declare const getHourCycle: (timeFormat?: string) => 12 | 24;
12
13
  export declare const format: (val: string, _name: string, field: DateFieldProps) => string;
13
14
  export declare const parse: (val: string) => string;
@@ -65,6 +65,7 @@ export declare const Icons: {
65
65
  search: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
66
66
  settings: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
67
67
  highlight: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
68
+ textColor: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
68
69
  strikethrough: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
69
70
  subscript: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
70
71
  superscript: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
@@ -4,3 +4,4 @@ export declare const StrikethroughToolbarButton: () => React.JSX.Element;
4
4
  export declare const ItalicToolbarButton: () => React.JSX.Element;
5
5
  export declare const CodeToolbarButton: () => React.JSX.Element;
6
6
  export declare const HighlightToolbarButton: () => React.JSX.Element;
7
+ export declare const TextColorToolbarButton: () => React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Leaf mark for coloured text. The mark's value is the CSS colour itself
3
+ * (`{ text, textColor: '#CC4141' }`) — there is no separate boolean flag.
4
+ */
5
+ export declare const TextColorPlugin: import("@udecode/plate/react").PlatePlugin<import("@udecode/plate").PluginConfig<"textColor", {}, {}, {}, {}>>;
@@ -177,6 +177,7 @@ export declare const Components: () => {
177
177
  a: typeof LinkElement;
178
178
  code: typeof CodeLeaf;
179
179
  highlight: ({ leaf, ...props }: React.ComponentProps<typeof PlateLeaf>) => React.JSX.Element;
180
+ textColor: ({ leaf, ...props }: React.ComponentProps<typeof PlateLeaf>) => React.JSX.Element;
180
181
  underline: React.ForwardRefExoticComponent<Omit<Omit<import("@udecode/plate/react").PlateLeafProps<import("@udecode/plate").TText, import("@udecode/plate").AnyPluginConfig>, keyof {
181
182
  className?: string;
182
183
  style?: React.CSSProperties;
@@ -1,5 +1,5 @@
1
- import type { HeadingLevel, ToolbarOverrideType } from '@tinacms/schema-tools';
2
- export type { HeadingLevel, ToolbarOverrideType };
1
+ import type { HeadingLevel, RichTextColorOption, ToolbarOverrideType } from '@tinacms/schema-tools';
2
+ export type { HeadingLevel, RichTextColorOption, ToolbarOverrideType };
3
3
  export declare const STANDARD_ICON_WIDTH = 32;
4
4
  export declare const HEADING_ICON_WITH_TEXT = 127;
5
5
  export declare const HEADING_ICON_ONLY = 58;
@@ -11,4 +11,5 @@ export type ToolbarOverrides = {
11
11
  toolbar?: ToolbarOverrideType[];
12
12
  showFloatingToolbar?: boolean;
13
13
  headingLevels?: HeadingLevel[];
14
+ textColors?: RichTextColorOption[];
14
15
  };
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import { type ReactNode } from 'react';
3
+ import { type HeadingLevel } from '@tinacms/schema-tools';
3
4
  import type { Form } from '../../../../../forms';
4
5
  import type { MdxTemplate } from '../types';
5
- import { type HeadingLevel } from '@tinacms/schema-tools';
6
- import type { ToolbarOverrides, ToolbarOverrideType } from './toolbar-overrides';
6
+ import type { RichTextColorOption, ToolbarOverrideType, ToolbarOverrides } from './toolbar-overrides';
7
7
  interface ToolbarContextProps {
8
8
  tinaForm: Form;
9
9
  templates: MdxTemplate[];
@@ -16,8 +16,10 @@ interface ToolbarContextProps {
16
16
  * from "use the legacy default" without re-deriving the check.
17
17
  */
18
18
  headingLevelsConfigured: boolean;
19
+ /** Palette for the text colour dropdown (schema value or default). */
20
+ textColors: readonly RichTextColorOption[];
19
21
  }
20
- interface ToolbarProviderProps extends Omit<ToolbarContextProps, 'headingLevels' | 'headingLevelsConfigured'> {
22
+ interface ToolbarProviderProps extends Omit<ToolbarContextProps, 'headingLevels' | 'headingLevelsConfigured' | 'textColors'> {
21
23
  children: ReactNode;
22
24
  }
23
25
  export declare const ToolbarProvider: React.FC<ToolbarProviderProps>;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tinacms",
3
3
  "type": "module",
4
4
  "typings": "dist/index.d.ts",
5
- "version": "3.14.4",
5
+ "version": "3.15.0",
6
6
  "main": "dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "exports": {
@@ -104,19 +104,18 @@
104
104
  "react-dnd-html5-backend": "^16.0.1",
105
105
  "react-dropzone": "14.2.3",
106
106
  "react-final-form": "^6.5.9",
107
- "react-router-dom": "^6.30.3",
107
+ "react-router-dom": "^7.18.4",
108
108
  "react-use": "^17.6.0",
109
109
  "tailwind-merge": "^2.6.0",
110
110
  "webfontloader": "1.6.28",
111
111
  "yup": "^1.6.1",
112
112
  "zod": "^3.24.2",
113
- "@tinacms/mdx": "^2.3.0",
114
- "@tinacms/schema-tools": "^2.10.1",
115
- "@tinacms/search": "^1.2.26",
116
- "@tinacms/bridge": "^0.3.1"
113
+ "@tinacms/bridge": "^0.3.1",
114
+ "@tinacms/schema-tools": "^2.11.0",
115
+ "@tinacms/mdx": "^2.4.0",
116
+ "@tinacms/search": "^1.2.26"
117
117
  },
118
118
  "devDependencies": {
119
- "@graphql-tools/utils": "^10.8.1",
120
119
  "@testing-library/dom": "^10.4.0",
121
120
  "@testing-library/jest-dom": "^6.7.0",
122
121
  "@testing-library/react": "^16.2.0",
@@ -132,7 +131,7 @@
132
131
  "identity-obj-proxy": "^3.0.0",
133
132
  "jest-file-snapshot": "^0.7.0",
134
133
  "lowlight": "^3.3.0",
135
- "next": "15.5.21",
134
+ "next": "15.5.27",
136
135
  "react": "^18.3.1",
137
136
  "react-dom": "^18.3.1",
138
137
  "react-is": "^18.3.1",