diff --git a/.gitignore b/.gitignore index b21718b9..16a95637 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,8 @@ android/keystores/debug.keystore # Expo .expo/ +dist/ +web-build/ # Turborepo .turbo/ diff --git a/README.md b/README.md index 5fca71ab..f20e7cf4 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ # react-native-enriched-markdown -`react-native-enriched-markdown` is a powerful React Native library that renders Markdown content as native text and provides a rich text input with Markdown output. It supports iOS, Android, and macOS, and requires the New Architecture (Fabric). +`react-native-enriched-markdown` is a powerful React Native library that renders Markdown content as native text and provides a rich text input with Markdown output. It supports iOS, Android, macOS, and Web, and requires the New Architecture (Fabric) for native platforms. ### EnrichedMarkdownText - ⚡ Fully native text rendering (no WebView) +- 🌐 Web support via [react-native-web](https://necolas.github.io/react-native-web/) + [md4c](https://github.com/mity/md4c) compiled to WebAssembly - 🎯 High-performance Markdown parsing with [md4c](https://github.com/mity/md4c) - 📐 CommonMark standard compliant - 📊 GitHub Flavored Markdown (GFM) @@ -18,7 +19,7 @@ - 🔗 Interactive link handling - 🖼️ Native image interactions (iOS: Copy, Save to Camera Roll) - 🌐 Native platform features (Translate, Look Up, Search Web, Share) -- 🗣️ Accessibility support (VoiceOver on iOS, TalkBack on Android) +- 🗣️ Accessibility support (VoiceOver on iOS, TalkBack on Android, semantic HTML on web) - 🔄 Full RTL (right-to-left) support including text, lists, blockquotes, tables, and task lists ### EnrichedMarkdownInput @@ -58,6 +59,7 @@ We can help you build your next dream product – - [Other Events](docs/INPUT.md#other-events) - [Customizing Styles](docs/INPUT.md#customizing-enrichedmarkdowninput--styles) - [API Reference](#api-reference) +- [Web Support](docs/WEB.md) - [macOS Support](docs/MACOS.md) - [Contributing](#contributing) - [Future Plans](#future-plans) @@ -65,14 +67,32 @@ We can help you build your next dream product – ## Prerequisites -- Supports iOS, Android, and macOS platforms +**Native (iOS / Android / macOS)** - Requires [the React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page) - Supported React Native releases: `0.81`, `0.82`, `0.83`, and `0.84` - macOS support via [react-native-macos](https://github.com/microsoft/react-native-macos) `0.81+` +**Web** +- Requires [`react-native-web`](https://necolas.github.io/react-native-web/) and Metro (or another bundler with `.web.tsx` platform resolution) +- No New Architecture requirement — the web renderer runs entirely in JavaScript via WebAssembly +- Only `EnrichedMarkdownText` is supported on web (`EnrichedMarkdownInput` is native-only) +- LaTeX math requires the optional [`katex`](https://katex.org/) peer dependency + ## Installation -### Bare React Native app +### Web + +No steps beyond having `react-native-web` configured. For LaTeX math, install the optional peer dependency: + +```sh +npm install katex +# or +yarn add katex +``` + +See [Web Support](docs/WEB.md) for full setup details, supported features, and prop behaviour. + +### Bare React Native app (iOS / Android) #### 1. Install the library @@ -142,6 +162,10 @@ See [EnrichedMarkdownInput](docs/INPUT.md) for detailed documentation on usage e See the [API Reference](docs/API_REFERENCE.md) for a detailed overview of all the props, methods, and events available. +## Web Support + +See [Web Support](docs/WEB.md) for details on supported features, web-specific prop behaviour, and known limitations. + ## macOS Support `react-native-enriched-markdown` supports macOS via [react-native-macos](https://github.com/microsoft/react-native-macos). See [macOS Support](docs/MACOS.md) for details on macOS-specific features, known limitations, and the example app. @@ -151,8 +175,9 @@ See the [API Reference](docs/API_REFERENCE.md) for a detailed overview of all th We're actively working on expanding the capabilities of `react-native-enriched-markdown`. Here's what's on the roadmap: - `EnrichedMarkdownInput`: headings, lists, blockquotes, code blocks, mentions, inline images -- Web implementation via `react-native-web` +- `EnrichedMarkdownInput` web support - macOS: block math rendering, VoiceOver accessibility, tail fade-in animation +- Web: streaming animation, configurable link `target`, copy options (Copy as Markdown, multi-format clipboard) ## Contributing diff --git a/apps/web-example/app.json b/apps/web-example/app.json new file mode 100644 index 00000000..5dc11b0c --- /dev/null +++ b/apps/web-example/app.json @@ -0,0 +1,11 @@ +{ + "expo": { + "name": "EnrichedMarkdownWebExample", + "slug": "enriched-markdown-web-example", + "version": "1.0.0", + "platforms": ["web"], + "web": { + "output": "single" + } + } +} diff --git a/apps/web-example/babel.config.js b/apps/web-example/babel.config.js new file mode 100644 index 00000000..9d89e131 --- /dev/null +++ b/apps/web-example/babel.config.js @@ -0,0 +1,6 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + }; +}; diff --git a/apps/web-example/index.js b/apps/web-example/index.js new file mode 100644 index 00000000..c225fa60 --- /dev/null +++ b/apps/web-example/index.js @@ -0,0 +1,6 @@ +import '@expo/metro-runtime'; + +import { registerRootComponent } from 'expo'; +import App from './src/App'; + +registerRootComponent(App); diff --git a/apps/web-example/metro.config.js b/apps/web-example/metro.config.js new file mode 100644 index 00000000..56b85fbc --- /dev/null +++ b/apps/web-example/metro.config.js @@ -0,0 +1,17 @@ +const { getDefaultConfig } = require('expo/metro-config'); + +const path = require('path'); + +const projectRoot = __dirname; +const monorepoRoot = path.resolve(projectRoot, '../..'); + +const config = getDefaultConfig(projectRoot); + +config.watchFolders = [monorepoRoot]; + +config.resolver.nodeModulesPaths = [ + path.resolve(projectRoot, 'node_modules'), + path.resolve(monorepoRoot, 'node_modules'), +]; + +module.exports = config; diff --git a/apps/web-example/package.json b/apps/web-example/package.json new file mode 100644 index 00000000..38533540 --- /dev/null +++ b/apps/web-example/package.json @@ -0,0 +1,26 @@ +{ + "name": "react-native-enriched-markdown-web-example", + "version": "0.0.1", + "private": true, + "main": "index.js", + "scripts": { + "start": "expo start", + "web": "expo start --web", + "build:web": "expo export --platform web" + }, + "dependencies": { + "expo": "~55.0.0", + "katex": "^0.16.44", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.84.1", + "react-native-enriched-markdown": "*", + "react-native-web": "~0.19.13" + }, + "devDependencies": { + "@expo/metro-runtime": "~5.0.0", + "@types/react": "^19.2.0", + "babel-preset-expo": "~55.0.13", + "typescript": "^5.9.2" + } +} diff --git a/apps/web-example/src/App.tsx b/apps/web-example/src/App.tsx new file mode 100644 index 00000000..7547f280 --- /dev/null +++ b/apps/web-example/src/App.tsx @@ -0,0 +1,270 @@ +import { useCallback, useState } from 'react'; +import { Linking, ScrollView, StyleSheet, View, Text } from 'react-native'; +import { EnrichedMarkdownText } from 'react-native-enriched-markdown'; +import type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, +} from 'react-native-enriched-markdown'; +import { sampleMarkdown } from './sampleMarkdown'; + +const latexMarkdown = ` +## Inline Math + +The **quadratic formula** solves $ax^2 + bx + c = 0$, giving $x = \\dfrac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$. + +Euler's identity $e^{i\\pi} + 1 = 0$ is often called the most beautiful equation in mathematics. + +Einstein's mass-energy equivalence: $E = mc^2$. + +## Display Math + +The Gaussian integral: + +$$\\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}$$ + +Newton's second law in differential form: + +$$F = m\\frac{d^2x}{dt^2}$$ + +The Schrödinger equation: + +$$i\\hbar\\frac{\\partial}{\\partial t}\\Psi(\\mathbf{r},t) = \\left[-\\frac{\\hbar^2}{2m}\\nabla^2 + V(\\mathbf{r},t)\\right]\\Psi(\\mathbf{r},t)$$ + +Bayes' theorem: + +$$P(A \\mid B) = \\frac{P(B \\mid A) \\, P(A)}{P(B)}$$ +`.trim(); + +const rtlMarkdown = ` +# مرحباً بالعالم + +هذا مثال على **النص العربي** في اتجاه RTL مع دعم كامل لتنسيق Markdown. يمكنك أيضاً استخدام *تنسيق مائل* و~~نص مشطوب~~. + +--- + +## الاقتباسات + +> اقتباس: يجب أن يظهر الحد العمودي على **الجانب الأيمن** في وضع RTL. +> +> هذا السلوك مدعوم تلقائياً عبر خاصية CSS المنطقية \`border-inline-start\`. + +## قوائم المهام + +- [x] تصميم واجهة المستخدم +- [x] تطوير الواجهة الخلفية +- [ ] كتابة الاختبارات +- [ ] نشر التطبيق + +## قائمة مرتبة + +1. الخطوة الأولى: تثبيت المكتبة +2. الخطوة الثانية: إضافة المكوّن +3. الخطوة الثالثة: تخصيص الأنماط + +## قائمة متداخلة + +- العنصر الأول + - عنصر متداخل + - عنصر متداخل آخر +- العنصر الثاني + - قائمة متداخلة ثانية + +## الروابط والصور + +تفضل بزيارة [موقع المكتبة](https://github.com) لمزيد من المعلومات. + +## الجداول + +| الاسم | الدور | الحالة | +| -------- | ----------- | ------- | +| أحمد | مطوّر | نشط | +| فاطمة | مصمّمة | نشط | +| محمد | مدير مشروع | غائب | + +## الكود + +استخدم الدالة \`console.log()\` لطباعة الرسائل. + +\`\`\`javascript +function greet(name) { + return \`مرحباً، \${name}!\`; +} + +console.log(greet("العالم")); +\`\`\` +`.trim(); + +interface EventLog { + kind: 'link' | 'linkLong' | 'task'; + label: string; + detail: string; +} + +const KIND_COLOR: Record = { + link: '#2563EB', + linkLong: '#7C3AED', + task: '#059669', +}; + +export default function App() { + const [lastEvent, setLastEvent] = useState(null); + + const onLinkPress = useCallback(({ url }: LinkPressEvent) => { + setLastEvent({ kind: 'link', label: 'onLinkPress', detail: url }); + Linking.openURL(url); + }, []); + + const onLinkLongPress = useCallback(({ url }: LinkLongPressEvent) => { + setLastEvent({ kind: 'linkLong', label: 'onLinkLongPress', detail: url }); + }, []); + + const onTaskListItemPress = useCallback( + ({ index, checked, text }: TaskListItemPressEvent) => { + setLastEvent({ + kind: 'task', + label: 'onTaskListItemPress', + detail: `index=${index} checked=${checked} "${text.slice(0, 40)}${text.length > 40 ? '…' : ''}"`, + }); + }, + [] + ); + + return ( + + + + react-native-enriched-markdown — web example + + + + + LaTeX + + + + + LTR + + + + + RTL + + + + {lastEvent && ( + + + {lastEvent.label} + + + {lastEvent.detail} + + setLastEvent(null)}> + ✕ + + + )} + + ); +} + +function SectionLabel({ children }: { children: string }) { + return ( + + {children} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#ffffff', + }, + header: { + paddingHorizontal: 16, + paddingVertical: 12, + borderBottomWidth: 1, + borderBottomColor: '#E5E7EB', + backgroundColor: '#F9FAFB', + }, + headerText: { + fontSize: 14, + fontWeight: '600', + color: '#374151', + fontFamily: 'monospace', + }, + scroll: { + flex: 1, + }, + scrollContent: { + paddingHorizontal: 24, + paddingVertical: 16, + }, + divider: { + height: 1, + backgroundColor: '#E5E7EB', + marginVertical: 24, + }, + sectionLabel: { + marginBottom: 12, + }, + sectionLabelText: { + fontSize: 11, + fontWeight: '600', + color: '#9CA3AF', + fontFamily: 'monospace', + letterSpacing: 1, + }, + eventBar: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + paddingHorizontal: 16, + paddingVertical: 10, + borderTopWidth: 1, + borderTopColor: '#E5E7EB', + backgroundColor: '#F9FAFB', + }, + kindBadge: { + borderRadius: 4, + paddingHorizontal: 8, + paddingVertical: 2, + }, + kindText: { + fontSize: 11, + fontWeight: '600', + color: '#ffffff', + fontFamily: 'monospace', + }, + detailText: { + flex: 1, + fontSize: 12, + color: '#374151', + fontFamily: 'monospace', + }, + dismissText: { + fontSize: 14, + color: '#9CA3AF', + paddingLeft: 4, + }, +}); diff --git a/apps/web-example/src/sampleMarkdown.ts b/apps/web-example/src/sampleMarkdown.ts new file mode 100644 index 00000000..96e12559 --- /dev/null +++ b/apps/web-example/src/sampleMarkdown.ts @@ -0,0 +1 @@ +export { sampleMarkdown } from '../../example/src/sampleMarkdown'; diff --git a/apps/web-example/tsconfig.json b/apps/web-example/tsconfig.json new file mode 100644 index 00000000..06f40c35 --- /dev/null +++ b/apps/web-example/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "lib": ["ESNext", "DOM"], + "moduleSuffixes": [".web", ""], + "paths": { + "react-native-enriched-markdown": ["../../src/index"] + } + } +} diff --git a/cpp/wasm/ASTSerializer.cpp b/cpp/wasm/ASTSerializer.cpp new file mode 100644 index 00000000..fb1b6320 --- /dev/null +++ b/cpp/wasm/ASTSerializer.cpp @@ -0,0 +1,150 @@ +#include "ASTSerializer.hpp" +#include +#include + +namespace Markdown { + +static const char *nodeTypeToString(NodeType type) { + switch (type) { + case NodeType::Document: + return "Document"; + case NodeType::Paragraph: + return "Paragraph"; + case NodeType::Text: + return "Text"; + case NodeType::Link: + return "Link"; + case NodeType::Heading: + return "Heading"; + case NodeType::LineBreak: + return "LineBreak"; + case NodeType::Strong: + return "Strong"; + case NodeType::Emphasis: + return "Emphasis"; + case NodeType::Strikethrough: + return "Strikethrough"; + case NodeType::Underline: + return "Underline"; + case NodeType::Code: + return "Code"; + case NodeType::Image: + return "Image"; + case NodeType::Blockquote: + return "Blockquote"; + case NodeType::UnorderedList: + return "UnorderedList"; + case NodeType::OrderedList: + return "OrderedList"; + case NodeType::ListItem: + return "ListItem"; + case NodeType::CodeBlock: + return "CodeBlock"; + case NodeType::ThematicBreak: + return "ThematicBreak"; + case NodeType::Table: + return "Table"; + case NodeType::TableHead: + return "TableHead"; + case NodeType::TableBody: + return "TableBody"; + case NodeType::TableRow: + return "TableRow"; + case NodeType::TableHeaderCell: + return "TableHeaderCell"; + case NodeType::TableCell: + return "TableCell"; + case NodeType::LatexMathInline: + return "LatexMathInline"; + case NodeType::LatexMathDisplay: + return "LatexMathDisplay"; + default: + assert(false && "unhandled NodeType in nodeTypeToString"); + return ""; + } +} + +void ASTSerializer::appendEscaped(const std::string &str, std::string &out) { + out += '"'; + for (unsigned char c : str) { + switch (c) { + case '"': + out += "\\\""; + break; + case '\\': + out += "\\\\"; + break; + case '\n': + out += "\\n"; + break; + case '\r': + out += "\\r"; + break; + case '\t': + out += "\\t"; + break; + case '\b': + out += "\\b"; + break; + case '\f': + out += "\\f"; + break; + default: + if (c < 0x20) { + char buf[7]; + std::snprintf(buf, sizeof(buf), "\\u%04x", c); + out += buf; + } else { + out += static_cast(c); + } + break; + } + } + out += '"'; +} + +void ASTSerializer::serializeNode(const MarkdownASTNode &node, std::string &out) { + out += "{\"type\":\""; + out += nodeTypeToString(node.type); + out += '"'; + + if (!node.content.empty()) { + out += ",\"content\":"; + appendEscaped(node.content, out); + } + + if (!node.attributes.empty()) { + out += ",\"attributes\":{"; + bool first = true; + for (const auto &kv : node.attributes) { + if (!first) + out += ','; + first = false; + appendEscaped(kv.first, out); + out += ':'; + appendEscaped(kv.second, out); + } + out += '}'; + } + + if (!node.children.empty()) { + out += ",\"children\":["; + for (size_t i = 0; i < node.children.size(); ++i) { + if (i > 0) + out += ','; + serializeNode(*node.children[i], out); + } + out += ']'; + } + + out += '}'; +} + +std::string ASTSerializer::serialize(const MarkdownASTNode &node) { + std::string out; + out.reserve(1024); + serializeNode(node, out); + return out; +} + +} // namespace Markdown diff --git a/cpp/wasm/ASTSerializer.hpp b/cpp/wasm/ASTSerializer.hpp new file mode 100644 index 00000000..99c4dbae --- /dev/null +++ b/cpp/wasm/ASTSerializer.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "../parser/MarkdownASTNode.hpp" +#include + +namespace Markdown { + +class ASTSerializer { +public: + static std::string serialize(const MarkdownASTNode &node); + +private: + static void serializeNode(const MarkdownASTNode &node, std::string &out); + static void appendEscaped(const std::string &str, std::string &out); +}; + +} // namespace Markdown diff --git a/cpp/wasm/build.sh b/cpp/wasm/build.sh new file mode 100755 index 00000000..e60ff0d5 --- /dev/null +++ b/cpp/wasm/build.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Compile md4c + the WASM wrapper to a single self-contained JS file. +# +# Prerequisites: +# brew install emscripten # macOS +# # or follow https://emscripten.org/docs/getting_started/downloads.html +# +# Usage: +# bash cpp/wasm/build.sh +# +# Output: +# src/web/wasm/md4c.js — Emscripten glue with WASM binary inlined as base64 +# (SINGLE_FILE=1 means no separate .wasm file is needed) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +OUT_DIR="$REPO_ROOT/src/web/wasm" + +mkdir -p "$OUT_DIR" + +echo "Building md4c WASM…" + +# Compile the C file separately (no -std=c++17) +emcc \ + -I "$REPO_ROOT/cpp" \ + -O2 \ + -c "$REPO_ROOT/cpp/md4c/md4c.c" \ + -o "$OUT_DIR/md4c.o" + +# Compile C++ sources and link everything together +emcc \ + "$SCRIPT_DIR/md4c_wasm.cpp" \ + "$SCRIPT_DIR/ASTSerializer.cpp" \ + "$REPO_ROOT/cpp/parser/MD4CParser.cpp" \ + "$OUT_DIR/md4c.o" \ + -I "$REPO_ROOT/cpp" \ + -I "$SCRIPT_DIR" \ + -O2 \ + -std=c++17 \ + -Wswitch \ + -s WASM=1 \ + -s SINGLE_FILE=1 \ + -s EXPORTED_FUNCTIONS='["_parseMarkdown"]' \ + -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap","UTF8ToString"]' \ + -s ENVIRONMENT='web' \ + -s MODULARIZE=1 \ + -s EXPORT_NAME='createMd4cModule' \ + -o "$OUT_DIR/md4c.js" + +rm "$OUT_DIR/md4c.o" + +echo "Done → $OUT_DIR/md4c.js" diff --git a/cpp/wasm/md4c_wasm.cpp b/cpp/wasm/md4c_wasm.cpp new file mode 100644 index 00000000..7c40b403 --- /dev/null +++ b/cpp/wasm/md4c_wasm.cpp @@ -0,0 +1,36 @@ +#include "../parser/MD4CParser.hpp" +#include "ASTSerializer.hpp" +#include + +// Static buffer for the JSON result. +// Safe for single-threaded WASM execution — the caller must consume (copy) +// the returned string before calling parseMarkdown again. +static std::string g_resultBuffer; + +extern "C" { + +/** + * Parse a markdown string and return its AST as a JSON string. + * + * @param markdown Null-terminated UTF-8 markdown input. + * @param underline 1 → enable __ underline extension; 0 → __ means emphasis. + * @param latexMath 1 → enable $…$ / $$…$$ LaTeX math spans; 0 → disable. + * @return Null-terminated UTF-8 JSON string, valid until the next call. + */ +const char *parseMarkdown(const char *markdown, int underline, int latexMath) { + if (!markdown) { + g_resultBuffer = "{\"type\":\"Document\"}"; + return g_resultBuffer.c_str(); + } + + Markdown::Md4cFlags flags; + flags.underline = (underline != 0); + flags.latexMath = (latexMath != 0); + + Markdown::MD4CParser parser; + auto root = parser.parse(std::string(markdown), flags); + g_resultBuffer = Markdown::ASTSerializer::serialize(*root); + return g_resultBuffer.c_str(); +} + +} // extern "C" diff --git a/docs/LATEX_MATH.md b/docs/LATEX_MATH.md index 09ce5812..20c7c145 100644 --- a/docs/LATEX_MATH.md +++ b/docs/LATEX_MATH.md @@ -37,6 +37,35 @@ Block math equations are rendered as standalone display elements with spacing an > [!IMPORTANT] > LaTeX commands use backslashes (e.g. `\frac`, `\alpha`). In regular JS strings and template literals, backslashes are escape characters. Use `String.raw` or double backslashes (`\\frac`) to preserve them. Block math (`$$...$$`) must be on its own line to render as a display element. +## Web + +On web the library renders LaTeX via [KaTeX](https://katex.org/) in **MathML output mode**. Browsers render MathML natively — no CSS or font files are required. + +### Installation + +Install the optional peer dependency: + +```sh +npm install katex +# or +yarn add katex +``` + +KaTeX is loaded lazily the first time a LaTeX node is encountered, so it has no impact on pages that do not render math. No stylesheet or `` tag is needed. + +> [!NOTE] +> MathML is supported natively in Chrome 109+, Firefox, and Safari. Older browsers will display the raw LaTeX source as a text fallback. + +### Disabling on web + +Pass `latexMath: false` in `md4cFlags` to skip parsing and treat `$` as plain text: + +```tsx + +``` + +This also prevents KaTeX from being loaded at runtime. + ## Disabling LaTeX Math (reducing bundle size) LaTeX math rendering relies on native third-party libraries — **iosMath** (~2.5 MB) on iOS and **AndroidMath** on Android. These are included by default but can be excluded to reduce your app's binary size. diff --git a/docs/WEB.md b/docs/WEB.md new file mode 100644 index 00000000..7a5829fb --- /dev/null +++ b/docs/WEB.md @@ -0,0 +1,46 @@ +# Web Support + +`EnrichedMarkdownText` runs on web using [`react-native-web`](https://necolas.github.io/react-native-web/) for the React Native primitives and [md4c](https://github.com/mity/md4c) compiled to WebAssembly for parsing. The WASM binary is bundled in the npm package — no build step is required by consumers. + +The web renderer uses semantic HTML elements (`

`, `

`–`

`, `
`, `
    `, `
      `, ``, etc.) for improved accessibility. + +## Supported features + +All core `EnrichedMarkdownText` features are supported on web, including: + +- Full GFM: tables (with horizontal scroll), task lists (with checkbox interaction), strikethrough, links, images (block and inline), code blocks, LaTeX math (block and inline) +- All `markdownStyle` customisation options +- `onLinkPress`, `onLinkLongPress` (mapped to `contextmenu` event), `onTaskListItemPress` callbacks +- `allowTrailingMargin`, `containerStyle`, `selectable`, `md4cFlags` (`underline`, `latexMath`) +- RTL support via the `dir` prop (CSS logical properties automatically flip blockquote borders, list indentation, etc.) + +### Accessibility + +- Semantic HTML elements for all markdown structures +- Images: `alt` text falls back to `title`, then URL filename, then `"Image"` +- Code blocks: `aria-label` with language when available (e.g. `"Code block: python"`) +- Math (KaTeX fallback): `role="math"` and `aria-label` with the expression content +- Task list checkboxes: `aria-label` with the task text (e.g. `"Task: Buy groceries"`) + +### Web-only props + +| Prop | Description | +|---|---| +| `dir` | Sets the text direction on the root container (`'ltr'`, `'rtl'`, or `'auto'`). CSS logical properties in the renderers automatically flip layout for RTL. | + +The web implementation also exports `WebMarkdownTextProps` which extends `EnrichedMarkdownTextProps` with the web-only props above. + +## Ignored props (native-only) + +| Prop | Reason | +|---|---| +| `flavor` | The web renderer always uses full GFM capabilities. On native, `flavor` controls whether a single `TextView` (CommonMark) or container-based renderer (GitHub) is used; the DOM has no such constraint. | +| `enableLinkPreview` | iOS-only feature (native link preview on long press). | +| `allowFontScaling` / `maxFontSizeMultiplier` | React Native text scaling props. Browsers handle font scaling natively via OS accessibility settings. | +| `streamingAnimation` | Native-only tail fade-in animation. Not yet implemented on web. | +| `contextMenuItems` | Not supported — browsers don't allow extending the native context menu. | + +## Not supported on web + +- `EnrichedMarkdownInput` — native-only +- Configurable link `target` — all links open in a new tab (`target="_blank"`). Use `onLinkPress` for custom navigation. diff --git a/eslint.config.mjs b/eslint.config.mjs index 16b00bbc..23c7d664 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,11 @@ export default defineConfig([ }, }, { - ignores: ['node_modules/', 'lib/'], + ignores: [ + 'node_modules/', + 'lib/', + // Emscripten-generated WASM glue — not human-authored + 'src/web/wasm/md4c.js', + ], }, ]); diff --git a/package.json b/package.json index 1df5ffb2..9f3874ea 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,10 @@ "name": "react-native-enriched-markdown", "version": "0.4.1", "description": "Markdown Text component for React Native", - "main": "./lib/module/index.js", + "main": "./lib/module/index", + "module": "./lib/module/index", + "source": "./src/index.tsx", "types": "./lib/typescript/src/index.d.ts", - "exports": { - ".": { - "source": "./src/index.tsx", - "types": "./lib/typescript/src/index.d.ts", - "default": "./lib/module/index.js" - }, - "./package.json": "./package.json", - "./app.plugin.js": "./app.plugin.js" - }, "files": [ "src", "lib", @@ -36,11 +29,13 @@ "scripts": { "example": "yarn workspace react-native-enriched-markdown-example", "macos-example": "yarn workspace react-native-enriched-markdown-macos-example", + "web-example": "yarn workspace react-native-enriched-markdown-web-example", + "build:wasm": "bash cpp/wasm/build.sh", "android:build:release": "cd apps/example && npx react-native build-android --mode=release", "android:test:release": "cd apps/example && yarn android --mode release", "test": "jest", "typecheck": "tsc", - "lint": "eslint \"**/*.{js,ts,tsx}\"", + "lint": "eslint \"**/*.{js,ts,tsx}\" --no-warn-ignored", "lint-clang:ios": "find ios/ \\( -iname \"*.h\" -o -iname \"*.m\" -o -iname \"*.mm\" \\) | grep -v -e Pods -e build | xargs npx clang-format -i -n --Werror", "lint-clang:ios:fix": "find ios/ \\( -iname \"*.h\" -o -iname \"*.m\" -o -iname \"*.mm\" \\) | grep -v -e Pods -e build | xargs npx clang-format -i", "lint-clang:android": "find android/ \\( -iname \"*.h\" -o -iname \"*.cpp\" \\) | grep -v -e build | xargs npx clang-format -i -n --Werror", @@ -102,17 +97,22 @@ }, "peerDependencies": { "@expo/config-plugins": ">=50.0.0", + "katex": ">=0.16.0", "react": "*", "react-native": "*" }, "peerDependenciesMeta": { "@expo/config-plugins": { "optional": true + }, + "katex": { + "optional": true } }, "workspaces": [ "apps/example", - "apps/macos-example" + "apps/macos-example", + "apps/web-example" ], "packageManager": "yarn@4.11.0", "jest": { diff --git a/src/EnrichedMarkdownText.tsx b/src/EnrichedMarkdownText.tsx deleted file mode 100644 index 482b3fb9..00000000 --- a/src/EnrichedMarkdownText.tsx +++ /dev/null @@ -1,454 +0,0 @@ -import { useMemo, useCallback, useRef, useEffect } from 'react'; -import EnrichedMarkdownTextNativeComponent, { - type NativeProps, - type LinkPressEvent, - type LinkLongPressEvent, - type TaskListItemPressEvent, - type OnContextMenuItemPressEvent, -} from './EnrichedMarkdownTextNativeComponent'; -import type { MarkdownStyleInternal } from './EnrichedMarkdownTextNativeComponent'; -import EnrichedMarkdownNativeComponent from './EnrichedMarkdownNativeComponent'; -import { normalizeMarkdownStyle } from './normalizeMarkdownStyle'; -import type { ViewStyle, TextStyle, NativeSyntheticEvent } from 'react-native'; - -type TextAlign = 'auto' | 'left' | 'right' | 'center' | 'justify'; - -interface BaseBlockStyle { - fontSize?: number; - fontFamily?: string; - fontWeight?: string; - color?: string; - marginTop?: number; - marginBottom?: number; - lineHeight?: number; -} - -interface ParagraphStyle extends BaseBlockStyle { - textAlign?: TextAlign; -} - -interface HeadingStyle extends BaseBlockStyle { - textAlign?: TextAlign; -} - -interface BlockquoteStyle extends BaseBlockStyle { - borderColor?: string; - borderWidth?: number; - gapWidth?: number; - backgroundColor?: string; -} - -interface ListStyle extends BaseBlockStyle { - bulletColor?: string; - bulletSize?: number; - markerColor?: string; - markerFontWeight?: string; - gapWidth?: number; - marginLeft?: number; -} - -interface CodeBlockStyle extends BaseBlockStyle { - backgroundColor?: string; - borderColor?: string; - borderRadius?: number; - borderWidth?: number; - padding?: number; -} - -interface LinkStyle { - fontFamily?: string; - color?: string; - underline?: boolean; -} - -interface StrongStyle { - fontFamily?: string; - /** - * Controls whether bold is applied on top of the custom fontFamily. - * Only relevant when fontFamily is set. Defaults to 'bold'. - * Set to 'normal' to use the font face as-is without adding bold. - */ - fontWeight?: 'bold' | 'normal'; - color?: string; -} - -interface EmphasisStyle { - fontFamily?: string; - /** - * Controls whether italic is applied on top of the custom fontFamily. - * Only relevant when fontFamily is set. Defaults to 'italic'. - * Set to 'normal' to use the font face as-is without adding italic. - */ - fontStyle?: 'italic' | 'normal'; - color?: string; -} - -interface StrikethroughStyle { - /** - * Color of the strikethrough line. - * @platform iOS - */ - color?: string; -} - -interface UnderlineStyle { - /** - * Color of the underline. - * @platform iOS - */ - color?: string; -} - -interface CodeStyle { - fontFamily?: string; - fontSize?: number; - color?: string; - backgroundColor?: string; - borderColor?: string; -} - -interface ImageStyle { - height?: number; - borderRadius?: number; - marginTop?: number; - marginBottom?: number; -} - -interface InlineImageStyle { - size?: number; -} - -interface ThematicBreakStyle { - color?: string; - height?: number; - marginTop?: number; - marginBottom?: number; -} - -interface TableStyle extends BaseBlockStyle { - headerFontFamily?: string; - headerBackgroundColor?: string; - headerTextColor?: string; - rowEvenBackgroundColor?: string; - rowOddBackgroundColor?: string; - borderColor?: string; - borderWidth?: number; - borderRadius?: number; - cellPaddingHorizontal?: number; - cellPaddingVertical?: number; -} - -interface TaskListStyle { - checkedColor?: string; - borderColor?: string; - checkboxSize?: number; - checkboxBorderRadius?: number; - checkmarkColor?: string; - checkedTextColor?: string; - checkedStrikethrough?: boolean; -} - -interface MathStyle { - fontSize?: number; - color?: string; - backgroundColor?: string; - padding?: number; - marginTop?: number; - marginBottom?: number; - textAlign?: 'left' | 'center' | 'right'; -} - -interface InlineMathStyle { - color?: string; -} - -export interface MarkdownStyle { - paragraph?: ParagraphStyle; - h1?: HeadingStyle; - h2?: HeadingStyle; - h3?: HeadingStyle; - h4?: HeadingStyle; - h5?: HeadingStyle; - h6?: HeadingStyle; - blockquote?: BlockquoteStyle; - list?: ListStyle; - codeBlock?: CodeBlockStyle; - link?: LinkStyle; - strong?: StrongStyle; - em?: EmphasisStyle; - strikethrough?: StrikethroughStyle; - underline?: UnderlineStyle; - code?: CodeStyle; - image?: ImageStyle; - inlineImage?: InlineImageStyle; - thematicBreak?: ThematicBreakStyle; - table?: TableStyle; - taskList?: TaskListStyle; - math?: MathStyle; - inlineMath?: InlineMathStyle; -} - -/** - * MD4C parser flags configuration. - * Controls how the markdown parser interprets certain syntax. - */ -export interface Md4cFlags { - /** - * Enable underline syntax support (__text__). - * When enabled, underscores are treated as underline markers. - * When disabled, underscores are treated as emphasis markers (same as asterisks). - * @default false - */ - underline?: boolean; - /** - * Enable LaTeX math span parsing ($..$ and $$..$$). - * When enabled, the parser recognizes LaTeX math delimiters. - * When disabled, dollar signs are treated as plain text. - * Requires the optional iosMath (iOS) / AndroidMath (Android) native dependencies. - * @default true - */ - latexMath?: boolean; -} - -export interface ContextMenuItem { - text: string; - onPress: (event: { - text: string; - selection: { start: number; end: number }; - }) => void; - icon?: string; - visible?: boolean; -} - -export interface EnrichedMarkdownTextProps - extends Omit< - NativeProps, - | 'markdownStyle' - | 'style' - | 'onLinkPress' - | 'onLinkLongPress' - | 'onTaskListItemPress' - | 'md4cFlags' - | 'enableLinkPreview' - | 'contextMenuItems' - | 'onContextMenuItemPress' - > { - /** - * Style configuration for markdown elements - */ - markdownStyle?: MarkdownStyle; - /** - * Style for the container view. - */ - containerStyle?: ViewStyle | TextStyle; - /** - * Callback fired when a link is pressed. - * Receives the link URL directly. - */ - onLinkPress?: (event: LinkPressEvent) => void; - /** - * Callback fired when a link is long pressed. - * Receives the link URL directly. - * - iOS: When provided, automatically disables the system link preview (unless `enableLinkPreview` is explicitly set to `true`). - * - Android: Handles long press gestures on links. - */ - onLinkLongPress?: (event: LinkLongPressEvent) => void; - /** - * Callback fired when a task list checkbox is tapped. - * - * The checkbox is toggled on the native side automatically. - * Receives the 0-based task index, the new checked state (after toggling), - * and the item's plain text. - * - * Only fires when `flavor="github"` (GFM task lists require GitHub flavor). - */ - onTaskListItemPress?: (event: TaskListItemPressEvent) => void; - /** - * Controls whether the system link preview is shown on long press (iOS only). - * - * When `true`, long-pressing a link shows the native iOS link preview. - * When `false`, the system preview is suppressed. - * - * Defaults to `true`, but automatically becomes `false` when `onLinkLongPress` is provided. - * Set explicitly to override the automatic behavior. - * - * Android: No-op. - * - * @default true - * @platform ios - */ - enableLinkPreview?: boolean; - /** - * MD4C parser flags configuration. - * Controls how the markdown parser interprets certain syntax. - */ - md4cFlags?: Md4cFlags; - /** - * Specifies whether fonts should scale to respect Text Size accessibility settings. - * When false, text will not scale with the user's accessibility settings. - * @default true - */ - allowFontScaling?: boolean; - /** - * Specifies the largest possible scale a font can reach when allowFontScaling is enabled. - * Possible values: - * - undefined/null (default): no limit - * - 0: no limit - * - >= 1: sets the maxFontSizeMultiplier of this node to this value - * @default undefined - */ - maxFontSizeMultiplier?: number; - /** - * When false (default), removes trailing margin from the last element to eliminate bottom spacing. - * When true, keeps the trailing margin from the last element's marginBottom style. - * @default false - */ - allowTrailingMargin?: boolean; - /** - * Specifies which Markdown flavor to use for rendering. - * - `'commonmark'` (default): standard CommonMark renderer (single TextView). - * - `'github'`: GitHub Flavored Markdown — container-based renderer with support for tables and other GFM extensions. - * @default 'commonmark' - */ - flavor?: 'commonmark' | 'github'; - /** - * When true, newly appended content fades in during streaming updates. - * Only the tail (new characters beyond the previous content) is animated. - * Recommended for LLM streaming use cases with `flavor="commonmark"`. - * @default false - */ - streamingAnimation?: boolean; - /** - * Custom items to show in the text selection context menu. - * Each item requires a `text` label and an `onPress` callback. - * Items with `visible: false` are hidden from the menu. - */ - contextMenuItems?: ContextMenuItem[]; -} - -const defaultMd4cFlags: Md4cFlags = { - underline: false, - latexMath: true, -}; - -export const EnrichedMarkdownText = ({ - markdown, - markdownStyle = {}, - containerStyle, - onLinkPress, - onLinkLongPress, - onTaskListItemPress, - enableLinkPreview, - selectable = true, - md4cFlags = defaultMd4cFlags, - allowFontScaling = true, - maxFontSizeMultiplier, - allowTrailingMargin = false, - flavor = 'commonmark', - streamingAnimation = false, - contextMenuItems, - ...rest -}: EnrichedMarkdownTextProps) => { - const normalizedStyleRef = useRef(null); - const normalized = normalizeMarkdownStyle(markdownStyle); - // normalizeMarkdownStyle returns cached objects for structurally equal inputs, - // so this referential check is sufficient to preserve a stable prop reference. - if (normalizedStyleRef.current !== normalized) { - normalizedStyleRef.current = normalized; - } - const normalizedStyle = normalizedStyleRef.current!; - - const normalizedMd4cFlags = useMemo( - () => ({ - underline: md4cFlags.underline ?? false, - latexMath: md4cFlags.latexMath ?? true, - }), - [md4cFlags] - ); - - const contextMenuCallbacksRef = useRef< - Map - >(new Map()); - - useEffect(() => { - const callbacksMap = new Map(); - if (contextMenuItems) { - for (const item of contextMenuItems) { - callbacksMap.set(item.text, item.onPress); - } - } - contextMenuCallbacksRef.current = callbacksMap; - }, [contextMenuItems]); - - const nativeContextMenuItems = useMemo( - () => - contextMenuItems - ?.filter((item) => item.visible !== false) - .map((item) => ({ text: item.text, icon: item.icon })), - [contextMenuItems] - ); - - const handleContextMenuItemPress = useCallback( - (e: NativeSyntheticEvent) => { - const { itemText, selectedText, selectionStart, selectionEnd } = - e.nativeEvent; - const callback = contextMenuCallbacksRef.current.get(itemText); - callback?.({ - text: selectedText, - selection: { start: selectionStart, end: selectionEnd }, - }); - }, - [] - ); - - const handleLinkPress = useCallback( - (e: NativeSyntheticEvent) => { - const { url } = e.nativeEvent; - onLinkPress?.({ url }); - }, - [onLinkPress] - ); - - const handleLinkLongPress = useCallback( - (e: NativeSyntheticEvent) => { - const { url } = e.nativeEvent; - onLinkLongPress?.({ url }); - }, - [onLinkLongPress] - ); - - const handleTaskListItemPress = useCallback( - (e: NativeSyntheticEvent) => { - const { index, checked, text } = e.nativeEvent; - onTaskListItemPress?.({ index, checked, text }); - }, - [onTaskListItemPress] - ); - - const sharedProps = { - markdown, - markdownStyle: normalizedStyle, - onLinkPress: handleLinkPress, - onLinkLongPress: handleLinkLongPress, - onTaskListItemPress: handleTaskListItemPress, - enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true), - selectable, - md4cFlags: normalizedMd4cFlags, - allowFontScaling, - maxFontSizeMultiplier, - allowTrailingMargin, - streamingAnimation, - style: containerStyle, - contextMenuItems: nativeContextMenuItems, - onContextMenuItemPress: handleContextMenuItemPress, - ...rest, - }; - - if (flavor === 'github') { - return ; - } - - return ; -}; - -export default EnrichedMarkdownText; diff --git a/src/index.tsx b/src/index.tsx index 23b37684..f2a6fd2b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,14 +1,15 @@ -export { default as EnrichedMarkdownText } from './EnrichedMarkdownText'; +export { default as EnrichedMarkdownText } from './native/EnrichedMarkdownText'; export type { EnrichedMarkdownTextProps, MarkdownStyle, Md4cFlags, -} from './EnrichedMarkdownText'; + ContextMenuItem as TextContextMenuItem, +} from './native/EnrichedMarkdownText'; export type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, -} from './EnrichedMarkdownTextNativeComponent'; +} from './types/events'; export { EnrichedMarkdownInput } from './EnrichedMarkdownInput'; export type { @@ -19,5 +20,3 @@ export type { ContextMenuItem, OnLinkDetected, } from './EnrichedMarkdownInput'; - -export type { ContextMenuItem as TextContextMenuItem } from './EnrichedMarkdownText'; diff --git a/src/index.web.tsx b/src/index.web.tsx new file mode 100644 index 00000000..4b4305cf --- /dev/null +++ b/src/index.web.tsx @@ -0,0 +1,8 @@ +export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText'; +export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web'; +export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle'; +export type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, +} from './types/events'; diff --git a/src/native/EnrichedMarkdownText.tsx b/src/native/EnrichedMarkdownText.tsx new file mode 100644 index 00000000..30de89d2 --- /dev/null +++ b/src/native/EnrichedMarkdownText.tsx @@ -0,0 +1,148 @@ +import { useMemo, useCallback, useRef, useEffect } from 'react'; +import EnrichedMarkdownTextNativeComponent from '../EnrichedMarkdownTextNativeComponent'; +import type { MarkdownStyleInternal } from '../EnrichedMarkdownTextNativeComponent'; +import EnrichedMarkdownNativeComponent from '../EnrichedMarkdownNativeComponent'; +import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle'; +import type { NativeSyntheticEvent } from 'react-native'; +import type { MarkdownStyle, Md4cFlags } from '../types/MarkdownStyle'; +import type { + EnrichedMarkdownTextProps, + ContextMenuItem, +} from '../types/MarkdownTextProps'; +import type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, + OnContextMenuItemPressEvent, +} from '../types/events'; + +export type { MarkdownStyle, Md4cFlags }; +export type { EnrichedMarkdownTextProps, ContextMenuItem }; +export type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent }; + +const defaultMd4cFlags: Md4cFlags = { + underline: false, + latexMath: true, +}; + +export const EnrichedMarkdownText = ({ + markdown, + markdownStyle = {}, + containerStyle, + onLinkPress, + onLinkLongPress, + onTaskListItemPress, + enableLinkPreview, + selectable = true, + md4cFlags = defaultMd4cFlags, + allowFontScaling = true, + maxFontSizeMultiplier, + allowTrailingMargin = false, + flavor = 'commonmark', + streamingAnimation = false, + contextMenuItems, + ...rest +}: EnrichedMarkdownTextProps) => { + const normalizedStyleRef = useRef(null); + const normalized = normalizeMarkdownStyle(markdownStyle); + // normalizeMarkdownStyle returns cached objects for structurally equal inputs, + // so this referential check is sufficient to preserve a stable prop reference. + if (normalizedStyleRef.current !== normalized) { + normalizedStyleRef.current = normalized; + } + const normalizedStyle = normalizedStyleRef.current!; + + const normalizedMd4cFlags = useMemo( + () => ({ + underline: md4cFlags.underline ?? false, + latexMath: md4cFlags.latexMath ?? true, + }), + [md4cFlags] + ); + + const contextMenuCallbacksRef = useRef< + Map + >(new Map()); + + useEffect(() => { + const callbacksMap = new Map(); + if (contextMenuItems) { + for (const item of contextMenuItems) { + callbacksMap.set(item.text, item.onPress); + } + } + contextMenuCallbacksRef.current = callbacksMap; + }, [contextMenuItems]); + + const nativeContextMenuItems = useMemo( + () => + contextMenuItems + ?.filter((item) => item.visible !== false) + .map((item) => ({ text: item.text, icon: item.icon })), + [contextMenuItems] + ); + + const handleContextMenuItemPress = useCallback( + (e: NativeSyntheticEvent) => { + const { itemText, selectedText, selectionStart, selectionEnd } = + e.nativeEvent; + const callback = contextMenuCallbacksRef.current.get(itemText); + callback?.({ + text: selectedText, + selection: { start: selectionStart, end: selectionEnd }, + }); + }, + [] + ); + + const handleLinkPress = useCallback( + (e: NativeSyntheticEvent) => { + const { url } = e.nativeEvent; + onLinkPress?.({ url }); + }, + [onLinkPress] + ); + + const handleLinkLongPress = useCallback( + (e: NativeSyntheticEvent) => { + const { url } = e.nativeEvent; + onLinkLongPress?.({ url }); + }, + [onLinkLongPress] + ); + + const handleTaskListItemPress = useCallback( + (e: NativeSyntheticEvent) => { + const { index, checked, text } = e.nativeEvent; + onTaskListItemPress?.({ index, checked, text }); + }, + [onTaskListItemPress] + ); + + const sharedProps = { + markdown, + markdownStyle: normalizedStyle, + onLinkPress: handleLinkPress, + onLinkLongPress: handleLinkLongPress, + onTaskListItemPress: handleTaskListItemPress, + enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true), + selectable, + md4cFlags: normalizedMd4cFlags, + allowFontScaling, + maxFontSizeMultiplier, + allowTrailingMargin, + streamingAnimation, + style: containerStyle, + contextMenuItems: nativeContextMenuItems, + onContextMenuItemPress: handleContextMenuItemPress, + ...rest, + }; + + if (flavor === 'github') { + return ; + } + + return ; +}; + +export default EnrichedMarkdownText; diff --git a/src/normalizeMarkdownStyle.ts b/src/normalizeMarkdownStyle.ts index d06e947a..d5bc278a 100644 --- a/src/normalizeMarkdownStyle.ts +++ b/src/normalizeMarkdownStyle.ts @@ -1,19 +1,40 @@ -import { Platform, processColor, type ColorValue } from 'react-native'; -import type { MarkdownStyle } from './EnrichedMarkdownText'; -import type { MarkdownStyleInternal } from './EnrichedMarkdownTextNativeComponent'; +import { Platform, processColor } from 'react-native'; +import type { MarkdownStyle } from './types/MarkdownStyle'; +import type { + BlockTextAlign, + EmphasisFontStyle, + MarkdownStyleInternal, +} from './types/MarkdownStyleInternal'; +// On native, processColor converts hex strings to ARGB integers the renderer +// expects. On web, CSS accepts hex strings natively — no conversion needed. +// MarkdownStyleInternal types colors as `string`; native consumers +// (EnrichedMarkdownTextNativeComponent) accept `ColorValue` (string | number) +// at runtime, so the ARGB integers processColor produces are handled correctly. export const normalizeColor = ( color: string | undefined -): ColorValue | undefined => (color ? processColor(color) : undefined); +): string | undefined => + color + ? Platform.OS === 'web' + ? color + : ((processColor(color) ?? undefined) as string | undefined) + : undefined; -const getSystemFont = () => +const getSystemFont = (): string => Platform.select({ ios: 'System', android: 'sans-serif', + web: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', default: 'sans-serif', - }); -const getMonospaceFont = () => - Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }); + })!; + +const getMonospaceFont = (): string => + Platform.select({ + ios: 'Menlo', + android: 'monospace', + web: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace', + default: 'monospace', + })!; function mergeSubStyle>( defaultStyle: T, @@ -21,12 +42,14 @@ function mergeSubStyle>( ): T { if (!userStyle) return defaultStyle; const result: Record = { ...defaultStyle, ...userStyle }; + // Normalize any user-supplied color strings. On web this is a no-op (CSS + // accepts hex strings); on native it converts them to ARGB integers. for (const key in result) { if ( key.toLowerCase().includes('color') && typeof result[key] === 'string' ) { - result[key] = normalizeColor(result[key]); + result[key] = normalizeColor(result[key] as string); } } return result as T; @@ -34,7 +57,16 @@ function mergeSubStyle>( const defaultTextColor = normalizeColor('#1F2937')!; const defaultHeadingColor = normalizeColor('#111827')!; -const baseHeader = { + +// Explicit type annotation needed: Object.freeze breaks contextual typing, so +// TypeScript widens literal 'auto' to `string` instead of `BlockTextAlign`. +const baseHeader: { + fontFamily: string; + fontWeight: string; + marginTop: number; + marginBottom: number; + textAlign: BlockTextAlign; +} = { fontFamily: getSystemFont(), fontWeight: '', marginTop: 0, @@ -48,53 +80,53 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ fontFamily: getSystemFont(), fontWeight: '', color: defaultTextColor, - lineHeight: Platform.select({ ios: 24, android: 26, default: 26 }), + lineHeight: Platform.select({ ios: 24, android: 26, default: 26 })!, marginTop: 0, marginBottom: 16, - textAlign: 'auto', + textAlign: 'auto' as BlockTextAlign, }, h1: { ...baseHeader, fontSize: 30, color: defaultHeadingColor, - lineHeight: Platform.select({ ios: 36, android: 38, default: 38 }), + lineHeight: Platform.select({ ios: 36, android: 38, default: 38 })!, }, h2: { ...baseHeader, fontSize: 24, color: defaultHeadingColor, - lineHeight: Platform.select({ ios: 30, android: 32, default: 32 }), + lineHeight: Platform.select({ ios: 30, android: 32, default: 32 })!, }, h3: { ...baseHeader, fontSize: 20, color: defaultHeadingColor, - lineHeight: Platform.select({ ios: 26, android: 28, default: 28 }), + lineHeight: Platform.select({ ios: 26, android: 28, default: 28 })!, }, h4: { ...baseHeader, fontSize: 18, color: defaultHeadingColor, - lineHeight: Platform.select({ ios: 24, android: 26, default: 26 }), + lineHeight: Platform.select({ ios: 24, android: 26, default: 26 })!, }, h5: { ...baseHeader, fontSize: 16, color: normalizeColor('#374151')!, - lineHeight: Platform.select({ ios: 22, android: 24, default: 24 }), + lineHeight: Platform.select({ ios: 22, android: 24, default: 24 })!, }, h6: { ...baseHeader, fontSize: 14, color: normalizeColor('#4B5563')!, - lineHeight: Platform.select({ ios: 20, android: 22, default: 22 }), + lineHeight: Platform.select({ ios: 20, android: 22, default: 22 })!, }, blockquote: { fontSize: 16, fontFamily: getSystemFont(), fontWeight: '', color: normalizeColor('#4B5563')!, - lineHeight: Platform.select({ ios: 24, android: 26, default: 26 }), + lineHeight: Platform.select({ ios: 24, android: 26, default: 26 })!, marginTop: 0, marginBottom: 16, borderColor: normalizeColor('#D1D5DB')!, @@ -107,7 +139,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ fontFamily: getSystemFont(), fontWeight: '', color: defaultTextColor, - lineHeight: Platform.select({ ios: 22, android: 26, default: 26 }), + lineHeight: Platform.select({ ios: 22, android: 26, default: 26 })!, marginTop: 0, marginBottom: 16, bulletColor: normalizeColor('#6B7280')!, @@ -122,7 +154,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ fontFamily: getMonospaceFont(), fontWeight: '', color: normalizeColor('#F3F4F6')!, - lineHeight: Platform.select({ ios: 20, android: 22, default: 22 }), + lineHeight: Platform.select({ ios: 20, android: 22, default: 22 })!, marginTop: 0, marginBottom: 16, backgroundColor: normalizeColor('#1F2937')!, @@ -133,11 +165,17 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ }, link: { fontFamily: '', color: normalizeColor('#2563EB')!, underline: true }, strong: { fontFamily: '', fontWeight: 'bold', color: undefined }, - em: { fontFamily: '', fontStyle: 'italic', color: undefined }, + em: { + fontFamily: '', + fontStyle: 'italic' as EmphasisFontStyle, + color: undefined, + }, strikethrough: { color: normalizeColor('#9CA3AF')! }, underline: { color: defaultTextColor }, code: { - fontFamily: '', + // Native uses '' (inherit); web needs an explicit monospace stack so inline + // code doesn't fall back to the browser's default proportional font. + fontFamily: Platform.select({ web: getMonospaceFont(), default: '' })!, fontSize: 0, color: normalizeColor('#E01E5A')!, backgroundColor: normalizeColor('#FDF2F4')!, @@ -158,7 +196,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ color: defaultTextColor, marginTop: 0, marginBottom: 16, - lineHeight: Platform.select({ ios: 20, android: 22, default: 22 }), + lineHeight: Platform.select({ ios: 20, android: 22, default: 22 })!, headerFontFamily: '', headerBackgroundColor: normalizeColor('#F3F4F6')!, headerTextColor: normalizeColor('#111827')!, @@ -177,7 +215,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ padding: 12, marginTop: 0, marginBottom: 16, - textAlign: 'center', + textAlign: 'center' as BlockTextAlign, }, inlineMath: { color: defaultTextColor }, taskList: { @@ -185,7 +223,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ ios: normalizeColor('#007AFF')!, android: normalizeColor('#2196F3')!, default: normalizeColor('#007AFF')!, - }), + })!, borderColor: normalizeColor('#9E9E9E')!, checkboxSize: 14, checkboxBorderRadius: 3, @@ -233,22 +271,24 @@ export const normalizeMarkdownStyle = ( return entry.result; } - const result: any = {}; + const result: Record = {}; ( Object.keys(DEFAULT_NORMALIZED_STYLE) as (keyof MarkdownStyleInternal)[] ).forEach((key) => { result[key] = mergeSubStyle( - DEFAULT_NORMALIZED_STYLE[key] as any, - style[key] as any + DEFAULT_NORMALIZED_STYLE[key] as unknown as Record, + style[key] as unknown as Record | undefined ); }); if (style.taskList?.checkboxSize === undefined) { - const listSize = result.list.fontSize; - result.taskList.checkboxSize = Math.round(listSize * 0.9); + const listSize = (result.list as { fontSize: number }).fontSize; + (result.taskList as { checkboxSize: number }).checkboxSize = Math.round( + listSize * 0.9 + ); } - const finalResult = Object.freeze(result); + const finalResult = Object.freeze(result) as unknown as MarkdownStyleInternal; refCache.set(style, finalResult); structuralCache.unshift({ style, result: finalResult }); if (structuralCache.length > LRU_MAX) structuralCache.pop(); diff --git a/src/normalizeMarkdownStyle.web.ts b/src/normalizeMarkdownStyle.web.ts new file mode 100644 index 00000000..e00533b4 --- /dev/null +++ b/src/normalizeMarkdownStyle.web.ts @@ -0,0 +1,252 @@ +import type { MarkdownStyle } from './types/MarkdownStyle'; +import type { + BlockTextAlign, + EmphasisFontStyle, + MarkdownStyleInternal, +} from './types/MarkdownStyleInternal'; + +const SYSTEM_FONT = + 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; +const MONOSPACE_FONT = + 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace'; + +function mergeSubStyle>( + defaultStyle: T, + userStyle?: Partial +): T { + if (!userStyle) return defaultStyle; + return { ...defaultStyle, ...userStyle }; +} + +const defaultTextColor = '#1F2937'; +const defaultHeadingColor = '#111827'; + +const baseHeader: { + fontFamily: string; + fontWeight: string; + marginTop: number; + marginBottom: number; + textAlign: BlockTextAlign; +} = { + fontFamily: SYSTEM_FONT, + fontWeight: '', + marginTop: 0, + marginBottom: 8, + textAlign: 'auto', +}; + +const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ + paragraph: { + fontSize: 16, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: defaultTextColor, + lineHeight: 26, + marginTop: 0, + marginBottom: 16, + textAlign: 'auto' as BlockTextAlign, + }, + h1: { + ...baseHeader, + fontSize: 30, + color: defaultHeadingColor, + lineHeight: 38, + }, + h2: { + ...baseHeader, + fontSize: 24, + color: defaultHeadingColor, + lineHeight: 32, + }, + h3: { + ...baseHeader, + fontSize: 20, + color: defaultHeadingColor, + lineHeight: 28, + }, + h4: { + ...baseHeader, + fontSize: 18, + color: defaultHeadingColor, + lineHeight: 26, + }, + h5: { + ...baseHeader, + fontSize: 16, + color: '#374151', + lineHeight: 24, + }, + h6: { + ...baseHeader, + fontSize: 14, + color: '#4B5563', + lineHeight: 22, + }, + blockquote: { + fontSize: 16, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: '#4B5563', + lineHeight: 26, + marginTop: 0, + marginBottom: 16, + borderColor: '#D1D5DB', + borderWidth: 3, + gapWidth: 16, + backgroundColor: '#F9FAFB', + }, + list: { + fontSize: 16, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: defaultTextColor, + lineHeight: 26, + marginTop: 0, + marginBottom: 16, + bulletColor: '#6B7280', + bulletSize: 6, + markerColor: '#6B7280', + markerFontWeight: '500', + gapWidth: 12, + marginLeft: 24, + }, + codeBlock: { + fontSize: 14, + fontFamily: MONOSPACE_FONT, + fontWeight: '', + color: '#F3F4F6', + lineHeight: 22, + marginTop: 0, + marginBottom: 16, + backgroundColor: '#1F2937', + borderColor: '#374151', + borderRadius: 8, + borderWidth: 1, + padding: 16, + }, + link: { fontFamily: '', color: '#2563EB', underline: true }, + strong: { fontFamily: '', fontWeight: 'bold', color: undefined }, + em: { + fontFamily: '', + fontStyle: 'italic' as EmphasisFontStyle, + color: undefined, + }, + strikethrough: { color: '#9CA3AF' }, + underline: { color: defaultTextColor }, + code: { + fontFamily: MONOSPACE_FONT, + fontSize: 0, + color: '#E01E5A', + backgroundColor: '#FDF2F4', + borderColor: '#F8D7DA', + }, + image: { height: 200, borderRadius: 8, marginTop: 0, marginBottom: 16 }, + inlineImage: { size: 20 }, + thematicBreak: { + color: '#E5E7EB', + height: 1, + marginTop: 24, + marginBottom: 24, + }, + table: { + fontSize: 14, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: defaultTextColor, + marginTop: 0, + marginBottom: 16, + lineHeight: 22, + headerFontFamily: '', + headerBackgroundColor: '#F3F4F6', + headerTextColor: '#111827', + rowEvenBackgroundColor: '#FFFFFF', + rowOddBackgroundColor: '#F9FAFB', + borderColor: '#E5E7EB', + borderWidth: 1, + borderRadius: 6, + cellPaddingHorizontal: 12, + cellPaddingVertical: 8, + }, + math: { + fontSize: 20, + color: defaultTextColor, + backgroundColor: '#F3F4F6', + padding: 12, + marginTop: 0, + marginBottom: 16, + textAlign: 'center' as BlockTextAlign, + }, + inlineMath: { color: defaultTextColor }, + taskList: { + checkedColor: '#007AFF', + borderColor: '#9E9E9E', + checkboxSize: 14, + checkboxBorderRadius: 3, + checkmarkColor: '#FFFFFF', + checkedTextColor: '#000000', + checkedStrikethrough: false, + }, +}); + +const refCache = new WeakMap(); +const structuralCache: { + style: MarkdownStyle; + result: MarkdownStyleInternal; +}[] = []; +const LRU_MAX = 8; + +const isStyleEqual = (a: MarkdownStyle, b: MarkdownStyle): boolean => { + const keys = Object.keys(DEFAULT_NORMALIZED_STYLE) as (keyof MarkdownStyle)[]; + return keys.every((key) => { + const subA = a[key], + subB = b[key]; + if (subA === subB) return true; + if (!subA || !subB) return false; + const subKeys = Object.keys(subA) as (keyof typeof subA)[]; + return subKeys.every((k) => subA[k] === subB[k]); + }); +}; + +export const normalizeMarkdownStyle = ( + style: MarkdownStyle +): MarkdownStyleInternal => { + if (!style || Object.keys(style).length === 0) + return DEFAULT_NORMALIZED_STYLE; + + const refHit = refCache.get(style); + if (refHit) return refHit; + + const structIdx = structuralCache.findIndex((e) => + isStyleEqual(e.style, style) + ); + if (structIdx !== -1) { + const entry = structuralCache.splice(structIdx, 1)[0]!; + structuralCache.unshift(entry); + refCache.set(style, entry.result); + return entry.result; + } + + const result: Record = {}; + ( + Object.keys(DEFAULT_NORMALIZED_STYLE) as (keyof MarkdownStyleInternal)[] + ).forEach((key) => { + result[key] = mergeSubStyle( + DEFAULT_NORMALIZED_STYLE[key] as unknown as Record, + style[key] as unknown as Record | undefined + ); + }); + + if (style.taskList?.checkboxSize === undefined) { + const listSize = (result.list as { fontSize: number }).fontSize; + (result.taskList as { checkboxSize: number }).checkboxSize = Math.round( + listSize * 0.9 + ); + } + + const finalResult = Object.freeze(result) as unknown as MarkdownStyleInternal; + refCache.set(style, finalResult); + structuralCache.unshift({ style, result: finalResult }); + if (structuralCache.length > LRU_MAX) structuralCache.pop(); + + return finalResult; +}; diff --git a/src/types/MarkdownStyle.ts b/src/types/MarkdownStyle.ts new file mode 100644 index 00000000..fc37ac8f --- /dev/null +++ b/src/types/MarkdownStyle.ts @@ -0,0 +1,198 @@ +type TextAlign = 'auto' | 'left' | 'right' | 'center' | 'justify'; + +interface BaseBlockStyle { + fontSize?: number; + fontFamily?: string; + fontWeight?: string; + color?: string; + marginTop?: number; + marginBottom?: number; + lineHeight?: number; +} + +interface ParagraphStyle extends BaseBlockStyle { + textAlign?: TextAlign; +} + +interface HeadingStyle extends BaseBlockStyle { + textAlign?: TextAlign; +} + +interface BlockquoteStyle extends BaseBlockStyle { + borderColor?: string; + borderWidth?: number; + gapWidth?: number; + backgroundColor?: string; +} + +interface ListStyle extends BaseBlockStyle { + bulletColor?: string; + bulletSize?: number; + markerColor?: string; + markerFontWeight?: string; + gapWidth?: number; + marginLeft?: number; +} + +interface CodeBlockStyle extends BaseBlockStyle { + backgroundColor?: string; + borderColor?: string; + borderRadius?: number; + borderWidth?: number; + padding?: number; +} + +interface LinkStyle { + fontFamily?: string; + color?: string; + underline?: boolean; +} + +interface StrongStyle { + fontFamily?: string; + /** + * Controls whether bold is applied on top of the custom fontFamily. + * Only relevant when fontFamily is set. Defaults to 'bold'. + * Set to 'normal' to use the font face as-is without adding bold. + */ + fontWeight?: 'bold' | 'normal'; + color?: string; +} + +interface EmphasisStyle { + fontFamily?: string; + /** + * Controls whether italic is applied on top of the custom fontFamily. + * Only relevant when fontFamily is set. Defaults to 'italic'. + * Set to 'normal' to use the font face as-is without adding italic. + */ + fontStyle?: 'italic' | 'normal'; + color?: string; +} + +interface StrikethroughStyle { + /** + * Color of the strikethrough line. + * @platform iOS + */ + color?: string; +} + +interface UnderlineStyle { + /** + * Color of the underline. + * @platform iOS + */ + color?: string; +} + +interface CodeStyle { + fontFamily?: string; + fontSize?: number; + color?: string; + backgroundColor?: string; + borderColor?: string; +} + +interface ImageStyle { + height?: number; + borderRadius?: number; + marginTop?: number; + marginBottom?: number; +} + +interface InlineImageStyle { + size?: number; +} + +interface ThematicBreakStyle { + color?: string; + height?: number; + marginTop?: number; + marginBottom?: number; +} + +interface TableStyle extends BaseBlockStyle { + headerFontFamily?: string; + headerBackgroundColor?: string; + headerTextColor?: string; + rowEvenBackgroundColor?: string; + rowOddBackgroundColor?: string; + borderColor?: string; + borderWidth?: number; + borderRadius?: number; + cellPaddingHorizontal?: number; + cellPaddingVertical?: number; +} + +interface TaskListStyle { + checkedColor?: string; + borderColor?: string; + checkboxSize?: number; + checkboxBorderRadius?: number; + checkmarkColor?: string; + checkedTextColor?: string; + checkedStrikethrough?: boolean; +} + +interface MathStyle { + fontSize?: number; + color?: string; + backgroundColor?: string; + padding?: number; + marginTop?: number; + marginBottom?: number; + textAlign?: 'left' | 'center' | 'right'; +} + +interface InlineMathStyle { + color?: string; +} + +export interface MarkdownStyle { + paragraph?: ParagraphStyle; + h1?: HeadingStyle; + h2?: HeadingStyle; + h3?: HeadingStyle; + h4?: HeadingStyle; + h5?: HeadingStyle; + h6?: HeadingStyle; + blockquote?: BlockquoteStyle; + list?: ListStyle; + codeBlock?: CodeBlockStyle; + link?: LinkStyle; + strong?: StrongStyle; + em?: EmphasisStyle; + strikethrough?: StrikethroughStyle; + underline?: UnderlineStyle; + code?: CodeStyle; + image?: ImageStyle; + inlineImage?: InlineImageStyle; + thematicBreak?: ThematicBreakStyle; + table?: TableStyle; + taskList?: TaskListStyle; + math?: MathStyle; + inlineMath?: InlineMathStyle; +} + +/** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + */ +export interface Md4cFlags { + /** + * Enable underline syntax support (__text__). + * When enabled, underscores are treated as underline markers. + * When disabled, underscores are treated as emphasis markers (same as asterisks). + * @default false + */ + underline?: boolean; + /** + * Enable LaTeX math span parsing ($..$ and $$..$$). + * When enabled, the parser recognizes LaTeX math delimiters. + * When disabled, dollar signs are treated as plain text. + * Requires the optional iosMath (iOS) / AndroidMath (Android) native dependencies. + * @default true + */ + latexMath?: boolean; +} diff --git a/src/types/MarkdownStyleInternal.ts b/src/types/MarkdownStyleInternal.ts new file mode 100644 index 00000000..f9ff70c3 --- /dev/null +++ b/src/types/MarkdownStyleInternal.ts @@ -0,0 +1,164 @@ +// Mirrors the public TextAlign type from MarkdownStyle.ts plus 'auto' sentinel. +// 'auto' is a React Native concept meaning "use the device default direction"; +// the web normalizer maps it to undefined (no CSS textAlign set). +export type BlockTextAlign = 'auto' | 'left' | 'right' | 'center' | 'justify'; + +// Mirrors the public fontStyle values; empty string means "inherit / no override". +export type EmphasisFontStyle = 'normal' | 'italic' | 'oblique' | ''; + +interface BaseBlockStyleInternal { + fontSize: number; + fontFamily: string; + fontWeight: string; + color: string; + marginTop: number; + marginBottom: number; + lineHeight: number; +} + +interface ParagraphStyleInternal extends BaseBlockStyleInternal { + textAlign: BlockTextAlign; +} + +interface HeadingStyleInternal extends BaseBlockStyleInternal { + textAlign: BlockTextAlign; +} + +interface BlockquoteStyleInternal extends BaseBlockStyleInternal { + borderColor: string; + borderWidth: number; + gapWidth: number; + backgroundColor: string; +} + +interface ListStyleInternal extends BaseBlockStyleInternal { + bulletColor: string; + bulletSize: number; + markerColor: string; + markerFontWeight: string; + gapWidth: number; + marginLeft: number; +} + +interface CodeBlockStyleInternal extends BaseBlockStyleInternal { + backgroundColor: string; + borderColor: string; + borderRadius: number; + borderWidth: number; + padding: number; +} + +interface LinkStyleInternal { + fontFamily: string; + color: string; + underline: boolean; +} + +interface StrongStyleInternal { + fontFamily: string; + fontWeight: string; + color?: string; +} + +interface EmphasisStyleInternal { + fontFamily: string; + fontStyle: EmphasisFontStyle; + color?: string; +} + +interface StrikethroughStyleInternal { + color: string; +} + +interface UnderlineStyleInternal { + color: string; +} + +interface CodeStyleInternal { + fontFamily: string; + fontSize: number; + color: string; + backgroundColor: string; + borderColor: string; +} + +interface ImageStyleInternal { + height: number; + borderRadius: number; + marginTop: number; + marginBottom: number; +} + +interface InlineImageStyleInternal { + size: number; +} + +interface ThematicBreakStyleInternal { + color: string; + height: number; + marginTop: number; + marginBottom: number; +} + +interface TableStyleInternal extends BaseBlockStyleInternal { + headerFontFamily: string; + headerBackgroundColor: string; + headerTextColor: string; + rowEvenBackgroundColor: string; + rowOddBackgroundColor: string; + borderColor: string; + borderWidth: number; + borderRadius: number; + cellPaddingHorizontal: number; + cellPaddingVertical: number; +} + +interface TaskListStyleInternal { + checkedColor: string; + borderColor: string; + checkboxSize: number; + checkboxBorderRadius: number; + checkmarkColor: string; + checkedTextColor: string; + checkedStrikethrough: boolean; +} + +interface MathStyleInternal { + fontSize: number; + color: string; + backgroundColor: string; + padding: number; + marginTop: number; + marginBottom: number; + textAlign: BlockTextAlign; +} + +interface InlineMathStyleInternal { + color: string; +} + +export interface MarkdownStyleInternal { + paragraph: ParagraphStyleInternal; + h1: HeadingStyleInternal; + h2: HeadingStyleInternal; + h3: HeadingStyleInternal; + h4: HeadingStyleInternal; + h5: HeadingStyleInternal; + h6: HeadingStyleInternal; + blockquote: BlockquoteStyleInternal; + list: ListStyleInternal; + codeBlock: CodeBlockStyleInternal; + link: LinkStyleInternal; + strong: StrongStyleInternal; + em: EmphasisStyleInternal; + strikethrough: StrikethroughStyleInternal; + underline: UnderlineStyleInternal; + code: CodeStyleInternal; + image: ImageStyleInternal; + inlineImage: InlineImageStyleInternal; + thematicBreak: ThematicBreakStyleInternal; + table: TableStyleInternal; + taskList: TaskListStyleInternal; + math: MathStyleInternal; + inlineMath: InlineMathStyleInternal; +} diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts new file mode 100644 index 00000000..6599119b --- /dev/null +++ b/src/types/MarkdownTextProps.ts @@ -0,0 +1,150 @@ +import type { ViewProps, ViewStyle, TextStyle } from 'react-native'; +import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; +import type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, +} from './events'; + +/** + * Public context menu item. Each item includes a JS-side `onPress` callback + * that is called when the user taps the item in the selection context menu. + */ +export interface ContextMenuItem { + text: string; + onPress: (event: { + text: string; + selection: { start: number; end: number }; + }) => void; + icon?: string; + visible?: boolean; +} + +export interface EnrichedMarkdownTextProps extends Omit { + /** + * Markdown content to render. + * @platform ios, android, web + */ + markdown: string; + /** + * Style configuration for markdown elements. + * @platform ios, android, web + */ + markdownStyle?: MarkdownStyle; + /** + * Style for the container view. + * @platform ios, android, web + */ + containerStyle?: ViewStyle | TextStyle; + /** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + * @platform ios, android, web + */ + md4cFlags?: Md4cFlags; + /** + * Callback fired when a link is pressed. + * Receives the link URL directly. + * @platform ios, android, web + */ + onLinkPress?: (event: LinkPressEvent) => void; + /** + * Callback fired when a link is long pressed. + * Receives the link URL directly. + * - iOS: When provided, automatically disables the system link preview + * (unless `enableLinkPreview` is explicitly set to `true`). + * - Android: Handles long press gestures on links. + * - Web: Mapped to the `contextmenu` event (right-click). + * @platform ios, android, web + */ + onLinkLongPress?: (event: LinkLongPressEvent) => void; + /** + * Callback fired when a task list checkbox is tapped. + * + * The checkbox is toggled on the native side automatically. + * Receives the 0-based task index, the new checked state (after toggling), + * and the item's plain text. + * + * Only fires when `flavor="github"` (GFM task lists require GitHub flavor). + * @platform ios, android, web + */ + onTaskListItemPress?: (event: TaskListItemPressEvent) => void; + /** + * Controls whether the system link preview is shown on long press (iOS only). + * + * When `true`, long-pressing a link shows the native iOS link preview. + * When `false`, the system preview is suppressed. + * + * Defaults to `true`, but automatically becomes `false` when `onLinkLongPress` + * is provided. Set explicitly to override the automatic behavior. + * + * @default true + * @platform ios + */ + enableLinkPreview?: boolean; + /** + * Controls text selection. + * - iOS: Controls text selection and link previews on long press. + * - Android: Controls text selection. + * - Web: Applies `user-select: none` when `false`. + * @default true + * @platform ios, android, web + */ + selectable?: boolean; + /** + * Specifies whether fonts should scale to respect Text Size accessibility settings. + * When false, text will not scale with the user's accessibility settings. + * @default true + * @platform ios, android + */ + allowFontScaling?: boolean; + /** + * Specifies the largest possible scale a font can reach when `allowFontScaling` + * is enabled. + * - `undefined` / `null` (default): no limit + * - `0`: no limit + * - `>= 1`: sets the maxFontSizeMultiplier of this node to this value + * @default undefined + * @platform ios, android + */ + maxFontSizeMultiplier?: number; + /** + * When false (default), removes trailing margin from the last element to + * eliminate bottom spacing. + * When true, keeps the trailing margin from the last element's marginBottom style. + * @default false + * @platform ios, android, web + */ + allowTrailingMargin?: boolean; + /** + * Specifies which Markdown flavor to use for rendering. + * - `'commonmark'` (default): standard CommonMark renderer (single TextView). + * - `'github'`: GitHub Flavored Markdown — container-based renderer with + * support for tables and other GFM extensions. + * @default 'commonmark' + * @platform ios, android + */ + flavor?: 'commonmark' | 'github'; + /** + * When true, newly appended content fades in during streaming updates. + * Only the tail (new characters beyond the previous content) is animated. + * Recommended for LLM streaming use cases with `flavor="commonmark"`. + * @default false + * @platform ios, android + */ + streamingAnimation?: boolean; + /** + * Custom items to show in the text selection context menu. + * Each item requires a `text` label and an `onPress` callback. + * Items with `visible: false` are hidden from the menu. + * @platform ios, android + */ + contextMenuItems?: ContextMenuItem[]; + /** + * Sets the text direction on the root container. + * Useful for RTL languages — CSS logical properties in the renderers + * automatically flip blockquote borders, list indentation, etc. + * @platform web + */ + dir?: 'ltr' | 'rtl' | 'auto'; +} diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts new file mode 100644 index 00000000..fa9d4014 --- /dev/null +++ b/src/types/MarkdownTextProps.web.ts @@ -0,0 +1,83 @@ +import type { CSSProperties, HTMLAttributes } from 'react'; +import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; +import type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, +} from './events'; + +export interface EnrichedMarkdownTextProps + extends Omit, 'style' | 'dir'> { + /** + * Markdown content to render. + * @platform ios, android, web + */ + markdown: string; + /** + * Style configuration for markdown elements. + * @platform ios, android, web + */ + markdownStyle?: MarkdownStyle; + /** + * Style for the container view. + * @platform ios, android, web + */ + containerStyle?: CSSProperties; + /** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + * @platform ios, android, web + */ + md4cFlags?: Md4cFlags; + /** + * Callback fired when a link is pressed. + * Receives the link URL directly. + * @platform ios, android, web + */ + onLinkPress?: (event: LinkPressEvent) => void; + /** + * Callback fired when a link is long pressed. + * Receives the link URL directly. + * - iOS: When provided, automatically disables the system link preview + * (unless `enableLinkPreview` is explicitly set to `true`). + * - Android: Handles long press gestures on links. + * - Web: Mapped to the `contextmenu` event (right-click). + * @platform ios, android, web + */ + onLinkLongPress?: (event: LinkLongPressEvent) => void; + /** + * Callback fired when a task list checkbox is tapped. + * + * The checkbox is toggled on the native side automatically. + * Receives the 0-based task index, the new checked state (after toggling), + * and the item's plain text. + * + * Only fires when `flavor="github"` (GFM task lists require GitHub flavor). + * @platform ios, android, web + */ + onTaskListItemPress?: (event: TaskListItemPressEvent) => void; + /** + * Controls text selection. + * - iOS: Controls text selection and link previews on long press. + * - Android: Controls text selection. + * - Web: Applies `user-select: none` when `false`. + * @default true + * @platform ios, android, web + */ + selectable?: boolean; + /** + * When false (default), removes trailing margin from the last element to + * eliminate bottom spacing. + * When true, keeps the trailing margin from the last element's marginBottom style. + * @default false + * @platform ios, android, web + */ + allowTrailingMargin?: boolean; + /** + * Sets the text direction on the root container. + * Useful for RTL languages — CSS logical properties in the renderers + * automatically flip blockquote borders, list indentation, etc. + * @platform web + */ + dir?: 'ltr' | 'rtl' | 'auto'; +} diff --git a/src/types/events.ts b/src/types/events.ts new file mode 100644 index 00000000..11f876c7 --- /dev/null +++ b/src/types/events.ts @@ -0,0 +1,32 @@ +export interface LinkPressEvent { + url: string; +} + +export interface LinkLongPressEvent { + url: string; +} + +export interface TaskListItemPressEvent { + index: number; + checked: boolean; + text: string; +} + +/** + * Native-level context menu item config sent to the native component. + * Does not include the `onPress` callback — callbacks are managed on the JS side. + */ +export interface ContextMenuItemConfig { + text: string; + icon?: string; +} + +/** + * Event payload fired by the native component when a context menu item is pressed. + */ +export interface OnContextMenuItemPressEvent { + itemText: string; + selectedText: string; + selectionStart: number; + selectionEnd: number; +} diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx new file mode 100644 index 00000000..70eb3a4c --- /dev/null +++ b/src/web/EnrichedMarkdownText.tsx @@ -0,0 +1,137 @@ +import { useState, useEffect, useMemo, type CSSProperties } from 'react'; +import type { EnrichedMarkdownTextProps } from '../types/MarkdownTextProps.web'; +import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle.web'; +import { + zeroTrailingMargins, + parseErrorFallbackStyle, + buildStyles, +} from './styles'; +import { parseMarkdown } from './parseMarkdown'; +import { RenderNode } from './renderers'; +import type { ASTNode, RendererCallbacks, RenderCapabilities } from './types'; +import { indexTaskItems, markInlineImages } from './utils'; +import { loadKaTeX } from './katex'; +import type { KaTeXInstance } from './katex'; + +export const EnrichedMarkdownText = ({ + markdown, + markdownStyle = {}, + md4cFlags = {}, + onLinkPress, + onLinkLongPress, + onTaskListItemPress, + allowTrailingMargin = false, + containerStyle, + selectable = true, + dir, + ...rest +}: EnrichedMarkdownTextProps) => { + const normalizedStyle = useMemo( + () => normalizeMarkdownStyle(markdownStyle), + [markdownStyle] + ); + + const [ast, setAst] = useState(null); + const [katex, setKatex] = useState(null); + const [parseError, setParseError] = useState(false); + + const { underline = false, latexMath = true } = md4cFlags; + + useEffect(() => { + let cancelled = false; + + const katexPromise = latexMath ? loadKaTeX() : Promise.resolve(null); + + Promise.all([ + parseMarkdown(markdown, { underline, latexMath }), + katexPromise, + ]) + .then(([result, katexInstance]) => { + if (!cancelled) { + indexTaskItems(result); + markInlineImages(result); + + setParseError(false); + setKatex(katexInstance); + setAst(result); + } + }) + .catch((error) => { + if (!cancelled) { + if (__DEV__) { + console.error('[EnrichedMarkdownText] Parse failed:', error); + } + + setParseError(true); + setAst(null); + setKatex(null); + } + }); + + return () => { + cancelled = true; + }; + }, [markdown, underline, latexMath]); + + const callbacks = useMemo( + () => ({ onLinkPress, onLinkLongPress, onTaskListItemPress }), + [onLinkPress, onLinkLongPress, onTaskListItemPress] + ); + + const capabilities = useMemo(() => ({ katex }), [katex]); + + const lastChildStyle = useMemo( + () => + allowTrailingMargin + ? normalizedStyle + : zeroTrailingMargins(normalizedStyle), + [normalizedStyle, allowTrailingMargin] + ); + + const styles = useMemo(() => buildStyles(normalizedStyle), [normalizedStyle]); + + const lastChildStyles = useMemo( + () => buildStyles(lastChildStyle), + [lastChildStyle] + ); + + const wrapperStyle = useMemo( + () => ({ + display: 'flex', + flexDirection: 'column', + ...(containerStyle as CSSProperties), + ...(selectable ? undefined : { userSelect: 'none' }), + }), + [containerStyle, selectable] + ); + + if (parseError) { + return ( +
      +
      {markdown}
      +
      + ); + } + + if (!ast) return null; + + const children = ast.children ?? []; + const lastIdx = children.length - 1; + + return ( +
      + {children.map((child, index) => ( + + ))} +
      + ); +}; + +export default EnrichedMarkdownText; diff --git a/src/web/katex.ts b/src/web/katex.ts new file mode 100644 index 00000000..2ab4a06f --- /dev/null +++ b/src/web/katex.ts @@ -0,0 +1,31 @@ +export interface KaTeXInstance { + renderToString( + expression: string, + options?: { + displayMode?: boolean; + throwOnError?: boolean; + trust?: boolean; + output?: 'html' | 'mathml' | 'htmlAndMathml'; + } + ): string; +} + +let katexLoadPromise: Promise | null = null; + +/** Lazily loads KaTeX. Resolves to null if not installed. */ +export function loadKaTeX(): Promise { + if (!katexLoadPromise) { + let instance: KaTeXInstance | null = null; + try { + const mod = require('katex'); + const candidate = (mod?.default ?? mod) as KaTeXInstance | null; + if (typeof candidate?.renderToString === 'function') { + instance = candidate; + } + } catch { + // katex not installed — math rendering will be skipped + } + katexLoadPromise = Promise.resolve(instance); + } + return katexLoadPromise; +} diff --git a/src/web/parseMarkdown.ts b/src/web/parseMarkdown.ts new file mode 100644 index 00000000..911e3f1d --- /dev/null +++ b/src/web/parseMarkdown.ts @@ -0,0 +1,59 @@ +import type { ASTNode } from './types'; +import type { Md4cFlags } from '../types/MarkdownStyle'; + +type ParseFn = ( + markdown: string, + underline: number, + latexMath: number +) => string; + +// Caching the Promise (not the resolved value) means concurrent callers share +// a single WASM initialization — no duplicate loading. +let parserPromise: Promise | null = null; + +// SINGLE_FILE=1 inlines the WASM binary as base64 inside md4c.js, so no +// network fetch is needed — only a one-time decode + compile on first call. +function initializeParser(): Promise { + if (!parserPromise) { + parserPromise = import('./wasm/md4c') + .then((module) => module.default()) + .then((wasmModule) => + wasmModule.cwrap('parseMarkdown', 'string', [ + 'string', + 'number', + 'number', + ]) + ) + .catch((error) => { + parserPromise = null; + throw error; + }) as Promise; + } + return parserPromise; +} + +function isASTNode(value: unknown): value is ASTNode { + return ( + typeof value === 'object' && + value !== null && + 'type' in value && + typeof (value as ASTNode).type === 'string' + ); +} + +export async function parseMarkdown( + markdown: string, + { underline = false, latexMath = true }: Md4cFlags = {} +): Promise { + const parse = await initializeParser(); + + const result: unknown = JSON.parse( + parse(markdown, underline ? 1 : 0, latexMath ? 1 : 0) + ); + + if (!isASTNode(result)) { + throw new Error('WASM parser returned invalid AST'); + } + + return result; +} diff --git a/src/web/renderers/BlockRenderers.tsx b/src/web/renderers/BlockRenderers.tsx new file mode 100644 index 00000000..28c5386d --- /dev/null +++ b/src/web/renderers/BlockRenderers.tsx @@ -0,0 +1,91 @@ +import { extractNodeText, filenameFromUrl } from '../utils'; +import type { RendererProps, RendererMap } from '../types'; +import { toHeadingLevel } from '../styles'; +import { KaTeXRenderer } from './KaTeXRenderer'; + +function ParagraphRenderer({ + node, + styles, + parentType, + renderChildren, +}: RendererProps) { + const isImageOnly = + node.children?.length === 1 && node.children[0]?.type === 'Image'; + if (isImageOnly) return <>{renderChildren(node)}; + + if (parentType === 'Blockquote') { + return

      {renderChildren(node)}

      ; + } + + if (parentType === 'ListItem') { + return {renderChildren(node)}; + } + + return

      {renderChildren(node)}

      ; +} + +function HeadingRenderer({ node, styles, renderChildren }: RendererProps) { + const Tag = toHeadingLevel(node.attributes?.level ?? '1'); + return {renderChildren(node)}; +} + +function BlockquoteRenderer({ node, styles, renderChildren }: RendererProps) { + return ( +
      {renderChildren(node)}
      + ); +} + +function CodeBlockRenderer({ node, styles, renderChildren }: RendererProps) { + const language = node.attributes?.language; + const label = language ? `Code block: ${language}` : 'Code block'; + + return ( +
      +      {renderChildren(node)}
      +    
      + ); +} + +function ThematicBreakRenderer({ styles }: RendererProps) { + return
      ; +} + +function ImageRenderer({ node, styles }: RendererProps) { + const url = node.attributes?.url; + if (!url) return null; + + const title = node.attributes?.title; + const alt = extractNodeText(node) || title || filenameFromUrl(url) || 'Image'; + const imgStyle = node.attributes?.isInline + ? styles.inlineImage + : styles.image; + return {alt}; +} + +function LatexMathDisplayRenderer({ + node, + styles, + capabilities, +}: RendererProps) { + const content = extractNodeText(node); + + return ( + + ); +} + +export const blockRenderers: RendererMap = { + Paragraph: ParagraphRenderer, + Heading: HeadingRenderer, + Blockquote: BlockquoteRenderer, + CodeBlock: CodeBlockRenderer, + ThematicBreak: ThematicBreakRenderer, + Image: ImageRenderer, + LatexMathDisplay: LatexMathDisplayRenderer, +}; diff --git a/src/web/renderers/InlineRenderers.tsx b/src/web/renderers/InlineRenderers.tsx new file mode 100644 index 00000000..f25cb2a6 --- /dev/null +++ b/src/web/renderers/InlineRenderers.tsx @@ -0,0 +1,106 @@ +import type { MouseEvent } from 'react'; +import type { RendererProps, RendererMap } from '../types'; +import { extractNodeText } from '../utils'; +import { KaTeXRenderer } from './KaTeXRenderer'; + +function TextRenderer({ node }: RendererProps) { + return <>{node.content ?? ''}; +} + +function LineBreakRenderer(_props: RendererProps) { + return
      ; +} + +function StrongRenderer({ node, styles, renderChildren }: RendererProps) { + return {renderChildren(node)}; +} + +function EmphasisRenderer({ node, styles, renderChildren }: RendererProps) { + return {renderChildren(node)}; +} + +function StrikethroughRenderer({ + node, + styles, + renderChildren, +}: RendererProps) { + return {renderChildren(node)}; +} + +function UnderlineRenderer({ node, styles, renderChildren }: RendererProps) { + return {renderChildren(node)}; +} + +function CodeRenderer({ node, styles, renderChildren }: RendererProps) { + return ( + {node.content ?? renderChildren(node)} + ); +} + +function LinkRenderer({ + node, + styles, + callbacks, + renderChildren, +}: RendererProps) { + const url = node.attributes?.url; + + if (!url) return <>{renderChildren(node)}; + + const handleClick = (event: MouseEvent) => { + if (callbacks.onLinkPress) { + event.preventDefault(); + callbacks.onLinkPress({ url }); + } + }; + + const handleContextMenu = (event: MouseEvent) => { + if (callbacks.onLinkLongPress) { + event.preventDefault(); + callbacks.onLinkLongPress({ url }); + } + }; + + return ( + + {renderChildren(node)} + + ); +} + +function LatexMathInlineRenderer({ + node, + styles, + capabilities, +}: RendererProps) { + const content = extractNodeText(node); + + return ( + + ); +} + +export const inlineRenderers: RendererMap = { + Text: TextRenderer, + LineBreak: LineBreakRenderer, + Strong: StrongRenderer, + Emphasis: EmphasisRenderer, + Strikethrough: StrikethroughRenderer, + Underline: UnderlineRenderer, + Code: CodeRenderer, + Link: LinkRenderer, + LatexMathInline: LatexMathInlineRenderer, +}; diff --git a/src/web/renderers/KaTeXRenderer.tsx b/src/web/renderers/KaTeXRenderer.tsx new file mode 100644 index 00000000..b8076963 --- /dev/null +++ b/src/web/renderers/KaTeXRenderer.tsx @@ -0,0 +1,49 @@ +import { useMemo, type CSSProperties } from 'react'; +import type { KaTeXInstance } from '../katex'; + +interface KaTeXRendererProps { + content: string; + katex: KaTeXInstance | null; + displayMode: boolean; + style: CSSProperties; + fallbackTag: 'code' | 'pre'; +} + +export function KaTeXRenderer({ + content, + katex, + displayMode, + style, + fallbackTag: FallbackTag, +}: KaTeXRendererProps) { + const delimiter = displayMode ? '$$' : '$'; + + const html = useMemo(() => { + if (!katex) return null; + return katex.renderToString(content, { + output: 'mathml', + displayMode, + throwOnError: false, + trust: false, + }); + }, [katex, content, displayMode]); + + if (!html) { + return ( + + {`${delimiter}${content}${delimiter}`} + + ); + } + + const WrapperTag = displayMode ? 'div' : 'span'; + + return ( + + ); +} diff --git a/src/web/renderers/ListRenderers.tsx b/src/web/renderers/ListRenderers.tsx new file mode 100644 index 00000000..dfe69cbc --- /dev/null +++ b/src/web/renderers/ListRenderers.tsx @@ -0,0 +1,110 @@ +import { useState, useEffect } from 'react'; +import { extractNodeText } from '../utils'; +import type { RendererProps, RendererMap, ASTNode, NodeType } from '../types'; +import { listItemStyle, checkedTaskTextStyle } from '../styles'; + +const NESTED_LIST_TYPES: Set = new Set([ + 'UnorderedList', + 'OrderedList', +]); + +function ListRenderer({ + node, + styles, + parentType, + renderChildren, + listTag: ListTag, +}: RendererProps & { listTag: 'ul' | 'ol' }) { + const isNested = parentType === 'ListItem'; + const hasTaskChild = + !isNested && + node.children?.some((child) => child.attributes?.isTask === 'true'); + + const resolvedStyle = isNested + ? styles.listNested + : hasTaskChild + ? styles.listTask + : styles.list; + return {renderChildren(node)}; +} + +function UnorderedListRenderer(props: RendererProps) { + return ; +} + +function OrderedListRenderer(props: RendererProps) { + return ; +} + +function isNestedList(child: ASTNode): boolean { + return NESTED_LIST_TYPES.has(child.type); +} + +function ListItemRenderer({ + node, + style, + styles, + callbacks, + renderChildren, +}: RendererProps) { + const isTask = node.attributes?.isTask === 'true'; + const initialChecked = node.attributes?.taskChecked === 'true'; + const taskText = isTask ? extractNodeText(node) : ''; + const [isChecked, setIsChecked] = useState(initialChecked); + + useEffect(() => { + setIsChecked(initialChecked); + }, [initialChecked]); + + const handleChange = () => { + const taskIndex = node.attributes?.taskIndex; + if (taskIndex === undefined) return; + + const newChecked = !isChecked; + setIsChecked(newChecked); + + callbacks.onTaskListItemPress?.({ + index: taskIndex, + checked: newChecked, + text: taskText, + }); + }; + + const hasNestedList = node.children?.some(isNestedList); + const checkedStyle = + isTask && isChecked ? checkedTaskTextStyle(style) : undefined; + + const inlineNode: ASTNode = hasNestedList + ? { + ...node, + children: node.children?.filter((child) => !isNestedList(child)), + } + : node; + const nestedNode: ASTNode | null = hasNestedList + ? { ...node, children: node.children?.filter(isNestedList) } + : null; + + return ( +
    1. + + {isTask && ( + + )} + {renderChildren(inlineNode)} + + {nestedNode && renderChildren(nestedNode)} +
    2. + ); +} + +export const listRenderers: RendererMap = { + UnorderedList: UnorderedListRenderer, + OrderedList: OrderedListRenderer, + ListItem: ListItemRenderer, +}; diff --git a/src/web/renderers/TableRenderers.tsx b/src/web/renderers/TableRenderers.tsx new file mode 100644 index 00000000..4ea26ebd --- /dev/null +++ b/src/web/renderers/TableRenderers.tsx @@ -0,0 +1,61 @@ +import type { RendererProps, RendererMap } from '../types'; +import { tableBodyRowStyle } from '../styles'; + +function TableRenderer({ node, styles, renderChildren }: RendererProps) { + return ( +
      +
      {renderChildren(node)}
      + + ); +} + +function TableHeadRenderer({ node, renderChildren }: RendererProps) { + return {renderChildren(node)}; +} + +// Renders directly instead of delegating to TableRowRenderer because +// zebra-striping requires the row index, which renderChildren doesn't provide. +function TableBodyRenderer({ node, style, renderChildren }: RendererProps) { + return ( + + {node.children?.map((rowNode, rowIndex) => ( + + {renderChildren(rowNode)} + + ))} + + ); +} + +function TableRowRenderer({ node, renderChildren }: RendererProps) { + return {renderChildren(node)}; +} + +function TableHeaderCellRenderer({ + node, + styles, + renderChildren, +}: RendererProps) { + return ( + + {renderChildren(node)} + + ); +} + +function TableCellRenderer({ node, styles, renderChildren }: RendererProps) { + return ( + + {renderChildren(node)} + + ); +} + +export const tableRenderers: RendererMap = { + Table: TableRenderer, + TableHead: TableHeadRenderer, + TableBody: TableBodyRenderer, + TableRow: TableRowRenderer, + TableHeaderCell: TableHeaderCellRenderer, + TableCell: TableCellRenderer, +}; diff --git a/src/web/renderers/index.tsx b/src/web/renderers/index.tsx new file mode 100644 index 00000000..dca24cf1 --- /dev/null +++ b/src/web/renderers/index.tsx @@ -0,0 +1,75 @@ +import type { ReactNode } from 'react'; +import type { + ASTNode, + NodeType, + RendererCallbacks, + RenderCapabilities, + RendererMap, +} from '../types'; +import type { MarkdownStyleInternal } from '../../types/MarkdownStyleInternal'; +import type { Styles } from '../styles'; +import { blockRenderers } from './BlockRenderers'; +import { inlineRenderers } from './InlineRenderers'; +import { listRenderers } from './ListRenderers'; +import { tableRenderers } from './TableRenderers'; + +function nodeKey(node: ASTNode, index: number): string | number { + if (node.type === 'ListItem' && node.attributes?.isTask === 'true') { + const taskId = node.attributes.taskIndex ?? index; + return `task-${taskId}-${node.attributes.taskChecked}`; + } + return index; +} + +const RENDERERS: RendererMap = { + ...blockRenderers, + ...inlineRenderers, + ...listRenderers, + ...tableRenderers, +}; + +export interface RenderNodeProps { + node: ASTNode; + style: MarkdownStyleInternal; + styles: Styles; + callbacks: RendererCallbacks; + capabilities: RenderCapabilities; + parentType?: NodeType; +} + +export function RenderNode({ + node, + style, + styles, + callbacks, + capabilities, + parentType, +}: RenderNodeProps): ReactNode { + const Renderer = RENDERERS[node.type]; + if (!Renderer) return null; + + const renderChildren = (childNode: ASTNode): ReactNode => + childNode.children?.map((child, index) => ( + + )) ?? null; + + return ( + + ); +} diff --git a/src/web/styles.ts b/src/web/styles.ts new file mode 100644 index 00000000..4d0ff8e0 --- /dev/null +++ b/src/web/styles.ts @@ -0,0 +1,469 @@ +import type { CSSProperties } from 'react'; +import type { + BlockTextAlign, + MarkdownStyleInternal, +} from '../types/MarkdownStyleInternal'; + +const normalizeFontFamily = (value: string): string | undefined => + value || undefined; + +const VALID_FONT_WEIGHTS = new Set([ + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', +]); + +const normalizeFontWeight = (value: string): CSSProperties['fontWeight'] => { + if (!value) return undefined; + if (VALID_FONT_WEIGHTS.has(value)) + return value as CSSProperties['fontWeight']; + return undefined; +}; + +const normalizeTextAlign = ( + value: BlockTextAlign +): CSSProperties['textAlign'] => (value === 'auto' ? undefined : value); + +// 'default' is an AST sentinel for "unspecified column alignment". +function resolveColumnAlign( + align: 'left' | 'center' | 'right' | 'default' | undefined +): 'left' | 'center' | 'right' { + if (align === 'center' || align === 'right') return align; + return 'left'; +} + +export function zeroTrailingMargins( + style: MarkdownStyleInternal +): MarkdownStyleInternal { + return { + ...style, + paragraph: { ...style.paragraph, marginBottom: 0 }, + h1: { ...style.h1, marginBottom: 0 }, + h2: { ...style.h2, marginBottom: 0 }, + h3: { ...style.h3, marginBottom: 0 }, + h4: { ...style.h4, marginBottom: 0 }, + h5: { ...style.h5, marginBottom: 0 }, + h6: { ...style.h6, marginBottom: 0 }, + blockquote: { ...style.blockquote, marginBottom: 0 }, + list: { ...style.list, marginBottom: 0 }, + codeBlock: { ...style.codeBlock, marginBottom: 0 }, + thematicBreak: { ...style.thematicBreak, marginBottom: 0 }, + image: { ...style.image, marginBottom: 0 }, + math: { ...style.math, marginBottom: 0 }, + table: { ...style.table, marginBottom: 0 }, + }; +} + +type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + +export function toHeadingLevel(level: string): HeadingLevel { + const clamped = Math.max(1, Math.min(6, parseInt(level, 10) || 1)); + return `h${clamped}` as HeadingLevel; +} + +type BaseBlock = Pick< + MarkdownStyleInternal['paragraph'], + | 'fontSize' + | 'fontFamily' + | 'fontWeight' + | 'color' + | 'lineHeight' + | 'marginTop' + | 'marginBottom' + | 'textAlign' +>; + +function baseBlock(block: BaseBlock): CSSProperties { + return { + fontSize: block.fontSize, + fontFamily: normalizeFontFamily(block.fontFamily), + fontWeight: normalizeFontWeight(block.fontWeight), + color: block.color, + lineHeight: `${block.lineHeight}px`, + marginTop: block.marginTop, + marginBottom: block.marginBottom, + textAlign: normalizeTextAlign(block.textAlign), + }; +} + +function paragraphStyle(style: MarkdownStyleInternal): CSSProperties { + return baseBlock(style.paragraph); +} + +function paragraphInBlockquoteStyle( + style: MarkdownStyleInternal +): CSSProperties { + return { ...baseBlock(style.paragraph), marginTop: 0, marginBottom: 0 }; +} + +function headingStyle( + style: MarkdownStyleInternal, + level: string +): CSSProperties { + return baseBlock(style[toHeadingLevel(level)]); +} + +function blockquoteStyle(style: MarkdownStyleInternal): CSSProperties { + const blockquote = style.blockquote; + return { + fontSize: blockquote.fontSize, + fontFamily: normalizeFontFamily(blockquote.fontFamily), + fontWeight: normalizeFontWeight(blockquote.fontWeight), + color: blockquote.color, + lineHeight: `${blockquote.lineHeight}px`, + marginTop: blockquote.marginTop, + marginBottom: blockquote.marginBottom, + marginInlineStart: 0, // reset UA default (40px in LTR, auto in RTL) + marginInlineEnd: 0, + paddingInlineStart: blockquote.gapWidth, + borderInlineStart: `${blockquote.borderWidth}px solid ${blockquote.borderColor}`, + backgroundColor: blockquote.backgroundColor, + }; +} + +function listStyle( + style: MarkdownStyleInternal, + isTaskList = false +): CSSProperties { + const list = style.list; + return { + listStylePosition: 'outside', + fontSize: list.fontSize, + fontFamily: normalizeFontFamily(list.fontFamily), + fontWeight: normalizeFontWeight(list.fontWeight), + color: list.color, + lineHeight: `${list.lineHeight}px`, + marginTop: list.marginTop, + marginBottom: list.marginBottom, + paddingInlineStart: isTaskList ? 0 : list.marginLeft, + }; +} + +function codeBlockStyle(style: MarkdownStyleInternal): CSSProperties { + const codeBlock = style.codeBlock; + return { + fontSize: codeBlock.fontSize, + fontFamily: normalizeFontFamily(codeBlock.fontFamily), + fontWeight: normalizeFontWeight(codeBlock.fontWeight), + color: codeBlock.color, + lineHeight: `${codeBlock.lineHeight}px`, + backgroundColor: codeBlock.backgroundColor, + border: `${codeBlock.borderWidth}px solid ${codeBlock.borderColor}`, + borderRadius: codeBlock.borderRadius, + padding: codeBlock.padding, + margin: 0, + marginTop: codeBlock.marginTop, + marginBottom: codeBlock.marginBottom, + overflowX: 'auto', + direction: 'ltr', + }; +} + +function thematicBreakStyle(style: MarkdownStyleInternal): CSSProperties { + const thematicBreak = style.thematicBreak; + return { + border: 'none', // reset UA borders on all sides before drawing only the top + borderTop: `${thematicBreak.height}px solid ${thematicBreak.color}`, + marginTop: thematicBreak.marginTop, + marginBottom: thematicBreak.marginBottom, + width: '100%', //
      as a flex item doesn't auto-stretch — must be explicit + }; +} + +function imageStyle(style: MarkdownStyleInternal): CSSProperties { + const image = style.image; + return { + height: image.height, + borderRadius: image.borderRadius, + marginTop: image.marginTop, + marginBottom: image.marginBottom, + maxWidth: '100%', + display: 'block', + }; +} + +function inlineImageStyle(style: MarkdownStyleInternal): CSSProperties { + const size = style.inlineImage.size; + return { + width: size, + height: size, + verticalAlign: 'middle', + display: 'inline', + }; +} + +function strongStyle(style: MarkdownStyleInternal): CSSProperties { + const strong = style.strong; + return { + fontFamily: normalizeFontFamily(strong.fontFamily), + fontWeight: normalizeFontWeight(strong.fontWeight) || 'bold', + color: strong.color ?? style.paragraph.color, + }; +} + +function emphasisStyle(style: MarkdownStyleInternal): CSSProperties { + const emphasis = style.em; + return { + fontFamily: normalizeFontFamily(emphasis.fontFamily), + fontStyle: emphasis.fontStyle || 'italic', // '' means "inherit default" → fall back to italic + color: emphasis.color ?? style.paragraph.color, + }; +} + +function codeStyle(style: MarkdownStyleInternal): CSSProperties { + const code = style.code; + return { + fontFamily: normalizeFontFamily(code.fontFamily), + fontSize: code.fontSize || undefined, + color: code.color, + backgroundColor: code.backgroundColor, + border: `1px solid ${code.borderColor}`, + borderRadius: 3, + padding: '1px 4px', + direction: 'ltr', + unicodeBidi: 'embed', + }; +} + +function linkStyle(style: MarkdownStyleInternal): CSSProperties { + const link = style.link; + return { + color: link.color, + fontFamily: normalizeFontFamily(link.fontFamily), + textDecoration: link.underline ? 'underline' : 'none', + }; +} + +function strikethroughStyle(style: MarkdownStyleInternal): CSSProperties { + return { + textDecorationLine: 'line-through', + textDecorationColor: style.strikethrough.color, + }; +} + +function underlineStyle(style: MarkdownStyleInternal): CSSProperties { + return { + textDecorationLine: 'underline', + textDecorationColor: style.underline.color, + }; +} + +function mathInlineStyle(style: MarkdownStyleInternal): CSSProperties { + return { color: style.inlineMath.color }; +} + +function mathDisplayStyle(style: MarkdownStyleInternal): CSSProperties { + const math = style.math; + return { + fontSize: math.fontSize, + color: math.color, + backgroundColor: math.backgroundColor, + padding: math.padding, + marginTop: math.marginTop, + marginBottom: math.marginBottom, + textAlign: normalizeTextAlign(math.textAlign), + overflowX: 'auto', + }; +} + +function tableStyle(style: MarkdownStyleInternal): CSSProperties { + const table = style.table; + return { + borderCollapse: 'collapse', + width: '100%', + fontSize: table.fontSize, + fontFamily: normalizeFontFamily(table.fontFamily), + fontWeight: normalizeFontWeight(table.fontWeight), + color: table.color, + lineHeight: `${table.lineHeight}px`, + border: `${table.borderWidth}px solid ${table.borderColor}`, + }; +} + +export function listItemStyle(isTask: boolean): CSSProperties | undefined { + return isTask ? { listStyle: 'none' } : undefined; +} + +export function checkedTaskTextStyle( + style: MarkdownStyleInternal +): CSSProperties { + const taskList = style.taskList; + return { + color: taskList.checkedTextColor || undefined, + textDecorationLine: taskList.checkedStrikethrough + ? 'line-through' + : undefined, + }; +} + +function taskCheckboxStyle(style: MarkdownStyleInternal): CSSProperties { + const taskList = style.taskList; + return { + width: taskList.checkboxSize, + height: taskList.checkboxSize, + borderRadius: taskList.checkboxBorderRadius, + marginInlineEnd: 6, + accentColor: taskList.checkedColor, + verticalAlign: 'middle', + }; +} + +export function tableBodyRowStyle( + style: MarkdownStyleInternal, + rowIndex: number +): CSSProperties { + const table = style.table; + return { + backgroundColor: + rowIndex % 2 === 0 + ? table.rowEvenBackgroundColor + : table.rowOddBackgroundColor, + }; +} + +function tableWrapperStyle(style: MarkdownStyleInternal): CSSProperties { + const table = style.table; + return { + overflowX: 'auto', + overflowY: 'hidden', + marginTop: table.marginTop, + marginBottom: table.marginBottom, + // borderRadius must live on the wrapper, not the — border-collapse: + // collapse causes browsers to ignore border-radius on the table element itself. + borderRadius: table.borderRadius, + }; +} + +function tableHeaderCellStyle( + style: MarkdownStyleInternal, + align: 'left' | 'center' | 'right' | 'default' | undefined +): CSSProperties { + const table = style.table; + return { + backgroundColor: table.headerBackgroundColor, + color: table.headerTextColor, + fontFamily: + normalizeFontFamily(table.headerFontFamily) ?? + normalizeFontFamily(table.fontFamily), + fontWeight: 'bold', + padding: `${table.cellPaddingVertical}px ${table.cellPaddingHorizontal}px`, + border: `${table.borderWidth}px solid ${table.borderColor}`, + textAlign: resolveColumnAlign(align), + }; +} + +function tableCellStyle( + style: MarkdownStyleInternal, + align: 'left' | 'center' | 'right' | 'default' | undefined +): CSSProperties { + const table = style.table; + return { + padding: `${table.cellPaddingVertical}px ${table.cellPaddingHorizontal}px`, + border: `${table.borderWidth}px solid ${table.borderColor}`, + textAlign: resolveColumnAlign(align), + }; +} + +export const parseErrorFallbackStyle: CSSProperties = { + whiteSpace: 'pre-wrap', + margin: 0, +}; + +export interface Styles { + paragraph: CSSProperties; + paragraphInBlockquote: CSSProperties; + h1: CSSProperties; + h2: CSSProperties; + h3: CSSProperties; + h4: CSSProperties; + h5: CSSProperties; + h6: CSSProperties; + blockquote: CSSProperties; + list: CSSProperties; + listNested: CSSProperties; + listTask: CSSProperties; + codeBlock: CSSProperties; + codeBlockFont: CSSProperties; + thematicBreak: CSSProperties; + image: CSSProperties; + inlineImage: CSSProperties; + strong: CSSProperties; + emphasis: CSSProperties; + code: CSSProperties; + link: CSSProperties; + strikethrough: CSSProperties; + underline: CSSProperties; + mathInline: CSSProperties; + mathDisplay: CSSProperties; + table: CSSProperties; + tableWrapper: CSSProperties; + tableHeaderCell: Record; + tableCell: Record; + taskCheckbox: CSSProperties; +} + +type ColumnAlign = 'left' | 'center' | 'right' | 'default'; + +const stylesStore = new WeakMap(); + +export function buildStyles(style: MarkdownStyleInternal): Styles { + const cached = stylesStore.get(style); + if (cached) return cached; + + const codeBlock = codeBlockStyle(style); + const result: Styles = { + paragraph: paragraphStyle(style), + paragraphInBlockquote: paragraphInBlockquoteStyle(style), + h1: headingStyle(style, '1'), + h2: headingStyle(style, '2'), + h3: headingStyle(style, '3'), + h4: headingStyle(style, '4'), + h5: headingStyle(style, '5'), + h6: headingStyle(style, '6'), + blockquote: blockquoteStyle(style), + list: listStyle(style), + listNested: { ...listStyle(style), marginBottom: 0 }, + listTask: listStyle(style, true), + codeBlock, + codeBlockFont: { fontFamily: codeBlock.fontFamily }, + thematicBreak: thematicBreakStyle(style), + image: imageStyle(style), + inlineImage: inlineImageStyle(style), + strong: strongStyle(style), + emphasis: emphasisStyle(style), + code: codeStyle(style), + link: linkStyle(style), + strikethrough: strikethroughStyle(style), + underline: underlineStyle(style), + mathInline: mathInlineStyle(style), + mathDisplay: mathDisplayStyle(style), + table: tableStyle(style), + tableWrapper: tableWrapperStyle(style), + tableHeaderCell: { + left: tableHeaderCellStyle(style, 'left'), + center: tableHeaderCellStyle(style, 'center'), + right: tableHeaderCellStyle(style, 'right'), + default: tableHeaderCellStyle(style, 'default'), + }, + tableCell: { + left: tableCellStyle(style, 'left'), + center: tableCellStyle(style, 'center'), + right: tableCellStyle(style, 'right'), + default: tableCellStyle(style, 'default'), + }, + taskCheckbox: taskCheckboxStyle(style), + }; + + stylesStore.set(style, result); + return result; +} diff --git a/src/web/types.ts b/src/web/types.ts new file mode 100644 index 00000000..3a297c54 --- /dev/null +++ b/src/web/types.ts @@ -0,0 +1,89 @@ +import type { ComponentType, ReactNode } from 'react'; +import type { MarkdownStyleInternal } from '../types/MarkdownStyleInternal'; +import type { Styles } from './styles'; +import type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, +} from '../types/events'; +import type { KaTeXInstance } from './katex'; + +export type NodeType = + | 'Document' + | 'Paragraph' + | 'Text' + | 'Link' + | 'Heading' + | 'LineBreak' + | 'Strong' + | 'Emphasis' + | 'Strikethrough' + | 'Underline' + | 'Code' + | 'Image' + | 'Blockquote' + | 'UnorderedList' + | 'OrderedList' + | 'ListItem' + | 'CodeBlock' + | 'ThematicBreak' + | 'Table' + | 'TableHead' + | 'TableBody' + | 'TableRow' + | 'TableHeaderCell' + | 'TableCell' + | 'LatexMathInline' + | 'LatexMathDisplay'; + +export interface NodeAttributes { + level?: string; + url?: string; + title?: string; + language?: string; + fenceChar?: string; + isTask?: string; + taskChecked?: string; + /** Stamped by indexTaskItems() — not present in the raw WASM output. */ + taskIndex?: number; + /** Stamped by markInlineImages() — not present in the raw WASM output. */ + isInline?: boolean; + colCount?: string; + headRowCount?: string; + bodyRowCount?: string; + align?: 'left' | 'center' | 'right' | 'default'; +} + +export interface ASTNode { + type: NodeType; + /** Present on Text, Code, LatexMathInline, LatexMathDisplay nodes. */ + content?: string; + /** Present on nodes that carry structural metadata (Heading, Link, etc.). */ + attributes?: NodeAttributes; + /** Child nodes; absent on leaf nodes (Text, LineBreak, ThematicBreak). */ + children?: ASTNode[]; +} + +export interface RendererCallbacks { + onLinkPress?: (event: LinkPressEvent) => void; + onLinkLongPress?: (event: LinkLongPressEvent) => void; + onTaskListItemPress?: (event: TaskListItemPressEvent) => void; +} + +export interface RenderCapabilities { + katex: KaTeXInstance | null; +} + +export interface RendererProps { + node: ASTNode; + style: MarkdownStyleInternal; + styles: Styles; + parentType?: NodeType; + callbacks: RendererCallbacks; + capabilities: RenderCapabilities; + renderChildren: (node: ASTNode) => ReactNode; +} + +export type RendererMap = Partial< + Record> +>; diff --git a/src/web/utils.ts b/src/web/utils.ts new file mode 100644 index 00000000..dea6f0d0 --- /dev/null +++ b/src/web/utils.ts @@ -0,0 +1,52 @@ +import type { ASTNode } from './types'; + +/** Recursively collects plain text content from an AST node's subtree. */ +export function extractNodeText(node: ASTNode): string { + if (node.content !== undefined) return node.content; + return node.children?.map(extractNodeText).join('') ?? ''; +} + +/** Extracts the filename from a URL path, without extension. */ +export function filenameFromUrl(url: string): string { + try { + const pathname = new URL(url, 'https://placeholder').pathname; + const filename = pathname.split('/').pop() ?? ''; + return filename.replace(/\.[^.]+$/, ''); + } catch { + return ''; + } +} + +/** + * Stamps each task ListItem with a sequential `taskIndex` matching the order + * native C++ assigns — so onTaskListItemPress.index is correct on web too. + * Mutates the AST in-place (safe: called once on the freshly-parsed result). + */ +export function indexTaskItems(node: ASTNode, counter = { value: 0 }): void { + if (node.type === 'ListItem' && node.attributes?.isTask === 'true') { + node.attributes.taskIndex = counter.value++; + } + node.children?.forEach((child) => indexTaskItems(child, counter)); +} + +/** + * Stamps Image nodes with `isInline` when they appear inside a paragraph + * that also contains other content (text, links, etc.). + * Matches native behavior: sole-child images are block-level, mixed are inline. + * Mutates the AST in-place (safe: called once on the freshly-parsed result). + */ +export function markInlineImages(node: ASTNode): void { + if (node.type === 'Paragraph' && node.children) { + const hasNonImageChild = node.children.some( + (child) => child.type !== 'Image' + ); + if (hasNonImageChild) { + for (const child of node.children) { + if (child.type === 'Image') { + child.attributes = { ...child.attributes, isInline: true }; + } + } + } + } + node.children?.forEach(markInlineImages); +} diff --git a/src/web/wasm/md4c.d.ts b/src/web/wasm/md4c.d.ts new file mode 100644 index 00000000..14535d4a --- /dev/null +++ b/src/web/wasm/md4c.d.ts @@ -0,0 +1,21 @@ +// Type declarations for the Emscripten-generated md4c WASM module. +// The actual md4c.js file is produced by cpp/wasm/build.sh and committed to git. + +interface Md4cModule { + cwrap( + name: string, + returnType: string, + argTypes: string[] + ): (...args: unknown[]) => unknown; + ccall( + name: string, + returnType: string, + argTypes: string[], + args: unknown[] + ): unknown; + UTF8ToString(ptr: number): string; +} + +declare function createMd4cModule(options?: object): Promise; + +export default createMd4cModule; diff --git a/src/web/wasm/md4c.js b/src/web/wasm/md4c.js new file mode 100644 index 00000000..745874c5 Binary files /dev/null and b/src/web/wasm/md4c.js differ diff --git a/yarn.lock b/yarn.lock index 11b3d32e..1d5df6aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,7 +41,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.20.0, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": +"@babel/code-frame@npm:^7.20.0, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": version: 7.29.0 resolution: "@babel/code-frame@npm:7.29.0" dependencies: @@ -89,7 +89,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.24.4": +"@babel/core@npm:^7.20.0, @babel/core@npm:^7.24.4": version: 7.29.0 resolution: "@babel/core@npm:7.29.0" dependencies: @@ -126,29 +126,29 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" +"@babel/generator@npm:^7.20.5, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.29.0, @babel/generator@npm:^7.29.1": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 languageName: node linkType: hard -"@babel/generator@npm:^7.29.0, @babel/generator@npm:^7.29.1": - version: 7.29.1 - resolution: "@babel/generator@npm:7.29.1" +"@babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": + version: 7.28.3 + resolution: "@babel/generator@npm:7.28.3" dependencies: - "@babel/parser": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" + "@babel/parser": "npm:^7.28.3" + "@babel/types": "npm:^7.28.2" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 + checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc languageName: node linkType: hard @@ -204,6 +204,23 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-class-features-plugin@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-create-class-features-plugin@npm:7.28.6" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0b62b46717891f4366006b88c9b7f277980d4f578c4c3789b7a4f5a2e09e121de4cda9a414ab403986745cd3ad1af3fe2d948c9f78ab80d4dc085afc9602af50 + languageName: node + linkType: hard + "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.27.1" @@ -249,17 +266,17 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-module-imports@npm:7.27.1" +"@babel/helper-member-expression-to-functions@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5" dependencies: - "@babel/traverse": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + checksum: 10c0/4e6e05fbf4dffd0bc3e55e28fcaab008850be6de5a7013994ce874ec2beb90619cda4744b11607a60f8aae0227694502908add6188ceb1b5223596e765b44814 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.28.6": +"@babel/helper-module-imports@npm:^7.25.9, @babel/helper-module-imports@npm:^7.28.6": version: 7.28.6 resolution: "@babel/helper-module-imports@npm:7.28.6" dependencies: @@ -269,6 +286,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.3": version: 7.28.3 resolution: "@babel/helper-module-transforms@npm:7.28.3" @@ -344,6 +371,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-replace-supers@npm:7.28.6" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/04663c6389551b99b8c3e7ba4e2638b8ca2a156418c26771516124c53083aa8e74b6a45abe5dd46360af79709a0e9c6b72c076d0eab9efecdd5aaf836e79d8d5 + languageName: node + linkType: hard + "@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" @@ -494,6 +534,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-decorators@npm:^7.12.9": + version: 7.29.0 + resolution: "@babel/plugin-proposal-decorators@npm:7.29.0" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/plugin-syntax-decorators": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b397506fb245374544e2c52909dcbd2193b0327594e3493ea4a47d8a22f6991a90128900d6ee3b129be5246ee08b0d07c8796cfb60502aacf20ac52cc6a92b68 + languageName: node + linkType: hard + "@babel/plugin-proposal-export-default-from@npm:^7.24.7": version: 7.27.1 resolution: "@babel/plugin-proposal-export-default-from@npm:7.27.1" @@ -558,6 +611,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-decorators@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-decorators@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/bd12119646f65e156709d1d6f4949758de36a4192c5c3057b5a5972b896386da5411a763aba087691edf539808616b254b84084b3340cff6e7968f9cab5004dd + languageName: node + linkType: hard + "@babel/plugin-syntax-dynamic-import@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" @@ -745,6 +809,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-typescript@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-typescript@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b0c392a35624883ac480277401ac7d92d8646b66e33639f5d350de7a6723924265985ae11ab9ebd551740ded261c443eaa9a87ea19def9763ca1e0d78c97dea8 + languageName: node + linkType: hard + "@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6" @@ -828,6 +903,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-class-static-block@npm:^7.27.1": + version: 7.28.6 + resolution: "@babel/plugin-transform-class-static-block@npm:7.28.6" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.12.0 + checksum: 10c0/dbe9b1fd302ae41b73186e17ac8d8ecf625ebc2416a91f2dc8013977a1bdf21e6ea288a83f084752b412242f3866e789d4fddeb428af323fe35b60e0fae4f98c + languageName: node + linkType: hard + "@babel/plugin-transform-class-static-block@npm:^7.28.3": version: 7.28.3 resolution: "@babel/plugin-transform-class-static-block@npm:7.28.3" @@ -973,7 +1060,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.27.1": +"@babel/plugin-transform-export-namespace-from@npm:^7.25.9, @babel/plugin-transform-export-namespace-from@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-export-namespace-from@npm:7.27.1" dependencies: @@ -1294,7 +1381,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.24.7, @babel/plugin-transform-react-display-name@npm:^7.27.1": +"@babel/plugin-transform-react-display-name@npm:^7.24.7, @babel/plugin-transform-react-display-name@npm:^7.27.1, @babel/plugin-transform-react-display-name@npm:^7.28.0": version: 7.28.0 resolution: "@babel/plugin-transform-react-display-name@npm:7.28.0" dependencies: @@ -1509,6 +1596,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typescript@npm:^7.28.5": + version: 7.28.6 + resolution: "@babel/plugin-transform-typescript@npm:7.28.6" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/72dbfd3e5f71c4e30445e610758ec0eef65347fafd72bd46f4903733df0d537663a72a81c1626f213a0feab7afc68ba83f1648ffece888dd0868115c9cb748f6 + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-escapes@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.27.1" @@ -1649,6 +1751,22 @@ __metadata: languageName: node linkType: hard +"@babel/preset-react@npm:^7.22.15": + version: 7.28.5 + resolution: "@babel/preset-react@npm:7.28.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-transform-react-display-name": "npm:^7.28.0" + "@babel/plugin-transform-react-jsx": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" + "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0d785e708ff301f4102bd4738b77e550e32f981e54dfd3de1191b4d68306bbb934d2d465fc78a6bc22fff0a6b3ce3195a53984f52755c4349e7264c7e01e8c7c + languageName: node + linkType: hard + "@babel/preset-react@npm:^7.24.7": version: 7.27.1 resolution: "@babel/preset-react@npm:7.27.1" @@ -1665,6 +1783,21 @@ __metadata: languageName: node linkType: hard +"@babel/preset-typescript@npm:^7.23.0": + version: 7.28.5 + resolution: "@babel/preset-typescript@npm:7.28.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" + "@babel/plugin-transform-typescript": "npm:^7.28.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b3d55548854c105085dd80f638147aa8295bc186d70492289242d6c857cb03a6c61ec15186440ea10ed4a71cdde7d495f5eb3feda46273f36b0ac926e8409629 + languageName: node + linkType: hard + "@babel/preset-typescript@npm:^7.24.7": version: 7.27.1 resolution: "@babel/preset-typescript@npm:7.27.1" @@ -1680,6 +1813,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0": + version: 7.29.2 + resolution: "@babel/runtime@npm:7.29.2" + checksum: 10c0/30b80a0140d16467792e1bbeb06f655b0dab70407da38dfac7fedae9c859f9ae9d846ef14ad77bd3814c064295fe9b1bc551f1541ea14646ae9f22b71a8bc17a + languageName: node + linkType: hard + "@babel/runtime@npm:^7.25.0": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" @@ -1709,7 +1849,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": version: 7.28.4 resolution: "@babel/traverse@npm:7.28.4" dependencies: @@ -1749,7 +1889,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": +"@babel/types@npm:^7.25.2, @babel/types@npm:^7.26.0, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": version: 7.29.0 resolution: "@babel/types@npm:7.29.0" dependencies: @@ -2095,6 +2235,91 @@ __metadata: languageName: node linkType: hard +"@expo/cli@npm:55.0.19": + version: 55.0.19 + resolution: "@expo/cli@npm:55.0.19" + dependencies: + "@expo/code-signing-certificates": "npm:^0.0.6" + "@expo/config": "npm:~55.0.11" + "@expo/config-plugins": "npm:~55.0.7" + "@expo/devcert": "npm:^1.2.1" + "@expo/env": "npm:~2.1.1" + "@expo/image-utils": "npm:^0.8.12" + "@expo/json-file": "npm:^10.0.12" + "@expo/log-box": "npm:55.0.8" + "@expo/metro": "npm:~54.2.0" + "@expo/metro-config": "npm:~55.0.11" + "@expo/osascript": "npm:^2.4.2" + "@expo/package-manager": "npm:^1.10.3" + "@expo/plist": "npm:^0.5.2" + "@expo/prebuild-config": "npm:^55.0.11" + "@expo/require-utils": "npm:^55.0.3" + "@expo/router-server": "npm:^55.0.11" + "@expo/schema-utils": "npm:^55.0.2" + "@expo/spawn-async": "npm:^1.7.2" + "@expo/ws-tunnel": "npm:^1.0.1" + "@expo/xcpretty": "npm:^4.4.0" + "@react-native/dev-middleware": "npm:0.83.4" + accepts: "npm:^1.3.8" + arg: "npm:^5.0.2" + better-opn: "npm:~3.0.2" + bplist-creator: "npm:0.1.0" + bplist-parser: "npm:^0.3.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.3.0" + compression: "npm:^1.7.4" + connect: "npm:^3.7.0" + debug: "npm:^4.3.4" + dnssd-advertise: "npm:^1.1.3" + expo-server: "npm:^55.0.6" + fetch-nodeshim: "npm:^0.4.6" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + lan-network: "npm:^0.2.0" + multitars: "npm:^0.2.3" + node-forge: "npm:^1.3.3" + npm-package-arg: "npm:^11.0.0" + ora: "npm:^3.4.0" + picomatch: "npm:^4.0.3" + pretty-format: "npm:^29.7.0" + progress: "npm:^2.0.3" + prompts: "npm:^2.3.2" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.0" + send: "npm:^0.19.0" + slugify: "npm:^1.3.4" + source-map-support: "npm:~0.5.21" + stacktrace-parser: "npm:^0.1.10" + structured-headers: "npm:^0.4.1" + terminal-link: "npm:^2.1.1" + toqr: "npm:^0.1.1" + wrap-ansi: "npm:^7.0.0" + ws: "npm:^8.12.1" + zod: "npm:^3.25.76" + peerDependencies: + expo: "*" + expo-router: "*" + react-native: "*" + peerDependenciesMeta: + expo-router: + optional: true + react-native: + optional: true + bin: + expo-internal: build/bin/cli + checksum: 10c0/eaa8487fc88830d3e1cc86e071d23bd307e2bc094b8198cf3c3ce185fd635f07dbb0f594d0a8cd200134fc82f7e9915189d8ca178edfca8f0a4086219f1037c0 + languageName: node + linkType: hard + +"@expo/code-signing-certificates@npm:^0.0.6": + version: 0.0.6 + resolution: "@expo/code-signing-certificates@npm:0.0.6" + dependencies: + node-forge: "npm:^1.3.3" + checksum: 10c0/3c60be55fb056ccebf7355c1dbe959cee191eaa1c33c6ff5a7331c1ffe1cfa66edc6b62e8005b4a9023bbd40462d81d35284e79eaa8893facb2493801685bbea + languageName: node + linkType: hard + "@expo/config-plugins@npm:^55.0.6": version: 55.0.6 resolution: "@expo/config-plugins@npm:55.0.6" @@ -2116,6 +2341,27 @@ __metadata: languageName: node linkType: hard +"@expo/config-plugins@npm:~55.0.7": + version: 55.0.7 + resolution: "@expo/config-plugins@npm:55.0.7" + dependencies: + "@expo/config-types": "npm:^55.0.5" + "@expo/json-file": "npm:~10.0.12" + "@expo/plist": "npm:^0.5.2" + "@expo/sdk-runtime-versions": "npm:^1.0.0" + chalk: "npm:^4.1.2" + debug: "npm:^4.3.5" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.4" + slugify: "npm:^1.6.6" + xcode: "npm:^3.0.1" + xml2js: "npm:0.6.0" + checksum: 10c0/46cea118d9a780ac367862ef136aafa65063a3bc9a9864ece2a18d4b9e66e64d2bb19f0c0972e07f1106a58203f65671208bebcf5e207953f54217210807746c + languageName: node + linkType: hard + "@expo/config-types@npm:^55.0.5": version: 55.0.5 resolution: "@expo/config-types@npm:55.0.5" @@ -2123,7 +2369,111 @@ __metadata: languageName: node linkType: hard -"@expo/json-file@npm:~10.0.12": +"@expo/config@npm:~55.0.10, @expo/config@npm:~55.0.11": + version: 55.0.11 + resolution: "@expo/config@npm:55.0.11" + dependencies: + "@expo/config-plugins": "npm:~55.0.7" + "@expo/config-types": "npm:^55.0.5" + "@expo/json-file": "npm:^10.0.12" + "@expo/require-utils": "npm:^55.0.3" + deepmerge: "npm:^4.3.1" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + resolve-from: "npm:^5.0.0" + resolve-workspace-root: "npm:^2.0.0" + semver: "npm:^7.6.0" + slugify: "npm:^1.3.4" + checksum: 10c0/7156bf02f23b854c6edde8ccfaabf6c37cde0c81efa4e57feba82ed3cb1dfdd0ca3fc2fc39395016eaad3e78014854bf5507274f6951115eeba4fd4b81a7591f + languageName: node + linkType: hard + +"@expo/devcert@npm:^1.2.1": + version: 1.2.1 + resolution: "@expo/devcert@npm:1.2.1" + dependencies: + "@expo/sudo-prompt": "npm:^9.3.1" + debug: "npm:^3.1.0" + checksum: 10c0/7c5cb4fa74a14702a44b4772a56f27fd191b6cd08988f3da01323f6d592623c80247171b7d66b2c0a32408f48a0814162dbb2764042444887f27e38b89ad1051 + languageName: node + linkType: hard + +"@expo/devtools@npm:55.0.2": + version: 55.0.2 + resolution: "@expo/devtools@npm:55.0.2" + dependencies: + chalk: "npm:^4.1.2" + peerDependencies: + react: "*" + react-native: "*" + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + checksum: 10c0/f247e2a5d2c3129d8b0dbee6daa4a5bca5103d5e3a177257522a89661deb598d84af9805d302cc0af166635604a57fa73b38ff1304b5921b17d0bd372b459686 + languageName: node + linkType: hard + +"@expo/dom-webview@npm:^55.0.3": + version: 55.0.3 + resolution: "@expo/dom-webview@npm:55.0.3" + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: 10c0/db27b9f464042cdbf33458b7523e0f5024c855b4a6a219ade7245a8e44688a3764679375761809d8d1bd7edf7eca1ad8b6ae7a75a1c8720d2fd5c95bc6a041bc + languageName: node + linkType: hard + +"@expo/env@npm:^2.0.11, @expo/env@npm:~2.1.1": + version: 2.1.1 + resolution: "@expo/env@npm:2.1.1" + dependencies: + chalk: "npm:^4.0.0" + debug: "npm:^4.3.4" + getenv: "npm:^2.0.0" + checksum: 10c0/c863fb05f16e0ffaac10ba0e5f632472c94ff755e5bfea1ce31820a17efc21dc932ccf8d307793187c752e85e151fe0579cc9038db5abc12f4b650174b182cbe + languageName: node + linkType: hard + +"@expo/fingerprint@npm:0.16.6": + version: 0.16.6 + resolution: "@expo/fingerprint@npm:0.16.6" + dependencies: + "@expo/env": "npm:^2.0.11" + "@expo/spawn-async": "npm:^1.7.2" + arg: "npm:^5.0.2" + chalk: "npm:^4.1.2" + debug: "npm:^4.3.4" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + ignore: "npm:^5.3.1" + minimatch: "npm:^10.2.2" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.0" + bin: + fingerprint: bin/cli.js + checksum: 10c0/18f597e71aa2fa75ca72c59f81a825df5a0262b27d82b948b2d2a6edd11fa7a3a7f6daccef9d4d92224577631a1a15afd02ce7e0568a8a6db4738704706906fe + languageName: node + linkType: hard + +"@expo/image-utils@npm:^0.8.12": + version: 0.8.12 + resolution: "@expo/image-utils@npm:0.8.12" + dependencies: + "@expo/spawn-async": "npm:^1.7.2" + chalk: "npm:^4.0.0" + getenv: "npm:^2.0.0" + jimp-compact: "npm:0.16.1" + parse-png: "npm:^2.1.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.0" + checksum: 10c0/f9ea7b8ac746602e824e6f5005242a400fce59f776caed05d27e3aa8a8354059ce44d0c3d50f6c1aa4e3256282f504150d0ea62c86e6cae5bacc626d530a35f6 + languageName: node + linkType: hard + +"@expo/json-file@npm:^10.0.12, @expo/json-file@npm:~10.0.12": version: 10.0.12 resolution: "@expo/json-file@npm:10.0.12" dependencies: @@ -2133,6 +2483,118 @@ __metadata: languageName: node linkType: hard +"@expo/local-build-cache-provider@npm:55.0.7": + version: 55.0.7 + resolution: "@expo/local-build-cache-provider@npm:55.0.7" + dependencies: + "@expo/config": "npm:~55.0.10" + chalk: "npm:^4.1.2" + checksum: 10c0/cccc1fb130333c7f202b21208381194e5c0699267bf7e011b5d550d53b8f3005347bcce6234f1bc7a991d25786e5c1134005cc889a1fb55fe0621215cf7f419d + languageName: node + linkType: hard + +"@expo/log-box@npm:55.0.8": + version: 55.0.8 + resolution: "@expo/log-box@npm:55.0.8" + dependencies: + "@expo/dom-webview": "npm:^55.0.3" + anser: "npm:^1.4.9" + stacktrace-parser: "npm:^0.1.10" + peerDependencies: + "@expo/dom-webview": ^55.0.3 + expo: "*" + react: "*" + react-native: "*" + checksum: 10c0/7e7353c106d1368227295a5890d3fabf1b93f125b9849acf0b5788dd2a9849318af632096645c3f68da745d23066cc02704af7879c112ab4b4edf4e52ec6954d + languageName: node + linkType: hard + +"@expo/metro-config@npm:55.0.11, @expo/metro-config@npm:~55.0.11": + version: 55.0.11 + resolution: "@expo/metro-config@npm:55.0.11" + dependencies: + "@babel/code-frame": "npm:^7.20.0" + "@babel/core": "npm:^7.20.0" + "@babel/generator": "npm:^7.20.5" + "@expo/config": "npm:~55.0.10" + "@expo/env": "npm:~2.1.1" + "@expo/json-file": "npm:~10.0.12" + "@expo/metro": "npm:~54.2.0" + "@expo/spawn-async": "npm:^1.7.2" + browserslist: "npm:^4.25.0" + chalk: "npm:^4.1.0" + debug: "npm:^4.3.2" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + hermes-parser: "npm:^0.32.0" + jsc-safe-url: "npm:^0.2.4" + lightningcss: "npm:^1.30.1" + picomatch: "npm:^4.0.3" + postcss: "npm:~8.4.32" + resolve-from: "npm:^5.0.0" + peerDependencies: + expo: "*" + peerDependenciesMeta: + expo: + optional: true + checksum: 10c0/942bfef59f722bd5fb11e91d2fe273ce5f478f6f6de1ad98a929d738bd3f251a02bc9c1c046f00cc05e914b27026a52ec6f21c0422382cee4a85bef95540860b + languageName: node + linkType: hard + +"@expo/metro-runtime@npm:~5.0.0": + version: 5.0.5 + resolution: "@expo/metro-runtime@npm:5.0.5" + peerDependencies: + react-native: "*" + checksum: 10c0/fba80998a17b46ea21079fe416ca2a3ece665e19c3d55ccc46e3dac7f6f20e86d7e898e4fab1145deb1620abcfa3dbcc4da3ebba9324c3e7d2390803d1de9af3 + languageName: node + linkType: hard + +"@expo/metro@npm:~54.2.0": + version: 54.2.0 + resolution: "@expo/metro@npm:54.2.0" + dependencies: + metro: "npm:0.83.3" + metro-babel-transformer: "npm:0.83.3" + metro-cache: "npm:0.83.3" + metro-cache-key: "npm:0.83.3" + metro-config: "npm:0.83.3" + metro-core: "npm:0.83.3" + metro-file-map: "npm:0.83.3" + metro-minify-terser: "npm:0.83.3" + metro-resolver: "npm:0.83.3" + metro-runtime: "npm:0.83.3" + metro-source-map: "npm:0.83.3" + metro-symbolicate: "npm:0.83.3" + metro-transform-plugins: "npm:0.83.3" + metro-transform-worker: "npm:0.83.3" + checksum: 10c0/5114ac19021094e19fcbd383778748451bdf78c904cb9be831b04d44880b4ca05071c1e045e5ccf8076418e32a87de2e5163529f1d91fed4bdda2184958e8a61 + languageName: node + linkType: hard + +"@expo/osascript@npm:^2.4.2": + version: 2.4.2 + resolution: "@expo/osascript@npm:2.4.2" + dependencies: + "@expo/spawn-async": "npm:^1.7.2" + checksum: 10c0/80adc04b4a6f0695d00a88dcfe3336b395d6431fdccb9e8316c2ec1819ae6524a7063d7c8f4da7f1f3718e57637204c62c2383b7488b0008410efeb7108aa00f + languageName: node + linkType: hard + +"@expo/package-manager@npm:^1.10.3": + version: 1.10.3 + resolution: "@expo/package-manager@npm:1.10.3" + dependencies: + "@expo/json-file": "npm:^10.0.12" + "@expo/spawn-async": "npm:^1.7.2" + chalk: "npm:^4.0.0" + npm-package-arg: "npm:^11.0.0" + ora: "npm:^3.4.0" + resolve-workspace-root: "npm:^2.0.0" + checksum: 10c0/b9e6071b9f29f20ef4aae06390c207f22b17eced1fa2d77903100ab7efefe0951a8735dee997ac434550938d939d132a5b1f3f35344bfe9e40344c090d0ebedc + languageName: node + linkType: hard + "@expo/plist@npm:^0.5.2": version: 0.5.2 resolution: "@expo/plist@npm:0.5.2" @@ -2144,6 +2606,77 @@ __metadata: languageName: node linkType: hard +"@expo/prebuild-config@npm:^55.0.11": + version: 55.0.11 + resolution: "@expo/prebuild-config@npm:55.0.11" + dependencies: + "@expo/config": "npm:~55.0.11" + "@expo/config-plugins": "npm:~55.0.7" + "@expo/config-types": "npm:^55.0.5" + "@expo/image-utils": "npm:^0.8.12" + "@expo/json-file": "npm:^10.0.12" + "@react-native/normalize-colors": "npm:0.83.4" + debug: "npm:^4.3.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.0" + xml2js: "npm:0.6.0" + peerDependencies: + expo: "*" + checksum: 10c0/e8cffdd29467c3b33deeb911079231253ac258399ac01c2a9765765753af79287233e6351fc1dff7c77fc61590f92353286f764591e421863e25eb5da8e063e6 + languageName: node + linkType: hard + +"@expo/require-utils@npm:^55.0.3": + version: 55.0.3 + resolution: "@expo/require-utils@npm:55.0.3" + dependencies: + "@babel/code-frame": "npm:^7.20.0" + "@babel/core": "npm:^7.25.2" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + peerDependencies: + typescript: ^5.0.0 || ^5.0.0-0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/562a2dc71f983fba2215295bbcc376d6911217c3a98b96484331112ff98c7a2e979fb1904ac293008ac557114c6658c1deb9b2f441bd246764b507103d2560cd + languageName: node + linkType: hard + +"@expo/router-server@npm:^55.0.11": + version: 55.0.11 + resolution: "@expo/router-server@npm:55.0.11" + dependencies: + debug: "npm:^4.3.4" + peerDependencies: + "@expo/metro-runtime": ^55.0.6 + expo: "*" + expo-constants: ^55.0.9 + expo-font: ^55.0.4 + expo-router: "*" + expo-server: ^55.0.6 + react: "*" + react-dom: "*" + react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 + peerDependenciesMeta: + "@expo/metro-runtime": + optional: true + expo-router: + optional: true + react-dom: + optional: true + react-server-dom-webpack: + optional: true + checksum: 10c0/54db0a2f3b4d07ddf58ebadd77b2a5d67c54523044c3cf9dac4a078d73795a3450f5d1c3f3a15b2a2a6c0c3eb5fdb11f482ffcbd25fd3a1f481242c3de3f575b + languageName: node + linkType: hard + +"@expo/schema-utils@npm:^55.0.2": + version: 55.0.2 + resolution: "@expo/schema-utils@npm:55.0.2" + checksum: 10c0/0b443cd733f078a34ef6419f0051073f7333c338e108ca13509a728ae8c20dacfd7bba92cbe152e4bdb45f92bbc58290d3041f9a21cbd8518908708b10ccb3ad + languageName: node + linkType: hard + "@expo/sdk-runtime-versions@npm:^1.0.0": version: 1.0.0 resolution: "@expo/sdk-runtime-versions@npm:1.0.0" @@ -2151,6 +2684,53 @@ __metadata: languageName: node linkType: hard +"@expo/spawn-async@npm:^1.7.2": + version: 1.7.2 + resolution: "@expo/spawn-async@npm:1.7.2" + dependencies: + cross-spawn: "npm:^7.0.3" + checksum: 10c0/0548c4e95ee39393c2f3919bc605f21eba4f0a8ba66fa82fbbc4b1b624e0054526918489227b924f03af5bc156a011f39a2472c223c0d2237fb7afd8dedd5357 + languageName: node + linkType: hard + +"@expo/sudo-prompt@npm:^9.3.1": + version: 9.3.2 + resolution: "@expo/sudo-prompt@npm:9.3.2" + checksum: 10c0/032652bf1c3f326c9c194f336de5821b9ece9d48b22e3e277950d939fcd728c85459680a9771705904d375f128221cca2e1e91c5d7a85cf3c07fe6f88c361e9d + languageName: node + linkType: hard + +"@expo/vector-icons@npm:^15.0.2": + version: 15.1.1 + resolution: "@expo/vector-icons@npm:15.1.1" + peerDependencies: + expo-font: ">=14.0.4" + react: "*" + react-native: "*" + checksum: 10c0/fdd50c90484934204b90d345904fa69ca788a5de704d62b3cb580c52aa4cf4bffe1c05c509d043cf2b6842f1bdad6b78585524f3c6ae5f77e36385d83c0aa577 + languageName: node + linkType: hard + +"@expo/ws-tunnel@npm:^1.0.1": + version: 1.0.6 + resolution: "@expo/ws-tunnel@npm:1.0.6" + checksum: 10c0/050eb7fbd54b636c97c818e7ec5402ce616cae655290386a51600b200947e281cdd12d182251c07fab449e11a732135d61429b738cd03945e94757061e652ecd + languageName: node + linkType: hard + +"@expo/xcpretty@npm:^4.4.0": + version: 4.4.1 + resolution: "@expo/xcpretty@npm:4.4.1" + dependencies: + "@babel/code-frame": "npm:^7.20.0" + chalk: "npm:^4.1.0" + js-yaml: "npm:^4.1.0" + bin: + excpretty: build/cli.js + checksum: 10c0/23bfd12b54bb296284402a4c547a73874b0ed4fa5f5dea26d5f80525c29befe40edb79df921fb3fd783cf0008779b29b7d4d606f2540cc23f96e39cbdc0b21dd + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -3582,6 +4162,16 @@ __metadata: languageName: node linkType: hard +"@react-native/babel-plugin-codegen@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/babel-plugin-codegen@npm:0.83.4" + dependencies: + "@babel/traverse": "npm:^7.25.3" + "@react-native/codegen": "npm:0.83.4" + checksum: 10c0/f33af98ee3256e6ab1f8b4828c00ec92fa5f10ceeba22336fd4cd837525c348c8d595709902785bd38f8e8bd8a45c0a4d518d7e9f1d8c4acf8e207995f54fb12 + languageName: node + linkType: hard + "@react-native/babel-plugin-codegen@npm:0.84.1": version: 0.84.1 resolution: "@react-native/babel-plugin-codegen@npm:0.84.1" @@ -3592,9 +4182,64 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.81.2": - version: 0.81.2 - resolution: "@react-native/babel-preset@npm:0.81.2" +"@react-native/babel-preset@npm:0.81.2": + version: 0.81.2 + resolution: "@react-native/babel-preset@npm:0.81.2" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" + "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" + "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" + "@babel/plugin-transform-block-scoping": "npm:^7.25.0" + "@babel/plugin-transform-class-properties": "npm:^7.25.4" + "@babel/plugin-transform-classes": "npm:^7.25.4" + "@babel/plugin-transform-computed-properties": "npm:^7.24.7" + "@babel/plugin-transform-destructuring": "npm:^7.24.8" + "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-for-of": "npm:^7.24.7" + "@babel/plugin-transform-function-name": "npm:^7.25.1" + "@babel/plugin-transform-literals": "npm:^7.25.2" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" + "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" + "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" + "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-react-display-name": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx": "npm:^7.25.2" + "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" + "@babel/plugin-transform-regenerator": "npm:^7.24.7" + "@babel/plugin-transform-runtime": "npm:^7.24.7" + "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" + "@babel/plugin-transform-spread": "npm:^7.24.7" + "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" + "@babel/plugin-transform-typescript": "npm:^7.25.2" + "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" + "@babel/template": "npm:^7.25.0" + "@react-native/babel-plugin-codegen": "npm:0.81.2" + babel-plugin-syntax-hermes-parser: "npm:0.29.1" + babel-plugin-transform-flow-enums: "npm:^0.0.2" + react-refresh: "npm:^0.14.0" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/eacc54187dab167c3045078a93dc8b92a2d56cf06982b05e53fd416ad21b4612f18ff222e4b7f9ee8f6e335c961cd85629ba80d333456dc5663c2569e8a56a6c + languageName: node + linkType: hard + +"@react-native/babel-preset@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/babel-preset@npm:0.83.4" dependencies: "@babel/core": "npm:^7.25.2" "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" @@ -3637,13 +4282,13 @@ __metadata: "@babel/plugin-transform-typescript": "npm:^7.25.2" "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" "@babel/template": "npm:^7.25.0" - "@react-native/babel-plugin-codegen": "npm:0.81.2" - babel-plugin-syntax-hermes-parser: "npm:0.29.1" + "@react-native/babel-plugin-codegen": "npm:0.83.4" + babel-plugin-syntax-hermes-parser: "npm:0.32.0" babel-plugin-transform-flow-enums: "npm:^0.0.2" react-refresh: "npm:^0.14.0" peerDependencies: "@babel/core": "*" - checksum: 10c0/eacc54187dab167c3045078a93dc8b92a2d56cf06982b05e53fd416ad21b4612f18ff222e4b7f9ee8f6e335c961cd85629ba80d333456dc5663c2569e8a56a6c + checksum: 10c0/1c1e80d77b513b0762426a207fbcf04335bc41afe077d48b95a304f3a31dcf317d16ad1a6ad7badf04fc5aa7058365db40d3daf8a2503fe16b9e58a7080b7946 languageName: node linkType: hard @@ -3724,6 +4369,23 @@ __metadata: languageName: node linkType: hard +"@react-native/codegen@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/codegen@npm:0.83.4" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/parser": "npm:^7.25.3" + glob: "npm:^7.1.1" + hermes-parser: "npm:0.32.0" + invariant: "npm:^2.2.4" + nullthrows: "npm:^1.1.1" + yargs: "npm:^17.6.2" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/00b781097fece80cf004f8156ae0d0e24936bfad87bf1305c2d8c946b7bb18f924b0a19697d041a9ac5df3af765c62139cfabd9994d040fc60f79734adab471e + languageName: node + linkType: hard + "@react-native/codegen@npm:0.84.1": version: 0.84.1 resolution: "@react-native/codegen@npm:0.84.1" @@ -3794,6 +4456,13 @@ __metadata: languageName: node linkType: hard +"@react-native/debugger-frontend@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/debugger-frontend@npm:0.83.4" + checksum: 10c0/b2ed3a317a471c14e0ae1c366d81b29b8bf3b4bf98a4e1903d413d04882522df4a6295d6003031dce849cb223de89308575474e3df26a4378cee3e5b15b85e33 + languageName: node + linkType: hard + "@react-native/debugger-frontend@npm:0.84.1": version: 0.84.1 resolution: "@react-native/debugger-frontend@npm:0.84.1" @@ -3801,6 +4470,16 @@ __metadata: languageName: node linkType: hard +"@react-native/debugger-shell@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/debugger-shell@npm:0.83.4" + dependencies: + cross-spawn: "npm:^7.0.6" + fb-dotslash: "npm:0.5.8" + checksum: 10c0/b51ee53b023bbc00e114dd1234c4472646221802ed2cb6c1aeb35ddb43020a71ec9dc724f6f9925d0ca93d01d6c1f5198325a7ccb64186271cfd9d99814c77a3 + languageName: node + linkType: hard + "@react-native/debugger-shell@npm:0.84.1": version: 0.84.1 resolution: "@react-native/debugger-shell@npm:0.84.1" @@ -3831,6 +4510,26 @@ __metadata: languageName: node linkType: hard +"@react-native/dev-middleware@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/dev-middleware@npm:0.83.4" + dependencies: + "@isaacs/ttlcache": "npm:^1.4.1" + "@react-native/debugger-frontend": "npm:0.83.4" + "@react-native/debugger-shell": "npm:0.83.4" + chrome-launcher: "npm:^0.15.2" + chromium-edge-launcher: "npm:^0.2.0" + connect: "npm:^3.6.5" + debug: "npm:^4.4.0" + invariant: "npm:^2.2.4" + nullthrows: "npm:^1.1.1" + open: "npm:^7.0.3" + serve-static: "npm:^1.16.2" + ws: "npm:^7.5.10" + checksum: 10c0/bd2237771e70cb3b4ee1bf84e099df2fa9d9343812db4e847e2b498f854ab82789c41f649e47bb4da767b22ecdbac8806834f9440f41d5957d53409676069d24 + languageName: node + linkType: hard + "@react-native/dev-middleware@npm:0.84.1": version: 0.84.1 resolution: "@react-native/dev-middleware@npm:0.84.1" @@ -3989,6 +4688,13 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-colors@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/normalize-colors@npm:0.83.4" + checksum: 10c0/0a6cc6c6136872606a35b7a214ea6d320135b220fd220e16b5ca7a74b244938b48c9df1ae422046c56f6779c64914cb7e979873de5cf60a410f9a5261c28b4ba + languageName: node + linkType: hard + "@react-native/normalize-colors@npm:0.84.1": version: 0.84.1 resolution: "@react-native/normalize-colors@npm:0.84.1" @@ -3996,6 +4702,13 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-colors@npm:^0.74.1": + version: 0.74.89 + resolution: "@react-native/normalize-colors@npm:0.74.89" + checksum: 10c0/6d0e5c91793ca5a66b4a0e5995361f474caacac56bde4772ac02b8ab470bd323076c567bd8856b0b097816d2b890e73a4040a3df01fd284adee683f5ba89d5ba + languageName: node + linkType: hard + "@react-native/typescript-config@npm:0.84.1": version: 0.84.1 resolution: "@react-native/typescript-config@npm:0.84.1" @@ -4419,6 +5132,13 @@ __metadata: languageName: node linkType: hard +"@ungap/structured-clone@npm:^1.3.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a + languageName: node + linkType: hard + "@vscode/sudo-prompt@npm:^9.0.0": version: 9.3.1 resolution: "@vscode/sudo-prompt@npm:9.3.1" @@ -4461,6 +5181,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:^1.3.7, accepts@npm:^1.3.8, accepts@npm:~1.3.7": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + "accepts@npm:^2.0.0": version: 2.0.0 resolution: "accepts@npm:2.0.0" @@ -4471,16 +5201,6 @@ __metadata: languageName: node linkType: hard -"accepts@npm:~1.3.7": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 - languageName: node - linkType: hard - "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -4595,7 +5315,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.0": +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" dependencies: @@ -4644,6 +5364,13 @@ __metadata: languageName: node linkType: hard +"arg@npm:^5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e + languageName: node + linkType: hard + "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -4777,7 +5504,7 @@ __metadata: languageName: node linkType: hard -"asap@npm:~2.0.6": +"asap@npm:~2.0.3, asap@npm:~2.0.6": version: 2.0.6 resolution: "asap@npm:2.0.6" checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d @@ -4924,6 +5651,22 @@ __metadata: languageName: node linkType: hard +"babel-plugin-react-compiler@npm:^1.0.0": + version: 1.0.0 + resolution: "babel-plugin-react-compiler@npm:1.0.0" + dependencies: + "@babel/types": "npm:^7.26.0" + checksum: 10c0/9406267ada8d7dbdfe8906b40ecadb816a5f4cee2922bee23f7729293b369624ee135b5a9b0f263851c263c9787522ac5d97016c9a2b82d1668300e42b18aff8 + languageName: node + linkType: hard + +"babel-plugin-react-native-web@npm:~0.21.0": + version: 0.21.2 + resolution: "babel-plugin-react-native-web@npm:0.21.2" + checksum: 10c0/45fa9b2fce90cb0d962bbc9c665e944ef6720f5740a573d457adf8e2881bd4112396922d5d5c0ab7cfc706f0c457e3edebddc55289d30924e1f42b4b7d849b8e + languageName: node + linkType: hard + "babel-plugin-syntax-hermes-parser@npm:0.29.1": version: 0.29.1 resolution: "babel-plugin-syntax-hermes-parser@npm:0.29.1" @@ -4951,6 +5694,15 @@ __metadata: languageName: node linkType: hard +"babel-plugin-syntax-hermes-parser@npm:^0.32.0": + version: 0.32.1 + resolution: "babel-plugin-syntax-hermes-parser@npm:0.32.1" + dependencies: + hermes-parser: "npm:0.32.1" + checksum: 10c0/b254a2a324cf823c9ec749de0019cf787d59102e9bdd79fc687937e631574ba44f7d249954e284997f1ada1a2b9a1ffa87bc10b16f7e81869b767f99a978b2cf + languageName: node + linkType: hard + "babel-plugin-transform-flow-enums@npm:^0.0.2": version: 0.0.2 resolution: "babel-plugin-transform-flow-enums@npm:0.0.2" @@ -4985,6 +5737,49 @@ __metadata: languageName: node linkType: hard +"babel-preset-expo@npm:~55.0.13": + version: 55.0.13 + resolution: "babel-preset-expo@npm:55.0.13" + dependencies: + "@babel/generator": "npm:^7.20.5" + "@babel/helper-module-imports": "npm:^7.25.9" + "@babel/plugin-proposal-decorators": "npm:^7.12.9" + "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-transform-class-static-block": "npm:^7.27.1" + "@babel/plugin-transform-export-namespace-from": "npm:^7.25.9" + "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-runtime": "npm:^7.24.7" + "@babel/preset-react": "npm:^7.22.15" + "@babel/preset-typescript": "npm:^7.23.0" + "@react-native/babel-preset": "npm:0.83.4" + babel-plugin-react-compiler: "npm:^1.0.0" + babel-plugin-react-native-web: "npm:~0.21.0" + babel-plugin-syntax-hermes-parser: "npm:^0.32.0" + babel-plugin-transform-flow-enums: "npm:^0.0.2" + debug: "npm:^4.3.4" + resolve-from: "npm:^5.0.0" + peerDependencies: + "@babel/runtime": ^7.20.0 + expo: "*" + expo-widgets: ^55.0.8 + react-refresh: ">=0.14.0 <1.0.0" + peerDependenciesMeta: + "@babel/runtime": + optional: true + expo: + optional: true + expo-widgets: + optional: true + checksum: 10c0/458b22d04634a87fe0f10d3a96515eb948a634a41a1981d0775d8a34f030dcce5511ead42ab11d9ceaf9ee2343afc8a603360f9b42333ef64ca081b5e7bd076a + languageName: node + linkType: hard + "babel-preset-jest@npm:^29.6.3": version: 29.6.3 resolution: "babel-preset-jest@npm:29.6.3" @@ -5027,6 +5822,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.0": + version: 2.10.12 + resolution: "baseline-browser-mapping@npm:2.10.12" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/391d354240160546c8248317698b61f21f287cc6444766414c2d299a8880045e605ed97e8d8cd198a0b9dfaa4e73c2fa765bbef089474533a904733b1dc9a363 + languageName: node + linkType: hard + "basic-ftp@npm:^5.0.2": version: 5.0.5 resolution: "basic-ftp@npm:5.0.5" @@ -5041,6 +5845,15 @@ __metadata: languageName: node linkType: hard +"better-opn@npm:~3.0.2": + version: 3.0.2 + resolution: "better-opn@npm:3.0.2" + dependencies: + open: "npm:^8.0.4" + checksum: 10c0/911ef25d44da75aabfd2444ce7a4294a8000ebcac73068c04a60298b0f7c7506b60421aa4cd02ac82502fb42baaff7e4892234b51e6923eded44c5a11185f2f5 + languageName: node + linkType: hard + "big-integer@npm:1.6.x": version: 1.6.52 resolution: "big-integer@npm:1.6.52" @@ -5079,6 +5892,15 @@ __metadata: languageName: node linkType: hard +"bplist-creator@npm:0.1.0": + version: 0.1.0 + resolution: "bplist-creator@npm:0.1.0" + dependencies: + stream-buffers: "npm:2.2.x" + checksum: 10c0/86f5fe95f34abd369b381abf0f726e220ecebd60a3d932568ae94895ccf1989a87553e4aee9ab3cfb4f35e6f72319f52aa73028165eec82819ed39f15189d493 + languageName: node + linkType: hard + "bplist-creator@npm:0.1.1": version: 0.1.1 resolution: "bplist-creator@npm:0.1.1" @@ -5088,7 +5910,7 @@ __metadata: languageName: node linkType: hard -"bplist-parser@npm:0.3.2": +"bplist-parser@npm:0.3.2, bplist-parser@npm:^0.3.1": version: 0.3.2 resolution: "bplist-parser@npm:0.3.2" dependencies: @@ -5149,6 +5971,21 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.25.0": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -5296,6 +6133,24 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001781 + resolution: "caniuse-lite@npm:1.0.30001781" + checksum: 10c0/79e77d8759a55e90f0f5db96ab9e7925c7b2e3021f77852e647e45f64f7dc701954174188438e84b810824afc16d706c64a38f20f9c1ed9ac174b6362d33325f + languageName: node + linkType: hard + +"chalk@npm:^2.0.1, chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + "chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -5378,7 +6233,7 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.2.0": +"ci-info@npm:^3.2.0, ci-info@npm:^3.3.0": version: 3.9.0 resolution: "ci-info@npm:3.9.0" checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a @@ -5430,6 +6285,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-cursor@npm:2.1.0" + dependencies: + restore-cursor: "npm:^2.0.0" + checksum: 10c0/09ee6d8b5b818d840bf80ec9561eaf696672197d3a02a7daee2def96d5f52ce6e0bbe7afca754ccf14f04830b5a1b4556273e983507d5029f95bba3016618eda + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -5448,7 +6312,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.0.0, cli-spinners@npm:^2.5.0": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 @@ -5572,6 +6436,20 @@ __metadata: languageName: node linkType: hard +"commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10c0/8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + "commander@npm:^9.4.1": version: 9.5.0 resolution: "commander@npm:9.5.0" @@ -5610,7 +6488,7 @@ __metadata: languageName: node linkType: hard -"compression@npm:^1.7.1": +"compression@npm:^1.7.1, compression@npm:^1.7.4": version: 1.8.1 resolution: "compression@npm:1.8.1" dependencies: @@ -5651,7 +6529,7 @@ __metadata: languageName: node linkType: hard -"connect@npm:^3.6.5": +"connect@npm:^3.6.5, connect@npm:^3.7.0": version: 3.7.0 resolution: "connect@npm:3.7.0" dependencies: @@ -5932,6 +6810,15 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:^3.1.5": + version: 3.2.0 + resolution: "cross-fetch@npm:3.2.0" + dependencies: + node-fetch: "npm:^2.7.0" + checksum: 10c0/d8596adf0269130098a676f6739a0922f3cc7b71cc89729925411ebe851a87026171c82ea89154c4811c9867c01c44793205a52e618ce2684650218c7fbeeb9f + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -5943,6 +6830,15 @@ __metadata: languageName: node linkType: hard +"css-in-js-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "css-in-js-utils@npm:3.1.0" + dependencies: + hyphenate-style-name: "npm:^1.0.3" + checksum: 10c0/8bb042e8f7701a7edadc3cce5ce2d5cf41189631d7e2aed194d5a7059b25776dded2a0466cb9da1d1f3fc6c99dcecb51e45671148d073b8a2a71e34755152e52 + languageName: node + linkType: hard + "csstype@npm:^3.2.2": version: 3.2.3 resolution: "csstype@npm:3.2.3" @@ -6025,6 +6921,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:^3.1.0": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: "npm:^2.1.1" + checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a + languageName: node + linkType: hard + "decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" @@ -6058,7 +6963,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0": +"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0, deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 @@ -6102,6 +7007,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 + languageName: node + linkType: hard + "define-lazy-prop@npm:^3.0.0": version: 3.0.0 resolution: "define-lazy-prop@npm:3.0.0" @@ -6182,7 +7094,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c @@ -6203,6 +7115,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -6226,6 +7145,13 @@ __metadata: languageName: node linkType: hard +"dnssd-advertise@npm:^1.1.3": + version: 1.1.4 + resolution: "dnssd-advertise@npm:1.1.4" + checksum: 10c0/7a875a206f1d08ad74683b73b2399361b4cc15ff855f4d7831c40375e0f582609ca35a0b7dc55f5b8055efe615fa70d80e057a32e81278d97a81ed362149b3e3 + languageName: node + linkType: hard + "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -6283,6 +7209,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.263": + version: 1.5.328 + resolution: "electron-to-chromium@npm:1.5.328" + checksum: 10c0/284a642ee800f5e1968696a3b00dcc070f556b6a49d0a7df1aa8cf95558344a300724356c060e911c238a683c30ee01596cefc4664744f6a565555c4238b72e6 + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -6973,16 +7906,157 @@ __metadata: languageName: node linkType: hard -"expect@npm:^29.0.0, expect@npm:^29.7.0": - version: 29.7.0 - resolution: "expect@npm:29.7.0" - dependencies: - "@jest/expect-utils": "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + +"expo-asset@npm:~55.0.10": + version: 55.0.10 + resolution: "expo-asset@npm:55.0.10" + dependencies: + "@expo/image-utils": "npm:^0.8.12" + expo-constants: "npm:~55.0.9" + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: 10c0/9e259620dfca1801719cb42de95b91924dd8d760ac2aea51b8454949d158dc974b91ab6a1306bc7267db0462b9fcdcc03dd834ad7ba5836e38433619d1ca49c0 + languageName: node + linkType: hard + +"expo-constants@npm:~55.0.9": + version: 55.0.9 + resolution: "expo-constants@npm:55.0.9" + dependencies: + "@expo/config": "npm:~55.0.10" + "@expo/env": "npm:~2.1.1" + peerDependencies: + expo: "*" + react-native: "*" + checksum: 10c0/7f4fcd7283521dd2cd664a01ca014d70aa090ec877072327c621b006023bc3ca9196e82f55618bf200df012801f4cb613f3396e8ba7ca6eb4c61864cfb890682 + languageName: node + linkType: hard + +"expo-file-system@npm:~55.0.12": + version: 55.0.12 + resolution: "expo-file-system@npm:55.0.12" + peerDependencies: + expo: "*" + react-native: "*" + checksum: 10c0/e099a7c1491348db9dacf9593322c0dd7b42f30883eff9a854fb17f6a0796e684d3a54b4e03a6d19fd3caf5f1d5de8f33d5d2774d36f57e2a0297eeff5a7f7a4 + languageName: node + linkType: hard + +"expo-font@npm:~55.0.4": + version: 55.0.4 + resolution: "expo-font@npm:55.0.4" + dependencies: + fontfaceobserver: "npm:^2.1.0" + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: 10c0/6584feec71a153324cf8c60d1093092976f709b7dcf122f01bbe101bafa594cd38eddde831235ea1bf34cf01b8001fc0dd8a8faa24c03805aea5c2735a681217 + languageName: node + linkType: hard + +"expo-keep-awake@npm:~55.0.4": + version: 55.0.4 + resolution: "expo-keep-awake@npm:55.0.4" + peerDependencies: + expo: "*" + react: "*" + checksum: 10c0/a8bdd4c331086ec0cb2906aff11f2c6ff6acb852bde6db86af52f39391261bbe041f2692ae356ab00b33bec1f5aee29bdc24d2d57d6508d8e74a4b75a794acc5 + languageName: node + linkType: hard + +"expo-modules-autolinking@npm:55.0.12": + version: 55.0.12 + resolution: "expo-modules-autolinking@npm:55.0.12" + dependencies: + "@expo/require-utils": "npm:^55.0.3" + "@expo/spawn-async": "npm:^1.7.2" + chalk: "npm:^4.1.0" + commander: "npm:^7.2.0" + bin: + expo-modules-autolinking: bin/expo-modules-autolinking.js + checksum: 10c0/fed9c401e762b059527fbd57480dabd94224b80f19fe2edff17d918ace93722920f9cfbf1a083aa25639ce3e25fcbcdcacde18f50e8710e7f611caa8686ddff9 + languageName: node + linkType: hard + +"expo-modules-core@npm:55.0.18": + version: 55.0.18 + resolution: "expo-modules-core@npm:55.0.18" + dependencies: + invariant: "npm:^2.2.4" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10c0/3ac2607e6ed05d62e85d1932cc5bf29ad824713c6c44d0c12a666ed7219e33c202af04fd6a01d8b22cffdb4ecce00e112fd2bfd49a0764f0fa1b80117b24d465 + languageName: node + linkType: hard + +"expo-server@npm:^55.0.6": + version: 55.0.6 + resolution: "expo-server@npm:55.0.6" + checksum: 10c0/c8918cd5f09d6cbda028b527edcc6dbc8df932e3def085ef5de063678164d8f048e1fdb975c641f9d7ff2552394fb3acb34ffd58ac214f302eb239210c60fdc4 + languageName: node + linkType: hard + +"expo@npm:~55.0.0": + version: 55.0.9 + resolution: "expo@npm:55.0.9" + dependencies: + "@babel/runtime": "npm:^7.20.0" + "@expo/cli": "npm:55.0.19" + "@expo/config": "npm:~55.0.11" + "@expo/config-plugins": "npm:~55.0.7" + "@expo/devtools": "npm:55.0.2" + "@expo/fingerprint": "npm:0.16.6" + "@expo/local-build-cache-provider": "npm:55.0.7" + "@expo/log-box": "npm:55.0.8" + "@expo/metro": "npm:~54.2.0" + "@expo/metro-config": "npm:55.0.11" + "@expo/vector-icons": "npm:^15.0.2" + "@ungap/structured-clone": "npm:^1.3.0" + babel-preset-expo: "npm:~55.0.13" + expo-asset: "npm:~55.0.10" + expo-constants: "npm:~55.0.9" + expo-file-system: "npm:~55.0.12" + expo-font: "npm:~55.0.4" + expo-keep-awake: "npm:~55.0.4" + expo-modules-autolinking: "npm:55.0.12" + expo-modules-core: "npm:55.0.18" + pretty-format: "npm:^29.7.0" + react-refresh: "npm:^0.14.2" + whatwg-url-minimum: "npm:^0.1.1" + peerDependencies: + "@expo/dom-webview": "*" + "@expo/metro-runtime": "*" + react: "*" + react-native: "*" + react-native-webview: "*" + peerDependenciesMeta: + "@expo/dom-webview": + optional: true + "@expo/metro-runtime": + optional: true + react-native-webview: + optional: true + bin: + expo: bin/cli + expo-modules-autolinking: bin/autolinking + fingerprint: bin/fingerprint + checksum: 10c0/16c918df3e43743a9347027bd58de2df112001abeb06e0b0557a136c1ab162bc12c3054aa83fb96e1055c436d30174de9a99ff699a6727b8543a6edfbf413cad languageName: node linkType: hard @@ -7048,6 +8122,13 @@ __metadata: languageName: node linkType: hard +"fast-loops@npm:^1.1.3": + version: 1.1.4 + resolution: "fast-loops@npm:1.1.4" + checksum: 10c0/25e8a608fccc0d84c1d037efa715ab1e6f21576e1070931b3ed966657204c47ed2b1cba16e5c46ddde2d62aba0b4100d86616d995318b7367fa0a902a78ed885 + languageName: node + linkType: hard + "fast-uri@npm:^3.0.1": version: 3.1.0 resolution: "fast-uri@npm:3.1.0" @@ -7093,6 +8174,28 @@ __metadata: languageName: node linkType: hard +"fbjs-css-vars@npm:^1.0.0": + version: 1.0.2 + resolution: "fbjs-css-vars@npm:1.0.2" + checksum: 10c0/dfb64116b125a64abecca9e31477b5edb9a2332c5ffe74326fe36e0a72eef7fc8a49b86adf36c2c293078d79f4524f35e80f5e62546395f53fb7c9e69821f54f + languageName: node + linkType: hard + +"fbjs@npm:^3.0.4": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: "npm:^3.1.5" + fbjs-css-vars: "npm:^1.0.0" + loose-envify: "npm:^1.0.0" + object-assign: "npm:^4.1.0" + promise: "npm:^7.1.1" + setimmediate: "npm:^1.0.5" + ua-parser-js: "npm:^1.0.35" + checksum: 10c0/66d0a2fc9a774f9066e35ac2ac4bf1245931d27f3ac287c7d47e6aa1fc152b243c2109743eb8f65341e025621fb51a12038fadb9fd8fda2e3ddae04ebab06f91 + languageName: node + linkType: hard + "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -7105,6 +8208,13 @@ __metadata: languageName: node linkType: hard +"fetch-nodeshim@npm:^0.4.6": + version: 0.4.10 + resolution: "fetch-nodeshim@npm:0.4.10" + checksum: 10c0/73b840b5d1252e82c416b350526ff24f5aebf554bfe911c713a19fbe4ad1218fb4c488f95055362a132f5dd733679c929fbe6a65ee23339592290c4d107ade92 + languageName: node + linkType: hard + "file-entry-cache@npm:^8.0.0": version: 8.0.0 resolution: "file-entry-cache@npm:8.0.0" @@ -7200,6 +8310,13 @@ __metadata: languageName: node linkType: hard +"fontfaceobserver@npm:^2.1.0": + version: 2.3.0 + resolution: "fontfaceobserver@npm:2.3.0" + checksum: 10c0/9b539d5021757d3ed73c355bdb839296d6654de473a992aa98993ef46d951f0361545323de68f6d70c5334d7e3e9f409c1ae7a03c168b00cb0f6c5dea6c77bfa + languageName: node + linkType: hard + "for-each@npm:^0.3.3, for-each@npm:^0.3.5": version: 0.3.5 resolution: "for-each@npm:0.3.5" @@ -7219,7 +8336,7 @@ __metadata: languageName: node linkType: hard -"fresh@npm:0.5.2": +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a @@ -7669,6 +8786,13 @@ __metadata: languageName: node linkType: hard +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -7754,6 +8878,13 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.32.1": + version: 0.32.1 + resolution: "hermes-estree@npm:0.32.1" + checksum: 10c0/750d1e26c0df4aae15707765368352c6a34934939df09d96e6d260ee1e1500e753f7a18adac56647ef8ca2057e8f0e5d21ae07b97103b0d9c94d68afee154c5e + languageName: node + linkType: hard + "hermes-estree@npm:0.33.3": version: 0.33.3 resolution: "hermes-estree@npm:0.33.3" @@ -7788,6 +8919,15 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.32.1, hermes-parser@npm:^0.32.0": + version: 0.32.1 + resolution: "hermes-parser@npm:0.32.1" + dependencies: + hermes-estree: "npm:0.32.1" + checksum: 10c0/77dc8b116c51d1b30ba9942629d4965301f2c7fa6a751a1842828d110ce33410daed5755ce8943a110dbfc6a5cafc704ddbfb7559e76b5c3170d2173c513047c + languageName: node + linkType: hard + "hermes-parser@npm:0.33.3": version: 0.33.3 resolution: "hermes-parser@npm:0.33.3" @@ -7842,6 +8982,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 + languageName: node + linkType: hard + "http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -7883,6 +9036,13 @@ __metadata: languageName: node linkType: hard +"hyphenate-style-name@npm:^1.0.3": + version: 1.1.0 + resolution: "hyphenate-style-name@npm:1.1.0" + checksum: 10c0/bfe88deac2414a41a0d08811e277c8c098f23993d6a1eb17f14a0f11b54c4d42865a63d3cfe1914668eefb9a188e2de58f38b55a179a238fd1fef606893e194f + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -7917,7 +9077,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.0.5, ignore@npm:^5.2.0": +"ignore@npm:^5.0.5, ignore@npm:^5.2.0, ignore@npm:^5.3.1": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 @@ -8002,7 +9162,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -8016,6 +9176,16 @@ __metadata: languageName: node linkType: hard +"inline-style-prefixer@npm:^6.0.1": + version: 6.0.4 + resolution: "inline-style-prefixer@npm:6.0.4" + dependencies: + css-in-js-utils: "npm:^3.1.0" + fast-loops: "npm:^1.1.3" + checksum: 10c0/d3d42bf0c48d621ea4bcfb077b5d370b106995422300a3a472674f96c9b489d96b4aac6f29dea3bb26ff2dfd7293e4752098bc2b53407769eafdb66c6c4c1764 + languageName: node + linkType: hard + "inquirer@npm:12.9.6": version: 12.9.6 resolution: "inquirer@npm:12.9.6" @@ -8160,7 +9330,7 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^2.0.0": +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" bin: @@ -9093,6 +10263,13 @@ __metadata: languageName: node linkType: hard +"jimp-compact@npm:0.16.1": + version: 0.16.1 + resolution: "jimp-compact@npm:0.16.1" + checksum: 10c0/2d73bb927d840ce6dc093d089d770eddbb81472635ced7cad1d7c4545d8734aecf5bd3dedf7178a6cfab4d06c9d6cbbf59e5cb274ed99ca11cd4835a6374f16c + languageName: node + linkType: hard + "jiti@npm:^2.5.1, jiti@npm:^2.6.1": version: 2.6.1 resolution: "jiti@npm:2.6.1" @@ -9145,7 +10322,7 @@ __metadata: languageName: node linkType: hard -"jsc-safe-url@npm:^0.2.2": +"jsc-safe-url@npm:^0.2.2, jsc-safe-url@npm:^0.2.4": version: 0.2.4 resolution: "jsc-safe-url@npm:0.2.4" checksum: 10c0/429bd645f8a35938f08f5b01c282e5ef55ed8be30a9ca23517b7ca01dcbf84b4b0632042caceab50f8f5c0c1e76816fe3c74de3e59be84da7f89ae1503bd3c68 @@ -9249,6 +10426,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.16.44": + version: 0.16.45 + resolution: "katex@npm:0.16.45" + dependencies: + commander: "npm:^8.3.0" + bin: + katex: cli.js + checksum: 10c0/f715eb9a73daff68ac14dcc8c2f47f02f5fc756a74077d22479e64e06872b2b44f0d81f1c91a98a9873722a08841388a869a0b9ddb96b224362eb8054ddf533a + languageName: node + linkType: hard + "keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -9272,6 +10460,15 @@ __metadata: languageName: node linkType: hard +"lan-network@npm:^0.2.0": + version: 0.2.0 + resolution: "lan-network@npm:0.2.0" + bin: + lan-network: dist/lan-network-cli.js + checksum: 10c0/06da664a94e962ded0e30705bebacca73e02a7575c6cf1e91d6b12d5af49b0491727a1308cbf0ff47a4d19c5779b74919dffd5b60b8fd1c879d4d2e79d98887e + languageName: node + linkType: hard + "launch-editor@npm:^2.9.1": version: 2.11.1 resolution: "launch-editor@npm:2.11.1" @@ -9420,6 +10617,126 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.30.1": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -9566,6 +10883,15 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^2.2.0": + version: 2.2.0 + resolution: "log-symbols@npm:2.2.0" + dependencies: + chalk: "npm:^2.0.1" + checksum: 10c0/574eb4205f54f0605021aa67ebb372c30ca64e8ddd439efeb8507af83c776dce789e83614e80059014d9e48dcc94c4b60cef2e85f0dc944eea27c799cec62353 + languageName: node + linkType: hard + "log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -9712,6 +11038,13 @@ __metadata: languageName: node linkType: hard +"memoize-one@npm:^6.0.0": + version: 6.0.0 + resolution: "memoize-one@npm:6.0.0" + checksum: 10c0/45c88e064fd715166619af72e8cf8a7a17224d6edf61f7a8633d740ed8c8c0558a4373876c9b8ffc5518c2b65a960266adf403cc215cb1e90f7e262b58991f54 + languageName: node + linkType: hard + "meow@npm:^12.0.1": version: 12.1.1 resolution: "meow@npm:12.1.1" @@ -9740,6 +11073,18 @@ __metadata: languageName: node linkType: hard +"metro-babel-transformer@npm:0.83.3": + version: 0.83.3 + resolution: "metro-babel-transformer@npm:0.83.3" + dependencies: + "@babel/core": "npm:^7.25.2" + flow-enums-runtime: "npm:^0.0.6" + hermes-parser: "npm:0.32.0" + nullthrows: "npm:^1.1.1" + checksum: 10c0/b0107f86cdc9ef9419d669b5b3dac22e35b02c67c480563a63d98f5fb50953587938769efc854bfc09c225557790cd6488dbe3fed6f05c2b3f322cfb2e5ff577 + languageName: node + linkType: hard + "metro-babel-transformer@npm:0.83.5": version: 0.83.5 resolution: "metro-babel-transformer@npm:0.83.5" @@ -9752,6 +11097,15 @@ __metadata: languageName: node linkType: hard +"metro-cache-key@npm:0.83.3": + version: 0.83.3 + resolution: "metro-cache-key@npm:0.83.3" + dependencies: + flow-enums-runtime: "npm:^0.0.6" + checksum: 10c0/403a2ca5b5bbb31a979effaa31fba0c47e2eb3830428c39c99db58aa0739a6fcc386f5a56c91495c53a4569065f0bda29e3038e9c41ca17af443971395f257dc + languageName: node + linkType: hard + "metro-cache-key@npm:0.83.5": version: 0.83.5 resolution: "metro-cache-key@npm:0.83.5" @@ -9761,6 +11115,18 @@ __metadata: languageName: node linkType: hard +"metro-cache@npm:0.83.3": + version: 0.83.3 + resolution: "metro-cache@npm:0.83.3" + dependencies: + exponential-backoff: "npm:^3.1.1" + flow-enums-runtime: "npm:^0.0.6" + https-proxy-agent: "npm:^7.0.5" + metro-core: "npm:0.83.3" + checksum: 10c0/608e85d819092c0b472c9adabb5de58e88355739de71833230626c1af7f3ce5dd1dca9f1ff3a836d995201f717315fd769c4c646a818c1f490ea2ec29417e32a + languageName: node + linkType: hard + "metro-cache@npm:0.83.5": version: 0.83.5 resolution: "metro-cache@npm:0.83.5" @@ -9773,6 +11139,22 @@ __metadata: languageName: node linkType: hard +"metro-config@npm:0.83.3": + version: 0.83.3 + resolution: "metro-config@npm:0.83.3" + dependencies: + connect: "npm:^3.6.5" + flow-enums-runtime: "npm:^0.0.6" + jest-validate: "npm:^29.7.0" + metro: "npm:0.83.3" + metro-cache: "npm:0.83.3" + metro-core: "npm:0.83.3" + metro-runtime: "npm:0.83.3" + yaml: "npm:^2.6.1" + checksum: 10c0/c53e4a061cfc776a65cdb5055c0be840055f9741dae25e7d407835988618b15f1407270dbd957c7333d01e9c79eccbf8e6bcb76421b2145bd134b53df459a033 + languageName: node + linkType: hard + "metro-config@npm:0.83.5, metro-config@npm:^0.83.1, metro-config@npm:^0.83.3": version: 0.83.5 resolution: "metro-config@npm:0.83.5" @@ -9789,6 +11171,17 @@ __metadata: languageName: node linkType: hard +"metro-core@npm:0.83.3": + version: 0.83.3 + resolution: "metro-core@npm:0.83.3" + dependencies: + flow-enums-runtime: "npm:^0.0.6" + lodash.throttle: "npm:^4.1.1" + metro-resolver: "npm:0.83.3" + checksum: 10c0/d44c1f117c4b27f18abd27110e9536abf3105733e8fccaa522bd0e008248cce0260130517840c4914d7ce5df498f39ecfd43b6046a0f0b1c0f8ada7de38e52c4 + languageName: node + linkType: hard + "metro-core@npm:0.83.5, metro-core@npm:^0.83.1, metro-core@npm:^0.83.3": version: 0.83.5 resolution: "metro-core@npm:0.83.5" @@ -9800,6 +11193,23 @@ __metadata: languageName: node linkType: hard +"metro-file-map@npm:0.83.3": + version: 0.83.3 + resolution: "metro-file-map@npm:0.83.3" + dependencies: + debug: "npm:^4.4.0" + fb-watchman: "npm:^2.0.0" + flow-enums-runtime: "npm:^0.0.6" + graceful-fs: "npm:^4.2.4" + invariant: "npm:^2.2.4" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + nullthrows: "npm:^1.1.1" + walker: "npm:^1.0.7" + checksum: 10c0/4bf9c0fcdb5a5c08851f7370d6427fb68a770f156c4eabbddf20bd3583fb25ae428507eaeb8dc525e792db41d048620209750f33735055863abc909cbb6ef71a + languageName: node + linkType: hard + "metro-file-map@npm:0.83.5": version: 0.83.5 resolution: "metro-file-map@npm:0.83.5" @@ -9817,6 +11227,16 @@ __metadata: languageName: node linkType: hard +"metro-minify-terser@npm:0.83.3": + version: 0.83.3 + resolution: "metro-minify-terser@npm:0.83.3" + dependencies: + flow-enums-runtime: "npm:^0.0.6" + terser: "npm:^5.15.0" + checksum: 10c0/9158e3199c0ea647776a7ed5c68ec1bb493f5347ac979f1ca75020cf1c39f907bd29983d60f8cb24dca17053d6b5c35f140c6d720fad0bd0fa9728e8c51e95c6 + languageName: node + linkType: hard + "metro-minify-terser@npm:0.83.5": version: 0.83.5 resolution: "metro-minify-terser@npm:0.83.5" @@ -9827,6 +11247,15 @@ __metadata: languageName: node linkType: hard +"metro-resolver@npm:0.83.3": + version: 0.83.3 + resolution: "metro-resolver@npm:0.83.3" + dependencies: + flow-enums-runtime: "npm:^0.0.6" + checksum: 10c0/1d6c030a00b987fbee38e5c632219b2be602e38c9aa9628bb4b591f646e64130d08adb8dcb35076c5c8cc151135557b655f3dee514c0df9f26d3416629eb006b + languageName: node + linkType: hard + "metro-resolver@npm:0.83.5": version: 0.83.5 resolution: "metro-resolver@npm:0.83.5" @@ -9836,6 +11265,16 @@ __metadata: languageName: node linkType: hard +"metro-runtime@npm:0.83.3": + version: 0.83.3 + resolution: "metro-runtime@npm:0.83.3" + dependencies: + "@babel/runtime": "npm:^7.25.0" + flow-enums-runtime: "npm:^0.0.6" + checksum: 10c0/1d788483b6c2f13e0ea9ff4564996154754d3de84f683812ac848053eaea9243144adee3e8ffe90789e6c253f7402211d72b1b5ebf09e6c23841bc956a680253 + languageName: node + linkType: hard + "metro-runtime@npm:0.83.5, metro-runtime@npm:^0.83.1, metro-runtime@npm:^0.83.3": version: 0.83.5 resolution: "metro-runtime@npm:0.83.5" @@ -9846,6 +11285,24 @@ __metadata: languageName: node linkType: hard +"metro-source-map@npm:0.83.3": + version: 0.83.3 + resolution: "metro-source-map@npm:0.83.3" + dependencies: + "@babel/traverse": "npm:^7.25.3" + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" + "@babel/types": "npm:^7.25.2" + flow-enums-runtime: "npm:^0.0.6" + invariant: "npm:^2.2.4" + metro-symbolicate: "npm:0.83.3" + nullthrows: "npm:^1.1.1" + ob1: "npm:0.83.3" + source-map: "npm:^0.5.6" + vlq: "npm:^1.0.0" + checksum: 10c0/47e984bde1f8f06348298771f44b5803657c9cfa387df8ff36a359cc72ae3bc0e9c4ea6141345609b183ac8c63dcc997000d3626006e388c24779abb57c6f82c + languageName: node + linkType: hard + "metro-source-map@npm:0.83.5, metro-source-map@npm:^0.83.1, metro-source-map@npm:^0.83.3": version: 0.83.5 resolution: "metro-source-map@npm:0.83.5" @@ -9863,6 +11320,22 @@ __metadata: languageName: node linkType: hard +"metro-symbolicate@npm:0.83.3": + version: 0.83.3 + resolution: "metro-symbolicate@npm:0.83.3" + dependencies: + flow-enums-runtime: "npm:^0.0.6" + invariant: "npm:^2.2.4" + metro-source-map: "npm:0.83.3" + nullthrows: "npm:^1.1.1" + source-map: "npm:^0.5.6" + vlq: "npm:^1.0.0" + bin: + metro-symbolicate: src/index.js + checksum: 10c0/bd3d234c7581466a9a78f952caa25816666753f6b560fe41502727b3e59931ac65225c9909635dc7c25d4dfaf392631366ef3ec5fa8490413385d60f8d900112 + languageName: node + linkType: hard + "metro-symbolicate@npm:0.83.5": version: 0.83.5 resolution: "metro-symbolicate@npm:0.83.5" @@ -9879,6 +11352,20 @@ __metadata: languageName: node linkType: hard +"metro-transform-plugins@npm:0.83.3": + version: 0.83.3 + resolution: "metro-transform-plugins@npm:0.83.3" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/generator": "npm:^7.25.0" + "@babel/template": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.3" + flow-enums-runtime: "npm:^0.0.6" + nullthrows: "npm:^1.1.1" + checksum: 10c0/df3c6db6a69d4888e1b6aad40d48ffec0c3c3faa38e89c07633432fc107ef12c47d55598904c91aadfe0751c5bcb7ec191f8a5ee70c18d253201150fc617ca37 + languageName: node + linkType: hard + "metro-transform-plugins@npm:0.83.5": version: 0.83.5 resolution: "metro-transform-plugins@npm:0.83.5" @@ -9893,6 +11380,27 @@ __metadata: languageName: node linkType: hard +"metro-transform-worker@npm:0.83.3": + version: 0.83.3 + resolution: "metro-transform-worker@npm:0.83.3" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/generator": "npm:^7.25.0" + "@babel/parser": "npm:^7.25.3" + "@babel/types": "npm:^7.25.2" + flow-enums-runtime: "npm:^0.0.6" + metro: "npm:0.83.3" + metro-babel-transformer: "npm:0.83.3" + metro-cache: "npm:0.83.3" + metro-cache-key: "npm:0.83.3" + metro-minify-terser: "npm:0.83.3" + metro-source-map: "npm:0.83.3" + metro-transform-plugins: "npm:0.83.3" + nullthrows: "npm:^1.1.1" + checksum: 10c0/bea0cbcc7d13cd2b97a2159257b3a53b9ecfb15da18ace82ae05bf2d0ac7cc1806c0bd77ed3b8f4c82c9532773fb99f3938e4b1480e2673f5eda69575ee1d7ef + languageName: node + linkType: hard + "metro-transform-worker@npm:0.83.5": version: 0.83.5 resolution: "metro-transform-worker@npm:0.83.5" @@ -9910,7 +11418,57 @@ __metadata: metro-source-map: "npm:0.83.5" metro-transform-plugins: "npm:0.83.5" nullthrows: "npm:^1.1.1" - checksum: 10c0/aef57bbdc0cffc85f6fd713e3e8dad4cac6d8bf11e8c87b0a26a56dd1f7d677cd6844c7dfe18af58c88a54730b68c4562def2e7c227aba4cae0c8376e85938ba + checksum: 10c0/aef57bbdc0cffc85f6fd713e3e8dad4cac6d8bf11e8c87b0a26a56dd1f7d677cd6844c7dfe18af58c88a54730b68c4562def2e7c227aba4cae0c8376e85938ba + languageName: node + linkType: hard + +"metro@npm:0.83.3": + version: 0.83.3 + resolution: "metro@npm:0.83.3" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/core": "npm:^7.25.2" + "@babel/generator": "npm:^7.25.0" + "@babel/parser": "npm:^7.25.3" + "@babel/template": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.3" + "@babel/types": "npm:^7.25.2" + accepts: "npm:^1.3.7" + chalk: "npm:^4.0.0" + ci-info: "npm:^2.0.0" + connect: "npm:^3.6.5" + debug: "npm:^4.4.0" + error-stack-parser: "npm:^2.0.6" + flow-enums-runtime: "npm:^0.0.6" + graceful-fs: "npm:^4.2.4" + hermes-parser: "npm:0.32.0" + image-size: "npm:^1.0.2" + invariant: "npm:^2.2.4" + jest-worker: "npm:^29.7.0" + jsc-safe-url: "npm:^0.2.2" + lodash.throttle: "npm:^4.1.1" + metro-babel-transformer: "npm:0.83.3" + metro-cache: "npm:0.83.3" + metro-cache-key: "npm:0.83.3" + metro-config: "npm:0.83.3" + metro-core: "npm:0.83.3" + metro-file-map: "npm:0.83.3" + metro-resolver: "npm:0.83.3" + metro-runtime: "npm:0.83.3" + metro-source-map: "npm:0.83.3" + metro-symbolicate: "npm:0.83.3" + metro-transform-plugins: "npm:0.83.3" + metro-transform-worker: "npm:0.83.3" + mime-types: "npm:^2.1.27" + nullthrows: "npm:^1.1.1" + serialize-error: "npm:^2.1.0" + source-map: "npm:^0.5.6" + throat: "npm:^5.0.0" + ws: "npm:^7.5.10" + yargs: "npm:^17.6.2" + bin: + metro: src/cli.js + checksum: 10c0/9513c05725c3984ce3b72896c4f7d019ad4fd024a1231b8b84c5c655a0563fc7f26725f28c20c5d3511e3825d64fec3a1e68621f6a6af34d785c5e714ed7da89 languageName: node linkType: hard @@ -9997,6 +11555,15 @@ __metadata: languageName: node linkType: hard +"mime-types@npm:^2.1.27, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + "mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": version: 3.0.2 resolution: "mime-types@npm:3.0.2" @@ -10006,15 +11573,6 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - "mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -10033,6 +11591,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^1.0.0": + version: 1.2.0 + resolution: "mimic-fn@npm:1.2.0" + checksum: 10c0/ad55214aec6094c0af4c0beec1a13787556f8116ed88807cf3f05828500f21f93a9482326bcd5a077ae91e3e8795b4e76b5b4c8bb12237ff0e4043a365516cba + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -10187,13 +11752,20 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 languageName: node linkType: hard +"multitars@npm:^0.2.3": + version: 0.2.4 + resolution: "multitars@npm:0.2.4" + checksum: 10c0/c46a7385ea9c51a34ad8df1829501f070af48ab56c128861d2f6c8106a7fb586730deb82ca834afb43d12eac56d868a50d7c71c01097bb1791ebe1bd1ccf76d1 + languageName: node + linkType: hard + "mute-stream@npm:^2.0.0": version: 2.0.0 resolution: "mute-stream@npm:2.0.0" @@ -10201,6 +11773,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.7": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -10266,6 +11847,27 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.7.0": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + +"node-forge@npm:^1.3.3": + version: 1.4.0 + resolution: "node-forge@npm:1.4.0" + checksum: 10c0/67330a5f1f95257a4c8a93b7d555abe87b5f15e350123aa396c97a21a8ca94f9c6549008eb2c73668a91e0d7e3a905785acbd8f8bd0751c29401292011f8f8e1 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 11.5.0 resolution: "node-gyp@npm:11.5.0" @@ -10300,6 +11902,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.27": + version: 2.0.36 + resolution: "node-releases@npm:2.0.36" + checksum: 10c0/85d8d7f4b6248c8372831cbcc3829ce634cb2b01dbd85e55705cefc8a9eda4ce8121bd218b9629cf2579aef8a360541bad409f3925a35675c825b9471a49d7e9 + languageName: node + linkType: hard + "node-stream-zip@npm:^1.9.1": version: 1.15.0 resolution: "node-stream-zip@npm:1.15.0" @@ -10336,6 +11945,18 @@ __metadata: languageName: node linkType: hard +"npm-package-arg@npm:^11.0.0": + version: 11.0.3 + resolution: "npm-package-arg@npm:11.0.3" + dependencies: + hosted-git-info: "npm:^7.0.0" + proc-log: "npm:^4.0.0" + semver: "npm:^7.3.5" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10c0/e18333485e05c3a8774f4b5701ef74f4799533e650b70a68ca8dd697666c9a8d46932cb765fc593edce299521033bd4025a40323d5240cea8a393c784c0c285a + languageName: node + linkType: hard + "npm-run-path@npm:^4.0.0, npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" @@ -10376,6 +11997,15 @@ __metadata: languageName: node linkType: hard +"ob1@npm:0.83.3": + version: 0.83.3 + resolution: "ob1@npm:0.83.3" + dependencies: + flow-enums-runtime: "npm:^0.0.6" + checksum: 10c0/9231315de39cf0612a01e283c7d7ef31d16618e598de96e44ae1ab3007629296ce1a3d5d02ef60ff22d9fefe33050358c10e7fcba8278861157b89befe13cb3d + languageName: node + linkType: hard + "ob1@npm:0.83.5": version: 0.83.5 resolution: "ob1@npm:0.83.5" @@ -10385,7 +12015,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": +"object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 @@ -10463,7 +12093,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -10497,6 +12127,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^2.0.0": + version: 2.0.1 + resolution: "onetime@npm:2.0.1" + dependencies: + mimic-fn: "npm:^1.0.0" + checksum: 10c0/b4e44a8c34e70e02251bfb578a6e26d6de6eedbed106cd78211d2fd64d28b6281d54924696554e4e966559644243753ac5df73c87f283b0927533d3315696215 + languageName: node + linkType: hard + "onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -10555,6 +12194,17 @@ __metadata: languageName: node linkType: hard +"open@npm:^8.0.4": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: "npm:^2.0.0" + is-docker: "npm:^2.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -10586,6 +12236,20 @@ __metadata: languageName: node linkType: hard +"ora@npm:^3.4.0": + version: 3.4.0 + resolution: "ora@npm:3.4.0" + dependencies: + chalk: "npm:^2.4.2" + cli-cursor: "npm:^2.1.0" + cli-spinners: "npm:^2.0.0" + log-symbols: "npm:^2.2.0" + strip-ansi: "npm:^5.2.0" + wcwidth: "npm:^1.0.1" + checksum: 10c0/04cb375f222c36a16a95e6c39c473644a99a42fc34d35c37507cb836ea0a71f4d831fcd53198a460869114b2730891d63cc1047304afe5ddb078974d468edfb1 + languageName: node + linkType: hard + "ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" @@ -10775,6 +12439,15 @@ __metadata: languageName: node linkType: hard +"parse-png@npm:^2.1.0": + version: 2.1.0 + resolution: "parse-png@npm:2.1.0" + dependencies: + pngjs: "npm:^3.3.0" + checksum: 10c0/5157a8bbb976ae1ca990fc53c7014d42aac0967cb30e2daf36c3fef1876c8db0d551e695400c904f33c5c5add76a572c65b5044721d62417d8cc7abe4c4ffa41 + languageName: node + linkType: hard + "parse-url@npm:^9.2.0": version: 9.2.0 resolution: "parse-url@npm:9.2.0" @@ -10941,6 +12614,13 @@ __metadata: languageName: node linkType: hard +"pngjs@npm:^3.3.0": + version: 3.4.0 + resolution: "pngjs@npm:3.4.0" + checksum: 10c0/88ee73e2ad3f736e0b2573722309eb80bd2aa28916f0862379b4fd0f904751b4f61bb6bd1ecd7d4242d331f2b5c28c13309dd4b7d89a9b78306e35122fdc5011 + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": version: 1.1.0 resolution: "possible-typed-array-names@npm:1.1.0" @@ -10948,6 +12628,24 @@ __metadata: languageName: node linkType: hard +"postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 + languageName: node + linkType: hard + +"postcss@npm:~8.4.32": + version: 8.4.49 + resolution: "postcss@npm:8.4.49" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -10991,6 +12689,13 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^4.0.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 + languageName: node + linkType: hard + "proc-log@npm:^5.0.0": version: 5.0.0 resolution: "proc-log@npm:5.0.0" @@ -10998,6 +12703,13 @@ __metadata: languageName: node linkType: hard +"progress@npm:^2.0.3": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: 10c0/1697e07cb1068055dbe9fe858d242368ff5d2073639e652b75a7eb1f2a1a8d4afd404d719de23c7b48481a6aa0040686310e2dac2f53d776daa2176d3f96369c + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -11008,6 +12720,15 @@ __metadata: languageName: node linkType: hard +"promise@npm:^7.1.1": + version: 7.3.1 + resolution: "promise@npm:7.3.1" + dependencies: + asap: "npm:~2.0.3" + checksum: 10c0/742e5c0cc646af1f0746963b8776299701ad561ce2c70b49365d62c8db8ea3681b0a1bf0d4e2fe07910bf72f02d39e51e8e73dc8d7503c3501206ac908be107f + languageName: node + linkType: hard + "promise@npm:^8.3.0": version: 8.3.0 resolution: "promise@npm:8.3.0" @@ -11017,7 +12738,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1, prompts@npm:^2.4.2": +"prompts@npm:^2.0.1, prompts@npm:^2.3.2, prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -11156,6 +12877,17 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:19.2.3": + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 + languageName: node + linkType: hard + "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -11243,6 +12975,24 @@ __metadata: languageName: unknown linkType: soft +"react-native-enriched-markdown-web-example@workspace:apps/web-example": + version: 0.0.0-use.local + resolution: "react-native-enriched-markdown-web-example@workspace:apps/web-example" + dependencies: + "@expo/metro-runtime": "npm:~5.0.0" + "@types/react": "npm:^19.2.0" + babel-preset-expo: "npm:~55.0.13" + expo: "npm:~55.0.0" + katex: "npm:^0.16.44" + react: "npm:19.2.3" + react-dom: "npm:19.2.3" + react-native: "npm:0.84.1" + react-native-enriched-markdown: "npm:*" + react-native-web: "npm:~0.19.13" + typescript: "npm:^5.9.2" + languageName: unknown + linkType: soft + "react-native-enriched-markdown@npm:*, react-native-enriched-markdown@workspace:.": version: 0.0.0-use.local resolution: "react-native-enriched-markdown@workspace:." @@ -11275,11 +13025,14 @@ __metadata: typescript: "npm:^5.9.2" peerDependencies: "@expo/config-plugins": ">=50.0.0" + katex: ">=0.16.0" react: "*" react-native: "*" peerDependenciesMeta: "@expo/config-plugins": optional: true + katex: + optional: true languageName: unknown linkType: soft @@ -11355,6 +13108,25 @@ __metadata: languageName: node linkType: hard +"react-native-web@npm:~0.19.13": + version: 0.19.13 + resolution: "react-native-web@npm:0.19.13" + dependencies: + "@babel/runtime": "npm:^7.18.6" + "@react-native/normalize-colors": "npm:^0.74.1" + fbjs: "npm:^3.0.4" + inline-style-prefixer: "npm:^6.0.1" + memoize-one: "npm:^6.0.0" + nullthrows: "npm:^1.1.1" + postcss-value-parser: "npm:^4.2.0" + styleq: "npm:^0.1.3" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10c0/55e82a6f656843b2b4f6e4c4006a82ae8feed548e880e9fa3c2623da415d3abd9399c91c5360b71d5f24f47c5cbe30872a3ad785fa1a32cf152383d595f8ebd5 + languageName: node + linkType: hard + "react-native@npm:0.81.6": version: 0.81.6 resolution: "react-native@npm:0.81.6" @@ -11456,7 +13228,7 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:^0.14.0": +"react-refresh@npm:^0.14.0, react-refresh@npm:^0.14.2": version: 0.14.2 resolution: "react-refresh@npm:0.14.2" checksum: 10c0/875b72ef56b147a131e33f2abd6ec059d1989854b3ff438898e4f9310bfcc73acff709445b7ba843318a953cb9424bcc2c05af2b3d80011cee28f25aef3e2ebb @@ -11681,6 +13453,13 @@ __metadata: languageName: node linkType: hard +"resolve-workspace-root@npm:^2.0.0": + version: 2.0.1 + resolution: "resolve-workspace-root@npm:2.0.1" + checksum: 10c0/83104ea8476ba451a4bac32db42cf1dc79a7b98810764e507830a2f63af20cfb00fe7da5b0c324d77d4fcfda7a24e9e17895690d6f6a498735b633fd7fc372ca + languageName: node + linkType: hard + "resolve.exports@npm:^2.0.0": version: 2.0.3 resolution: "resolve.exports@npm:2.0.3" @@ -11766,6 +13545,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^2.0.0": + version: 2.0.0 + resolution: "restore-cursor@npm:2.0.0" + dependencies: + onetime: "npm:^2.0.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/f5b335bee06f440445e976a7031a3ef53691f9b7c4a9d42a469a0edaf8a5508158a0d561ff2b26a1f4f38783bcca2c0e5c3a44f927326f6694d5b44d7a4993e6 + languageName: node + linkType: hard + "restore-cursor@npm:^3.1.0": version: 3.1.0 resolution: "restore-cursor@npm:3.1.0" @@ -11912,7 +13701,7 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:0.27.0": +"scheduler@npm:0.27.0, scheduler@npm:^0.27.0": version: 0.27.0 resolution: "scheduler@npm:0.27.0" checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 @@ -11976,6 +13765,27 @@ __metadata: languageName: node linkType: hard +"send@npm:^0.19.0": + version: 0.19.2 + resolution: "send@npm:0.19.2" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.1" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:~2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:~2.0.2" + checksum: 10c0/20c2389fe0fdf3fc499938cac598bc32272287e993c4960717381a10de8550028feadfb9076f959a3a3ebdea42e1f690e116f0d16468fa56b9fd41866d3dc267 + languageName: node + linkType: hard + "serialize-error@npm:^2.1.0": version: 2.1.0 resolution: "serialize-error@npm:2.1.0" @@ -12039,7 +13849,14 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc @@ -12174,6 +13991,13 @@ __metadata: languageName: node linkType: hard +"slugify@npm:^1.3.4": + version: 1.6.8 + resolution: "slugify@npm:1.6.8" + checksum: 10c0/1052797a5e7bc6263c6e8dc64ad41d6f5ae2fd2e3a17c2aead09ded002f10de75283ff0b4da2b04dd2fbd7fa2f38a83abeaa3ab8d29709ca68e7592edd6e90dc + languageName: node + linkType: hard + "slugify@npm:^1.6.6": version: 1.6.6 resolution: "slugify@npm:1.6.6" @@ -12209,6 +14033,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -12219,7 +14050,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:~0.5.20": +"source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -12339,6 +14170,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + "stdin-discarder@npm:^0.2.2": version: 0.2.2 resolution: "stdin-discarder@npm:0.2.2" @@ -12499,7 +14337,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^5.0.0": +"strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.2.0": version: 5.2.0 resolution: "strip-ansi@npm:5.2.0" dependencies: @@ -12552,7 +14390,30 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": +"structured-headers@npm:^0.4.1": + version: 0.4.1 + resolution: "structured-headers@npm:0.4.1" + checksum: 10c0/b7d326f6fec7e7f7901d1e0542577293b5d029bf3e1fb84995e33d9aabe47d03259f64ca2d778ef5c427f6f00c78bafa051b6f233131e1556f8bb9102b11ed64 + languageName: node + linkType: hard + +"styleq@npm:^0.1.3": + version: 0.1.3 + resolution: "styleq@npm:0.1.3" + checksum: 10c0/975d951792e65052f1f6e41aaad46492642ce4922b3dc36d4b49b37c8509f9a776794d8f275360f00116a5e6ab1e31514bdcd5840656c4e3213da6803fa12941 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -12570,6 +14431,16 @@ __metadata: languageName: node linkType: hard +"supports-hyperlinks@npm:^2.0.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 10c0/4057f0d86afb056cd799602f72d575b8fdd79001c5894bcb691176f14e870a687e7981e50bc1484980e8b688c6d5bcd4931e1609816abb5a7dc1486b7babf6a1 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -12599,6 +14470,16 @@ __metadata: languageName: node linkType: hard +"terminal-link@npm:^2.1.1": + version: 2.1.1 + resolution: "terminal-link@npm:2.1.1" + dependencies: + ansi-escapes: "npm:^4.2.1" + supports-hyperlinks: "npm:^2.0.0" + checksum: 10c0/947458a5cd5408d2ffcdb14aee50bec8fb5022ae683b896b2f08ed6db7b2e7d42780d5c8b51e930e9c322bd7c7a517f4fa7c76983d0873c83245885ac5ee13e3 + languageName: node + linkType: hard + "terser@npm:^5.15.0": version: 5.44.0 resolution: "terser@npm:5.44.0" @@ -12678,13 +14559,27 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 languageName: node linkType: hard +"toqr@npm:^0.1.1": + version: 0.1.1 + resolution: "toqr@npm:0.1.1" + checksum: 10c0/eec346afae2eede8886938992a7eba59f765b3d3a3d5e7ce4984cb25b124e1a3d02531ed1ef3100d60fe443eeb1c7f83ca1fa0bbb04915d67baa5380e7c9eda4 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + "ts-api-utils@npm:^2.4.0": version: 2.4.0 resolution: "ts-api-utils@npm:2.4.0" @@ -12906,6 +14801,15 @@ __metadata: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.35": + version: 1.0.41 + resolution: "ua-parser-js@npm:1.0.41" + bin: + ua-parser-js: script/cli.js + checksum: 10c0/45dc1f7f3ce8248e0e64640d2e29c65c0ea1fc9cb105594de84af80e2a57bba4f718b9376098ca7a5b0ffe240f8995b0fa3714afa9d36861c41370a378f1a274 + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.19.3 resolution: "uglify-js@npm:3.19.3" @@ -13053,6 +14957,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -13113,6 +15031,13 @@ __metadata: languageName: node linkType: hard +"validate-npm-package-name@npm:^5.0.0": + version: 5.0.1 + resolution: "validate-npm-package-name@npm:5.0.1" + checksum: 10c0/903e738f7387404bb72f7ac34e45d7010c877abd2803dc2d614612527927a40a6d024420033132e667b1bade94544b8a1f65c9431a4eb30d0ce0d80093cd1f74 + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -13145,6 +15070,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + "whatwg-fetch@npm:^3.0.0": version: 3.6.20 resolution: "whatwg-fetch@npm:3.6.20" @@ -13152,6 +15084,23 @@ __metadata: languageName: node linkType: hard +"whatwg-url-minimum@npm:^0.1.1": + version: 0.1.1 + resolution: "whatwg-url-minimum@npm:0.1.1" + checksum: 10c0/0e10fa110a3f7292d3fe0192ac0d823ab83601c5e2c1817a6371df038a9e1790cabdb866b0e1e67967e11d9b0d0b8ad1c61511fea62771e9f5fbb48c54b71319 + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": version: 1.1.1 resolution: "which-boxed-primitive@npm:1.1.1" @@ -13346,6 +15295,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.12.1": + version: 8.20.0 + resolution: "ws@npm:8.20.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/956ac5f11738c914089b65878b9223692ace77337ba55379ae68e1ecbeae9b47a0c6eb9403688f609999a58c80d83d99865fe0029b229d308b08c1ef93d4ea14 + languageName: node + linkType: hard + "wsl-utils@npm:^0.1.0": version: 0.1.0 resolution: "wsl-utils@npm:0.1.0" @@ -13527,3 +15491,10 @@ __metadata: checksum: 10c0/860d25a81ab41d33aa25f8d0d07b091a04acb426e605f396227a796e9e800c44723ed96d0f53a512b57be3d1520f45bf69c0cb3b378a232a00787a2609625307 languageName: node linkType: hard + +"zod@npm:^3.25.76": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c + languageName: node + linkType: hard