diff --git a/packages/common/core/index.ts b/packages/common/core/index.ts index c96f32b4..0718f56e 100644 --- a/packages/common/core/index.ts +++ b/packages/common/core/index.ts @@ -1,7 +1,22 @@ // Shared exports export {assertNonNullable} from './src/shared/checkers/assertNonNullable' -export {convertDataAttrs, cx, merge} from './src/shared/utils' -export {KEYBOARD, DEFAULT_MARKUP, DEFAULT_OVERLAY_TRIGGER} from './src/shared/constants' +export { + convertDataAttrs, + cx, + merge, + resolveOptionSlot, + resolveSlot, + resolveSlotProps, + type SlotName, +} from './src/shared/utils' +export { + KEYBOARD, + DEFAULT_MARKUP, + DEFAULT_OVERLAY_TRIGGER, + DEFAULT_OPTIONS, + type DefaultOption, + type DefaultOverlayConfig, +} from './src/shared/constants' export type { OverlayMatch, EventKey, @@ -16,6 +31,7 @@ export type { GenericAttributes, CoreSlots, CoreSlotProps, + DataAttributes, } from './src/shared/types' // Parsing exports (modern API) @@ -43,7 +59,14 @@ export {findGap, getClosestIndexes} from './src/features/preparsing' export {toString} from './src/features/parsing' export {shallow, createNewSpan, deleteMark} from './src/features/text-manipulation' export {Store, type StoreOptions} from './src/features/store' -export {OverlayController} from './src/features/overlay' +export { + OverlayController, + createMarkFromOverlay, + filterSuggestions, + navigateSuggestions, + type NavigationAction, + type NavigationResult, +} from './src/features/overlay' export {FocusController} from './src/features/focus' export {KeyDownController} from './src/features/input' export {SystemListenerController} from './src/features/events' @@ -59,7 +82,7 @@ export {createCoreFeatures} from './src/features/coreFeatures' export {Lifecycle, type LifecycleOptions} from './src/features/lifecycle' // Mark Handler -export {MarkHandler, type RefAccessor} from './src/features/mark' +export {MarkHandler, type MarkOptions, type RefAccessor} from './src/features/mark' // Blocks export {splitTokensIntoBlocks, reorderBlocks, type Block} from './src/features/blocks' diff --git a/packages/common/core/src/features/mark/index.ts b/packages/common/core/src/features/mark/index.ts index 853c291c..2894b39a 100644 --- a/packages/common/core/src/features/mark/index.ts +++ b/packages/common/core/src/features/mark/index.ts @@ -1 +1,2 @@ -export {MarkHandler, type RefAccessor} from './MarkHandler' \ No newline at end of file +export {MarkHandler, type RefAccessor} from './MarkHandler' +export type {MarkOptions} from './types' \ No newline at end of file diff --git a/packages/common/core/src/features/mark/types.ts b/packages/common/core/src/features/mark/types.ts new file mode 100644 index 00000000..5e8b7d0e --- /dev/null +++ b/packages/common/core/src/features/mark/types.ts @@ -0,0 +1,3 @@ +export interface MarkOptions { + controlled?: boolean +} \ No newline at end of file diff --git a/packages/common/core/src/features/overlay/createMarkFromOverlay.ts b/packages/common/core/src/features/overlay/createMarkFromOverlay.ts new file mode 100644 index 00000000..eaebc043 --- /dev/null +++ b/packages/common/core/src/features/overlay/createMarkFromOverlay.ts @@ -0,0 +1,26 @@ +import type {OverlayMatch} from '../../shared/types' +import type {MarkToken} from '../parsing' + +export function createMarkFromOverlay(match: OverlayMatch, value: string, meta?: string): MarkToken { + return { + type: 'mark', + value, + meta, + content: '', + position: { + start: match.index, + end: match.index + match.span.length, + }, + descriptor: { + markup: match.option.markup!, + index: 0, + segments: [], + gapTypes: [], + hasNested: false, + hasTwoValues: false, + segmentGlobalIndices: [], + }, + children: [], + nested: undefined, + } +} \ No newline at end of file diff --git a/packages/common/core/src/features/overlay/filterSuggestions.ts b/packages/common/core/src/features/overlay/filterSuggestions.ts new file mode 100644 index 00000000..08d13974 --- /dev/null +++ b/packages/common/core/src/features/overlay/filterSuggestions.ts @@ -0,0 +1,4 @@ +export function filterSuggestions(data: string[], search: string): string[] { + const query = search.toLowerCase() + return data.filter(s => s.toLowerCase().indexOf(query) > -1) +} \ No newline at end of file diff --git a/packages/common/core/src/features/overlay/index.ts b/packages/common/core/src/features/overlay/index.ts index 61edbdf6..5dacd73b 100644 --- a/packages/common/core/src/features/overlay/index.ts +++ b/packages/common/core/src/features/overlay/index.ts @@ -1 +1,4 @@ -export {OverlayController} from './OverlayController' \ No newline at end of file +export {filterSuggestions} from './filterSuggestions' +export {createMarkFromOverlay} from './createMarkFromOverlay' +export {OverlayController} from './OverlayController' +export {navigateSuggestions, type NavigationAction, type NavigationResult} from './suggestionNavigation' \ No newline at end of file diff --git a/packages/common/core/src/features/overlay/suggestionNavigation.ts b/packages/common/core/src/features/overlay/suggestionNavigation.ts new file mode 100644 index 00000000..f32f01fb --- /dev/null +++ b/packages/common/core/src/features/overlay/suggestionNavigation.ts @@ -0,0 +1,25 @@ +import {KEYBOARD} from '../../shared/constants' + +export type NavigationAction = 'up' | 'down' | 'select' | 'none' + +export interface NavigationResult { + action: NavigationAction + index: number +} + +export function navigateSuggestions(key: string, activeIndex: number, length: number): NavigationResult { + if (length === 0) return {action: 'none', index: activeIndex} + + const hasActive = !isNaN(activeIndex) + + switch (key) { + case KEYBOARD.UP: + return {action: 'up', index: hasActive ? (length + ((activeIndex - 1) % length)) % length : 0} + case KEYBOARD.DOWN: + return {action: 'down', index: hasActive ? (activeIndex + 1) % length : 0} + case KEYBOARD.ENTER: + return hasActive ? {action: 'select', index: activeIndex} : {action: 'none', index: activeIndex} + default: + return {action: 'none', index: activeIndex} + } +} \ No newline at end of file diff --git a/packages/common/core/src/shared/constants.ts b/packages/common/core/src/shared/constants.ts index 780f4ab5..0a36d9a2 100644 --- a/packages/common/core/src/shared/constants.ts +++ b/packages/common/core/src/shared/constants.ts @@ -1,4 +1,14 @@ import type {Markup} from '../features/parsing/ParserV2/types' +import type {CoreOption} from './types' + +export interface DefaultOverlayConfig { + trigger?: string + data?: string[] +} + +export interface DefaultOption extends CoreOption { + overlay?: DefaultOverlayConfig +} export enum KEYBOARD { // Navigation Keys @@ -27,4 +37,14 @@ export enum KEYBOARD { export const DEFAULT_OVERLAY_TRIGGER = '@' -export const DEFAULT_MARKUP: Markup = '@[__value__](__meta__)' \ No newline at end of file +export const DEFAULT_MARKUP: Markup = '@[__value__](__meta__)' + +export const DEFAULT_OPTIONS: DefaultOption[] = [ + { + markup: DEFAULT_MARKUP, + overlay: { + trigger: DEFAULT_OVERLAY_TRIGGER, + data: [], + }, + }, +] \ No newline at end of file diff --git a/packages/common/core/src/shared/types.ts b/packages/common/core/src/shared/types.ts index fdcc21dc..60d8ebd7 100644 --- a/packages/common/core/src/shared/types.ts +++ b/packages/common/core/src/shared/types.ts @@ -110,6 +110,8 @@ export type OverlayTrigger = Array<'change' | 'selectionChange'> | 'change' | 's export type StyleProperties = Record +export type DataAttributes = Record<`data${Capitalize}`, string | number | boolean | undefined> + export type GenericComponent = unknown export type GenericElement = unknown export type GenericAttributes = Record diff --git a/packages/common/core/src/shared/utils/index.ts b/packages/common/core/src/shared/utils/index.ts index 82c0c37b..938097d2 100644 --- a/packages/common/core/src/shared/utils/index.ts +++ b/packages/common/core/src/shared/utils/index.ts @@ -1,3 +1,5 @@ export {convertDataAttrs} from './dataAttributes' export {cx} from './cx' -export {merge} from './merge' \ No newline at end of file +export {merge} from './merge' +export {resolveOptionSlot} from './resolveOptionSlot' +export {resolveSlot, resolveSlotProps, type SlotName} from './resolveSlot' \ No newline at end of file diff --git a/packages/common/core/src/shared/utils/resolveOptionSlot.ts b/packages/common/core/src/shared/utils/resolveOptionSlot.ts new file mode 100644 index 00000000..cec69d86 --- /dev/null +++ b/packages/common/core/src/shared/utils/resolveOptionSlot.ts @@ -0,0 +1,6 @@ +export function resolveOptionSlot(optionConfig: T | ((base: T) => T) | undefined, baseProps: T): T { + if (optionConfig !== undefined) { + return typeof optionConfig === 'function' ? optionConfig(baseProps) : optionConfig + } + return baseProps ?? {} +} \ No newline at end of file diff --git a/packages/common/core/src/shared/utils/resolveSlot.ts b/packages/common/core/src/shared/utils/resolveSlot.ts new file mode 100644 index 00000000..7accd878 --- /dev/null +++ b/packages/common/core/src/shared/utils/resolveSlot.ts @@ -0,0 +1,21 @@ +import type {CoreSlotProps, CoreSlots} from '../types' +import {convertDataAttrs} from './dataAttributes' + +export type SlotName = 'container' | 'span' + +const defaultSlots: Record = { + container: 'div', + span: 'span', +} + +export function resolveSlot(slotName: SlotName, slots: CoreSlots | undefined): T { + return (slots?.[slotName] ?? defaultSlots[slotName]) as T +} + +export function resolveSlotProps>( + slotName: SlotName, + slotProps: CoreSlotProps | undefined +): T | undefined { + const props = slotProps?.[slotName] + return props ? (convertDataAttrs(props as Record) as T) : undefined +} \ No newline at end of file diff --git a/packages/react/markput/src/components/BlockContainer.tsx b/packages/react/markput/src/components/BlockContainer.tsx index 195995c3..18b4486d 100644 --- a/packages/react/markput/src/components/BlockContainer.tsx +++ b/packages/react/markput/src/components/BlockContainer.tsx @@ -1,8 +1,15 @@ -import {splitTokensIntoBlocks, reorderBlocks, parseWithParser, type Block} from '@markput/core' +import { + resolveSlot, + resolveSlotProps, + splitTokensIntoBlocks, + reorderBlocks, + parseWithParser, + type Block, +} from '@markput/core' +import type {ElementType} from 'react' import {memo, useCallback, useMemo, useRef} from 'react' -import {useStore} from '../lib/hooks/useStore' -import {resolveSlot, resolveSlotProps} from '../lib/utils/resolveSlot' +import {useStore} from '../lib/providers/StoreContext' import {DraggableBlock} from './DraggableBlock' import {Token} from './Token' @@ -19,7 +26,7 @@ export const BlockContainer = memo(() => { const key = store.key const refs = store.refs - const ContainerComponent = useMemo(() => resolveSlot('container', slots), [slots]) + const ContainerComponent = useMemo(() => resolveSlot('container', slots), [slots]) const containerProps = useMemo(() => resolveSlotProps('container', slotProps), [slotProps]) const blocks = useMemo(() => splitTokensIntoBlocks(tokens), [tokens]) diff --git a/packages/react/markput/src/components/Container.tsx b/packages/react/markput/src/components/Container.tsx index f73c38a7..8373ad25 100644 --- a/packages/react/markput/src/components/Container.tsx +++ b/packages/react/markput/src/components/Container.tsx @@ -1,7 +1,8 @@ +import {resolveSlot, resolveSlotProps} from '@markput/core' +import type {ElementType} from 'react' import {memo, useMemo} from 'react' -import {useStore} from '../lib/hooks/useStore' -import {resolveSlot, resolveSlotProps} from '../lib/utils/resolveSlot' +import {useStore} from '../lib/providers/StoreContext' import {Token} from './Token' export const Container = memo(() => { @@ -13,7 +14,7 @@ export const Container = memo(() => { const style = store.state.style.use() const key = store.key const refs = store.refs - const ContainerComponent = useMemo(() => resolveSlot('container', slots), [slots]) + const ContainerComponent = useMemo(() => resolveSlot('container', slots), [slots]) const containerProps = useMemo(() => resolveSlotProps('container', slotProps), [slotProps]) return ( diff --git a/packages/react/markput/src/components/MarkRenderer.tsx b/packages/react/markput/src/components/MarkRenderer.tsx index eff995c5..ea2a9fa7 100644 --- a/packages/react/markput/src/components/MarkRenderer.tsx +++ b/packages/react/markput/src/components/MarkRenderer.tsx @@ -1,8 +1,8 @@ import type {MarkToken} from '@markput/core' import {useSlot} from '../lib/hooks/useSlot' -import {useStore} from '../lib/hooks/useStore' -import {useToken} from '../lib/providers/TokenProvider' +import {useStore} from '../lib/providers/StoreContext' +import {useToken} from '../lib/providers/TokenContext' import type {MarkProps} from '../types' // eslint-disable-next-line import/no-cycle import {Token} from './Token' diff --git a/packages/react/markput/src/components/MarkedInput.tsx b/packages/react/markput/src/components/MarkedInput.tsx index 60c526c7..56630581 100644 --- a/packages/react/markput/src/components/MarkedInput.tsx +++ b/packages/react/markput/src/components/MarkedInput.tsx @@ -1,9 +1,8 @@ import type {CoreSlotProps, CoreSlots, MarkputHandler, OverlayTrigger, StyleProperties} from '@markput/core' -import {cx, merge, Store} from '@markput/core' +import {cx, DEFAULT_OPTIONS, merge, Store} from '@markput/core' import type {ComponentType, CSSProperties, Ref} from 'react' import {useState} from 'react' -import {DEFAULT_OPTIONS} from '../constants' import {createUseHook} from '../lib/hooks/createUseHook' import {useCoreFeatures} from '../lib/hooks/useCoreFeatures' import {StoreContext} from '../lib/providers/StoreContext' @@ -118,9 +117,9 @@ export function MarkedInput + - + ) } \ No newline at end of file diff --git a/packages/react/markput/src/components/OverlayRenderer.tsx b/packages/react/markput/src/components/OverlayRenderer.tsx index 05fa4ed7..f17d5d98 100644 --- a/packages/react/markput/src/components/OverlayRenderer.tsx +++ b/packages/react/markput/src/components/OverlayRenderer.tsx @@ -1,7 +1,7 @@ import {memo, useMemo} from 'react' import {useSlot} from '../lib/hooks/useSlot' -import {useStore} from '../lib/hooks/useStore' +import {useStore} from '../lib/providers/StoreContext' import {Suggestions} from './Suggestions' export const OverlayRenderer = memo(() => { diff --git a/packages/react/markput/src/components/Suggestions/Suggestions.tsx b/packages/react/markput/src/components/Suggestions/Suggestions.tsx index 2e63ea53..857ce5ef 100644 --- a/packages/react/markput/src/components/Suggestions/Suggestions.tsx +++ b/packages/react/markput/src/components/Suggestions/Suggestions.tsx @@ -1,9 +1,9 @@ -import {KEYBOARD} from '@markput/core' +import {filterSuggestions, navigateSuggestions} from '@markput/core' import type {RefObject} from 'react' import {useEffect, useMemo, useState} from 'react' import {useOverlay} from '../../lib/hooks/useOverlay' -import {useStore} from '../../lib/hooks/useStore' +import {useStore} from '../../lib/providers/StoreContext' import styles from '@markput/core/styles.module.css' @@ -12,10 +12,7 @@ export const Suggestions = () => { const {match, select, style, ref} = useOverlay() const [active, setActive] = useState(NaN) const data = match.option.overlay?.data || [] - const filtered = useMemo( - () => data.filter(s => s.toLowerCase().indexOf(match.value.toLowerCase()) > -1), - [match.value, data] - ) + const filtered = useMemo(() => filterSuggestions(data, match.value), [match.value, data]) const length = filtered.length useEffect(() => { @@ -23,30 +20,24 @@ export const Suggestions = () => { if (!container) return const handler = (event: KeyboardEvent) => { - switch (event.key) { - case KEYBOARD.UP: - event.preventDefault() - setActive(prev => (isNaN(prev) ? 0 : (length + ((prev - 1) % length)) % length)) - break - case KEYBOARD.DOWN: + const result = navigateSuggestions(event.key, active, length) + switch (result.action) { + case 'up': + case 'down': event.preventDefault() - setActive(prev => (isNaN(prev) ? 0 : (prev + 1) % length)) + setActive(result.index) break - case KEYBOARD.ENTER: + case 'select': event.preventDefault() - setActive(current => { - if (isNaN(current)) return current - const suggestion = filtered[current] - select({value: suggestion, meta: current.toString()}) - return current - }) + const suggestion = filtered[result.index] + select({value: suggestion, meta: result.index.toString()}) break } } container.addEventListener('keydown', handler) return () => container.removeEventListener('keydown', handler) - }, [length, filtered]) + }, [length, filtered, active]) if (!filtered.length) return null diff --git a/packages/react/markput/src/components/TextSpan.tsx b/packages/react/markput/src/components/TextSpan.tsx index e6ca34c3..fda87619 100644 --- a/packages/react/markput/src/components/TextSpan.tsx +++ b/packages/react/markput/src/components/TextSpan.tsx @@ -1,8 +1,9 @@ +import {resolveSlot, resolveSlotProps} from '@markput/core' +import type {ElementType} from 'react' import {useLayoutEffect, useMemo, useRef} from 'react' -import {useStore} from '../lib/hooks/useStore' -import {useToken} from '../lib/providers/TokenProvider' -import {resolveSlot, resolveSlotProps} from '../lib/utils/resolveSlot' +import {useStore} from '../lib/providers/StoreContext' +import {useToken} from '../lib/providers/TokenContext' export const TextSpan = () => { const token = useToken() @@ -11,7 +12,7 @@ export const TextSpan = () => { const slots = store.state.slots.use() const slotProps = store.state.slotProps.use() - const SpanComponent = useMemo(() => resolveSlot('span', slots), [slots]) + const SpanComponent = useMemo(() => resolveSlot('span', slots), [slots]) const spanProps = useMemo(() => resolveSlotProps('span', slotProps), [slotProps]) if (token.type !== 'text') { diff --git a/packages/react/markput/src/components/Token.tsx b/packages/react/markput/src/components/Token.tsx index 19b8b364..8b376b62 100644 --- a/packages/react/markput/src/components/Token.tsx +++ b/packages/react/markput/src/components/Token.tsx @@ -1,7 +1,7 @@ import type {Token as TokenType} from '@markput/core' import {memo} from 'react' -import {TokenProvider} from '../lib/providers/TokenProvider' +import {TokenContext} from '../lib/providers/TokenContext' // eslint-disable-next-line import/no-cycle import {MarkRenderer} from './MarkRenderer' import {TextSpan} from './TextSpan' @@ -10,9 +10,9 @@ import {TextSpan} from './TextSpan' export const Token = memo(({mark, isNested = false}: {mark: TokenType; isNested?: boolean}) => { if (mark.type === 'mark') { return ( - + - + ) } @@ -21,9 +21,9 @@ export const Token = memo(({mark, isNested = false}: {mark: TokenType; isNested? } return ( - + - + ) }) diff --git a/packages/react/markput/src/constants.ts b/packages/react/markput/src/constants.ts deleted file mode 100644 index 7ee76d14..00000000 --- a/packages/react/markput/src/constants.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {DEFAULT_MARKUP, DEFAULT_OVERLAY_TRIGGER} from '@markput/core' - -import type {Option} from './types' - -/** - * React-specific default options for MarkedInput. - * Extends core DEFAULT_OPTIONS with framework-specific configuration: - * - Includes trigger configuration via overlay.trigger - * - Provides empty data array for overlay suggestions - * - * Architecture: - * - Core maintains framework-agnostic DEFAULT_OPTIONS (markup only) - * - React layer extends with trigger and overlay configuration - * - This keeps separation of concerns: core focuses on parsing, React on UI - */ -export const DEFAULT_OPTIONS: Option[] = [ - { - markup: DEFAULT_MARKUP, - overlay: { - trigger: DEFAULT_OVERLAY_TRIGGER, - data: [], - }, - }, -] \ No newline at end of file diff --git a/packages/react/markput/src/lib/hooks/useMark.tsx b/packages/react/markput/src/lib/hooks/useMark.tsx index 44e963b7..3c06596d 100644 --- a/packages/react/markput/src/lib/hooks/useMark.tsx +++ b/packages/react/markput/src/lib/hooks/useMark.tsx @@ -1,14 +1,10 @@ -import type {MarkToken} from '@markput/core' +import type {MarkOptions, MarkToken} from '@markput/core' import {MarkHandler} from '@markput/core' import type {RefObject} from 'react' import {useEffect, useRef, useState} from 'react' -import {useToken} from '../providers/TokenProvider' -import {useStore} from './useStore' - -export interface MarkOptions { - controlled?: boolean -} +import {useStore} from '../providers/StoreContext' +import {useToken} from '../providers/TokenContext' export const useMark = (options: MarkOptions = {}): MarkHandler => { const store = useStore() diff --git a/packages/react/markput/src/lib/hooks/useOverlay.tsx b/packages/react/markput/src/lib/hooks/useOverlay.tsx index 0b735ac4..0db2ddd4 100644 --- a/packages/react/markput/src/lib/hooks/useOverlay.tsx +++ b/packages/react/markput/src/lib/hooks/useOverlay.tsx @@ -1,10 +1,10 @@ -import type {OverlayMatch, Token} from '@markput/core' -import {Caret} from '@markput/core' +import type {OverlayMatch} from '@markput/core' +import {Caret, createMarkFromOverlay} from '@markput/core' import type {RefObject} from 'react' import {useCallback, useMemo} from 'react' import type {Option} from '../../types' -import {useStore} from './useStore' +import {useStore} from '../providers/StoreContext' export interface OverlayHandler { style: { @@ -25,19 +25,7 @@ export function useOverlay(): OverlayHandler { const close = useCallback(() => store.events.clearOverlay(), []) const select = useCallback( (value: {value: string; meta?: string}) => { - const mark: Token = { - type: 'mark', - value: value.value, - meta: value.meta, - content: '', - position: {start: match.index, end: match.index + match.span.length}, - descriptor: { - index: 0, - markup: match.option.markup, - } as any, - children: [], - nested: undefined, - } + const mark = createMarkFromOverlay(match, value.value, value.meta) store.events.select({mark, match}) store.events.clearOverlay() }, diff --git a/packages/react/markput/src/lib/hooks/useSlot.ts b/packages/react/markput/src/lib/hooks/useSlot.ts index ea48f585..cbcb16ce 100644 --- a/packages/react/markput/src/lib/hooks/useSlot.ts +++ b/packages/react/markput/src/lib/hooks/useSlot.ts @@ -1,7 +1,8 @@ +import {resolveOptionSlot} from '@markput/core' import type {ComponentType} from 'react' import type {MarkProps, Option, OverlayProps} from '../../types' -import {useStore} from './useStore' +import {useStore} from '../providers/StoreContext' export type SlotType = 'mark' | 'overlay' @@ -30,17 +31,7 @@ export function useSlot( const globalComponent = (type === 'mark' ? Mark : Overlay) as ComponentType | undefined const optionConfig = type === 'mark' ? option?.mark : option?.overlay - let props: any - - if (optionConfig !== undefined) { - if (typeof optionConfig === 'function') { - props = optionConfig(baseProps) - } else { - props = optionConfig - } - } else { - props = baseProps ?? {} - } + const props = resolveOptionSlot(optionConfig as any, baseProps ?? {}) const Component = (props.slot || globalComponent || defaultComponent) as ComponentType diff --git a/packages/react/markput/src/lib/hooks/useStore.ts b/packages/react/markput/src/lib/hooks/useStore.ts deleted file mode 100644 index 72d361e3..00000000 --- a/packages/react/markput/src/lib/hooks/useStore.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type {Store} from '@markput/core' -import {assertNonNullable} from '@markput/core' -import {useContext} from 'react' - -import {StoreContext} from '../providers/StoreContext' - -export function useStore(): Store { - const store = useContext(StoreContext) - assertNonNullable(store) - return store -} \ No newline at end of file diff --git a/packages/react/markput/src/lib/providers/StoreContext.ts b/packages/react/markput/src/lib/providers/StoreContext.ts index 0cc51ed2..573fd434 100644 --- a/packages/react/markput/src/lib/providers/StoreContext.ts +++ b/packages/react/markput/src/lib/providers/StoreContext.ts @@ -1,5 +1,13 @@ import type {Store} from '@markput/core' -import {createContext} from 'react' +import {createContext, useContext} from 'react' export const StoreContext = createContext(undefined) -StoreContext.displayName = 'StoreContext' \ No newline at end of file +StoreContext.displayName = 'StoreContext' + +export function useStore(): Store { + const store = useContext(StoreContext) + if (store === undefined) { + throw new Error('Store not found. Make sure to wrap component in StoreContext.') + } + return store +} \ No newline at end of file diff --git a/packages/react/markput/src/lib/providers/TokenContext.ts b/packages/react/markput/src/lib/providers/TokenContext.ts new file mode 100644 index 00000000..9666bba9 --- /dev/null +++ b/packages/react/markput/src/lib/providers/TokenContext.ts @@ -0,0 +1,13 @@ +import type {Token} from '@markput/core' +import {createContext, useContext} from 'react' + +export const TokenContext = createContext(undefined) +TokenContext.displayName = 'TokenProvider' + +export function useToken(): Token { + const value = useContext(TokenContext) + if (value === undefined) { + throw new Error('Token not found. Make sure to wrap component in TokenContext.Provider.') + } + return value +} \ No newline at end of file diff --git a/packages/react/markput/src/lib/providers/TokenProvider.ts b/packages/react/markput/src/lib/providers/TokenProvider.ts deleted file mode 100644 index da16ad8d..00000000 --- a/packages/react/markput/src/lib/providers/TokenProvider.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type {Token} from '@markput/core' - -import {createContext} from '../utils/createContext' - -export const [useToken, TokenProvider] = createContext('NodeProvider') \ No newline at end of file diff --git a/packages/react/markput/src/lib/utils/createContext.ts b/packages/react/markput/src/lib/utils/createContext.ts deleted file mode 100644 index f962f975..00000000 --- a/packages/react/markput/src/lib/utils/createContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {createContext as createReactContext, useContext} from 'react' - -export const createContext = (name: string) => { - const context = createReactContext(undefined) - context.displayName = name - - const useHook = () => { - const value = useContext(context) - if (value === undefined) { - throw new Error(`Context "${name}" not found. Make sure to wrap component in its Provider.`) - } - return value - } - - return [useHook, context.Provider, context] as const -} \ No newline at end of file diff --git a/packages/react/markput/src/lib/utils/resolveSlot.ts b/packages/react/markput/src/lib/utils/resolveSlot.ts deleted file mode 100644 index d14fba42..00000000 --- a/packages/react/markput/src/lib/utils/resolveSlot.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type {CoreSlotProps, CoreSlots} from '@markput/core' -import {convertDataAttrs} from '@markput/core' -import type {ElementType, HTMLAttributes} from 'react' - -export type SlotName = 'container' | 'span' - -const defaultSlots: Record = { - container: 'div', - span: 'span', -} - -export function resolveSlot(slotName: SlotName, slots: CoreSlots | undefined): ElementType { - if (slots?.[slotName]) { - return slots[slotName] as ElementType - } - - return defaultSlots[slotName] -} - -export function resolveSlotProps( - slotName: SlotName, - slotProps: CoreSlotProps | undefined -): HTMLAttributes | undefined { - const props = slotProps?.[slotName] - return props ? (convertDataAttrs(props as Record) as HTMLAttributes) : undefined -} \ No newline at end of file diff --git a/packages/react/markput/src/types.ts b/packages/react/markput/src/types.ts index c5b5560f..0c4c121e 100644 --- a/packages/react/markput/src/types.ts +++ b/packages/react/markput/src/types.ts @@ -1,4 +1,4 @@ -import type {CoreOption} from '@markput/core' +import type {CoreOption, DataAttributes} from '@markput/core' import type {ComponentType, ElementType, HTMLAttributes, ReactNode} from 'react' /** @@ -67,17 +67,7 @@ export interface Slots { span?: ElementType> } -/** - * Data attributes with automatic camelCase to kebab-case conversion - */ -export type DataAttributes = Record<`data${Capitalize}`, string | number | boolean | undefined> - -/** - * Props for each slot component - */ export interface SlotProps { - /** Props to pass to the container slot */ container?: HTMLAttributes & DataAttributes - /** Props to pass to the span slot */ span?: HTMLAttributes & DataAttributes } \ No newline at end of file diff --git a/packages/vue/markput/src/components/BlockContainer.vue b/packages/vue/markput/src/components/BlockContainer.vue index 22742133..591d4362 100644 --- a/packages/vue/markput/src/components/BlockContainer.vue +++ b/packages/vue/markput/src/components/BlockContainer.vue @@ -1,10 +1,10 @@ diff --git a/packages/vue/markput/src/components/MarkedInput.vue b/packages/vue/markput/src/components/MarkedInput.vue index a103d124..025d8882 100644 --- a/packages/vue/markput/src/components/MarkedInput.vue +++ b/packages/vue/markput/src/components/MarkedInput.vue @@ -1,9 +1,8 @@ diff --git a/packages/vue/markput/src/components/Suggestions/Suggestions.vue b/packages/vue/markput/src/components/Suggestions/Suggestions.vue index e8402927..3807028f 100644 --- a/packages/vue/markput/src/components/Suggestions/Suggestions.vue +++ b/packages/vue/markput/src/components/Suggestions/Suggestions.vue @@ -1,5 +1,5 @@