diff --git a/.changeset/bright-docs-arrive.md b/.changeset/bright-docs-arrive.md new file mode 100644 index 0000000..0b1e560 --- /dev/null +++ b/.changeset/bright-docs-arrive.md @@ -0,0 +1,5 @@ +--- +'@react-native-motion-kit/swipe-deck': patch +--- + +Add the Rspress documentation site and refresh the npm package metadata. diff --git a/README.ko.md b/README.ko.md index a87defd..797fc51 100644 --- a/README.ko.md +++ b/README.ko.md @@ -6,15 +6,16 @@ React Native Motion Kit logo -Reanimated와 Gesture Handler 기반의 고성능 React Native용 Tinder 스타일 swipe deck / swipe card 라이브러리입니다. +Reanimated, Worklets, Gesture Handler 기반의 고성능 React Native용 Tinder 스타일 +swipe deck / swipe card 라이브러리입니다. ## 특징 - **작은 render window**: 현재 카드와 bounded forward stack만 mount합니다. - **Item-keyed rendering**: promoted card가 React Native view identity를 유지합니다. - **Typed compound API**: `createSwipeDeck()`로 typed deck family를 만듭니다. -- **Motion preset**: Tinder 스타일 drag, rotation, dismiss, next-card, action motion을 조정합니다. -- **Reanimated-first**: gesture/animation 상태를 shared value와 worklet 중심으로 유지합니다. +- **Deck hooks**: state, action, event, animated overlay를 같은 factory에서 다룹니다. +- **Motion recipes**: gesture motion, programmatic action, undo restore를 따로 조정합니다. ## 설치 @@ -25,693 +26,56 @@ npm install @react-native-motion-kit/swipe-deck react-native-gesture-handler rea 사용 중인 React Native 또는 Expo 버전에 맞춰 Reanimated/Worklets 설정을 완료하세요. Babel 설정에서는 `react-native-worklets/plugin`을 마지막 Babel plugin으로 추가해야 합니다. -### 최소 지원 버전 - -| Package | Minimum | -| ------------------------------ | -------- | -| `react` | `18.0.0` | -| `react-native` | `0.75.0` | -| `react-native-gesture-handler` | `2.24.0` | -| `react-native-reanimated` | `4.0.0` | -| `react-native-worklets` | `0.5.0` | - ## 빠른 시작 -`Root`, `Card`, 외부 action, state, interaction hook이 같은 factory instance를 공유해야 한다면 item type에 맞는 typed deck family를 하나 만드세요. - ```tsx +import { Text, View } from 'react-native'; import { createSwipeDeck, SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; -const ProfileDeck = createSwipeDeck({ - motion: SwipeDeckMotion.tinder({ - drag: { - mode: 'horizontal', - liftYFactor: 0.15, - }, - rotation: { - mode: 'grab-position', - }, - dismiss: { - threshold: ({ width }) => width * 0.3, - velocityThreshold: 800, - minDuration: 300, - maxDuration: 520, - }, - }), -}); - -function ProfileDeckEvents() { - ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { - console.log(item, direction, source); - }); - ProfileDeck.useDeckEventListener('endReached', () => { - console.log('No more cards'); - }); - - return null; -} - -function ProfileDeckScreen() { - return ( - <> - - item.id} visibleCardCount={3}> - - {({ item, role, isActive }) => ( - - )} - - - - ); -} -``` - -## 핵심 개념 - -### Bounded forward window - -Deck은 전체 data set을 한 번에 렌더링하지 않습니다. - -기본적으로 active index부터 최대 **3개의 item-keyed card**를 mount합니다. - -1. current card -2. next card -3. next buffered card - -이 방식은 dismissed previous card를 다시 채우지 않으면서도 현재 카드와 들어오는 stack의 연속성을 유지합니다. - -`visibleCardCount`는 최대 예산입니다. - -- 기본값은 더 자연스러운 next-card promotion을 위해 `3`입니다. -- `1`보다 작은 값은 데이터가 충분할 때 `1`로 정규화됩니다. -- `visibleCardCount={1}`는 현재 카드만 렌더링하므로 next-card stack/promotion 시각 효과는 보이지 않습니다. -- `visibleCardCount={2}`는 현재 카드와 바로 다음 카드만 렌더링합니다. -- 실제 mount 개수는 active index부터 남은 데이터 개수를 넘지 않습니다. -- 짝수 값은 그대로 최대 예산으로 유지되며, 홀수로 올림 처리하지 않습니다. - -### 안정적인 item key - -`getKey`는 card identity 렌더링 계약의 일부이므로 필수입니다. - -Key는 다음 조건을 만족해야 합니다. - -- 같은 logical item은 swipe가 진행되어도 같은 key를 반환해야 합니다. -- 서로 다른 item은 같은 key를 공유하지 않는 것이 좋습니다. - -그래야 promoted card가 React Native view identity를 유지하고, 다른 item의 native text subtree를 재사용하지 않습니다. - -### Typed compound API - -주요 API는 compound/slot 기반입니다. - -- `Root`는 data, gesture state, deck-level option을 소유합니다. -- `Card`는 각 item의 rendering을 정의합니다. -- `createSwipeDeck()`는 typed component family를 만듭니다. - -```tsx -const ProfileDeck = createSwipeDeck(); - -function ProfileDeckScreen() { - return ( - item.id}> - {({ item }) => } - - ); -} -``` - -이렇게 하면 `Root`, `Card`, 앞으로 추가될 slot들이 JSX에서 generic을 반복하지 않고 같은 item type을 공유할 수 있습니다. - -`allowedDirections`는 하나의 Root에서 dismiss로 받아들일 방향을 제한합니다. - -```tsx - item.id} allowedDirections={['right']}> - {({ item }) => } - -``` - -- `allowedDirections`를 생략하면 양방향을 모두 허용합니다. -- `['right']` 또는 `['left']`는 해당 dismiss 방향만 허용합니다. -- `[]`는 card drag 자체는 가능하지만, 손을 놓으면 항상 snap-back되고 `swipeLeft()` / `swipeRight()`는 `false`를 반환합니다. -- `canSwipe`는 “dismiss action이 현재 받아들여질 수 있는가”를 의미합니다. 허용된 방향이 없으면 `false`가 됩니다. - -Hook과 외부 UI가 공유할 수 있는 이름 붙은 factory instance가 필요하면 이 factory pattern을 -사용하세요. - -### Deck hooks - -Factory hook은 Provider를 추가하거나 controller prop을 tree로 넘기지 않고도 deck 전체 state, -action, Reanimated interaction 값을 제공합니다. Hook은 `createSwipeDeck()`로 만든 factory -instance에 scope됩니다. - -```tsx -// profile-deck.ts -export const ProfileDeck = createSwipeDeck(); -``` - -Counter와 button처럼 React로 렌더링되는 UI에는 state/action hook을 사용하세요. - -```tsx -function ProfileDeckControls() { - const { activeIndex, count, canSwipe, canUndo, isCompleted } = ProfileDeck.useDeckState(); - const { swipeLeft, swipeRight, undo } = ProfileDeck.useDeckActions(); - const current = activeIndex >= 0 ? activeIndex + 1 : 0; - - return ( - - {isCompleted ? 'Done' : `${current} / ${count}`} - - Nope - - - Undo - - - Like - - - ); -} -``` - -Swipe 진행도에 따라 움직이는 animated UI에는 interaction shared value를 사용하세요. 이 값들은 -UI thread에서 업데이트되고 gesture의 매 frame마다 React rerender를 만들지 않습니다. - -```tsx -function SwipeReactionOverlay() { - const { signedProgress } = ProfileDeck.useDeckInteraction(); - - const likeStyle = useAnimatedStyle(() => { - const progress = Math.max(signedProgress.get(), 0); - - return { - opacity: progress, - transform: [{ scale: 0.9 + progress * 0.18 }], - }; - }); - - return ; -} -``` - -Swipe, undo, index 변경, end reached 같은 commit된 model event에는 event hook을 사용하세요. - -```tsx -function ProfileDeckEvents() { - const lastSwipe = ProfileDeck.useDeckEvent('swipe', null); - const endReached = ProfileDeck.useDeckEvent('endReached', false); - - ProfileDeck.useDeckEventListener('undo', ({ item }) => { - console.log('Restored', item); - }); - - return ( - - {endReached - ? 'Done' - : lastSwipe - ? `Last swipe: ${lastSwipe.direction} from ${lastSwipe.source}` - : 'No swipe yet'} - - ); -} -``` - -Event hook은 commit 단위의 latest-value API입니다. - -- `useDeckEvent()`는 event name별 최신 event 하나만 저장합니다. Event history가 아닙니다. -- `initialValue`는 event payload shape, `null`, `undefined`, 또는 `endReached`의 `false`로 제한됩니다. - `swipe` 같은 object event에는 `null`을 사용하세요. `{}`는 event payload 타입을 넓혀버리지 않도록 의도적으로 막습니다. -- Object initial value를 넘기면 `eventName`을 기준으로 contextual typing이 걸립니다. - 그래서 `useDeckEvent('swipe', { ... })`에서는 `item`, `index`, `direction`, `source`가 자동완성됩니다. -- Swipe event에는 `source: 'gesture' | 'programmatic'`이 포함됩니다. `gesture`는 사용자가 pan - gesture를 threshold/velocity 기준 이상으로 놓아서 commit된 경우입니다. `programmatic`은 - `actions.swipeLeft()` 또는 `actions.swipeRight()`로 commit된 경우이며, button을 뜻하지는 - 않습니다. 앱 UI에서 그 action을 button으로만 호출한다면 앱에서 button 의미로 매핑하면 됩니다. -- Initial value 없이 named deck을 읽어야 한다면 id를 두 번째 인자로 넘기면 됩니다. - `useDeckEvent('swipe', 'nearby')`처럼 사용할 수 있고, initial value도 함께 넘기는 경우에는 id가 세 번째 인자입니다. -- Root가 attach될 때와 detach될 때 event snapshot은 clear됩니다. 그래서 fresh/detached deck에서는 - hook이 `initialValue`를 반환합니다. -- `useDeckEventListener()`는 imperative 구독입니다. Mounted listener는 attach/detach를 지나도 - 같은 factory/id store에 구독된 상태를 유지하지만, active Root가 새 event를 emit할 때만 실행됩니다. -- Event는 commit path에서 emit됩니다. 같은 tick 안에서는 event payload가 해당 event의 source of truth이고, - 뒤따르는 `useDeckState()` render는 event 직후에 settle될 수 있습니다. - -그리고 같은 factory의 Root 주변에서 control을 렌더링합니다. - -```tsx -function ProfileDeckScreen() { - return ( - - item.id} undoEnabled> - - {({ item, isActive }) => ( - - - {isActive ? : null} - - )} - - - - - ); -} -``` - -- `useDeckState(id?)`는 `activeIndex`, `count`, `isCompleted`, `canSwipe`, `canUndo` 같은 React 렌더링용 deck state를 반환합니다. - 현재 item이 필요하면 deck state가 primitive하고 stable하게 유지되도록 사용자의 `data[activeIndex]`에서 직접 계산하세요. -- `useDeckActions(id?)`는 `swipeLeft()`, `swipeRight()`, `undo()` 같은 stable action을 반환합니다. - swipe action은 실행이 받아들여지면 `true`, deck이 아직 연결되지 않았거나 disabled/animating/layout 미측정/완료 상태라면 `false`를 반환합니다. - `undo()`는 deck이 완료된 뒤에도 `canUndo`가 true라면 `true`를 반환합니다. -- `useDeckInteraction(id?)`는 progress 기반 UI를 위한 Reanimated shared value를 반환합니다. - gesture progress는 UI thread에 남고, 매 frame React rerender를 만들지 않습니다. - - `interaction.phase`는 UI thread lifecycle signal입니다: - `idle | dragging | dismissing | undoing`. - - deck이 drag 중인지, dismiss 중인지, restore 중인지 알아야 하는 visual feedback에는 - `phase`를 사용하세요. swipe, undo, index change처럼 commit된 model event는 - `useDeckEvent` / `useDeckEventListener`를 사용하세요. - - `dismissing`은 dismiss가 accepted된 뒤 다음 item commit과 interaction reset이 끝날 때까지의 - lifecycle을 포함합니다. 즉, 화면 밖으로 나가는 animation frame만 의미하지는 않습니다. - - `interaction.direction`은 raw live drag/dismiss signal입니다(`-1 | 0 | 1`). - 외부 UI가 UI thread에서 accepted dismiss 방향(`left | right | null`)을 알아야 한다면 - `interaction.dismissDirection`을 사용하세요. `dismissDirection`은 lifecycle state이지, - commit된 `swipe` event payload가 아닙니다. - - programmatic springboard action은 action이 받아들여지는 즉시 `dismissing`으로 들어갑니다. - anticipation 중에는 실제 dismiss phase가 시작되기 전까지 `interaction.direction`이 neutral일 수 있지만, - `interaction.dismissDirection`에는 이미 accepted action direction이 들어있습니다. -- `useDeckEvent(eventName, initialValue?, id?)`는 React 렌더링용 최신 commit event를 반환합니다. - 첫 event 전과 deck이 detach된 뒤에는 `undefined` 또는 `initialValue`를 반환합니다. -- `useDeckEventListener(eventName, listener, id?)`는 앱 코드에서 별도 state를 만들지 않고 commit된 model event를 구독합니다. - listener hook은 unmount 시 자동으로 정리됩니다. - 성공한 swipe는 `swipe -> indexChange -> endReached` 순서로 emit됩니다. Undo는 - `undo -> indexChange` 순서로 emit됩니다. - -### Programmatic action motion - -`motion`은 손으로 drag하는 gesture 기반 deck feel을 제어합니다. `actionMotion`은 -`useDeckActions()`에서 실행하는 like/pass 버튼 같은 programmatic action만 제어합니다. -따라서 버튼으로 넘길 때의 움직임을 바꿔도 manual drag, threshold, flick 동작은 바뀌지 않습니다. - -```tsx -import { - createSwipeDeck, - SwipeDeckActionMotion, - SwipeDeckMotion, -} from '@react-native-motion-kit/swipe-deck'; +type Profile = { + id: string; + name: string; +}; const ProfileDeck = createSwipeDeck({ motion: SwipeDeckMotion.tinder(), - actionMotion: SwipeDeckActionMotion.springboard({ - anticipationDistance: ({ width }) => width * 0.04, - anticipationDuration: 80, - dismissDuration: 320, - }), -}); - -function LikeButton() { - const actions = ProfileDeck.useDeckActions(); - - return Like; -} -``` - -제공되는 recipe는 다음과 같습니다. - -- `SwipeDeckActionMotion.direct(options?)`: action 방향으로 바로 dismiss합니다. 생략한 값은 - deck에서 resolve된 dismiss duration, easing, offscreen multiplier를 재사용합니다. -- `SwipeDeckActionMotion.springboard(options?)`: 최종 방향의 반대쪽으로 살짝 움직인 뒤 화면 밖으로 - dismiss합니다. Anticipation 단계에서는 swipe progress를 중립으로 유지해 반대쪽 overlay가 - 순간적으로 보이지 않게 하며, `interaction.direction`도 dismiss phase가 시작될 때까지 - 중립으로 유지합니다. 생략한 dismiss 값은 deck에서 resolve된 dismiss duration, easing, - offscreen multiplier를 재사용합니다. - -특정 action 한 번만 override할 수도 있습니다. - -```tsx -actions.swipeLeft( - SwipeDeckActionMotion.direct({ - duration: 180, - }), -); -``` - -`actionMotion` 우선순위는 deep merge가 아니라 replacement 방식입니다. - -1. `createSwipeDeck({ actionMotion })`의 factory `actionMotion` -2. 해당 Root에서 factory default를 대체하는 `Root actionMotion` -3. `swipeLeft(recipe)` 또는 `swipeRight(recipe)`에 넘긴 per-call recipe - -Action은 callback으로 바로 넘겨도 안전합니다. React Native press event가 `swipeRight` 또는 -`swipeLeft`로 전달되면 그 event 인자는 무시하고 설정된 action motion을 사용합니다. - -### Undo / back swipe motion - -Undo는 opt-in입니다. 해당 deck에서 undo/back-swipe UX를 제공할 때 Root에 `undoEnabled`를 -추가하세요. 활성화되면 성공한 swipe마다 key/index/direction metadata entry 하나를 LIFO undo stack에 -저장합니다. Lookup은 현재 `data`에 대한 key-to-index map을 사용하고, data나 key가 바뀌면 invalid -entry를 prune합니다. 생략하면 성공한 swipe도 undo metadata를 저장하지 않고, `canUndo`는 `false`를 -유지하며, `actions.undo()`는 `false`를 반환합니다. - -Undo가 받아들여지면 deck은 main stack을 restored index 기준으로 잠시 렌더링하고, 그 실제 current -card를 카드가 원래 나갔던 방향에서 다시 중앙으로 들어오도록 animation한 뒤 restored index를 -commit합니다. - -```tsx -import { createSwipeDeck, SwipeDeckUndoMotion } from '@react-native-motion-kit/swipe-deck'; - -const ProfileDeck = createSwipeDeck({ - undoMotion: SwipeDeckUndoMotion.spring({ - springConfig: { - damping: 36, - stiffness: 300, - mass: 3, - }, - }), }); -function ProfileDeckExample() { +function ProfileCard({ profile }: { profile: Profile }) { return ( - item.id} undoEnabled> - {({ item }) => } - - ); -} - -function UndoButton() { - const state = ProfileDeck.useDeckState(); - const actions = ProfileDeck.useDeckActions(); - - return ( - - Undo - - ); -} -``` - -Undo는 기본적으로 비활성화되어 있으므로 undo control을 노출하지 않는 deck은 undo history 비용을 -내지 않습니다. `undoEnabled`를 켜면 key가 `data`에 남아 있는 동안 이전 swipe들을 LIFO 순서로 반복 -복원할 수 있습니다. 오래 실행되는 undo-enabled deck은 해당 entry가 undo되거나 data 변경으로 prune될 -때까지 accepted swipe마다 metadata entry 하나를 유지합니다. 기본 undo motion은 zero-duration -`SwipeDeckUndoMotion.timing()`을 사용하므로, 별도 motion을 설정하지 않으면 card를 즉시 복원합니다. - -제공되는 recipe는 다음과 같습니다. - -- `SwipeDeckUndoMotion.spring(options?)`: Reanimated `withSpring`으로 card를 복원합니다. -- `SwipeDeckUndoMotion.timing(options?)`: deterministic한 `withTiming`으로 card를 복원합니다. - -두 recipe는 공통으로 다음 옵션을 받습니다. - -- `from: 'auto' | 'left' | 'right'`: `auto`는 원래 swipe된 방향에서 card를 되돌립니다. -- `entryDistance`: 화면 밖 시작 거리를 number 또는 layout callback으로 지정합니다. - -`timing`은 `duration`, `easing`을 추가로 받고 기본 duration은 `0`입니다. `spring`은 `springConfig`를 받습니다. - -`undoMotion` 우선순위는 replacement 방식입니다. - -1. `createSwipeDeck({ undoMotion })`의 factory `undoMotion` -2. 해당 Root에서 factory default를 대체하는 `Root undoMotion` -3. `actions.undo(recipe)`에 넘긴 per-call recipe - -Undo도 callback으로 바로 넘겨도 안전합니다. React Native press event가 `undo`로 전달되면 그 -event 인자는 무시하고 설정된 undo motion을 사용합니다. Undo restore 중 public interaction 값 -(`progress`, `signedProgress`, `direction`, `translationX`, `translationY`)은 중립으로 유지되어, -progress 기반 swipe overlay가 순간적으로 깜빡이지 않습니다. - -같은 factory에서 여러 Root를 렌더링할 때만 `id`를 사용하세요. - -```tsx -function MultiDeckScreen() { - const nearbyState = ProfileDeck.useDeckState('nearby'); - - return ( - <> - item.id}> - {({ item }) => } - - - item.id}> - {({ item }) => } - - - {nearbyState.activeIndex + 1} - - ); -} -``` - -`id`는 item key가 아니라 factory 안에서 deck instance를 구분하는 namespace입니다. -서로 다른 factory는 둘 다 default id를 써도 충돌하지 않지만, 같은 factory와 같은 id의 Root 두 개가 동시에 mount되는 것은 잘못된 사용입니다. -id는 `"nearby"`, `"recommended"`처럼 화면/용도 단위의 안정적이고 적은 개수의 값으로 유지하세요. -Registry는 hook, action, interaction shared value의 identity를 안정적으로 유지하기 위해 factory lifetime 동안 id별 store를 유지합니다. -따라서 item id, timestamp, 매 render마다 바뀌는 값, 일회성 route 값에서 deck id를 만들지 마세요. -Factory와 id는 render path 밖에서 안정적으로 만들고 유지하세요. 동적 factory나 동적 id는 registry가 -의도적으로 안정적으로 유지하는 long-lived namespace를 계속 만들 수 있습니다. - -짧은 이름을 선호한다면 같은 factory instance에서 destructuring한 alias를 export해도 됩니다. - -```ts -const ProfileDeck = createSwipeDeck(); - -export const { - Root: ProfileDeckRoot, - Card: ProfileDeckCard, - useDeckState: useProfileDeckState, - useDeckActions: useProfileDeckActions, - useDeckInteraction: useProfileDeckInteraction, -} = ProfileDeck; -``` - -Factory는 deck family마다 한 번만 만들고 shared module에서 export하세요. Hook, action, interaction은 같은 factory instance에서 만든 Root에만 연결됩니다. item type과 id가 같아도 `createSwipeDeck()`를 다시 호출하면 별도 registry namespace가 만들어지므로, 한 factory의 hook으로 다른 factory의 Root를 제어할 수 없습니다. - -### 간단한 inline 사용 - -카드 렌더링만 필요하다면 factory instance를 만들지 않고 static API를 사용할 수 있습니다. -작은 inline deck에는 유용하지만, `useDeckState`, `useDeckActions`, `useDeckInteraction`, `useDeckEvent`, -`useDeckEventListener` 같은 factory hook은 노출하지 않습니다. -Static `Root`는 `id`를 받지 않습니다. 이름이 있는 deck state, action, interaction, model event가 필요하면 factory instance를 사용하세요. -Static `Root`와 `Card`는 factory처럼 item type을 함께 고정하지 않습니다. -따라서 card render prop에서 `item` 타입이 필요하면 `Card`에 item type을 직접 넘기세요. - -```tsx -import { SwipeDeck } from '@react-native-motion-kit/swipe-deck'; - -function InlineDeck() { - return ( - item.id}> - >{({ item }) => } - + + {profile.name} + ); } -``` - -## Motion - -### Swipe progress를 따라가는 것 - -Buffered next card는 swipe progress에 따라 애니메이션됩니다. - -- scale은 `1`에 가까워집니다. -- opacity는 `1`에 가까워집니다. -- `translateY`는 `0`에 가까워집니다. - -Swipe가 commit되면 다음 일이 일어납니다. - -- dismissed card는 화면 밖으로 나갑니다. -- promoted next card는 자신의 item identity를 유지합니다. -- 새로운 future item이 bounded window에 들어옵니다. - -이 동작은 `SwipeDeckMotion.tinder(...)` 같은 motion preset으로 조정합니다. - -### Drag mode - -`drag.mode`는 drag 중 active card가 손가락 translation을 어떻게 사용할지 제어합니다. - -| mode | Active card 움직임 | -| ------------ | ----------------------------------------------- | -| `free` | 손가락의 X/Y 이동을 모두 따라갑니다. | -| `horizontal` | 손가락의 Y 이동은 무시하고 X 이동만 반영합니다. | - -`drag.liftYFactor`는 active card를 `abs(translationX) * liftYFactor`만큼 위로 올립니다. - -```tsx -SwipeDeckMotion.tinder({ - drag: { - mode: 'horizontal', - liftYFactor: 0, - }, - rotation: { - mode: 'fixed', - origin: 'bottom-center', - }, -}); -``` - -`drag.mode: 'horizontal'`과 `rotation: { mode: 'fixed', origin: 'bottom-center' }`를 함께 쓰면 아래쪽 축을 기준으로 좌우로만 움직이는 느낌을 만들 수 있습니다. - -### Motion 우선순위 - -Motion 값은 다음 순서로 resolve됩니다. - -1. `createSwipeDeck({ motion })`의 factory motion default -2. 명시한 field만 부분 override하는 `Root motion` -3. `swipeThreshold`, `velocityThreshold` 같은 direct root prop - -Factory motion과 Root motion은 deep merge됩니다. `maxDegrees`, `inputRange` 같은 numeric rotation tuning은 Root motion에서 명시적으로 덮어쓰지 않는 한 유지되며, `rotation.mode`를 바꿔도 mode default로 reset되지 않습니다. - -### Motion preset 안정성 - -Motion preset은 module-scope constant 또는 `useMemo`로 안정적으로 유지하는 것을 권장합니다. - -안정적인 preset reference를 유지하면 Reanimated easing function이나 spring config object를 넘길 때 불필요한 gesture/worklet setup 재생성을 피할 수 있습니다. - -앱 전체 또는 deck family 단위 기본값은 render 바깥에서 정의하고 factory에 넘기세요. - -```tsx -const profileDeckMotion = SwipeDeckMotion.tinder({ - rotation: { - mode: 'fixed', - origin: 'bottom-center', - }, - dismiss: { - threshold: ({ width }) => width * 0.3, - }, -}); - -const ProfileDeck = createSwipeDeck({ - motion: profileDeckMotion, -}); -``` - -Motion이 props 또는 state에 의존한다면 call site에서 config를 memoize하세요. - -```tsx -function ProfileScreen({ slowMotion }: { slowMotion: boolean }) { - const motion = useMemo( - () => - SwipeDeckMotion.tinder({ - dismiss: { - minDuration: slowMotion ? 420 : 220, - maxDuration: slowMotion ? 700 : 420, - }, - }), - [slowMotion], - ); +export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { return ( - item.id} motion={motion}> + item.id} visibleCardCount={3}> {({ item }) => } ); } ``` -### Rotation - -`rotation.mode`는 회전 기준점을 고정할지, gesture 시작 위치에서 계산할지 제어합니다. 기본값은 Tinder-like `mode: 'grab-position'`입니다. Rotation 설정은 swipe 인식을 바꾸지 않습니다. 다만 `dismiss.duration`을 생략하면 release target을 회전된 card geometry로 계산하기 때문에 velocity-derived dismiss timing은 달라질 수 있습니다. - -#### Fixed rotation - -기본 grab-position 동작 대신 모든 gesture가 같은 anchor를 사용해야 한다면 fixed rotation을 사용하세요. - -| origin | 느낌 | -| --------------- | --------------------------------------------------------------------------------------------------------------------------- | -| `center` | 카드 중앙을 기준으로 회전합니다. | -| `top-center` | 카드의 위쪽 중앙 edge를 기준으로 회전합니다. 위쪽은 거의 고정된 축처럼 느껴지고, 아래쪽은 더 큰 원호를 그리며 움직입니다. | -| `bottom-center` | 카드의 아래쪽 중앙 edge를 기준으로 회전합니다. 아래쪽은 거의 고정된 축처럼 느껴지고, 위쪽은 더 큰 원호를 그리며 움직입니다. | - -```tsx -SwipeDeckMotion.tinder({ - rotation: { - mode: 'fixed', - origin: 'bottom-center', - direction: 'default', - }, -}); -``` +Control, animated overlay, commit event가 필요하다면 같은 factory Root 주변에서 +`ProfileDeck.useDeckState()`, `ProfileDeck.useDeckActions()`, +`ProfileDeck.useDeckInteraction()`, `ProfileDeck.useDeckEvent()`, +`ProfileDeck.useDeckEventListener()`를 사용하세요. -같은 고정 anchor에서 회전 부호만 반대로 만들고 싶다면 `direction: 'reverse'`를 사용하세요. +## 문서 -#### Grab-position rotation - -Grab-position rotation은 기본 Tinder-like 동작입니다. `maxDegrees`, `inputRange` 같은 공통 rotation 값만 덮어쓰고 싶을 때 명시적으로 사용하세요. - -```tsx -SwipeDeckMotion.tinder({ - rotation: { - mode: 'grab-position', - direction: 'default', - maxDegrees: 25, - }, -}); -``` - -카드 위쪽에서 잡으면 top-center anchor와 default rotation sign을 사용합니다. 아래쪽에서 잡으면 bottom-center anchor와 reverse rotation sign을 사용합니다. `direction: 'reverse'`를 주면 이 mapping을 뒤집습니다. `maxDegrees`, `inputRange`는 계속 설정할 수 있지만, 이 mode에서는 고정 `origin`은 의도적으로 받지 않습니다. - -같은 `maxDegrees` 값도 edge 기반 rotation에서는 더 강하게 느껴집니다. 그래서 Tinder preset은 `maxDegrees`를 직접 지정하지 않은 경우 `top-center`, `bottom-center`, `grab-position`에 더 작은 기본 회전 각도를 사용합니다. - -### Dismiss target - -`dismiss.offscreenMultiplier`는 성공한 swipe의 release target을 제어합니다. - -- 성공한 swipe는 항상 화면 밖으로 dismiss됩니다. -- 기본값 `1.5`는 카드를 `clearDistance * 1.5` 위치로 보냅니다. -- `clearDistance`는 손을 놓는 순간의 실제 swipe direction, rotation mode, rotation direction, gesture start position으로 계산합니다. -- `1`보다 작은 값은 `1`로 정규화됩니다. -- `duration`을 생략하면 release velocity와 target까지 남은 거리로 duration을 계산합니다. - -대부분의 앱은 이 옵션을 생략하고 다음 값만 조정하면 됩니다. - -- `threshold` -- `velocityThreshold` -- `duration`, `minDuration`, `maxDuration` -- Reanimated `easing` - -`dismiss.easing`은 Reanimated `withTiming`과 같은 easing 값을 받습니다. -기본값은 `Easing.out(Easing.cubic)`입니다. - -## Visible card budget - -```tsx -function CurrentOnlyDeck() { - return ( - item.id} visibleCardCount={1}> - {/* 가장 가벼운 예산: 현재 카드만 렌더링, next-card promotion 시각 효과 없음 */} - - ); -} - -function DefaultDeck() { - return ( - item.id} visibleCardCount={3}> - {/* default budget for smoother next-card promotion */} - - ); -} - -function DeepStackDeck() { - return ( - item.id} visibleCardCount={9}> - {/* deeper stacked UI */} - - ); -} -``` +공개 사이트를 준비하는 동안 전체 가이드는 이 저장소의 `docs/` 아래에서 관리합니다. -| 입력 | Mount되는 card | -| ---------------------------------------------- | -------------------------------------------------------- | -| `visibleCardCount={1}` | 현재 카드만 | -| `visibleCardCount={2}` | 데이터가 충분할 때 최대 `2`개 | -| 남은 데이터가 10개이고 `visibleCardCount={20}` | 최대 남은 10개 | -| 짝수 값 | 최대 예산으로 그대로 유지되며, 홀수로 올림 처리하지 않음 | +- [개요](docs/docs/1.x/ko/guide/getting-started/overview.mdx) +- [빠른 시작](docs/docs/1.x/ko/guide/getting-started/quick-start.mdx) +- [API 레퍼런스](docs/docs/1.x/ko/guide/usage/api-reference.mdx) +- [AI 사용 가이드](docs/docs/1.x/ko/guide/getting-started/ai.mdx) -## 기여하기 +AI가 읽기 좋은 문서는 docs build 시 생성됩니다. -- [Development workflow](CONTRIBUTING.md#development-workflow) -- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request) -- [Code of conduct](CODE_OF_CONDUCT.md) +- [llms.txt](docs/doc_build/ko/llms.txt) +- [llms-full.txt](docs/doc_build/ko/llms-full.txt) ## 라이선스 diff --git a/README.md b/README.md index 7711ef0..a5ad9fb 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,16 @@ React Native Motion Kit logo -High-performance Tinder-style swipe deck and swipe cards for React Native, powered by Reanimated and Gesture Handler. +High-performance Tinder-style swipe deck and swipe cards for React Native, +powered by Reanimated, Worklets, and Gesture Handler. ## Highlights - **Small render window**: mounts only the active card and a bounded forward stack. - **Item-keyed rendering**: promoted cards keep their React Native view identity. - **Typed compound API**: create one typed deck family with `createSwipeDeck()`. -- **Motion presets**: tune Tinder-style drag, rotation, dismiss, next-card, and action motion. -- **Reanimated-first**: gesture and animation state stays on shared values/worklets. +- **Deck hooks**: read state, run actions, subscribe to events, and drive animated overlays. +- **Motion recipes**: tune gesture motion, programmatic actions, and undo restores separately. ## Installation @@ -25,700 +26,57 @@ npm install @react-native-motion-kit/swipe-deck react-native-gesture-handler rea Then follow the Reanimated/Worklets setup for your React Native or Expo version. Make sure `react-native-worklets/plugin` is the last Babel plugin. -### Minimum versions - -| Package | Minimum | -| ------------------------------ | -------- | -| `react` | `18.0.0` | -| `react-native` | `0.75.0` | -| `react-native-gesture-handler` | `2.24.0` | -| `react-native-reanimated` | `4.0.0` | -| `react-native-worklets` | `0.5.0` | - -## Quick start - -Create one typed deck family for your item type when you want `Root`, `Card`, external actions, state, and interaction hooks to share the same factory instance. +## Quick Start ```tsx +import { Text, View } from 'react-native'; import { createSwipeDeck, SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; -const ProfileDeck = createSwipeDeck({ - motion: SwipeDeckMotion.tinder({ - drag: { - mode: 'horizontal', - liftYFactor: 0.15, - }, - rotation: { - mode: 'grab-position', - }, - dismiss: { - threshold: ({ width }) => width * 0.3, - velocityThreshold: 800, - minDuration: 300, - maxDuration: 520, - }, - }), -}); - -function ProfileDeckEvents() { - ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { - console.log(item, direction, source); - }); - ProfileDeck.useDeckEventListener('endReached', () => { - console.log('No more cards'); - }); - - return null; -} - -function ProfileDeckScreen() { - return ( - <> - - item.id} visibleCardCount={3}> - - {({ item, role, isActive }) => ( - - )} - - - - ); -} -``` - -## Core concepts - -### Bounded forward window - -The deck never renders the whole data set. - -By default, it mounts up to **3 item-keyed cards** from the active index forward: - -1. current card -2. next card -3. next buffered card - -This keeps the active/incoming stack continuous without backfilling dismissed previous cards. - -`visibleCardCount` is a maximum budget: - -- the default is `3` for smoother next-card promotion; -- values below `1` normalize to `1` when enough data exists; -- `visibleCardCount={1}` renders only the current card, so no next-card stack/promotion visual is shown; -- `visibleCardCount={2}` renders only the current card and the immediate next card; -- the mounted count never exceeds the remaining data from the active index; -- even values are kept as-is and are not rounded up. - -### Stable item keys - -`getKey` is required because card identity is part of the rendering contract. - -A key must be: - -- stable for the same logical item across swipes; -- unique across different items. - -That lets promoted cards keep their React Native view identity instead of reusing another item's native text subtree. - -### Typed compound API - -The primary API is compound/slot based: - -- `Root` owns data, gesture state, and deck-level options. -- `Card` defines how each item is rendered. -- `createSwipeDeck()` creates a typed component family. - -```tsx -const ProfileDeck = createSwipeDeck(); - -function ProfileDeckScreen() { - return ( - item.id}> - {({ item }) => } - - ); -} -``` - -This keeps `Root`, `Card`, and future slots on the same item type without repeating generics in JSX. - -`allowedDirections` limits which directions can be accepted as a dismiss for one Root: - -```tsx - item.id} allowedDirections={['right']}> - {({ item }) => } - -``` - -- Omit `allowedDirections` to allow both directions. -- `['right']` or `['left']` allows only that dismiss direction. -- `[]` still lets the card drag, but every release snaps back and `swipeLeft()` / `swipeRight()` return `false`. -- `canSwipe` means “a dismiss action can currently be accepted”; it becomes `false` when no direction is allowed. - -Use this factory pattern when the deck needs a named instance that can be shared by hooks and external UI. - -### Deck hooks - -Factory hooks expose deck-wide state, actions, and Reanimated interaction values without adding a -Provider or passing a controller prop through your tree. They are scoped to the factory instance -created by `createSwipeDeck()`. - -```tsx -// profile-deck.ts -export const ProfileDeck = createSwipeDeck(); -``` - -Use state/actions for React-rendered UI such as counters and buttons: - -```tsx -function ProfileDeckControls() { - const { activeIndex, count, canSwipe, canUndo, isCompleted } = ProfileDeck.useDeckState(); - const { swipeLeft, swipeRight, undo } = ProfileDeck.useDeckActions(); - const current = activeIndex >= 0 ? activeIndex + 1 : 0; - - return ( - - {isCompleted ? 'Done' : `${current} / ${count}`} - - Nope - - - Undo - - - Like - - - ); -} -``` - -Use interaction shared values for progress-driven animated UI. These values update on the UI thread -and do not rerender React every gesture frame: - -```tsx -function SwipeReactionOverlay() { - const { signedProgress } = ProfileDeck.useDeckInteraction(); - - const likeStyle = useAnimatedStyle(() => { - const progress = Math.max(signedProgress.get(), 0); - - return { - opacity: progress, - transform: [{ scale: 0.9 + progress * 0.18 }], - }; - }); - - return ; -} -``` - -Use event hooks for committed model events such as swipe, undo, index changes, and end reached: - -```tsx -function ProfileDeckEvents() { - const lastSwipe = ProfileDeck.useDeckEvent('swipe', null); - const endReached = ProfileDeck.useDeckEvent('endReached', false); - - ProfileDeck.useDeckEventListener('undo', ({ item }) => { - console.log('Restored', item); - }); - - return ( - - {endReached - ? 'Done' - : lastSwipe - ? `Last swipe: ${lastSwipe.direction} from ${lastSwipe.source}` - : 'No swipe yet'} - - ); -} -``` - -Event hooks are commit-level, latest-value APIs: - -- `useDeckEvent()` stores only the latest event for each event name. It is not an event history. -- `initialValue` is restricted to the event payload shape, `null`, `undefined`, or `false` for - `endReached`. Use `null` for object events such as `swipe`; `{}` is intentionally rejected so - the event payload type is not widened away. -- If you do pass an object initial value, the object is contextually typed from `eventName`, so - `useDeckEvent('swipe', { ... })` autocompletes `item`, `index`, `direction`, and `source`. -- Swipe events include `source: 'gesture' | 'programmatic'`. `gesture` means the user committed the - swipe by releasing a pan gesture past threshold/velocity. `programmatic` means the swipe was - committed through `actions.swipeLeft()` or `actions.swipeRight()`; apps may map that to a button - only when those actions are triggered by buttons in that app. -- For a named deck without an initial value, pass the id as the second argument: - `useDeckEvent('swipe', 'nearby')`. If you also pass an initial value, the id remains the third - argument. -- Event snapshots are cleared when a Root attaches and when it detaches, so the hook returns - `initialValue` for a fresh or detached deck. -- `useDeckEventListener()` is imperative. Mounted listeners stay subscribed to the factory/id - store across attach/detach, but they only run when the active Root emits a new event. -- Events are emitted from the commit path. During the same tick, the event payload is the source of - truth for that event; a following `useDeckState()` render can settle just after the event. - -Then render those controls around the same factory Root: - -```tsx -function ProfileDeckScreen() { - return ( - - item.id} undoEnabled> - - {({ item, isActive }) => ( - - - {isActive ? : null} - - )} - - - - - ); -} -``` - -- `useDeckState(id?)` returns React-rendered deck state such as `activeIndex`, `count`, - `isCompleted`, `canSwipe`, and `canUndo`. Derive the active item from your own - `data[activeIndex]` when needed so deck state stays primitive and stable. -- `useDeckActions(id?)` returns stable actions such as `swipeLeft()`, `swipeRight()`, - and `undo()`. - Swipe actions return `true` when accepted and `false` when the deck is unattached, disabled, - animating, unmeasured, or completed. `undo()` returns `true` when `canUndo` is true, including - after the deck is completed. -- `useDeckInteraction(id?)` returns Reanimated shared values for progress-driven UI. Gesture - progress stays on the UI thread and does not rerender React every frame. - - `interaction.phase` is a UI-thread lifecycle signal: - `idle | dragging | dismissing | undoing`. - - Use `phase` for visual feedback that needs to know whether the deck is actively dragging, - dismissing, or restoring. Use `useDeckEvent` / `useDeckEventListener` for committed model - events such as swipe, undo, and index changes. - - `dismissing` covers the accepted dismiss lifecycle until the deck has committed the next item - and reset interaction values. It is intentionally broader than only the offscreen animation - frames. - - `interaction.direction` is the raw live drag/dismiss signal (`-1 | 0 | 1`). Use - `interaction.dismissDirection` when external UI needs the accepted dismiss side - (`left | right | null`) on the UI thread. `dismissDirection` is lifecycle state, not the - committed `swipe` event payload. - - Programmatic springboard actions enter `dismissing` as soon as the action is accepted. During - anticipation, `interaction.direction` can still be neutral until the real dismiss phase starts, - while `interaction.dismissDirection` already contains the accepted action direction. -- `useDeckEvent(eventName, initialValue?, id?)` returns the latest committed deck event for - React-rendered UI. It returns `undefined` or `initialValue` before the first event and after the - deck detaches. -- `useDeckEventListener(eventName, listener, id?)` subscribes to committed model events without - forcing React state into your app code. Listener hooks clean up automatically on unmount. - Successful swipe events emit in `swipe -> indexChange -> endReached` order. Undo emits - `undo -> indexChange`. - -### Programmatic action motion - -`motion` controls gesture-driven deck feel. `actionMotion` controls only programmatic actions -from `useDeckActions()`, such as a like/pass button. This keeps button-triggered motion tunable -without changing manual drag, threshold, or flick behavior. - -```tsx -import { - createSwipeDeck, - SwipeDeckActionMotion, - SwipeDeckMotion, -} from '@react-native-motion-kit/swipe-deck'; +type Profile = { + id: string; + name: string; +}; const ProfileDeck = createSwipeDeck({ motion: SwipeDeckMotion.tinder(), - actionMotion: SwipeDeckActionMotion.springboard({ - anticipationDistance: ({ width }) => width * 0.04, - anticipationDuration: 80, - dismissDuration: 320, - }), -}); - -function LikeButton() { - const actions = ProfileDeck.useDeckActions(); - - return Like; -} -``` - -Available recipes: - -- `SwipeDeckActionMotion.direct(options?)`: dismisses immediately toward the action direction. - Omitted values reuse the deck's resolved dismiss duration, easing, and offscreen multiplier. -- `SwipeDeckActionMotion.springboard(options?)`: moves a little in the opposite direction first, - then dismisses offscreen. During the anticipation phase, swipe progress stays neutral so - opposite-side overlays do not flash; `interaction.direction` also stays neutral until the - dismiss phase starts. Omitted dismiss values reuse the deck's resolved dismiss duration, easing, - and offscreen multiplier. - -You can override one action call without changing the Root or factory default: - -```tsx -actions.swipeLeft( - SwipeDeckActionMotion.direct({ - duration: 180, - }), -); -``` - -`actionMotion` precedence is replacement-based, not deep-merged: - -1. factory `actionMotion` from `createSwipeDeck({ actionMotion })`; -2. `Root actionMotion`, which replaces the factory default for that Root; -3. per-call recipe passed to `swipeLeft(recipe)` or `swipeRight(recipe)`. - -Actions are callback-safe. If a React Native press event is passed to `swipeRight` or `swipeLeft`, -the event argument is ignored and the configured action motion is used. - -### Undo / back swipe motion - -Undo is opt-in. Add `undoEnabled` to a Root when that deck exposes undo/back-swipe UX. When -enabled, each successful swipe stores one key/index/direction metadata entry in a LIFO undo stack. -Lookups use a key-to-index map for the current `data`, and invalid entries are pruned when data or -keys change. When omitted, successful swipes do not store undo metadata, `canUndo` stays `false`, -and `actions.undo()` returns `false`. - -When accepted, the deck temporarily renders the main stack from the restored index, animates that -real current card from the side it originally left, then commits the restored index. - -```tsx -import { createSwipeDeck, SwipeDeckUndoMotion } from '@react-native-motion-kit/swipe-deck'; - -const ProfileDeck = createSwipeDeck({ - undoMotion: SwipeDeckUndoMotion.spring({ - springConfig: { - damping: 36, - stiffness: 300, - mass: 3, - }, - }), }); -function ProfileDeckExample() { +function ProfileCard({ profile }: { profile: Profile }) { return ( - item.id} undoEnabled> - {({ item }) => } - - ); -} - -function UndoButton() { - const state = ProfileDeck.useDeckState(); - const actions = ProfileDeck.useDeckActions(); - - return ( - - Undo - - ); -} -``` - -Undo is disabled by default so decks that do not expose undo controls pay no undo-history cost. -With `undoEnabled`, repeated undo restores previously swiped cards in LIFO order while their keys -remain present in `data`. Long-running undo-enabled decks retain one metadata entry per accepted -swipe until that entry is undone or pruned by data changes. Undo motion uses zero-duration -`SwipeDeckUndoMotion.timing()` by default, so the card is restored immediately unless you opt into a -custom motion. - -Available recipes: - -- `SwipeDeckUndoMotion.spring(options?)`: restores the card with Reanimated `withSpring`. -- `SwipeDeckUndoMotion.timing(options?)`: restores the card with deterministic `withTiming`. - -Both recipes accept: - -- `from: 'auto' | 'left' | 'right'`: `auto` returns from the original swipe direction. -- `entryDistance`: number or layout callback for the offscreen start distance. - -`timing` also accepts `duration` and `easing`; its default duration is `0`. `spring` accepts `springConfig`. - -Undo motion precedence is replacement-based: - -1. factory `undoMotion` from `createSwipeDeck({ undoMotion })`; -2. `Root undoMotion`, which replaces the factory default for that Root; -3. per-call recipe passed to `actions.undo(recipe)`. - -Undo is callback-safe. If a React Native press event is passed to `undo`, the event argument is -ignored and the configured undo motion is used. During undo restore, public interaction values -(`progress`, `signedProgress`, `direction`, `translationX`, `translationY`) remain neutral, so -progress-driven swipe overlays do not flash. - -Use an `id` only when you render multiple roots from the same factory: - -```tsx -function MultiDeckScreen() { - const nearbyState = ProfileDeck.useDeckState('nearby'); - - return ( - <> - item.id}> - {({ item }) => } - - - item.id}> - {({ item }) => } - - - {nearbyState.activeIndex + 1} - - ); -} -``` - -`id` is a factory-scoped deck namespace, not an item key. Two different factories can both -use the default id safely, but two mounted roots from the same factory and same id are invalid. -Keep ids stable and low-cardinality, such as screen-level names (`"nearby"` or `"recommended"`). -The registry keeps one store per id for the lifetime of the factory so hooks, actions, and -interaction shared values stay stable. Do not derive ids from item ids, timestamps, values that -change per render, or one-off route values. Create factories and ids outside render paths; dynamic -factories or dynamic ids create long-lived namespaces that the registry intentionally keeps stable. - -If you prefer shorter names, destructure from the same factory instance and export aliases: - -```ts -const ProfileDeck = createSwipeDeck(); - -export const { - Root: ProfileDeckRoot, - Card: ProfileDeckCard, - useDeckState: useProfileDeckState, - useDeckActions: useProfileDeckActions, - useDeckInteraction: useProfileDeckInteraction, -} = ProfileDeck; -``` - -Create the factory once per deck family and export it from a shared module. Hooks, actions, and interactions only connect to Roots created by that same factory instance. Calling `createSwipeDeck()` again creates a separate registry namespace even if the item type and id are the same, so hooks from one factory cannot control Roots from another. - -### Simple inline usage - -If you only need card rendering, you can use the static API without creating a factory instance. -This is useful for small inline decks, but it does not expose factory hooks such as -`useDeckState`, `useDeckActions`, `useDeckInteraction`, `useDeckEvent`, or -`useDeckEventListener`. -Static `Root` does not accept `id`; use a factory instance when you need named deck state, -actions, interactions, or model events. -Because static `Root` and `Card` do not share a factory type, pass the item type to `Card` when you want typed render props. - -```tsx -import { SwipeDeck } from '@react-native-motion-kit/swipe-deck'; - -function InlineDeck() { - return ( - item.id}> - >{({ item }) => } - + + {profile.name} + ); } -``` - -## Motion - -### What follows swipe progress? - -Buffered next cards animate with swipe progress: - -- scale moves toward `1`; -- opacity moves toward `1`; -- `translateY` moves toward `0`. - -When a swipe commits: - -- the dismissed card exits offscreen; -- the promoted next card keeps its item identity; -- a new future item enters the bounded window. - -Tune this with motion presets such as `SwipeDeckMotion.tinder(...)`. - -### Drag mode - -`drag.mode` controls how the active card uses finger translation while dragging. - -| mode | Active card movement | -| ------------ | ---------------------------------------------------------------------------- | -| `free` | Follows both horizontal and vertical finger movement. | -| `horizontal` | Ignores vertical finger movement and moves from horizontal translation only. | - -`drag.liftYFactor` lifts the active card upward by `abs(translationX) * liftYFactor`. - -```tsx -SwipeDeckMotion.tinder({ - drag: { - mode: 'horizontal', - liftYFactor: 0, - }, - rotation: { - mode: 'fixed', - origin: 'bottom-center', - }, -}); -``` - -Use `drag.mode: 'horizontal'` with `rotation: { mode: 'fixed', origin: 'bottom-center' }` for a lower-anchor, left/right-only feel. - -### Motion precedence - -Motion values are resolved in this order: - -1. factory motion defaults from `createSwipeDeck({ motion })`; -2. `Root motion`, which partially overrides only the fields it specifies; -3. direct root props such as `swipeThreshold` and `velocityThreshold`. - -Factory and Root motion are deep-merged. Numeric rotation tuning such as `maxDegrees` and `inputRange` is preserved unless the Root motion explicitly overrides it; changing `rotation.mode` does not reset those values to mode defaults. - -### Motion preset stability - -Prefer keeping motion presets stable with a module-scope constant or `useMemo`. - -Stable preset references avoid unnecessary gesture/worklet setup when you pass Reanimated easing functions or spring config objects. - -For app-wide or deck-family defaults, define motion outside render and pass it to the factory: - -```tsx -const profileDeckMotion = SwipeDeckMotion.tinder({ - rotation: { - mode: 'fixed', - origin: 'bottom-center', - }, - dismiss: { - threshold: ({ width }) => width * 0.3, - }, -}); - -const ProfileDeck = createSwipeDeck({ - motion: profileDeckMotion, -}); -``` - -When motion depends on props or state, memoize the config at the call site: - -```tsx -function ProfileScreen({ slowMotion }: { slowMotion: boolean }) { - const motion = useMemo( - () => - SwipeDeckMotion.tinder({ - dismiss: { - minDuration: slowMotion ? 420 : 220, - maxDuration: slowMotion ? 700 : 420, - }, - }), - [slowMotion], - ); +export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { return ( - item.id} motion={motion}> + item.id} visibleCardCount={3}> {({ item }) => } ); } ``` -### Rotation - -`rotation.mode` controls whether the rotation anchor is fixed or resolved from the gesture start position. The default is Tinder-like `mode: 'grab-position'`. Rotation settings do not change swipe recognition. When `dismiss.duration` is omitted, however, velocity-derived dismiss timing can change because the release target is computed from the rotated card geometry. - -#### Fixed rotation - -Use fixed rotation when every gesture should use the same anchor instead of the default grab-position behavior. - -| origin | Feel | -| --------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `center` | Rotates around the card center. | -| `top-center` | Rotates around the top-center edge, so the upper part feels anchored and the bottom travels through a larger arc. | -| `bottom-center` | Rotates around the bottom-center edge, so the lower part feels almost anchored and the top travels through a larger arc. | - -```tsx -SwipeDeckMotion.tinder({ - rotation: { - mode: 'fixed', - origin: 'bottom-center', - direction: 'default', - }, -}); -``` +Use `ProfileDeck.useDeckState()`, `ProfileDeck.useDeckActions()`, +`ProfileDeck.useDeckInteraction()`, `ProfileDeck.useDeckEvent()`, and +`ProfileDeck.useDeckEventListener()` around the same factory Root when you need +controls, animated overlays, or committed model events. -Use `direction: 'reverse'` when you want the same fixed anchor but the opposite rotation sign. +## Docs -#### Grab-position rotation - -Grab-position rotation is the default Tinder-like behavior. Use it explicitly when you want to override only shared rotation values such as `maxDegrees` or `inputRange`. - -```tsx -SwipeDeckMotion.tinder({ - rotation: { - mode: 'grab-position', - direction: 'default', - maxDegrees: 25, - }, -}); -``` - -Upper-half grabs use a top-center anchor with the default rotation sign. Lower-half grabs use a bottom-center anchor with the reverse rotation sign. `direction: 'reverse'` flips that mapping. `maxDegrees` and `inputRange` are still configurable, but fixed `origin` is intentionally omitted in this mode. - -Because the same `maxDegrees` feels stronger with edge-based rotation, the Tinder preset uses a smaller default rotation angle for `top-center`, `bottom-center`, and `grab-position` unless you provide `maxDegrees` explicitly. - -### Dismiss target - -`dismiss.offscreenMultiplier` controls the successful swipe release target. - -- Successful swipes always dismiss offscreen. -- The default `1.5` sends the card to `clearDistance * 1.5`. -- `clearDistance` is resolved at release from the actual swipe direction, rotation mode, rotation direction, and gesture start position. -- Values below `1` are normalized to `1`. -- If `duration` is omitted, velocity-derived timing is computed from the remaining distance to this target. - -Most apps can skip this option and tune only: - -- `threshold` -- `velocityThreshold` -- `duration`, `minDuration`, `maxDuration` -- Reanimated `easing` - -`dismiss.easing` accepts the same easing value as Reanimated `withTiming`. -The default is `Easing.out(Easing.cubic)`. - -## Visible card budget - -```tsx -function CurrentOnlyDeck() { - return ( - item.id} visibleCardCount={1}> - {/* lightest budget: current card only, no next-card promotion visual */} - - ); -} - -function DefaultDeck() { - return ( - item.id} visibleCardCount={3}> - {/* default budget for smoother next-card promotion */} - - ); -} - -function DeepStackDeck() { - return ( - item.id} visibleCardCount={9}> - {/* deeper stacked UI */} - - ); -} -``` +Full guides are maintained in this repository under `docs/` while the public +site is being prepared. -| Input | Mounted cards | -| ----------------------------------------------- | ------------------------------------------- | -| `visibleCardCount={1}` | Only the current card. | -| `visibleCardCount={2}` | Up to `2` cards when enough data exists. | -| `visibleCardCount={20}` with 10 remaining items | At most those 10 remaining items. | -| even values | Kept as the maximum budget; not rounded up. | +- [Overview](docs/docs/1.x/en/guide/getting-started/overview.mdx) +- [Quick Start](docs/docs/1.x/en/guide/getting-started/quick-start.mdx) +- [API Reference](docs/docs/1.x/en/guide/usage/api-reference.mdx) +- [AI Usage Guide](docs/docs/1.x/en/guide/getting-started/ai.mdx) -## Contributing +AI-readable docs are generated during the docs build: -- [Development workflow](CONTRIBUTING.md#development-workflow) -- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request) -- [Code of conduct](CODE_OF_CONDUCT.md) +- [llms.txt](docs/doc_build/llms.txt) +- [llms-full.txt](docs/doc_build/llms-full.txt) ## License diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..a8be826 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,14 @@ +# Local +.DS_Store +*.local +*.log* + +# Dist +node_modules/ +dist/ +doc_build/ + +# IDE +.vscode/* +!.vscode/extensions.json +.idea/ diff --git a/docs/docs/1.x/en/_nav.json b/docs/docs/1.x/en/_nav.json new file mode 100644 index 0000000..a6c3ff0 --- /dev/null +++ b/docs/docs/1.x/en/_nav.json @@ -0,0 +1,7 @@ +[ + { + "text": "Guide", + "link": "/guide/getting-started/overview.html", + "activeMatch": "/guide/" + } +] diff --git a/docs/docs/1.x/en/guide/_meta.json b/docs/docs/1.x/en/guide/_meta.json new file mode 100644 index 0000000..169ecfc --- /dev/null +++ b/docs/docs/1.x/en/guide/_meta.json @@ -0,0 +1,12 @@ +[ + { + "type": "dir", + "name": "getting-started", + "label": "Getting Started" + }, + { + "type": "dir", + "name": "usage", + "label": "Usage" + } +] diff --git a/docs/docs/1.x/en/guide/getting-started/_meta.json b/docs/docs/1.x/en/guide/getting-started/_meta.json new file mode 100644 index 0000000..17fcb81 --- /dev/null +++ b/docs/docs/1.x/en/guide/getting-started/_meta.json @@ -0,0 +1 @@ +["overview", "installation", "quick-start", "ai"] diff --git a/docs/docs/1.x/en/guide/getting-started/ai.mdx b/docs/docs/1.x/en/guide/getting-started/ai.mdx new file mode 100644 index 0000000..ac99258 --- /dev/null +++ b/docs/docs/1.x/en/guide/getting-started/ai.mdx @@ -0,0 +1,73 @@ +# AI Usage Guide + +When generating code with an AI assistant, give it these rules. They prevent the +most common integration mistakes. + +## LLM-Readable Docs + +This site publishes Markdown files for AI tools during `rspress build`. + +- [`/llms.txt`](/llms.txt): compact documentation index with page links and + summaries. Use this when the agent should choose only the relevant pages. +- [`/llms-full.txt`](/llms-full.txt): full English documentation in one + Markdown file. Use this when the agent has enough context budget and should + understand the whole API surface. +- [`/ko/llms.txt`](/ko/llms.txt) and + [`/ko/llms-full.txt`](/ko/llms-full.txt): Korean equivalents. + +Before the public docs URL is available, build locally and read the generated +files from `docs/doc_build/`. + +## Recommended Prompt + +```md +Use @react-native-motion-kit/swipe-deck. + +- Create one createSwipeDeck() factory outside render. +- Render Root and Card from that same factory. +- Use a stable getKey(item) value, usually item.id. +- Use factory hooks from the same factory instance as the Root. +- Use useDeckState/useDeckActions for React UI. +- Use useDeckInteraction for Reanimated UI-thread overlays. +- Use useDeckEvent/useDeckEventListener for committed swipe, undo, indexChange, + and endReached events. +- Add undoEnabled only when the UI exposes undo. +- Do not derive deck id from item ids, timestamps, or values that change each render. +``` + +## Good Factory Shape + +```tsx +// profile-deck.ts +import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +export type Profile = { + id: string; + name: string; +}; + +export const ProfileDeck = createSwipeDeck(); +``` + +```tsx +// ProfileDeckScreen.tsx +import { ProfileDeck } from './profile-deck'; + +export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { + return ( + item.id}> + {({ item }) => } + + ); +} +``` + +## Common Mistakes To Avoid + +- Do not call `createSwipeDeck()` inside a component render path. +- Do not use hooks from one factory to control a Root from another factory. +- Do not use array indexes as keys when items can be inserted, removed, or + reordered. +- Do not use `useDeckInteraction` for committed business logic; use events for + committed model changes. +- Do not turn on `undoEnabled` unless the deck exposes undo controls. diff --git a/docs/docs/1.x/en/guide/getting-started/installation.mdx b/docs/docs/1.x/en/guide/getting-started/installation.mdx new file mode 100644 index 0000000..5bfa73e --- /dev/null +++ b/docs/docs/1.x/en/guide/getting-started/installation.mdx @@ -0,0 +1,47 @@ +# Installation + +Install the package and its React Native gesture/animation peer dependencies: + +```sh +npm install @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets +``` + +You can use your package manager of choice. For Yarn: + +```sh +yarn add @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets +``` + +## Minimum Versions + +| Package | Minimum | +| ------------------------------ | -------- | +| `react` | `18.0.0` | +| `react-native` | `0.75.0` | +| `react-native-gesture-handler` | `2.24.0` | +| `react-native-reanimated` | `4.0.0` | +| `react-native-worklets` | `0.5.0` | + +## React Native Setup + +Follow the Reanimated and Worklets setup instructions for your React Native or +Expo version. In Babel config, make sure `react-native-worklets/plugin` is the +last Babel plugin. + +```js title="babel.config.js" +module.exports = { + presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-worklets/plugin'], +}; +``` + +Gesture Handler also expects your app surface to be under +`GestureHandlerRootView`. + +```tsx +import { GestureHandlerRootView } from 'react-native-gesture-handler'; + +export function AppRoot() { + return {/* app */}; +} +``` diff --git a/docs/docs/1.x/en/guide/getting-started/overview.mdx b/docs/docs/1.x/en/guide/getting-started/overview.mdx new file mode 100644 index 0000000..8db095d --- /dev/null +++ b/docs/docs/1.x/en/guide/getting-started/overview.mdx @@ -0,0 +1,65 @@ +# Overview + +`@react-native-motion-kit/swipe-deck` is a high-performance Tinder-style swipe +deck for React Native. It is built for apps that need a smooth card stack, +programmatic like/pass buttons, progress-driven overlays, and a typed API that +does not make every screen wire a controller by hand. + +## Mental Model + +The main API is a typed compound component family: + +- `createSwipeDeck()` creates one deck family for an item type. +- `Root` owns data, gesture state, motion options, actions, events, and layout. +- `Card` renders each mounted item in the bounded forward window. +- Factory hooks read state, run actions, subscribe to events, and expose + Reanimated shared values for animated UI. + +```tsx +import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +type Profile = { + id: string; + name: string; +}; + +const ProfileDeck = createSwipeDeck(); +``` + +Create the factory once, export it from a shared module, and use hooks from that +same factory around a matching `Root`. + +## Why It Exists + +- The deck does not render the full data set. +- Promoted cards keep item identity through stable keys. +- Manual drag motion and programmatic action motion can be tuned separately. +- Interaction shared values update on the UI thread without rerendering React + every gesture frame. +- Events describe committed model changes such as swipes, undo, index changes, + and end reached. + +## Core Concepts + +### Bounded Forward Window + +By default, the deck mounts up to three cards from the active index forward: + +1. current card +2. next card +3. next buffered card + +This keeps the stack continuous without backfilling dismissed cards. Use +`visibleCardCount` to tune that maximum budget. + +### Stable Item Keys + +`getKey` is required. The same logical item must return the same key across +swipes, and different items should not share a key. The deck uses that key as +the React identity for each mounted card. + +### Committed Events vs Live Interaction + +Use event hooks for committed model changes. Use interaction shared values for +frame-synchronous visual feedback while a card is dragging, dismissing, or +restoring. diff --git a/docs/docs/1.x/en/guide/getting-started/quick-start.mdx b/docs/docs/1.x/en/guide/getting-started/quick-start.mdx new file mode 100644 index 0000000..857c5b3 --- /dev/null +++ b/docs/docs/1.x/en/guide/getting-started/quick-start.mdx @@ -0,0 +1,103 @@ +# Quick Start + +Create one typed deck family for your item type. `Root`, `Card`, hooks, actions, +and events from that factory share the same registry namespace. + +```tsx +import { Text, View } from 'react-native'; +import { createSwipeDeck, SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; + +type Profile = { + id: string; + name: string; + bio: string; +}; + +const ProfileDeck = createSwipeDeck({ + motion: SwipeDeckMotion.tinder({ + drag: { + mode: 'horizontal', + liftYFactor: 0.15, + }, + rotation: { + mode: 'grab-position', + }, + dismiss: { + threshold: ({ width }) => width * 0.3, + velocityThreshold: 800, + minDuration: 300, + maxDuration: 520, + }, + }), +}); + +function ProfileDeckEvents() { + ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { + console.log(item.name, direction, source); + }); + + ProfileDeck.useDeckEventListener('endReached', () => { + console.log('No more cards'); + }); + + return null; +} + +function ProfileCard({ profile, active }: { profile: Profile; active: boolean }) { + return ( + + {active ? 'Now viewing' : 'Up next'} + {profile.name} + {profile.bio} + + ); +} + +export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { + return ( + <> + + item.id} visibleCardCount={3}> + + {({ item, isActive }) => } + + + + ); +} +``` + +## Add Controls + +Use state and action hooks for React-rendered UI such as counters and buttons. + +```tsx +function ProfileDeckControls() { + const { activeIndex, count, canSwipe, canUndo, isCompleted } = ProfileDeck.useDeckState(); + const { swipeLeft, swipeRight, undo } = ProfileDeck.useDeckActions(); + const current = activeIndex >= 0 ? activeIndex + 1 : 0; + + return ( + + {isCompleted ? 'Done' : `${current} / ${count}`} + + Nope + + + Undo + + + Like + + + ); +} +``` + +Enable undo tracking only when you expose undo UX: + +```tsx + item.id} undoEnabled> + {({ item }) => } + +``` diff --git a/docs/docs/1.x/en/guide/usage/_meta.json b/docs/docs/1.x/en/guide/usage/_meta.json new file mode 100644 index 0000000..cd32246 --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/_meta.json @@ -0,0 +1,11 @@ +[ + "basic-usage", + "deck-hooks", + "handling-events", + "programmatic-actions", + "undo", + "motion", + "multi-instance-management", + "visible-card-budget", + "api-reference" +] diff --git a/docs/docs/1.x/en/guide/usage/api-reference.mdx b/docs/docs/1.x/en/guide/usage/api-reference.mdx new file mode 100644 index 0000000..461caa0 --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/api-reference.mdx @@ -0,0 +1,133 @@ +--- +description: Reference the main exports, Root and Card props, hooks, events, and built-in motion helpers. +--- + +# API Reference + +## Main Exports + +```ts +export { + SwipeDeckActionMotion, + SwipeDeckMotion, + createSwipeDeck, + SwipeDeck, + SwipeDeckUndoMotion, +} from '@react-native-motion-kit/swipe-deck'; +``` + +[Go to source](https://github.com/react-native-motion-kit/react-native-swipe-deck/blob/main/src/index.tsx) + +## `createSwipeDeck(config?)` + +Creates a typed deck family. + +```tsx +const ProfileDeck = createSwipeDeck({ + motion: SwipeDeckMotion.tinder(), +}); +``` + +The returned instance includes: + +- `Root` +- `Card` +- `useDeckState` +- `useDeckActions` +- `useDeckInteraction` +- `useDeckEvent` +- `useDeckEventListener` + +## `SwipeDeck.Root` Props + +[Go to source](https://github.com/react-native-motion-kit/react-native-swipe-deck/blob/main/src/types.ts) + +| Prop | Type | Notes | +| ------------------- | ------------------------------------ | ----------------------------------------- | +| `id` | `string` | Factory-scoped deck namespace. | +| `data` | `readonly T[]` | Ordered items rendered by the deck. | +| `getKey` | `(item: T, index: number) => string` | Required stable item key. | +| `initialIndex` | `number` | Initial active item index. | +| `disabled` | `boolean` | Disables accepted actions and gestures. | +| `allowedDirections` | `AllowedDirections` | Limits accepted dismiss directions. | +| `swipeThreshold` | `SwipeThreshold` | Root-level dismiss threshold override. | +| `velocityThreshold` | `number` | Root-level flick threshold override. | +| `motion` | `SwipeDeckMotionPreset` | Gesture-driven motion preset. | +| `actionMotion` | `SwipeDeckActionMotionRecipe` | Programmatic swipe motion recipe. | +| `undoMotion` | `SwipeDeckUndoMotionRecipe` | Programmatic undo restore motion recipe. | +| `undoEnabled` | `boolean` | Enables undo history tracking. | +| `visibleCardCount` | `number` | Maximum mounted forward card budget. | +| `containerStyle` | `StyleProp` | Style applied to the deck container. | +| `children` | `ReactNode` | Must include a matching `SwipeDeck.Card`. | + +```ts +type AllowedDirections = readonly ('left' | 'right')[]; +type SwipeThreshold = number | ((layout: SwipeDeckLayout) => number); +``` + +Static `SwipeDeck.Root` accepts the same props except `id`. + +## `SwipeDeck.Card` Props + +| Prop | Type | Notes | +| ---------- | ----------------------------------------- | --------------------------------------- | +| `style` | `StyleProp` | Style applied to the absolute card. | +| `children` | `(info: SwipeRenderInfo) => ReactNode` | Renders one item in the bounded window. | + +`SwipeRenderInfo` includes `item`, `index`, `offset`, `role`, and +`isActive`. + +## State And Actions + +```ts +type SwipeDeckState = { + activeIndex: number; + count: number; + isCompleted: boolean; + canSwipe: boolean; + canUndo: boolean; +}; + +type SwipeDeckActions = { + swipeLeft: SwipeDeckAction; + swipeRight: SwipeDeckAction; + undo: SwipeDeckUndoAction; +}; +``` + +Programmatic actions return `boolean` to tell whether the action was accepted. + +## Interaction + +```ts +type SwipeDeckInteractionPhase = 'idle' | 'dragging' | 'dismissing' | 'undoing'; +``` + +`useDeckInteraction()` returns Reanimated shared values for `progress`, +`signedProgress`, `direction`, `dismissDirection`, `translationX`, +`translationY`, `isDragging`, and `phase`. + +## Events + +```ts +type SwipeDeckEventMap = { + swipe: SwipeEvent; + undo: UndoEvent; + indexChange: { index: number }; + endReached: true; +}; +``` + +Use `useDeckEvent` for latest event snapshots and `useDeckEventListener` for +imperative subscriptions. + +## Motion Helpers + +- `SwipeDeckMotion.tinder(options?)` +- `SwipeDeckActionMotion.direct(options?)` +- `SwipeDeckActionMotion.springboard(options?)` +- `SwipeDeckUndoMotion.spring(options?)` +- `SwipeDeckUndoMotion.timing(options?)` + +For behavior details, prefer the guide pages over treating this reference as an +onboarding surface. diff --git a/docs/docs/1.x/en/guide/usage/basic-usage.mdx b/docs/docs/1.x/en/guide/usage/basic-usage.mdx new file mode 100644 index 0000000..9687430 --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/basic-usage.mdx @@ -0,0 +1,80 @@ +--- +description: Learn when to use the factory API, Card render info, allowed directions, and the static SwipeDeck API. +--- + +# Basic Usage + +## Factory API + +The factory API is the primary API. Use it when a deck needs hooks, actions, +events, interaction shared values, or multiple named instances. + +```tsx +import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +const ProfileDeck = createSwipeDeck(); + +function Screen() { + return ( + item.id}> + {({ item }) => } + + ); +} +``` + +The factory keeps `Root`, `Card`, and hooks on the same item type without +repeating generics in JSX. + +## Card Render Info + +`Card` receives render info for each mounted item. + +| Field | Meaning | +| ---------- | ------------------------------------------------------- | +| `item` | The item from your `data` array. | +| `index` | The item index in `data`. | +| `offset` | The card offset from the active item. | +| `role` | `'current'` for the active card, `'next'` for buffered. | +| `isActive` | `true` only for the active card. | + +```tsx + + {({ item, role, isActive }) => } + +``` + +## Allowed Directions + +`allowedDirections` limits which directions can be accepted as a dismiss. + +```tsx + item.id} allowedDirections={['right']}> + {({ item }) => } + +``` + +- Omit `allowedDirections` to allow both directions. +- Pass `['left']` or `['right']` to allow only one dismiss direction. +- Pass `[]` to allow dragging but reject every dismiss release and + programmatic swipe action. + +## Static API + +Use the static API only when you need inline card rendering and do not need +factory hooks. + +```tsx +import { SwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +function InlineDeck() { + return ( + item.id}> + >{({ item }) => } + + ); +} +``` + +Static `Root` does not accept `id`, and static `Root` / `Card` do not share a +factory type. Pass the item type to `Card` when you want typed render props. diff --git a/docs/docs/1.x/en/guide/usage/deck-hooks.mdx b/docs/docs/1.x/en/guide/usage/deck-hooks.mdx new file mode 100644 index 0000000..c0b3751 --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/deck-hooks.mdx @@ -0,0 +1,100 @@ +# Deck Hooks + +Factory hooks expose deck state, actions, and interaction values without a +provider prop or controller object. + +```tsx +const ProfileDeck = createSwipeDeck(); +``` + +## `useDeckState(id?)` + +Returns React-rendered deck state. + +| Field | Meaning | +| ------------- | --------------------------------------------------------- | +| `activeIndex` | Current active item index, or `-1` before attach. | +| `count` | Total item count in the attached deck. | +| `isCompleted` | Whether the deck has consumed all items. | +| `canSwipe` | Whether a dismiss action can currently be accepted. | +| `canUndo` | Whether the latest valid swipe can currently be restored. | + +```tsx +function Counter() { + const { activeIndex, count, isCompleted } = ProfileDeck.useDeckState(); + const current = activeIndex >= 0 ? activeIndex + 1 : 0; + + return {isCompleted ? 'Done' : `${current} / ${count}`}; +} +``` + +Derive the current item from your own `data[activeIndex]` when needed. Deck +state stays primitive and stable. + +## `useDeckActions(id?)` + +Returns stable action callbacks. + +```tsx +function Controls() { + const { canSwipe, canUndo } = ProfileDeck.useDeckState(); + const { swipeLeft, swipeRight, undo } = ProfileDeck.useDeckActions(); + + return ( + + + Nope + + + Undo + + + Like + + + ); +} +``` + +`swipeLeft()` and `swipeRight()` return `true` when accepted and `false` when the +deck is unattached, disabled, animating, unmeasured, completed, or the direction +is not allowed. `undo()` returns `true` when `canUndo` is true, including after +the deck is completed. + +## `useDeckInteraction(id?)` + +Returns Reanimated shared values for progress-driven UI. + +```tsx +function SwipeReactionOverlay() { + const { signedProgress } = ProfileDeck.useDeckInteraction(); + + const likeStyle = useAnimatedStyle(() => { + const progress = Math.max(signedProgress.get(), 0); + + return { + opacity: progress, + transform: [{ scale: 0.9 + progress * 0.18 }], + }; + }); + + return ; +} +``` + +Interaction values update on the UI thread and do not rerender React every +gesture frame. + +| Value | Meaning | +| ------------------ | ------------------------------------------------------ | +| `progress` | Absolute swipe progress from `0` to `1`. | +| `signedProgress` | Signed progress from `-1` to `1`. | +| `direction` | Raw live direction signal: `-1`, `0`, or `1`. | +| `dismissDirection` | Accepted dismiss side: `'left'`, `'right'`, or `null`. | +| `translationX` | Active card horizontal translation. | +| `translationY` | Active card vertical translation. | +| `isDragging` | Whether the deck is dragging or dismissing. | +| `phase` | `idle`, `dragging`, `dismissing`, or `undoing`. | + +Use `phase` for frame-synchronous visual feedback. Use event hooks for +committed state changes. diff --git a/docs/docs/1.x/en/guide/usage/handling-events.mdx b/docs/docs/1.x/en/guide/usage/handling-events.mdx new file mode 100644 index 0000000..0d10682 --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/handling-events.mdx @@ -0,0 +1,80 @@ +# Handling Events + +Event hooks describe committed model events. They are separate from live +interaction values. + +## Event Map + +| Event | Payload | +| ------------- | ------------------------------------ | +| `swipe` | `{ item, index, direction, source }` | +| `undo` | `{ item, index, direction }` | +| `indexChange` | `{ index }` | +| `endReached` | `true` | + +`swipe.source` is `'gesture'` when the user releases a pan gesture past the +configured threshold or velocity policy. It is `'programmatic'` when the swipe +commits through `actions.swipeLeft()` or `actions.swipeRight()`. + +## `useDeckEvent` + +`useDeckEvent(eventName, initialValue?, id?)` returns the latest committed event +snapshot for React-rendered UI. + +```tsx +function ProfileDeckStatus() { + const lastSwipe = ProfileDeck.useDeckEvent('swipe', null); + const endReached = ProfileDeck.useDeckEvent('endReached', false); + + return ( + + {endReached + ? 'Done' + : lastSwipe + ? `Last swipe: ${lastSwipe.direction} from ${lastSwipe.source}` + : 'No swipe yet'} + + ); +} +``` + +This is a latest-value API, not an event history. Event snapshots clear when a +Root attaches or detaches. + +Initial values are intentionally restricted to the event payload shape, `null`, +`undefined`, or `false` for `endReached`. Use `null` for object events such as +`swipe`. + +For a named deck without an initial value, pass the id as the second argument: + +```tsx +const lastSwipe = ProfileDeck.useDeckEvent('swipe', 'nearby'); +``` + +If you also pass an initial value, id is the third argument. + +## `useDeckEventListener` + +`useDeckEventListener(eventName, listener, id?)` subscribes imperatively without +forcing React state into your app code. + +```tsx +function ProfileDeckEvents() { + ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { + console.log(item, direction, source); + }); + + ProfileDeck.useDeckEventListener('undo', ({ item }) => { + console.log('Restored', item); + }); + + ProfileDeck.useDeckEventListener('endReached', () => { + console.log('No more cards'); + }); + + return null; +} +``` + +Successful swipe events emit in `swipe -> indexChange -> endReached` order. Undo +emits `undo -> indexChange`. diff --git a/docs/docs/1.x/en/guide/usage/motion.mdx b/docs/docs/1.x/en/guide/usage/motion.mdx new file mode 100644 index 0000000..cad636f --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/motion.mdx @@ -0,0 +1,139 @@ +# Motion + +`SwipeDeckMotion.tinder()` is the built-in motion preset for Tinder-style card +stacks. + +```tsx +import { SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; + +const motion = SwipeDeckMotion.tinder({ + drag: { + mode: 'horizontal', + liftYFactor: 0.15, + }, + rotation: { + mode: 'grab-position', + }, + dismiss: { + threshold: ({ width }) => width * 0.3, + velocityThreshold: 800, + minDuration: 300, + maxDuration: 520, + }, +}); +``` + +## What Follows Swipe Progress? + +Buffered next cards animate with swipe progress: + +- scale moves toward `1` +- opacity moves toward `1` +- `translateY` moves toward `0` + +When a swipe commits, the dismissed card exits offscreen, the promoted next card +keeps its item identity, and a new future item enters the bounded window. + +## Drag Mode + +`drag.mode` controls how the active card uses finger translation while dragging. + +| Mode | Active card movement | +| ------------ | ---------------------------------------------------------- | +| `free` | Follows both horizontal and vertical finger movement. | +| `horizontal` | Ignores vertical finger movement and uses horizontal drag. | + +`drag.liftYFactor` lifts the active card upward by +`abs(translationX) * liftYFactor`. + +## Rotation + +`rotation.mode` controls whether the rotation anchor is fixed or resolved from +the gesture start position. + +### Fixed Rotation + +Use fixed rotation when every gesture should use the same anchor. + +| Origin | Feel | +| --------------- | ------------------------------- | +| `center` | Rotates around the card center. | +| `top-center` | The upper edge feels anchored. | +| `bottom-center` | The lower edge feels anchored. | + +```tsx +SwipeDeckMotion.tinder({ + rotation: { + mode: 'fixed', + origin: 'bottom-center', + direction: 'default', + }, +}); +``` + +Use `direction: 'reverse'` when you want the same fixed anchor with the opposite +rotation sign. + +### Grab-Position Rotation + +Grab-position rotation is the default Tinder-like behavior. + +```tsx +SwipeDeckMotion.tinder({ + rotation: { + mode: 'grab-position', + direction: 'default', + maxDegrees: 25, + }, +}); +``` + +Upper-half grabs use a top-center anchor with the default rotation sign. +Lower-half grabs use a bottom-center anchor with the reverse rotation sign. +`direction: 'reverse'` flips that mapping. + +## Dismiss Target + +`dismiss.offscreenMultiplier` controls the successful swipe release target. + +- Successful swipes always dismiss offscreen. +- The default `1.5` sends the card to `clearDistance * 1.5`. +- `clearDistance` is resolved at release from the swipe direction, rotation + mode, rotation direction, and gesture start position. +- Values below `1` normalize to `1`. +- If `duration` is omitted, velocity-derived timing is computed from the + remaining distance to this target. + +Most apps can tune only `threshold`, `velocityThreshold`, `duration`, +`minDuration`, `maxDuration`, and `easing`. + +## Motion Precedence + +Motion values resolve in this order: + +1. factory motion defaults from `createSwipeDeck({ motion })` +2. `Root motion`, partially overriding only the fields it specifies +3. direct root props such as `swipeThreshold` and `velocityThreshold` + +Factory and Root motion are deep-merged. Changing `rotation.mode` does not reset +numeric rotation tuning such as `maxDegrees` or `inputRange`. + +## Preset Stability + +Keep motion presets stable with a module-scope constant or `useMemo`. + +```tsx +const profileDeckMotion = SwipeDeckMotion.tinder({ + rotation: { + mode: 'fixed', + origin: 'bottom-center', + }, + dismiss: { + threshold: ({ width }) => width * 0.3, + }, +}); + +const ProfileDeck = createSwipeDeck({ + motion: profileDeckMotion, +}); +``` diff --git a/docs/docs/1.x/en/guide/usage/multi-instance-management.mdx b/docs/docs/1.x/en/guide/usage/multi-instance-management.mdx new file mode 100644 index 0000000..a47066a --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/multi-instance-management.mdx @@ -0,0 +1,58 @@ +# Multi-Instance Management + +Use `id` only when you render multiple roots from the same factory. + +```tsx +function MultiDeckScreen() { + const nearbyState = ProfileDeck.useDeckState('nearby'); + + return ( + <> + item.id}> + {({ item }) => } + + + item.id}> + {({ item }) => } + + + {nearbyState.activeIndex + 1} + + ); +} +``` + +`id` is a factory-scoped deck namespace, not an item key. Two different +factories can both use the default id safely, but two mounted roots from the +same factory and same id are invalid. + +## Id Rules + +- Keep ids stable and low-cardinality. +- Use screen-level names such as `"nearby"` or `"recommended"`. +- Do not derive ids from item ids, timestamps, values that change per render, or + one-off route values. +- Create factories and ids outside render paths. + +The registry keeps one store per id for the lifetime of the factory so hooks, +actions, and interaction shared values stay stable. + +## Same-Factory Rule + +Hooks, actions, and interactions only connect to Roots created by the same +factory instance. + +```ts +const ProfileDeck = createSwipeDeck(); + +export const { + Root: ProfileDeckRoot, + Card: ProfileDeckCard, + useDeckState: useProfileDeckState, + useDeckActions: useProfileDeckActions, + useDeckInteraction: useProfileDeckInteraction, +} = ProfileDeck; +``` + +Calling `createSwipeDeck()` again creates a separate registry namespace +even when the item type and id are the same. diff --git a/docs/docs/1.x/en/guide/usage/programmatic-actions.mdx b/docs/docs/1.x/en/guide/usage/programmatic-actions.mdx new file mode 100644 index 0000000..f84f0aa --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/programmatic-actions.mdx @@ -0,0 +1,79 @@ +# Programmatic Actions + +Programmatic actions come from `useDeckActions()`. Use them for buttons, +external controls, or any app logic that should dismiss the active card. + +```tsx +function LikeButton() { + const { swipeRight } = ProfileDeck.useDeckActions(); + + return ( + + Like + + ); +} +``` + +## Action Motion + +`motion` controls manual drag feel. `actionMotion` controls only programmatic +actions such as `swipeLeft()` and `swipeRight()`. + +```tsx +import { + createSwipeDeck, + SwipeDeckActionMotion, + SwipeDeckMotion, +} from '@react-native-motion-kit/swipe-deck'; + +const ProfileDeck = createSwipeDeck({ + motion: SwipeDeckMotion.tinder(), + actionMotion: SwipeDeckActionMotion.springboard({ + anticipationDistance: ({ width }) => width * 0.04, + anticipationDuration: 80, + dismissDuration: 320, + }), +}); +``` + +## Recipes + +### `SwipeDeckActionMotion.direct(options?)` + +Dismisses immediately toward the action direction. Omitted values reuse the +deck's resolved dismiss duration, easing, and offscreen multiplier. + +```tsx +actions.swipeLeft( + SwipeDeckActionMotion.direct({ + duration: 180, + }), +); +``` + +### `SwipeDeckActionMotion.springboard(options?)` + +Moves a little in the opposite direction first, then dismisses offscreen. During +anticipation, swipe progress and live direction stay neutral so opposite-side +overlays do not flash. + +```tsx +const actionMotion = SwipeDeckActionMotion.springboard({ + anticipationDistance: 40, + anticipationDuration: 160, + dismissDuration: 500, +}); +``` + +## Precedence + +`actionMotion` is replacement-based, not deep-merged: + +1. factory `actionMotion` from `createSwipeDeck({ actionMotion })` +2. `Root actionMotion`, replacing the factory default for that Root +3. per-call recipe passed to `swipeLeft(recipe)` or `swipeRight(recipe)` + +Actions are callback-safe. If a React Native press event is passed to +`swipeRight` or `swipeLeft`, the event argument is ignored and the configured +action motion is used. diff --git a/docs/docs/1.x/en/guide/usage/undo.mdx b/docs/docs/1.x/en/guide/usage/undo.mdx new file mode 100644 index 0000000..155dd9d --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/undo.mdx @@ -0,0 +1,77 @@ +# Undo + +Undo is opt-in. Add `undoEnabled` to a Root when the deck exposes undo or +back-swipe UX. + +```tsx + item.id} undoEnabled> + {({ item }) => } + +``` + +When enabled, each successful swipe stores one key/index/direction metadata +entry in a LIFO undo stack. Lookups use a key-to-index map for the current +`data`, and invalid entries are pruned when data or keys change. + +When omitted, successful swipes do not store undo metadata, `canUndo` stays +`false`, and `actions.undo()` returns `false`. + +## Undo Button + +```tsx +function UndoButton() { + const { canUndo } = ProfileDeck.useDeckState(); + const { undo } = ProfileDeck.useDeckActions(); + + return ( + + Undo + + ); +} +``` + +## Undo Motion + +Use `SwipeDeckUndoMotion` to customize how a restored card enters. + +```tsx +import { createSwipeDeck, SwipeDeckUndoMotion } from '@react-native-motion-kit/swipe-deck'; + +const ProfileDeck = createSwipeDeck({ + undoMotion: SwipeDeckUndoMotion.spring({ + springConfig: { + damping: 36, + stiffness: 300, + mass: 3, + }, + }), +}); +``` + +Available recipes: + +- `SwipeDeckUndoMotion.spring(options?)`: restores with Reanimated `withSpring`. +- `SwipeDeckUndoMotion.timing(options?)`: restores with deterministic + `withTiming`. + +Both recipes accept: + +- `from: 'auto' | 'left' | 'right'`: `auto` returns from the original swipe + direction. +- `entryDistance`: number or layout callback for the offscreen start distance. + +`timing` also accepts `duration` and `easing`. Its default duration is `0`, so +the card is restored immediately unless you opt into custom motion. `spring` +accepts `springConfig`. + +## Precedence + +`undoMotion` is replacement-based: + +1. factory `undoMotion` from `createSwipeDeck({ undoMotion })` +2. `Root undoMotion`, replacing the factory default for that Root +3. per-call recipe passed to `actions.undo(recipe)` + +Undo is callback-safe. If a React Native press event is passed to `undo`, the +event argument is ignored. diff --git a/docs/docs/1.x/en/guide/usage/visible-card-budget.mdx b/docs/docs/1.x/en/guide/usage/visible-card-budget.mdx new file mode 100644 index 0000000..c14867c --- /dev/null +++ b/docs/docs/1.x/en/guide/usage/visible-card-budget.mdx @@ -0,0 +1,45 @@ +# Visible Card Budget + +`visibleCardCount` controls the maximum number of cards kept mounted from the +active card forward. + +```tsx +function CurrentOnlyDeck() { + return ( + item.id} visibleCardCount={1}> + {/* current card only */} + + ); +} + +function DefaultDeck() { + return ( + item.id} visibleCardCount={3}> + {/* default budget for smoother next-card promotion */} + + ); +} + +function DeepStackDeck() { + return ( + item.id} visibleCardCount={9}> + {/* deeper stacked UI */} + + ); +} +``` + +## Rules + +| Input | Mounted cards | +| ----------------------------------------------- | ---------------------------------------- | +| `visibleCardCount={1}` | Only the current card. | +| `visibleCardCount={2}` | Up to two cards when enough data exists. | +| `visibleCardCount={20}` with 10 remaining items | At most those 10 remaining items. | +| Even values | Kept as the maximum budget. | + +Values below `1` normalize to `1` when enough forward data exists. The mounted +count never exceeds the remaining data from the active index. + +Keep the budget as small as your UI allows. Increase it only when your design +visibly exposes a deeper stack or intentionally needs more cards pre-mounted. diff --git a/docs/docs/1.x/en/index.md b/docs/docs/1.x/en/index.md new file mode 100644 index 0000000..a3dba6b --- /dev/null +++ b/docs/docs/1.x/en/index.md @@ -0,0 +1,50 @@ +--- +pageType: home +description: High-performance Tinder-style swipe deck documentation for React Native apps. + +hero: + name: React Native Swipe Deck + text: Tinder-style swipe decks for React Native + tagline: High-performance card stacks powered by Reanimated, Worklets, and Gesture Handler + actions: + - theme: brand + text: Quick Start + link: /guide/getting-started/quick-start.html + - theme: alt + text: GitHub + link: https://github.com/react-native-motion-kit/react-native-swipe-deck + image: + src: /logo.png + alt: React Native Motion Kit logo +features: + - title: Gesture-First Deck UX + details: Drag, flick, threshold, and direction controls tuned for Tinder-style card stacks. + icon: 🤌 + - title: High-Performance Animations + details: Smooth card motion powered by React Native Reanimated shared values and worklets. + icon: 🏎️ + - title: Bounded Render Window + details: Mount only the active card and a small forward stack instead of the whole data set. + icon: 🪟 + - title: Item-Stable Promotion + details: Stable item keys let promoted cards keep their React Native view identity. + icon: 🧬 + - title: Typed Compound API + details: Create one typed deck family with Root, Card, hooks, actions, and events. + icon: 🧠 + - title: External Control API + details: Trigger swipeLeft, swipeRight, and undo from buttons or other UI components. + icon: 🎛️ + - title: Motion Recipes + details: Tune gesture motion, programmatic actions, and undo restores independently. + icon: 🎨 + - title: Multi-Instance Management + details: Manage multiple deck roots independently with stable factory-scoped IDs. + icon: 🧩 + - title: Undo Support + details: Opt into back-swipe UX with action-safe undo motion and LIFO history. + icon: ↩️ + - title: Easy-to-Use API + details: Start with Root and Card, then add hooks only when controls or events need them. + icon: 🪄 +--- diff --git a/docs/docs/1.x/ko/_nav.json b/docs/docs/1.x/ko/_nav.json new file mode 100644 index 0000000..4761e3e --- /dev/null +++ b/docs/docs/1.x/ko/_nav.json @@ -0,0 +1,7 @@ +[ + { + "text": "가이드", + "link": "/guide/getting-started/overview.html", + "activeMatch": "/guide/" + } +] diff --git a/docs/docs/1.x/ko/guide/_meta.json b/docs/docs/1.x/ko/guide/_meta.json new file mode 100644 index 0000000..9cc7069 --- /dev/null +++ b/docs/docs/1.x/ko/guide/_meta.json @@ -0,0 +1,12 @@ +[ + { + "type": "dir", + "name": "getting-started", + "label": "시작하기" + }, + { + "type": "dir", + "name": "usage", + "label": "사용법" + } +] diff --git a/docs/docs/1.x/ko/guide/getting-started/_meta.json b/docs/docs/1.x/ko/guide/getting-started/_meta.json new file mode 100644 index 0000000..17fcb81 --- /dev/null +++ b/docs/docs/1.x/ko/guide/getting-started/_meta.json @@ -0,0 +1 @@ +["overview", "installation", "quick-start", "ai"] diff --git a/docs/docs/1.x/ko/guide/getting-started/ai.mdx b/docs/docs/1.x/ko/guide/getting-started/ai.mdx new file mode 100644 index 0000000..c4c18b4 --- /dev/null +++ b/docs/docs/1.x/ko/guide/getting-started/ai.mdx @@ -0,0 +1,69 @@ +# AI 사용 가이드 + +AI assistant에게 코드를 생성하게 할 때 아래 규칙을 함께 전달하세요. 가장 흔한 integration +실수를 줄일 수 있습니다. + +## AI가 읽기 좋은 문서 + +이 사이트는 `rspress build` 시 AI 도구가 읽기 좋은 Markdown 파일을 함께 생성합니다. + +- [`/ko/llms.txt`](/ko/llms.txt): 페이지 링크와 요약을 담은 compact 문서 index입니다. + Agent가 필요한 페이지만 골라 읽게 할 때 사용하세요. +- [`/ko/llms-full.txt`](/ko/llms-full.txt): 한국어 문서 전체를 하나로 합친 Markdown + 파일입니다. Agent의 context budget이 충분하고 전체 API를 이해해야 할 때 사용하세요. +- [`/llms.txt`](/llms.txt)와 [`/llms-full.txt`](/llms-full.txt): English equivalents. + +공개 문서 URL이 생기기 전에는 로컬에서 build한 뒤 `docs/doc_build/` 아래의 파일을 +사용하세요. + +## 추천 프롬프트 + +```md +Use @react-native-motion-kit/swipe-deck. + +- Create one createSwipeDeck() factory outside render. +- Render Root and Card from that same factory. +- Use a stable getKey(item) value, usually item.id. +- Use factory hooks from the same factory instance as the Root. +- Use useDeckState/useDeckActions for React UI. +- Use useDeckInteraction for Reanimated UI-thread overlays. +- Use useDeckEvent/useDeckEventListener for committed swipe, undo, indexChange, + and endReached events. +- Add undoEnabled only when the UI exposes undo. +- Do not derive deck id from item ids, timestamps, or values that change each render. +``` + +## 좋은 Factory 형태 + +```tsx +// profile-deck.ts +import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +export type Profile = { + id: string; + name: string; +}; + +export const ProfileDeck = createSwipeDeck(); +``` + +```tsx +// ProfileDeckScreen.tsx +import { ProfileDeck } from './profile-deck'; + +export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { + return ( + item.id}> + {({ item }) => } + + ); +} +``` + +## 피해야 할 실수 + +- Component render path 안에서 `createSwipeDeck()`을 호출하지 마세요. +- 한 factory의 hook으로 다른 factory의 Root를 제어하지 마세요. +- Item이 삽입/삭제/재정렬될 수 있다면 array index를 key로 쓰지 마세요. +- Commit된 business logic에는 `useDeckInteraction`이 아니라 event hook을 사용하세요. +- Undo control이 없다면 `undoEnabled`를 켜지 마세요. diff --git a/docs/docs/1.x/ko/guide/getting-started/installation.mdx b/docs/docs/1.x/ko/guide/getting-started/installation.mdx new file mode 100644 index 0000000..9822f0c --- /dev/null +++ b/docs/docs/1.x/ko/guide/getting-started/installation.mdx @@ -0,0 +1,45 @@ +# 설치 + +패키지와 React Native gesture/animation peer dependency를 설치합니다. + +```sh +npm install @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets +``` + +Yarn을 사용한다면: + +```sh +yarn add @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets +``` + +## 최소 지원 버전 + +| Package | Minimum | +| ------------------------------ | -------- | +| `react` | `18.0.0` | +| `react-native` | `0.75.0` | +| `react-native-gesture-handler` | `2.24.0` | +| `react-native-reanimated` | `4.0.0` | +| `react-native-worklets` | `0.5.0` | + +## React Native 설정 + +사용 중인 React Native 또는 Expo 버전에 맞춰 Reanimated와 Worklets 설정을 완료하세요. +Babel 설정에서는 `react-native-worklets/plugin`을 마지막 Babel plugin으로 추가해야 합니다. + +```js title="babel.config.js" +module.exports = { + presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-worklets/plugin'], +}; +``` + +Gesture Handler는 앱 surface가 `GestureHandlerRootView` 아래에 있어야 합니다. + +```tsx +import { GestureHandlerRootView } from 'react-native-gesture-handler'; + +export function AppRoot() { + return {/* app */}; +} +``` diff --git a/docs/docs/1.x/ko/guide/getting-started/overview.mdx b/docs/docs/1.x/ko/guide/getting-started/overview.mdx new file mode 100644 index 0000000..c9b2074 --- /dev/null +++ b/docs/docs/1.x/ko/guide/getting-started/overview.mdx @@ -0,0 +1,60 @@ +# 개요 + +`@react-native-motion-kit/swipe-deck`는 React Native용 고성능 Tinder 스타일 +swipe deck 라이브러리입니다. 부드러운 card stack, like/pass 버튼, progress +기반 overlay, type-safe compound API가 필요한 앱에 맞춰져 있습니다. + +## 핵심 모델 + +주요 API는 typed compound component family입니다. + +- `createSwipeDeck()`가 item type에 맞는 deck family를 만듭니다. +- `Root`는 data, gesture state, motion option, action, event, layout을 소유합니다. +- `Card`는 bounded forward window 안에 mount된 item을 렌더링합니다. +- Factory hook은 state, action, event, animated UI용 Reanimated shared value를 제공합니다. + +```tsx +import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +type Profile = { + id: string; + name: string; +}; + +const ProfileDeck = createSwipeDeck(); +``` + +Factory는 한 번 만들고 shared module에서 export하세요. 같은 factory의 `Root` 주변에서 +그 factory hook을 사용해야 합니다. + +## 이 라이브러리가 해결하는 것 + +- 전체 data set을 한 번에 렌더링하지 않습니다. +- Stable key를 통해 promoted card가 item identity를 유지합니다. +- Manual drag motion과 programmatic action motion을 따로 조정할 수 있습니다. +- Interaction shared value는 UI thread에서 업데이트되어 gesture frame마다 React rerender를 만들지 않습니다. +- Event는 swipe, undo, index change, end reached 같은 commit된 model 변화를 표현합니다. + +## 핵심 개념 + +### Bounded Forward Window + +기본적으로 active index부터 최대 세 장을 mount합니다. + +1. current card +2. next card +3. next buffered card + +이 방식은 dismissed card를 다시 채우지 않으면서도 stack의 연속성을 유지합니다. +`visibleCardCount`로 최대 budget을 조정할 수 있습니다. + +### Stable Item Key + +`getKey`는 필수입니다. 같은 logical item은 swipe가 진행되어도 같은 key를 반환해야 하고, +서로 다른 item은 같은 key를 공유하지 않는 것이 좋습니다. Deck은 이 key를 각 mounted +card의 React identity로 사용합니다. + +### Commit Event와 Live Interaction + +Commit된 model 변화에는 event hook을 사용하세요. Card가 dragging, dismissing, restoring +중인지에 따라 frame-synchronous visual feedback이 필요하면 interaction shared value를 사용하세요. diff --git a/docs/docs/1.x/ko/guide/getting-started/quick-start.mdx b/docs/docs/1.x/ko/guide/getting-started/quick-start.mdx new file mode 100644 index 0000000..4b70cf7 --- /dev/null +++ b/docs/docs/1.x/ko/guide/getting-started/quick-start.mdx @@ -0,0 +1,103 @@ +# 빠른 시작 + +Item type에 맞는 typed deck family를 하나 만드세요. 같은 factory의 `Root`, `Card`, +hook, action, event가 같은 registry namespace를 공유합니다. + +```tsx +import { Text, View } from 'react-native'; +import { createSwipeDeck, SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; + +type Profile = { + id: string; + name: string; + bio: string; +}; + +const ProfileDeck = createSwipeDeck({ + motion: SwipeDeckMotion.tinder({ + drag: { + mode: 'horizontal', + liftYFactor: 0.15, + }, + rotation: { + mode: 'grab-position', + }, + dismiss: { + threshold: ({ width }) => width * 0.3, + velocityThreshold: 800, + minDuration: 300, + maxDuration: 520, + }, + }), +}); + +function ProfileDeckEvents() { + ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { + console.log(item.name, direction, source); + }); + + ProfileDeck.useDeckEventListener('endReached', () => { + console.log('No more cards'); + }); + + return null; +} + +function ProfileCard({ profile, active }: { profile: Profile; active: boolean }) { + return ( + + {active ? '현재 카드' : '다음 카드'} + {profile.name} + {profile.bio} + + ); +} + +export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { + return ( + <> + + item.id} visibleCardCount={3}> + + {({ item, isActive }) => } + + + + ); +} +``` + +## Control 추가 + +Counter와 button처럼 React로 렌더링되는 UI에는 state/action hook을 사용하세요. + +```tsx +function ProfileDeckControls() { + const { activeIndex, count, canSwipe, canUndo, isCompleted } = ProfileDeck.useDeckState(); + const { swipeLeft, swipeRight, undo } = ProfileDeck.useDeckActions(); + const current = activeIndex >= 0 ? activeIndex + 1 : 0; + + return ( + + {isCompleted ? 'Done' : `${current} / ${count}`} + + Nope + + + Undo + + + Like + + + ); +} +``` + +Undo UX를 노출할 때만 `undoEnabled`를 켜세요. + +```tsx + item.id} undoEnabled> + {({ item }) => } + +``` diff --git a/docs/docs/1.x/ko/guide/usage/_meta.json b/docs/docs/1.x/ko/guide/usage/_meta.json new file mode 100644 index 0000000..cd32246 --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/_meta.json @@ -0,0 +1,11 @@ +[ + "basic-usage", + "deck-hooks", + "handling-events", + "programmatic-actions", + "undo", + "motion", + "multi-instance-management", + "visible-card-budget", + "api-reference" +] diff --git a/docs/docs/1.x/ko/guide/usage/api-reference.mdx b/docs/docs/1.x/ko/guide/usage/api-reference.mdx new file mode 100644 index 0000000..7fd5c75 --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/api-reference.mdx @@ -0,0 +1,130 @@ +--- +description: 주요 export, Root와 Card props, hook, event, built-in motion helper를 빠르게 확인합니다. +--- + +# API 레퍼런스 + +## 주요 export + +```ts +export { + SwipeDeckActionMotion, + SwipeDeckMotion, + createSwipeDeck, + SwipeDeck, + SwipeDeckUndoMotion, +} from '@react-native-motion-kit/swipe-deck'; +``` + +[Source 보기](https://github.com/react-native-motion-kit/react-native-swipe-deck/blob/main/src/index.tsx) + +## `createSwipeDeck(config?)` + +Typed deck family를 만듭니다. + +```tsx +const ProfileDeck = createSwipeDeck({ + motion: SwipeDeckMotion.tinder(), +}); +``` + +반환되는 instance는 다음을 포함합니다. + +- `Root` +- `Card` +- `useDeckState` +- `useDeckActions` +- `useDeckInteraction` +- `useDeckEvent` +- `useDeckEventListener` + +## `SwipeDeck.Root` props + +[Source 보기](https://github.com/react-native-motion-kit/react-native-swipe-deck/blob/main/src/types.ts) + +| Prop | Type | 설명 | +| ------------------- | ------------------------------------ | ---------------------------------------------- | +| `id` | `string` | Factory-scoped deck namespace입니다. | +| `data` | `readonly T[]` | Deck이 렌더링할 ordered item입니다. | +| `getKey` | `(item: T, index: number) => string` | 필수 stable item key입니다. | +| `initialIndex` | `number` | 초기 active item index입니다. | +| `disabled` | `boolean` | accepted action과 gesture를 비활성화합니다. | +| `allowedDirections` | `AllowedDirections` | 허용할 dismiss direction을 제한합니다. | +| `swipeThreshold` | `SwipeThreshold` | Root-level dismiss threshold override입니다. | +| `velocityThreshold` | `number` | Root-level flick threshold override입니다. | +| `motion` | `SwipeDeckMotionPreset` | Gesture-driven motion preset입니다. | +| `actionMotion` | `SwipeDeckActionMotionRecipe` | Programmatic swipe motion recipe입니다. | +| `undoMotion` | `SwipeDeckUndoMotionRecipe` | Programmatic undo restore motion recipe입니다. | +| `undoEnabled` | `boolean` | Undo history tracking을 활성화합니다. | +| `visibleCardCount` | `number` | Forward mounted card의 최대 budget입니다. | +| `containerStyle` | `StyleProp` | Deck container에 적용할 style입니다. | +| `children` | `ReactNode` | Matching `SwipeDeck.Card`를 포함해야 합니다. | + +```ts +type AllowedDirections = readonly ('left' | 'right')[]; +type SwipeThreshold = number | ((layout: SwipeDeckLayout) => number); +``` + +Static `SwipeDeck.Root`는 `id`를 제외한 같은 props를 받습니다. + +## `SwipeDeck.Card` props + +| Prop | Type | 설명 | +| ---------- | ----------------------------------------- | ------------------------------------------ | +| `style` | `StyleProp` | Absolute card container에 적용됩니다. | +| `children` | `(info: SwipeRenderInfo) => ReactNode` | Bounded window의 item 하나를 렌더링합니다. | + +`SwipeRenderInfo`는 `item`, `index`, `offset`, `role`, `isActive`를 포함합니다. + +## State와 Actions + +```ts +type SwipeDeckState = { + activeIndex: number; + count: number; + isCompleted: boolean; + canSwipe: boolean; + canUndo: boolean; +}; + +type SwipeDeckActions = { + swipeLeft: SwipeDeckAction; + swipeRight: SwipeDeckAction; + undo: SwipeDeckUndoAction; +}; +``` + +Programmatic action은 action이 받아들여졌는지 알려주는 `boolean`을 반환합니다. + +## Interaction + +```ts +type SwipeDeckInteractionPhase = 'idle' | 'dragging' | 'dismissing' | 'undoing'; +``` + +`useDeckInteraction()`은 `progress`, `signedProgress`, `direction`, `dismissDirection`, +`translationX`, `translationY`, `isDragging`, `phase` Reanimated shared value를 반환합니다. + +## Events + +```ts +type SwipeDeckEventMap = { + swipe: SwipeEvent; + undo: UndoEvent; + indexChange: { index: number }; + endReached: true; +}; +``` + +최신 event snapshot에는 `useDeckEvent`를, imperative subscription에는 `useDeckEventListener`를 +사용하세요. + +## Motion Helpers + +- `SwipeDeckMotion.tinder(options?)` +- `SwipeDeckActionMotion.direct(options?)` +- `SwipeDeckActionMotion.springboard(options?)` +- `SwipeDeckUndoMotion.spring(options?)` +- `SwipeDeckUndoMotion.timing(options?)` + +동작 세부 사항은 이 reference보다 guide page를 먼저 참고하는 것을 권장합니다. diff --git a/docs/docs/1.x/ko/guide/usage/basic-usage.mdx b/docs/docs/1.x/ko/guide/usage/basic-usage.mdx new file mode 100644 index 0000000..9c78909 --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/basic-usage.mdx @@ -0,0 +1,78 @@ +--- +description: Factory API, Card render info, allowedDirections, static SwipeDeck API를 언제 쓰는지 정리합니다. +--- + +# 기본 사용법 + +## Factory API + +Factory API가 기본 API입니다. Deck state, action, event, interaction shared value, +또는 여러 named instance가 필요하다면 factory API를 사용하세요. + +```tsx +import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +const ProfileDeck = createSwipeDeck(); + +function Screen() { + return ( + item.id}> + {({ item }) => } + + ); +} +``` + +Factory는 `Root`, `Card`, hook이 같은 item type을 공유하도록 해 JSX에서 generic을 +반복하지 않아도 됩니다. + +## Card Render Info + +`Card`는 mounted item마다 render info를 받습니다. + +| Field | 의미 | +| ---------- | ---------------------------------------------------------- | +| `item` | 사용자의 `data` 배열에 있는 item입니다. | +| `index` | `data` 안에서의 item index입니다. | +| `offset` | active item으로부터의 card offset입니다. | +| `role` | active card는 `'current'`, buffered card는 `'next'`입니다. | +| `isActive` | active card에서만 `true`입니다. | + +```tsx + + {({ item, role, isActive }) => } + +``` + +## Allowed Directions + +`allowedDirections`는 dismiss로 받아들일 방향을 제한합니다. + +```tsx + item.id} allowedDirections={['right']}> + {({ item }) => } + +``` + +- 생략하면 양방향을 모두 허용합니다. +- `['left']` 또는 `['right']`는 한 방향만 허용합니다. +- `[]`는 drag는 허용하지만 모든 dismiss release와 programmatic swipe action을 거절합니다. + +## Static API + +Card rendering만 필요하고 factory hook이 필요 없다면 static API를 사용할 수 있습니다. + +```tsx +import { SwipeDeck } from '@react-native-motion-kit/swipe-deck'; + +function InlineDeck() { + return ( + item.id}> + >{({ item }) => } + + ); +} +``` + +Static `Root`는 `id`를 받지 않습니다. Static `Root`와 `Card`는 factory처럼 item type을 +함께 고정하지 않으므로, typed render prop이 필요하면 `Card`에 item type을 직접 넘기세요. diff --git a/docs/docs/1.x/ko/guide/usage/deck-hooks.mdx b/docs/docs/1.x/ko/guide/usage/deck-hooks.mdx new file mode 100644 index 0000000..4799a3d --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/deck-hooks.mdx @@ -0,0 +1,97 @@ +# Deck Hooks + +Factory hook은 Provider prop이나 controller object 없이 deck state, action, interaction 값을 +제공합니다. + +```tsx +const ProfileDeck = createSwipeDeck(); +``` + +## `useDeckState(id?)` + +React로 렌더링되는 deck state를 반환합니다. + +| Field | 의미 | +| ------------- | ------------------------------------------------------- | +| `activeIndex` | 현재 active item index입니다. attach 전에는 `-1`입니다. | +| `count` | attach된 deck의 전체 item 수입니다. | +| `isCompleted` | 모든 item을 소비했는지 여부입니다. | +| `canSwipe` | dismiss action이 현재 받아들여질 수 있는지 여부입니다. | +| `canUndo` | 최신 valid swipe를 현재 복원할 수 있는지 여부입니다. | + +```tsx +function Counter() { + const { activeIndex, count, isCompleted } = ProfileDeck.useDeckState(); + const current = activeIndex >= 0 ? activeIndex + 1 : 0; + + return {isCompleted ? 'Done' : `${current} / ${count}`}; +} +``` + +현재 item이 필요하면 사용자의 `data[activeIndex]`에서 직접 계산하세요. Deck state는 +primitive하고 stable하게 유지됩니다. + +## `useDeckActions(id?)` + +Stable action callback을 반환합니다. + +```tsx +function Controls() { + const { canSwipe, canUndo } = ProfileDeck.useDeckState(); + const { swipeLeft, swipeRight, undo } = ProfileDeck.useDeckActions(); + + return ( + + + Nope + + + Undo + + + Like + + + ); +} +``` + +`swipeLeft()`와 `swipeRight()`는 action이 받아들여지면 `true`, deck이 unattached, +disabled, animating, unmeasured, completed 상태이거나 direction이 허용되지 않으면 `false`를 +반환합니다. `undo()`는 deck 완료 후에도 `canUndo`가 true이면 `true`를 반환합니다. + +## `useDeckInteraction(id?)` + +Progress 기반 UI를 위한 Reanimated shared value를 반환합니다. + +```tsx +function SwipeReactionOverlay() { + const { signedProgress } = ProfileDeck.useDeckInteraction(); + + const likeStyle = useAnimatedStyle(() => { + const progress = Math.max(signedProgress.get(), 0); + + return { + opacity: progress, + transform: [{ scale: 0.9 + progress * 0.18 }], + }; + }); + + return ; +} +``` + +Interaction value는 UI thread에서 업데이트되고 gesture frame마다 React rerender를 만들지 않습니다. + +| Value | 의미 | +| ------------------ | --------------------------------------------------------- | +| `progress` | `0`부터 `1`까지의 absolute swipe progress입니다. | +| `signedProgress` | `-1`부터 `1`까지의 signed progress입니다. | +| `direction` | raw live direction signal입니다. `-1`, `0`, `1`. | +| `dismissDirection` | accepted dismiss side입니다. `'left'`, `'right'`, `null`. | +| `translationX` | active card의 horizontal translation입니다. | +| `translationY` | active card의 vertical translation입니다. | +| `isDragging` | deck이 dragging 또는 dismissing 중인지 여부입니다. | +| `phase` | `idle`, `dragging`, `dismissing`, `undoing`. | + +Frame-synchronous visual feedback에는 `phase`를 사용하세요. Commit된 state 변화에는 event hook을 사용하세요. diff --git a/docs/docs/1.x/ko/guide/usage/handling-events.mdx b/docs/docs/1.x/ko/guide/usage/handling-events.mdx new file mode 100644 index 0000000..f3f18f4 --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/handling-events.mdx @@ -0,0 +1,78 @@ +# Event 처리 + +Event hook은 commit된 model event를 표현합니다. Live interaction value와 분리해서 생각하세요. + +## Event Map + +| Event | Payload | +| ------------- | ------------------------------------ | +| `swipe` | `{ item, index, direction, source }` | +| `undo` | `{ item, index, direction }` | +| `indexChange` | `{ index }` | +| `endReached` | `true` | + +`swipe.source`는 사용자가 pan gesture를 threshold/velocity 기준 이상으로 놓아 commit한 경우 +`'gesture'`입니다. `actions.swipeLeft()` 또는 `actions.swipeRight()`로 commit된 경우 +`'programmatic'`입니다. + +## `useDeckEvent` + +`useDeckEvent(eventName, initialValue?, id?)`는 React 렌더링 UI를 위한 최신 commit event +snapshot을 반환합니다. + +```tsx +function ProfileDeckStatus() { + const lastSwipe = ProfileDeck.useDeckEvent('swipe', null); + const endReached = ProfileDeck.useDeckEvent('endReached', false); + + return ( + + {endReached + ? 'Done' + : lastSwipe + ? `Last swipe: ${lastSwipe.direction} from ${lastSwipe.source}` + : 'No swipe yet'} + + ); +} +``` + +이 API는 latest-value API이며 event history가 아닙니다. Root가 attach/detach될 때 event +snapshot은 clear됩니다. + +Initial value는 event payload shape, `null`, `undefined`, 또는 `endReached`의 `false`로 +제한됩니다. `swipe` 같은 object event에는 `null`을 사용하세요. + +Initial value 없이 named deck을 읽어야 한다면 id를 두 번째 인자로 넘깁니다. + +```tsx +const lastSwipe = ProfileDeck.useDeckEvent('swipe', 'nearby'); +``` + +Initial value도 함께 넘기는 경우 id는 세 번째 인자입니다. + +## `useDeckEventListener` + +`useDeckEventListener(eventName, listener, id?)`는 앱 코드에 별도 React state를 만들지 않고 +imperative하게 event를 구독합니다. + +```tsx +function ProfileDeckEvents() { + ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { + console.log(item, direction, source); + }); + + ProfileDeck.useDeckEventListener('undo', ({ item }) => { + console.log('Restored', item); + }); + + ProfileDeck.useDeckEventListener('endReached', () => { + console.log('No more cards'); + }); + + return null; +} +``` + +성공한 swipe는 `swipe -> indexChange -> endReached` 순서로 emit됩니다. Undo는 +`undo -> indexChange` 순서로 emit됩니다. diff --git a/docs/docs/1.x/ko/guide/usage/motion.mdx b/docs/docs/1.x/ko/guide/usage/motion.mdx new file mode 100644 index 0000000..a5b7d0a --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/motion.mdx @@ -0,0 +1,133 @@ +# Motion + +`SwipeDeckMotion.tinder()`는 Tinder 스타일 card stack을 위한 기본 motion preset입니다. + +```tsx +import { SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; + +const motion = SwipeDeckMotion.tinder({ + drag: { + mode: 'horizontal', + liftYFactor: 0.15, + }, + rotation: { + mode: 'grab-position', + }, + dismiss: { + threshold: ({ width }) => width * 0.3, + velocityThreshold: 800, + minDuration: 300, + maxDuration: 520, + }, +}); +``` + +## Swipe Progress를 따라가는 것 + +Buffered next card는 swipe progress에 따라 애니메이션됩니다. + +- scale은 `1`에 가까워집니다. +- opacity는 `1`에 가까워집니다. +- `translateY`는 `0`에 가까워집니다. + +Swipe가 commit되면 dismissed card는 화면 밖으로 나가고, promoted next card는 item identity를 +유지하며, 새로운 future item이 bounded window에 들어옵니다. + +## Drag Mode + +`drag.mode`는 drag 중 active card가 손가락 translation을 어떻게 사용할지 제어합니다. + +| Mode | Active card 움직임 | +| ------------ | ----------------------------------------------- | +| `free` | 손가락의 X/Y 이동을 모두 따라갑니다. | +| `horizontal` | 손가락의 Y 이동은 무시하고 X 이동만 반영합니다. | + +`drag.liftYFactor`는 active card를 `abs(translationX) * liftYFactor`만큼 위로 올립니다. + +## Rotation + +`rotation.mode`는 회전 기준점을 고정할지, gesture 시작 위치에서 계산할지 제어합니다. + +### Fixed Rotation + +모든 gesture가 같은 anchor를 사용해야 한다면 fixed rotation을 사용하세요. + +| Origin | 느낌 | +| --------------- | -------------------------------------------- | +| `center` | 카드 중앙을 기준으로 회전합니다. | +| `top-center` | 카드 위쪽 edge가 고정된 축처럼 느껴집니다. | +| `bottom-center` | 카드 아래쪽 edge가 고정된 축처럼 느껴집니다. | + +```tsx +SwipeDeckMotion.tinder({ + rotation: { + mode: 'fixed', + origin: 'bottom-center', + direction: 'default', + }, +}); +``` + +같은 고정 anchor에서 회전 부호만 반대로 만들고 싶다면 `direction: 'reverse'`를 사용하세요. + +### Grab-Position Rotation + +Grab-position rotation은 기본 Tinder-like 동작입니다. + +```tsx +SwipeDeckMotion.tinder({ + rotation: { + mode: 'grab-position', + direction: 'default', + maxDegrees: 25, + }, +}); +``` + +카드 위쪽에서 잡으면 top-center anchor와 default rotation sign을 사용합니다. 아래쪽에서 잡으면 +bottom-center anchor와 reverse rotation sign을 사용합니다. `direction: 'reverse'`는 이 mapping을 +뒤집습니다. + +## Dismiss Target + +`dismiss.offscreenMultiplier`는 성공한 swipe의 release target을 제어합니다. + +- 성공한 swipe는 항상 화면 밖으로 dismiss됩니다. +- 기본값 `1.5`는 카드를 `clearDistance * 1.5` 위치로 보냅니다. +- `clearDistance`는 swipe direction, rotation mode, rotation direction, gesture start position으로 계산합니다. +- `1`보다 작은 값은 `1`로 정규화됩니다. +- `duration`을 생략하면 target까지 남은 거리로 velocity-derived timing을 계산합니다. + +대부분의 앱은 `threshold`, `velocityThreshold`, `duration`, `minDuration`, `maxDuration`, +`easing`만 조정해도 충분합니다. + +## Motion 우선순위 + +Motion 값은 다음 순서로 resolve됩니다. + +1. `createSwipeDeck({ motion })`의 factory motion default +2. 명시한 field만 부분 override하는 `Root motion` +3. `swipeThreshold`, `velocityThreshold` 같은 direct root prop + +Factory motion과 Root motion은 deep merge됩니다. `rotation.mode`를 바꿔도 `maxDegrees`, +`inputRange` 같은 numeric rotation tuning은 reset되지 않습니다. + +## Preset 안정성 + +Motion preset은 module-scope constant 또는 `useMemo`로 안정적으로 유지하세요. + +```tsx +const profileDeckMotion = SwipeDeckMotion.tinder({ + rotation: { + mode: 'fixed', + origin: 'bottom-center', + }, + dismiss: { + threshold: ({ width }) => width * 0.3, + }, +}); + +const ProfileDeck = createSwipeDeck({ + motion: profileDeckMotion, +}); +``` diff --git a/docs/docs/1.x/ko/guide/usage/multi-instance-management.mdx b/docs/docs/1.x/ko/guide/usage/multi-instance-management.mdx new file mode 100644 index 0000000..65d630c --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/multi-instance-management.mdx @@ -0,0 +1,56 @@ +# Multi-Instance Management + +같은 factory에서 여러 Root를 렌더링할 때만 `id`를 사용하세요. + +```tsx +function MultiDeckScreen() { + const nearbyState = ProfileDeck.useDeckState('nearby'); + + return ( + <> + item.id}> + {({ item }) => } + + + item.id}> + {({ item }) => } + + + {nearbyState.activeIndex + 1} + + ); +} +``` + +`id`는 item key가 아니라 factory 안에서 deck instance를 구분하는 namespace입니다. 서로 다른 +factory는 둘 다 default id를 써도 충돌하지 않지만, 같은 factory와 같은 id의 Root 두 개가 +동시에 mount되는 것은 잘못된 사용입니다. + +## Id 규칙 + +- 안정적이고 적은 개수의 id를 사용하세요. +- `"nearby"`, `"recommended"` 같은 화면/용도 단위 이름을 사용하세요. +- Item id, timestamp, 매 render마다 바뀌는 값, 일회성 route 값에서 id를 만들지 마세요. +- Factory와 id는 render path 밖에서 안정적으로 만들고 유지하세요. + +Registry는 hook, action, interaction shared value의 identity를 안정적으로 유지하기 위해 factory +lifetime 동안 id별 store를 유지합니다. + +## Same-Factory Rule + +Hook, action, interaction은 같은 factory instance에서 만든 Root에만 연결됩니다. + +```ts +const ProfileDeck = createSwipeDeck(); + +export const { + Root: ProfileDeckRoot, + Card: ProfileDeckCard, + useDeckState: useProfileDeckState, + useDeckActions: useProfileDeckActions, + useDeckInteraction: useProfileDeckInteraction, +} = ProfileDeck; +``` + +Item type과 id가 같아도 `createSwipeDeck()`를 다시 호출하면 별도 registry namespace가 +만들어집니다. diff --git a/docs/docs/1.x/ko/guide/usage/programmatic-actions.mdx b/docs/docs/1.x/ko/guide/usage/programmatic-actions.mdx new file mode 100644 index 0000000..d40abe1 --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/programmatic-actions.mdx @@ -0,0 +1,77 @@ +# Programmatic Actions + +Programmatic action은 `useDeckActions()`에서 가져옵니다. 버튼, 외부 control, 앱 로직에서 +active card를 dismiss할 때 사용하세요. + +```tsx +function LikeButton() { + const { swipeRight } = ProfileDeck.useDeckActions(); + + return ( + + Like + + ); +} +``` + +## Action Motion + +`motion`은 manual drag feel을 제어합니다. `actionMotion`은 `swipeLeft()`와 `swipeRight()` +같은 programmatic action만 제어합니다. + +```tsx +import { + createSwipeDeck, + SwipeDeckActionMotion, + SwipeDeckMotion, +} from '@react-native-motion-kit/swipe-deck'; + +const ProfileDeck = createSwipeDeck({ + motion: SwipeDeckMotion.tinder(), + actionMotion: SwipeDeckActionMotion.springboard({ + anticipationDistance: ({ width }) => width * 0.04, + anticipationDuration: 80, + dismissDuration: 320, + }), +}); +``` + +## Recipe + +### `SwipeDeckActionMotion.direct(options?)` + +Action 방향으로 바로 dismiss합니다. 생략한 값은 deck에서 resolve된 dismiss duration, easing, +offscreen multiplier를 재사용합니다. + +```tsx +actions.swipeLeft( + SwipeDeckActionMotion.direct({ + duration: 180, + }), +); +``` + +### `SwipeDeckActionMotion.springboard(options?)` + +최종 방향의 반대쪽으로 살짝 움직인 뒤 화면 밖으로 dismiss합니다. Anticipation 동안 swipe +progress와 live direction은 neutral 상태로 유지되어 반대쪽 overlay가 순간적으로 보이지 않습니다. + +```tsx +const actionMotion = SwipeDeckActionMotion.springboard({ + anticipationDistance: 40, + anticipationDuration: 160, + dismissDuration: 500, +}); +``` + +## 우선순위 + +`actionMotion`은 deep merge가 아니라 replacement 방식입니다. + +1. `createSwipeDeck({ actionMotion })`의 factory `actionMotion` +2. 해당 Root에서 factory default를 대체하는 `Root actionMotion` +3. `swipeLeft(recipe)` 또는 `swipeRight(recipe)`에 넘긴 per-call recipe + +Action은 callback으로 바로 넘겨도 안전합니다. React Native press event가 `swipeRight` 또는 +`swipeLeft`로 전달되면 그 event 인자는 무시됩니다. diff --git a/docs/docs/1.x/ko/guide/usage/undo.mdx b/docs/docs/1.x/ko/guide/usage/undo.mdx new file mode 100644 index 0000000..606e18b --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/undo.mdx @@ -0,0 +1,74 @@ +# Undo + +Undo는 opt-in입니다. 해당 deck에서 undo/back-swipe UX를 제공할 때 Root에 `undoEnabled`를 +추가하세요. + +```tsx + item.id} undoEnabled> + {({ item }) => } + +``` + +활성화되면 성공한 swipe마다 key/index/direction metadata entry 하나를 LIFO undo stack에 +저장합니다. Lookup은 현재 `data`에 대한 key-to-index map을 사용하고, data나 key가 바뀌면 +invalid entry를 prune합니다. + +생략하면 성공한 swipe도 undo metadata를 저장하지 않고, `canUndo`는 `false`를 유지하며, +`actions.undo()`는 `false`를 반환합니다. + +## Undo Button + +```tsx +function UndoButton() { + const { canUndo } = ProfileDeck.useDeckState(); + const { undo } = ProfileDeck.useDeckActions(); + + return ( + + Undo + + ); +} +``` + +## Undo Motion + +`SwipeDeckUndoMotion`으로 restored card가 들어오는 motion을 조정할 수 있습니다. + +```tsx +import { createSwipeDeck, SwipeDeckUndoMotion } from '@react-native-motion-kit/swipe-deck'; + +const ProfileDeck = createSwipeDeck({ + undoMotion: SwipeDeckUndoMotion.spring({ + springConfig: { + damping: 36, + stiffness: 300, + mass: 3, + }, + }), +}); +``` + +제공되는 recipe는 다음과 같습니다. + +- `SwipeDeckUndoMotion.spring(options?)`: Reanimated `withSpring`으로 restore합니다. +- `SwipeDeckUndoMotion.timing(options?)`: deterministic한 `withTiming`으로 restore합니다. + +두 recipe는 공통으로 다음 옵션을 받습니다. + +- `from: 'auto' | 'left' | 'right'`: `auto`는 원래 swipe된 방향에서 card를 되돌립니다. +- `entryDistance`: 화면 밖 시작 거리를 number 또는 layout callback으로 지정합니다. + +`timing`은 `duration`, `easing`을 추가로 받고 기본 duration은 `0`입니다. 별도 motion을 +설정하지 않으면 card를 즉시 복원합니다. `spring`은 `springConfig`를 받습니다. + +## 우선순위 + +`undoMotion`은 replacement 방식입니다. + +1. `createSwipeDeck({ undoMotion })`의 factory `undoMotion` +2. 해당 Root에서 factory default를 대체하는 `Root undoMotion` +3. `actions.undo(recipe)`에 넘긴 per-call recipe + +Undo도 callback으로 바로 넘겨도 안전합니다. React Native press event가 `undo`로 전달되면 +그 event 인자는 무시됩니다. diff --git a/docs/docs/1.x/ko/guide/usage/visible-card-budget.mdx b/docs/docs/1.x/ko/guide/usage/visible-card-budget.mdx new file mode 100644 index 0000000..fb0306a --- /dev/null +++ b/docs/docs/1.x/ko/guide/usage/visible-card-budget.mdx @@ -0,0 +1,44 @@ +# Visible Card Budget + +`visibleCardCount`는 active card부터 forward 방향으로 mount할 card의 최대 개수를 제어합니다. + +```tsx +function CurrentOnlyDeck() { + return ( + item.id} visibleCardCount={1}> + {/* 현재 카드만 렌더링 */} + + ); +} + +function DefaultDeck() { + return ( + item.id} visibleCardCount={3}> + {/* 더 자연스러운 next-card promotion을 위한 기본 budget */} + + ); +} + +function DeepStackDeck() { + return ( + item.id} visibleCardCount={9}> + {/* 더 깊은 stack UI */} + + ); +} +``` + +## 규칙 + +| 입력 | Mount되는 card | +| ---------------------------------------------- | ----------------------------- | +| `visibleCardCount={1}` | 현재 카드만 | +| `visibleCardCount={2}` | 데이터가 충분할 때 최대 두 장 | +| 남은 데이터가 10개이고 `visibleCardCount={20}` | 최대 남은 10장 | +| 짝수 값 | 최대 budget으로 그대로 유지 | + +`1`보다 작은 값은 forward data가 충분할 때 `1`로 정규화됩니다. 실제 mount 개수는 active index부터 +남은 data 개수를 넘지 않습니다. + +UI가 허용하는 한 작은 budget을 유지하세요. 더 깊은 stack이 실제로 보이거나 더 많은 card를 미리 +mount해야 할 때만 늘리는 것을 권장합니다. diff --git a/docs/docs/1.x/ko/index.md b/docs/docs/1.x/ko/index.md new file mode 100644 index 0000000..109aee0 --- /dev/null +++ b/docs/docs/1.x/ko/index.md @@ -0,0 +1,50 @@ +--- +pageType: home +description: React Native 앱을 위한 고성능 Tinder 스타일 swipe deck 문서. + +hero: + name: React Native Swipe Deck + text: React Native용 Tinder 스타일 swipe deck + tagline: Reanimated, Worklets, Gesture Handler 기반의 고성능 card stack + actions: + - theme: brand + text: 빠른 시작 + link: /ko/guide/getting-started/quick-start.html + - theme: alt + text: GitHub + link: https://github.com/react-native-motion-kit/react-native-swipe-deck + image: + src: /logo.png + alt: React Native Motion Kit 로고 +features: + - title: Gesture 중심 Deck UX + details: Tinder 스타일 card stack에 맞춰 drag, flick, threshold, direction control을 조정합니다. + icon: 🤌 + - title: 고성능 애니메이션 + details: React Native Reanimated shared value와 worklet 기반으로 부드러운 card motion을 만듭니다. + icon: 🏎️ + - title: Bounded Render Window + details: 전체 데이터가 아니라 active card와 작은 forward stack만 mount합니다. + icon: 🪟 + - title: Item-Stable Promotion + details: 안정적인 item key로 promoted card가 React Native view identity를 유지합니다. + icon: 🧬 + - title: Typed Compound API + details: Root, Card, hook, action, event를 하나의 typed deck family로 묶습니다. + icon: 🧠 + - title: 외부 제어 API + details: button이나 다른 UI에서 swipeLeft, swipeRight, undo를 programmatic하게 실행합니다. + icon: 🎛️ + - title: Motion Recipes + details: gesture motion, programmatic action, undo restore를 각각 독립적으로 조정합니다. + icon: 🎨 + - title: Multi-Instance Management + details: 안정적인 factory-scoped id로 여러 deck root를 독립적으로 관리합니다. + icon: 🧩 + - title: Undo Support + details: opt-in undo history와 action-safe restore motion으로 back-swipe UX를 만듭니다. + icon: ↩️ + - title: 쉬운 API + details: Root와 Card로 시작하고, control이나 event가 필요할 때만 hook을 추가합니다. + icon: 🪄 +--- diff --git a/docs/docs/env.d.ts b/docs/docs/env.d.ts new file mode 100644 index 0000000..73badc4 --- /dev/null +++ b/docs/docs/env.d.ts @@ -0,0 +1,11 @@ +declare module '*.mdx' { + import type { ComponentType } from 'react'; + + const MDXComponent: ComponentType<{ + [key: string]: any; + }>; + + export default MDXComponent; +} + +declare module '*.css'; diff --git a/docs/docs/public/logo.png b/docs/docs/public/logo.png new file mode 100644 index 0000000..b69e1e3 Binary files /dev/null and b/docs/docs/public/logo.png differ diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..582f32c --- /dev/null +++ b/docs/package.json @@ -0,0 +1,21 @@ +{ + "name": "@react-native-motion-kit/swipe-deck-docs", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "rspress build", + "dev": "rspress dev", + "preview": "rspress preview", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@rspress/core": "^2.0.13", + "@shikijs/transformers": "^4.1.0", + "@types/node": "^24", + "@types/react": "^19.2.0", + "react": "19.2.3", + "react-dom": "19.2.3", + "typescript": "^6.0.2" + } +} diff --git a/docs/rspress.config.ts b/docs/rspress.config.ts new file mode 100644 index 0000000..f4014ec --- /dev/null +++ b/docs/rspress.config.ts @@ -0,0 +1,63 @@ +import { defineConfig } from '@rspress/core'; +import { transformerNotationDiff, transformerNotationHighlight } from '@shikijs/transformers'; +import * as path from 'node:path'; + +export default defineConfig({ + root: path.join(__dirname, 'docs'), + lang: 'en', + title: 'React Native Swipe Deck', + description: 'High-performance Tinder-style swipe deck documentation for React Native apps.', + icon: '/logo.png', + logo: '/logo.png', + logoText: 'React Native Swipe Deck', + llms: true, + multiVersion: { + default: '1.x', + versions: ['1.x'], + }, + search: { + versioned: true, + }, + ssg: true, + markdown: { + showLineNumbers: true, + defaultWrapCode: false, + link: { + checkDeadLinks: { + excludes: ['/llms.txt', '/llms-full.txt', '/ko/llms.txt', '/ko/llms-full.txt'], + }, + }, + shiki: { + transformers: [transformerNotationDiff(), transformerNotationHighlight()], + }, + }, + themeConfig: { + lastUpdated: true, + socialLinks: [ + { + icon: 'github', + mode: 'link', + content: 'https://github.com/react-native-motion-kit/react-native-swipe-deck', + }, + { + icon: 'npm', + mode: 'link', + content: 'https://www.npmjs.com/package/@react-native-motion-kit/swipe-deck', + }, + ], + locales: [ + { + lang: 'en', + label: 'English', + }, + { + lang: 'ko', + label: '한국어', + }, + ], + editLink: { + docRepoBaseUrl: + 'https://github.com/react-native-motion-kit/react-native-swipe-deck/tree/main/docs/docs', + }, + }, +}); diff --git a/docs/theme/index.css b/docs/theme/index.css new file mode 100644 index 0000000..52c58b9 --- /dev/null +++ b/docs/theme/index.css @@ -0,0 +1,8 @@ +.rp-home-hero .home-install-command { + width: min(100%, 450px); + margin-top: 1rem; +} + +.rp-home-hero .home-install-command .rp-tabs { + margin: 0; +} diff --git a/docs/theme/index.tsx b/docs/theme/index.tsx new file mode 100644 index 0000000..a2a6f08 --- /dev/null +++ b/docs/theme/index.tsx @@ -0,0 +1,73 @@ +import './index.css'; +import { + HomeLayout as BasicHomeLayout, + NotFoundLayout as BasicNotFoundLayout, + PackageManagerTabs, +} from '@rspress/core/theme-original'; +import { useEffect } from 'react'; + +const GUIDE_SECTION_NAMES = new Set(['getting-started', 'usage']); +const INSTALL_PACKAGES = '@react-native-motion-kit/swipe-deck'; +const INSTALL_COMMANDS = { + npm: `npm install ${INSTALL_PACKAGES}`, + yarn: `yarn add ${INSTALL_PACKAGES}`, + pnpm: `pnpm add ${INSTALL_PACKAGES}`, + bun: `bun add ${INSTALL_PACKAGES}`, +}; + +function getGuideRedirectPath(pathname: string) { + const parts = pathname.split('/').filter(Boolean); + const prefix: string[] = []; + let rest = parts; + let hadDefaultVersionPrefix = false; + + if (rest[0] === '1.x') { + hadDefaultVersionPrefix = true; + rest = rest.slice(1); + } + + if (rest[0] === 'ko') { + prefix.push(rest[0]); + rest = rest.slice(1); + } + + const [section] = rest; + + if (hadDefaultVersionPrefix && section === 'guide') { + return `/${[...prefix, ...rest].join('/')}`; + } + + if (section && GUIDE_SECTION_NAMES.has(section)) { + return `/${[...prefix, 'guide', ...rest].join('/')}`; + } + + return null; +} + +function HomeLayout() { + return ( + + + + } + /> + ); +} + +function NotFoundLayout() { + useEffect(() => { + const redirectPath = getGuideRedirectPath(window.location.pathname); + + if (redirectPath) { + window.location.replace(`${redirectPath}${window.location.search}${window.location.hash}`); + } + }, []); + + return ; +} + +export { HomeLayout, NotFoundLayout }; +// oxlint-disable-next-line import/export +export * from '@rspress/core/theme-original'; diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..fa4849d --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["DOM", "ES2020"], + "module": "ESNext", + "jsx": "react-jsx", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "isolatedModules": true, + "resolveJsonModule": true, + "moduleResolution": "bundler", + "useDefineForClassFields": true, + "allowImportingTsExtensions": true, + "paths": { + "@theme": ["./theme"] + } + }, + "include": ["docs/**/*", "theme/**/*", "rspress.config.ts"], + "mdx": { + "checkMdx": true + } +} diff --git a/package.json b/package.json index eb00933..aa428a9 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,35 @@ { "name": "@react-native-motion-kit/swipe-deck", "version": "1.3.0", - "description": "High-performance Tinder-style swipe deck and swipe cards for React Native, powered by Reanimated and Gesture Handler.", + "description": "High-performance Tinder-style swipe deck and swipe cards for React Native, powered by Reanimated, Worklets, and Gesture Handler.", "keywords": [ "android", "animation", "card-stack", + "card-swiper", "deck-swiper", + "expo", "gesture", "gesture-handler", "ios", "react-native", + "react-native-card-stack", + "react-native-card-swiper", + "react-native-deck-swiper", "react-native-gesture-handler", + "react-native-motion-kit", "react-native-reanimated", + "react-native-swipe-cards", "react-native-swipe-deck", + "react-native-worklets", "reanimated", "swipe-cards", "swipe-deck", + "swipeable-cards", "tinder-cards", - "tinder-style" + "tinder-style", + "tinder-swipe", + "worklets" ], "homepage": "https://github.com/react-native-motion-kit/react-native-swipe-deck#readme", "bugs": { @@ -32,7 +43,8 @@ }, "workspaces": [ ".", - "example" + "example", + "docs" ], "files": [ "src", diff --git a/tsconfig.build.json b/tsconfig.build.json index e50e85a..690b8ce 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,5 @@ { "extends": "./tsconfig", + "include": ["src"], "exclude": ["example", "lib", "type-tests"] } diff --git a/tsconfig.json b/tsconfig.json index c041b18..eb98270 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,6 @@ "strict": true, "target": "ESNext", "verbatimModuleSyntax": true - } + }, + "exclude": ["docs"] } diff --git a/yarn.lock b/yarn.lock index d0e78d8..11b3fc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2035,6 +2035,34 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/core@npm:1.10.0" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.1" + tslib: "npm:^2.4.0" + checksum: 10c0/f51d08227857b60632de7714d708124f0e100a1462dde6df8221760939aa3204a73193830371830fac0716f3ccd2129f2cac1b17cd7d7958bc4da9018a296edb + languageName: node + linkType: hard + +"@emnapi/runtime@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/runtime@npm:1.10.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/953f14991d1aefb92ee6f8eb27dea725e484791a53a0cb5f47d9e0087b9a2c929ff2e92adf95af15d6ad456db6300c6b761ebf72b50a875b874a83520b3ba093 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.2.1": + version: 1.2.1 + resolution: "@emnapi/wasi-threads@npm:1.2.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 + languageName: node + linkType: hard + "@expo/cli@npm:55.0.32": version: 55.0.32 resolution: "@expo/cli@npm:55.0.32" @@ -2914,6 +2942,63 @@ __metadata: languageName: node linkType: hard +"@mdx-js/mdx@npm:^3.1.1": + version: 3.1.1 + resolution: "@mdx-js/mdx@npm:3.1.1" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdx": "npm:^2.0.0" + acorn: "npm:^8.0.0" + collapse-white-space: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + estree-util-scope: "npm:^1.0.0" + estree-walker: "npm:^3.0.0" + hast-util-to-jsx-runtime: "npm:^2.0.0" + markdown-extensions: "npm:^2.0.0" + recma-build-jsx: "npm:^1.0.0" + recma-jsx: "npm:^1.0.0" + recma-stringify: "npm:^1.0.0" + rehype-recma: "npm:^1.0.0" + remark-mdx: "npm:^3.0.0" + remark-parse: "npm:^11.0.0" + remark-rehype: "npm:^11.0.0" + source-map: "npm:^0.7.0" + unified: "npm:^11.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + unist-util-stringify-position: "npm:^4.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/371ed95e2bee7731f30a7ce57db66383a0b7470e66c38139427174cb456d6a40bf7d259f3652716370c1de64acfba50a1ba27eb8c556e7a431dc7940b04cb1a1 + languageName: node + linkType: hard + +"@mdx-js/react@npm:^3.1.1": + version: 3.1.1 + resolution: "@mdx-js/react@npm:3.1.1" + dependencies: + "@types/mdx": "npm:^2.0.0" + peerDependencies: + "@types/react": ">=16" + react: ">=16" + checksum: 10c0/34ca98bc2a0f969894ea144dc5c8a5294690505458cd24965cd9be854d779c193ad9192bf9143c4c18438fafd1902e100d99067e045c69319288562d497558c6 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:1.1.4": + version: 1.1.4 + resolution: "@napi-rs/wasm-runtime@npm:1.1.4" + dependencies: + "@tybys/wasm-util": "npm:^0.10.1" + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/2e88e1955258949ccf2d18c79975821ad38071b465ef126a5e14110977b97868867b016c1ad046e963cccc42c0bd9db6c8ff5fd1ebb61b87bb3487f339041658 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -3207,6 +3292,20 @@ __metadata: languageName: node linkType: hard +"@react-native-motion-kit/swipe-deck-docs@workspace:docs": + version: 0.0.0-use.local + resolution: "@react-native-motion-kit/swipe-deck-docs@workspace:docs" + dependencies: + "@rspress/core": "npm:^2.0.13" + "@shikijs/transformers": "npm:^4.1.0" + "@types/node": "npm:^24" + "@types/react": "npm:^19.2.0" + react: "npm:19.2.3" + react-dom: "npm:19.2.3" + typescript: "npm:^6.0.2" + languageName: unknown + linkType: soft + "@react-native-motion-kit/swipe-deck@workspace:*, @react-native-motion-kit/swipe-deck@workspace:.": version: 0.0.0-use.local resolution: "@react-native-motion-kit/swipe-deck@workspace:." @@ -3676,6 +3775,344 @@ __metadata: languageName: node linkType: hard +"@rsbuild/core@npm:^2.0.9": + version: 2.0.15 + resolution: "@rsbuild/core@npm:2.0.15" + dependencies: + "@rspack/core": "npm:~2.0.8" + "@swc/helpers": "npm:^0.5.23" + peerDependencies: + core-js: ">= 3.0.0" + peerDependenciesMeta: + core-js: + optional: true + bin: + rsbuild: ./bin/rsbuild.js + checksum: 10c0/0aa77a2e4928e1b62b26fa986467d8226a89f2fb0680ae825a36f7e581a976e6bb43debcca4dcdb7fd8d68b05a7f7ef2e5b25b9769afd70ea2a65c1103b59e2e + languageName: node + linkType: hard + +"@rsbuild/plugin-react@npm:~2.0.1": + version: 2.0.1 + resolution: "@rsbuild/plugin-react@npm:2.0.1" + dependencies: + "@rspack/plugin-react-refresh": "npm:2.0.0" + react-refresh: "npm:^0.18.0" + peerDependencies: + "@rsbuild/core": ^2.0.0-0 + peerDependenciesMeta: + "@rsbuild/core": + optional: true + checksum: 10c0/b9bb8941ca8081a7231e5b61fe071f8eda79f09ef3177b1ba4d2472ad6df749931446e0a3c71f2de930b8cdf05eac90d37d7f50f498752577516a694c2ee43e9 + languageName: node + linkType: hard + +"@rspack/binding-darwin-arm64@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-darwin-arm64@npm:2.0.8" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rspack/binding-darwin-x64@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-darwin-x64@npm:2.0.8" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding-linux-arm64-gnu@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-linux-arm64-gnu@npm:2.0.8" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rspack/binding-linux-arm64-musl@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-linux-arm64-musl@npm:2.0.8" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rspack/binding-linux-x64-gnu@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-linux-x64-gnu@npm:2.0.8" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rspack/binding-linux-x64-musl@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-linux-x64-musl@npm:2.0.8" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rspack/binding-wasm32-wasi@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-wasm32-wasi@npm:2.0.8" + dependencies: + "@emnapi/core": "npm:1.10.0" + "@emnapi/runtime": "npm:1.10.0" + "@napi-rs/wasm-runtime": "npm:1.1.4" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rspack/binding-win32-arm64-msvc@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-win32-arm64-msvc@npm:2.0.8" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rspack/binding-win32-ia32-msvc@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-win32-ia32-msvc@npm:2.0.8" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rspack/binding-win32-x64-msvc@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding-win32-x64-msvc@npm:2.0.8" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding@npm:2.0.8": + version: 2.0.8 + resolution: "@rspack/binding@npm:2.0.8" + dependencies: + "@rspack/binding-darwin-arm64": "npm:2.0.8" + "@rspack/binding-darwin-x64": "npm:2.0.8" + "@rspack/binding-linux-arm64-gnu": "npm:2.0.8" + "@rspack/binding-linux-arm64-musl": "npm:2.0.8" + "@rspack/binding-linux-x64-gnu": "npm:2.0.8" + "@rspack/binding-linux-x64-musl": "npm:2.0.8" + "@rspack/binding-wasm32-wasi": "npm:2.0.8" + "@rspack/binding-win32-arm64-msvc": "npm:2.0.8" + "@rspack/binding-win32-ia32-msvc": "npm:2.0.8" + "@rspack/binding-win32-x64-msvc": "npm:2.0.8" + dependenciesMeta: + "@rspack/binding-darwin-arm64": + optional: true + "@rspack/binding-darwin-x64": + optional: true + "@rspack/binding-linux-arm64-gnu": + optional: true + "@rspack/binding-linux-arm64-musl": + optional: true + "@rspack/binding-linux-x64-gnu": + optional: true + "@rspack/binding-linux-x64-musl": + optional: true + "@rspack/binding-wasm32-wasi": + optional: true + "@rspack/binding-win32-arm64-msvc": + optional: true + "@rspack/binding-win32-ia32-msvc": + optional: true + "@rspack/binding-win32-x64-msvc": + optional: true + checksum: 10c0/a5a61d6ea25fafb002bbdb6bc2600c4fba1ae1e2da3d4afd86b458eb83bfff413fa2415ecd83ffd7c4e8aa60f2cc90df6793a27e56681a5fdb415d0df428e42b + languageName: node + linkType: hard + +"@rspack/core@npm:~2.0.8": + version: 2.0.8 + resolution: "@rspack/core@npm:2.0.8" + dependencies: + "@rspack/binding": "npm:2.0.8" + peerDependencies: + "@module-federation/runtime-tools": ^0.24.1 || ^2.0.0 + "@swc/helpers": ^0.5.23 + peerDependenciesMeta: + "@module-federation/runtime-tools": + optional: true + "@swc/helpers": + optional: true + checksum: 10c0/7862d5e17df9bcf70de1472142df047188a1a87c028481225702af15d69940d9935b91502e05d04f42ccc12fcdd70d6d846222817a2054b18135b4143d483047 + languageName: node + linkType: hard + +"@rspack/plugin-react-refresh@npm:2.0.0": + version: 2.0.0 + resolution: "@rspack/plugin-react-refresh@npm:2.0.0" + peerDependencies: + "@rspack/core": ^2.0.0-0 + react-refresh: ">=0.10.0 <1.0.0" + peerDependenciesMeta: + "@rspack/core": + optional: true + checksum: 10c0/ee8e6492c0772536abb98ab5ec92d0e4ad092613a3a6085542f0b354131eef48f4d7cbe9a8cf9a9389d5e51d623d08a2c07594c8add5ad5fb54fd5543150ead1 + languageName: node + linkType: hard + +"@rspress/core@npm:^2.0.13": + version: 2.0.14 + resolution: "@rspress/core@npm:2.0.14" + dependencies: + "@mdx-js/mdx": "npm:^3.1.1" + "@mdx-js/react": "npm:^3.1.1" + "@rsbuild/core": "npm:^2.0.9" + "@rsbuild/plugin-react": "npm:~2.0.1" + "@rspress/shared": "npm:2.0.14" + "@shikijs/rehype": "npm:^4.0.2" + "@types/unist": "npm:^3.0.3" + "@unhead/react": "npm:^2.1.15" + body-scroll-lock: "npm:4.0.0-beta.0" + clsx: "npm:2.1.1" + copy-to-clipboard: "npm:^3.3.3" + flexsearch: "npm:0.8.212" + hast-util-heading-rank: "npm:^3.0.0" + hast-util-to-jsx-runtime: "npm:^2.3.6" + mdast-util-mdx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.1" + medium-zoom: "npm:1.1.0" + nprogress: "npm:^0.2.0" + react: "npm:^19.2.6" + react-dom: "npm:^19.2.6" + react-lazy-with-preload: "npm:^2.2.1" + react-reconciler: "npm:0.33.0" + react-render-to-markdown: "npm:19.1.0" + react-router-dom: "npm:^7.15.1" + rehype-external-links: "npm:^3.0.0" + rehype-raw: "npm:^7.0.0" + remark-cjk-friendly: "npm:^2.0.1" + remark-cjk-friendly-gfm-strikethrough: "npm:^2.0.1" + remark-gfm: "npm:^4.0.1" + remark-mdx: "npm:^3.1.1" + remark-parse: "npm:^11.0.0" + remark-stringify: "npm:^11.0.0" + scroll-into-view-if-needed: "npm:^3.1.0" + shiki: "npm:^4.0.2" + unified: "npm:^11.0.5" + unist-util-remove: "npm:^4.0.0" + unist-util-visit: "npm:^5.1.0" + unist-util-visit-children: "npm:^3.0.0" + bin: + rspress: bin/rspress.js + checksum: 10c0/118ae88259f55dae748f92bdb64a7185861612fe5d784ebb5991b2598b85320ec9a718a72f6015542b79f620c358a34ed9f4f83cf98ff2aab45036f44b0f9455 + languageName: node + linkType: hard + +"@rspress/shared@npm:2.0.14": + version: 2.0.14 + resolution: "@rspress/shared@npm:2.0.14" + dependencies: + "@rsbuild/core": "npm:^2.0.9" + "@shikijs/rehype": "npm:^4.0.2" + unified: "npm:^11.0.5" + checksum: 10c0/a33bec3b057886e549f5fbd5b6c754e770836533346b339241d2cc77a8792ee501654434afceac459fcc5b1bf1a7b0d1d2f940ba296df57a628e9697accde596 + languageName: node + linkType: hard + +"@shikijs/core@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/core@npm:4.3.0" + dependencies: + "@shikijs/primitive": "npm:4.3.0" + "@shikijs/types": "npm:4.3.0" + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.4" + hast-util-to-html: "npm:^9.0.5" + checksum: 10c0/b8a92d73137b9a2e3f3877565bf4a5b69e1615d28fdcb9c671f1681fbb1005b4719ad024945847d73536434aff0afbf30e6951d9dcd3b12a2613a816f6884955 + languageName: node + linkType: hard + +"@shikijs/engine-javascript@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/engine-javascript@npm:4.3.0" + dependencies: + "@shikijs/types": "npm:4.3.0" + "@shikijs/vscode-textmate": "npm:^10.0.2" + oniguruma-to-es: "npm:^4.3.6" + checksum: 10c0/09afe0ec19f06ee6aa8dd3d7811fad143d473c9016cabfef8d4ed66748e4d2489c532ad8bbea54eb8e676c188b302078bbe836bac12bd8c2ae7e4eb7b1ff1a6c + languageName: node + linkType: hard + +"@shikijs/engine-oniguruma@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/engine-oniguruma@npm:4.3.0" + dependencies: + "@shikijs/types": "npm:4.3.0" + "@shikijs/vscode-textmate": "npm:^10.0.2" + checksum: 10c0/aee8b08df9be4fbfe20def4828b721014c5c5d062e016796309c9f5910dab521fd3093d39ef54ce6cca19a592c7d0e81cbe62645dff2e3bcd75179f505051ac9 + languageName: node + linkType: hard + +"@shikijs/langs@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/langs@npm:4.3.0" + dependencies: + "@shikijs/types": "npm:4.3.0" + checksum: 10c0/876cb2af730c66ec0f2c0946f7309d7e1706f42d4d360a1acb73d54c73edfa0786f8fcb2d70f5c2fca8ff04fdd25c28c7577fbe6bcf7d1ab73bf25579c9f1648 + languageName: node + linkType: hard + +"@shikijs/primitive@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/primitive@npm:4.3.0" + dependencies: + "@shikijs/types": "npm:4.3.0" + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.4" + checksum: 10c0/ce34e6bc917bd73e3aed0591c3ff2d7f73069c5dc1b1eb2cff3f5a7407340518568e3b2a43eb3dac6854dfc7cf146e60c7868de0f0fe68edb5873ba633a4c6a2 + languageName: node + linkType: hard + +"@shikijs/rehype@npm:^4.0.2": + version: 4.3.0 + resolution: "@shikijs/rehype@npm:4.3.0" + dependencies: + "@shikijs/types": "npm:4.3.0" + "@types/hast": "npm:^3.0.4" + hast-util-to-string: "npm:^3.0.1" + shiki: "npm:4.3.0" + unified: "npm:^11.0.5" + unist-util-visit: "npm:^5.1.0" + checksum: 10c0/37149f94a3dcbbd30d86e21d9d166ecdba4ff9bba75fe9dc1da6d8a7673508c6d41b0399ffc0fe79765c6dba5ab177e57b7a38c8daba10a8ead067bc5f6a7296 + languageName: node + linkType: hard + +"@shikijs/themes@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/themes@npm:4.3.0" + dependencies: + "@shikijs/types": "npm:4.3.0" + checksum: 10c0/5c63942de6eabf6c6e72c801000cbf4042222d855ee94c0cd3e4b2b62c70083db47d6b542f9c789d9bc62552be139b436a4db270bb73cf1206f1e63802521a4b + languageName: node + linkType: hard + +"@shikijs/transformers@npm:^4.1.0": + version: 4.3.0 + resolution: "@shikijs/transformers@npm:4.3.0" + dependencies: + "@shikijs/core": "npm:4.3.0" + "@shikijs/types": "npm:4.3.0" + checksum: 10c0/270d575450d7982073edd20b16f50c23b998d10eb0cefa3a78588a8e5107b07c5d249ffd47ff041c8e0fb0ccb2db2ca28635b3edfa1a1dc63584e07851a10fbf + languageName: node + linkType: hard + +"@shikijs/types@npm:4.3.0": + version: 4.3.0 + resolution: "@shikijs/types@npm:4.3.0" + dependencies: + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.4" + checksum: 10c0/b2f461511cefe20ba25978dfa2462246db7053d9fd03732e6c969c30fda413db9109e635aea5e58d56ab3946caa0d0bd6fdb043240a29c6f4ad5a4996442a823 + languageName: node + linkType: hard + +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10c0/36b682d691088ec244de292dc8f91b808f95c89466af421cf84cbab92230f03c8348649c14b3251991b10ce632b0c715e416e992dd5f28ff3221dc2693fd9462 + languageName: node + linkType: hard + "@simple-libs/child-process-utils@npm:^1.0.0": version: 1.0.2 resolution: "@simple-libs/child-process-utils@npm:1.0.2" @@ -3731,6 +4168,15 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:^0.5.23": + version: 0.5.23 + resolution: "@swc/helpers@npm:0.5.23" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/02da7b4df465693933ecd4851cc193ec729c309939c8a84eccae5ec0010aafc3894e713b8ef8d13a6ba401759f0e900c88e2dcfef5872c27bb91e70f73275cce + languageName: node + linkType: hard + "@testing-library/react-native@npm:^14.0.0": version: 14.0.0 resolution: "@testing-library/react-native@npm:14.0.0" @@ -3793,6 +4239,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.3 + resolution: "@tybys/wasm-util@npm:0.10.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/fd2bd2a79c6cd8c79ed1cf7a0fa375c64589264c88a27acaf9756d556b453ea222b62a4f68dd2fbb8b3a78b6bab3b1f4fb2431b6afc6aeda8344b53a521a1cd3 + languageName: node + linkType: hard + "@types/babel__core@npm:^7, @types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -3834,6 +4289,31 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.0.0": + version: 4.1.13 + resolution: "@types/debug@npm:4.1.13" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/e5e124021bbdb23a82727eee0a726ae0fc8a3ae1f57253cbcc47497f259afb357de7f6941375e773e1abbfa1604c1555b901a409d762ec2bb4c1612131d4afb7 + languageName: node + linkType: hard + +"@types/estree-jsx@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree-jsx@npm:1.0.5" + dependencies: + "@types/estree": "npm:*" + checksum: 10c0/07b354331516428b27a3ab99ee397547d47eb223c34053b48f84872fafb841770834b90cc1a0068398e7c7ccb15ec51ab00ec64b31dc5e3dbefd624638a35c6d + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:^1.0.0": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" @@ -3850,6 +4330,15 @@ __metadata: languageName: node linkType: hard +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/3249781a511b38f1d330fd1e3344eed3c4e7ea8eff82e835d35da78e637480d36fad37a78be5a7aed8465d237ad0446abc1150859d0fde395354ea634decf9f7 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" @@ -3875,6 +4364,29 @@ __metadata: languageName: node linkType: hard +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/84f403dbe582ee508fd9c7643ac781ad8597fcbfc9ccb8d4715a2c92e4545e5772cbd0dbdf18eda65789386d81b009967fdef01b24faf6640f817287f54d9c82 + languageName: node + linkType: hard + +"@types/mdx@npm:^2.0.0": + version: 2.0.14 + resolution: "@types/mdx@npm:2.0.14" + checksum: 10c0/f1e3873c1e36e2685fb99222bf81a23c55a8e2edf49d0c45cabf2c875b0ae814cd6050cce5770ff1c6881ac747d2ad33731e1de32139b04cf181d8d7e1943ff4 + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + "@types/node@npm:*": version: 25.9.1 resolution: "@types/node@npm:25.9.1" @@ -3891,6 +4403,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^24": + version: 24.13.2 + resolution: "@types/node@npm:24.13.2" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/d7d48a88a4feb0a6aac3cbfaf9ef3b12752b4b09447f88dd0b4c77c03b281e3d4330fe6982a99aedcd63fc16c7540a0c248b91eb2abb0b3edd884d7fe684e9ea + languageName: node + linkType: hard + "@types/react-reconciler@npm:~0.33.0": version: 0.33.0 resolution: "@types/react-reconciler@npm:0.33.0" @@ -3925,6 +4446,20 @@ __metadata: languageName: node linkType: hard +"@types/unist@npm:*, @types/unist@npm:^3.0.0, @types/unist@npm:^3.0.3": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 10c0/2b1e4adcab78388e088fcc3c0ae8700f76619dbcb4741d7d201f87e2cb346bfc29a89003cfea2d76c996e1061452e14fcd737e8b25aacf949c1f2d6b2bc3dd60 + languageName: node + linkType: hard + +"@types/unist@npm:^2.0.0": + version: 2.0.11 + resolution: "@types/unist@npm:2.0.11" + checksum: 10c0/24dcdf25a168f453bb70298145eb043cfdbb82472db0bc0b56d6d51cd2e484b9ed8271d4ac93000a80da568f2402e9339723db262d0869e2bf13bc58e081768d + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -3941,6 +4476,13 @@ __metadata: languageName: node linkType: hard +"@ungap/structured-clone@npm:^1.0.0": + version: 1.3.2 + resolution: "@ungap/structured-clone@npm:1.3.2" + checksum: 10c0/4d76bb376ec3e15f38bdffe045377807c79057daf54ae17eeb977c5b95efddd2d726b38c15aeb5d5c1a45c64ad03aa7e8b1a6dc67895480cba536ffd1c7a06ec + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.3.0": version: 1.3.1 resolution: "@ungap/structured-clone@npm:1.3.1" @@ -3948,6 +4490,17 @@ __metadata: languageName: node linkType: hard +"@unhead/react@npm:^2.1.15": + version: 2.1.15 + resolution: "@unhead/react@npm:2.1.15" + dependencies: + unhead: "npm:2.1.15" + peerDependencies: + react: ">=18.3.1" + checksum: 10c0/0a341187a86b9ac38065b521cad124bc0f8092052507551ffbc2d9538598c0dfcd0be7c473a9d117c20949aae0a02fced271a9b81d3826a6d62c0c69e1b4482b + languageName: node + linkType: hard + "@xmldom/xmldom@npm:^0.8.8": version: 0.8.13 resolution: "@xmldom/xmldom@npm:0.8.13" @@ -3998,6 +4551,24 @@ __metadata: languageName: node linkType: hard +"acorn-jsx@npm:^5.0.0": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.0.0": + version: 8.17.0 + resolution: "acorn@npm:8.17.0" + bin: + acorn: bin/acorn + checksum: 10c0/5dcefea5f8f023b6cc24cbe71fb5a8112b601d36c4fa07d14e4e6ffc2ee47383332c46b36c766d9437725aa6660156eae50efa0c838719823b50d7c327c4ed42 + languageName: node + linkType: hard + "acorn@npm:^8.15.0": version: 8.16.0 resolution: "acorn@npm:8.16.0" @@ -4176,6 +4747,15 @@ __metadata: languageName: node linkType: hard +"astring@npm:^1.8.0": + version: 1.9.0 + resolution: "astring@npm:1.9.0" + bin: + astring: bin/astring + checksum: 10c0/e7519544d9824494e80ef0e722bb3a0c543a31440d59691c13aeaceb75b14502af536b23f08db50aa6c632dafaade54caa25f0788aa7550b6b2d6e2df89e0830 + languageName: node + linkType: hard + "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -4407,6 +4987,13 @@ __metadata: languageName: node linkType: hard +"bail@npm:^2.0.0": + version: 2.0.2 + resolution: "bail@npm:2.0.2" + checksum: 10c0/25cbea309ef6a1f56214187004e8f34014eb015713ea01fa5b9b7e9e776ca88d0fdffd64143ac42dc91966c915a4b7b683411b56e14929fad16153fc026ffb8b + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -4462,6 +5049,13 @@ __metadata: languageName: node linkType: hard +"body-scroll-lock@npm:4.0.0-beta.0": + version: 4.0.0-beta.0 + resolution: "body-scroll-lock@npm:4.0.0-beta.0" + checksum: 10c0/32a9553b83424e69f3784d7bbcef2949c43159ea13b5084c96dc08b1e2585e4eeb1e915f14e37a4ac44110339dfa6fa2ddde4d3d812be88ecbea060aa724a791 + languageName: node + linkType: hard + "bplist-creator@npm:0.1.0": version: 0.1.0 resolution: "bplist-creator@npm:0.1.0" @@ -4583,6 +5177,13 @@ __metadata: languageName: node linkType: hard +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 10c0/3939b1664390174484322bc3f45b798462e6c07ee6384cb3d645e0aa2f318502d174845198c1561930e1d431087f74cf1fe291ae9a4722821a9f4ba67e574350 + languageName: node + linkType: hard + "chalk@npm:^2.0.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -4611,8 +5212,36 @@ __metadata: languageName: node linkType: hard -"chardet@npm:^2.1.1": - version: 2.1.1 +"character-entities-html4@npm:^2.0.0": + version: 2.1.0 + resolution: "character-entities-html4@npm:2.1.0" + checksum: 10c0/fe61b553f083400c20c0b0fd65095df30a0b445d960f3bbf271536ae6c3ba676f39cb7af0b4bf2755812f08ab9b88f2feed68f9aebb73bb153f7a115fe5c6e40 + languageName: node + linkType: hard + +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 10c0/ec4b430af873661aa754a896a2b55af089b4e938d3d010fad5219299a6b6d32ab175142699ee250640678cd64bdecd6db3c9af0b8759ab7b155d970d84c4c7d1 + languageName: node + linkType: hard + +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: 10c0/b0c645a45bcc90ff24f0e0140f4875a8436b8ef13b6bcd31ec02cfb2ca502b680362aa95386f7815bdc04b6464d48cf191210b3840d7c04241a149ede591a308 + languageName: node + linkType: hard + +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 10c0/2ae0dec770cd8659d7e8b0ce24392d83b4c2f0eb4a3395c955dce5528edd4cc030a794cfa06600fcdd700b3f2de2f9b8e40e309c0011c4180e3be64a0b42e6a1 + languageName: node + linkType: hard + +"chardet@npm:^2.1.1": + version: 2.1.1 resolution: "chardet@npm:2.1.1" checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 languageName: node @@ -4732,6 +5361,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -4739,6 +5375,13 @@ __metadata: languageName: node linkType: hard +"collapse-white-space@npm:^2.0.0": + version: 2.1.0 + resolution: "collapse-white-space@npm:2.1.0" + checksum: 10c0/b2e2800f4ab261e62eb27a1fbe853378296e3a726d6695117ed033e82d61fb6abeae4ffc1465d5454499e237005de9cfc52c9562dc7ca4ac759b9a222ef14453 + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0": version: 1.0.3 resolution: "collect-v8-coverage@npm:1.0.3" @@ -4778,6 +5421,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: 10c0/91f90f1aae320f1755d6957ef0b864fe4f54737f3313bd95e0802686ee2ca38bff1dd381964d00ae5db42912dd1f4ae5c2709644e82706ffc6f6842a813cdd67 + languageName: node + linkType: hard + "commander@npm:^12.0.0": version: 12.1.0 resolution: "commander@npm:12.1.0" @@ -4845,6 +5495,13 @@ __metadata: languageName: node linkType: hard +"compute-scroll-into-view@npm:^3.0.2": + version: 3.1.1 + resolution: "compute-scroll-into-view@npm:3.1.1" + checksum: 10c0/59761ed62304a9599b52ad75d0d6fbf0669ee2ab7dd472fdb0ad9da36628414c014dea7b5810046560180ad30ffec52a953d19297f66a1d4f3aa0999b9d2521d + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -4901,6 +5558,22 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.0.1": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c + languageName: node + linkType: hard + +"copy-to-clipboard@npm:^3.3.3": + version: 3.3.3 + resolution: "copy-to-clipboard@npm:3.3.3" + dependencies: + toggle-selection: "npm:^1.0.6" + checksum: 10c0/3ebf5e8ee00601f8c440b83ec08d838e8eabb068c1fae94a9cda6b42f288f7e1b552f3463635f419af44bf7675afc8d0390d30876cf5c2d5d35f86d9c56a3e5f + languageName: node + linkType: hard + "core-js-compat@npm:^3.43.0, core-js-compat@npm:^3.48.0": version: 3.49.0 resolution: "core-js-compat@npm:3.49.0" @@ -4991,7 +5664,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -5012,6 +5685,15 @@ __metadata: languageName: node linkType: hard +"decode-named-character-reference@npm:^1.0.0": + version: 1.3.0 + resolution: "decode-named-character-reference@npm:1.3.0" + dependencies: + character-entities: "npm:^2.0.0" + checksum: 10c0/787f4c87f3b82ea342aa7c2d7b1882b6fb9511bb77f72ae44dcaabea0470bacd1e9c6a0080ab886545019fa0cb3a7109573fad6b61a362844c3a0ac52b36e4bb + languageName: node + linkType: hard + "dedent@npm:^1.0.0, dedent@npm:^1.7.2": version: 1.7.2 resolution: "dedent@npm:1.7.2" @@ -5083,6 +5765,13 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.0": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 + languageName: node + linkType: hard + "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -5111,6 +5800,15 @@ __metadata: languageName: node linkType: hard +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: "npm:^2.0.0" + checksum: 10c0/e0928ab8f94c59417a2b8389c45c55ce0a02d9ac7fd74ef62d01ba48060129e1d594501b77de01f3eeafc7cb00773819b0df74d96251cf20b31c5b3071f45c0e + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -5218,6 +5916,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10c0/ed836ddac5acb34341094eb495185d527bd70e8632b6c0d59548cbfa23defdbae70b96f9a405c82904efa421230b5b3fd2283752447d737beffd3f3e6ee74414 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -5264,6 +5969,30 @@ __metadata: languageName: node linkType: hard +"esast-util-from-estree@npm:^2.0.0": + version: 2.0.0 + resolution: "esast-util-from-estree@npm:2.0.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + devlop: "npm:^1.0.0" + estree-util-visit: "npm:^2.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + checksum: 10c0/6c619bc6963314f8f64b32e3b101b321bf121f659e62b11e70f425619c2db6f1d25f4c594a57fd00908da96c67d9bfbf876eb5172abf9e13f47a71796f6630ff + languageName: node + linkType: hard + +"esast-util-from-js@npm:^2.0.0": + version: 2.0.1 + resolution: "esast-util-from-js@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + acorn: "npm:^8.0.0" + esast-util-from-estree: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/3a446fb0b0d7bcd7e0157aa44b3b692802a08c93edbea81cc0f7fe4437bfdfb4b72e4563fe63b4e36d390086b71185dba4ac921f4180cc6349985c263cc74421 + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -5316,6 +6045,74 @@ __metadata: languageName: node linkType: hard +"estree-util-attach-comments@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-attach-comments@npm:3.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/ee69bb5c45e2ad074725b90ed181c1c934b29d81bce4b0c7761431e83c4c6ab1b223a6a3d6a4fbeb92128bc5d5ee201d5dd36cf1770aa5e16a40b0cf36e8a1f1 + languageName: node + linkType: hard + +"estree-util-build-jsx@npm:^3.0.0": + version: 3.0.1 + resolution: "estree-util-build-jsx@npm:3.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + estree-walker: "npm:^3.0.0" + checksum: 10c0/274c119817b8e7caa14a9778f1e497fea56cdd2b01df1a1ed037f843178992d3afe85e0d364d485e1e2e239255763553d1b647b15e4a7ba50851bcb43dc6bf80 + languageName: node + linkType: hard + +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: 10c0/d1881c6ed14bd588ebd508fc90bf2a541811dbb9ca04dec2f39d27dcaa635f85b5ed9bbbe7fc6fb1ddfca68744a5f7c70456b4b7108b6c4c52780631cc787c5b + languageName: node + linkType: hard + +"estree-util-scope@npm:^1.0.0": + version: 1.0.0 + resolution: "estree-util-scope@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + checksum: 10c0/ef8a573cc899277c613623a1722f630e2163abbc6e9e2f49e758c59b81b484e248b585df6df09a38c00fbfb6390117997cc80c1347b7a86bc1525d9e462b60d5 + languageName: node + linkType: hard + +"estree-util-to-js@npm:^2.0.0": + version: 2.0.0 + resolution: "estree-util-to-js@npm:2.0.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + astring: "npm:^1.8.0" + source-map: "npm:^0.7.0" + checksum: 10c0/ac88cb831401ef99e365f92f4af903755d56ae1ce0e0f0fb8ff66e678141f3d529194f0fb15f6c78cd7554c16fda36854df851d58f9e05cfab15bddf7a97cea0 + languageName: node + linkType: hard + +"estree-util-visit@npm:^2.0.0": + version: 2.0.0 + resolution: "estree-util-visit@npm:2.0.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/unist": "npm:^3.0.0" + checksum: 10c0/acda8b03cc8f890d79c7c7361f6c95331ba84b7ccc0c32b49f447fc30206b20002b37ffdfc97b6ad16e6fe065c63ecbae1622492e2b6b4775c15966606217f39 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.0": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -5572,6 +6369,13 @@ __metadata: languageName: node linkType: hard +"extend@npm:^3.0.0": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9 + languageName: node + linkType: hard + "extendable-error@npm:^0.1.5": version: 0.1.7 resolution: "extendable-error@npm:0.1.7" @@ -5693,6 +6497,13 @@ __metadata: languageName: node linkType: hard +"flexsearch@npm:0.8.212": + version: 0.8.212 + resolution: "flexsearch@npm:0.8.212" + checksum: 10c0/557a320fe028e694579d9f3f4cacd6acab6bcfa9101a63dbacf23ddbc872310a3370586a12007cf080ed2dfe3c99e6b21dd1e83697f2b4734722fbd43285d782 + languageName: node + linkType: hard + "flow-enums-runtime@npm:^0.0.6": version: 0.0.6 resolution: "flow-enums-runtime@npm:0.0.6" @@ -5794,7 +6605,7 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.0.0": +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.4.0": version: 1.6.0 resolution: "get-east-asian-width@npm:1.6.0" checksum: 10c0/7e72e9550fd49ca5b246f9af6bb2afc129c96412845ff6556b3274fd44817a381702ca17028efe9866b261a3d44254cbf21e6c90cf05b4b61675630af776d431 @@ -5944,6 +6755,182 @@ __metadata: languageName: node linkType: hard +"hast-util-from-parse5@npm:^8.0.0": + version: 8.0.3 + resolution: "hast-util-from-parse5@npm:8.0.3" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + devlop: "npm:^1.0.0" + hastscript: "npm:^9.0.0" + property-information: "npm:^7.0.0" + vfile: "npm:^6.0.0" + vfile-location: "npm:^5.0.0" + web-namespaces: "npm:^2.0.0" + checksum: 10c0/40ace6c0ad43c26f721c7499fe408e639cde917b2350c9299635e6326559855896dae3c3ebf7440df54766b96c4276a7823e8f376a2b6a28b37b591f03412545 + languageName: node + linkType: hard + +"hast-util-heading-rank@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-heading-rank@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/1879c84f629e73f1f13247ab349324355cd801363b44e3d46f763aa5c0ea3b42dcd47b46e5643a0502cf01a6b1fdb9208fd12852e44ca6c671b3e4bccf9369a1 + languageName: node + linkType: hard + +"hast-util-is-element@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-is-element@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/f5361e4c9859c587ca8eb0d8343492f3077ccaa0f58a44cd09f35d5038f94d65152288dcd0c19336ef2c9491ec4d4e45fde2176b05293437021570aa0bc3613b + languageName: node + linkType: hard + +"hast-util-parse-selector@npm:^4.0.0": + version: 4.0.0 + resolution: "hast-util-parse-selector@npm:4.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/5e98168cb44470dc274aabf1a28317e4feb09b1eaf7a48bbaa8c1de1b43a89cd195cb1284e535698e658e3ec26ad91bc5e52c9563c36feb75abbc68aaf68fb9f + languageName: node + linkType: hard + +"hast-util-raw@npm:^9.0.0": + version: 9.1.0 + resolution: "hast-util-raw@npm:9.1.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + hast-util-from-parse5: "npm:^8.0.0" + hast-util-to-parse5: "npm:^8.0.0" + html-void-elements: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + parse5: "npm:^7.0.0" + unist-util-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + web-namespaces: "npm:^2.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/d0d909d2aedecef6a06f0005cfae410d6475e6e182d768bde30c3af9fcbbe4f9beb0522bdc21d0679cb3c243c0df40385797ed255148d68b3d3f12e82d12aacc + languageName: node + linkType: hard + +"hast-util-to-estree@npm:^3.0.0": + version: 3.1.3 + resolution: "hast-util-to-estree@npm:3.1.3" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-attach-comments: "npm:^3.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-js: "npm:^1.0.0" + unist-util-position: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/8e86c075319082c8a6304c5bcdf24ec02466074571e993f58bfa2cfd70850ef46d33b5c402208597a87fe0f02f1e620bda5958217efb1b7396c81c486373b75f + languageName: node + linkType: hard + +"hast-util-to-html@npm:^9.0.5": + version: 9.0.5 + resolution: "hast-util-to-html@npm:9.0.5" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + ccount: "npm:^2.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-whitespace: "npm:^3.0.0" + html-void-elements: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + stringify-entities: "npm:^4.0.0" + zwitch: "npm:^2.0.4" + checksum: 10c0/b7a08c30bab4371fc9b4a620965c40b270e5ae7a8e94cf885f43b21705179e28c8e43b39c72885d1647965fb3738654e6962eb8b58b0c2a84271655b4d748836 + languageName: node + linkType: hard + +"hast-util-to-jsx-runtime@npm:^2.0.0, hast-util-to-jsx-runtime@npm:^2.3.6": + version: 2.3.6 + resolution: "hast-util-to-jsx-runtime@npm:2.3.6" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-js: "npm:^1.0.0" + unist-util-position: "npm:^5.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/27297e02848fe37ef219be04a26ce708d17278a175a807689e94a821dcffc88aa506d62c3a85beed1f9a8544f7211bdcbcde0528b7b456a57c2e342c3fd11056 + languageName: node + linkType: hard + +"hast-util-to-parse5@npm:^8.0.0": + version: 8.0.1 + resolution: "hast-util-to-parse5@npm:8.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + web-namespaces: "npm:^2.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/8e8a1817c7ff8906ac66e7201b1b8d19d9e1b705e695a6e71620270d498d982ec1ecc0e227bd517f723e91e7fdfb90ef75f9ae64d14b3b65239a7d5e1194d7dd + languageName: node + linkType: hard + +"hast-util-to-string@npm:^3.0.1": + version: 3.0.1 + resolution: "hast-util-to-string@npm:3.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/b5fa1912a6ba6131affae52a0f4394406c4c0d23c2b0307f1d69988f1030c7bb830289303e67c5ad8f674f5f23a454c1dcd492c39e45a22c1f46d3c9bce5bd0c + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/b898bc9fe27884b272580d15260b6bbdabe239973a147e97fa98c45fa0ffec967a481aaa42291ec34fb56530dc2d484d473d7e2bae79f39c83f3762307edfea8 + languageName: node + linkType: hard + +"hastscript@npm:^9.0.0": + version: 9.0.1 + resolution: "hastscript@npm:9.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-parse-selector: "npm:^4.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + checksum: 10c0/18dc8064e5c3a7a2ae862978e626b97a254e1c8a67ee9d0c9f06d373bba155ed805fc5b5ce21b990fb7bc174624889e5e1ce1cade264f1b1d58b48f994bc85ce + languageName: node + linkType: hard + "hermes-compiler@npm:0.14.1": version: 0.14.1 resolution: "hermes-compiler@npm:0.14.1" @@ -6047,6 +7034,13 @@ __metadata: languageName: node linkType: hard +"hookable@npm:^6.0.1": + version: 6.1.1 + resolution: "hookable@npm:6.1.1" + checksum: 10c0/bb46cd9ffc0a997af21febd97835da4e59a6989adec73dc3c215fcc44c7ac01de4781f251c3d420bf45862d2592a695f5f0de095b6f5df52db8afb5166938212 + languageName: node + linkType: hard + "hosted-git-info@npm:^7.0.0": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" @@ -6063,6 +7057,13 @@ __metadata: languageName: node linkType: hard +"html-void-elements@npm:^3.0.0": + version: 3.0.0 + resolution: "html-void-elements@npm:3.0.0" + checksum: 10c0/a8b9ec5db23b7c8053876dad73a0336183e6162bf6d2677376d8b38d654fdc59ba74fdd12f8812688f7db6fad451210c91b300e472afc0909224e0a44c8610d2 + languageName: node + linkType: hard + "http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" @@ -6210,6 +7211,13 @@ __metadata: languageName: node linkType: hard +"inline-style-parser@npm:0.2.7": + version: 0.2.7 + resolution: "inline-style-parser@npm:0.2.7" + checksum: 10c0/d884d76f84959517430ae6c22f0bda59bb3f58f539f99aac75a8d786199ec594ed648c6ab4640531f9fc244b0ed5cd8c458078e592d016ef06de793beb1debff + languageName: node + linkType: hard + "invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" @@ -6219,6 +7227,13 @@ __metadata: languageName: node linkType: hard +"is-absolute-url@npm:^4.0.0": + version: 4.0.1 + resolution: "is-absolute-url@npm:4.0.1" + checksum: 10c0/6f8f603945bd9f2c6031758bbc12352fc647bd5d807cad10d96cc6300fd0e15240cc091521a61db767e4ec0bacff257b4f1015fd5249c147bbb4a4497356c72e + languageName: node + linkType: hard + "is-absolute@npm:^1.0.0": version: 1.0.0 resolution: "is-absolute@npm:1.0.0" @@ -6229,6 +7244,23 @@ __metadata: languageName: node linkType: hard +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 10c0/932367456f17237533fd1fc9fe179df77957271020b83ea31da50e5cc472d35ef6b5fb8147453274ffd251134472ce24eb6f8d8398d96dee98237cdb81a6c9a7 + languageName: node + linkType: hard + +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + checksum: 10c0/4b35c42b18e40d41378293f82a3ecd9de77049b476f748db5697c297f686e1e05b072a6aaae2d16f54d2a57f85b00cbbe755c75f6d583d1c77d6657bd0feb5a2 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -6245,6 +7277,13 @@ __metadata: languageName: node linkType: hard +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 10c0/8085dd66f7d82f9de818fba48b9e9c0429cb4291824e6c5f2622e96b9680b54a07a624cfc663b24148b8e853c62a1c987cfe8b0b5a13f5156991afaf6736e334 + languageName: node + linkType: hard + "is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -6304,6 +7343,13 @@ __metadata: languageName: node linkType: hard +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 10c0/3eb60fe2f1e2bbc760b927dcad4d51eaa0c60138cf7fc671803f66353ad90c301605b502c7ea4c6bb0548e1c7e79dfd37b73b632652e3b76030bba603a7e9626 + languageName: node + linkType: hard + "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -6332,7 +7378,7 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^4.1.0": +"is-plain-obj@npm:^4.0.0, is-plain-obj@npm:^4.1.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e @@ -7358,6 +8404,13 @@ __metadata: languageName: node linkType: hard +"longest-streak@npm:^3.0.0": + version: 3.1.0 + resolution: "longest-streak@npm:3.1.0" + checksum: 10c0/7c2f02d0454b52834d1bcedef79c557bd295ee71fdabb02d041ff3aa9da48a90b5df7c0409156dedbc4df9b65da18742652aaea4759d6ece01f08971af6a7eaa + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -7410,6 +8463,20 @@ __metadata: languageName: node linkType: hard +"markdown-extensions@npm:^2.0.0": + version: 2.0.0 + resolution: "markdown-extensions@npm:2.0.0" + checksum: 10c0/406139da2aa0d5ebad86195c8e8c02412f873c452b4c087ae7bc767af37956141be449998223bb379eea179b5fd38dfa610602b6f29c22ddab5d51e627a7e41d + languageName: node + linkType: hard + +"markdown-table@npm:^3.0.0": + version: 3.0.4 + resolution: "markdown-table@npm:3.0.4" + checksum: 10c0/1257b31827629a54c24a5030a3dac952256c559174c95ce3ef89bebd6bff0cb1444b1fd667b1a1bb53307f83278111505b3e26f0c4e7b731e0060d435d2d930b + languageName: node + linkType: hard + "marky@npm:^1.2.2": version: 1.3.0 resolution: "marky@npm:1.3.0" @@ -7417,72 +8484,335 @@ __metadata: languageName: node linkType: hard -"memoize-one@npm:^5.0.0": - version: 5.2.1 - resolution: "memoize-one@npm:5.2.1" - checksum: 10c0/fd22dbe9a978a2b4f30d6a491fc02fb90792432ad0dab840dc96c1734d2bd7c9cdeb6a26130ec60507eb43230559523615873168bcbe8fafab221c30b11d54c1 +"mdast-util-find-and-replace@npm:^3.0.0": + version: 3.0.2 + resolution: "mdast-util-find-and-replace@npm:3.0.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + escape-string-regexp: "npm:^5.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/c8417a35605d567772ff5c1aa08363ff3010b0d60c8ea68c53cba09bf25492e3dd261560425c1756535f3b7107f62e7ff3857cdd8fb1e62d1b2cc2ea6e074ca2 languageName: node linkType: hard -"meow@npm:^13.0.0": - version: 13.2.0 - resolution: "meow@npm:13.2.0" - checksum: 10c0/d5b339ae314715bcd0b619dd2f8a266891928e21526b4800d49b4fba1cc3fff7e2c1ff5edd3344149fac841bc2306157f858e8c4d5eaee4d52ce52ad925664ce +"mdast-util-from-markdown@npm:^2.0.0": + version: 2.0.3 + resolution: "mdast-util-from-markdown@npm:2.0.3" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark: "npm:^4.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-stringify-position: "npm:^4.0.0" + checksum: 10c0/d3eac9ac2b88e3b41fb85aa81c7bfd1f4f8a2fde497ad805e66fea7b2abfe486ffd94d2a20f9fd2951dcdebe4916f3bdcf851319891dd62d343e26c2f02583ba languageName: node linkType: hard -"meow@npm:^14.0.0": - version: 14.1.0 - resolution: "meow@npm:14.1.0" - checksum: 10c0/f0ca4bb4fd08e4b9470fcbb7332deb61d72d40d4bda18ffb87c1a98e5014c0b44749ae9f0cab18fa532e26d61cef5d453831f9ae23ac09fa8ea0e0469be73ebc +"mdast-util-gfm-autolink-literal@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-gfm-autolink-literal@npm:2.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.0.0" + mdast-util-find-and-replace: "npm:^3.0.0" + micromark-util-character: "npm:^2.0.0" + checksum: 10c0/963cd22bd42aebdec7bdd0a527c9494d024d1ad0739c43dc040fee35bdfb5e29c22564330a7418a72b5eab51d47a6eff32bc0255ef3ccb5cebfe8970e91b81b6 languageName: node linkType: hard -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 +"mdast-util-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "mdast-util-gfm-footnote@npm:2.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.1.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + checksum: 10c0/8ab965ee6be3670d76ec0e95b2ba3101fc7444eec47564943ab483d96ac17d29da2a4e6146a2a288be30c21b48c4f3938a1e54b9a46fbdd321d49a5bc0077ed0 languageName: node linkType: hard -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb +"mdast-util-gfm-strikethrough@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/b053e93d62c7545019bd914271ea9e5667ad3b3b57d16dbf68e56fea39a7e19b4a345e781312714eb3d43fdd069ff7ee22a3ca7f6149dfa774554f19ce3ac056 languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.7": - version: 0.83.7 - resolution: "metro-babel-transformer@npm:0.83.7" +"mdast-util-gfm-table@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-table@npm:2.0.0" dependencies: - "@babel/core": "npm:^7.25.2" - flow-enums-runtime: "npm:^0.0.6" - hermes-parser: "npm:0.35.0" - metro-cache-key: "npm:0.83.7" - nullthrows: "npm:^1.1.1" - checksum: 10c0/c50c2fe9fa3d3fbe382d8a482ebcc77d7dba0ef4673cee4904a55b9ea28dea4b86d3b80e9b9ac0cd2b094b9ecd0368c953bba0f1df1cfd36c3a27c666352fd86 + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + markdown-table: "npm:^3.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/128af47c503a53bd1c79f20642561e54a510ad5e2db1e418d28fefaf1294ab839e6c838e341aef5d7e404f9170b9ca3d1d89605f234efafde93ee51174a6e31e languageName: node linkType: hard -"metro-babel-transformer@npm:0.84.4": - version: 0.84.4 - resolution: "metro-babel-transformer@npm:0.84.4" +"mdast-util-gfm-task-list-item@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" dependencies: - "@babel/core": "npm:^7.25.2" - flow-enums-runtime: "npm:^0.0.6" - hermes-parser: "npm:0.35.0" - metro-cache-key: "npm:0.84.4" - nullthrows: "npm:^1.1.1" - checksum: 10c0/d1ac996666334bc1cfe9d399cbf4cd747b675f6f8f758c2317eebcc52bd76046ed864ddb7b270efeb8cf337940a61fb03912e5c859b7cbc54687c2f5c41a9d2a + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/258d725288482b636c0a376c296431390c14b4f29588675297cb6580a8598ed311fc73ebc312acfca12cc8546f07a3a285a53a3b082712e2cbf5c190d677d834 languageName: node linkType: hard -"metro-cache-key@npm:0.83.7": - version: 0.83.7 - resolution: "metro-cache-key@npm:0.83.7" +"mdast-util-gfm@npm:^3.0.0": + version: 3.1.0 + resolution: "mdast-util-gfm@npm:3.1.0" dependencies: - flow-enums-runtime: "npm:^0.0.6" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-gfm-autolink-literal: "npm:^2.0.0" + mdast-util-gfm-footnote: "npm:^2.0.0" + mdast-util-gfm-strikethrough: "npm:^2.0.0" + mdast-util-gfm-table: "npm:^2.0.0" + mdast-util-gfm-task-list-item: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/4bedcfb6a20e39901c8772f0d2bb2d7a64ae87a54c13cbd92eec062cf470fbb68c2ad754e149af5b30794e2de61c978ab1de1ace03c0c40f443ca9b9b8044f81 + languageName: node + linkType: hard + +"mdast-util-mdx-expression@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdx-expression@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/9a1e57940f66431f10312fa239096efa7627f375e7933b5d3162c0b5c1712a72ac87447aff2b6838d2bbd5c1311b188718cc90b33b67dc67a88550e0a6ef6183 + languageName: node + linkType: hard + +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.1.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + stringify-entities: "npm:^4.0.0" + unist-util-stringify-position: "npm:^4.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/3acadaf3b962254f7ad2990fed4729961dc0217ca31fde9917986e880843f3ecf3392b1f22d569235cacd180d50894ad266db7af598aedca69d330d33c7ac613 + languageName: node + linkType: hard + +"mdast-util-mdx@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-mdx@npm:3.0.0" + dependencies: + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/4faea13f77d6bc9aa64ee41a5e4779110b73444a17fda363df6ebe880ecfa58b321155b71f8801c3faa6d70d6222a32a00cbd6dbf5fad8db417f4688bc9c74e1 + languageName: node + linkType: hard + +"mdast-util-mdxjs-esm@npm:^2.0.0, mdast-util-mdxjs-esm@npm:^2.0.1": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/5bda92fc154141705af2b804a534d891f28dac6273186edf1a4c5e3f045d5b01dbcac7400d27aaf91b7e76e8dce007c7b2fdf136c11ea78206ad00bdf9db46bc + languageName: node + linkType: hard + +"mdast-util-phrasing@npm:^4.0.0": + version: 4.1.0 + resolution: "mdast-util-phrasing@npm:4.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10c0/bf6c31d51349aa3d74603d5e5a312f59f3f65662ed16c58017169a5fb0f84ca98578f626c5ee9e4aa3e0a81c996db8717096705521bddb4a0185f98c12c9b42f + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.1 + resolution: "mdast-util-to-hast@npm:13.2.1" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + trim-lines: "npm:^3.0.0" + unist-util-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/3eeaf28a5e84e1e08e6d54a1a8a06c0fca88cb5d36f4cf8086f0177248d1ce6e4e751f4ad0da19a3dea1c6ea61bd80784acc3ae021e44ceeb21aa5413a375e43 + languageName: node + linkType: hard + +"mdast-util-to-markdown-cjk-friendly-gfm-strikethrough@npm:1.0.0": + version: 1.0.0 + resolution: "mdast-util-to-markdown-cjk-friendly-gfm-strikethrough@npm:1.0.0" + dependencies: + mdast-util-gfm-strikethrough: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.1.2" + micromark-extension-cjk-friendly-util: "npm:3.0.1" + micromark-util-symbol: "npm:^2.0.1" + peerDependencies: + "@types/mdast": "*" + peerDependenciesMeta: + "@types/mdast": + optional: true + checksum: 10c0/dc03d1001d8f5d2b33134935daa162d8f5bd307f1dc93af5b257487bdb216a0b60434d238c8f4f762f77c99767838a2f1f0790d355b711318df1de4f95072953 + languageName: node + linkType: hard + +"mdast-util-to-markdown-cjk-friendly@npm:1.0.0": + version: 1.0.0 + resolution: "mdast-util-to-markdown-cjk-friendly@npm:1.0.0" + dependencies: + mdast-util-to-markdown: "npm:^2.1.2" + micromark-extension-cjk-friendly-util: "npm:3.0.1" + micromark-util-symbol: "npm:^2.0.1" + peerDependencies: + "@types/mdast": "*" + peerDependenciesMeta: + "@types/mdast": + optional: true + checksum: 10c0/5fedb40578f136f74921abd3457626637328ddec8d0e60f490ee7c69b10308d5a580faa627a06a11303985d2c714ad46a883d4a436015c0e9639bd333b54ec6b + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^2.0.0, mdast-util-to-markdown@npm:^2.1.2": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + longest-streak: "npm:^3.0.0" + mdast-util-phrasing: "npm:^4.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + unist-util-visit: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/4649722a6099f12e797bd8d6469b2b43b44e526b5182862d9c7766a3431caad2c0112929c538a972f214e63c015395e5d3f54bd81d9ac1b16e6d8baaf582f749 + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + checksum: 10c0/2d3c1af29bf3fe9c20f552ee9685af308002488f3b04b12fa66652c9718f66f41a32f8362aa2d770c3ff464c034860b41715902ada2306bb0a055146cef064d7 + languageName: node + linkType: hard + +"medium-zoom@npm:1.1.0": + version: 1.1.0 + resolution: "medium-zoom@npm:1.1.0" + checksum: 10c0/7d1f05e8eab045c33d7c04d4ee7bf04f5246cf7a720d7b5f5a51c36ab23666e363bcbb6bffae50b5948d5eb19361914cb0e26a1fce5c1fff7a266bc0217893f3 + languageName: node + linkType: hard + +"memoize-one@npm:^5.0.0": + version: 5.2.1 + resolution: "memoize-one@npm:5.2.1" + checksum: 10c0/fd22dbe9a978a2b4f30d6a491fc02fb90792432ad0dab840dc96c1734d2bd7c9cdeb6a26130ec60507eb43230559523615873168bcbe8fafab221c30b11d54c1 + languageName: node + linkType: hard + +"meow@npm:^13.0.0": + version: 13.2.0 + resolution: "meow@npm:13.2.0" + checksum: 10c0/d5b339ae314715bcd0b619dd2f8a266891928e21526b4800d49b4fba1cc3fff7e2c1ff5edd3344149fac841bc2306157f858e8c4d5eaee4d52ce52ad925664ce + languageName: node + linkType: hard + +"meow@npm:^14.0.0": + version: 14.1.0 + resolution: "meow@npm:14.1.0" + checksum: 10c0/f0ca4bb4fd08e4b9470fcbb7332deb61d72d40d4bda18ffb87c1a98e5014c0b44749ae9f0cab18fa532e26d61cef5d453831f9ae23ac09fa8ea0e0469be73ebc + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"metro-babel-transformer@npm:0.83.7": + version: 0.83.7 + resolution: "metro-babel-transformer@npm:0.83.7" + dependencies: + "@babel/core": "npm:^7.25.2" + flow-enums-runtime: "npm:^0.0.6" + hermes-parser: "npm:0.35.0" + metro-cache-key: "npm:0.83.7" + nullthrows: "npm:^1.1.1" + checksum: 10c0/c50c2fe9fa3d3fbe382d8a482ebcc77d7dba0ef4673cee4904a55b9ea28dea4b86d3b80e9b9ac0cd2b094b9ecd0368c953bba0f1df1cfd36c3a27c666352fd86 + languageName: node + linkType: hard + +"metro-babel-transformer@npm:0.84.4": + version: 0.84.4 + resolution: "metro-babel-transformer@npm:0.84.4" + dependencies: + "@babel/core": "npm:^7.25.2" + flow-enums-runtime: "npm:^0.0.6" + hermes-parser: "npm:0.35.0" + metro-cache-key: "npm:0.84.4" + nullthrows: "npm:^1.1.1" + checksum: 10c0/d1ac996666334bc1cfe9d399cbf4cd747b675f6f8f758c2317eebcc52bd76046ed864ddb7b270efeb8cf337940a61fb03912e5c859b7cbc54687c2f5c41a9d2a + languageName: node + linkType: hard + +"metro-cache-key@npm:0.83.7": + version: 0.83.7 + resolution: "metro-cache-key@npm:0.83.7" + dependencies: + flow-enums-runtime: "npm:^0.0.6" checksum: 10c0/a2f20b86b173cd2be710552a49e6389a2c9ef994ac35873616bd4e5c34588beb8c93279bcf525295ca73b6d206ab0d9942f35cbd4a76e642d359bdab48924d7a languageName: node linkType: hard @@ -7900,6 +9230,497 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-destination: "npm:^2.0.0" + micromark-factory-label: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-title: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-html-tag-name: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bd4a794fdc9e88dbdf59eaf1c507ddf26e5f7ddf4e52566c72239c0f1b66adbcd219ba2cd42350debbe24471434d5f5e50099d2b3f4e5762ca222ba8e5b549ee + languageName: node + linkType: hard + +"micromark-extension-cjk-friendly-gfm-strikethrough@npm:2.0.1": + version: 2.0.1 + resolution: "micromark-extension-cjk-friendly-gfm-strikethrough@npm:2.0.1" + dependencies: + devlop: "npm:^1.1.0" + get-east-asian-width: "npm:^1.4.0" + micromark-extension-cjk-friendly-util: "npm:3.0.1" + micromark-util-character: "npm:^2.1.1" + micromark-util-chunked: "npm:^2.0.1" + micromark-util-resolve-all: "npm:^2.0.1" + micromark-util-symbol: "npm:^2.0.1" + peerDependencies: + micromark: ^4.0.0 + micromark-util-types: ^2.0.0 + peerDependenciesMeta: + micromark-util-types: + optional: true + checksum: 10c0/6e51e6101fc287322b12379b32052fb6cbfdb1f427f87671d1b5a3b9d4fc685b60eb8a01f897bb5fc1aeae6197ec668e8f0a3179db80870b11e295cc2fe4bbb3 + languageName: node + linkType: hard + +"micromark-extension-cjk-friendly-util@npm:3.0.1": + version: 3.0.1 + resolution: "micromark-extension-cjk-friendly-util@npm:3.0.1" + dependencies: + get-east-asian-width: "npm:^1.4.0" + micromark-util-character: "npm:^2.1.1" + micromark-util-symbol: "npm:^2.0.1" + peerDependenciesMeta: + micromark-util-types: + optional: true + checksum: 10c0/5a9210b21fcf38b6b6f17dd49587eb70cc17001294e49a81cc128975be8fcb7654d70ed198280c7d56e8664338c1cbe0ca9d4ecbe57348e380f0552b7178602c + languageName: node + linkType: hard + +"micromark-extension-cjk-friendly@npm:2.0.1": + version: 2.0.1 + resolution: "micromark-extension-cjk-friendly@npm:2.0.1" + dependencies: + devlop: "npm:^1.1.0" + micromark-extension-cjk-friendly-util: "npm:3.0.1" + micromark-util-chunked: "npm:^2.0.1" + micromark-util-resolve-all: "npm:^2.0.1" + micromark-util-symbol: "npm:^2.0.1" + peerDependencies: + micromark: ^4.0.0 + micromark-util-types: ^2.0.0 + peerDependenciesMeta: + micromark-util-types: + optional: true + checksum: 10c0/23bd7aabecc83f58508e29a181cf1baa5ad7e0612e1a227cdbd445214afbfd47e9bb2100e740d15728078eaeedd7820cdc70585f36c62ecb70e38bd6e017afa4 + languageName: node + linkType: hard + +"micromark-extension-gfm-autolink-literal@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe + languageName: node + linkType: hard + +"micromark-extension-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-footnote@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc + languageName: node + linkType: hard + +"micromark-extension-gfm-strikethrough@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-strikethrough@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/ef4f248b865bdda71303b494671b7487808a340b25552b11ca6814dff3fcfaab9be8d294643060bbdb50f79313e4a686ab18b99cbe4d3ee8a4170fcd134234fb + languageName: node + linkType: hard + +"micromark-extension-gfm-table@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-extension-gfm-table@npm:2.1.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/04bc00e19b435fa0add62cd029d8b7eb6137522f77832186b1d5ef34544a9bd030c9cf85e92ddfcc5c31f6f0a58a43d4b96dba4fc21316037c734630ee12c912 + languageName: node + linkType: hard + +"micromark-extension-gfm-tagfilter@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-gfm-tagfilter@npm:2.0.0" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/995558843fff137ae4e46aecb878d8a4691cdf23527dcf1e2f0157d66786be9f7bea0109c52a8ef70e68e3f930af811828ba912239438e31a9cfb9981f44d34d + languageName: node + linkType: hard + +"micromark-extension-gfm-task-list-item@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-task-list-item@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/78aa537d929e9309f076ba41e5edc99f78d6decd754b6734519ccbbfca8abd52e1c62df68d41a6ae64d2a3fc1646cea955893c79680b0b4385ced4c52296181f + languageName: node + linkType: hard + +"micromark-extension-gfm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-gfm@npm:3.0.0" + dependencies: + micromark-extension-gfm-autolink-literal: "npm:^2.0.0" + micromark-extension-gfm-footnote: "npm:^2.0.0" + micromark-extension-gfm-strikethrough: "npm:^2.0.0" + micromark-extension-gfm-table: "npm:^2.0.0" + micromark-extension-gfm-tagfilter: "npm:^2.0.0" + micromark-extension-gfm-task-list-item: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/970e28df6ebdd7c7249f52a0dda56e0566fbfa9ae56c8eeeb2445d77b6b89d44096880cd57a1c01e7821b1f4e31009109fbaca4e89731bff7b83b8519690e5d9 + languageName: node + linkType: hard + +"micromark-extension-mdx-expression@npm:^3.0.0": + version: 3.0.1 + resolution: "micromark-extension-mdx-expression@npm:3.0.1" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-mdx-expression: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/4d8cc5353b083b06bd51c98389de9c198261a5b2b440b75e85000a18d10511f21ba77538d6dfde0e0589df9de3fba9a1d14c2448d30c92d6b461c26d86e397f4 + languageName: node + linkType: hard + +"micromark-extension-mdx-jsx@npm:^3.0.0": + version: 3.0.2 + resolution: "micromark-extension-mdx-jsx@npm:3.0.2" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + micromark-factory-mdx-expression: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/5693b2e51934ac29a6aab521eaa2151f891d1fe092550bbd4ce24e4dd7567c1421a54f5e585a57dfa1769a79570f6df57ddd7a98bf0889dd11d495847a266dd7 + languageName: node + linkType: hard + +"micromark-extension-mdx-md@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-mdx-md@npm:2.0.0" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bae91c61273de0e5ba80a980c03470e6cd9d7924aa936f46fbda15d780704d9386e945b99eda200e087b96254fbb4271a9545d5ce02676cd6ae67886a8bf82df + languageName: node + linkType: hard + +"micromark-extension-mdxjs-esm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs-esm@npm:3.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/13e3f726495a960650cdedcba39198ace5bdc953ccb12c14d71fc9ed9bb88e40cc3ba9231e973f6984da3b3573e7ddb23ce409f7c16f52a8d57b608bf46c748d + languageName: node + linkType: hard + +"micromark-extension-mdxjs@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs@npm:3.0.0" + dependencies: + acorn: "npm:^8.0.0" + acorn-jsx: "npm:^5.0.0" + micromark-extension-mdx-expression: "npm:^3.0.0" + micromark-extension-mdx-jsx: "npm:^3.0.0" + micromark-extension-mdx-md: "npm:^2.0.0" + micromark-extension-mdxjs-esm: "npm:^3.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/fd84f036ddad0aabbc12e7f1b3e9dcfe31573bbc413c5ae903779ef0366d7a4c08193547e7ba75718c9f45654e45f52e575cfc2f23a5f89205a8a70d9a506aea + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bbafcf869cee5bf511161354cb87d61c142592fbecea051000ff116068dc85216e6d48519d147890b9ea5d7e2864a6341c0c09d9948c203bff624a80a476023c + languageName: node + linkType: hard + +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/0137716b4ecb428114165505e94a2f18855c8bbea21b07a8b5ce514b32a595ed789d2b967125718fc44c4197ceaa48f6609d58807a68e778138d2e6b91b824e8 + languageName: node + linkType: hard + +"micromark-factory-mdx-expression@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-factory-mdx-expression@npm:2.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/a6004ef6272dd01a5d718f2affd7bfb5e08f0849340f5fd96ac823fbc5e9d3b3343acedda50805873ccda5e3b8af4d5fbb302abc874544044ac90c217345cf97 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/f9ed43f1c0652d8d898de0ac2be3f77f776fffe7dd96bdbba1e02d7ce33d3853c6ff5daa52568fc4fa32cdf3a62d86b85ead9b9189f7211e1d69ff2163c450fb + languageName: node + linkType: hard + +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/e72fad8d6e88823514916890099a5af20b6a9178ccf78e7e5e05f4de99bb8797acb756257d7a3a57a53854cb0086bf8aab15b1a9e9db8982500dd2c9ff5948b6 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/20a1ec58698f24b766510a309b23a10175034fcf1551eaa9da3adcbed3e00cd53d1ebe5f030cf873f76a1cec3c34eb8c50cc227be3344caa9ed25d56cf611224 + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0, micromark-util-character@npm:^2.1.1": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d3fe7a5e2c4060fc2a076f9ce699c82a2e87190a3946e1e5eea77f563869b504961f5668d9c9c014724db28ac32fa909070ea8b30c3a39bd0483cc6c04cc76a1 + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^2.0.0, micromark-util-chunked@npm:^2.0.1": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/b68c0c16fe8106949537bdcfe1be9cf36c0ccd3bc54c4007003cb0984c3750b6cdd0fd77d03f269a3382b85b0de58bde4f6eedbe7ecdf7244759112289b1ab56 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/8a02e59304005c475c332f581697e92e8c585bcd45d5d225a66c1c1b14ab5a8062705188c2ccec33cc998d33502514121478b2091feddbc751887fc9c290ed08 + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" + dependencies: + micromark-util-chunked: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/f15e282af24c8372cbb10b9b0b3e2c0aa681fea0ca323a44d6bc537dc1d9382c819c3689f14eaa000118f5a163245358ce6276b2cda9a84439cdb221f5d86ae7 + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/9c8a9f2c790e5593ffe513901c3a110e9ec8882a08f466da014112a25e5059b51551ca0aeb7ff494657d86eceb2f02ee556c6558b8d66aadc61eae4a240da0df + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-string@npm:2.0.1" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/f24d75b2e5310be6e7b6dee532e0d17d3bf46996841d6295f2a9c87a2046fff4ab603c52ab9d7a7a6430a8b787b1574ae895849c603d262d1b22eef71736b5cb + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: 10c0/b2b29f901093845da8a1bf997ea8b7f5e061ffdba85070dfe14b0197c48fda64ffcf82bfe53c90cf9dc185e69eef8c5d41cae3ba918b96bc279326921b59008a + languageName: node + linkType: hard + +"micromark-util-events-to-acorn@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-util-events-to-acorn@npm:2.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/unist": "npm:^3.0.0" + devlop: "npm:^1.0.0" + estree-util-visit: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/a4e0716e943ffdd16a918edf51d4f8291ec2692f5c4d04693dbef3358716fba891f288197afd102c14f4d98dac09d52351046ab7aad1d50b74677bdd5fa683c0 + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: 10c0/ae80444db786fde908e9295f19a27a4aa304171852c77414516418650097b8afb401961c9edb09d677b06e97e8370cfa65638dde8438ebd41d60c0a8678b85b9 + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/5299265fa360769fc499a89f40142f10a9d4a5c3dd8e6eac8a8ef3c2e4a6570e4c009cf75ea46dce5ee31c01f25587bde2f4a5cc0a935584ae86dd857f2babbd + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^2.0.0, micromark-util-resolve-all@npm:^2.0.1": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bb6ca28764696bb479dc44a2d5b5fe003e7177aeae1d6b0d43f24cc223bab90234092d9c3ce4a4d2b8df095ccfd820537b10eb96bb7044d635f385d65a4c984a + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/60e92166e1870fd4f1961468c2651013ff760617342918e0e0c3c4e872433aa2e60c1e5a672bfe5d89dc98f742d6b33897585cf86ae002cda23e905a3c02527c + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bee69eece4393308e657c293ba80d92ebcb637e5f55e21dcf9c3fa732b91a8eda8ac248d76ff375e675175bfadeae4712e5158ef97eef1111789da1ce7ab5067 + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0, micromark-util-symbol@npm:^2.0.1": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: 10c0/f2d1b207771e573232436618e78c5e46cd4b5c560dd4a6d63863d58018abbf49cb96ec69f7007471e51434c60de3c9268ef2bf46852f26ff4aacd10f9da16fe9 + languageName: node + linkType: hard + +"micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 10c0/c8c15b96c858db781c4393f55feec10004bf7df95487636c9a9f7209e51002a5cca6a047c5d2a5dc669ff92da20e57aaa881e81a268d9ccadb647f9dce305298 + languageName: node + linkType: hard + +"micromark@npm:^4.0.0": + version: 4.0.2 + resolution: "micromark@npm:4.0.2" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/07462287254219d6eda6eac8a3cebaff2994e0575499e7088027b825105e096e4f51e466b14b2a81b71933a3b6c48ee069049d87bc2c2127eee50d9cc69e8af6 + languageName: node + linkType: hard + "micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" @@ -8181,6 +10002,13 @@ __metadata: languageName: node linkType: hard +"nprogress@npm:^0.2.0": + version: 0.2.0 + resolution: "nprogress@npm:0.2.0" + checksum: 10c0/eab9a923a1ad1eed71a455ecfbc358442dd9bcd71b9fa3fa1c67eddf5159360b182c218f76fca320c97541a1b45e19ced04e6dcb044a662244c5419f8ae9e821 + languageName: node + linkType: hard + "nullthrows@npm:^1.1.1": version: 1.1.1 resolution: "nullthrows@npm:1.1.1" @@ -8258,6 +10086,24 @@ __metadata: languageName: node linkType: hard +"oniguruma-parser@npm:^0.12.2": + version: 0.12.2 + resolution: "oniguruma-parser@npm:0.12.2" + checksum: 10c0/fe5255d2cd5a6b845d5a0abe1725898ef40cea5522290dba6ccc08cc388891e9e8007af4baa5059942b786ad44b2b677ef25948039c9ba3f057bd35d2c52076a + languageName: node + linkType: hard + +"oniguruma-to-es@npm:^4.3.6": + version: 4.3.6 + resolution: "oniguruma-to-es@npm:4.3.6" + dependencies: + oniguruma-parser: "npm:^0.12.2" + regex: "npm:^6.1.0" + regex-recursion: "npm:^6.0.2" + checksum: 10c0/044e08b98e706987c2882ccf228de2a671de4aff33e3bd370da137ba599c0d520549625ad26ecc8898502445c991e19f106f584e94382262ba2af02c908ca3cf + languageName: node + linkType: hard + "open@npm:^7.0.3": version: 7.4.2 resolution: "open@npm:7.4.2" @@ -8528,6 +10374,21 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + character-reference-invalid: "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + is-hexadecimal: "npm:^2.0.0" + checksum: 10c0/a13906b1151750b78ed83d386294066daf5fb559e08c5af9591b2d98cc209123103016a01df776f65f8219ad26652d6d6b210d0974d452049cddfc53a8916c34 + languageName: node + linkType: hard + "parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -8549,6 +10410,15 @@ __metadata: languageName: node linkType: hard +"parse5@npm:^7.0.0": + version: 7.3.0 + resolution: "parse5@npm:7.3.0" + dependencies: + entities: "npm:^6.0.0" + checksum: 10c0/7fd2e4e247e85241d6f2a464d0085eed599a26d7b0a5233790c49f53473232eb85350e8133344d9b3fd58b89339e7ad7270fe1f89d28abe50674ec97b87f80b5 + languageName: node + linkType: hard + "parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -8760,6 +10630,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^7.0.0": + version: 7.2.0 + resolution: "property-information@npm:7.2.0" + checksum: 10c0/03662c8f9e1544510914c5e594ae72963f67d261027a5fdc06c4134742584fe40dd49b959470d30e757e9fb70b297d8546ab1362a040364144029926ad4e07c4 + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.4 resolution: "pump@npm:3.0.4" @@ -8817,6 +10694,28 @@ __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-dom@npm:^19.2.6": + version: 19.2.7 + resolution: "react-dom@npm:19.2.7" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.7 + checksum: 10c0/970ff600f6e80d47d39e2f226f12f226173b3cba3382efc97c5f0cd663de9af38c7a4c11c213fb936094faeac83060d660247accaa96b752180d5b951b9cfecb + languageName: node + linkType: hard + "react-is-18@npm:react-is@^18.3.1, react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -8838,6 +10737,13 @@ __metadata: languageName: node linkType: hard +"react-lazy-with-preload@npm:^2.2.1": + version: 2.2.1 + resolution: "react-lazy-with-preload@npm:2.2.1" + checksum: 10c0/57c36baedc24a2c8fa148681dc7c5123ac183542964063b4a0433856a06d4edead27deb893012a43f75905af51a479b4409e688b97be3ef70cffff926954f6d2 + languageName: node + linkType: hard + "react-native-builder-bob@npm:^0.41.0": version: 0.41.0 resolution: "react-native-builder-bob@npm:0.41.0" @@ -9045,7 +10951,7 @@ __metadata: languageName: node linkType: hard -"react-reconciler@npm:~0.33.0": +"react-reconciler@npm:0.33.0, react-reconciler@npm:~0.33.0": version: 0.33.0 resolution: "react-reconciler@npm:0.33.0" dependencies: @@ -9063,6 +10969,52 @@ __metadata: languageName: node linkType: hard +"react-refresh@npm:^0.18.0": + version: 0.18.0 + resolution: "react-refresh@npm:0.18.0" + checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2 + languageName: node + linkType: hard + +"react-render-to-markdown@npm:19.1.0": + version: 19.1.0 + resolution: "react-render-to-markdown@npm:19.1.0" + dependencies: + react-reconciler: "npm:0.33.0" + peerDependencies: + react: ">=19" + checksum: 10c0/f24f733f20ddeaa43c3564b5e8fd60ffa851c026d5b5469569503b5fbef43da44d959d2ffdf64d0c11b2b26db673df0d7484d701775946c10d8996787d722d22 + languageName: node + linkType: hard + +"react-router-dom@npm:^7.15.1": + version: 7.18.0 + resolution: "react-router-dom@npm:7.18.0" + dependencies: + react-router: "npm:7.18.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10c0/035303f4d14680bf5356ab20e40889fc7bac281f59399c34b8fd0ed884e2f8e9e1b17ce7e668cad5e8bc1b428fe32a33d97509947568a23f4271e0392501304c + languageName: node + linkType: hard + +"react-router@npm:7.18.0": + version: 7.18.0 + resolution: "react-router@npm:7.18.0" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/7ab6c6dc7efdc14092486d63f55b04f9e3ef269fdec8c695a513f69e55d02a9beab8f637a13396952815d85accf16e7db2d65d9cbb68def13c349be82ceb5aee + languageName: node + linkType: hard + "react@npm:19.2.3": version: 19.2.3 resolution: "react@npm:19.2.3" @@ -9070,6 +11022,13 @@ __metadata: languageName: node linkType: hard +"react@npm:^19.2.6": + version: 19.2.7 + resolution: "react@npm:19.2.7" + checksum: 10c0/0bd0e2f1bbd4ba97561c6597bf8a5fec05e6476fe61e165c1065598d16668efc6715205599c94d3ddd49d36cb0f21cbf1b9bcc18ee840b805ce222c3e8d558ac + languageName: node + linkType: hard + "read-yaml-file@npm:^1.1.0": version: 1.1.0 resolution: "read-yaml-file@npm:1.1.0" @@ -9082,6 +11041,56 @@ __metadata: languageName: node linkType: hard +"recma-build-jsx@npm:^1.0.0": + version: 1.0.0 + resolution: "recma-build-jsx@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-util-build-jsx: "npm:^3.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/ca30f5163887b44c74682355da2625f7b49f33267699d22247913e513e043650cbdd6a7497cf13c60f09ad9e7bc2bd35bd20853672773c19188569814b56bb04 + languageName: node + linkType: hard + +"recma-jsx@npm:^1.0.0": + version: 1.0.1 + resolution: "recma-jsx@npm:1.0.1" + dependencies: + acorn-jsx: "npm:^5.0.0" + estree-util-to-js: "npm:^2.0.0" + recma-parse: "npm:^1.0.0" + recma-stringify: "npm:^1.0.0" + unified: "npm:^11.0.0" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/9921b1270581ff133b94678868e665ba0fb6285ee60a6936106bac4899196c2ffb02dde894d9bc088fbf3deacb3e2426a3452e72066bf1203cbefebd7809d93f + languageName: node + linkType: hard + +"recma-parse@npm:^1.0.0": + version: 1.0.0 + resolution: "recma-parse@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + esast-util-from-js: "npm:^2.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/37c0990859a562d082e02d475ca5f4c8ef0840d285270f6699fe888cbb06260f97eb098585eda4aae416182c207fd19cf05e4f0b2dcf55cbf81dde4406d95545 + languageName: node + linkType: hard + +"recma-stringify@npm:^1.0.0": + version: 1.0.0 + resolution: "recma-stringify@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-util-to-js: "npm:^2.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/c2ed4c0e8cf8a09aedcd47c5d016d47f6e1ff6c2d4b220e2abaf1b77713bf404756af2ea3ea7999aec5862e8825aff035edceb370c7fd8603a7e9da03bd6987e + languageName: node + linkType: hard + "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -9115,6 +11124,31 @@ __metadata: languageName: node linkType: hard +"regex-recursion@npm:^6.0.2": + version: 6.0.2 + resolution: "regex-recursion@npm:6.0.2" + dependencies: + regex-utilities: "npm:^2.3.0" + checksum: 10c0/68e8b6889680e904b75d7f26edaf70a1a4dc1087406bff53face4c2929d918fd77c72223843fe816ac8ed9964f96b4160650e8d5909e26a998c6e9de324dadb1 + languageName: node + linkType: hard + +"regex-utilities@npm:^2.3.0": + version: 2.3.0 + resolution: "regex-utilities@npm:2.3.0" + checksum: 10c0/78c550a80a0af75223244fff006743922591bd8f61d91fef7c86b9b56cf9bbf8ee5d7adb6d8991b5e304c57c90103fc4818cf1e357b11c6c669b782839bd7893 + languageName: node + linkType: hard + +"regex@npm:^6.1.0": + version: 6.1.0 + resolution: "regex@npm:6.1.0" + dependencies: + regex-utilities: "npm:^2.3.0" + checksum: 10c0/6e0ee2a1c17d5a66dc1120dfc51899dedf6677857e83a0df4d5a822ebb8645a54a079772efc1ade382b67aad35e4e22b5bd2d33c05ed28b0e000f8f57eb0aec1 + languageName: node + linkType: hard + "regexpu-core@npm:^6.3.1": version: 6.4.0 resolution: "regexpu-core@npm:6.4.0" @@ -9147,6 +11181,134 @@ __metadata: languageName: node linkType: hard +"rehype-external-links@npm:^3.0.0": + version: 3.0.0 + resolution: "rehype-external-links@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + hast-util-is-element: "npm:^3.0.0" + is-absolute-url: "npm:^4.0.0" + space-separated-tokens: "npm:^2.0.0" + unist-util-visit: "npm:^5.0.0" + checksum: 10c0/486b5db73d8fe72611d62b4eb0b56ec71025ea32bba764ad54473f714ca627be75e057ac29243763f85a77c3810f31727ce3e03c975b3803c1c98643d038e9ae + languageName: node + linkType: hard + +"rehype-raw@npm:^7.0.0": + version: 7.0.0 + resolution: "rehype-raw@npm:7.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + hast-util-raw: "npm:^9.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/1435b4b6640a5bc3abe3b2133885c4dbff5ef2190ef9cfe09d6a63f74dd7d7ffd0cede70603278560ccf1acbfb9da9faae4b68065a28bc5aa88ad18e40f32d52 + languageName: node + linkType: hard + +"rehype-recma@npm:^1.0.0": + version: 1.0.0 + resolution: "rehype-recma@npm:1.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + hast-util-to-estree: "npm:^3.0.0" + checksum: 10c0/be60d7433a7f788a14f41da3e93ba9d9272c908ddef47757026cc4bbcc912f6301d56810349adf876d294a8d048626a0dbf6988aaa574afbfc29eac1ddc1eb74 + languageName: node + linkType: hard + +"remark-cjk-friendly-gfm-strikethrough@npm:^2.0.1": + version: 2.3.1 + resolution: "remark-cjk-friendly-gfm-strikethrough@npm:2.3.1" + dependencies: + mdast-util-to-markdown-cjk-friendly-gfm-strikethrough: "npm:1.0.0" + micromark-extension-cjk-friendly-gfm-strikethrough: "npm:2.0.1" + peerDependencies: + "@types/mdast": ^4.0.0 + unified: ^11.0.0 + peerDependenciesMeta: + "@types/mdast": + optional: true + checksum: 10c0/465fbe9716f6b589ae1e8cf0127f6d02b9ca07a09631a15b30c53b6ffbddbcb152154d844b2ed0931cbd609e27dfed227c614ac4f143dbb303f23d47c55714ce + languageName: node + linkType: hard + +"remark-cjk-friendly@npm:^2.0.1": + version: 2.3.1 + resolution: "remark-cjk-friendly@npm:2.3.1" + dependencies: + mdast-util-to-markdown-cjk-friendly: "npm:1.0.0" + micromark-extension-cjk-friendly: "npm:2.0.1" + peerDependencies: + "@types/mdast": ^4.0.0 + unified: ^11.0.0 + peerDependenciesMeta: + "@types/mdast": + optional: true + checksum: 10c0/323347462b83f4a0b6e842d45d933b8b5aefc99c41a4724075fc33383c8163115c86654781bdfc664f8350ffcdcd4d99dab4a98504aa890d5bbde2084fc52d2b + languageName: node + linkType: hard + +"remark-gfm@npm:^4.0.1": + version: 4.0.1 + resolution: "remark-gfm@npm:4.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-gfm: "npm:^3.0.0" + micromark-extension-gfm: "npm:^3.0.0" + remark-parse: "npm:^11.0.0" + remark-stringify: "npm:^11.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/427ecc6af3e76222662061a5f670a3e4e33ec5fffe2cabf04034da6a3f9a1bda1fc023e838a636385ba314e66e2bebbf017ca61ebea357eb0f5200fe0625a4b7 + languageName: node + linkType: hard + +"remark-mdx@npm:^3.0.0, remark-mdx@npm:^3.1.1": + version: 3.1.1 + resolution: "remark-mdx@npm:3.1.1" + dependencies: + mdast-util-mdx: "npm:^3.0.0" + micromark-extension-mdxjs: "npm:^3.0.0" + checksum: 10c0/3e5585d4c2448d8ac7548b1d148f04b89251ff47fbfc80be1428cecec2fc2530abe30a5da53bb031283f8a78933259df6120c1cd4cc7cc1d43978d508798ba88 + languageName: node + linkType: hard + +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/6eed15ddb8680eca93e04fcb2d1b8db65a743dcc0023f5007265dda558b09db595a087f622062ccad2630953cd5cddc1055ce491d25a81f3317c858348a8dd38 + languageName: node + linkType: hard + +"remark-rehype@npm:^11.0.0": + version: 11.1.2 + resolution: "remark-rehype@npm:11.1.2" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + mdast-util-to-hast: "npm:^13.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/f9eccacfb596d9605581dc05bfad28635d6ded5dd0a18e88af5fd4df0d3fcf9612e1501d4513bc2164d833cfe9636dab20400080b09e53f155c6e1442a1231fb + languageName: node + linkType: hard + +"remark-stringify@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-stringify@npm:11.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/0cdb37ce1217578f6f847c7ec9f50cbab35df5b9e3903d543e74b405404e67c07defcb23cd260a567b41b769400f6de03c2c3d9cd6ae7a6707d5c8d89ead489f + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -9291,6 +11453,15 @@ __metadata: languageName: node linkType: hard +"scroll-into-view-if-needed@npm:^3.1.0": + version: 3.1.0 + resolution: "scroll-into-view-if-needed@npm:3.1.0" + dependencies: + compute-scroll-into-view: "npm:^3.0.2" + checksum: 10c0/1f46b090e1e04fcfdef1e384f6d7e615f9f84d4176faf4dbba7347cc0a6e491e5d578eaf4dbe9618dd3d8d38efafde58535b3e00f2a21ce4178c14be364850ff + languageName: node + linkType: hard + "semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -9349,6 +11520,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 + languageName: node + linkType: hard + "setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -9379,6 +11557,22 @@ __metadata: languageName: node linkType: hard +"shiki@npm:4.3.0, shiki@npm:^4.0.2": + version: 4.3.0 + resolution: "shiki@npm:4.3.0" + dependencies: + "@shikijs/core": "npm:4.3.0" + "@shikijs/engine-javascript": "npm:4.3.0" + "@shikijs/engine-oniguruma": "npm:4.3.0" + "@shikijs/langs": "npm:4.3.0" + "@shikijs/themes": "npm:4.3.0" + "@shikijs/types": "npm:4.3.0" + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.4" + checksum: 10c0/17d0719bd1afa857a9513322ddbebc57d3292ed2ae536cb16cd32d1fa52811e7892a1f774824a4fe3de0e4cc9e60d4f99421914d8246517ca0146dce7e85f03a + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -9473,6 +11667,20 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.7.0": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c + languageName: node + linkType: hard + +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 10c0/6173e1d903dca41dcab6a2deed8b4caf61bd13b6d7af8374713500570aa929ff9414ae09a0519f4f8772df993300305a395d4871f35bc4ca72b6db57e1f30af8 + languageName: node + linkType: hard + "spawndamnit@npm:^3.0.1": version: 3.0.1 resolution: "spawndamnit@npm:3.0.1" @@ -9568,6 +11776,16 @@ __metadata: languageName: node linkType: hard +"stringify-entities@npm:^4.0.0": + version: 4.0.4 + resolution: "stringify-entities@npm:4.0.4" + dependencies: + character-entities-html4: "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + checksum: 10c0/537c7e656354192406bdd08157d759cd615724e9d0873602d2c9b2f6a5c0a8d0b1d73a0a08677848105c5eebac6db037b57c0b3a4ec86331117fa7319ed50448 + languageName: node + linkType: hard + "strip-ansi@npm:^5.2.0": version: 5.2.0 resolution: "strip-ansi@npm:5.2.0" @@ -9639,6 +11857,24 @@ __metadata: languageName: node linkType: hard +"style-to-js@npm:^1.0.0": + version: 1.1.21 + resolution: "style-to-js@npm:1.1.21" + dependencies: + style-to-object: "npm:1.0.14" + checksum: 10c0/94231aa80f58f442c3a5ae01a21d10701e5d62f96b4b3e52eab3499077ee52df203cc0df4a1a870707f5e99470859136ea8657b782a5f4ca7934e0ffe662a588 + languageName: node + linkType: hard + +"style-to-object@npm:1.0.14": + version: 1.0.14 + resolution: "style-to-object@npm:1.0.14" + dependencies: + inline-style-parser: "npm:0.2.7" + checksum: 10c0/854d9e9b77afc336e6d7b09348e7939f2617b34eb0895824b066d8cd1790284cb6d8b2ba36be88025b2595d715dba14b299ae76e4628a366541106f639e13679 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -9797,6 +12033,13 @@ __metadata: languageName: node linkType: hard +"toggle-selection@npm:^1.0.6": + version: 1.0.6 + resolution: "toggle-selection@npm:1.0.6" + checksum: 10c0/f2cf1f2c70f374fd87b0cdc8007453ba9e981c4305a8bf4eac10a30e62ecdfd28bca7d18f8f15b15a506bf8a7bfb20dbe3539f0fcf2a2c8396c1a78d53e1f179 + languageName: node + linkType: hard + "toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" @@ -9818,6 +12061,27 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: 10c0/3a1611fa9e52aa56a94c69951a9ea15b8aaad760eaa26c56a65330dc8adf99cb282fc07cc9d94968b7d4d88003beba220a7278bbe2063328eb23fb56f9509e94 + languageName: node + linkType: hard + +"trough@npm:^2.0.0": + version: 2.2.0 + resolution: "trough@npm:2.2.0" + checksum: 10c0/58b671fc970e7867a48514168894396dd94e6d9d6456aca427cc299c004fe67f35ed7172a36449086b2edde10e78a71a284ec0076809add6834fb8f857ccb9b0 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0, tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + "turbo@npm:^2.8.21": version: 2.9.16 resolution: "turbo@npm:2.9.16" @@ -9902,6 +12166,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d + languageName: node + linkType: hard + "undici@npm:^6.25.0": version: 6.26.0 resolution: "undici@npm:6.26.0" @@ -9909,6 +12180,15 @@ __metadata: languageName: node linkType: hard +"unhead@npm:2.1.15": + version: 2.1.15 + resolution: "unhead@npm:2.1.15" + dependencies: + hookable: "npm:^6.0.1" + checksum: 10c0/ab266e9cda44d0b528ae3b62ce9136cb4f81bd2aa1e2d66431cefe6b41151a811971951e05b4d81e6adea105b65901d9091dffa29d646f1be6246691606b2407 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -9947,6 +12227,98 @@ __metadata: languageName: node linkType: hard +"unified@npm:^11.0.0, unified@npm:^11.0.5": + version: 11.0.5 + resolution: "unified@npm:11.0.5" + dependencies: + "@types/unist": "npm:^3.0.0" + bail: "npm:^2.0.0" + devlop: "npm:^1.0.0" + extend: "npm:^3.0.0" + is-plain-obj: "npm:^4.0.0" + trough: "npm:^2.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/53c8e685f56d11d9d458a43e0e74328a4d6386af51c8ac37a3dcabec74ce5026da21250590d4aff6733ccd7dc203116aae2b0769abc18cdf9639a54ae528dfc9 + languageName: node + linkType: hard + +"unist-util-is@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-is@npm:6.0.1" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/5a487d390193811d37a68264e204dbc7c15c40b8fc29b5515a535d921d071134f571d7b5cbd59bcd58d5ce1c0ab08f20fc4a1f0df2287a249c979267fc32ce06 + languageName: node + linkType: hard + +"unist-util-position-from-estree@npm:^2.0.0": + version: 2.0.0 + resolution: "unist-util-position-from-estree@npm:2.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/39127bf5f0594e0a76d9241dec4f7aa26323517120ce1edd5ed91c8c1b9df7d6fb18af556e4b6250f1c7368825720ed892e2b6923be5cdc08a9bb16536dc37b3 + languageName: node + linkType: hard + +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/dde3b31e314c98f12b4dc6402f9722b2bf35e96a4f2d463233dd90d7cde2d4928074a7a11eff0a5eb1f4e200f27fc1557e0a64a7e8e4da6558542f251b1b7400 + languageName: node + linkType: hard + +"unist-util-remove@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-remove@npm:4.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/30f3ed31095dd7f3109266d39c514fab5f2da3fb656d5f78a0e3e7700f219760f2f4d8286c810ae43c241fee3f0a8dd40f8d1e5ebeee3cb810581d5e7e8d4f7d + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/dfe1dbe79ba31f589108cb35e523f14029b6675d741a79dea7e5f3d098785045d556d5650ec6a8338af11e9e78d2a30df12b1ee86529cded1098da3f17ee999e + languageName: node + linkType: hard + +"unist-util-visit-children@npm:^3.0.0": + version: 3.0.0 + resolution: "unist-util-visit-children@npm:3.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/51e95f54fbf11d414952c011c761c3960864948ad3fd2abe3989eb18b18d96b8f48e7ea5ab6f23264d1a3f4f5a1ff76312dd8f2196c78b762098403505c3abb9 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.2 + resolution: "unist-util-visit-parents@npm:6.0.2" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10c0/f1e4019dbd930301825895e3737b1ee0cd682f7622ddd915062135cbb39f8c090aaece3a3b5eae1f2ea52ec33f0931abb8f8a8b5c48a511a4203e3d360a8cd49 + languageName: node + linkType: hard + +"unist-util-visit@npm:^5.0.0, unist-util-visit@npm:^5.1.0": + version: 5.1.0 + resolution: "unist-util-visit@npm:5.1.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/a56e1bbbf63fcb55abe379e660b9a3367787e8be1e2473bdb7e86cfa6f32b6c1fa0092432d7040b8a30b2fc674bbbe024ffe6d03c3d6bf4839b064f584463a4e + languageName: node + linkType: hard + "universalify@npm:^0.1.0": version: 0.1.2 resolution: "universalify@npm:0.1.2" @@ -10023,6 +12395,36 @@ __metadata: languageName: node linkType: hard +"vfile-location@npm:^5.0.0": + version: 5.0.3 + resolution: "vfile-location@npm:5.0.3" + dependencies: + "@types/unist": "npm:^3.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/1711f67802a5bc175ea69750d59863343ed43d1b1bb25c0a9063e4c70595e673e53e2ed5cdbb6dcdc370059b31605144d95e8c061b9361bcc2b036b8f63a4966 + languageName: node + linkType: hard + +"vfile-message@npm:^4.0.0": + version: 4.0.3 + resolution: "vfile-message@npm:4.0.3" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-stringify-position: "npm:^4.0.0" + checksum: 10c0/33d9f219610d27987689bb14fa5573d2daa146941d1a05416dd7702c4215b23f44ed81d059e70d0e4e24f9a57d5f4dc9f18d35a993f04cf9446a7abe6d72d0c0 + languageName: node + linkType: hard + +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": "npm:^3.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/e5d9eb4810623f23758cfc2205323e33552fb5972e5c2e6587babe08fe4d24859866277404fb9e2a20afb71013860d96ec806cb257536ae463c87d70022ab9ef + languageName: node + linkType: hard + "vlq@npm:^1.0.0": version: 1.0.1 resolution: "vlq@npm:1.0.1" @@ -10048,6 +12450,13 @@ __metadata: languageName: node linkType: hard +"web-namespaces@npm:^2.0.0": + version: 2.0.1 + resolution: "web-namespaces@npm:2.0.1" + checksum: 10c0/df245f466ad83bd5cd80bfffc1674c7f64b7b84d1de0e4d2c0934fb0782e0a599164e7197a4bce310ee3342fd61817b8047ff04f076a1ce12dd470584142a4bd + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -10290,3 +12699,10 @@ __metadata: checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c languageName: node linkType: hard + +"zwitch@npm:^2.0.0, zwitch@npm:^2.0.4": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: 10c0/3c7830cdd3378667e058ffdb4cf2bb78ac5711214e2725900873accb23f3dfe5f9e7e5a06dcdc5f29605da976fc45c26d9a13ca334d6eea2245a15e77b8fc06e + languageName: node + linkType: hard