mobile: search and sorting

This commit is contained in:
Ammar Ahmed
2025-05-23 14:58:21 +05:00
committed by Abdullah Atta
parent 8dac907ead
commit 5660178760
15 changed files with 301 additions and 167 deletions

View File

@@ -0,0 +1,181 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { HighlightedResult } from "@notesnook/core";
import { View } from "react-native";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { useThemeColors } from "@notesnook/theme";
import { DefaultAppStyles } from "../../../utils/styles";
import Heading from "../../ui/typography/heading";
import { AppFontSize } from "../../../utils/size";
import { Properties } from "../../properties";
import { db } from "../../../common/database";
import { eSendEvent } from "../../../services/event-manager";
import { eOnLoadNote } from "../../../utils/events";
import { IconButton } from "../../ui/icon-button";
import { fluidTabsRef } from "../../../utils/global-refs";
type SearchResultProps = {
item: HighlightedResult;
};
export const SearchResult = (props: SearchResultProps) => {
const [expanded, setExpanded] = React.useState(true);
const { colors } = useThemeColors();
const openNote = async (index?: number) => {
const note = await db.notes.note(props.item.id);
eSendEvent(eOnLoadNote, {
item: {
...note,
content: props.item.rawContent
? {
data: props.item.rawContent || "",
type: "tiptap"
}
: undefined
},
searchResultIndex: index
});
fluidTabsRef.current?.goToPage("editor");
};
return (
<Pressable
style={{
alignSelf: "flex-start",
alignItems: "flex-start",
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderRadius: 0
}}
onLongPress={async () => {
const note = await db.notes.note(props.item.id);
Properties.present(note);
}}
onPress={async () => openNote()}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%"
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL / 2,
flexShrink: 1
}}
>
{props.item.content?.length ? (
<IconButton
name={!expanded ? "chevron-right" : "chevron-down"}
onPress={() => setExpanded((prev) => !prev)}
size={AppFontSize.md + 2}
color={colors.secondary.icon}
style={{
width: 23,
height: 23
}}
/>
) : null}
<Heading
size={AppFontSize.sm}
style={{
flexShrink: 1
}}
>
{props.item.title.map((title) => (
<>
{title.prefix}
<Heading
size={AppFontSize.sm}
style={{
backgroundColor: colors.secondary.accent,
color: colors.secondary.accentForeground
}}
>
{title.match}
</Heading>
{title.suffix}
</>
))}
</Heading>
</View>
{props.item.content?.length ? (
<Paragraph size={AppFontSize.xxs} color={colors.secondary.paragraph}>
{props.item.content.length}
</Paragraph>
) : null}
</View>
{expanded &&
props.item.content.map((content, index) => (
<Pressable
key={props.item.id + index}
style={{
alignSelf: "flex-start",
alignItems: "flex-start",
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderColor: colors.primary.border,
borderWidth: 0,
borderRadius: 0,
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
width: "100%"
}}
onLongPress={async () => {
const note = await db.notes.note(props.item.id);
Properties.present(note);
}}
onPress={() => {
let activeIndex = 0;
for (let i = 0; i <= index; i++) {
activeIndex += props.item.content[i].length;
}
console.log(activeIndex);
openNote(activeIndex);
}}
>
<Paragraph>
{content.map((match) => (
<>
{match.prefix}
<Paragraph
size={AppFontSize.sm}
style={{
backgroundColor: colors.secondary.accent,
color: colors.secondary.accentForeground
}}
>
{match.match}
</Paragraph>
{match.suffix}
</>
))}
</Paragraph>
</Pressable>
))}
</Pressable>
);
};

View File

@@ -150,7 +150,7 @@ export default function List(props: ListProps) {
flex: 1
}}
>
{props.data?.placeholders?.length === 0 ? (
{props.data?.placeholders?.length === 0 || !props.data ? (
<>
{props.CustomLisHeader ? (
props.CustomLisHeader

View File

@@ -27,6 +27,7 @@ import {
GroupHeader,
GroupOptions,
GroupingKey,
HighlightedResult,
Item,
ItemType,
Note,
@@ -49,6 +50,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import ReminderItem from "../list-items/reminder";
import TagItem from "../list-items/tag";
import { SearchResult } from "../list-items/search-result";
type ListItemWrapperProps<TItem = Item> = {
group?: GroupingKey;
@@ -153,7 +155,9 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
},
items?.cacheItem(index) ? 100 : 0
);
} catch (e) {}
} catch (e) {
/** empty */
}
})();
}, [index, items, refreshItem]);
@@ -196,7 +200,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
notebooks={notebooks.current}
reminder={reminder.current}
attachmentsCount={attachmentsCount.current}
date={getDate(item, group)}
date={getDate(item as Note, group)}
isRenderedInActionSheet={isSheet}
index={index}
locked={locked.current}
@@ -227,7 +231,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
<NotebookWrapper
item={item as Notebook}
totalNotes={totalNotes.current}
date={getDate(item, group)}
date={getDate(item as Notebook, group)}
index={index}
/>
</>
@@ -285,12 +289,34 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
/>
</>
);
case "searchResult":
return (
<>
{groupHeader && previousIndex.current === index && !isSheet ? (
<SectionHeader
screen={props.renderedInRoute}
item={groupHeader}
index={index}
dataType={item.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: props.scrollRef,
data: items
});
}}
/>
) : null}
<SearchResult item={item as HighlightedResult} />
</>
);
default:
return null;
}
}
function getDate(item: Item, groupType?: GroupingKey): number {
function getDate(item: Notebook | Note, groupType?: GroupingKey): number {
return (
getSortValue(
groupType

View File

@@ -236,6 +236,11 @@ class Commands {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("scrollIntoViewById", id, tabId);
};
scrollToSearchResult = (index: number) => {
const tabId = useTabStore.getState().currentTab;
return this.sendCommand("scrollToSearchResult", index, tabId);
};
}
export default Commands;

View File

@@ -469,8 +469,7 @@ export const useEditor = (
}
) => {
currentNotes.current[note.id] = note;
const locked = note && (await db.vaults.itemExists(note));
if ((locked || note.content) && note.content?.data) {
if (note.content && note.content?.data) {
currentContents.current[note.id] = {
data: note.content?.data,
type: note.content?.type || "tiptap",
@@ -498,6 +497,7 @@ export const useEditor = (
session?: TabSessionItem;
newTab?: boolean;
refresh?: boolean;
searchResultIndex?: number;
}) => {
loadNoteMutex.runExclusive(async () => {
if (!event) return;
@@ -683,13 +683,17 @@ export const useEditor = (
{
data: currentContents.current[item.id]?.data || "",
scrollTop: tab?.session?.scrollTop,
selection: tab?.session?.selection
selection: tab?.session?.selection,
searchResultIndex: event.searchResultIndex
},
tabId,
10000
);
setTimeout(() => {
if (event.searchResultIndex !== undefined) {
commands.scrollToSearchResult(event.searchResultIndex);
}
if (blockIdRef.current) {
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;

View File

@@ -17,7 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Item, Note, VirtualizedGrouping } from "@notesnook/core";
import {
FilteredSelector,
Item,
Note,
VirtualizedGrouping
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import React, { useEffect, useRef, useState } from "react";
import { DatabaseLogger, db } from "../../common/database";
import List from "../../components/list";
@@ -28,20 +34,20 @@ import { NavigationProps } from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { eGroupOptionsUpdated, eOnRefreshSearch } from "../../utils/events";
import { SearchBar } from "./search-bar";
import { FilteredSelector } from "@notesnook/core";
import { strings } from "@notesnook/intl";
export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
const [results, setResults] = useState<VirtualizedGrouping<Item>>();
const [loading, setLoading] = useState(false);
const [searchStatus, setSearchStatus] = useState<string>();
const currentQuery = useRef<string>();
const timer = useRef<NodeJS.Timeout>();
const isFocused = useNavigationFocus(navigation, {
useNavigationFocus(navigation, {
onFocus: (prev) => {
useNavigationStore.getState().setFocusedRouteId(route.name);
return !prev?.current;
},
onBlur: () => false
onBlur: () => {
return false;
}
});
const onSearch = React.useCallback(
@@ -57,12 +63,14 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
setLoading(true);
let results: VirtualizedGrouping<Item> | undefined;
const groupOptions = db.settings.getGroupOptions("search");
switch (route.params.type) {
case "note":
results = await db.lookup
.notes(query, route.params.items as FilteredSelector<Note>)
.sorted(groupOptions);
results = await db.lookup.notes(
query,
groupOptions,
route.params.items as FilteredSelector<Note>
);
break;
case "notebook":
results = await db.lookup.notebooks(query).sorted(groupOptions);
@@ -143,7 +151,7 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
onSearch(query);
}, 300);
}, 500);
}}
loading={loading}
/>

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.1.0-beta.2",
"version": "3.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.1.0-beta.2",
"version": "3.1.1",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -1069,8 +1069,7 @@
"prismjs": "^1.29.0",
"qclone": "^1.2.0",
"rfdc": "^1.3.0",
"spark-md5": "^3.0.2",
"sqlite-better-trigram": "0.0.2"
"spark-md5": "^3.0.2"
},
"devDependencies": {
"@notesnook/crypto": "file:../crypto",
@@ -1096,6 +1095,8 @@
"nanoid": "5.0.7",
"otplib": "^12.0.1",
"refractor": "^4.8.1",
"sqlite-better-trigram": "^0.0.3",
"sqlite3-fts5-html": "^0.0.3",
"vitest": "2.1.8",
"vitest-fetch-mock": "^0.2.2",
"ws": "^8.13.0"
@@ -3727,7 +3728,7 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.1.1",
"@notesnook-importer/core": "^2.2.2",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",

View File

@@ -83,7 +83,7 @@
"otplib": "^12.0.1",
"refractor": "^4.8.1",
"sqlite-better-trigram": "^0.0.3",
"sqlite3-fts5-html": "^0.0.2",
"sqlite3-fts5-html": "^0.0.3",
"vitest": "2.1.8",
"vitest-fetch-mock": "^0.2.2",
"ws": "^8.13.0"

View File

@@ -93,7 +93,7 @@ export type Collections = {
export type CollectionType = keyof Collections;
export type ItemType = ValueOf<Collections>;
export type ItemType = ValueOf<Collections> | "searchResult";
export type Item = ValueOf<ItemMap>;
export type GroupableItem = ValueOf<
@@ -130,6 +130,7 @@ export type ItemMap = {
sessioncontent: SessionContentItem;
settingitem: SettingItem;
vault: Vault;
searchResult: HighlightedResult;
/**
* @deprecated only kept here for migration purposes
@@ -493,9 +494,8 @@ export type Match = {
match: string;
suffix: string;
};
export interface HighlightedResult {
type: "searchResult";
id: string;
export interface HighlightedResult extends BaseItem<"searchResult"> {
rawContent?: string;
title: Match[];
content: Match[][];

View File

@@ -63,7 +63,7 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.1.1",
"@notesnook-importer/core": "^2.2.2",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",

View File

@@ -201,6 +201,9 @@ const Tiptap = ({
copyToClipboard: (text) => {
globalThis.editorControllers[tab.id]?.copyToClipboard(text);
},
onFocus: () => {
getContentDiv().classList.remove("searching");
},
onSelectionUpdate: () => {
if (tabRef.current.session?.noteId) {
clearTimeout(noteStateUpdateTimer.current);
@@ -245,7 +248,11 @@ const Tiptap = ({
]);
const update = useCallback(
(scrollTop?: number, selection?: { to: number; from: number }) => {
(
scrollTop?: number,
selection?: { to: number; from: number },
searchResultIndex?: number
) => {
setTick((tick) => tick + 1);
globalThis.editorControllers[tabRef.current.id]?.setTitlePlaceholder(
strings.noteTitle()
@@ -253,7 +260,13 @@ const Tiptap = ({
setTimeout(() => {
editorControllers[tabRef.current.id]?.setLoading(false);
setTimeout(() => {
restoreNoteSelection(scrollTop, selection);
if (searchResultIndex !== undefined) {
globalThis.editorControllers[
tabRef.current.id
]?.scrollToSearchResult(searchResultIndex);
} else {
restoreNoteSelection(scrollTop, selection);
}
}, 300);
}, 1);
},
@@ -270,6 +283,9 @@ const Tiptap = ({
scrollTop: () => containerRef.current?.scrollTop || 0,
scrollTo: (top) => {
containerRef.current?.scrollTo({ top, behavior: "auto" });
},
getContentDiv() {
return getContentDiv();
}
});
const controllerRef = useRef(controller);
@@ -888,7 +904,7 @@ const TiptapProvider = (): JSX.Element => {
return contentRef.current;
}
const editorContainer = document.createElement("div");
editorContainer.classList.add("selectable", "main-editor");
editorContainer.classList.add("selectable", "main-editor", "searching");
editorContainer.style.flex = "1";
editorContainer.style.cursor = "text";
editorContainer.style.padding = "0px 12px";
@@ -897,6 +913,7 @@ const TiptapProvider = (): JSX.Element => {
editorContainer.style.fontFamily =
getFontById(settings.fontFamily)?.font || "sans-serif";
contentRef.current = editorContainer;
return editorContainer;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

View File

@@ -101,20 +101,25 @@ export type EditorController = {
passwordInputRef: MutableRefObject<HTMLInputElement | null>;
focusPassInput: () => void;
blurPassInput: () => void;
scrollToSearchResult: (index: number) => void;
getContentDiv: () => HTMLElement | null;
};
export function useEditorController({
update,
getTableOfContents,
scrollTo,
scrollTop
scrollTop,
getContentDiv
}: {
update: (
scrollTop?: number,
selection?: { to: number; from: number }
selection?: { to: number; from: number },
searchResultIndex?: number
) => void;
getTableOfContents: () => any[];
scrollTo: (top: number) => void;
scrollTop: () => number;
getContentDiv: () => HTMLElement | null;
}): EditorController {
const passwordInputRef = useRef<HTMLInputElement | null>(null);
const tab = useTabContext();
@@ -360,7 +365,7 @@ export function useEditorController({
htmlContentRef.current = value.data;
logger("info", "LOADING NOTE HTML");
if (!editor) break;
update(value.scrollTop, value.selection);
update(value.scrollTop, value.selection, value.searchResultIndex);
setTimeout(() => {
countWords(0);
}, 300);
@@ -449,9 +454,23 @@ export function useEditorController({
});
};
const scrollToSearchResult = useCallback((index: number) => {
const marks = document.getElementsByTagName("nn-search-result");
if (marks.length > index) {
const mark = marks[index];
if (mark) {
mark.scrollIntoView({
behavior: "instant",
block: "start"
});
}
}
}, []);
return {
getTableOfContents: getTableOfContents,
scrollIntoView: (id: string) => scrollIntoViewById(id),
scrollToSearchResult: scrollToSearchResult,
contentChange,
selectionChange,
titleChange,
@@ -471,6 +490,7 @@ export function useEditorController({
countWords,
copyToClipboard,
getAttachmentData,
getContentDiv,
updateTab: () => {
// When the tab is focused, we apply any updates to content that were recieved when
// the tab was not focused.

View File

@@ -196,5 +196,9 @@ globalThis.commands = {
scrollIntoViewById: (id: string, tabId: string) => {
return editorControllers[tabId]?.scrollIntoView(id) || [];
},
scrollToSearchResult: (index: number, tabId: string) => {
editorControllers[tabId]?.getContentDiv()?.classList.add("searching");
editorControllers[tabId]?.scrollToSearchResult(index);
}
};

View File

@@ -529,39 +529,6 @@
"polished": "^4.0.5"
}
},
"node_modules/@theme-ui/color-modes": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/color-modes/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/components": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
@@ -610,39 +577,6 @@
"@emotion/react": "^11.11.1"
}
},
"node_modules/@theme-ui/theme-provider": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/color-modes": "^0.16.2",
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/theme-provider/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@trpc/server": {
"version": "10.45.2",
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.45.2.tgz",

View File

@@ -560,39 +560,6 @@
"@styled-system/css": "^5.1.5"
}
},
"node_modules/@theme-ui/color-modes": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/color-modes/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/components": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
@@ -641,39 +608,6 @@
"@emotion/react": "^11.11.1"
}
},
"node_modules/@theme-ui/theme-provider": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/color-modes": "^0.16.2",
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/theme-provider/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",